Compare commits

...

488 Commits

Author SHA1 Message Date
Frappe PR Bot
9a0b54c649 chore(release): Bumped to Version 16.6.1
## [16.6.1](https://github.com/frappe/erpnext/compare/v16.6.0...v16.6.1) (2026-02-19)

### Bug Fixes

* better permissions on make payment request ([78fc942](78fc9424d9))
2026-02-19 05:04:10 +00:00
ruthra kumar
1f78a9fa6c Merge pull request #52790 from frappe/mergify/bp/version-16/pr-52763
fix: better permissions on make payment request (backport #52763)
2026-02-19 10:32:36 +05:30
ruthra kumar
78fc9424d9 fix: better permissions on make payment request
(cherry picked from commit f36962fc58)
2026-02-19 04:32:31 +00:00
Frappe PR Bot
372b0119bb chore(release): Bumped to Version 16.6.0
# [16.6.0](https://github.com/frappe/erpnext/compare/v16.5.0...v16.6.0) (2026-02-17)

### Bug Fixes

* allow non-stock items while updating items ([e2183eb](e2183ebde9))
* allow sequence id edit in BOM if routing is not set ([6f812cc](6f812ccaf5))
* better validation for negative batch ([e46e874](e46e8741b4))
* cancel SABB if SLE cancelled from LCV ([ac90975](ac90975f43))
* consider sle for negative stock validation ([ca8f324](ca8f324b51))
* consider table multiselect in delete transaction ([38679d6](38679d6d14))
* correct typos in marketing campaign custom fields function ([531bdbc](531bdbc727))
* do not allow plant floor company and warehouse to be updated ([a039c17](a039c176c8))
* ensure layout has Bootstrap row and column (backport [#52649](https://github.com/frappe/erpnext/issues/52649)) ([#52719](https://github.com/frappe/erpnext/issues/52719)) ([53e400c](53e400cca1))
* log changes made to accounts settings ([2200b9a](2200b9aa67))
* **manufacturing:** add sales order fields in subassembly child table ([605c0db](605c0db976))
* **manufacturing:** set sales order references in subassembly child table ([f4b0e64](f4b0e646b4))
* **pos_invoice:** add correct depends on condition ([#52689](https://github.com/frappe/erpnext/issues/52689)) ([28592d0](28592d0180))
* prevent rows from being added to sub_assembly_items and mr_items ([80c98cd](80c98cdcf4))
* production plan status ([62ea18f](62ea18f1cc))
* removed lost reason detail ([3c33a19](3c33a19634))
* **selling-workspace-sidebar:** changed order of pos profile ([1820c35](1820c35880))
* standalone credit/debit notes should not fetch any serial or batch by default ([dd4e186](dd4e1867f5))
* **stock:** remove hardcoded letter_head from report ([1d444e5](1d444e53eb)), closes [#52569](https://github.com/frappe/erpnext/issues/52569)
* total weight does not update when updating items ([46b5884](46b5884420))
* wrong display_depends_on condition for item group and brand child tables ([6ae1b18](6ae1b18616))

### Features

* Negative Batch report ([8649543](8649543ae0))
* show formatted currency symbol on ledger preview ([b844afe](b844afe0ec))
2026-02-17 14:10:20 +00:00
ruthra kumar
7a46fad6e7 Merge pull request #52731 from frappe/version-16-hotfix
chore: release v16
2026-02-17 19:38:48 +05:30
rohitwaghchaure
2ccb8c839d Merge pull request #52740 from frappe/mergify/bp/version-16-hotfix/pr-52729
feat: Negative Batch report (backport #52729)
2026-02-17 17:23:50 +05:30
Rohit Waghchaure
8649543ae0 feat: Negative Batch report
(cherry picked from commit 34edbed00b)
2026-02-17 11:04:33 +00:00
Mihir Kandoi
bed2c83272 Merge pull request #52736 from frappe/mergify/bp/version-16-hotfix/pr-52733
fix: allow sequence ID edit in BOM if routing is not set (backport #52733)
2026-02-17 16:28:39 +05:30
Mihir Kandoi
5461945d00 Merge pull request #52738 from frappe/mergify/bp/version-16-hotfix/pr-52677
fix: standalone credit/debit notes should not fetch any serial or bat… (backport #52677)
2026-02-17 16:25:36 +05:30
Mihir Kandoi
dd4e1867f5 fix: standalone credit/debit notes should not fetch any serial or batch by default
(cherry picked from commit 2017edca88)
2026-02-17 10:40:32 +00:00
Mihir Kandoi
6f812ccaf5 fix: allow sequence id edit in BOM if routing is not set
(cherry picked from commit 08529964b4)
2026-02-17 10:36:17 +00:00
ruthra kumar
6c4bba3992 Merge pull request #52727 from frappe/mergify/bp/version-16-hotfix/pr-52679
fix: log changes made to accounts settings (backport #52679)
2026-02-17 14:15:25 +05:30
ruthra kumar
eda479a917 Merge pull request #52725 from frappe/mergify/bp/version-16-hotfix/pr-52648
fix: correct typos in marketing campaign custom fields function (backport #52648)
2026-02-17 13:59:19 +05:30
AarDG10
2200b9aa67 fix: log changes made to accounts settings
(cherry picked from commit 45febbabd7)
2026-02-17 08:25:47 +00:00
Mihir Kandoi
af15050acc Merge pull request #52723 from frappe/mergify/bp/version-16-hotfix/pr-52720
fix: wrong display_depends_on condition for item group and brand chil… (backport #52720)
2026-02-17 13:43:00 +05:30
Abdeali Chharchhoda
531bdbc727 fix: correct typos in marketing campaign custom fields function
(cherry picked from commit 6b7fed7f59)
2026-02-17 08:12:00 +00:00
ruthra kumar
53e400cca1 fix: ensure layout has Bootstrap row and column (backport #52649) (#52719)
Merge pull request #52649 from aerele/fix-sales-funnel-layout

fix: ensure layout has Bootstrap row and column
(cherry picked from commit ae0be7f6ce)

Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
2026-02-17 13:04:21 +05:30
Shllokkk
6ae1b18616 fix: wrong display_depends_on condition for item group and brand child tables
(cherry picked from commit de2843d9f1)
2026-02-17 07:30:21 +00:00
Mihir Kandoi
a8f21dbc07 Merge pull request #52718 from frappe/mergify/bp/version-16-hotfix/pr-52716
fix: do not allow plant floor company and warehouse to be updated (backport #52716)
2026-02-17 12:32:45 +05:30
Vishnu Priya Baskaran
1dd471fb18 Merge pull request #52649 from aerele/fix-sales-funnel-layout
fix: ensure layout has Bootstrap row and column
(cherry picked from commit ae0be7f6ce)
2026-02-17 06:55:51 +00:00
Mihir Kandoi
a039c176c8 fix: do not allow plant floor company and warehouse to be updated
(cherry picked from commit fd72132743)
2026-02-17 06:42:33 +00:00
Mihir Kandoi
cf141045ba Merge pull request #52715 from frappe/mergify/bp/version-16-hotfix/pr-52713
fix: production plan status (backport #52713)
2026-02-17 11:41:43 +05:30
Mihir Kandoi
62ea18f1cc fix: production plan status
(cherry picked from commit b3e6b304e4)
2026-02-17 05:54:10 +00:00
Mihir Kandoi
302ae382f1 Merge pull request #52697 from frappe/mergify/bp/version-16-hotfix/pr-52678
chore: do not show stock details if update stock is disabled (backport #52678)
2026-02-17 10:55:49 +05:30
Mihir Kandoi
98ff54a871 chore: resolve conflicts 2026-02-17 10:40:34 +05:30
rohitwaghchaure
41b089ab51 Merge pull request #52705 from frappe/mergify/bp/version-16-hotfix/pr-52620
fix: prevent rows from being added to sub_assembly_items and mr_items (backport #52620)
2026-02-17 10:16:09 +05:30
Diptanil Saha
46acd328a9 Merge pull request #52707 from frappe/mergify/bp/version-16-hotfix/pr-52706
fix(selling-workspace-sidebar): changed order of pos profile (backport #52706)
2026-02-17 00:55:44 +05:30
diptanilsaha
1820c35880 fix(selling-workspace-sidebar): changed order of pos profile
(cherry picked from commit 72f4fd08ee)
2026-02-16 19:11:27 +00:00
Shllokkk
80c98cdcf4 fix: prevent rows from being added to sub_assembly_items and mr_items
(cherry picked from commit 25f979a825)
2026-02-16 18:43:01 +00:00
Mihir Kandoi
4a8363e7da Merge pull request #52703 from frappe/mergify/bp/version-16-hotfix/pr-52626
fix(manufacturing): add sales order fields in subassembly child table (backport #52626)
2026-02-16 23:58:13 +05:30
rohitwaghchaure
dcbc1e1303 Merge pull request #52701 from frappe/mergify/bp/version-16-hotfix/pr-52699
fix: consider sle for negative stock validation (backport #52699)
2026-02-16 23:56:30 +05:30
Sudharsanan11
ecfd193002 test(manufacturing): add test to validate the sales order references for sub assembly items
(cherry picked from commit 341dc4be7a)
2026-02-16 18:11:53 +00:00
Sudharsanan11
f4b0e646b4 fix(manufacturing): set sales order references in subassembly child table
(cherry picked from commit 0f2ed28ab7)
2026-02-16 18:11:52 +00:00
Sudharsanan11
605c0db976 fix(manufacturing): add sales order fields in subassembly child table
(cherry picked from commit c2282eaf08)
2026-02-16 18:11:52 +00:00
Rohit Waghchaure
ca8f324b51 fix: consider sle for negative stock validation
(cherry picked from commit 38f35acffe)
2026-02-16 17:56:51 +00:00
Mihir Kandoi
7677b2f573 chore: do not show serial batch selector if not needed
(cherry picked from commit cdc62e7327)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
2026-02-16 17:01:48 +00:00
Mihir Kandoi
f08964683a chore: do not show stock details if update stock is disabled
(cherry picked from commit 4499e974a0)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
2026-02-16 17:01:48 +00:00
rohitwaghchaure
d2eabcbf74 Merge pull request #52696 from frappe/mergify/bp/version-16-hotfix/pr-52691
fix: cancel SABB if SLE cancelled from LCV (backport #52691)
2026-02-16 21:52:33 +05:30
Diptanil Saha
7d8bbac5fd Merge pull request #52694 from frappe/mergify/bp/version-16-hotfix/pr-52689
fix(pos_invoice): add correct depends on condition (backport #52689)
2026-02-16 21:23:24 +05:30
Rohit Waghchaure
ac90975f43 fix: cancel SABB if SLE cancelled from LCV
(cherry picked from commit f23a49a25e)
2026-02-16 15:42:23 +00:00
Soham Kulkarni
28592d0180 fix(pos_invoice): add correct depends on condition (#52689)
* fix(pos_invoice): add correct depends on condition

* fix: show field in sales order

* refactor: eval condition

(cherry picked from commit 219cf6bc57)
2026-02-16 15:37:25 +00:00
rohitwaghchaure
de5e8a6e6b Merge pull request #52684 from frappe/mergify/bp/version-16-hotfix/pr-52681
fix: better validation for negative batch (backport #52681)
2026-02-16 16:02:55 +05:30
Rohit Waghchaure
e46e8741b4 fix: better validation for negative batch
(cherry picked from commit a8636e4f59)
2026-02-16 09:47:05 +00:00
MochaMind
4a4e9956e2 chore: update POT file (#52674) 2026-02-15 14:00:35 +01:00
Mihir Kandoi
8ec83f2080 Merge pull request #52672 from frappe/mergify/bp/version-16-hotfix/pr-52670 2026-02-15 14:30:52 +05:30
Mihir Kandoi
46b5884420 fix: total weight does not update when updating items
(cherry picked from commit 63323a2611)
2026-02-15 08:45:19 +00:00
Mihir Kandoi
3548073a07 Merge pull request #52669 from frappe/mergify/bp/version-16-hotfix/pr-52658
fix: allow non-stock items while updating items (backport #52658)
2026-02-15 13:07:37 +05:30
ervishnucs
e2183ebde9 fix: allow non-stock items while updating items
(cherry picked from commit 07db5941aa)
2026-02-15 07:28:48 +00:00
Mihir Kandoi
37f740caa6 Merge pull request #52668 from frappe/revert-52578-mergify/bp/version-16-hotfix/pr-51594
Revert "fix: Workspace sidebar links for Debit/Credit Notes (backport #51594)"
2026-02-15 12:44:37 +05:30
Mihir Kandoi
485c1b025a Revert "fix: Workspace sidebar links for Debit/Credit Notes (backport #51594)" 2026-02-15 12:29:28 +05:30
ruthra kumar
880cc50ae4 Merge pull request #52659 from frappe/mergify/bp/version-16-hotfix/pr-52643
fix: consider table multiselect in delete transaction (backport #52643)
2026-02-13 15:12:54 +05:30
SowmyaArunachalam
3c33a19634 fix: removed lost reason detail
(cherry picked from commit 9bb60405e7)

# Conflicts:
#	erpnext/patches.txt
2026-02-13 14:11:58 +05:30
SowmyaArunachalam
38679d6d14 fix: consider table multiselect in delete transaction
(cherry picked from commit be3d2422a7)
2026-02-13 08:37:46 +00:00
ruthra kumar
3744c32950 Merge pull request #52646 from frappe/mergify/bp/version-16-hotfix/pr-52644
refactor: use query builder for profitability analysis (backport #52644)
2026-02-12 14:40:46 +05:30
ruthra kumar
543e0131b5 refactor: use query builder for profitability analysis
(cherry picked from commit 5e34325604)
2026-02-12 08:55:03 +00:00
ruthra kumar
eb5ffcdc88 Merge pull request #52642 from frappe/mergify/bp/version-16-hotfix/pr-52640
refactor: use query builder for sales person commission summary (backport #52640)
2026-02-12 12:53:24 +05:30
ruthra kumar
f98e53692e refactor: use query builder for sales person commission summary
(cherry picked from commit 7105e3fb69)
2026-02-12 07:07:42 +00:00
ruthra kumar
e778eabcb0 Merge pull request #52639 from frappe/mergify/bp/version-16-hotfix/pr-52571
fix(stock): remove hardcoded letter_head from report (backport #52571)
2026-02-12 12:04:57 +05:30
Roxxane
1d444e53eb fix(stock): remove hardcoded letter_head from report
The 'Incorrect Serial and Batch Bundle' report had a hardcoded
letter_head value of 'Test', preventing users from deleting a
Letter Head named 'Test' due to link check.

Standard reports should not reference specific Letter Head names.

Fixes #52569

(cherry picked from commit 99cd29d88f)
2026-02-12 06:19:37 +00:00
ruthra kumar
3bf9aff67e Merge pull request #52638 from frappe/mergify/bp/version-16-hotfix/pr-52619
feat: show formatted currency symbol on ledger preview (backport #52619)
2026-02-12 11:48:49 +05:30
Navin-S-R
b844afe0ec feat: show formatted currency symbol on ledger preview
(cherry picked from commit 5c8cb1e7ec)
2026-02-12 05:59:06 +00:00
Frappe PR Bot
6e7e219f71 chore(release): Bumped to Version 16.5.0
# [16.5.0](https://github.com/frappe/erpnext/compare/v16.4.1...v16.5.0) (2026-02-11)

### Bug Fixes

* Added a missing option to the currency field (backport [#52528](https://github.com/frappe/erpnext/issues/52528)) ([#52587](https://github.com/frappe/erpnext/issues/52587)) ([1c3fe00](1c3fe000ba))
* Added validation for quality inspection in job card ([a1ec68c](a1ec68cd1e))
* apply composite asset logic only in draft ([d2387a3](d2387a3af8))
* **balance sheet:** removed the extra labels from the chart ([6a9a28b](6a9a28b4ae))
* **buying:** add supplier group link filters in field level ([9886b46](9886b46cb4))
* correctly calculate running balances for financial report ([7df18af](7df18af799))
* do not show update stock flag unneccessarily ([c0c6cc5](c0c6cc58ed))
* email campaign timeout issue (backport [#51994](https://github.com/frappe/erpnext/issues/51994)) ([#52556](https://github.com/frappe/erpnext/issues/52556)) ([e753df8](e753df8ff0))
* enabling skip delivery option for order type maintenance ([7a78e97](7a78e9705c))
* **gross profit report:** translate column Sales Invoice ([6ff8820](6ff8820732))
* **gross-profit:** handle item group filters ([0d02bbb](0d02bbb01a))
* **gross-profit:** handle returns outside sale period ([9b32c84](9b32c84462))
* handle gross profit and percentage for return invoices ([d081a26](d081a26608))
* item code is tuple with operation id ([9d14c0b](9d14c0b60e))
* **manufacturing:** fix chart period keys ([b99ca48](b99ca486d7))
* **manufacturing:** handle None value for actual_end_date ([b1b75ec](b1b75eca3d))
* **map_current_doc:** prevent mutation of query args in get_query (backport [#52202](https://github.com/frappe/erpnext/issues/52202)) ([#52584](https://github.com/frappe/erpnext/issues/52584)) ([b8256e5](b8256e5f31))
* move company field to first position in sales invoice, purchase invoice, sales order, purchase order and journal entry ([e53ccd0](e53ccd0745))
* not able to complete job card ([c5ff534](c5ff534d58))
* operation status and bom validation ([1122265](11222653ce))
* Period Closing Voucher doesn't exist for GL Entry ([921584c](921584c769))
* process loss error incorrectly thrown even when semi FG BOM does not have any process loss ([4a7ffce](4a7ffce320))
* **quotation:** ignore zero ordered_qty ([cf7c127](cf7c127dc6))
* rate comparison in stock reco ([1f78f45](1f78f45aee))
* remove customer_pos_id reference ([#52396](https://github.com/frappe/erpnext/issues/52396)) ([ab59f73](ab59f73064))
* remove incorrect validation from email digest throwing spurious error (backport [#51827](https://github.com/frappe/erpnext/issues/51827)) ([#52579](https://github.com/frappe/erpnext/issues/52579)) ([dffb6ac](dffb6ac4cf))
* return None instead of 0 if valuation rate is falsy ([64a7956](64a7956a4a))
* stock balance report issue ([62616ad](62616ad9e1))
* stock reservation created against job card ([305483e](305483e074))
* **stock:** add is group filter for warehouse fields ([23a26b5](23a26b540b))
* **stock:** ignore pos reserved batches for stock levels ([e2c1204](e2c12043ae))
* **stock:** inward stock for pick list test record ([801a26a](801a26ae67))
* **stock:** set source warehouse for issue type ([53e512c](53e512ceaf))
* **stock:** update target field attribute ([96dfecf](96dfecf0d5))
* test cases ([ece8d00](ece8d00415))
* validate asset movement transaction date ([#52340](https://github.com/frappe/erpnext/issues/52340)) ([898d2e3](898d2e3c9a))
* Workspace sidebar links for Debit/Credit Notes ([29d33b3](29d33b3139))

### Features

* **accounts:** expand Journal Entry Template to support dimensions and party ([#51621](https://github.com/frappe/erpnext/issues/51621)) ([d06a46a](d06a46ae85))
* allow negative stock for the batch item ([b6afe7f](b6afe7f4da))

### Reverts

* "fix: allow sales invoice to be renamed" ([abef910](abef9109b0))
2026-02-11 06:03:37 +00:00
ruthra kumar
ce225d87f3 Merge pull request #52597 from frappe/version-16-hotfix
chore: release v16
2026-02-11 11:32:00 +05:30
ruthra kumar
0d831aad41 Merge pull request #52605 from frappe/mergify/bp/version-16-hotfix/pr-52593
refactor: update labels for tax withholding reports columns to improve clarity (backport #52593)
2026-02-10 18:30:52 +05:30
ruthra kumar
00caebe90c Merge pull request #52397 from frappe/mergify/bp/version-16-hotfix/pr-52396
fix: remove customer_pos_id reference (backport #52396)
2026-02-10 18:28:49 +05:30
ruthra kumar
0ee97ffbbf Merge pull request #52414 from frappe/mergify/bp/version-16-hotfix/pr-51745
fix(gross profit report): translate column Sales Invoice (backport #51745)
2026-02-10 18:27:54 +05:30
ruthra kumar
4a6c428848 Merge pull request #52604 from frappe/mergify/bp/version-16-hotfix/pr-52017
fix(gross-profit): handle returns outside the given sale period (backport #52017)
2026-02-10 18:27:38 +05:30
Kavin
3e3daa50b1 Merge pull request #52542 from frappe/mergify/bp/version-16-hotfix/pr-52516
fix(stock): ignore pos reserved batches for stock levels (backport #52516)
2026-02-10 18:21:37 +05:30
ljain112
ac7a25fff9 refactor: update labels for tax withholding reports columns to improve clarity
(cherry picked from commit 2cfdcc1af4)
2026-02-10 12:43:06 +00:00
Navin-S-R
0d02bbb01a fix(gross-profit): handle item group filters
(cherry picked from commit 047b278791)
2026-02-10 12:41:30 +00:00
Navin-S-R
d01ea27f2f test: fix test assertions to use index-based totals
(cherry picked from commit fdfa7bc963)
2026-02-10 12:41:29 +00:00
Navin-S-R
345d25bdf1 test: validate sales person wise gross profit
(cherry picked from commit 3ab978ab46)
2026-02-10 12:41:29 +00:00
Navin-S-R
e8eaae4120 test: validate return invoice profit and profit percentage
(cherry picked from commit 4da3d43013)
2026-02-10 12:41:29 +00:00
Navin-S-R
d081a26608 fix: handle gross profit and percentage for return invoices
(cherry picked from commit 51709f032f)
2026-02-10 12:41:29 +00:00
Navin-S-R
9b32c84462 fix(gross-profit): handle returns outside sale period
(cherry picked from commit 67d8223f73)
2026-02-10 12:41:28 +00:00
Diptanil Saha
22c9b26a24 Merge pull request #52450 from frappe/mergify/bp/version-16-hotfix/pr-52360
fix: correctly calculate running balances for financial report (backport #52360)
2026-02-10 02:37:22 +05:30
mergify[bot]
1c3fe000ba fix: Added a missing option to the currency field (backport #52528) (#52587)
fix: Added a missing option to the currency field (#52528)

(cherry picked from commit da07f84e44)

Co-authored-by: El-Shafei H. <el.shafei.developer@gmail.com>
2026-02-09 21:01:04 +00:00
mergify[bot]
ffd9b248f6 refactor: drop usages of db_query (backport #52559) (#52565)
refactor: drop usages of db_query


(cherry picked from commit 1e45195ef9)

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
Co-authored-by: Akhil Narang <me@akhilnarang.dev>
2026-02-10 01:47:17 +05:30
mergify[bot]
b8256e5f31 fix(map_current_doc): prevent mutation of query args in get_query (backport #52202) (#52584)
fix(map_current_doc): prevent mutation of query args in get_query (#52202)

(cherry picked from commit 23a73c9cdb)

Co-authored-by: V Shankar <shankarv292002@gmail.com>
2026-02-10 01:26:00 +05:30
Trusted Computer
dffb6ac4cf fix: remove incorrect validation from email digest throwing spurious error (backport #51827) (#52579) 2026-02-09 18:26:57 +00:00
mergify[bot]
e753df8ff0 fix: email campaign timeout issue (backport #51994) (#52556)
fix: email campaign timeout issue (#51994)

* fix: email campaign timeout issue

* refactor: email campaign backend logic

* refactor: use sendmail instead of manually batching

(cherry picked from commit 22123dd955)

Co-authored-by: Pratik Badhe <badhepd@gmail.com>
2026-02-09 23:45:02 +05:30
Mihir Kandoi
560fd2e0d1 Merge pull request #52581 from frappe/mergify/bp/version-16-hotfix/pr-52527
fix(stock): correct warehouse mapping for material issue (backport #52527)
2026-02-09 21:22:27 +05:30
Mihir Kandoi
327719a0fd Merge pull request #52578 from frappe/mergify/bp/version-16-hotfix/pr-51594
fix: Workspace sidebar links for Debit/Credit Notes (backport #51594)
2026-02-09 21:19:00 +05:30
Pandiyan37
801a26ae67 fix(stock): inward stock for pick list test record
(cherry picked from commit f22b9e297b)
2026-02-09 15:36:44 +00:00
Pandiyan37
8c4a9040b7 test(stock): add test to check from warehouse for issue type
(cherry picked from commit da0322e994)
2026-02-09 15:36:44 +00:00
Pandiyan37
53e512ceaf fix(stock): set source warehouse for issue type
(cherry picked from commit a34e8c99cd)
2026-02-09 15:36:43 +00:00
Mihir Kandoi
e9212c6a32 chore: resolve conflicts 2026-02-09 21:03:37 +05:30
Nabin Hait
29d33b3139 fix: Workspace sidebar links for Debit/Credit Notes
(cherry picked from commit 8acf373e68)

# Conflicts:
#	erpnext/workspace_sidebar/invoicing.json
2026-02-09 15:32:25 +00:00
rohitwaghchaure
7e0dc2ff86 Merge pull request #52561 from frappe/mergify/bp/version-16-hotfix/pr-52340
fix: validate asset movement transaction date (backport #52340)
2026-02-09 19:52:15 +05:30
Poojashree T R
898d2e3c9a fix: validate asset movement transaction date (#52340)
* fix: validate asset transaction date

* fix: validate asset transaction date

* fix: add translation in validate_transaction_date

* test: test_movement_transaction_date

* fix: to ensure test reliability

(cherry picked from commit e98b68c38f)
2026-02-09 11:52:19 +00:00
rohitwaghchaure
4c562b2903 Merge pull request #52558 from frappe/mergify/bp/version-16-hotfix/pr-52550
feat: allow negative stock for the batch item (backport #52550)
2026-02-09 16:40:12 +05:30
Mihir Kandoi
04d6273713 Merge pull request #52554 from frappe/mergify/bp/version-16-hotfix/pr-52501
fix(quotation): ignore zero ordered_qty (backport #52501)
2026-02-09 16:25:45 +05:30
Rohit Waghchaure
b6afe7f4da feat: allow negative stock for the batch item
(cherry picked from commit 376ab0e346)
2026-02-09 10:50:46 +00:00
ravibharathi656
cf7c127dc6 fix(quotation): ignore zero ordered_qty
(cherry picked from commit 32ea37035e)
2026-02-09 10:39:04 +00:00
Sudharsanan11
59f6012c57 test(stock): add test to ignore pos reserved batches for stock levels
(cherry picked from commit 47ac67f7a2)
2026-02-09 06:28:58 +00:00
Sudharsanan11
e2c12043ae fix(stock): ignore pos reserved batches for stock levels
(cherry picked from commit 277ba9cb79)
2026-02-09 06:28:58 +00:00
Mihir Kandoi
3e0a7f2400 Merge pull request #52539 from frappe/mergify/bp/version-16-hotfix/pr-52538
revert: "fix: allow sales invoice to be renamed" (backport #52538)
2026-02-09 10:47:42 +05:30
Mihir Kandoi
abef9109b0 revert: "fix: allow sales invoice to be renamed"
This reverts commit 95fdbe55f9.

(cherry picked from commit 2660907ac8)
2026-02-09 05:00:36 +00:00
MochaMind
9766361c07 chore: update POT file (#52530) 2026-02-08 17:49:45 +01:00
Mihir Kandoi
6bcd311214 Merge pull request #52524 from frappe/mergify/bp/version-16-hotfix/pr-52497
fix: add is_group filter for supplier_group and warehouse fields (backport #52497)
2026-02-07 21:58:58 +05:30
Mihir Kandoi
dd45bb5664 chore: resolve conflicts 2026-02-07 21:44:42 +05:30
Sudharsanan11
23a26b540b fix(stock): add is group filter for warehouse fields
(cherry picked from commit a9829f5f7b)
2026-02-07 16:11:32 +00:00
Sudharsanan11
9886b46cb4 fix(buying): add supplier group link filters in field level
(cherry picked from commit cfdc554a19)

# Conflicts:
#	erpnext/buying/doctype/supplier/supplier.json
2026-02-07 16:11:32 +00:00
Mihir Kandoi
32d5cedafc Merge pull request #52486 from frappe/mergify/bp/version-16-hotfix/pr-52000
fix: Added validation for quality inspection in job card (backport #52000)
2026-02-06 14:49:10 +05:30
Nishka Gosalia
ece8d00415 fix: test cases 2026-02-06 14:34:08 +05:30
Khushi Rawat
c171b9a184 Merge pull request #52492 from frappe/mergify/bp/version-16-hotfix/pr-52491
fix: apply composite asset logic only in draft (backport #52491)
2026-02-06 13:10:24 +05:30
khushi8112
d2387a3af8 fix: apply composite asset logic only in draft
(cherry picked from commit ee501e884a)
2026-02-06 07:36:31 +00:00
rohitwaghchaure
79e0e07446 Merge pull request #52482 from frappe/mergify/bp/version-16-hotfix/pr-52476
fix: stock reservation created against job card (backport #52476)
2026-02-06 12:52:43 +05:30
Mihir Kandoi
4caca08b90 Merge pull request #52488 from frappe/mergify/bp/version-16-hotfix/pr-52219
fix: enabling skip delivery option for order type maintenance (backport #52219)
2026-02-06 12:44:47 +05:30
Mihir Kandoi
738cb6847e Merge pull request #52484 from frappe/mergify/bp/version-16-hotfix/pr-52475
fix: do not show update stock flag unneccessarily (backport #52475)
2026-02-06 12:28:36 +05:30
Nishka Gosalia
7a78e9705c fix: enabling skip delivery option for order type maintenance
(cherry picked from commit 1a22e3cb61)
2026-02-06 06:40:03 +00:00
Nishka Gosalia
a1ec68cd1e fix: Added validation for quality inspection in job card
(cherry picked from commit 46b4cf3add)
2026-02-06 06:38:32 +00:00
Mihir Kandoi
4847a76cb8 chore: resolve conflicts 2026-02-06 12:00:47 +05:30
Mihir Kandoi
f56c6f93a1 chore: resolve conflicts 2026-02-06 12:00:23 +05:30
Mihir Kandoi
c0c6cc58ed fix: do not show update stock flag unneccessarily
(cherry picked from commit 5fb5b7b30e)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json
2026-02-06 06:27:46 +00:00
rohitwaghchaure
8c372faf50 chore: fix linter issue
Removed duplicate import of get_bom_items_as_dict from BOM module.
2026-02-06 11:29:45 +05:30
Diptanil Saha
ae4cf27f4f Merge pull request #52471 from frappe/mergify/bp/version-16-hotfix/pr-51621
feat(accounts): expand Journal Entry Template to support dimensions and party (backport #51621)
2026-02-06 11:07:27 +05:30
Rohit Waghchaure
305483e074 fix: stock reservation created against job card
(cherry picked from commit dca2cfd009)
2026-02-05 23:05:19 +00:00
rohitwaghchaure
86dd2e786c Merge pull request #52474 from frappe/mergify/bp/version-16-hotfix/pr-52467
fix: operation status and bom validation (backport #52467)
2026-02-05 21:06:23 +05:30
Rohit Waghchaure
11222653ce fix: operation status and bom validation
(cherry picked from commit 95baf953a8)
2026-02-05 14:16:38 +00:00
Nikhil Kothari
d06a46ae85 feat(accounts): expand Journal Entry Template to support dimensions and party (#51621)
* feat(accounts): expand Journal Entry Template to support dimensions and party

* fix: do not update standard row values

(cherry picked from commit ef44528ba5)
2026-02-05 12:55:13 +00:00
rohitwaghchaure
8c536df5f2 Merge pull request #52428 from frappe/mergify/bp/version-16-hotfix/pr-52422
fix: not able to complete job card (backport #52422)
2026-02-05 17:05:16 +05:30
Frappe PR Bot
d74a649016 chore(release): Bumped to Version 16.4.1
## [16.4.1](https://github.com/frappe/erpnext/compare/v16.4.0...v16.4.1) (2026-02-05)

### Bug Fixes

* stock balance report issue ([e009319](e0093199b1))
2026-02-05 10:52:48 +00:00
rohitwaghchaure
bc9928b32b Merge pull request #52464 from frappe/mergify/bp/version-16/pr-52461
fix: stock balance report issue (backport #52459) (backport #52461)
2026-02-05 16:20:53 +05:30
rohitwaghchaure
4305415ed9 chore: fix issue
(cherry picked from commit 1d24abf5dd)
2026-02-05 10:20:46 +00:00
Rohit Waghchaure
e0093199b1 fix: stock balance report issue
(cherry picked from commit 7e584dd84a)
(cherry picked from commit 62616ad9e1)
2026-02-05 10:20:45 +00:00
rohitwaghchaure
4dec567421 Merge pull request #52461 from frappe/mergify/bp/version-16-hotfix/pr-52459
fix: stock balance report issue (backport #52459)
2026-02-05 15:49:58 +05:30
rohitwaghchaure
1d24abf5dd chore: fix issue 2026-02-05 15:02:47 +05:30
Rohit Waghchaure
62616ad9e1 fix: stock balance report issue
(cherry picked from commit 7e584dd84a)
2026-02-05 09:20:01 +00:00
Jatin3128
6bffdbce56 Merge pull request #52455 from frappe/mergify/bp/version-16-hotfix/pr-52438
fix(balance sheet): removed the extra labels from the chart (backport #52438)
2026-02-05 13:39:41 +05:30
Jatin3128
6a9a28b4ae fix(balance sheet): removed the extra labels from the chart
(cherry picked from commit a64b5f2c5d)
2026-02-05 07:54:14 +00:00
Mihir Kandoi
66d0ab6380 Merge pull request #52454 from frappe/mergify/bp/version-16-hotfix/pr-52452
fix: process loss error incorrectly thrown even when semi FG BOM does… (backport #52452)
2026-02-05 13:15:53 +05:30
ruthra kumar
5913d5f14e Merge pull request #52351 from frappe/mergify/bp/version-16-hotfix/pr-52346
fix: move company field to first position in sales invoice, purchase … (backport #52346)
2026-02-05 13:11:28 +05:30
Mihir Kandoi
4a7ffce320 fix: process loss error incorrectly thrown even when semi FG BOM does not have any process loss
(cherry picked from commit 99ddc36c26)
2026-02-05 07:27:41 +00:00
Smit Vora
2994ba1b41 test: further tests for query builder
(cherry picked from commit 12f8bb2937)
2026-02-05 06:53:30 +00:00
Smit Vora
55eb631116 test: correct error message
(cherry picked from commit a29710dc07)
2026-02-05 06:53:30 +00:00
Smit Vora
77693b12a4 test: revert original pcv setting
(cherry picked from commit f45a5a63a7)
2026-02-05 06:53:29 +00:00
Smit Vora
8caf609f8d test: add tests for query builder
(cherry picked from commit 61d8308e81)
2026-02-05 06:53:29 +00:00
Smit Vora
921584c769 fix: Period Closing Voucher doesn't exist for GL Entry
(cherry picked from commit b41c1858a3)
2026-02-05 06:53:29 +00:00
Smit Vora
7df18af799 fix: correctly calculate running balances for financial report
(cherry picked from commit ee2f8d8ebc)
2026-02-05 06:53:29 +00:00
Mihir Kandoi
64d82a811f Merge pull request #52446 from frappe/mergify/bp/version-16-hotfix/pr-52445
fix: item code is tuple with operation id (backport #52445)
2026-02-05 12:12:35 +05:30
Mihir Kandoi
9d14c0b60e fix: item code is tuple with operation id
(cherry picked from commit 481deee4b2)
2026-02-05 06:29:11 +00:00
ruthra kumar
0a285523a8 Merge pull request #52426 from frappe/mergify/bp/version-16-hotfix/pr-51990
refactor: use https over http while saving website link (backport #51990)
2026-02-05 11:30:16 +05:30
ruthra kumar
ce695ebdd0 refactor: patch partner_website for old data
(cherry picked from commit 8db29b0a81)

# Conflicts:
#	erpnext/patches.txt
2026-02-05 11:00:58 +05:30
Mihir Kandoi
9f31910226 Merge pull request #52441 from frappe/mergify/bp/version-16-hotfix/pr-52416
fix(stock): update target field attribute (backport #52416)
2026-02-05 10:40:29 +05:30
Pandiyan37
f8405e4ca4 test(stock): testcase for different inventory dimension
(cherry picked from commit 21d0ee8db1)
2026-02-05 04:54:39 +00:00
Pandiyan37
96dfecf0d5 fix(stock): update target field attribute
(cherry picked from commit 7e08154217)
2026-02-05 04:54:38 +00:00
Mihir Kandoi
b5b8032ce0 Merge pull request #52430 from frappe/mergify/bp/version-16-hotfix/pr-52427 2026-02-04 20:17:43 +05:30
archielister
5298e26a11 fix for obtaining bom_no
(cherry picked from commit e4df0a393a)
2026-02-04 14:33:24 +00:00
Rohit Waghchaure
c5ff534d58 fix: not able to complete job card
(cherry picked from commit 175fe9279c)
2026-02-04 13:13:27 +00:00
Mihir Kandoi
1dc44691db Merge pull request #52421 from frappe/mergify/bp/version-16-hotfix/pr-51773
fix(manufacturing): refactor production analytics report (backport #51773)
2026-02-04 18:06:40 +05:30
ruthra kumar
91043de352 refactor: scrub http and use https in sales partner
(cherry picked from commit 8cf31548f2)
2026-02-04 12:32:42 +00:00
Sudharsanan11
b99ca486d7 fix(manufacturing): fix chart period keys
(cherry picked from commit 27091e5168)
2026-02-04 17:51:53 +05:30
Sudharsanan11
b1b75eca3d fix(manufacturing): handle None value for actual_end_date
(cherry picked from commit 16f09141da)
2026-02-04 17:51:53 +05:30
Mihir Kandoi
33305550b7 Merge pull request #52408 from frappe/mergify/bp/version-16-hotfix/pr-52383
fix: rate comparison in stock reco (backport #52383)
2026-02-04 17:42:06 +05:30
Mihir Kandoi
e86ab97b7e chore: add line in the end so linter check passes 2026-02-04 17:26:01 +05:30
elshafei-developer
6ff8820732 fix(gross profit report): translate column Sales Invoice
(cherry picked from commit 3e39d13172)
2026-02-04 09:18:31 +00:00
Mihir Kandoi
64a7956a4a fix: return None instead of 0 if valuation rate is falsy
(cherry picked from commit e8d1e9d946)
2026-02-04 06:48:33 +00:00
Mihir Kandoi
1f78f45aee fix: rate comparison in stock reco
(cherry picked from commit f1b4fe12a2)
2026-02-04 06:48:32 +00:00
Diptanil Saha
ab59f73064 fix: remove customer_pos_id reference (#52396)
(cherry picked from commit 036f64013d)
2026-02-04 05:18:56 +00:00
Frappe PR Bot
0312d58dca chore(release): Bumped to Version 16.4.0
# [16.4.0](https://github.com/frappe/erpnext/compare/v16.3.0...v16.4.0) (2026-02-04)

### Bug Fixes

* add precision to rejected batch no qty calculation ([b365444](b365444027))
* allow sales invoice to be renamed ([f7b915d](f7b915dfe6))
* **barcode:** failing request when item has both batch and serial ([e4cdd97](e4cdd971c8))
* batch selector not working if Use Legacy (Client side) Reactivity disabled ([2a3642b](2a3642b55a))
* better fix for aac39b2671 ([b8ab55f](b8ab55fee8))
* correct exchange gain loss in ppr ([e09406d](e09406d085))
* correct Sales Tax Template sidebar link to proper DocType ([4c14e74](4c14e74a12))
* correct spelling of Payment Reconciliation in Accounting ([c80b554](c80b554cd7))
* **credit-note:** set incoming rate as zero for expired batch ([0f9bf08](0f9bf08685))
* **demo:** removed toolbar eventlistener (backport [#52171](https://github.com/frappe/erpnext/issues/52171)) ([#52172](https://github.com/frappe/erpnext/issues/52172)) ([6608601](66086010fc))
* duplicate account number (Indonesia COA) (backport [#52080](https://github.com/frappe/erpnext/issues/52080)) ([#52317](https://github.com/frappe/erpnext/issues/52317)) ([81e6575](81e65757ee))
* failing test cases ([136b2cf](136b2cfba5))
* group item wise tax details by tax row ([45e4c04](45e4c04830))
* hide close button on WO if WO is completed ([0d1c30f](0d1c30f3f0))
* hide item_wise_tax_details table from print ([c619be9](c619be989b))
* include credit notes in project gross margin calculation ([e755a4a](e755a4ad98))
* item code not showing in report view ([af167f9](af167f91fe))
* journal auditing voucher print date to use posting_date ([7e5eab2](7e5eab261c))
* **journal-entry:** normalize exchange rate to float ([3f0032d](3f0032d793))
* js error if user does not have write permission for date field ([3944dfd](3944dfde31))
* lead time calculation for FG item ([5595602](5595602f24))
* make item name editable in RFQ ([dbe5846](dbe5846908))
* merge taxes in purchase receipt when get items from multiple purchase invoices ([#51422](https://github.com/frappe/erpnext/issues/51422)) ([d80c8d1](d80c8d14b0))
* missing depr_series causing error on jv creation (backport [#52085](https://github.com/frappe/erpnext/issues/52085)) ([#52206](https://github.com/frappe/erpnext/issues/52206)) ([78c4f01](78c4f01733))
* negative stock for purchase return ([220a528](220a528d7f))
* populate contact fields when creating quotation from customer ([55129e6](55129e697d))
* production plan not considering planning datetime when creating WO ([7e7b16b](7e7b16b23e))
* **profit and loss statement:** exclude non period columns ([28e8c40](28e8c40bfc))
* reset incoming rate in selling controller if there are changes in item ([024e7b0](024e7b01ac))
* **RFQ:** render email templates for preview and sending ([687a80d](687a80d74c))
* **stock:** add stock recon opening stock condition ([f9a8fc1](f9a8fc1f2d))
* **stock:** fetch batch wise valuation rate in get_items ([b132e3f](b132e3f22a))
* **stock:** ignore packing slip while cancelling the sales invoice ([f425f89](f425f89a26))
* **stock:** include subcontracting order qty while calculating the bin qty ([de244e0](de244e0af7))
* **stock:** remove is_return condition on pos batch qty calculation ([9dcaf38](9dcaf38142))
* **stock:** set incoming_rate with lcv rate for internal purchase ([f462639](f462639aa0))
* **subcontracting:** include item bom in supplied items grouping key ([95c4b8d](95c4b8de06))
* test cases ([e74389f](e74389f01c))
* validate over ordering of quotation ([e7ace8e](e7ace8e620))
* validation when more than one FG items in repack stock entry ([a2d302b](a2d302b3fa))
* zero valuation rate if returning from different warehouse ([8ce51b2](8ce51b2f80))

### Features

* clear demo data from desktop screen (backport [#52128](https://github.com/frappe/erpnext/issues/52128)) ([#52147](https://github.com/frappe/erpnext/issues/52147)) ([05e30dc](05e30dc011))
* **credit-note:** add checkbox to set valuation rate as zero for expired batch ([b84fd46](b84fd46841))
* **delivery-note:** add status indicator when document is partially billed ([6048add](6048add4c0))
* document naming rule will now use posting date of the document ([b03494b](b03494bb67))
* **Transaction Deletion Record:** Editable "DocTypes To Delete" List with CSV import/export ([#50592](https://github.com/frappe/erpnext/issues/50592)) ([4963261](4963261dc8))
2026-02-04 04:29:22 +00:00
ruthra kumar
e8e9fb25fe Merge pull request #52347 from frappe/version-16-hotfix
chore: release v16
2026-02-04 09:57:57 +05:30
rohitwaghchaure
4a7e2742ec Merge pull request #52388 from frappe/mergify/bp/version-16-hotfix/pr-51422
fix: merge taxes in purchase receipt when get items from multiple purchase invoices (backport #51422)
2026-02-03 22:53:40 +05:30
rohitwaghchaure
55d5d6535b Merge pull request #52391 from frappe/mergify/bp/version-16-hotfix/pr-51898
fix: group item wise tax details by tax row (backport #51898)
2026-02-03 22:52:37 +05:30
rohitwaghchaure
b8ac04fb54 Merge branch 'version-16' into version-16-hotfix 2026-02-03 22:50:07 +05:30
ravibharathi656
45e4c04830 fix: group item wise tax details by tax row
(cherry picked from commit 57bd1facf5)
2026-02-03 15:38:48 +00:00
NaviN
d80c8d14b0 fix: merge taxes in purchase receipt when get items from multiple purchase invoices (#51422)
* fix: merge taxes in purchase receipt when get items from multiple purchase invoices

* fix: make merge tax configurable

* chore: follow standard merge taxes method

* chore: follow standard merge taxes method

(cherry picked from commit 6fde0a6261)
2026-02-03 15:34:03 +00:00
mergify[bot]
66e47f5651 Merge pull request #52380 from frappe/mergify/bp/version-16-hotfix/pr-52278 2026-02-03 15:32:58 +00:00
ruthra kumar
a3e8af19a6 Merge pull request #52379 from frappe/mergify/bp/version-16-hotfix/pr-51651
fix: correct exchange gain loss in ppr (backport #51651)
2026-02-03 20:59:41 +05:30
Mihir Kandoi
9b498a8da8 Merge pull request #52385 from frappe/mergify/bp/version-16-hotfix/pr-52259
fix(stock): include subcontracting order qty while calculating the bin qty (backport #52259)
2026-02-03 20:48:37 +05:30
Mihir Kandoi
25389be340 Merge pull request #52382 from frappe/mergify/bp/version-16-hotfix/pr-52374
fix(stock): fetch batch wise valuation rate in get_items (backport #52374)
2026-02-03 20:34:37 +05:30
Sudharsanan11
de244e0af7 fix(stock): include subcontracting order qty while calculating the bin qty
(cherry picked from commit de8f8ef9f4)
2026-02-03 14:55:27 +00:00
kavin-114
b132e3f22a fix(stock): fetch batch wise valuation rate in get_items
(cherry picked from commit c5df570262)
2026-02-03 14:47:52 +00:00
ruthra kumar
670fd79e38 Merge pull request #52377 from frappe/mergify/bp/version-16-hotfix/pr-52314
fix(journal-entry): normalize exchange rate to float (backport #52314)
2026-02-03 20:10:03 +05:30
ravibharathi656
e09406d085 fix: correct exchange gain loss in ppr
(cherry picked from commit 02e96039ac)
2026-02-03 14:35:58 +00:00
rohitwaghchaure
3bc348d6f0 Merge pull request #52376 from frappe/mergify/bp/version-16-hotfix/pr-52375
fix: zero valuation rate if returning from different warehouse (backport #52369) (backport #52375)
2026-02-03 20:04:39 +05:30
Dharanidharan2813
3f0032d793 fix(journal-entry): normalize exchange rate to float
(cherry picked from commit be0040ddc7)
2026-02-03 14:17:24 +00:00
ruthra kumar
163b848455 Merge pull request #52368 from frappe/mergify/bp/version-16-hotfix/pr-52320
fix: correct Sales Tax Template sidebar link to proper DocType (backport #52320)
2026-02-03 19:44:26 +05:30
Rohit Waghchaure
8ce51b2f80 fix: zero valuation rate if returning from different warehouse
(cherry picked from commit 28929df0e8)
(cherry picked from commit eb2119e292)
2026-02-03 13:59:59 +00:00
ruthra kumar
422b37332e Merge pull request #52367 from frappe/mergify/bp/version-16-hotfix/pr-52279
fix(profit and loss statement): exclude non period columns (backport #52279)
2026-02-03 17:47:37 +05:30
Luis Mendoza
4c14e74a12 fix: correct Sales Tax Template sidebar link to proper DocType
(cherry picked from commit 06a7c85c93)
2026-02-03 12:05:37 +00:00
ravibharathi656
28e8c40bfc fix(profit and loss statement): exclude non period columns
(cherry picked from commit 6180e5eb53)
2026-02-03 12:00:00 +00:00
ruthra kumar
660fc8f76a Merge pull request #52365 from frappe/mergify/bp/version-16-hotfix/pr-52160
fix(stock): remove is_return condition on pos batch qty calculation (backport #52160)
2026-02-03 17:19:50 +05:30
ruthra kumar
22456a5857 Merge pull request #52362 from frappe/mergify/bp/version-16-hotfix/pr-51997
Add partially billed status indicator (backport #51997)
2026-02-03 17:19:31 +05:30
kavin-114
76e0eb00a5 test: add unit test case for pos reserved with return qty
(cherry picked from commit 12ec997027)
2026-02-03 11:11:50 +00:00
kavin-114
9dcaf38142 fix(stock): remove is_return condition on pos batch qty calculation
(cherry picked from commit 2c19c1fd06)
2026-02-03 11:11:50 +00:00
Dharanidharan2813
6048add4c0 feat(delivery-note): add status indicator when document is partially billed
(cherry picked from commit 7767000ccf)
2026-02-03 10:59:20 +00:00
rohitwaghchaure
0552b48328 Merge pull request #52356 from frappe/mergify/bp/version-16-hotfix/pr-52338
fix: negative stock for purchase return (backport #52338)
2026-02-03 16:16:28 +05:30
ruthra kumar
193b29d5fc Merge pull request #52350 from frappe/mergify/bp/version-16-hotfix/pr-52339
chore: rename icons (backport #52339)
2026-02-03 16:13:06 +05:30
Rohit Waghchaure
220a528d7f fix: negative stock for purchase return
(cherry picked from commit 77893933a2)
2026-02-03 10:25:10 +00:00
ruthra kumar
e087a8b179 Merge pull request #52353 from frappe/mergify/bp/version-16-hotfix/pr-52345
ci: skip svg (backport #52345)
2026-02-03 15:41:53 +05:30
ruthra kumar
d7067f6b7a ci: skip svg
(cherry picked from commit e565d2283e)
2026-02-03 10:11:12 +00:00
Shllokkk
e53ccd0745 fix: move company field to first position in sales invoice, purchase invoice, sales order, purchase order and journal entry
(cherry picked from commit 8e9365eb3b)
2026-02-03 10:04:56 +00:00
sokumon
e264d8e2d6 chore: rename icons
(cherry picked from commit 2d312bcfe8)
2026-02-03 10:02:54 +00:00
ruthra kumar
8b2559ab0c Merge pull request #52337 from frappe/mergify/bp/version-16-hotfix/pr-52280
fix(stock): ignore packing slip while cancelling the sales invoice (backport #52280)
2026-02-03 13:58:59 +05:30
Sudharsanan11
f425f89a26 fix(stock): ignore packing slip while cancelling the sales invoice
(cherry picked from commit c58887b44a)
2026-02-03 08:25:04 +00:00
ruthra kumar
e3bf84c572 Merge pull request #52321 from frappe/mergify/bp/version-16-hotfix/pr-50592
feat(Transaction Deletion Record): Editable "DocTypes To Delete" List with CSV import/export (backport #50592)
2026-02-03 13:50:00 +05:30
ruthra kumar
e2b88218ec Merge pull request #52330 from frappe/mergify/bp/version-16-hotfix/pr-51655
fix: include credit notes in project gross margin calculation (backport #51655)
2026-02-03 12:19:28 +05:30
ravibharathi656
e755a4ad98 fix: include credit notes in project gross margin calculation
(cherry picked from commit a378fee8e0)
2026-02-03 06:07:49 +00:00
ruthra kumar
d2ea428030 chore: resolve conflict 2026-02-03 10:38:39 +05:30
Mihir Kandoi
496956f08f Merge pull request #52324 from frappe/mergify/bp/version-16-hotfix/pr-52184
fix(subcontracting): include item bom in supplied items grouping key (backport #52184)
2026-02-03 09:24:00 +05:30
Sudharsanan11
a3190dd556 test(subcontracting): add test for consumed_qty calculation with similar finished goods
(cherry picked from commit 4d9412181c)
2026-02-03 03:40:18 +00:00
Sudharsanan11
95c4b8de06 fix(subcontracting): include item bom in supplied items grouping key
(cherry picked from commit 0d372a62a1)
2026-02-03 03:40:18 +00:00
Henning Wendtland
4963261dc8 feat(Transaction Deletion Record): Editable "DocTypes To Delete" List with CSV import/export (#50592)
* feat: add editable DocTypes To Delete list with import/export

Add user control over transaction deletion with reviewable and reusable deletion templates.

- New "DocTypes To Delete" table allows users to review and customize what will be deleted before submission
- Import/Export CSV templates for reusability across environments
- Company field rule: only filter by company if field is specifically named "company", otherwise delete all records
- Child tables (istable=1) automatically excluded from selection
- "Remove Zero Counts" helper button to clean up list
- Backward compatible with existing deletion records

* refactor: improve Transaction Deletion Record code quality

- Remove unnecessary chatty comments from AI-generated code
- Add concise docstrings to all new methods
- Remove redundant @frappe.whitelist() decorators from internal methods
- Improve CSV import validation (header check, child table filtering)
- Add better error feedback with consolidated skip messages
- Reorder form fields: To Delete list now appears before Excluded list
- Add conditional visibility for Summary table (legacy records only)
- Improve architectural clarity: single API entry point per feature

Technical improvements:
- export_to_delete_template_method and import_to_delete_template_method
  are now internal helpers without whitelist decorators
- CSV import now validates format and provides detailed skip reasons
- Summary table only shows for submitted records without To Delete list
- Maintains backward compatibility for existing deletion records

* fix: field order

* test: fix broken tests and add new ones

* fix: adapt create_transaction_deletion_request

* test: fix assertRaises trigger

* fix: conditionally execute Transaction Deletion pre-tasks based on selected DocTypes

* refactor: replace boolean task flags with status fields

* fix: remove UI comment

* fix: don't allow virtual doctype selection and improve protected Doctype List

* fix: replace outdated frappe.db.sql by frappe.qb

* feat: add support for multiple company fields

* fix: autofill comapny field, add docstrings, filter for company_field

* fix: add edge case handling for update_naming_series and add tests for prefix extraction

* fix: use redis for running deletion validation, check per doctype instead of company

(cherry picked from commit 0fb37ad792)

# Conflicts:
#	erpnext/patches.txt
2026-02-03 00:49:04 +00:00
mergify[bot]
81e65757ee fix: duplicate account number (Indonesia COA) (backport #52080) (#52317)
Co-authored-by: Apriliansyah Idris <apriliansyahidris@gmail.com>
fix: duplicate account number (Indonesia COA) (#52080)
2026-02-02 19:09:22 +00:00
rohitwaghchaure
78e581154b Merge pull request #52312 from frappe/mergify/bp/version-16-hotfix/pr-52303
fix: batch selector not working if Use Legacy (Client side) Reactivity disabled (backport #52303)
2026-02-02 23:32:40 +05:30
Rohit Waghchaure
2a3642b55a fix: batch selector not working if Use Legacy (Client side) Reactivity disabled
(cherry picked from commit d1cba1073f)
2026-02-02 16:32:18 +00:00
Mihir Kandoi
beaa76ca16 Merge pull request #52311 from frappe/mergify/bp/version-16-hotfix/pr-52246 2026-02-02 21:01:34 +05:30
Mihir Kandoi
9997185071 Merge pull request #52246 from mihir-kandoi/st58765
(cherry picked from commit 135a433018)
2026-02-02 15:15:38 +00:00
Mihir Kandoi
681c0b5917 Merge pull request #52308 from frappe/mergify/bp/version-16-hotfix/pr-52304
fix: populate contact fields when creating quotation from customer (backport #52304)
2026-02-02 20:36:19 +05:30
Mihir Kandoi
4a0e04ee20 Merge pull request #52306 from frappe/mergify/bp/version-16-hotfix/pr-52281
fix(stock): add stock recon opening stock condition (backport #52281)
2026-02-02 20:24:00 +05:30
Mihir Kandoi
55129e697d fix: populate contact fields when creating quotation from customer
(cherry picked from commit 75b2c2c83d)
2026-02-02 14:46:36 +00:00
kavin-114
f9a8fc1f2d fix(stock): add stock recon opening stock condition
(cherry picked from commit f3eb6c7078)
2026-02-02 14:37:36 +00:00
Mihir Kandoi
091ff81ae5 Merge pull request #52302 from frappe/mergify/bp/version-16-hotfix/pr-52286
fix: reset incoming rate in selling controller if there are changes i… (backport #52286)
2026-02-02 20:05:37 +05:30
Mihir Kandoi
024e7b01ac fix: reset incoming rate in selling controller if there are changes in item
(cherry picked from commit 2d6b43fd54)
2026-02-02 14:18:20 +00:00
ruthra kumar
a0156b61b8 Merge pull request #52284 from frappe/mergify/bp/version-16-hotfix/pr-52200
fix(accounts): correct date in Journal Auditing Voucher print format (backport #52200)
2026-02-02 12:55:41 +05:30
Tamal Majumdar
7e5eab261c fix: journal auditing voucher print date to use posting_date
(cherry picked from commit 43e2495df8)
2026-02-02 07:21:36 +00:00
ruthra kumar
63782e6355 Merge pull request #52282 from frappe/mergify/bp/version-16-hotfix/pr-51692
fix: correct spelling of Payment Reconciliation in Accounting (backport #51692)
2026-02-02 12:43:17 +05:30
nivithamerlin
c80b554cd7 fix: correct spelling of Payment Reconciliation in Accounting
(cherry picked from commit 35e53d28df)
2026-02-02 07:09:54 +00:00
Mihir Kandoi
7db88b210e Merge pull request #52276 from frappe/mergify/bp/version-16-hotfix/pr-52274 2026-02-02 10:24:07 +05:30
Mihir Kandoi
42d873f1d9 test: over ordering of quotation items
(cherry picked from commit 53e58f6678)
2026-02-02 04:38:13 +00:00
MochaMind
19c1dcc3dd chore: update POT file (#52264) 2026-02-01 15:07:28 +01:00
Mihir Kandoi
23e027b6be Merge pull request #52230 from frappe/mergify/bp/version-16-hotfix/pr-52222
fix: validate over ordering of quotation (backport #52222)
2026-01-31 20:26:44 +05:30
Mihir Kandoi
d91cfa76e6 Merge pull request #52257 from frappe/mergify/bp/version-16-hotfix/pr-52253 2026-01-31 20:19:18 +05:30
Mihir Kandoi
a29df7be67 chore: resolve conflicts 2026-01-31 20:13:26 +05:30
Mihir Kandoi
dbe5846908 fix: make item name editable in RFQ
(cherry picked from commit d9998a977c)
2026-01-31 14:34:24 +00:00
Mihir Kandoi
83fcb5d2d8 Merge pull request #52255 from frappe/mergify/bp/version-16-hotfix/pr-52252
fix: better fix for #51495 (backport #52252)
2026-01-31 19:52:45 +05:30
Mihir Kandoi
b8ab55fee8 fix: better fix for aac39b2671
(cherry picked from commit b24ae5e9a2)
2026-01-31 14:21:27 +00:00
rohitwaghchaure
4d03f4ebaa Merge pull request #52240 from frappe/mergify/bp/version-16-hotfix/pr-52232
fix: validation when more than one FG items in repack stock entry (backport #52232)
2026-01-31 17:54:08 +05:30
Rohit Waghchaure
a2d302b3fa fix: validation when more than one FG items in repack stock entry
(cherry picked from commit 6423ce2fa7)
2026-01-31 07:16:45 +00:00
Mihir Kandoi
b5321d42a3 Merge pull request #52234 from frappe/mergify/bp/version-16-hotfix/pr-52231
fix: item code not showing in report view (backport #52231)
2026-01-30 22:17:26 +05:30
UmakanthKaspa
af167f91fe fix: item code not showing in report view
(cherry picked from commit b20f57321f)
2026-01-30 16:46:26 +00:00
Mihir Kandoi
e74389f01c fix: test cases
(cherry picked from commit 36f1e3572c)
2026-01-30 14:00:52 +00:00
Mihir Kandoi
e7ace8e620 fix: validate over ordering of quotation
(cherry picked from commit 4cc306d2d8)

# Conflicts:
#	erpnext/patches.txt
2026-01-30 14:00:52 +00:00
Mihir Kandoi
e23e9b5d66 Merge pull request #52227 from frappe/mergify/bp/version-16-hotfix/pr-52226 2026-01-30 18:03:40 +05:30
Mihir Kandoi
136b2cfba5 fix: failing test cases
(cherry picked from commit d3f44a425c)
2026-01-30 12:18:10 +00:00
Mihir Kandoi
3b3738577d Merge pull request #52225 from frappe/mergify/bp/version-16-hotfix/pr-51433 2026-01-30 17:30:01 +05:30
Mihir Kandoi
1e646bd0ed Merge pull request #52224 from frappe/mergify/bp/version-16-hotfix/pr-52223
fix: allow sales invoice to be renamed (backport #52223)
2026-01-30 17:19:10 +05:30
Mihir Kandoi
02e6c49130 test: add test case
(cherry picked from commit e2c3d0fa94)
2026-01-30 11:41:46 +00:00
Mihir Kandoi
e9fa725030 chore: make feature opt in
(cherry picked from commit b8d4522ea1)
2026-01-30 11:41:46 +00:00
Mihir Kandoi
b03494bb67 feat: document naming rule will now use posting date of the document
(cherry picked from commit 22fd1a1cfd)
2026-01-30 11:41:45 +00:00
Mihir Kandoi
f7b915dfe6 fix: allow sales invoice to be renamed
(cherry picked from commit 95fdbe55f9)
2026-01-30 11:33:20 +00:00
Mihir Kandoi
0a5aac9ce7 Merge pull request #52218 from frappe/mergify/bp/version-16-hotfix/pr-52209
fix: add precision to rejected batch no qty calculation (backport #52209)
2026-01-30 12:21:50 +05:30
Mihir Kandoi
e3c62070d1 Merge pull request #52215 from frappe/mergify/bp/version-16-hotfix/pr-52213
fix: hide close button on WO if WO is completed (backport #52213)
2026-01-30 12:06:39 +05:30
Mihir Kandoi
b365444027 fix: add precision to rejected batch no qty calculation
(cherry picked from commit 838d245215)
2026-01-30 06:35:59 +00:00
Mihir Kandoi
0d1c30f3f0 fix: hide close button on WO if WO is completed
(cherry picked from commit 6e17ccf499)
2026-01-30 06:29:32 +00:00
Mihir Kandoi
d0b553dca3 Merge pull request #52212 from frappe/mergify/bp/version-16-hotfix/pr-52210
fix(barcode): failing request when item has both batch and serial (backport #52210)
2026-01-30 11:52:49 +05:30
Mihir Kandoi
e4cdd971c8 fix(barcode): failing request when item has both batch and serial
(cherry picked from commit 89f6f0f46f)
2026-01-30 06:17:41 +00:00
mergify[bot]
78c4f01733 fix: missing depr_series causing error on jv creation (backport #52085) (#52206)
fix: missing depr_series causing error on jv creation (#52085)

(cherry picked from commit b565dd3da8)

Co-authored-by: Dany Robert <rtdany10@gmail.com>
2026-01-29 23:30:29 +05:30
Mihir Kandoi
eac4978278 Merge pull request #52203 from frappe/mergify/bp/version-16-hotfix/pr-52201
fix: hide item_wise_tax_details table from print (backport #52201)
2026-01-29 21:58:37 +05:30
Mihir Kandoi
c619be989b fix: hide item_wise_tax_details table from print
(cherry picked from commit c38f884095)
2026-01-29 16:12:55 +00:00
rohitwaghchaure
64921fc1b5 Merge pull request #52197 from frappe/mergify/bp/version-16-hotfix/pr-52190
fix: lead time calculation for FG item (backport #52190)
2026-01-29 19:06:53 +05:30
rohitwaghchaure
6d8d502bbf Merge pull request #52192 from frappe/mergify/bp/version-16-hotfix/pr-52158
Add Landed Cost Voucher Amount in Internal Purchase Receipt (backport #52158)
2026-01-29 18:07:46 +05:30
Rohit Waghchaure
5595602f24 fix: lead time calculation for FG item
(cherry picked from commit 646688c291)
2026-01-29 12:29:39 +00:00
kavin-114
7042f2b8fb test: add unit test to check internal purchase with lcv
(cherry picked from commit dd4fd89ef8)
2026-01-29 12:02:50 +00:00
kavin-114
f462639aa0 fix(stock): set incoming_rate with lcv rate for internal purchase
(cherry picked from commit f0dccc3cd7)
2026-01-29 12:02:49 +00:00
Mihir Kandoi
14ba0f1cae Merge pull request #52183 from frappe/mergify/bp/version-16-hotfix/pr-52181
fix: js error if user does not have write permission for date field (backport #52181)
2026-01-29 15:42:29 +05:30
Mihir Kandoi
3944dfde31 fix: js error if user does not have write permission for date field
(cherry picked from commit 7f6f39f5e7)
2026-01-29 10:01:33 +00:00
ruthra kumar
16cc2b0a25 Merge pull request #52177 from frappe/mergify/bp/version-16-hotfix/pr-52173
New accounting icons (backport #52173)
2026-01-29 14:51:36 +05:30
Jacob Salvi
3394e1a126 refactor: new accounting icons (#52173)
* chore: new icons share-management

* chore: new accounting icons

* chore: trigger CI

---------

Co-authored-by: Soham Kulkarni <77533095+sokumon@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit cdcf3fa593)
2026-01-29 09:13:39 +00:00
mergify[bot]
66086010fc fix(demo): removed toolbar eventlistener (backport #52171) (#52172)
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
fix(demo): removed toolbar eventlistener (#52171)
2026-01-29 11:29:59 +05:30
Mihir Kandoi
195a2f3e74 Merge pull request #52168 from frappe/mergify/bp/version-16-hotfix/pr-52166
fix: production plan not considering planning datetime when creating WO (backport #52166)
2026-01-29 11:06:50 +05:30
Mihir Kandoi
7e7b16b23e fix: production plan not considering planning datetime when creating WO
(cherry picked from commit 4e19c7e8bd)
2026-01-29 05:20:59 +00:00
Aarol D'Souza
f4beb41df2 Merge pull request #52164 from frappe/mergify/bp/version-16-hotfix/pr-52092
fix(RFQ): render email templates for preview and sending (backport #52092)
2026-01-29 09:23:39 +05:30
AarDG10
4c4aa9bbdf ci: minor text correction
(cherry picked from commit 37cdae2f34)
2026-01-29 03:38:02 +00:00
AarDG10
687a80d74c fix(RFQ): render email templates for preview and sending
(cherry picked from commit 525b3960e1)
2026-01-29 03:38:01 +00:00
rohitwaghchaure
f01e0576b9 Merge pull request #52141 from frappe/mergify/bp/version-16-hotfix/pr-52007
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch (backport #52007)
2026-01-28 19:46:10 +05:30
Frappe PR Bot
83a0d957ef chore(release): Bumped to Version 16.3.0
# [16.3.0](https://github.com/frappe/erpnext/compare/v16.2.0...v16.3.0) (2026-01-28)

### Features

* clear demo data from desktop screen (backport [#52128](https://github.com/frappe/erpnext/issues/52128))  ([#52150](https://github.com/frappe/erpnext/issues/52150)) ([554aeb9](554aeb94fd))
2026-01-28 12:51:32 +00:00
mergify[bot]
554aeb94fd feat: clear demo data from desktop screen (backport #52128) (#52150)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Soham Kulkarni <77533095+sokumon@users.noreply.github.com>
2026-01-28 18:19:41 +05:30
mergify[bot]
05e30dc011 feat: clear demo data from desktop screen (backport #52128) (#52147)
Co-authored-by: Soham Kulkarni <77533095+sokumon@users.noreply.github.com>
2026-01-28 17:36:04 +05:30
kavin-114
40a3dabd30 test(credit-note): add unit test for zero valuation rate on expired batch
(cherry picked from commit 3460a7efb5)
2026-01-28 10:43:58 +00:00
kavin-114
0f9bf08685 fix(credit-note): set incoming rate as zero for expired batch
(cherry picked from commit e78c750b4e)
2026-01-28 10:43:58 +00:00
kavin-114
b84fd46841 feat(credit-note): add checkbox to set valuation rate as zero for expired batch
(cherry picked from commit 04cdf88715)
2026-01-28 10:43:58 +00:00
Frappe PR Bot
6209d633c2 chore(release): Bumped to Version 16.2.0
# [16.2.0](https://github.com/frappe/erpnext/compare/v16.1.0...v16.2.0) (2026-01-28)

### Bug Fixes

* **accounts:** correct base grand total and rounded total mismatch (backport [#51739](https://github.com/frappe/erpnext/issues/51739)) ([#52101](https://github.com/frappe/erpnext/issues/52101)) ([6115f8f](6115f8fb9a))
* **asset capitalization:** update total_asset_cost on asset capitalisation submission (backport [#52077](https://github.com/frappe/erpnext/issues/52077)) ([#52115](https://github.com/frappe/erpnext/issues/52115)) ([4f16956](4f1695616a))
* autofill warehouse for packed items ([881562f](881562fc37))
* Bin reserved qty for production for extra material transfer ([bf53133](bf53133f94))
* calculate weighted average rate for customer provided items in subcontracting inward order ([7120fbd](7120fbd14b))
* check the payment ledger entry has the dimension ([#51823](https://github.com/frappe/erpnext/issues/51823)) ([7342b25](7342b2551b))
* check the payment ledger entry has the dimension (backport [#51823](https://github.com/frappe/erpnext/issues/51823)) ([#52108](https://github.com/frappe/erpnext/issues/52108)) ([1927adb](1927adbd2e))
* create DN btn should not be shown if it cannot be created ([30e6b5d](30e6b5daac))
* **customer:** add customer group filters ([b1716bf](b1716bfeef))
* disable asset repair when status is fully depreciated ([13e4849](13e4849c43))
* Ensure paid_amount is always numeric before calling allocate_amount_to_references (backport [#50935](https://github.com/frappe/erpnext/issues/50935)) ([#52036](https://github.com/frappe/erpnext/issues/52036)) ([e9f3f0f](e9f3f0f445))
* force user to enter batch or serial for serial/batch items ([91199ea](91199ea9c9))
* handle parent level project change ([0b7684e](0b7684eccd))
* handle undefined bank_transaction_mapping in quick entry ([22a8d48](22a8d483e1))
* job cards should not be deleted on close of WO ([7c2bbe0](7c2bbe0d82))
* **journal-entry:** prevent submit failure due to double background queuing (backport [#52083](https://github.com/frappe/erpnext/issues/52083)) ([#52087](https://github.com/frappe/erpnext/issues/52087)) ([46e6096](46e6096fe3))
* negative stock for purchae return ([fb3fb8c](fb3fb8ca5e))
* not able to complete the job card ([f486071](f486071cf6))
* **payment entry:** update currency symbol (backport [#51956](https://github.com/frappe/erpnext/issues/51956)) ([#52094](https://github.com/frappe/erpnext/issues/52094)) ([b1b1f25](b1b1f25bb1))
* **project:** add missing counter to project update naming series ([37a237d](37a237dbb7))
* rejected qty in PR doesn't consider conversion factor ([c7c7a55](c7c7a55a58))
* **sales order:** set project at item level from parent ([27fe754](27fe754a7d))
* **shipment:** user contact validation to use full name ([0a56647](0a56647a61))
* show everything else besides other party specific item ([7575861](75758610dd))
* show message if image is removed from item description (backport [#52088](https://github.com/frappe/erpnext/issues/52088)) ([#52097](https://github.com/frappe/erpnext/issues/52097)) ([53b7375](53b73757ed))
* **stock:** use purchase UOM in Supplier Quotation items ([f97b850](f97b850077))
* strip whitespace in customer_name ([41e6687](41e6687b35))
* swedish_address_template ([cff09b7](cff09b71cc))
* tests ([6fa60d2](6fa60d2f1a))
* throw if item order field is not set in subcontracting controller ([264855e](264855e5e1))
* unable to split asset from capitalization (backport [#52020](https://github.com/frappe/erpnext/issues/52020)) ([#52114](https://github.com/frappe/erpnext/issues/52114)) ([c1cc1db](c1cc1dbd27)), closes [#52016](https://github.com/frappe/erpnext/issues/52016) [#52016](https://github.com/frappe/erpnext/issues/52016)
* UOM of item not fetching in BOM ([1b9a93f](1b9a93f90e))
* update country_wise_tax.json for Algerian Taxes (backport [#51878](https://github.com/frappe/erpnext/issues/51878)) ([#52038](https://github.com/frappe/erpnext/issues/52038)) ([8946f12](8946f12677))
* validation to check at-least one raw material for manufacture entry ([d067e37](d067e37ab6))
* warehouse permissions in MR incorrectly ignored ([504c84e](504c84e28a))

### Features

* **accounts:** retain filters when switching between financial statements (backport [#51668](https://github.com/frappe/erpnext/issues/51668)) ([#52117](https://github.com/frappe/erpnext/issues/52117)) ([9ed3801](9ed3801d06))
2026-01-28 04:15:59 +00:00
ruthra kumar
095fe65bef Merge pull request #52103 from frappe/version-16-hotfix
chore: release v16
2026-01-28 09:44:30 +05:30
Mihir Kandoi
b285548a46 Merge pull request #52124 from frappe/mergify/bp/version-16-hotfix/pr-51961
fix(sales order): set project at item level from parent (backport #51961)
2026-01-27 21:55:49 +05:30
SowmyaArunachalam
0b7684eccd fix: handle parent level project change
(cherry picked from commit 543b6e51c0)
2026-01-27 16:24:22 +00:00
SowmyaArunachalam
574460c009 chore: use frappe.model.set_value
(cherry picked from commit 3b27f49d79)
2026-01-27 16:24:22 +00:00
SowmyaArunachalam
27fe754a7d fix(sales order): set project at item level from parent
(cherry picked from commit 9e51701e2a)
2026-01-27 16:24:22 +00:00
Mihir Kandoi
531fe59a24 Merge pull request #52122 from frappe/mergify/bp/version-16-hotfix/pr-52084
fix(shipment): user contact validation to use full name (backport #52084)
2026-01-27 21:29:22 +05:30
harrishragavan
0a56647a61 fix(shipment): user contact validation to use full name
(cherry picked from commit 3c6eb9a531)
2026-01-27 15:57:22 +00:00
Soham Kulkarni
c2f666b7a3 Merge pull request #52120 from frappe/mergify/bp/version-16-hotfix/pr-52119 2026-01-27 21:11:42 +05:30
sokumon
c1bbe1104e chore: change color of icons in accounting folders
(cherry picked from commit 6f9cd8c261)
2026-01-27 15:15:16 +00:00
ruthra kumar
2c86327c7e Merge pull request #52112 from frappe/mergify/bp/version-16-hotfix/pr-52106
fix: show everything else besides other party specific item (backport #52106)
2026-01-27 20:08:54 +05:30
ruthra kumar
31385a1f91 Merge pull request #52118 from frappe/mergify/bp/version-16-hotfix/pr-51894
refactor: accounting workspace (backport #51894)
2026-01-27 20:08:29 +05:30
mergify[bot]
4f1695616a fix(asset capitalization): update total_asset_cost on asset capitalisation submission (backport #52077) (#52115)
fix(asset capitalization): update total_asset_cost on asset capitalisation submission (#52077)

fix(asset capitalization): update total_asset_cost on asset capitalization submission

(cherry picked from commit ec41f1b0f5)

Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com>
2026-01-27 19:31:52 +05:30
mergify[bot]
9ed3801d06 feat(accounts): retain filters when switching between financial statements (backport #51668) (#52117) 2026-01-27 19:03:44 +05:30
ruthra kumar
61ad67ec29 refactor: reuse icon for invoicing
(cherry picked from commit f0332c4dc7)
2026-01-27 13:15:37 +00:00
ruthra kumar
735b9da6b1 refactor: rename Accounts to Accounting
(cherry picked from commit fb9656b975)
2026-01-27 13:15:36 +00:00
ruthra kumar
fe7a797156 refactor: link payments dashboard to sidebar
(cherry picked from commit f7abf9c1da)
2026-01-27 13:15:36 +00:00
ruthra kumar
f951dd180a refactor: payments dashboard
(cherry picked from commit 99406ccc15)
2026-01-27 13:15:36 +00:00
ruthra kumar
8e871796d4 refactor: shed duplicates from invoicing sidebar
(cherry picked from commit 1295d7aa30)
2026-01-27 13:15:36 +00:00
ruthra kumar
c88ee50c34 refactor: reorder accounts setup sidebar
(cherry picked from commit 5a680d5037)
2026-01-27 13:15:35 +00:00
ruthra kumar
e49f6f4f09 refactor: introduce setup icon and reorder
(cherry picked from commit 7528d42187)
2026-01-27 13:15:35 +00:00
ruthra kumar
72942e6b8c refactor: payments sidebar and icon
(cherry picked from commit cbdc945287)
2026-01-27 13:15:35 +00:00
ruthra kumar
fdcf037f1b chore: rename accounting to invoicing
(cherry picked from commit faf0dcb102)
2026-01-27 13:15:35 +00:00
ruthra kumar
9e0c606b95 chore: remove accounting icon
(cherry picked from commit 5e02b4009e)
2026-01-27 13:15:34 +00:00
ruthra kumar
6b9f2ddf83 refactor: invoicing icon
(cherry picked from commit 8125f9035c)
2026-01-27 13:15:34 +00:00
mergify[bot]
c1cc1dbd27 fix: unable to split asset from capitalization (backport #52020) (#52114)
fix: unable to split asset from capitalization (#52020)

* fix: Allow split asset from capitalized composite asset (fixes #52016)

* test: Add test case for splitting asset created via capitalization (fixes #52016)

* docs: Add docstring to before_submit method

* fix: Remove unused variable and fix UTF-8 encoding in asset files

* fix: Remove UTF-8 BOM from asset.py to fix linting

* fix: Fix test_split_asset_created_via_capitalization test parameters

* fix: Remove unused import create_item

* chore: remove unnecessary comments

Removed validation comments for composite asset capitalization in before_submit method.

---------


(cherry picked from commit 7e9647f3f0)

Co-authored-by: madelyngamble2 <madelyngamble2@gmail.com>
Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2026-01-27 18:04:19 +05:30
Mihir Kandoi
de584e2e8d test: fix tests
(cherry picked from commit 5eeebbde7f)
2026-01-27 10:57:40 +00:00
Mihir Kandoi
75758610dd fix: show everything else besides other party specific item
(cherry picked from commit 71371b0ba5)
2026-01-27 10:57:40 +00:00
ruthra kumar
1927adbd2e fix: check the payment ledger entry has the dimension (backport #51823) (#52108)
fix: check the payment ledger entry has the dimension (#51823)

* fix: check the payment ledger entry has the dimension

* fix: add project in payment ledger entry

(cherry picked from commit efa3973b77)

Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com>
2026-01-27 16:25:33 +05:30
Vishnu Priya Baskaran
7342b2551b fix: check the payment ledger entry has the dimension (#51823)
* fix: check the payment ledger entry has the dimension

* fix: add project in payment ledger entry

(cherry picked from commit efa3973b77)
2026-01-27 10:27:23 +00:00
mergify[bot]
6115f8fb9a fix(accounts): correct base grand total and rounded total mismatch (backport #51739) (#52101)
Co-authored-by: Dharanidharan S <dharanidharans1328@gmail.com>
fix(accounts): correct base grand total and rounded total mismatch (#51739)
2026-01-27 14:23:10 +05:30
mergify[bot]
53b73757ed fix: show message if image is removed from item description (backport #52088) (#52097)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-01-27 14:09:02 +05:30
rohitwaghchaure
dee3357da2 Merge pull request #52075 from frappe/mergify/bp/version-16-hotfix/pr-52062
fix: not able to complete the job card (backport #52062)
2026-01-27 13:03:43 +05:30
mergify[bot]
b1b1f25bb1 fix(payment entry): update currency symbol (backport #51956) (#52094)
Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com>
fix(payment entry): update currency symbol (#51956)
2026-01-27 06:34:19 +00:00
mergify[bot]
46e6096fe3 fix(journal-entry): prevent submit failure due to double background queuing (backport #52083) (#52087)
Co-authored-by: V Shankar <shankarv292002@gmail.com>
fix(journal-entry): prevent submit failure due to double background queuing (#52083)
2026-01-27 05:53:05 +00:00
Rohit Waghchaure
f486071cf6 fix: not able to complete the job card
(cherry picked from commit 696ea68f86)
2026-01-26 17:45:21 +00:00
Mihir Kandoi
b541fbbd60 Merge pull request #52066 from frappe/mergify/bp/version-16-hotfix/pr-52064
fix: strip whitespace in customer_name (backport #52064)
2026-01-26 15:32:27 +05:30
Shankarv19bcr
41e6687b35 fix: strip whitespace in customer_name
(cherry picked from commit e5ba0e6401)
2026-01-26 09:47:06 +00:00
MochaMind
55ce40de37 chore: update POT file (#52058) 2026-01-25 20:37:09 +01:00
ruthra kumar
6f21ab5d9a Merge pull request #52040 from frappe/mergify/bp/version-16-hotfix/pr-51670
fix: handle undefined bank_transaction_mapping in quick entry (backport #51670)
2026-01-25 13:11:28 +05:30
ruthra kumar
7c81672b36 Merge pull request #52041 from frappe/mergify/bp/version-16-hotfix/pr-51691
refactor: not warn when filter field is missing in FS reports (backport #51691)
2026-01-25 13:10:02 +05:30
ruthra kumar
d0faff09cb Merge pull request #52055 from frappe/mergify/bp/version-16-hotfix/pr-52050
fix: swedish_address_template (backport #52050)
2026-01-25 13:08:42 +05:30
mahsem
cff09b71cc fix: swedish_address_template
(cherry picked from commit 334e8ada30)
2026-01-25 05:22:41 +00:00
rohitwaghchaure
b180e3b78c Merge pull request #52053 from frappe/mergify/bp/version-16-hotfix/pr-52043
fix: UOM of item not fetching in BOM (backport #52043)
2026-01-25 10:50:38 +05:30
rohitwaghchaure
6ba2795043 Merge pull request #51905 from frappe/mergify/bp/version-16-hotfix/pr-51900
fix: validation to check at-least one raw material for manufacture entry (backport #51900)
2026-01-25 10:45:11 +05:30
Rohit Waghchaure
1b9a93f90e fix: UOM of item not fetching in BOM
(cherry picked from commit ba8eadda52)
2026-01-25 05:15:05 +00:00
rohitwaghchaure
1f1428f00a Merge branch 'version-16-hotfix' into mergify/bp/version-16-hotfix/pr-51900 2026-01-24 13:49:24 +05:30
Abdeali Chharchhoda
606ac2a91a refactor: not warn when filter field is missing in FS reports
(cherry picked from commit d905f78984)
2026-01-24 07:08:08 +00:00
Abdeali Chharchhoda
9a175757ac refactor: use console.error for error logging in Plaid integration
(cherry picked from commit 9322095786)
2026-01-24 07:07:48 +00:00
Abdeali Chharchhoda
22a8d483e1 fix: handle undefined bank_transaction_mapping in quick entry
(cherry picked from commit 8a1b8259bd)
2026-01-24 07:07:47 +00:00
Abdeali Chharchhoda
7abaaed957 refactor: remove redundant onload function for bank mapping table
(cherry picked from commit 7c7ba0154a)
2026-01-24 07:07:47 +00:00
mergify[bot]
8946f12677 fix: update country_wise_tax.json for Algerian Taxes (backport #51878) (#52038)
fix: update country_wise_tax.json for Algerian Taxes (#51878)

* Algeria chart of accounts

Algeria chart of accounts

* Update Algeria Chart Of Account

* Algeria chart of account

* Algeria Chart of Account

Algeria Chart of Account

* Modify Algeria tax entries in country_wise_tax.json

Updated tax rates and account names for Algeria.

* Rename account for Algeria tax from VAT to TVA

Rename account for Algeria tax from VAT to TVA

(cherry picked from commit e810cd8440)

Co-authored-by: HALFWARE <contact@half-ware.com>
2026-01-24 06:48:16 +00:00
mergify[bot]
e9f3f0f445 fix: Ensure paid_amount is always numeric before calling allocate_amount_to_references (backport #50935) (#52036)
fix: Ensure paid_amount is always numeric before calling allocate_amount_to_references (#50935)

fix: ensure paid_amount is not null in allocate_party_amount_against_ref_docs
(cherry picked from commit 50b3396064)

Co-authored-by: El-Shafei H. <el.shafei.developer@gmail.com>
2026-01-24 12:03:34 +05:30
ruthra kumar
ba38bc3eaf Merge pull request #52026 from frappe/mergify/bp/version-16-hotfix/pr-51756
fix: disable asset repair when status is fully depreciated (backport #51756)
2026-01-24 09:46:53 +05:30
rohitwaghchaure
b4572978f9 Merge pull request #52031 from frappe/mergify/bp/version-16-hotfix/pr-52024
fix: Bin reserved qty for production for extra material transfer (backport #52024)
2026-01-24 08:47:44 +05:30
Rohit Waghchaure
bf53133f94 fix: Bin reserved qty for production for extra material transfer
(cherry picked from commit f5378b6573)
2026-01-23 15:45:46 +00:00
Mihir Kandoi
23c902c317 Merge pull request #52022 from frappe/mergify/bp/version-16-hotfix/pr-51999
fix(stock): use purchase UOM in Supplier Quotation items (backport #51999)
2026-01-23 19:21:12 +05:30
SowmyaArunachalam
13e4849c43 fix: disable asset repair when status is fully depreciated
(cherry picked from commit 66fe1aa85d)
2026-01-23 11:38:39 +00:00
Bharathidhasan06
f97b850077 fix(stock): use purchase UOM in Supplier Quotation items
(cherry picked from commit 2606ca6fa9)
2026-01-23 08:34:36 +00:00
rohitwaghchaure
b3e12f9acb Merge pull request #52015 from frappe/mergify/bp/version-16-hotfix/pr-52006
fix: negative stock for purchase return (backport #52006)
2026-01-23 13:22:36 +05:30
Rohit Waghchaure
fb3fb8ca5e fix: negative stock for purchae return
(cherry picked from commit d68a04ad16)
2026-01-23 06:04:02 +00:00
rohitwaghchaure
e2232340dc Merge pull request #52005 from frappe/mergify/bp/version-16-hotfix/pr-51989
fix: autofill warehouse for packed items (backport #51989)
2026-01-22 23:56:30 +05:30
Sudharsanan11
881562fc37 fix: autofill warehouse for packed items
(cherry picked from commit 3f8a0a4833)
2026-01-22 17:28:22 +00:00
Mihir Kandoi
dcd6279d47 Merge pull request #51983 from frappe/mergify/bp/version-16-hotfix/pr-51908
fix: throw if item order field is not set in subcontracting controller (backport #51908)
2026-01-22 10:53:05 +05:30
Mihir Kandoi
041a7c5a57 Merge pull request #51982 from frappe/mergify/bp/version-16-hotfix/pr-51929
fix: calculate weighted average rate for customer provided items in subcontracting inward order (backport #51929)
2026-01-22 10:51:54 +05:30
Mihir Kandoi
27ffef41a7 Merge pull request #51980 from frappe/mergify/bp/version-16-hotfix/pr-51966
fix(customer): add customer group filters (backport #51966)
2026-01-22 10:42:45 +05:30
Mihir Kandoi
9038f19fb6 Merge pull request #51978 from frappe/mergify/bp/version-16-hotfix/pr-51967
fix(project): add missing counter to project update naming series (backport #51967)
2026-01-22 10:38:33 +05:30
ljain112
264855e5e1 fix: throw if item order field is not set in subcontracting controller
(cherry picked from commit d256365f4a)
2026-01-22 05:05:22 +00:00
ljain112
7120fbd14b fix: calculate weighted average rate for customer provided items in subcontracting inward order
(cherry picked from commit 37ee560eae)
2026-01-22 05:02:16 +00:00
SowmyaArunachalam
b1716bfeef fix(customer): add customer group filters
(cherry picked from commit 1e3db9f916)
2026-01-22 04:57:04 +00:00
ravibharathi656
37a237dbb7 fix(project): add missing counter to project update naming series
(cherry picked from commit 49e64f4e1c)
2026-01-22 04:53:10 +00:00
Mihir Kandoi
be9112b6fc Merge pull request #51972 from frappe/mergify/bp/version-16-hotfix/pr-51968 2026-01-22 09:04:25 +05:30
Mihir Kandoi
2f240f3553 Merge pull request #51970 from frappe/mergify/bp/version-16-hotfix/pr-51964
fix: create DN btn should not be shown if it cannot be created (backport #51964)
2026-01-21 22:54:50 +05:30
Mihir Kandoi
c7c7a55a58 fix: rejected qty in PR doesn't consider conversion factor
(cherry picked from commit 343ee9695b)
2026-01-21 17:21:00 +00:00
Mihir Kandoi
30e6b5daac fix: create DN btn should not be shown if it cannot be created
(cherry picked from commit 70ec977cb2)
2026-01-21 17:09:48 +00:00
Mihir Kandoi
0b5cc039b6 Merge pull request #51962 from frappe/mergify/bp/version-16-hotfix/pr-51958
fix!: force user to enter batch or serial for serial/batch items (backport #51958)
2026-01-21 16:38:31 +05:30
Mihir Kandoi
6fa60d2f1a fix: tests
(cherry picked from commit 035b3cb61e)
2026-01-21 10:53:46 +00:00
Mihir Kandoi
91199ea9c9 fix: force user to enter batch or serial for serial/batch items
(cherry picked from commit 7170a1bd78)
2026-01-21 10:53:46 +00:00
Mihir Kandoi
e23ba0e852 Merge pull request #51960 from frappe/mergify/bp/version-16-hotfix/pr-51947
fix: job cards should not be deleted on close of WO (backport #51947)
2026-01-21 16:03:09 +05:30
Mihir Kandoi
7c2bbe0d82 fix: job cards should not be deleted on close of WO
(cherry picked from commit c919b1de38)
2026-01-21 10:17:16 +00:00
Mihir Kandoi
0a2234a814 Merge pull request #51951 from frappe/mergify/bp/version-16-hotfix/pr-51948
fix: warehouse permissions in MR incorrectly ignored (backport #51948)
2026-01-21 14:05:14 +05:30
Mihir Kandoi
504c84e28a fix: warehouse permissions in MR incorrectly ignored
(cherry picked from commit 5bacb67d36)
2026-01-21 07:36:22 +00:00
ruthra kumar
bb2bada1fd Merge pull request #51945 from ruthra-kumar/reenable_auto_close
chore: reenable auto close
2026-01-21 10:16:58 +05:30
ruthra kumar
ca85ee33f5 chore: reenable auto close 2026-01-21 10:13:32 +05:30
ruthra kumar
21c1189e24 Merge pull request #51944 from ruthra-kumar/remove_junk_comment
chore: remove stray comment and disable auto close
2026-01-21 10:10:35 +05:30
ruthra kumar
7e7885b304 chore: remove stray comment and disable auto close 2026-01-21 10:09:03 +05:30
Frappe PR Bot
30238e3063 chore(release): Bumped to Version 16.1.0
# [16.1.0](https://github.com/frappe/erpnext/compare/v16.0.1...v16.1.0) (2026-01-20)

### Bug Fixes

* **accounts_controller:** make return message translatable ([621243c](621243c1d3))
* **accounts:** add missing accounting dimensions in advance taxes and charges ([673635e](673635e2c3))
* add below-0 column in ar/ap report (backport [#51673](https://github.com/frappe/erpnext/issues/51673)) ([#51780](https://github.com/frappe/erpnext/issues/51780)) ([5c93bf5](5c93bf5798))
* add company filters for warehouse ([ccab91b](ccab91b9ed))
* add other charges in total ([68c8dfb](68c8dfb24c))
* add uom js error ([a660ed0](a660ed061b))
* add validation for amount and hours ([ce421bb](ce421bb1d4))
* add validation for direct return ([bfd6375](bfd6375508))
* add validation for duplication ([84a749e](84a749e3d0))
* add validation for return against ([6dade11](6dade11d8f))
* allow creation of DN in SI for items not having DN reference ([fef6df7](fef6df709d))
* allow disassemble stock entry without work order (backport [#51761](https://github.com/frappe/erpnext/issues/51761)) ([#51836](https://github.com/frappe/erpnext/issues/51836)) ([c830bf6](c830bf6fc7))
* **bank_account:** validation for is_company_account ([5d5d208](5d5d208a49))
* **bom:** pass company warehouse filter ([3c533d0](3c533d04f5))
* **budget variance report:** check budget dimensions ([a3d860e](a3d860eabf))
* bugs ([accce1f](accce1fe59))
* calculate net profit amount from root node accounts ([89b44c4](89b44c41a2))
* change docfield type to render html format (backport [#51795](https://github.com/frappe/erpnext/issues/51795)) ([#51804](https://github.com/frappe/erpnext/issues/51804)) ([fcea760](fcea7603a8))
* common_party_path ([#51826](https://github.com/frappe/erpnext/issues/51826)) ([aeb2b60](aeb2b60450))
* continuous raw material consumption with bom validation (backport [#51914](https://github.com/frappe/erpnext/issues/51914)) ([#51919](https://github.com/frappe/erpnext/issues/51919)) ([c9d7c6c](c9d7c6cd42))
* docs_path ([86d5939](86d5939d91))
* dont show certain fields based on permissions ([d3dfed9](d3dfed909e))
* handle return cancellation ([65a1c70](65a1c7086b))
* include total hours validation in depends on ([cbfc137](cbfc13728b))
* **manufacturing:** consider process loss qty while validating the work order ([7b3f746](7b3f74609a))
* no attribute error on LCV ([fe59ace](fe59ace285))
* no attribute error on subcontracting receipt ([2131c7a](2131c7aadb))
* overproduction % not considered when making WO from SO ([fb669eb](fb669eb6f4))
* **pos:** reapply set warehouse during cart update ([6869115](686911546f))
* **postgres:** compute current month sales without DATE_FORMAT ([49760e4](49760e4542))
* prevent UOM from updating incorrectly while scanning barcode ([9d5a0e5](9d5a0e56a0))
* qty with serial no count ([ae6b3af](ae6b3af013))
* remove already transferred batch ([f1e41f4](f1e41f4a4f))
* setting process loss qty causes fg item qty to be incorrect ([cb2d455](cb2d4550af))
* Show non-SLE vouchers with GL entries in Stock vs Account Value Comparison report ([e64ae9a](e64ae9a8a9))
* **stock:** resolve quantity issue when adding items via barcode scan ([ab482ca](ab482caac9))
* **transaction.js:** use flt instead of cint for plc_conversion_rate ([8ba4701](8ba470160d))
* validation message in stock reco row idx ([176096b](176096bc5b))
* valuation rate for non batchwise valuation ([768c131](768c131073))

### Features

* add list_view status for partial billing ([9b88275](9b88275312))
* add new 2025 Charts of Accounts for France ([9cc9fa5](9cc9fa59be))
* Adding Item name in update item dialog box ([1da8ed2](1da8ed202b))
* modify field properties ([e49add2](e49add20b7))
* remove old French chart of accounts with code as nex 2025 is provided ([3bdaab1](3bdaab149b))
* support for serial item ([c4c2d35](c4c2d35565))
* **timesheet:** handle partial billing in sales invoice ([332673f](332673f260))

### Performance Improvements

* prevent duplicate reposting for the same item ([3ac431b](3ac431bd50))
2026-01-20 16:47:59 +00:00
ruthra kumar
35b3045b72 Merge pull request #51911 from frappe/version-16-hotfix
chore: release v16
2026-01-20 22:16:27 +05:30
Mihir Kandoi
cf130ff865 Merge pull request #51936 from frappe/mergify/bp/version-16-hotfix/pr-51934
fix: validation message in stock reco row idx (backport #51934)
2026-01-20 21:05:40 +05:30
Mihir Kandoi
176096bc5b fix: validation message in stock reco row idx
(cherry picked from commit 3960c01798)
2026-01-20 15:17:53 +00:00
rohitwaghchaure
e854eafc0b Merge pull request #51933 from frappe/mergify/bp/version-16-hotfix/pr-51930
Revert "perf: prevent duplicate reposting for the same item" (backport #51930)
2026-01-20 20:05:41 +05:30
rohitwaghchaure
72cdddbeda Revert "perf: prevent duplicate reposting for the same item"
(cherry picked from commit 6e4b90055f)
2026-01-20 14:19:47 +00:00
mergify[bot]
c9d7c6cd42 fix: continuous raw material consumption with bom validation (backport #51914) (#51919)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-01-20 12:56:27 +00:00
Diptanil Saha
8393c3c32d Merge pull request #51922 from frappe/mergify/bp/version-16-hotfix/pr-51887
fix(bank_account): `is_company_account` related validations (backport #51887)
2026-01-20 18:09:01 +05:30
Mihir Kandoi
5752d2e0a1 Merge pull request #51926 from frappe/mergify/bp/version-16-hotfix/pr-51909
fix: allow creation of DN in SI for items not having DN reference (backport #51909)
2026-01-20 18:03:35 +05:30
rohitwaghchaure
f25558b4d7 Merge pull request #51924 from frappe/mergify/bp/version-16-hotfix/pr-51920
perf: prevent duplicate reposting for the same item (backport #51920)
2026-01-20 18:01:21 +05:30
Mihir Kandoi
fef6df709d fix: allow creation of DN in SI for items not having DN reference
(cherry picked from commit b691de0147)
2026-01-20 12:14:59 +00:00
Rohit Waghchaure
3ac431bd50 perf: prevent duplicate reposting for the same item
(cherry picked from commit 7535931571)
2026-01-20 12:09:03 +00:00
diptanilsaha
5d5d208a49 fix(bank_account): validation for is_company_account
(cherry picked from commit 7532ab01d6)
2026-01-20 11:53:16 +00:00
ruthra kumar
9535f3d583 Merge pull request #51916 from frappe/mergify/bp/version-16-hotfix/pr-51671
fix(accounts): add missing accounting dimensions in advance taxes and charges (backport #51671)
2026-01-20 17:19:35 +05:30
Nikhil Kothari
673635e2c3 fix(accounts): add missing accounting dimensions in advance taxes and charges
(cherry picked from commit 22e9cb4cf4)

# Conflicts:
#	erpnext/patches.txt
2026-01-20 17:04:51 +05:30
Rohit Waghchaure
d067e37ab6 fix: validation to check at-least one raw material for manufacture entry
(cherry picked from commit f003b3c378)
2026-01-20 08:25:59 +00:00
Mihir Kandoi
37e241ba15 Merge pull request #51897 from frappe/mergify/bp/version-16-hotfix/pr-51895
fix: overproduction % not considered when making WO from SO (backport #51895)
2026-01-20 13:25:01 +05:30
Mihir Kandoi
fb669eb6f4 fix: overproduction % not considered when making WO from SO
(cherry picked from commit edba9efb5e)
2026-01-20 07:34:24 +00:00
ruthra kumar
232225d753 Merge pull request #51891 from frappe/mergify/bp/version-16-hotfix/pr-51561
fix: delete advance ledger entries  while reconciling payment entry (backport #51561)
2026-01-20 08:17:44 +05:30
ruthra kumar
80cbd851d1 Merge pull request #51893 from frappe/mergify/bp/version-16-hotfix/pr-51886
fix(accounts_controller): make return message translatable (backport #51886)
2026-01-20 08:13:12 +05:30
ruthra kumar
5474ac298d Merge pull request #51884 from frappe/mergify/bp/version-16-hotfix/pr-51830
fix(manufacturing): consider process loss qty while validating the work order (backport #51830)
2026-01-20 08:08:29 +05:30
ruthra kumar
7a9b10a05e Merge pull request #51861 from frappe/mergify/bp/version-16-hotfix/pr-51822
fix(budget variance report): check budget dimensions (backport #51822)
2026-01-20 07:56:49 +05:30
barredterra
621243c1d3 fix(accounts_controller): make return message translatable
(cherry picked from commit 0209f0fe29)
2026-01-20 02:26:48 +00:00
Lakshit Jain
efa5173964 Merge pull request #51561 from ljain112/fic-adv-ple-po
fix: delete advance ledger entries  while reconciling payment entry
(cherry picked from commit aea70c5ec1)
2026-01-20 02:21:34 +00:00
Sudharsanan11
7b3f74609a fix(manufacturing): consider process loss qty while validating the work order
(cherry picked from commit e6366e830c)
2026-01-19 16:18:36 +00:00
Mihir Kandoi
775f6d07b1 Merge pull request #51882 from frappe/mergify/bp/version-16-hotfix/pr-51880
fix: no attribute error on LCV (backport #51880)
2026-01-19 20:30:03 +05:30
Mihir Kandoi
e80ed14456 Merge pull request #51881 from frappe/mergify/bp/version-16-hotfix/pr-51879
fix: no attribute error on subcontracting receipt (backport #51879)
2026-01-19 20:15:55 +05:30
Mihir Kandoi
fe59ace285 fix: no attribute error on LCV
(cherry picked from commit ad11914fca)
2026-01-19 14:35:22 +00:00
Mihir Kandoi
2131c7aadb fix: no attribute error on subcontracting receipt
(cherry picked from commit fbac8b032e)
2026-01-19 14:30:08 +00:00
Diptanil Saha
b7284c7717 Merge pull request #51877 from frappe/mergify/bp/version-16-hotfix/pr-51595 2026-01-19 18:12:07 +05:30
Florian HENRY
6b9107c05c chore: re add older template
(cherry picked from commit b3efb3084f)
2026-01-19 12:23:31 +00:00
Florian HENRY
1ed8857d31 chore: fix bank account type
(cherry picked from commit 4fe1b214c1)
2026-01-19 12:23:31 +00:00
Florian HENRY
a195690bc8 chore: fix CASH acount type
(cherry picked from commit 6a876de838)
2026-01-19 12:23:31 +00:00
Florian HENRY
0c546c9e5a chore: fix bank acount type
(cherry picked from commit 765487a087)
2026-01-19 12:23:30 +00:00
Florian HENRY
11d9fd3dee chore: add Expenses Included In Valuation account
(cherry picked from commit c519cd0268)
2026-01-19 12:23:30 +00:00
Florian HENRY
3bdaab149b feat: remove old French chart of accounts with code as nex 2025 is provided
(cherry picked from commit bf430fce09)
2026-01-19 12:23:30 +00:00
Florian HENRY
ad4ac4e53c chore: Review PR #51595
(cherry picked from commit 6bdaeb983d)
2026-01-19 12:23:30 +00:00
Florian HENRY
9cc9fa59be feat: add new 2025 Charts of Accounts for France
(cherry picked from commit c81dee137f)
2026-01-19 12:23:30 +00:00
rohitwaghchaure
ab2aedd9a2 Merge pull request #51866 from frappe/mergify/bp/version-16-hotfix/pr-51769
fix(pos): reapply set warehouse during cart update (backport #51769)
2026-01-19 15:44:38 +05:30
ravibharathi656
686911546f fix(pos): reapply set warehouse during cart update
(cherry picked from commit 5a53c45321)
2026-01-19 10:07:50 +00:00
rohitwaghchaure
4f3078ab1a Merge pull request #51863 from frappe/mergify/bp/version-16-hotfix/pr-51690
feat: Adding Item name in update item dialog box (backport #51690)
2026-01-19 15:34:47 +05:30
rohitwaghchaure
a950adab79 Merge pull request #51864 from frappe/mergify/bp/version-16-hotfix/pr-51856
fix: qty with serial no count (backport #51856)
2026-01-19 15:34:23 +05:30
Rohit Waghchaure
ae6b3af013 fix: qty with serial no count
(cherry picked from commit 56e58ef301)
2026-01-19 10:00:35 +00:00
Nishka Gosalia
1da8ed202b feat: Adding Item name in update item dialog box
(cherry picked from commit e6133ad6d4)
2026-01-19 10:00:34 +00:00
ervishnucs
a3d860eabf fix(budget variance report): check budget dimensions
(cherry picked from commit cb696a8880)
2026-01-19 09:55:17 +00:00
rohitwaghchaure
adc9dc82ca Merge pull request #51848 from frappe/mergify/bp/version-16-hotfix/pr-51644
Refactor batch bundle get snos sle (backport #51644)
2026-01-19 15:15:20 +05:30
Rohit Waghchaure
79e04ea1fe chore: fix semantic commit message
(cherry picked from commit dfcbee9cc0)
2026-01-19 09:22:45 +00:00
krupalvora
0981b894dd refactor: Batch & Bundle get sle for snos - Added docstring
(cherry picked from commit 22dee50348)
2026-01-19 09:22:45 +00:00
krupalvora
380564a677 refactor: Batch & Bundle get Stock ledger for snos - added posting date in select
(cherry picked from commit 1ccc7365a7)
2026-01-19 09:22:45 +00:00
krupalvora
75deb180fb refactor: Batch & Bundle get Stock ledger for snos v2
(cherry picked from commit a074d81754)
2026-01-19 09:22:45 +00:00
krupalvora
839315752b refactor: Batch & Bundle get Stock ledger for snos
(cherry picked from commit c0149925ad)
2026-01-19 09:22:44 +00:00
Mihir Kandoi
2b4a547e23 Merge pull request #51847 from frappe/mergify/bp/version-16-hotfix/pr-51845
fix(bom): pass company warehouse filter (backport #51845)
2026-01-19 14:18:39 +05:30
22-poojashree
3c533d04f5 fix(bom): pass company warehouse filter
(cherry picked from commit 73bcfc4710)
2026-01-19 08:34:16 +00:00
ruthra kumar
1c214eec98 Merge pull request #51844 from frappe/mergify/bp/version-16-hotfix/pr-51826
fix: common_party_path (backport #51826)
2026-01-19 13:21:14 +05:30
mahsem
aeb2b60450 fix: common_party_path (#51826)
* fix: common_pary_path

* chore: remove non-existent anchor

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit 0c0f43f7f7)
2026-01-19 07:50:30 +00:00
ruthra kumar
1b3f5e1c96 Merge pull request #51841 from frappe/mergify/bp/version-16-hotfix/pr-51513
fix: calculate net profit amount from root node accounts (backport #51513)
2026-01-19 13:05:56 +05:30
mergify[bot]
c830bf6fc7 fix: allow disassemble stock entry without work order (backport #51761) (#51836)
fix: allow disassemble stock entry without work order (#51761)

* fix: allow disassemble stock entry without work order

* fix: use existing functionality to load fg item

* chore: better dict update

(cherry picked from commit 83919119f8)

Co-authored-by: Smit Vora <smitvora203@gmail.com>
2026-01-19 12:54:06 +05:30
ruthra kumar
e2b95da24d Merge pull request #51839 from frappe/mergify/bp/version-16-hotfix/pr-51787
fix: recalculate taxes when item tax template changes after discount (backport #51787)
2026-01-19 12:50:34 +05:30
Navin-S-R
89b44c41a2 fix: calculate net profit amount from root node accounts
(cherry picked from commit c84986d00e)
2026-01-19 07:15:50 +00:00
Lakshit Jain
181141b56a Merge pull request #51787 from ljain112/fix-taxes-disc
fix: recalculate taxes when item tax template changes after discount
(cherry picked from commit f00aeec9b4)
2026-01-19 07:01:52 +00:00
ruthra kumar
5742a5d86a Merge pull request #51834 from frappe/mergify/bp/version-16-hotfix/pr-51742
fix: add other charges in total (backport #51742)
2026-01-19 11:34:22 +05:30
SowmyaArunachalam
68c8dfb24c fix: add other charges in total
(cherry picked from commit 9406c07c42)
2026-01-19 05:45:20 +00:00
Mihir Kandoi
7f54de7926 Merge pull request #51829 from frappe/mergify/bp/version-16-hotfix/pr-51824
fix: setting process loss qty causes fg item qty to be incorrect (backport #51824)
2026-01-18 22:51:58 +05:30
Mihir Kandoi
cb2d4550af fix: setting process loss qty causes fg item qty to be incorrect
(cherry picked from commit 56f5df6847)
2026-01-18 17:21:03 +00:00
Mihir Kandoi
c22d7e16d1 Merge pull request #51821 from frappe/mergify/bp/version-16-hotfix/pr-51817
fix: prevent UOM from updating incorrectly while scanning barcode (backport #51817)
2026-01-18 15:10:56 +05:30
Pandiyan5273
9d5a0e56a0 fix: prevent UOM from updating incorrectly while scanning barcode
(cherry picked from commit 30263b26a5)
2026-01-18 09:36:44 +00:00
mergify[bot]
fcea7603a8 fix: change docfield type to render html format (backport #51795) (#51804)
fix: change docfield type to render html format (#51795)

(cherry picked from commit 3fe5b5c80d)

Co-authored-by: Sowmya <106989392+SowmyaArunachalam@users.noreply.github.com>
2026-01-17 15:12:57 +05:30
ruthra kumar
42ebb7446a Merge pull request #51797 from frappe/mergify/bp/version-16-hotfix/pr-51555
fix(postgres): compute current month sales without DATE_FORMAT (backport #51555)
2026-01-16 17:15:48 +05:30
Matt Howard
49760e4542 fix(postgres): compute current month sales without DATE_FORMAT
(cherry picked from commit 64f391adf7)
2026-01-16 11:29:08 +00:00
Mihir Kandoi
6e1f4d84b6 Merge pull request #51794 from frappe/mergify/bp/version-16-hotfix/pr-51790
fix(stock): resolve quantity issue when adding items via barcode scan (backport #51790)
2026-01-16 16:20:37 +05:30
Pandiyan5273
ab482caac9 fix(stock): resolve quantity issue when adding items via barcode scan
(cherry picked from commit f959b2c59a)
2026-01-16 10:49:36 +00:00
Mihir Kandoi
d8506fb2c0 Merge pull request #51792 from frappe/mergify/bp/version-16-hotfix/pr-51791
fix: dont show certain fields based on permissions (backport #51791)
2026-01-16 16:02:37 +05:30
Mihir Kandoi
d3dfed909e fix: dont show certain fields based on permissions
(cherry picked from commit b3db2981de)
2026-01-16 10:31:39 +00:00
Mihir Kandoi
ac31c5ca19 Merge pull request #51789 from frappe/mergify/bp/version-16-hotfix/pr-51784
fix: add company filters for warehouse (backport #51784)
2026-01-16 15:16:05 +05:30
SowmyaArunachalam
ccab91b9ed fix: add company filters for warehouse
(cherry picked from commit f952b92d71)
2026-01-16 09:44:42 +00:00
Mihir Kandoi
541a8b135a Merge pull request #51785 from frappe/mergify/bp/version-16-hotfix/pr-51693 2026-01-16 14:09:49 +05:30
Mihir Kandoi
c0a30a5302 chore: typo
(cherry picked from commit 8fd1d6aec8)
2026-01-16 08:25:03 +00:00
Mihir Kandoi
accce1fe59 fix: bugs
(cherry picked from commit 19ae405742)
2026-01-16 08:25:03 +00:00
Mihir Kandoi
f04221417e test: add test case
(cherry picked from commit b567184dd7)
2026-01-16 08:25:03 +00:00
Mihir Kandoi
c4c2d35565 feat: support for serial item
(cherry picked from commit 3d0f649411)
2026-01-16 08:25:02 +00:00
Mihir Kandoi
f1e41f4a4f fix: remove already transferred batch
(cherry picked from commit b54067e04d)
2026-01-16 08:25:02 +00:00
Mihir Kandoi
d9326d80de refactor: sample retention stock entry
(cherry picked from commit 8d188cd32b)
2026-01-16 08:25:02 +00:00
ruthra kumar
5c93bf5798 fix: add below-0 column in ar/ap report (backport #51673) (#51780)
Merge pull request #51673 from Jatin3128/ar/ap-future-range-fix

fix: add below-0 column in ar/ap report
(cherry picked from commit c5b0787de6)

Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
2026-01-16 12:46:26 +05:30
Jatin3128
f62ad83d6f Merge pull request #51673 from Jatin3128/ar/ap-future-range-fix
fix: add below-0 column in ar/ap report
(cherry picked from commit c5b0787de6)
2026-01-16 06:37:34 +00:00
Ankush Menat
876e2d4e6e build: Update Frappe dependency (#51779) 2026-01-16 11:24:43 +05:30
rohitwaghchaure
4977e06c50 Merge pull request #51772 from frappe/mergify/bp/version-16-hotfix/pr-51768
fix: Show non-SLE vouchers with GL entries in Stock vs Account Value … (backport #51768)
2026-01-15 19:26:45 +05:30
Rohit Waghchaure
e64ae9a8a9 fix: Show non-SLE vouchers with GL entries in Stock vs Account Value Comparison report
(cherry picked from commit 1db9ce205f)
2026-01-15 12:20:36 +00:00
rohitwaghchaure
6ddf4eee15 Merge pull request #51752 from frappe/mergify/bp/version-16-hotfix/pr-51729
fix: valuation rate for non batchwise valuation (backport #51729)
2026-01-15 17:00:32 +05:30
Mihir Kandoi
d27a09cb9f Merge pull request #51755 from frappe/mergify/bp/version-16-hotfix/pr-51753
fix: docs_path (backport #51753)
2026-01-14 21:31:31 +05:30
mahsem
86d5939d91 fix: docs_path
(cherry picked from commit 7ef8c81caf)
2026-01-14 16:00:03 +00:00
Rohit Waghchaure
768c131073 fix: valuation rate for non batchwise valuation
(cherry picked from commit b6312bca9c)
2026-01-14 14:06:50 +00:00
Diptanil Saha
8f77223057 Merge pull request #51748 from frappe/mergify/bp/version-16-hotfix/pr-51730
fix(transaction.js): use flt instead of cint for plc_conversion_rate (backport #51730)
2026-01-14 15:55:13 +05:30
diptanilsaha
8ba470160d fix(transaction.js): use flt instead of cint for plc_conversion_rate
(cherry picked from commit 8b445e04e5)
2026-01-14 10:22:21 +00:00
Mihir Kandoi
1a6264d831 Merge pull request #51737 from frappe/mergify/bp/version-16-hotfix/pr-51684 2026-01-14 11:37:00 +05:30
Mihir Kandoi
19a90c0980 Merge pull request #51736 from frappe/mergify/bp/version-16-hotfix/pr-51295 2026-01-14 11:18:57 +05:30
Pandiyan5273
d57fc49896 test(stock-entry): manufacture entry without work order
(cherry picked from commit 784e338be4)
2026-01-14 05:34:40 +00:00
l0gesh29
fe0431a6d0 chore: modify error msg
(cherry picked from commit f7004aa8c3)
2026-01-14 05:33:39 +00:00
l0gesh29
bfd6375508 fix: add validation for direct return
(cherry picked from commit 8379b39aaf)
2026-01-14 05:33:38 +00:00
l0gesh29
6dade11d8f fix: add validation for return against
(cherry picked from commit ff9b936634)
2026-01-14 05:33:38 +00:00
Logesh Periyasamy
ce421bb1d4 fix: add validation for amount and hours
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
(cherry picked from commit 43d1d685c6)
2026-01-14 05:33:38 +00:00
l0gesh29
84a749e3d0 fix: add validation for duplication
(cherry picked from commit cda8a97f4a)
2026-01-14 05:33:37 +00:00
l0gesh29
65a1c7086b fix: handle return cancellation
(cherry picked from commit 50f73a5072)
2026-01-14 05:33:37 +00:00
l0gesh29
a04da71182 test: add test for partial billing and return
(cherry picked from commit ae594e81f9)
2026-01-14 05:33:37 +00:00
l0gesh29
cbfc13728b fix: include total hours validation in depends on
(cherry picked from commit 57d34ab146)
2026-01-14 05:33:37 +00:00
l0gesh29
9b88275312 feat: add list_view status for partial billing
(cherry picked from commit ff0b37055b)
2026-01-14 05:33:36 +00:00
l0gesh29
332673f260 feat(timesheet): handle partial billing in sales invoice
(cherry picked from commit c87b5d3132)
2026-01-14 05:33:36 +00:00
l0gesh29
e49add20b7 feat: modify field properties
(cherry picked from commit 38a4642479)
2026-01-14 05:33:36 +00:00
Mihir Kandoi
c3b0633eda Merge pull request #51734 from frappe/mergify/bp/version-16-hotfix/pr-51733
fix: add uom js error (backport #51733)
2026-01-14 10:28:24 +05:30
Mihir Kandoi
a660ed061b fix: add uom js error
(cherry picked from commit 6d3f6d73d0)
2026-01-14 04:55:22 +00:00
Frappe PR Bot
0b1c0c36b5 chore(release): Bumped to Version 16.0.1
## [16.0.1](https://github.com/frappe/erpnext/compare/v16.0.0...v16.0.1) (2026-01-13)

### Bug Fixes

* **asset value adjustment:** skip cancelling revaluation journal entry if already cancelled (backport [#51666](https://github.com/frappe/erpnext/issues/51666)) ([#51716](https://github.com/frappe/erpnext/issues/51716)) ([4b85d51](4b85d51257))
* Redirect to Desktop after signup ([#51696](https://github.com/frappe/erpnext/issues/51696)) ([0363b01](0363b01ab7))
* Redirect to Desktop after signup ([#51696](https://github.com/frappe/erpnext/issues/51696)) ([#51697](https://github.com/frappe/erpnext/issues/51697)) ([294fb27](294fb27dc8))
* Redirect to Desktop after signup (backport [#51696](https://github.com/frappe/erpnext/issues/51696)) ([#51714](https://github.com/frappe/erpnext/issues/51714)) ([2118321](211832104c))
* stock module not opened when no warehouses ([3420e21](3420e21d45))
* **tds:** correct tax logic for customer ([50ce61a](50ce61ae02))
2026-01-13 16:20:34 +00:00
Mihir Kandoi
d316ef2306 Merge pull request #51728 from frappe/trigger-release-v16 2026-01-13 21:44:48 +05:30
Mihir Kandoi
af3a7903b3 chore: trigger release 2026-01-13 21:43:30 +05:30
Mihir Kandoi
a66e114a71 Merge pull request #51727 from frappe/change-release-branch 2026-01-13 21:34:26 +05:30
Mihir Kandoi
631b9d3bb0 chore: update release branch from version-13 to version-16 2026-01-13 20:32:48 +05:30
Mihir Kandoi
eb03781718 Merge pull request #51713 from frappe/version-16-hotfix 2026-01-13 20:17:06 +05:30
rohitwaghchaure
eb7cebac91 Merge pull request #51720 from frappe/mergify/bp/version-16-hotfix/pr-51719
fix: stock module not opened when no warehouses (backport #51719)
2026-01-13 17:38:25 +05:30
Rohit Waghchaure
3420e21d45 fix: stock module not opened when no warehouses
(cherry picked from commit 9de3b07223)
2026-01-13 11:49:56 +00:00
Mihir Kandoi
211832104c fix: Redirect to Desktop after signup (backport #51696) (#51714)
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
fix: Redirect to Desktop after signup (#51696)
2026-01-13 16:07:20 +05:30
mergify[bot]
4b85d51257 fix(asset value adjustment): skip cancelling revaluation journal entry if already cancelled (backport #51666) (#51716)
Co-authored-by: Navin-S-R <navin@aerele.in>
2026-01-13 16:05:14 +05:30
Nabin Hait
0363b01ab7 fix: Redirect to Desktop after signup (#51696)
(cherry picked from commit 3bc58fb46f)
2026-01-13 09:46:21 +00:00
Mihir Kandoi
fc517f7fa2 Merge pull request #51707 from mihir-kandoi/ci-patch-test-2 2026-01-13 15:11:37 +05:30
ruthra kumar
18451b69e6 Merge pull request #51703 from frappe/mergify/bp/version-16-hotfix/pr-51412
fix(tds): correct tax logic for customer (backport #51412)
2026-01-13 11:41:28 +05:30
ljain112
50ce61ae02 fix(tds): correct tax logic for customer
(cherry picked from commit 86b0f67dbc)
2026-01-13 05:37:32 +00:00
Nabin Hait
294fb27dc8 fix: Redirect to Desktop after signup (#51696) (#51697) 2026-01-12 19:22:24 +05:30
Rohit Waghchaure
c3fdb191b9 Merge branch 'develop' into version-16 2026-01-12 16:20:48 +05:30
Rohit Waghchaure
93db2ebd6f Merge branch 'develop' into version-16 2026-01-12 15:07:27 +05:30
rohitwaghchaure
2925f9a04e chore: weekly release for v16 2026-01-12 14:29:14 +05:30
rohitwaghchaure
c7bf103c0c chore: fix version 2026-01-12 14:04:45 +05:30
rohitwaghchaure
6dead8fd85 chore: fix version 2026-01-12 12:19:58 +05:30
258 changed files with 16776 additions and 4599 deletions

View File

@@ -60,7 +60,7 @@ body:
description: Share exact version number of Frappe and ERPNext you are using.
placeholder: |
Frappe version -
ERPNext Verion -
ERPNext version -
validations:
required: true

View File

@@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: ["14", "15"]
version: ["14", "15", "16"]
steps:
- uses: octokit/request-action@v2.x

View File

@@ -113,8 +113,8 @@ jobs:
jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json
mv tmp.json ~/frappe-bench/sites/test_site/site_config.json
wget https://erpnext.com/files/v13-erpnext.sql.gz
bench --site test_site --force restore ~/frappe-bench/v13-erpnext.sql.gz
wget https://frappe.io/files/erpnext-v14.sql.gz
bench --site test_site --force restore ~/frappe-bench/erpnext-v14.sql.gz
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
@@ -142,7 +142,6 @@ jobs:
bench --site test_site migrate
}
update_to_version 14 3.11
update_to_version 15 3.13
echo "Updating to latest version"

View File

@@ -2,7 +2,7 @@ name: Generate Semantic Release
on:
push:
branches:
- version-13
- version-16
permissions:
contents: read

View File

@@ -7,6 +7,7 @@ on:
paths:
- "**.js"
- "**.css"
- "**.svg"
- "**.md"
- "**.html"
- 'crowdin.yml'

View File

@@ -1,5 +1,5 @@
{
"branches": ["version-13"],
"branches": ["version-16"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular",
@@ -21,4 +21,4 @@
],
"@semantic-release/github"
]
}
}

View File

@@ -1,3 +1,4 @@
<div align="center">
<a href="https://frappe.io/erpnext">
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>

View File

@@ -6,7 +6,7 @@ import frappe
from frappe.model.document import Document
from frappe.utils.user import is_website_user
__version__ = "16.0.0-dev"
__version__ = "16.6.1"
def get_default_company(user=None):

View File

@@ -0,0 +1,50 @@
{
"cards": [
{
"card": "Total Outgoing Bills"
},
{
"card": "Total Incoming Bills"
},
{
"card": "Total Incoming Payment"
},
{
"card": "Total Outgoing Payment"
}
],
"charts": [
{
"chart": "Incoming Bills (Purchase Invoice)",
"width": "Half"
},
{
"chart": "Outgoing Bills (Sales Invoice)",
"width": "Half"
},
{
"chart": "Accounts Receivable Ageing",
"width": "Half"
},
{
"chart": "Accounts Payable Ageing",
"width": "Half"
},
{
"chart": "Bank Balance",
"width": "Full"
}
],
"creation": "2026-01-26 21:25:12.793893",
"dashboard_name": "Payments",
"docstatus": 0,
"doctype": "Dashboard",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"modified": "2026-01-26 21:25:12.793893",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payments",
"owner": "Administrator"
}

View File

@@ -33,6 +33,17 @@
},
"account_number": "1151.000"
},
"Pajak Dibayar di Muka": {
"PPN Masukan": {
"account_number": "1152.001",
"account_type": "Tax"
},
"PPh 23 Dibayar di Muka": {
"account_number": "1152.002",
"account_type": "Tax"
},
"account_number": "1152.000"
},
"account_number": "1150.000"
},
"Kas": {
@@ -97,17 +108,6 @@
},
"account_number": "1130.000"
},
"Pajak Dibayar di Muka": {
"PPN Masukan": {
"account_number": "1151.001",
"account_type": "Tax"
},
"PPh 23 Dibayar di Muka": {
"account_number": "1152.001",
"account_type": "Tax"
},
"account_number": "1150.000"
},
"account_number": "1100.000"
},

View File

@@ -281,7 +281,7 @@
},
{
"default": "0",
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
@@ -640,7 +640,7 @@
}
],
"grid_page_length": 50,
"hide_toolbar": 1,
"hide_toolbar": 0,
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,

View File

@@ -50,6 +50,7 @@
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"options": "currency",
"read_only": 1
},
{

View File

@@ -3,9 +3,6 @@
frappe.provide("erpnext.integrations");
frappe.ui.form.on("Bank", {
onload: function (frm) {
add_fields_to_mapping_table(frm);
},
refresh: function (frm) {
add_fields_to_mapping_table(frm);
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
@@ -37,11 +34,11 @@ let add_fields_to_mapping_table = function (frm) {
});
});
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
"bank_transaction_field",
"options",
options
);
const grid = frm.fields_dict.bank_transaction_mapping?.grid;
if (grid) {
grid.update_docfield_property("bank_transaction_field", "options", options);
}
};
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
@@ -116,7 +113,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
)
);
console.log(error);
console.error(error);
}
plaid_success(token, response) {

View File

@@ -42,8 +42,4 @@ frappe.ui.form.on("Bank Account", {
});
}
},
is_company_account: function (frm) {
frm.set_df_property("account", "reqd", frm.doc.is_company_account);
},
});

View File

@@ -52,6 +52,7 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company Account",
"mandatory_depends_on": "is_company_account",
"options": "Account"
},
{
@@ -98,6 +99,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"mandatory_depends_on": "is_company_account",
"options": "Company"
},
{
@@ -252,7 +254,7 @@
"link_fieldname": "default_bank_account"
}
],
"modified": "2025-08-29 12:32:01.081687",
"modified": "2026-01-20 00:46:16.633364",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",

View File

@@ -51,25 +51,29 @@ class BankAccount(Document):
delete_contact_and_address("Bank Account", self.name)
def validate(self):
self.validate_company()
self.validate_account()
self.validate_is_company_account()
self.update_default_bank_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def validate_is_company_account(self):
if self.is_company_account:
if not self.company:
frappe.throw(_("Company is mandatory for company account"))
def validate_company(self):
if self.is_company_account and not self.company:
frappe.throw(_("Company is mandatory for company account"))
if not self.account:
frappe.throw(_("Company Account is mandatory"))
self.validate_account()
def validate_account(self):
if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def update_default_bank_account(self):
if self.is_default and not self.disabled:

View File

@@ -541,7 +541,7 @@ class FinancialQueryBuilder:
.where(acb_table.period_closing_voucher == closing_voucher)
)
query = self._apply_standard_filters(query, acb_table)
query = self._apply_standard_filters(query, acb_table, "Account Closing Balance")
results = self._execute_with_permissions(query, "Account Closing Balance")
for row in results:
@@ -636,12 +636,15 @@ class FinancialQueryBuilder:
return self._execute_with_permissions(query, "GL Entry")
def _calculate_running_balances(self, balances_data: dict, gl_data: list[dict]) -> dict:
for row in gl_data:
account = row["account"]
gl_dict = {row["account"]: row for row in gl_data}
accounts = set(balances_data.keys()) | set(gl_dict.keys())
for account in accounts:
if account not in balances_data:
balances_data[account] = AccountData(account=account, **self._get_account_meta(account))
account_data: AccountData = balances_data[account]
gl_movement = gl_dict.get(account, {})
if account_data.has_periods():
first_period = account_data.get_period(self.periods[0]["key"])
@@ -651,20 +654,13 @@ class FinancialQueryBuilder:
for period in self.periods:
period_key = period["key"]
movement = row.get(period_key, 0.0)
movement = gl_movement.get(period_key, 0.0)
closing_balance = current_balance + movement
account_data.add_period(PeriodValue(period_key, current_balance, closing_balance, movement))
current_balance = closing_balance
# Accounts with no movements
for account_data in balances_data.values():
for period in self.periods:
period_key = period["key"]
if period_key not in account_data.period_values:
account_data.add_period(PeriodValue(period_key, 0.0, 0.0, 0.0))
def _handle_balance_accumulation(self, balances_data):
for account_data in balances_data.values():
account_data: AccountData
@@ -683,12 +679,12 @@ class FinancialQueryBuilder:
else:
account_data.unaccumulate_values()
def _apply_standard_filters(self, query, table):
def _apply_standard_filters(self, query, table, doctype: str = "GL Entry"):
if self.filters.get("ignore_closing_entries"):
if hasattr(table, "is_period_closing_voucher_entry"):
query = query.where(table.is_period_closing_voucher_entry == 0)
else:
if doctype == "GL Entry":
query = query.where(table.voucher_type != "Period Closing Voucher")
else:
query = query.where(table.is_period_closing_voucher_entry == 0)
if self.filters.get("project"):
projects = self.filters.get("project")

View File

@@ -16,7 +16,8 @@ from erpnext.accounts.doctype.financial_report_template.financial_report_engine
from erpnext.accounts.doctype.financial_report_template.test_financial_report_template import (
FinancialReportTemplateTestCase,
)
from erpnext.accounts.utils import get_currency_precision
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.utils import get_currency_precision, get_fiscal_year
# On IntegrationTestCase, the doctype test records and all
# link-field test record dependencies are recursively loaded
@@ -1672,3 +1673,360 @@ class TestFilterExpressionParser(FinancialReportTemplateTestCase):
mock_row_invalid = self._create_mock_report_row(invalid_formula)
condition = parser.build_condition(mock_row_invalid, account_table)
self.assertIsNone(condition)
class TestFinancialQueryBuilder(FinancialReportTemplateTestCase):
def test_fetch_balances_with_journal_entries(self):
company = "_Test Company"
cash_account = "_Test Cash - _TC"
bank_account = "_Test Bank - _TC"
# Create journal entries in different periods
# October: Transfer 1000 from Bank to Cash
jv_oct = make_journal_entry(
account1=cash_account,
account2=bank_account,
amount=1000,
posting_date="2024-10-15",
company=company,
submit=True,
)
# November: Transfer 500 from Bank to Cash
jv_nov = make_journal_entry(
account1=cash_account,
account2=bank_account,
amount=500,
posting_date="2024-11-20",
company=company,
submit=True,
)
# December: No transactions (test zero movement period)
try:
# Set up filters and periods for Q4 2024
filters = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-10-01",
"period_end_date": "2024-12-31",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
}
periods = [
{"key": "2024_oct", "from_date": "2024-10-01", "to_date": "2024-10-31"},
{"key": "2024_nov", "from_date": "2024-11-01", "to_date": "2024-11-30"},
{"key": "2024_dec", "from_date": "2024-12-01", "to_date": "2024-12-31"},
]
query_builder = FinancialQueryBuilder(filters, periods)
# Create account objects as expected by fetch_account_balances
accounts = [
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
frappe._dict({"name": bank_account, "account_name": "Bank", "account_number": "1002"}),
]
# Fetch balances using the full workflow
balances_data = query_builder.fetch_account_balances(accounts)
# Verify Cash account balances
cash_data = balances_data.get(cash_account)
self.assertIsNotNone(cash_data, "Cash account should exist in results")
# October: movement = +1000 (debit)
oct_cash = cash_data.get_period("2024_oct")
self.assertIsNotNone(oct_cash, "October period should exist for cash")
self.assertEqual(oct_cash.movement, 1000.0, "October cash movement should be 1000")
# November: movement = +500
nov_cash = cash_data.get_period("2024_nov")
self.assertIsNotNone(nov_cash, "November period should exist for cash")
self.assertEqual(nov_cash.movement, 500.0, "November cash movement should be 500")
self.assertEqual(
nov_cash.opening, oct_cash.closing, "November opening should equal October closing"
)
# December: movement = 0 (no transactions)
dec_cash = cash_data.get_period("2024_dec")
self.assertIsNotNone(dec_cash, "December period should exist for cash")
self.assertEqual(dec_cash.movement, 0.0, "December cash movement should be 0")
self.assertEqual(
dec_cash.closing,
nov_cash.closing,
"December closing should equal November closing when no movement",
)
# Verify Bank account balances (opposite direction)
bank_data = balances_data.get(bank_account)
self.assertIsNotNone(bank_data, "Bank account should exist in results")
oct_bank = bank_data.get_period("2024_oct")
self.assertEqual(oct_bank.movement, -1000.0, "October bank movement should be -1000")
nov_bank = bank_data.get_period("2024_nov")
self.assertEqual(nov_bank.movement, -500.0, "November bank movement should be -500")
finally:
# Clean up: cancel journal entries
jv_nov.cancel()
jv_oct.cancel()
def test_opening_balance_from_previous_period_closing(self):
company = "_Test Company"
cash_account = "_Test Cash - _TC"
sales_account = "Sales - _TC"
posting_date_2023 = "2023-06-15"
# Create journal entry in prior period (2023)
# Cash Dr 5000, Sales Cr 5000
jv_2023 = make_journal_entry(
account1=cash_account,
account2=sales_account,
amount=5000,
posting_date=posting_date_2023,
company=company,
submit=True,
)
pcv = None
jv_2024 = None
original_pcv_setting = frappe.db.get_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv"
)
try:
# Create Period Closing Voucher for 2023
# This will create Account Closing Balance entries
closing_account = frappe.db.get_value(
"Account",
{
"company": company,
"root_type": "Liability",
"is_group": 0,
"account_type": ["not in", ["Payable", "Receivable"]],
},
"name",
)
fy_2023 = get_fiscal_year(posting_date_2023, company=company)
frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1)
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": "2023-12-31",
"period_start_date": fy_2023[1],
"period_end_date": fy_2023[2],
"company": company,
"fiscal_year": fy_2023[0],
"cost_center": "_Test Cost Center - _TC",
"closing_account_head": closing_account,
"remarks": "Test Period Closing",
}
)
pcv.insert()
pcv.submit()
pcv.reload()
# Now create a small transaction in 2024 to ensure the account appears
jv_2024 = make_journal_entry(
account1=cash_account,
account2=sales_account,
amount=100,
posting_date="2024-01-15",
company=company,
submit=True,
)
# Set up filters for Q1 2024 (after the period closing)
filters = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-01-01",
"period_end_date": "2024-03-31",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
"ignore_closing_entries": True, # Don't include PCV entries in movements
}
periods = [
{"key": "2024_jan", "from_date": "2024-01-01", "to_date": "2024-01-31"},
{"key": "2024_feb", "from_date": "2024-02-01", "to_date": "2024-02-29"},
{"key": "2024_mar", "from_date": "2024-03-01", "to_date": "2024-03-31"},
]
query_builder = FinancialQueryBuilder(filters, periods)
accounts = [
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
]
balances_data = query_builder.fetch_account_balances(accounts)
# Verify Cash account has opening balance from 2023 transactions
cash_data = balances_data.get(cash_account)
self.assertIsNotNone(cash_data, "Cash account should exist in results")
jan_cash = cash_data.get_period("2024_jan")
self.assertIsNotNone(jan_cash, "January period should exist")
# Opening balance should be from prior period
# Cash had 5000 debit in 2023, so opening in 2024 should be >= 5000
# (may be higher if there were other test transactions)
self.assertEqual(
jan_cash.opening,
5000.0,
"January opening should equal to balance from 2023 (5000)",
)
# Verify running balance logic
# Movement in January is 100 (from jv_2024)
self.assertEqual(jan_cash.movement, 100.0, "January movement should be 100")
self.assertEqual(
jan_cash.closing, jan_cash.opening + jan_cash.movement, "Closing = Opening + Movement"
)
# February and March should have no movement but carry the balance
feb_cash = cash_data.get_period("2024_feb")
self.assertEqual(feb_cash.opening, jan_cash.closing, "Feb opening = Jan closing")
self.assertEqual(feb_cash.movement, 0.0, "February should have no movement")
self.assertEqual(feb_cash.closing, feb_cash.opening, "Feb closing = opening when no movement")
mar_cash = cash_data.get_period("2024_mar")
self.assertEqual(mar_cash.opening, feb_cash.closing, "Mar opening = Feb closing")
self.assertEqual(mar_cash.movement, 0.0, "March should have no movement")
self.assertEqual(mar_cash.closing, mar_cash.opening, "Mar closing = opening when no movement")
# Set up filters for Q2 2024
filters_q2 = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-04-01",
"period_end_date": "2024-06-30",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
"ignore_closing_entries": True,
}
periods_q2 = [
{"key": "2024_apr", "from_date": "2024-04-01", "to_date": "2024-04-30"},
{"key": "2024_may", "from_date": "2024-05-01", "to_date": "2024-05-31"},
{"key": "2024_jun", "from_date": "2024-06-01", "to_date": "2024-06-30"},
]
query_builder_q2 = FinancialQueryBuilder(filters_q2, periods_q2)
balances_data_q2 = query_builder_q2.fetch_account_balances(accounts)
# Verify Cash account in Q2
cash_data_q2 = balances_data_q2.get(cash_account)
self.assertIsNotNone(cash_data_q2, "Cash account should exist in Q2 results")
apr_cash = cash_data_q2.get_period("2024_apr")
self.assertIsNotNone(apr_cash, "April period should exist")
# Opening balance in April should equal closing in March
self.assertEqual(
apr_cash.opening,
mar_cash.closing,
"April opening should equal March closing balance",
)
self.assertEqual(apr_cash.closing, apr_cash.opening, "April closing = opening when no movement")
finally:
# Clean up
frappe.db.set_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv", original_pcv_setting or 0
)
if jv_2024:
jv_2024.cancel()
if pcv:
pcv.reload()
if pcv.docstatus == 1:
pcv.cancel()
jv_2023.cancel()
def test_account_with_gl_entries_but_no_prior_closing_balance(self):
company = "_Test Company"
cash_account = "_Test Cash - _TC"
bank_account = "_Test Bank - _TC"
# Create journal entries WITHOUT any prior Period Closing Voucher
# This ensures the account exists in gl_dict but NOT in balances_data
jv = make_journal_entry(
account1=cash_account,
account2=bank_account,
amount=2500,
posting_date="2024-07-15",
company=company,
submit=True,
)
try:
# Set up filters - use a period with no prior PCV
filters = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-07-01",
"period_end_date": "2024-09-30",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
}
periods = [
{"key": "2024_jul", "from_date": "2024-07-01", "to_date": "2024-07-31"},
{"key": "2024_aug", "from_date": "2024-08-01", "to_date": "2024-08-31"},
{"key": "2024_sep", "from_date": "2024-09-01", "to_date": "2024-09-30"},
]
query_builder = FinancialQueryBuilder(filters, periods)
# Use accounts that have GL entries but may not have Account Closing Balance
accounts = [
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
frappe._dict({"name": bank_account, "account_name": "Bank", "account_number": "1002"}),
]
balances_data = query_builder.fetch_account_balances(accounts)
# Verify accounts are present in results even without prior closing balance
cash_data = balances_data.get(cash_account)
self.assertIsNotNone(cash_data, "Cash account should exist in results")
bank_data = balances_data.get(bank_account)
self.assertIsNotNone(bank_data, "Bank account should exist in results")
# Verify July has the movement from journal entry
jul_cash = cash_data.get_period("2024_jul")
self.assertIsNotNone(jul_cash, "July period should exist for cash")
self.assertEqual(jul_cash.movement, 2500.0, "July cash movement should be 2500")
jul_bank = bank_data.get_period("2024_jul")
self.assertIsNotNone(jul_bank, "July period should exist for bank")
self.assertEqual(jul_bank.movement, -2500.0, "July bank movement should be -2500")
# Verify subsequent periods exist with zero movement
aug_cash = cash_data.get_period("2024_aug")
self.assertIsNotNone(aug_cash, "August period should exist for cash")
self.assertEqual(aug_cash.movement, 0.0, "August cash movement should be 0")
self.assertEqual(aug_cash.opening, jul_cash.closing, "August opening = July closing")
sep_cash = cash_data.get_period("2024_sep")
self.assertIsNotNone(sep_cash, "September period should exist for cash")
self.assertEqual(sep_cash.movement, 0.0, "September cash movement should be 0")
self.assertEqual(sep_cash.opening, aug_cash.closing, "September opening = August closing")
finally:
jv.cancel()

View File

@@ -277,7 +277,21 @@ frappe.ui.form.on("Journal Entry", {
var update_jv_details = function (doc, r) {
$.each(r, function (i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
frappe.model.set_value(row.doctype, row.name, "account", d.account);
const {
idx,
name,
owner,
parent,
parenttype,
parentfield,
creation,
modified,
modified_by,
doctype,
docstatus,
...fields
} = d;
frappe.model.set_value(row.doctype, row.name, fields);
});
refresh_field("accounts");
};

View File

@@ -9,6 +9,7 @@
"engine": "InnoDB",
"field_order": [
"entry_type_and_date",
"company",
"is_system_generated",
"title",
"voucher_type",
@@ -17,7 +18,6 @@
"reversal_of",
"column_break1",
"from_template",
"company",
"posting_date",
"finance_book",
"apply_tds",
@@ -638,7 +638,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2025-11-13 17:54:14.542903",
"modified": "2026-02-03 14:40:39.944524",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -74,8 +74,8 @@ class JournalEntry(AccountsController):
mode_of_payment: DF.Link | None
multi_currency: DF.Check
naming_series: DF.Literal["ACC-JV-.YYYY.-"]
party_not_required: DF.Check
override_tax_withholding_entries: DF.Check
party_not_required: DF.Check
pay_to_recd_from: DF.Data | None
payment_order: DF.Link | None
periodic_entry_difference_account: DF.Link | None
@@ -179,11 +179,14 @@ class JournalEntry(AccountsController):
validate_docs_for_deferred_accounting([self.name], [])
def submit(self):
if len(self.accounts) > 100:
if len(self.accounts) > 100 and not self.meta.queue_in_background:
queue_submission(self, "_submit")
else:
return self._submit()
def before_cancel(self):
self.has_asset_adjustment_entry()
def cancel(self):
if len(self.accounts) > 100:
queue_submission(self, "_cancel")
@@ -554,12 +557,27 @@ class JournalEntry(AccountsController):
)
frappe.db.set_value("Journal Entry", self.name, "inter_company_journal_entry_reference", "")
def unlink_asset_adjustment_entry(self):
frappe.db.sql(
""" update `tabAsset Value Adjustment`
set journal_entry = null where journal_entry = %s""",
self.name,
def has_asset_adjustment_entry(self):
if self.flags.get("via_asset_value_adjustment"):
return
asset_value_adjustment = frappe.db.get_value(
"Asset Value Adjustment", {"docstatus": 1, "journal_entry": self.name}, "name"
)
if asset_value_adjustment:
frappe.throw(
_(
"Cannot cancel this document as it is linked with the submitted Asset Value Adjustment <b>{0}</b>. Please cancel the Asset Value Adjustment to continue."
).format(frappe.utils.get_link_to_form("Asset Value Adjustment", asset_value_adjustment))
)
def unlink_asset_adjustment_entry(self):
AssetValueAdjustment = frappe.qb.DocType("Asset Value Adjustment")
(
frappe.qb.update(AssetValueAdjustment)
.set(AssetValueAdjustment.journal_entry, None)
.where(AssetValueAdjustment.journal_entry == self.name)
).run()
def validate_party(self):
for d in self.get("accounts"):
@@ -1673,6 +1691,10 @@ def get_exchange_rate(
credit=None,
exchange_rate=None,
):
# Ensure exchange_rate is always numeric to avoid calculation errors
if isinstance(exchange_rate, str):
exchange_rate = flt(exchange_rate) or 1
account_details = frappe.get_cached_value(
"Account", account, ["account_type", "root_type", "account_currency", "company"], as_dict=1
)

View File

@@ -3,6 +3,7 @@
frappe.ui.form.on("Journal Entry Template", {
onload: function (frm) {
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
if (frm.is_new()) {
frappe.call({
type: "GET",
@@ -37,6 +38,31 @@ frappe.ui.form.on("Journal Entry Template", {
return { filters: filters };
});
frm.set_query("project", "accounts", function (doc, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
let filters = {
company: doc.company,
};
if (row.party_type == "Customer") {
filters.customer = row.party;
}
return {
query: "erpnext.controllers.queries.get_project_name",
filters,
};
});
frm.set_query("party_type", "accounts", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
filters: {
account: row.account,
},
};
});
},
voucher_type: function (frm) {
var add_accounts = function (doc, r) {

View File

@@ -3,6 +3,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
@@ -42,7 +43,29 @@ class JournalEntryTemplate(Document):
]
# end: auto-generated types
pass
def validate(self):
self.validate_party()
def validate_party(self):
"""
Loop over all accounts and see if party and party type is set correctly
"""
for account in self.accounts:
if account.party_type:
account_type = frappe.get_cached_value("Account", account.account, "account_type")
if account_type not in ["Receivable", "Payable"]:
frappe.throw(
_(
"Check row {0} for account {1}: Party Type is only allowed for Receivable or Payable accounts"
).format(account.idx, account.account)
)
if account.party and not account.party_type:
frappe.throw(
_("Check row {0} for account {1}: Party is only allowed if Party Type is set").format(
account.idx, account.account
)
)
@frappe.whitelist()

View File

@@ -5,7 +5,13 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account"
"account",
"party_type",
"party",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project"
],
"fields": [
{
@@ -15,18 +21,55 @@
"label": "Account",
"options": "Account",
"reqd": 1
},
{
"fieldname": "party_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Party Type",
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Party",
"options": "party_type"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:58.986448",
"modified": "2026-01-09 13:16:27.615083",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Template Account",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -16,9 +16,13 @@ class JournalEntryTemplateAccount(Document):
from frappe.types import DF
account: DF.Link
cost_center: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
party: DF.DynamicLink | None
party_type: DF.Link | None
project: DF.Link | None
# end: auto-generated types
pass

View File

@@ -400,6 +400,16 @@ frappe.ui.form.on("Payment Entry", {
);
frm.refresh_fields();
const party_currency =
frm.doc.payment_type === "Receive" ? "paid_from_account_currency" : "paid_to_account_currency";
var reference_grid = frm.fields_dict["references"].grid;
["total_amount", "outstanding_amount", "allocated_amount"].forEach((fieldname) => {
reference_grid.update_docfield_property(fieldname, "options", party_currency);
});
reference_grid.refresh();
},
show_general_ledger: function (frm) {
@@ -1104,7 +1114,7 @@ frappe.ui.form.on("Payment Entry", {
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
await frm.call("allocate_amount_to_references", {
paid_amount: paid_amount,
paid_amount: flt(paid_amount),
paid_amount_change: paid_amount_change,
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
});

View File

@@ -132,6 +132,12 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "due_date",

View File

@@ -38,6 +38,7 @@ class PaymentLedgerEntry(Document):
amount_in_account_currency: DF.Currency
company: DF.Link | None
cost_center: DF.Link | None
project: DF.Link | None
delinked: DF.Check
due_date: DF.Date | None
finance_book: DF.Link | None

View File

@@ -746,7 +746,7 @@ class PaymentReconciliation(Document):
ple = qb.DocType("Payment Ledger Entry")
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
if self.get(dimension) and frappe.db.has_column("Payment Ledger Entry", dimension):
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):

View File

@@ -546,6 +546,9 @@ def make_payment_request(**args):
if args.dn and not isinstance(args.dn, str):
frappe.throw(_("Invalid parameter. 'dn' should be of type str"))
frappe.has_permission("Payment Request", "create", throw=True)
frappe.has_permission(args.dt, "read", args.dn, throw=True)
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
if not args.get("company"):
args.company = ref_doc.company
@@ -819,7 +822,7 @@ def get_print_format_list(ref_doctype):
return {"print_format": print_format_list}
@frappe.whitelist(allow_guest=True)
@frappe.whitelist()
def resend_payment_email(docname):
return frappe.get_doc("Payment Request", docname).send_email()

View File

@@ -1610,13 +1610,14 @@
"hidden": 1,
"label": "Item Wise Tax Details",
"no_copy": 1,
"options": "Item Wise Tax Detail"
"options": "Item Wise Tax Detail",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2025-08-04 22:22:31.471752",
"modified": "2026-01-29 21:20:51.376875",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -898,6 +898,53 @@ class TestPOSInvoice(IntegrationTestCase):
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
self.assertEqual(batch.qty, 5)
def test_pos_batch_reservation_with_return_qty(self):
"""
Test POS Invoice reserved qty for batch without bundle with return invoices.
"""
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
get_auto_batch_nos,
)
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_batch_item_with_batch,
)
create_batch_item_with_batch("_Batch Item Reserve Return", "TestBatch-RR 01")
se = make_stock_entry(
target="_Test Warehouse - _TC",
item_code="_Batch Item Reserve Return",
qty=30,
basic_rate=100,
)
se.reload()
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
# POS Invoice for the batch without bundle
pos_inv = create_pos_invoice(item="_Batch Item Reserve Return", rate=300, qty=15, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "amount": 4500},
)
pos_inv.items[0].batch_no = batch_no
pos_inv.save()
pos_inv.submit()
# POS Invoice return
pos_return = make_sales_return(pos_inv.name)
pos_return.insert()
pos_return.submit()
batches = get_auto_batch_nos(
frappe._dict({"item_code": "_Batch Item Reserve Return", "warehouse": "_Test Warehouse - _TC"})
)
for batch in batches:
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
self.assertEqual(batch.qty, 30)
def test_pos_batch_item_qty_validation(self):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
BatchNegativeStockError,

View File

@@ -99,8 +99,7 @@ def get_customers_list(pos_profile=None):
return (
frappe.db.sql(
f""" select name, customer_name, customer_group,
territory, customer_pos_id from tabCustomer where disabled = 0
f""" select name, customer_name, customer_group, territory from tabCustomer where disabled = 0
and {cond}""",
tuple(customer_groups),
as_dict=1,

View File

@@ -121,7 +121,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Apply On",
"options": "\nItem Code\nItem Group\nBrand\nTransaction",
"options": "Item Code\nItem Group\nBrand\nTransaction",
"reqd": 1
},
{
@@ -657,7 +657,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2025-08-20 11:40:07.096854",
"modified": "2026-02-17 12:24:07.553505",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
@@ -714,9 +714,10 @@
"write": 1
}
],
"row_format": "Dynamic",
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title"
}
}

View File

@@ -45,7 +45,7 @@ class PricingRule(Document):
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
apply_discount_on_rate: DF.Check
apply_multiple_pricing_rules: DF.Check
apply_on: DF.Literal["", "Item Code", "Item Group", "Brand", "Transaction"]
apply_on: DF.Literal["Item Code", "Item Group", "Brand", "Transaction"]
apply_recursion_over: DF.Float
apply_rule_on_other: DF.Literal["", "Item Code", "Item Group", "Brand"]
brands: DF.Table[PricingRuleBrand]

View File

@@ -10,7 +10,7 @@
],
"fields": [
{
"depends_on": "eval:parent.apply_on == 'Item Code'",
"depends_on": "eval:parent.apply_on == 'Brand'",
"fieldname": "brand",
"fieldtype": "Link",
"in_list_view": 1,
@@ -28,14 +28,15 @@
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:17.857046",
"modified": "2026-02-17 12:17:13.073587",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule Brand",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -10,7 +10,7 @@
],
"fields": [
{
"depends_on": "eval:parent.apply_on == 'Item Code'",
"depends_on": "eval:parent.apply_on == 'Item Group'",
"fieldname": "item_group",
"fieldtype": "Link",
"in_list_view": 1,
@@ -28,14 +28,15 @@
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:18.221095",
"modified": "2026-02-17 12:16:57.778471",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule Item Group",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -415,8 +415,9 @@ def reconcile(doc: None | str = None) -> None:
for x in allocations:
pr.append("allocation", x)
skip_ref_details_update_for_pe = check_multi_currency(pr)
# reconcile
pr.reconcile_allocations(skip_ref_details_update_for_pe=True)
pr.reconcile_allocations(skip_ref_details_update_for_pe=skip_ref_details_update_for_pe)
# If Payment Entry, update details only for newly linked references
# This is for performance
@@ -504,6 +505,37 @@ def reconcile(doc: None | str = None) -> None:
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
def check_multi_currency(pr_doc):
GL = frappe.qb.DocType("GL Entry")
Account = frappe.qb.DocType("Account")
def get_account_currency(voucher_type, voucher_no):
currency = (
frappe.qb.from_(GL)
.join(Account)
.on(GL.account == Account.name)
.select(Account.account_currency)
.where(
(GL.voucher_type == voucher_type)
& (GL.voucher_no == voucher_no)
& (Account.account_type.isin(["Payable", "Receivable"]))
)
.limit(1)
).run(as_dict=True)
return currency[0].account_currency if currency else None
for allocation in pr_doc.allocation:
reference_currency = get_account_currency(allocation.reference_type, allocation.reference_name)
invoice_currency = get_account_currency(allocation.invoice_type, allocation.invoice_number)
if reference_currency != invoice_currency:
return True
return False
@frappe.whitelist()
def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None

View File

@@ -8,12 +8,12 @@
"email_append_to": 1,
"engine": "InnoDB",
"field_order": [
"company",
"title",
"naming_series",
"supplier",
"supplier_name",
"tax_id",
"company",
"column_break_6",
"posting_date",
"posting_time",
@@ -606,6 +606,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.pr_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
@@ -1625,7 +1626,8 @@
"hidden": 1,
"label": "Item Wise Tax Details",
"no_copy": 1,
"options": "Item Wise Tax Detail"
"options": "Item Wise Tax Detail",
"print_hide": 1
},
{
"collapsible": 1,
@@ -1667,7 +1669,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-12-15 06:41:38.237728",
"modified": "2026-02-05 20:45:16.964500",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -36,7 +36,7 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.accounts_controller import merge_taxes, validate_account_head
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
update_billed_amount_based_on_po,
@@ -2005,9 +2005,17 @@ def make_purchase_receipt(source_name, target_doc=None, args=None):
args = json.loads(args)
def post_parent_process(source_parent, target_parent):
for row in target_parent.get("items"):
if row.get("qty") == 0:
target_parent.remove(row)
remove_items_with_zero_qty(target_parent)
set_missing_values(source_parent, target_parent)
def remove_items_with_zero_qty(target_parent):
target_parent.items = [row for row in target_parent.get("items") if row.get("qty") != 0]
def set_missing_values(source_parent, target_parent):
target_parent.run_method("set_missing_values")
if args and args.get("merge_taxes"):
merge_taxes(source_parent, target_parent)
target_parent.run_method("calculate_taxes_and_totals")
def update_item(obj, target, source_parent):
from erpnext.controllers.sales_and_purchase_return import get_returned_qty_map_for_row
@@ -2059,7 +2067,11 @@ def make_purchase_receipt(source_name, target_doc=None, args=None):
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc),
},
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",
"reset_value": not (args and args.get("merge_taxes")),
"ignore": args.get("merge_taxes") if args else 0,
},
},
target_doc,
post_parent_process,

View File

@@ -52,6 +52,7 @@
"stock_uom_rate",
"is_free_item",
"apply_tds",
"allow_zero_valuation_rate",
"section_break_22",
"net_rate",
"net_amount",
@@ -97,7 +98,6 @@
"service_start_date",
"service_end_date",
"reference",
"allow_zero_valuation_rate",
"item_tax_rate",
"bom",
"include_exploded_items",
@@ -420,6 +420,7 @@
"options": "UOM"
},
{
"depends_on": "eval:parent.update_stock",
"fieldname": "warehouse_section",
"fieldtype": "Section Break",
"label": "Warehouse"
@@ -447,7 +448,6 @@
"print_hide": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
@@ -459,14 +459,12 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "rejected_serial_no",
"fieldtype": "Text",
"label": "Rejected Serial No",
@@ -577,6 +575,7 @@
},
{
"default": "0",
"depends_on": "eval:parent.update_stock",
"fieldname": "allow_zero_valuation_rate",
"fieldtype": "Check",
"label": "Allow Zero Valuation Rate",
@@ -800,7 +799,7 @@
"read_only": 1
},
{
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
"depends_on": "eval:parent.is_internal_supplier",
"fieldname": "from_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -896,7 +895,7 @@
"label": "Consider for Tax Withholding"
},
{
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@@ -906,7 +905,7 @@
"search_index": 1
},
{
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "rejected_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle",
@@ -922,7 +921,7 @@
"options": "Asset"
},
{
"depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
@@ -992,7 +991,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-12-13 14:10:02.379392",
"modified": "2026-02-15 21:07:49.455930",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -44,6 +44,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
"Unreconcile Payment Entries",
"Serial and Batch Bundle",
"Bank Transaction",
"Packing Slip",
];
if (!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
@@ -115,18 +116,21 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
}
if (cint(doc.update_stock) != 1) {
// show Make Delivery Note button only if Sales Invoice is not created from Delivery Note
var from_delivery_note = false;
from_delivery_note = this.frm.doc.items.some(function (item) {
return item.delivery_note ? true : false;
});
if (!from_delivery_note && !is_delivered_by_supplier) {
this.frm.add_custom_button(
__("Delivery"),
this.frm.cscript["Make Delivery Note"],
__("Create")
if (!is_delivered_by_supplier) {
const should_create_delivery_note = doc.items.some(
(item) =>
item.qty - item.delivered_qty > 0 &&
!item.scio_detail &&
!item.dn_detail &&
!item.delivered_by_supplier
);
if (should_create_delivery_note) {
this.frm.add_custom_button(
__("Delivery Note"),
this.frm.cscript["Make Delivery Note"],
__("Create")
);
}
}
}

View File

@@ -7,12 +7,12 @@
"engine": "InnoDB",
"field_order": [
"customer_section",
"company",
"company_tax_id",
"naming_series",
"customer",
"customer_name",
"tax_id",
"company",
"company_tax_id",
"column_break1",
"posting_date",
"posting_time",
@@ -703,6 +703,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.dn_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"hide_days": 1,
@@ -778,8 +779,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.total_billing_amount > 0",
"depends_on": "eval:!doc.is_return",
"collapsible_depends_on": "eval:doc.total_billing_amount > 0 || doc.total_billing_hours > 0",
"fieldname": "time_sheet_list",
"fieldtype": "Section Break",
"hide_border": 1,
@@ -793,7 +793,6 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Time Sheets",
"no_copy": 1,
"options": "Sales Invoice Timesheet",
"print_hide": 1
},
@@ -2092,7 +2091,7 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:(!doc.is_return && doc.total_billing_amount > 0)",
"depends_on": "eval:doc.total_billing_amount > 0 || doc.total_billing_hours > 0",
"fieldname": "section_break_104",
"fieldtype": "Section Break"
},
@@ -2252,7 +2251,8 @@
"hidden": 1,
"label": "Item Wise Tax Details",
"no_copy": 1,
"options": "Item Wise Tax Detail"
"options": "Item Wise Tax Detail",
"print_hide": 1
},
{
"default": "0",
@@ -2306,7 +2306,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-10-09 14:48:59.472826",
"modified": "2026-02-06 20:43:44.732805",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -352,10 +352,22 @@ class SalesInvoice(SellingController):
self.is_opening = "No"
self.set_against_income_account()
self.validate_time_sheets_are_submitted()
if self.is_return and not self.return_against and self.timesheets:
frappe.throw(_("Direct return is not allowed for Timesheet."))
if not self.is_return:
self.validate_time_sheets_are_submitted()
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
if self.is_return:
self.timesheets = []
if self.is_return and self.return_against:
for row in self.timesheets:
if row.billing_hours:
row.billing_hours = -abs(row.billing_hours)
if row.billing_amount:
row.billing_amount = -abs(row.billing_amount)
self.update_packing_list()
self.set_billing_hours_and_amount()
self.update_timesheet_billing_for_project()
@@ -484,7 +496,7 @@ class SalesInvoice(SellingController):
if cint(self.is_pos) != 1 and not self.is_return:
self.update_against_document_in_jv()
self.update_time_sheet(self.name)
self.update_time_sheet(None if (self.is_return and self.return_against) else self.name)
if frappe.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction":
update_company_current_month_sales(self.company)
@@ -564,7 +576,7 @@ class SalesInvoice(SellingController):
self.check_if_consolidated_invoice()
super().before_cancel()
self.update_time_sheet(None)
self.update_time_sheet(self.return_against if (self.is_return and self.return_against) else None)
def on_cancel(self):
check_if_return_invoice_linked_with_payment_entry(self)
@@ -804,8 +816,20 @@ class SalesInvoice(SellingController):
for data in timesheet.time_logs:
if (
(self.project and args.timesheet_detail == data.name)
or (not self.project and not data.sales_invoice)
or (not sales_invoice and data.sales_invoice == self.name)
or (not self.project and not data.sales_invoice and args.timesheet_detail == data.name)
or (
not sales_invoice
and data.sales_invoice == self.name
and args.timesheet_detail == data.name
)
or (
self.is_return
and self.return_against
and data.sales_invoice
and data.sales_invoice == self.return_against
and not sales_invoice
and args.timesheet_detail == data.name
)
):
data.sales_invoice = sales_invoice
@@ -845,11 +869,26 @@ class SalesInvoice(SellingController):
payment.account = get_bank_cash_account(payment.mode_of_payment, self.company).get("account")
def validate_time_sheets_are_submitted(self):
# Note: This validation is skipped for return invoices
# to allow returns to reference already-billed timesheet details
for data in self.timesheets:
# Handle invoice duplication
if data.time_sheet and data.timesheet_detail:
if sales_invoice := frappe.db.get_value(
"Timesheet Detail", data.timesheet_detail, "sales_invoice"
):
frappe.throw(
_("Row {0}: Sales Invoice {1} is already created for {2}").format(
data.idx, frappe.bold(sales_invoice), frappe.bold(data.time_sheet)
)
)
if data.time_sheet:
status = frappe.db.get_value("Timesheet", data.time_sheet, "status")
if status not in ["Submitted", "Payslip"]:
frappe.throw(_("Timesheet {0} is already completed or cancelled").format(data.time_sheet))
if status not in ["Submitted", "Payslip", "Partially Billed"]:
frappe.throw(
_("Timesheet {0} cannot be invoiced in its current state").format(data.time_sheet)
)
def set_pos_fields(self, for_validate=False):
"""Set retail related fields from POS Profiles"""
@@ -1283,7 +1322,12 @@ class SalesInvoice(SellingController):
timesheet.billing_amount = ts_doc.total_billable_amount
def update_timesheet_billing_for_project(self):
if not self.timesheets and self.project and self.is_auto_fetch_timesheet_enabled():
if (
not self.is_return
and not self.timesheets
and self.project
and self.is_auto_fetch_timesheet_enabled()
):
self.add_timesheet_data()
else:
self.calculate_billing_amount_for_timesheet()
@@ -2378,7 +2422,10 @@ def make_delivery_note(source_name, target_doc=None):
"cost_center": "cost_center",
},
"postprocess": update_item,
"condition": lambda doc: doc.delivered_by_supplier != 1 and not doc.scio_detail,
"condition": lambda doc: doc.delivered_by_supplier != 1
and not doc.scio_detail
and not doc.dn_detail
and doc.qty - doc.delivered_qty > 0,
},
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
"Sales Team": {

View File

@@ -2951,6 +2951,60 @@ class TestSalesInvoice(ERPNextTestSuite):
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
def test_item_tax_template_change_with_grand_total_discount(self):
"""
Test that when item tax template changes due to discount on Grand Total,
the tax calculations are consistent.
"""
item = create_item("Test Item With Multiple Tax Templates")
item.set("taxes", [])
item.append(
"taxes",
{
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"minimum_net_rate": 0,
"maximum_net_rate": 500,
},
)
item.append(
"taxes",
{
"item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
"minimum_net_rate": 501,
"maximum_net_rate": 1000,
},
)
item.save()
si = create_sales_invoice(item=item.name, rate=700, do_not_save=True)
si.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Excise Duty - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"rate": 0,
},
)
si.insert()
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
si.apply_discount_on = "Grand Total"
si.discount_amount = 300
si.save()
# Verify template changed to 10%
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
self.assertEqual(si.taxes[0].tax_amount, 70) # 10% of 700
self.assertEqual(si.grand_total, 470) # 700 + 70 - 300
si.submit()
@IntegrationTestCase.change_settings("Selling Settings", {"enable_discount_accounting": 1})
def test_sales_invoice_with_discount_accounting_enabled(self):
discount_account = create_account(
@@ -4691,6 +4745,66 @@ class TestSalesInvoice(ERPNextTestSuite):
doc.db_set("do_not_use_batchwise_valuation", original_value)
@change_settings("Selling Settings", {"set_zero_rate_for_expired_batch": True})
def test_zero_valuation_for_standalone_credit_note_with_expired_batch(self):
item_code = "_Test Item for Expiry Batch Zero Valuation"
make_item_for_si(
item_code,
{
"is_stock_item": 1,
"has_batch_no": 1,
"has_expiry_date": 1,
"shelf_life_in_days": 2,
"create_new_batch": 1,
"batch_number_series": "TBATCH-EBZV.####",
},
)
se = make_stock_entry(
item_code=item_code,
qty=10,
target="_Test Warehouse - _TC",
rate=100,
)
# fetch batch no from bundle
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
si = create_sales_invoice(
posting_date=add_days(nowdate(), 3),
item=item_code,
qty=-10,
rate=100,
is_return=1,
update_stock=1,
use_serial_batch_fields=1,
do_not_save=1,
do_not_submit=1,
)
si.items[0].batch_no = batch_no
si.save()
si.submit()
si.reload()
# check zero incoming rate in voucher
self.assertEqual(si.items[0].incoming_rate, 0.0)
# chekc zero incoming rate in stock ledger
stock_ledger_entry = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"item_code": item_code,
"warehouse": "_Test Warehouse - _TC",
},
["incoming_rate", "valuation_rate"],
as_dict=True,
)
self.assertEqual(stock_ledger_entry.incoming_rate, 0.0)
def make_item_for_si(item_code, properties=None):
from erpnext.stock.doctype.item.test_item import make_item

View File

@@ -52,6 +52,7 @@
"is_free_item",
"apply_tds",
"grant_commission",
"allow_zero_valuation_rate",
"section_break_21",
"net_rate",
"net_amount",
@@ -88,7 +89,6 @@
"serial_and_batch_bundle",
"use_serial_batch_fields",
"col_break5",
"allow_zero_valuation_rate",
"incoming_rate",
"item_tax_rate",
"actual_batch_qty",
@@ -580,6 +580,7 @@
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.serial_no || doc.batch_no",
"depends_on": "eval:parent.update_stock",
"fieldname": "warehouse_and_reference",
"fieldtype": "Section Break",
"label": "Stock Details"
@@ -595,7 +596,7 @@
"print_hide": 1
},
{
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
"depends_on": "eval: parent.is_internal_customer",
"fieldname": "target_warehouse",
"fieldtype": "Link",
"hidden": 1,
@@ -613,7 +614,6 @@
"options": "Quality Inspection"
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
@@ -626,6 +626,7 @@
},
{
"default": "0",
"depends_on": "eval:parent.update_stock",
"fieldname": "allow_zero_valuation_rate",
"fieldtype": "Check",
"label": "Allow Zero Valuation Rate",
@@ -633,7 +634,6 @@
"print_hide": 1
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
@@ -906,7 +906,7 @@
"read_only": 1
},
{
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@@ -916,7 +916,7 @@
"search_index": 1
},
{
"depends_on": "eval:parent.update_stock === 1",
"depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0",
"fieldname": "pick_serial_and_batch",
"fieldtype": "Button",
"label": "Pick Serial / Batch No"
@@ -1009,7 +1009,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-09-04 11:08:25.583561",
"modified": "2026-02-15 21:08:57.341638",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -26,7 +26,7 @@
},
{
"default": "0",
"depends_on": "eval:parent.doctype == 'Sales Invoice'",
"depends_on": "eval: [\"POS Invoice\", \"Sales Invoice\"].includes(parent.doctype)",
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
@@ -85,14 +85,15 @@
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:36.427565",
"modified": "2026-02-16 20:46:34.592604",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -52,7 +52,6 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Timesheet Detail",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
@@ -117,15 +116,16 @@
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:36.562795",
"modified": "2025-12-23 13:54:17.677187",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Timesheet",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -415,7 +415,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
"cost_center": "Main - _TC",
"tax_amount": 500,
"description": "Test",
"add_deduct_tax": "Add",
},
)
pi.save()
@@ -506,7 +505,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
"cost_center": "Main - _TC",
"tax_amount": 200,
"description": "Test Gross Tax",
"add_deduct_tax": "Add",
},
)
si.save()
@@ -541,10 +539,10 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
"cost_center": "Main - _TC",
"tax_amount": 400,
"description": "Test Gross Tax",
"add_deduct_tax": "Add",
},
)
si.save()
si.reload()
si.submit()
invoices.append(si)
# For amount before threshold (first 8000 + VAT): TCS entry with amount zero
@@ -594,7 +592,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
"cost_center": "Main - _TC",
"tax_amount": 500,
"description": "VAT added to test TDS calculation on gross amount",
"add_deduct_tax": "Add",
},
)
si.save()
@@ -1024,7 +1021,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
"cost_center": "Main - _TC",
"tax_amount": 1000,
"description": "VAT added to test TDS calculation on gross amount",
"add_deduct_tax": "Add",
},
)
pi.save()
@@ -1162,7 +1158,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
"cost_center": "Main - _TC",
"tax_amount": 8000,
"description": "Test",
"add_deduct_tax": "Add",
},
)

View File

@@ -708,6 +708,10 @@ class TaxWithholdingController:
existing_taxes = {row.account_head: row for row in self.doc.taxes if row.is_tax_withholding_account}
precision = self.doc.precision("tax_amount", "taxes")
conversion_rate = self.get_conversion_rate()
add_deduct_tax = "Deduct"
if self.party_type == "Customer":
add_deduct_tax = "Add"
for account_head, base_amount in account_amount_map.items():
tax_amount = flt(base_amount / conversion_rate, precision)
@@ -724,6 +728,7 @@ class TaxWithholdingController:
tax_row = self._create_tax_row(account_head, tax_amount)
for_update = False
tax_row.add_deduct_tax = add_deduct_tax
# Set item-wise tax breakup for this tax row
self._set_item_wise_tax_for_tds(
tax_row, account_head, category_withholding_map, for_update=for_update
@@ -743,7 +748,6 @@ class TaxWithholdingController:
"account_head": account_head,
"description": account_head,
"cost_center": cost_center,
"add_deduct_tax": "Deduct",
"tax_amount": tax_amount,
"dont_recompute_tax": 1,
},
@@ -807,12 +811,14 @@ class TaxWithholdingController:
else:
item_tax_amount = 0
multiplier = -1 if tax_row.add_deduct_tax == "Deduct" else 1
self.doc._item_wise_tax_details.append(
frappe._dict(
item=item,
tax=tax_row,
rate=category.tax_rate,
amount=item_tax_amount * -1, # Negative because it's a deduction
amount=item_tax_amount * multiplier,
taxable_amount=item_base_taxable,
)
)

View File

@@ -43,16 +43,18 @@
"read_only": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:55.008837",
"modified": "2025-11-14 16:17:25.584675",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Transaction Deletion Record Details",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -17,7 +17,7 @@
</div>
<div class="col-xs-6">
<table>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.posting_date) }}</td></tr>
</table>
</div>
</div>

View File

@@ -877,11 +877,15 @@ class ReceivablePayableReport:
else:
entry_date = row.posting_date
row.range0 = 0.0
self.get_ageing_data(entry_date, row)
# ageing buckets should not have amounts if due date is not reached
if getdate(entry_date) > getdate(self.age_as_on):
row.range0 = row.outstanding
[setattr(row, f"range{i}", 0.0) for i in self.range_numbers]
row.total_due = 0
return
row.total_due = sum(row[f"range{i}"] for i in self.range_numbers)
@@ -1281,6 +1285,8 @@ class ReceivablePayableReport:
ranges = [*self.ranges, _("Above")]
prev_range_value = 0
self.add_column(label=_("<0"), fieldname="range0", fieldtype="Currency")
self.ageing_column_labels.append(_("<0"))
for idx, curr_range_value in enumerate(ranges):
label = f"{prev_range_value}-{curr_range_value}"
self.add_column(label=label, fieldname="range" + str(idx + 1))
@@ -1296,7 +1302,9 @@ class ReceivablePayableReport:
for row in self.data:
row = frappe._dict(row)
if not cint(row.bold):
values = [flt(row.get(f"range{i}", None), precision) for i in self.range_numbers]
values = [flt(row.get("range0", 0), precision)] + [
flt(row.get(f"range{i}", 0), precision) for i in self.range_numbers
]
rows.append({"values": values})
self.chart = {

View File

@@ -232,11 +232,11 @@ def get_report_summary(
def get_chart_data(filters, columns, asset, liability, equity, currency):
labels = [d.get("label") for d in columns[2:]]
labels = [d.get("label") for d in columns[4:]]
asset_data, liability_data, equity_data = [], [], []
for p in columns[2:]:
for p in columns[4:]:
if asset:
asset_data.append(asset[-2].get(p.get("fieldname")))
if liability:

View File

@@ -18,6 +18,8 @@ def execute(filters=None):
dimensions = filters.get("budget_against_filter")
else:
dimensions = get_budget_dimensions(filters)
if not dimensions:
return columns, [], None, None
budget_records = get_budget_records(filters, dimensions)
budget_map = build_budget_map(budget_records, filters)

View File

@@ -219,13 +219,18 @@ def get_net_profit(
has_value = False
gross_income_roots = [row for row in (gross_income or []) if not flt(row.get("indent"))]
non_gross_income_roots = [row for row in (non_gross_income or []) if not flt(row.get("indent"))]
gross_expense_roots = [row for row in (gross_expense or []) if not flt(row.get("indent"))]
non_gross_expense_roots = [row for row in (non_gross_expense or []) if not flt(row.get("indent"))]
for period in period_list:
key = period if consolidated else period.key
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
non_gross_income_for_period = flt(non_gross_income[0].get(key, 0)) if non_gross_income else 0
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
non_gross_expense_for_period = flt(non_gross_expense[0].get(key, 0)) if non_gross_expense else 0
gross_income_for_period = sum(flt(row.get(key, 0)) for row in gross_income_roots)
non_gross_income_for_period = sum(flt(row.get(key, 0)) for row in non_gross_income_roots)
gross_expense_for_period = sum(flt(row.get(key, 0)) for row in gross_expense_roots)
non_gross_expense_for_period = sum(flt(row.get(key, 0)) for row in non_gross_expense_roots)
total_income = gross_income_for_period + non_gross_income_for_period
total_expense = gross_expense_for_period + non_gross_expense_for_period

View File

@@ -5,15 +5,16 @@ from collections import OrderedDict
import frappe
from frappe import _, qb, scrub
from frappe.query_builder import Order
from frappe.query_builder import Case, Order
from frappe.query_builder.functions import Coalesce
from frappe.utils import cint, flt, formatdate
from pypika.terms import ExistsCriterion
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
from erpnext.controllers.queries import get_match_cond
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
from erpnext.stock.utils import get_incoming_rate
@@ -176,7 +177,9 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
column_names = get_column_names()
# to display item as Item Code: Item Name
columns[0] = "Sales Invoice:Link/Item:300"
columns[0]["fieldname"] = "sales_invoice"
columns[0]["options"] = "Item"
columns[0]["width"] = 300
# removing Item Code and Item Name columns
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
@@ -203,7 +206,11 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
total_gross_profit = flt(
total_base_amount + abs(total_buying_amount)
if total_buying_amount < 0
else total_base_amount - total_buying_amount,
)
data.append(
frappe._dict(
{
@@ -215,7 +222,7 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
"buying_amount": total_buying_amount,
"gross_profit": total_gross_profit,
"gross_profit_%": flt(
(total_gross_profit / total_base_amount) * 100.0,
(total_gross_profit / abs(total_base_amount)) * 100.0,
cint(frappe.db.get_default("currency_precision")) or 3,
)
if total_base_amount
@@ -248,9 +255,13 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
total_gross_profit = flt(
total_base_amount + abs(total_buying_amount)
if total_buying_amount < 0
else total_base_amount - total_buying_amount,
)
currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
gross_profit_percent = (total_gross_profit / total_base_amount * 100.0) if total_base_amount else 0
gross_profit_percent = (total_gross_profit / abs(total_base_amount) * 100.0) if total_base_amount else 0
total_row = {
group_columns[0]: "Total",
@@ -581,10 +592,15 @@ class GrossProfitGenerator:
base_amount += row.base_amount
# calculate gross profit
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
row.gross_profit = flt(
row.base_amount + abs(row.buying_amount)
if row.buying_amount < 0
else row.base_amount - row.buying_amount,
self.currency_precision,
)
if row.base_amount:
row.gross_profit_percent = flt(
(row.gross_profit / row.base_amount) * 100.0,
(row.gross_profit / abs(row.base_amount)) * 100.0,
self.currency_precision,
)
else:
@@ -673,9 +689,14 @@ class GrossProfitGenerator:
return new_row
def set_average_gross_profit(self, new_row):
new_row.gross_profit = flt(new_row.base_amount - new_row.buying_amount, self.currency_precision)
new_row.gross_profit = flt(
new_row.base_amount + abs(new_row.buying_amount)
if new_row.buying_amount < 0
else new_row.base_amount - new_row.buying_amount,
self.currency_precision,
)
new_row.gross_profit_percent = (
flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision)
flt(((new_row.gross_profit / abs(new_row.base_amount)) * 100.0), self.currency_precision)
if new_row.base_amount
else 0
)
@@ -851,129 +872,173 @@ class GrossProfitGenerator:
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
def load_invoice_items(self):
conditions = ""
if self.filters.company:
conditions += " and `tabSales Invoice`.company = %(company)s"
if self.filters.from_date:
conditions += " and posting_date >= %(from_date)s"
if self.filters.to_date:
conditions += " and posting_date <= %(to_date)s"
self.si_list = []
SalesInvoice = frappe.qb.DocType("Sales Invoice")
base_query = self.prepare_invoice_query()
if self.filters.include_returned_invoices:
conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
invoice_query = base_query.where(
(SalesInvoice.is_return == 0)
| ((SalesInvoice.is_return == 1) & SalesInvoice.return_against.isnull())
)
else:
conditions += " and is_return = 0"
invoice_query = base_query.where(SalesInvoice.is_return == 0)
if self.filters.item_group:
conditions += f" and {get_item_group_condition(self.filters.item_group)}"
self.si_list += invoice_query.run(as_dict=True)
self.prepare_vouchers_to_ignore()
if self.filters.sales_person:
conditions += """
and exists(select 1
from `tabSales Team` st
where st.parent = `tabSales Invoice`.name
and st.sales_person = %(sales_person)s)
"""
ret_invoice_query = base_query.where(
(SalesInvoice.is_return == 1) & SalesInvoice.return_against.isnotnull()
)
if self.vouchers_to_ignore:
ret_invoice_query = ret_invoice_query.where(
SalesInvoice.return_against.notin(self.vouchers_to_ignore)
)
self.si_list += ret_invoice_query.run(as_dict=True)
def prepare_invoice_query(self):
SalesInvoice = frappe.qb.DocType("Sales Invoice")
SalesInvoiceItem = frappe.qb.DocType("Sales Invoice Item")
Item = frappe.qb.DocType("Item")
SalesTeam = frappe.qb.DocType("Sales Team")
PaymentSchedule = frappe.qb.DocType("Payment Schedule")
query = (
frappe.qb.from_(SalesInvoice)
.join(SalesInvoiceItem)
.on(SalesInvoiceItem.parent == SalesInvoice.name)
.join(Item)
.on(Item.name == SalesInvoiceItem.item_code)
.where((SalesInvoice.docstatus == 1) & (SalesInvoice.is_opening != "Yes"))
)
query = self.apply_common_filters(query, SalesInvoice, SalesInvoiceItem, SalesTeam, Item)
query = query.select(
SalesInvoiceItem.parenttype,
SalesInvoiceItem.parent,
SalesInvoice.posting_date,
SalesInvoice.posting_time,
SalesInvoice.project,
SalesInvoice.update_stock,
SalesInvoice.customer,
SalesInvoice.customer_group,
SalesInvoice.customer_name,
SalesInvoice.territory,
SalesInvoiceItem.item_code,
SalesInvoice.base_net_total.as_("invoice_base_net_total"),
SalesInvoiceItem.item_name,
SalesInvoiceItem.description,
SalesInvoiceItem.warehouse,
SalesInvoiceItem.item_group,
SalesInvoiceItem.brand,
SalesInvoiceItem.so_detail,
SalesInvoiceItem.sales_order,
SalesInvoiceItem.dn_detail,
SalesInvoiceItem.delivery_note,
SalesInvoiceItem.stock_qty.as_("qty"),
SalesInvoiceItem.base_net_rate,
SalesInvoiceItem.base_net_amount,
SalesInvoiceItem.name.as_("item_row"),
SalesInvoice.is_return,
SalesInvoiceItem.cost_center,
SalesInvoiceItem.serial_and_batch_bundle,
)
if self.filters.group_by == "Sales Person":
sales_person_cols = """, sales.sales_person,
sales.allocated_percentage * `tabSales Invoice Item`.base_net_amount / 100 as allocated_amount,
sales.incentives
"""
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
else:
sales_person_cols = ""
sales_team_table = ""
query = query.select(
SalesTeam.sales_person,
(SalesTeam.allocated_percentage * SalesInvoiceItem.base_net_amount / 100).as_(
"allocated_amount"
),
SalesTeam.incentives,
)
query = query.left_join(SalesTeam).on(SalesTeam.parent == SalesInvoice.name)
if self.filters.group_by == "Payment Term":
payment_term_cols = """,if(`tabSales Invoice`.is_return = 1,
'{}',
coalesce(schedule.payment_term, '{}')) as payment_term,
schedule.invoice_portion,
schedule.payment_amount """.format(_("Sales Return"), _("No Terms"))
payment_term_table = """ left join `tabPayment Schedule` schedule on schedule.parent = `tabSales Invoice`.name and
`tabSales Invoice`.is_return = 0 """
else:
payment_term_cols = ""
payment_term_table = ""
query = query.select(
Case()
.when(SalesInvoice.is_return == 1, _("Sales Return"))
.else_(Coalesce(PaymentSchedule.payment_term, _("No Terms")))
.as_("payment_term"),
PaymentSchedule.invoice_portion,
PaymentSchedule.payment_amount,
)
if self.filters.get("sales_invoice"):
conditions += " and `tabSales Invoice`.name = %(sales_invoice)s"
query = query.left_join(PaymentSchedule).on(
(PaymentSchedule.parent == SalesInvoice.name) & (SalesInvoice.is_return == 0)
)
if self.filters.get("item_code"):
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
query = query.orderby(SalesInvoice.posting_date, order=Order.desc).orderby(
SalesInvoice.posting_time, order=Order.desc
)
if self.filters.get("cost_center"):
return query
def apply_common_filters(self, query, SalesInvoice, SalesInvoiceItem, SalesTeam, Item):
if self.filters.company:
query = query.where(SalesInvoice.company == self.filters.company)
if self.filters.from_date:
query = query.where(SalesInvoice.posting_date >= self.filters.from_date)
if self.filters.to_date:
query = query.where(SalesInvoice.posting_date <= self.filters.to_date)
if self.filters.item_group:
query = query.where(get_item_group_condition(self.filters.item_group, Item))
if self.filters.sales_person:
query = query.where(
ExistsCriterion(
frappe.qb.from_(SalesTeam)
.select(1)
.where(
(SalesTeam.parent == SalesInvoice.name)
& (SalesTeam.sales_person == self.filters.sales_person)
)
)
)
if self.filters.sales_invoice:
query = query.where(SalesInvoice.name == self.filters.sales_invoice)
if self.filters.item_code:
query = query.where(SalesInvoiceItem.item_code == self.filters.item_code)
if self.filters.cost_center:
self.filters.cost_center = frappe.parse_json(self.filters.get("cost_center"))
self.filters.cost_center = get_cost_centers_with_children(self.filters.cost_center)
conditions += " and `tabSales Invoice Item`.cost_center in %(cost_center)s"
query = query.where(SalesInvoiceItem.cost_center.isin(self.filters.cost_center))
if self.filters.get("project"):
if self.filters.project:
self.filters.project = frappe.parse_json(self.filters.get("project"))
conditions += " and `tabSales Invoice Item`.project in %(project)s"
query = query.where(SalesInvoiceItem.project.isin(self.filters.project))
accounting_dimensions = get_accounting_dimensions(as_list=False)
if accounting_dimensions:
for dimension in accounting_dimensions:
if self.filters.get(dimension.fieldname):
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
self.filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, self.filters.get(dimension.fieldname)
)
conditions += (
f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
)
else:
conditions += (
f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
)
for dim in get_accounting_dimensions(as_list=False) or []:
if self.filters.get(dim.fieldname):
if frappe.get_cached_value("DocType", dim.document_type, "is_tree"):
self.filters[dim.fieldname] = get_dimension_with_children(
dim.document_type, self.filters.get(dim.fieldname)
)
query = query.where(SalesInvoiceItem[dim.fieldname].isin(self.filters[dim.fieldname]))
if self.filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1
if self.filters.warehouse:
lft, rgt = frappe.db.get_value("Warehouse", self.filters.warehouse, ["lft", "rgt"])
WH = frappe.qb.DocType("Warehouse")
query = query.where(
SalesInvoiceItem.warehouse.isin(
frappe.qb.from_(WH).select(WH.name).where((WH.lft >= lft) & (WH.rgt <= rgt))
)
)
if warehouse_details:
conditions += f" and `tabSales Invoice Item`.warehouse in (select name from `tabWarehouse` wh where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
self.si_list = frappe.db.sql(
"""
select
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.customer_name,
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.serial_and_batch_bundle
{sales_person_cols}
{payment_term_cols}
from
`tabSales Invoice` inner join `tabSales Invoice Item`
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
join `tabItem` item on item.name = `tabSales Invoice Item`.item_code
{sales_team_table}
{payment_term_table}
where
`tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond}
order by
`tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc""".format(
conditions=conditions,
sales_person_cols=sales_person_cols,
sales_team_table=sales_team_table,
payment_term_cols=payment_term_cols,
payment_term_table=payment_term_table,
match_cond=get_match_cond("Sales Invoice"),
),
self.filters,
as_dict=1,
)
return query
def prepare_vouchers_to_ignore(self):
self.vouchers_to_ignore = tuple(row["parent"] for row in self.si_list)
def get_delivery_notes(self):
self.delivery_notes = frappe._dict({})

View File

@@ -470,7 +470,7 @@ class TestGrossProfit(IntegrationTestCase):
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
"gross_profit_%": -100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
report_output = {k: v for k, v in gp_entry[0].items() if k in expected_entry}
@@ -649,21 +649,24 @@ class TestGrossProfit(IntegrationTestCase):
def test_profit_for_later_period_return(self):
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
sales_inv_date = month_start_date
return_inv_date = add_days(month_end_date, 1)
# create sales invoice on month start date
sinv = self.create_sales_invoice(qty=1, rate=100, do_not_save=True, do_not_submit=True)
sinv.set_posting_time = 1
sinv.posting_date = month_start_date
sinv.posting_date = sales_inv_date
sinv.save().submit()
# create credit note on next month start date
cr_note = make_sales_return(sinv.name)
cr_note.set_posting_time = 1
cr_note.posting_date = add_days(month_end_date, 1)
cr_note.posting_date = return_inv_date
cr_note.save().submit()
# apply filters for invoiced period
filters = frappe._dict(
company=self.company, from_date=month_start_date, to_date=month_end_date, group_by="Invoice"
company=self.company, from_date=month_start_date, to_date=month_start_date, group_by="Invoice"
)
_, data = execute(filters=filters)
@@ -675,7 +678,7 @@ class TestGrossProfit(IntegrationTestCase):
self.assertEqual(total.get("gross_profit_%"), 100.0)
# extend filters upto returned period
filters.update(to_date=add_days(month_end_date, 1))
filters.update({"to_date": return_inv_date})
_, data = execute(filters=filters)
total = data[-1]
@@ -684,3 +687,63 @@ class TestGrossProfit(IntegrationTestCase):
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 0.0)
self.assertEqual(total.get("gross_profit_%"), 0.0)
# apply filters only on returned period
filters.update({"from_date": return_inv_date, "to_date": return_inv_date})
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, -100.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, -100.0)
self.assertEqual(total.get("gross_profit_%"), -100.0)
def test_sales_person_wise_gross_profit(self):
sales_person = make_sales_person("_Test Sales Person")
posting_date = get_first_day(nowdate())
qty = 10
rate = 100
sinv = self.create_sales_invoice(qty=qty, rate=rate, do_not_save=True, do_not_submit=True)
sinv.set_posting_time = 1
sinv.posting_date = posting_date
sinv.append(
"sales_team",
{
"sales_person": sales_person.name,
"allocated_percentage": 100,
"allocated_amount": 1000.0,
"commission_rate": 5,
"incentives": 5,
},
)
sinv.save().submit()
filters = frappe._dict(
company=self.company, from_date=posting_date, to_date=posting_date, group_by="Sales Person"
)
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total[5], 1000.0)
self.assertEqual(total[6], 0.0)
self.assertEqual(total[7], 1000.0)
self.assertEqual(total[8], 100.0)
def make_sales_person(sales_person_name="_Test Sales Person"):
if not frappe.db.exists("Sales Person", {"sales_person_name": sales_person_name}):
sales_person_doc = frappe.get_doc(
{
"doctype": "Sales Person",
"is_group": 0,
"parent_sales_person": "Sales Team",
"sales_person_name": sales_person_name,
}
).insert(ignore_permissions=True)
else:
sales_person_doc = frappe.get_doc("Sales Person", {"sales_person_name": sales_person_name})
return sales_person_doc

View File

@@ -105,7 +105,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
{
"total_tax": total_tax,
"total_other_charges": total_other_charges,
"total": d.base_net_amount + total_tax,
"total": d.base_net_amount + total_tax + total_other_charges,
"currency": company_currency,
}
)

View File

@@ -163,11 +163,11 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co
def get_chart_data(filters, columns, income, expense, net_profit_loss, currency):
labels = [d.get("label") for d in columns[2:]]
labels = [d.get("label") for d in columns[4:]]
income_data, expense_data, net_profit = [], [], []
for p in columns[2:]:
for p in columns[4:]:
if income:
income_data.append(income[-2].get(p.get("fieldname")))
if expense:

View File

@@ -3,7 +3,8 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.query_builder import Criterion
from frappe.utils import cstr, flt
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
@@ -33,11 +34,19 @@ def execute(filters=None):
def get_accounts_data(based_on, company):
if based_on == "Cost Center":
return frappe.db.sql(
"""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
from `tabCost Center` where company=%s order by name""",
company,
as_dict=True,
cc = qb.DocType("Cost Center")
return (
qb.from_(cc)
.select(
cc.name,
cc.parent_cost_center.as_("parent_account"),
cc.cost_center_name.as_("account_name"),
cc.lft,
cc.rgt,
)
.where(cc.company.eq(company))
.orderby(cc.name)
.run(as_dict=True)
)
elif based_on == "Project":
return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
@@ -206,27 +215,38 @@ def set_gl_entries_by_account(
company, from_date, to_date, based_on, gl_entries_by_account, ignore_closing_entries=False
):
"""Returns a dict like { "account": [gl entries], ... }"""
additional_conditions = []
gl = qb.DocType("GL Entry")
acc = qb.DocType("Account")
conditions = []
conditions.append(gl.company.eq(company))
conditions.append(gl[based_on].notnull())
conditions.append(gl.is_cancelled.eq(0))
if from_date and to_date:
conditions.append(gl.posting_date.between(from_date, to_date))
elif from_date and not to_date:
conditions.append(gl.posting_date.gte(from_date))
elif not from_date and to_date:
conditions.append(gl.posting_date.lte(to_date))
if ignore_closing_entries:
additional_conditions.append("and voucher_type !='Period Closing Voucher'")
conditions.append(gl.voucher_type.ne("Period Closing Voucher"))
if from_date:
additional_conditions.append("and posting_date >= %(from_date)s")
gl_entries = frappe.db.sql(
"""select posting_date, {based_on} as based_on, debit, credit,
is_opening, (select root_type from `tabAccount` where name = account) as type
from `tabGL Entry` where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
and {based_on} is not null
and is_cancelled = 0
order by {based_on}, posting_date""".format(
additional_conditions="\n".join(additional_conditions), based_on=based_on
),
{"company": company, "from_date": from_date, "to_date": to_date},
as_dict=True,
root_subquery = qb.from_(acc).select(acc.root_type).where(acc.name.eq(gl.account))
gl_entries = (
qb.from_(gl)
.select(
gl.posting_date,
gl[based_on].as_("based_on"),
gl.debit,
gl.credit,
gl.is_opening,
root_subquery.as_("type"),
)
.where(Criterion.all(conditions))
.orderby(gl[based_on], gl.posting_date)
.run(as_dict=True)
)
for entry in gl_entries:

View File

@@ -154,17 +154,11 @@ def get_columns(filters):
"width": 60,
},
{
"label": _("Total Amount"),
"label": _("Taxable Amount"),
"fieldname": "total_amount",
"fieldtype": "Currency",
"width": 120,
},
{
"label": _("Base Total"),
"fieldname": "base_total",
"fieldtype": "Currency",
"width": 120,
},
{
"label": _("Tax Amount"),
"fieldname": "tax_amount",
@@ -172,10 +166,16 @@ def get_columns(filters):
"width": 120,
},
{
"label": _("Grand Total"),
"label": _("Grand Total (Company Currency)"),
"fieldname": "base_total",
"fieldtype": "Currency",
"width": 150,
},
{
"label": _("Grand Total (Transaction Currency)"),
"fieldname": "grand_total",
"fieldtype": "Currency",
"width": 120,
"width": 170,
},
{
"label": _("Reference Date"),

View File

@@ -106,7 +106,7 @@ def get_columns(filters):
"width": 120,
},
{
"label": _("Total Amount"),
"label": _("Total Taxable Amount"),
"fieldname": "total_amount",
"fieldtype": "Float",
"width": 120,

View File

@@ -11,6 +11,7 @@ import frappe.defaults
from frappe import _, qb, throw
from frappe.desk.reportview import build_match_conditions
from frappe.model.meta import get_field_precision
from frappe.model.naming import determine_consecutive_week_number
from frappe.query_builder import AliasedQuery, Case, Criterion, Field, Table
from frappe.query_builder.functions import Count, IfNull, Max, Round, Sum
from frappe.query_builder.utils import DocType
@@ -25,6 +26,7 @@ from frappe.utils import (
get_number_format_info,
getdate,
now,
now_datetime,
nowdate,
)
from frappe.utils.caching import site_cache
@@ -66,6 +68,7 @@ def get_fiscal_year(
as_dict=False,
boolean=None,
raise_on_missing=True,
truncate=False,
):
if isinstance(raise_on_missing, str):
raise_on_missing = loads(raise_on_missing)
@@ -79,7 +82,14 @@ def get_fiscal_year(
fiscal_years = get_fiscal_years(
date, fiscal_year, label, verbose, company, as_dict=as_dict, raise_on_missing=raise_on_missing
)
return False if not fiscal_years else fiscal_years[0]
if fiscal_years:
fiscal_year = fiscal_years[0]
if truncate:
return ("-".join(y[-2:] for y in fiscal_year[0].split("-")), fiscal_year[1], fiscal_year[2])
return fiscal_year
return False
def get_fiscal_years(
@@ -547,6 +557,7 @@ def reconcile_against_document(
doc.make_advance_gl_entries(entry=row)
else:
_delete_pl_entries(voucher_type, voucher_no)
_delete_adv_pl_entries(voucher_type, voucher_no)
gl_map = doc.build_gl_map()
# Make sure there is no overallocation
from erpnext.accounts.general_ledger import process_debit_credit_difference
@@ -1500,14 +1511,14 @@ def get_autoname_with_number(number_value, doc_title, company):
def parse_naming_series_variable(doc, variable):
if variable == "FY":
if variable in ["FY", "TFY"]:
if doc:
date = doc.get("posting_date") or doc.get("transaction_date") or getdate()
company = doc.get("company")
else:
date = getdate()
company = None
return get_fiscal_year(date=date, company=company)[0]
return get_fiscal_year(date=date, company=company, truncate=variable == "TFY")[0]
elif variable == "ABBR":
if doc:
@@ -1517,6 +1528,18 @@ def parse_naming_series_variable(doc, variable):
return frappe.db.get_value("Company", company, "abbr") if company else ""
else:
data = {"YY": "%y", "YYYY": "%Y", "MM": "%m", "DD": "%d", "JJJ": "%j"}
date = (
(
getdate(doc.get("posting_date") or doc.get("transaction_date") or doc.get("posting_datetime"))
or now_datetime()
)
if frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents")
else now_datetime()
)
return date.strftime(data[variable]) if variable in data else determine_consecutive_week_number(date)
@frappe.whitelist()
def get_coa(doctype, parent, is_root=None, chart=None):
@@ -1946,6 +1969,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
account=gle.account,
party_type=gle.party_type,
party=gle.party,
project=gle.project,
cost_center=gle.cost_center,
finance_book=gle.finance_book,
due_date=gle.due_date,

View File

@@ -14,10 +14,10 @@
"for_user": "",
"hide_custom": 0,
"icon": "accounting",
"idx": 3,
"idx": 4,
"indicator_color": "",
"is_hidden": 0,
"label": "Accounting",
"label": "Invoicing",
"links": [
{
"hidden": 0,
@@ -587,10 +587,10 @@
"type": "Link"
}
],
"modified": "2025-12-24 13:20:34.857205",
"modified": "2026-01-23 11:05:47.246213",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
"name": "Invoicing",
"number_cards": [
{
"label": "Outgoing Bills",
@@ -617,6 +617,6 @@
"roles": [],
"sequence_id": 2.0,
"shortcuts": [],
"title": "Accounting",
"title": "Invoicing",
"type": "Workspace"
}

View File

@@ -116,14 +116,6 @@ frappe.ui.form.on("Asset", {
__("Manage")
);
frm.add_custom_button(
__("Repair Asset"),
function () {
frm.trigger("create_asset_repair");
},
__("Manage")
);
frm.add_custom_button(
__("Split Asset"),
function () {
@@ -155,6 +147,14 @@ frappe.ui.form.on("Asset", {
},
__("Manage")
);
frm.add_custom_button(
__("Repair Asset"),
function () {
frm.trigger("create_asset_repair");
},
__("Manage")
);
}
if (!frm.doc.calculate_depreciation) {
@@ -513,12 +513,14 @@ frappe.ui.form.on("Asset", {
},
is_composite_asset: function (frm) {
if (frm.doc.is_composite_asset) {
frm.set_value("net_purchase_amount", 0);
} else {
frm.set_df_property("net_purchase_amount", "read_only", 0);
if (frm.doc.docstatus == 0) {
if (frm.doc.is_composite_asset) {
frm.set_value("net_purchase_amount", 0);
} else {
frm.set_df_property("net_purchase_amount", "read_only", 0);
}
frm.trigger("toggle_reference_doc");
}
frm.trigger("toggle_reference_doc");
},
make_sales_invoice: function (frm) {

View File

@@ -244,6 +244,8 @@ class Asset(AccountsController):
def before_submit(self):
if self.is_composite_asset and not has_active_capitalization(self.name):
if self.split_from and has_active_capitalization(self.split_from):
return
frappe.throw(_("Please capitalize this asset before submitting."))
def on_submit(self):

View File

@@ -246,7 +246,9 @@ def _make_journal_entry_for_depreciation(
def setup_journal_entry_metadata(je, depr_schedule_doc, depr_series, depr_schedule, asset):
je.voucher_type = "Depreciation Entry"
je.naming_series = depr_series
if depr_series:
je.naming_series = depr_series
je.posting_date = depr_schedule.schedule_date
je.company = asset.company
je.finance_book = depr_schedule_doc.finance_book

View File

@@ -1691,6 +1691,71 @@ class TestDepreciationBasics(AssetSetup):
pr.submit()
self.assertTrue(get_gl_entries("Purchase Receipt", pr.name))
def test_split_asset_created_via_capitalization(self):
"""Test that assets created via Asset Capitalization can be split without capitalization error"""
from erpnext.assets.doctype.asset_capitalization.test_asset_capitalization import (
create_asset_capitalization,
create_asset_capitalization_data,
)
# Ensure test data exists
create_asset_capitalization_data()
company = "_Test Company with perpetual inventory"
set_depreciation_settings_in_company(company=company)
name = frappe.db.get_value(
"Asset Category Account",
filters={"parent": "Computers", "company_name": company},
fieldname=["name"],
)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
stock_rate = 1000
stock_qty = 2
total_amount = 2000
# Create composite asset
wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset for Split",
is_composite_asset=1,
warehouse="Stores - TCP1",
company=company,
asset_quantity=2, # Set quantity > 1 to allow splitting
)
# Create and submit Asset Capitalization
asset_capitalization = create_asset_capitalization(
target_asset=wip_composite_asset.name,
stock_qty=stock_qty,
stock_rate=stock_rate,
company=company,
submit=1,
)
# Verify asset was capitalized
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
self.assertEqual(target_asset.net_purchase_amount, total_amount)
self.assertEqual(target_asset.status, "Work In Progress")
# Submit the capitalized asset
target_asset.submit()
self.assertEqual(target_asset.status, "Submitted")
# Split the asset - this should work without capitalization error
split_qty = 1
splitted_asset = split_asset(target_asset.name, split_qty)
# Verify split asset was created and submitted successfully
self.assertIsNotNone(splitted_asset)
self.assertEqual(splitted_asset.asset_quantity, split_qty)
self.assertEqual(splitted_asset.split_from, target_asset.name)
self.assertEqual(splitted_asset.docstatus, 1) # Should be submitted
self.assertEqual(splitted_asset.status, "Submitted")
# Verify original asset was updated
target_asset.reload()
self.assertEqual(target_asset.asset_quantity, 1) # Remaining quantity
def get_gl_entries(doctype, docname):
gl_entry = frappe.qb.DocType("GL Entry")

View File

@@ -574,13 +574,19 @@ class AssetCapitalization(StockController):
if self.docstatus == 2:
net_purchase_amount = asset_doc.net_purchase_amount - total_target_asset_value
purchase_amount = asset_doc.purchase_amount - total_target_asset_value
asset_doc.db_set("total_asset_cost", asset_doc.total_asset_cost - total_target_asset_value)
total_asset_cost = asset_doc.total_asset_cost - total_target_asset_value
else:
net_purchase_amount = asset_doc.net_purchase_amount + total_target_asset_value
purchase_amount = asset_doc.purchase_amount + total_target_asset_value
total_asset_cost = asset_doc.total_asset_cost + total_target_asset_value
asset_doc.db_set("net_purchase_amount", net_purchase_amount)
asset_doc.db_set("purchase_amount", purchase_amount)
asset_doc.db_set(
{
"net_purchase_amount": net_purchase_amount,
"purchase_amount": purchase_amount,
"total_asset_cost": total_asset_cost,
}
)
frappe.msgprint(
_("Asset {0} has been updated. Please set the depreciation details if any and submit it.").format(

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr, get_link_to_form
from frappe.utils import cstr, get_datetime, get_link_to_form
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
@@ -34,6 +34,7 @@ class AssetMovement(Document):
for d in self.assets:
self.validate_asset(d)
self.validate_movement(d)
self.validate_transaction_date(d)
def validate_asset(self, d):
status, company = frappe.db.get_value("Asset", d.asset, ["status", "company"])
@@ -51,6 +52,18 @@ class AssetMovement(Document):
else:
self.validate_employee(d)
def validate_transaction_date(self, d):
previous_movement_date = frappe.db.get_value(
"Asset Movement",
[["Asset Movement Item", "asset", "=", d.asset], ["docstatus", "=", 1]],
"transaction_date",
order_by="transaction_date desc",
)
if previous_movement_date and get_datetime(previous_movement_date) > get_datetime(
self.transaction_date
):
frappe.throw(_("Transaction date can't be earlier than previous movement date"))
def validate_location_and_employee(self, d):
self.validate_location(d)
self.validate_employee(d)

View File

@@ -4,9 +4,9 @@ import unittest
import frappe
from frappe.tests import IntegrationTestCase
from frappe.utils import now
from frappe.utils import add_days, now
from erpnext.assets.doctype.asset.test_asset import create_asset_data
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.setup.doctype.employee.test_employee import make_employee
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -147,6 +147,33 @@ class TestAssetMovement(IntegrationTestCase):
movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
def test_movement_transaction_date(self):
asset = create_asset(item_code="Macbook Pro", do_not_save=1)
asset.save().submit()
if not frappe.db.exists("Location", "Test Location 2"):
frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert()
asset_creation_date = frappe.db.get_value(
"Asset Movement",
[["Asset Movement Item", "asset", "=", asset.name], ["docstatus", "=", 1]],
"transaction_date",
)
asset_movement = create_asset_movement(
purpose="Transfer",
company=asset.company,
assets=[
{
"asset": asset.name,
"source_location": "Test Location",
"target_location": "Test Location 2",
}
],
transaction_date=add_days(asset_creation_date, -1),
do_not_save=True,
)
self.assertRaises(frappe.ValidationError, asset_movement.save)
def create_asset_movement(**args):
args = frappe._dict(args)
@@ -165,9 +192,10 @@ def create_asset_movement(**args):
"reference_name": args.reference_name,
}
)
movement.insert()
movement.submit()
if not args.do_not_save:
movement.insert(ignore_if_duplicate=True)
if not args.do_not_submit:
movement.submit()
return movement

View File

@@ -74,7 +74,7 @@ class AssetValueAdjustment(Document):
)
def on_cancel(self):
frappe.get_doc("Journal Entry", self.journal_entry).cancel()
self.cancel_asset_revaluation_entry()
self.update_asset()
add_asset_activity(
self.asset,
@@ -167,6 +167,17 @@ class AssetValueAdjustment(Document):
if dimension.get("mandatory_for_pl"):
debit_entry.update({dimension["fieldname"]: dimension_value})
def cancel_asset_revaluation_entry(self):
if not self.journal_entry:
return
revaluation_entry = frappe.get_doc("Journal Entry", self.journal_entry)
if revaluation_entry.docstatus == 1:
# Ignore permissions to match Journal Entry submission behavior
revaluation_entry.flags.ignore_permissions = True
revaluation_entry.flags.via_asset_value_adjustment = True
revaluation_entry.cancel()
def update_asset(self):
asset = self.update_asset_value_after_depreciation()
note = self.get_adjustment_note()

View File

@@ -803,7 +803,7 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function (frm) {
function prevent_past_schedule_dates(frm) {
if (frm.doc.transaction_date) {
frm.fields_dict["schedule_date"].datepicker.update({
frm.fields_dict["schedule_date"].datepicker?.update({
minDate: new Date(frm.doc.transaction_date),
});
}

View File

@@ -9,10 +9,9 @@
"engine": "InnoDB",
"field_order": [
"supplier_section",
"company",
"title",
"naming_series",
"supplier",
"supplier_name",
"order_confirmation_no",
"order_confirmation_date",
"get_items_from_open_material_requests",
@@ -21,8 +20,9 @@
"transaction_date",
"schedule_date",
"column_break1",
"company",
"supplier",
"is_subcontracted",
"supplier_name",
"has_unit_price_items",
"supplier_warehouse",
"amended_from",
@@ -1301,7 +1301,8 @@
"hidden": 1,
"label": "Item Wise Tax Details",
"no_copy": 1,
"options": "Item Wise Tax Detail"
"options": "Item Wise Tax Detail",
"print_hide": 1
}
],
"grid_page_length": 50,
@@ -1309,7 +1310,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2025-09-28 11:00:56.635116",
"modified": "2026-02-03 14:44:55.192192",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -149,6 +149,7 @@ class PurchaseOrder(BuyingController):
supplied_items: DF.Table[PurchaseOrderItemSupplied]
supplier: DF.Link
supplier_address: DF.Link | None
supplier_group: DF.Link | None
supplier_name: DF.Data | None
supplier_warehouse: DF.Link | None
tax_category: DF.Link | None

View File

@@ -1330,6 +1330,55 @@ class TestPurchaseOrder(IntegrationTestCase):
pi = make_pi_from_po(po.name)
self.assertEqual(pi.items[0].qty, 50)
def test_multiple_advances_against_purchase_order_are_allocated_across_partial_purchase_invoices(self):
# step - 1: create PO
po = create_purchase_order(qty=10, rate=10)
# step - 2: create first partial advance payment
pe1 = get_payment_entry("Purchase Order", po.name, bank_account="_Test Bank - _TC")
pe1.reference_no = "1"
pe1.reference_date = nowdate()
pe1.paid_amount = 50
pe1.references[0].allocated_amount = 50
pe1.save(ignore_permissions=True).submit()
# check first advance paid against PO
po.reload()
self.assertEqual(po.advance_paid, 50)
# step - 3: create first PI for partial qty and allocate first advance
pi_1 = make_pi_from_po(po.name)
pi_1.update_stock = 1
pi_1.allocate_advances_automatically = 1
pi_1.items[0].qty = 5
pi_1.save(ignore_permissions=True).submit()
# step - 4: create second advance payment for remaining
pe2 = get_payment_entry("Purchase Order", po.name, bank_account="_Test Bank - _TC")
pe2.reference_no = "2"
pe2.reference_date = nowdate()
pe2.paid_amount = 50
pe2.references[0].allocated_amount = 50
pe2.save(ignore_permissions=True).submit()
# check second advance paid against PO
po.reload()
self.assertEqual(po.advance_paid, 100)
# step - 5: create second PI for remaining qty and allocate second advance
pi_2 = make_pi_from_po(po.name)
pi_2.update_stock = 1
pi_2.allocate_advances_automatically = 1
pi_2.save(ignore_permissions=True).submit()
# check PO and PI status
po.reload()
pi_1.reload()
pi_2.reload()
self.assertEqual(pi_1.status, "Paid")
self.assertEqual(pi_2.status, "Paid")
self.assertEqual(po.status, "Completed")
def create_po_for_sc_testing():
from erpnext.controllers.tests.test_subcontracting_controller import (

View File

@@ -304,12 +304,17 @@ class RequestforQuotation(BuyingController):
else:
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
rendered_message = frappe.render_template(self.message_for_supplier, doc_args)
subject_source = (
self.subject
or frappe.get_value("Email Template", self.email_template, "subject")
or _("Request for Quotation")
)
rendered_subject = frappe.render_template(subject_source, doc_args)
if preview:
return {
"message": self.message_for_supplier,
"subject": self.subject
or frappe.get_value("Email Template", self.email_template, "subject")
or _("Request for Quotation"),
"message": rendered_message,
"subject": rendered_subject,
}
attachments = []
@@ -333,10 +338,8 @@ class RequestforQuotation(BuyingController):
self.send_email(
data,
sender,
self.subject
or frappe.get_value("Email Template", self.email_template, "subject")
or _("Request for Quotation"),
self.message_for_supplier,
rendered_subject,
rendered_message,
attachments,
)

View File

@@ -63,7 +63,6 @@
"fieldtype": "Column Break"
},
{
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"in_global_search": 1,
@@ -262,7 +261,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-04-28 23:30:22.927989",
"modified": "2026-01-31 19:46:27.884592",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Item",

View File

@@ -135,14 +135,6 @@ frappe.ui.form.on("Supplier", {
// indicators
erpnext.utils.set_party_dashboard_indicators(frm);
}
frm.set_query("supplier_group", () => {
return {
filters: {
is_group: 0,
},
};
});
},
get_supplier_group_details: function (frm) {
frappe.call({

View File

@@ -167,6 +167,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Supplier Group",
"link_filters": "[[\"Supplier Group\",\"is_group\",\"=\",0]]",
"oldfieldname": "supplier_type",
"oldfieldtype": "Link",
"options": "Supplier Group"
@@ -383,7 +384,7 @@
},
{
"fieldname": "primary_address",
"fieldtype": "Text",
"fieldtype": "Text Editor",
"label": "Primary Address",
"read_only": 1
},
@@ -500,7 +501,7 @@
"link_fieldname": "party"
}
],
"modified": "2025-06-29 05:30:50.398653",
"modified": "2026-02-06 12:58:01.398824",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",

View File

@@ -62,7 +62,7 @@ class Supplier(TransactionBase):
portal_users: DF.Table[PortalUser]
prevent_pos: DF.Check
prevent_rfqs: DF.Check
primary_address: DF.Text | None
primary_address: DF.TextEditor | None
release_date: DF.Date | None
represents_company: DF.Link | None
supplier_details: DF.Text | None

View File

@@ -16,6 +16,14 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e
return !doc.qty && me.frm.doc.has_unit_price_items ? "yellow" : "";
});
this.frm.set_query("warehouse", "items", (doc, cdt, cdn) => {
return {
filters: {
company: doc.company,
is_group: 0,
},
};
});
super.setup();
}

View File

@@ -938,7 +938,8 @@
"hidden": 1,
"label": "Item Wise Tax Details",
"no_copy": 1,
"options": "Item Wise Tax Detail"
"options": "Item Wise Tax Detail",
"print_hide": 1
}
],
"grid_page_length": 50,
@@ -947,7 +948,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-07-23 02:22:43.526822",
"modified": "2026-01-29 21:23:13.778468",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@@ -187,9 +187,8 @@ class AccountsController(TransactionBase):
msg = ""
if self.get("update_outstanding_for_self"):
msg = (
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, "
"uncheck '{2}' checkbox. <br><br>Or"
msg = _(
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck the '{2}' checkbox."
).format(
frappe.bold(document_type),
get_link_to_form(self.doctype, self.get("return_against")),
@@ -200,8 +199,8 @@ class AccountsController(TransactionBase):
abs(flt(self.rounded_total) or flt(self.grand_total)) > flt(against_voucher_outstanding)
):
self.update_outstanding_for_self = 1
msg = (
"The outstanding amount {} in {} is lesser than {}. Updating the outstanding to this invoice. <br><br>And"
msg = _(
"The outstanding amount {0} in {1} is lesser than {2}. Updating the outstanding to this invoice."
).format(
against_voucher_outstanding,
get_link_to_form(self.doctype, self.get("return_against")),
@@ -209,11 +208,11 @@ class AccountsController(TransactionBase):
)
if msg:
msg += " you can use {} tool to reconcile against {} later.".format(
msg += "<br><br>" + _("You can use {0} to reconcile against {1} later.").format(
get_link_to_form("Payment Reconciliation"),
get_link_to_form(self.doctype, self.get("return_against")),
)
frappe.msgprint(_(msg))
frappe.msgprint(msg)
def validate(self):
if not self.get("is_return") and not self.get("is_debit_note"):
@@ -4080,6 +4079,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
flt(d.get("conversion_factor"), conv_fac_precision) or conversion_factor
)
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
child_item.total_weight = flt(
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
child_item.precision("total_weight"),
)
if d.get("delivery_date") and parent_doctype == "Sales Order":
child_item.delivery_date = d.get("delivery_date")

View File

@@ -783,7 +783,9 @@ class BuyingController(SubcontractingController):
or self.is_return
or (self.is_internal_transfer() and self.docstatus == 2)
else self.get_package_for_target_warehouse(
d, type_of_transaction=type_of_transaction
d,
type_of_transaction=type_of_transaction,
via_landed_cost_voucher=via_landed_cost_voucher,
)
),
},
@@ -871,7 +873,22 @@ class BuyingController(SubcontractingController):
via_landed_cost_voucher=via_landed_cost_voucher,
)
def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str:
def get_package_for_target_warehouse(
self, item, warehouse=None, type_of_transaction=None, via_landed_cost_voucher=None
) -> str:
if via_landed_cost_voucher and item.get("warehouse"):
if sabb := frappe.db.get_value(
"Serial and Batch Bundle",
{
"voucher_detail_no": item.name,
"warehouse": item.get("warehouse"),
"docstatus": 1,
"is_cancelled": 0,
},
"name",
):
return sabb
if not item.serial_and_batch_bundle:
return ""

View File

@@ -212,7 +212,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
party = filters.get("customer") or filters.get("supplier")
item_rules_list = frappe.get_all(
"Party Specific Item",
filters={"party": party},
filters={
"party": ["!=", party],
"party_type": "Customer" if filters.get("customer") else "Supplier",
},
fields=["restrict_based_on", "based_on_value"],
)
@@ -226,7 +229,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
for filter in filters_dict:
filters[scrub(filter)] = ["in", filters_dict[filter]]
filters[scrub(filter)] = ["not in", filters_dict[filter]]
if filters.get("customer"):
del filters["customer"]

View File

@@ -12,7 +12,7 @@ from frappe.utils import cint, flt, format_datetime, get_datetime
import erpnext
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method
from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method, getdate
class StockOverReturnError(frappe.ValidationError):
@@ -759,6 +759,29 @@ def get_rate_for_return(
StockLedgerEntry = frappe.qb.DocType("Stock Ledger Entry")
select_field = Abs(StockLedgerEntry.stock_value_difference / StockLedgerEntry.actual_qty)
item_details = frappe.get_cached_value("Item", item_code, ["has_batch_no", "has_expiry_date"], as_dict=1)
set_zero_rate_for_expired_batch = frappe.db.get_single_value(
"Selling Settings", "set_zero_rate_for_expired_batch"
)
if (
set_zero_rate_for_expired_batch
and item_details.has_batch_no
and item_details.has_expiry_date
and not return_against
and voucher_type in ["Sales Invoice", "Delivery Note"]
):
# set incoming_rate zero explicitly for standalone credit note with expired batch
batch_no = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "batch_no")
if batch_no and is_batch_expired(batch_no, sle.get("posting_date")):
frappe.db.set_value(
voucher_type + " Item",
voucher_detail_no,
"incoming_rate",
0,
)
return 0
rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]:
rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate")
@@ -823,12 +846,34 @@ def get_filters(
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
if voucher_type in ["Purchase Receipt", "Purchase Invoice"] and item_row and item_row.get("warehouse"):
filters["warehouse"] = item_row.get("warehouse")
warehouses = []
if voucher_type in ["Purchase Receipt", "Purchase Invoice"] and item_row:
if reference_voucher_detail_no:
warehouses = get_warehouses_for_return(voucher_type, reference_voucher_detail_no)
if item_row.get("warehouse") and item_row.get("warehouse") in warehouses:
filters["warehouse"] = item_row.get("warehouse")
return filters
def get_warehouses_for_return(voucher_type, name):
warehouses = []
warehouse_details = frappe.get_all(
voucher_type + " Item",
filters={"name": name, "docstatus": 1},
fields=["warehouse", "rejected_warehouse"],
)
for d in warehouse_details:
if d.warehouse:
warehouses.append(d.warehouse)
if d.rejected_warehouse:
warehouses.append(d.rejected_warehouse)
return warehouses
def get_returned_serial_nos(child_doc, parent_doc, serial_no_field=None, ignore_voucher_detail_no=None):
from erpnext.stock.doctype.serial_no.serial_no import (
get_serial_nos as get_serial_nos_from_serial_no,
@@ -1276,3 +1321,17 @@ def get_sales_invoice_item_from_consolidated_invoice(return_against_pos_invoice,
return result[0].name if result else None
except Exception:
return None
def is_batch_expired(batch_no, posting_date):
"""
To check whether the batch is expired or not based on the posting date.
"""
expiry_date = frappe.db.get_value("Batch", batch_no, "expiry_date")
if not expiry_date:
return
if getdate(posting_date) > getdate(expiry_date):
return True
return False

View File

@@ -8,7 +8,7 @@ from frappe.utils import cint, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return, is_batch_expired
from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.item.item import set_item_default
from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
@@ -296,7 +296,7 @@ class SellingController(StockController):
_(
"""Row #{0}: Selling rate for item {1} is lower than its {2}.
Selling {3} should be atleast {4}.<br><br>Alternatively,
you can disable selling price validation in {5} to bypass
you can disable '{5}' in {6} to bypass
this validation."""
).format(
idx,
@@ -304,7 +304,8 @@ class SellingController(StockController):
bold(ref_rate_field),
bold("net rate"),
bold(rate),
get_link_to_form("Selling Settings", "Selling Settings"),
bold(frappe.get_meta("Selling Settings").get_label("validate_selling_price")),
get_link_to_form("Selling Settings"),
),
title=_("Invalid Selling Price"),
)
@@ -313,7 +314,6 @@ class SellingController(StockController):
return
is_internal_customer = self.get("is_internal_customer")
valuation_rate_map = {}
for item in self.items:
if not item.item_code or item.is_free_item:
@@ -323,7 +323,9 @@ class SellingController(StockController):
"Item", item.item_code, ("last_purchase_rate", "is_stock_item")
)
last_purchase_rate_in_sales_uom = last_purchase_rate * (item.conversion_factor or 1)
last_purchase_rate_in_sales_uom = flt(
last_purchase_rate * (item.conversion_factor or 1), item.precision("base_net_rate")
)
if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
throw_message(item.idx, item.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")
@@ -331,50 +333,16 @@ class SellingController(StockController):
if is_internal_customer or not is_stock_item:
continue
valuation_rate_map[(item.item_code, item.warehouse)] = None
if not valuation_rate_map:
return
or_conditions = (
f"""(item_code = {frappe.db.escape(valuation_rate[0])}
and warehouse = {frappe.db.escape(valuation_rate[1])})"""
for valuation_rate in valuation_rate_map
)
valuation_rates = frappe.db.sql(
f"""
select
item_code, warehouse, valuation_rate
from
`tabBin`
where
({" or ".join(or_conditions)})
and valuation_rate > 0
""",
as_dict=True,
)
for rate in valuation_rates:
valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
for item in self.items:
if not item.item_code or item.is_free_item:
continue
last_valuation_rate = valuation_rate_map.get((item.item_code, item.warehouse))
if not last_valuation_rate:
continue
last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
if item.get("incoming_rate") and item.base_net_rate < (
valuation_rate := flt(
item.incoming_rate * (item.conversion_factor or 1), item.precision("base_net_rate")
)
):
throw_message(
item.idx,
item.item_name,
last_valuation_rate_in_sales_uom,
"valuation rate (Moving Average)",
valuation_rate,
"valuation rate",
)
def get_item_list(self):
@@ -533,19 +501,37 @@ class SellingController(StockController):
if self.doctype not in ("Delivery Note", "Sales Invoice"):
return
from erpnext.stock.serial_batch_bundle import get_batch_nos
allow_at_arms_length_price = frappe.get_cached_value(
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
)
set_zero_rate_for_expired_batch = frappe.db.get_single_value(
"Selling Settings", "set_zero_rate_for_expired_batch"
)
old_doc = self.get_doc_before_save()
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
continue
item_details = frappe.get_cached_value(
"Item", d.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
"Item", d.item_code, ["has_serial_no", "has_batch_no", "has_expiry_date"], as_dict=1
)
if not self.get("return_against") or (
if (
set_zero_rate_for_expired_batch
and item_details.has_batch_no
and item_details.has_expiry_date
and self.get("is_return")
and not self.get("return_against")
and is_batch_expired(d.batch_no, self.get("posting_date"))
):
# set incoming rate as zero for stand-lone credit note with expired batch
d.incoming_rate = 0
elif not self.get("return_against") or (
get_valuation_method(d.item_code, self.company) == "Moving Average"
and self.get("is_return")
and not item_details.has_serial_no
@@ -554,6 +540,29 @@ class SellingController(StockController):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty"))
if old_doc:
old_item = next(
(
item
for item in (old_doc.get("items") + (old_doc.get("packed_items") or []))
if item.name == d.name
),
None,
)
if old_item:
old_qty = flt(
old_item.get("stock_qty") or old_item.get("actual_qty") or old_item.get("qty")
)
if (
old_item.item_code != d.item_code
or old_item.warehouse != d.warehouse
or old_qty != qty
or old_item.batch_no != d.batch_no
or get_batch_nos(old_item.serial_and_batch_bundle)
!= get_batch_nos(d.serial_and_batch_bundle)
):
d.incoming_rate = 0
if (
not d.incoming_rate
or self.is_internal_transfer()

View File

@@ -91,7 +91,8 @@ status_map = {
],
"Delivery Note": [
["Draft", None],
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
["To Bill", "eval:self.per_billed == 0 and self.docstatus == 1"],
["Partially Billed", "eval:self.per_billed < 100 and self.per_billed > 0 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Return", "eval:self.is_return == 1 and self.per_billed == 0 and self.docstatus == 1"],
@@ -443,7 +444,7 @@ class StatusUpdater(Document):
):
return
if args["source_dt"] != "Pick List Item":
if args["source_dt"] != "Pick List Item" and args["target_dt"] != "Quotation Item":
if qty_or_amount == "qty":
action_msg = _(
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'

View File

@@ -552,7 +552,10 @@ class StockController(AccountsController):
if is_rejected:
serial_nos = row.get("rejected_serial_no")
type_of_transaction = "Inward" if not self.is_return else "Outward"
qty = row.get("rejected_qty")
qty = flt(
row.get("rejected_qty") * row.get("conversion_factor", 1.0),
frappe.get_precision("Serial and Batch Entry", "qty"),
)
warehouse = row.get("rejected_warehouse")
if (
@@ -2014,7 +2017,7 @@ def get_gl_entries_for_preview(doctype, docname, fields):
def get_columns(raw_columns, fields):
return [
{"name": d.get("label"), "editable": False, "width": 110}
{"name": d.get("label"), "editable": False, "width": 110, "fieldtype": d.get("fieldtype")}
for d in raw_columns
if not d.get("hidden") and d.get("fieldname") in fields
]

View File

@@ -166,29 +166,46 @@ class SubcontractingController(StockController):
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
)
if self.doctype != "Subcontracting Receipt" and item.qty > flt(
get_pending_subcontracted_quantity(
self.doctype,
self.purchase_order if self.doctype == "Subcontracting Order" else self.sales_order,
).get(
item.purchase_order_item
if self.doctype == "Subcontracting Order"
else item.sales_order_item
)
/ item.subcontracting_conversion_factor,
frappe.get_precision(
if self.doctype != "Subcontracting Receipt":
order_item_doctype = (
"Purchase Order Item"
if self.doctype == "Subcontracting Order"
else "Sales Order Item",
"qty",
),
):
frappe.throw(
_(
"Row {0}: Item {1}'s quantity cannot be higher than the available quantity."
).format(item.idx, item.item_name)
else "Sales Order Item"
)
order_name = (
self.purchase_order if self.doctype == "Subcontracting Order" else self.sales_order
)
order_item_field = frappe.scrub(order_item_doctype)
if not item.get(order_item_field):
frappe.throw(
_("Row {0}: Item {1} must be linked to a {2}.").format(
item.idx, item.item_name, order_item_doctype
)
)
pending_qty = flt(
flt(
get_pending_subcontracted_quantity(
order_item_doctype,
order_name,
).get(item.get(order_item_field))
)
/ item.subcontracting_conversion_factor,
frappe.get_precision(
order_item_doctype,
"qty",
),
)
if item.qty > pending_qty:
frappe.throw(
_(
"Row {0}: Item {1}'s quantity cannot be higher than the available quantity."
).format(item.idx, item.item_name)
)
if self.doctype != "Subcontracting Inward Order":
item.amount = item.qty * item.rate
@@ -296,10 +313,10 @@ class SubcontractingController(StockController):
):
for row in frappe.get_all(
f"{self.subcontract_data.order_doctype} Item",
fields=["item_code", {"SUB": ["qty", "received_qty"], "as": "qty"}, "parent", "name"],
fields=["item_code", {"SUB": ["qty", "received_qty"], "as": "qty"}, "parent", "bom"],
filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)},
):
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
self.qty_to_be_received[(row.item_code, row.parent, row.bom)] += row.qty
def __get_transferred_items(self):
se = frappe.qb.DocType("Stock Entry")
@@ -610,7 +627,9 @@ class SubcontractingController(StockController):
and self.doctype != "Subcontracting Inward Order"
):
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
elif frappe.get_cached_value("Item", row.rm_item_code, "is_customer_provided_item"):
elif frappe.get_cached_value("Item", row.rm_item_code, "is_customer_provided_item") and self.get(
"customer_warehouse"
):
row.warehouse = self.customer_warehouse
def __set_alternative_item(self, bom_item):
@@ -904,13 +923,17 @@ class SubcontractingController(StockController):
self.__set_serial_nos(item_row, rm_obj)
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
key = (
item_row.item_code,
item_row.get(self.subcontract_data.order_field),
item_row.get("bom"),
)
if self.qty_to_be_received == item_row.qty:
return transfer_item.qty
if self.qty_to_be_received:
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
if self.qty_to_be_received.get(key):
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key))
transfer_item.item_details.required_qty = transfer_item.qty
if transfer_item.serial_no or frappe.get_cached_value(
@@ -959,7 +982,11 @@ class SubcontractingController(StockController):
if self.qty_to_be_received:
self.qty_to_be_received[
(row.item_code, row.get(self.subcontract_data.order_field))
(
row.item_code,
row.get(self.subcontract_data.order_field),
row.get("bom"),
)
] -= row.qty
def __set_rate_for_serial_and_batch_bundle(self):
@@ -1331,9 +1358,7 @@ def get_item_details(items):
def get_pending_subcontracted_quantity(doctype, name):
table = frappe.qb.DocType(
"Purchase Order Item" if doctype == "Subcontracting Order" else "Sales Order Item"
)
table = frappe.qb.DocType(doctype)
query = (
frappe.qb.from_(table)
.select(table.name, table.stock_qty, table.subcontracted_qty)

View File

@@ -720,6 +720,7 @@ class SubcontractingInwardController:
item.db_set("scio_detail", scio_rm.name)
if data:
precision = self.precision("customer_provided_item_cost", "items")
result = frappe.get_all(
"Subcontracting Inward Order Received Item",
filters={
@@ -734,10 +735,17 @@ class SubcontractingInwardController:
table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
case_expr_qty, case_expr_rate = Case(), Case()
for d in result:
d.received_qty += (
data[d.name].transfer_qty if self._action == "submit" else -data[d.name].transfer_qty
current_qty = flt(data[d.name].transfer_qty) * (1 if self._action == "submit" else -1)
current_rate = flt(data[d.name].rate)
# Calculate weighted average rate
old_total = d.rate * d.received_qty
current_total = current_rate * current_qty
d.received_qty = d.received_qty + current_qty
d.rate = (
flt((old_total + current_total) / d.received_qty, precision) if d.received_qty else 0.0
)
d.rate += data[d.name].rate if self._action == "submit" else -data[d.name].rate
if not d.required_qty and not d.received_qty:
deleted_docs.append(d.name)

View File

@@ -39,17 +39,23 @@ class calculate_taxes_and_totals:
items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
return items
def calculate(self):
def calculate(self, ignore_tax_template_validation=False):
if not len(self.doc.items):
return
self.discount_amount_applied = False
self.need_recomputation = False
self.ignore_tax_template_validation = ignore_tax_template_validation
self._calculate()
if self.doc.meta.get_field("discount_amount"):
self.set_discount_amount()
self.apply_discount_amount()
if not ignore_tax_template_validation and self.need_recomputation:
return self.calculate(ignore_tax_template_validation=True)
# Update grand total as per cash and non trade discount
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
self.doc.grand_total -= self.doc.discount_amount
@@ -79,6 +85,9 @@ class calculate_taxes_and_totals:
self.calculate_total_net_weight()
def validate_item_tax_template(self):
if self.ignore_tax_template_validation:
return
if self.doc.get("is_return") and self.doc.get("return_against"):
return
@@ -122,6 +131,10 @@ class calculate_taxes_and_totals:
)
)
# For correct tax_amount calculation re-computation is required
if self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total":
self.need_recomputation = True
def update_item_tax_map(self):
for item in self.doc.items:
item.item_tax_rate = get_item_tax_map(

View File

@@ -2,8 +2,6 @@
# For license information, please see license.txt
from datetime import datetime
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
@@ -2480,3 +2478,21 @@ class TestAccountsController(IntegrationTestCase):
self.assertRaises(frappe.ValidationError, po.save)
po.items[0].delivered_by_supplier = 1
po.save()
@IntegrationTestCase.change_settings("Global Defaults", {"use_posting_datetime_for_naming_documents": 1})
def test_document_naming_rule_based_on_posting_date(self):
frappe.new_doc(
"Document Naming Rule", document_type="Sales Invoice", prefix="SI-.MM.-.YYYY.-"
).submit()
si = create_sales_invoice(do_not_save=True)
si.set_posting_time = 1
si.posting_date = "2025-12-31"
si.save()
self.assertEqual(si.name, "SI-12-2025-00001")
si = create_sales_invoice(do_not_save=True)
si.set_posting_time = 1
si.posting_date = "2026-01-01"
si.save()
self.assertEqual(si.name, "SI-01-2026-00002")

View File

@@ -38,18 +38,18 @@ class EmailCampaign(Document):
def set_date(self):
if getdate(self.start_date) < getdate(today()):
frappe.throw(_("Start Date cannot be before the current date"))
# set the end date as start date + max(send after days) in campaign schedule
send_after_days = []
campaign = frappe.get_doc("Campaign", self.campaign_name)
for entry in campaign.get("campaign_schedules"):
send_after_days.append(entry.send_after_days)
try:
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
except ValueError:
campaign = frappe.get_cached_doc("Campaign", self.campaign_name)
send_after_days = [entry.send_after_days for entry in campaign.get("campaign_schedules")]
if not send_after_days:
frappe.throw(
_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name)
)
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
def validate_lead(self):
lead_email_id = frappe.db.get_value("Lead", self.recipient, "email_id")
if not lead_email_id:
@@ -77,58 +77,128 @@ class EmailCampaign(Document):
start_date = getdate(self.start_date)
end_date = getdate(self.end_date)
today_date = getdate(today())
if start_date > today_date:
self.db_set("status", "Scheduled", update_modified=False)
new_status = "Scheduled"
elif end_date >= today_date:
self.db_set("status", "In Progress", update_modified=False)
elif end_date < today_date:
self.db_set("status", "Completed", update_modified=False)
new_status = "In Progress"
else:
new_status = "Completed"
if self.status != new_status:
self.db_set("status", new_status, update_modified=False)
# called through hooks to send campaign mails to leads
def send_email_to_leads_or_contacts():
today_date = getdate(today())
# Get all active email campaigns in a single query
email_campaigns = frappe.get_all(
"Email Campaign", filters={"status": ("not in", ["Unsubscribed", "Completed", "Scheduled"])}
"Email Campaign",
filters={"status": "In Progress"},
fields=["name", "campaign_name", "email_campaign_for", "recipient", "start_date", "sender"],
)
for camp in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", camp.name)
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
if not email_campaigns:
return
# Process each email campaign
for email_campaign in email_campaigns:
try:
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
except frappe.DoesNotExistError:
frappe.log_error(
title=_("Email Campaign Error"),
message=_("Campaign {0} not found").format(email_campaign.campaign_name),
)
continue
# Find schedules that match today
for entry in campaign.get("campaign_schedules"):
scheduled_date = add_days(email_campaign.get("start_date"), entry.get("send_after_days"))
if scheduled_date == getdate(today()):
send_mail(entry, email_campaign)
try:
scheduled_date = add_days(getdate(email_campaign.start_date), entry.get("send_after_days"))
if scheduled_date == today_date:
send_mail(entry, email_campaign)
except Exception:
frappe.log_error(
title=_("Email Campaign Send Error"),
message=_("Failed to send email for campaign {0} to {1}").format(
email_campaign.name, email_campaign.recipient
),
)
def send_mail(entry, email_campaign):
recipient_list = []
if email_campaign.email_campaign_for == "Email Group":
for member in frappe.db.get_list(
"Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]
):
recipient_list.append(member["email"])
campaign_for = email_campaign.get("email_campaign_for")
recipient = email_campaign.get("recipient")
sender_user = email_campaign.get("sender")
campaign_name = email_campaign.get("name")
# Get recipient emails
if campaign_for == "Email Group":
recipient_list = frappe.get_all(
"Email Group Member",
filters={"email_group": recipient, "unsubscribed": 0},
pluck="email",
)
else:
recipient_list.append(
frappe.db.get_value(
email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id"
email_id = frappe.db.get_value(campaign_for, recipient, "email_id")
if not email_id:
frappe.log_error(
title=_("Email Campaign Error"),
message=_("No email found for {0} {1}").format(campaign_for, recipient),
)
return
recipient_list = [email_id]
if not recipient_list:
frappe.log_error(
title=_("Email Campaign Error"),
message=_("No recipients found for campaign {0}").format(campaign_name),
)
return
# Get email template and sender
email_template = frappe.get_cached_doc("Email Template", entry.get("email_template"))
sender = frappe.db.get_value("User", sender_user, "email") if sender_user else None
# Build context for template rendering
if campaign_for != "Email Group":
context = {"doc": frappe.get_doc(campaign_for, recipient)}
else:
# For email groups, use the email group document as context
context = {"doc": frappe.get_doc("Email Group", recipient)}
# Render template
subject = frappe.render_template(email_template.get("subject"), context)
content = frappe.render_template(email_template.response_, context)
try:
comm = make(
doctype="Email Campaign",
name=campaign_name,
subject=subject,
content=content,
sender=sender,
recipients=recipient_list,
communication_medium="Email",
sent_or_received="Sent",
send_email=False,
email_template=email_template.name,
)
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
sender = frappe.db.get_value("User", email_campaign.get("sender"), "email")
context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)}
# send mail and link communication to document
comm = make(
doctype="Email Campaign",
name=email_campaign.name,
subject=frappe.render_template(email_template.get("subject"), context),
content=frappe.render_template(email_template.response_, context),
sender=sender,
bcc=recipient_list,
communication_medium="Email",
sent_or_received="Sent",
send_email=True,
email_template=email_template.name,
)
frappe.sendmail(
recipients=recipient_list,
subject=subject,
content=content,
sender=sender,
communication=comm["name"],
queue_separately=True,
)
except Exception:
frappe.log_error(title="Email Campaign Failed.")
return comm
@@ -140,7 +210,12 @@ def unsubscribe_recipient(unsubscribe, method):
# called through hooks to update email campaign status daily
def set_email_campaign_status():
email_campaigns = frappe.get_all("Email Campaign", filters={"status": ("!=", "Unsubscribed")})
for entry in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", entry.name)
email_campaigns = frappe.get_all(
"Email Campaign",
filters={"status": ("!=", "Unsubscribed")},
pluck="name",
)
for name in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", name)
email_campaign.update_status()

View File

@@ -1,20 +1,20 @@
{
"app": "erpnext",
"creation": "2025-11-17 20:55:11.854086",
"creation": "2026-01-27 17:02:43.440221",
"docstatus": 0,
"doctype": "Desktop Icon",
"hidden": 0,
"icon": "accounting",
"icon_type": "Link",
"icon_type": "Folder",
"idx": 1,
"label": "Accounting",
"link_to": "Accounting",
"link_to": "",
"link_type": "Workspace Sidebar",
"modified": "2026-01-01 20:07:01.203651",
"modified": "2026-01-27 17:04:04.351402",
"modified_by": "Administrator",
"name": "Accounting",
"owner": "Administrator",
"parent_icon": "Accounts",
"parent_icon": "",
"restrict_removal": 0,
"roles": [],
"standard": 1

View File

@@ -1,18 +0,0 @@
{
"app": "erpnext",
"creation": "2025-11-12 13:07:51.988728",
"docstatus": 0,
"doctype": "Desktop Icon",
"hidden": 0,
"icon_type": "Folder",
"idx": 1,
"label": "Accounts",
"link_type": "DocType",
"logo_url": "",
"modified": "2025-11-17 17:39:36.915358",
"modified_by": "Administrator",
"name": "Accounts",
"owner": "Administrator",
"roles": [],
"standard": 1
}

Some files were not shown because too many files have changed in this diff Show More