Compare commits

...

813 Commits

Author SHA1 Message Date
Frappe PR Bot
a66854d16d chore(release): Bumped to Version 15.98.0
# [15.98.0](https://github.com/frappe/erpnext/compare/v15.97.0...v15.98.0) (2026-02-17)

### Bug Fixes

* **accounts-controller:** handle empty items list ([13239a9](13239a9dee))
* **accounts:** correct base grand total and rounded total mismatch ([#51739](https://github.com/frappe/erpnext/issues/51739)) ([8bdbb24](8bdbb24d73))
* add base_tax_withholding_net_total to tax withholding report ([ed42d54](ed42d54989))
* allow rename for market segment doctype ([0a41987](0a4198718b))
* allow sequence id edit in BOM if routing is not set ([c425944](c425944bdf))
* better validation for negative batch ([85d18fa](85d18fa7a4))
* cancel SABB if SLE cancelled from LCV ([f2a77d1](f2a77d178d))
* consider sle for negative stock validation ([ca79f64](ca79f6478a))
* do not allow plant floor company and warehouse to be updated ([d6333c1](d6333c1562))
* **manufacturing:** add sales order fields in subassembly child table ([0576752](0576752d3b))
* **manufacturing:** set sales order references in subassembly child table ([53e18a9](53e18a9beb))
* Payment Terms auto-fetched in Sales Invoice even when automatically_fetch_payment_terms is disabled ([78a3701](78a3701f4c))
* **pos_invoice:** add correct depends on condition (backport [#52689](https://github.com/frappe/erpnext/issues/52689)) ([#52693](https://github.com/frappe/erpnext/issues/52693)) ([4fe9689](4fe968961a))
* **postgres:** validate against period closing using MAX(period_end_date) ([#51554](https://github.com/frappe/erpnext/issues/51554)) ([9ec3031](9ec30319e4))
* production plan status ([97a6610](97a6610c0c))
* recalculate tax withholding during Purchase Order child update ([273029d](273029d0f0))
* set base_tax_withholding_net_total for jv in tds report ([68099a9](68099a9b5c))
* standalone credit/debit notes should not fetch any serial or batch by default ([79c3bc9](79c3bc9bcd))
* total weight does not update when updating items ([e12871b](e12871b408))

### Features

* Negative Batch report ([6313636](631363632b))
* show formatted currency symbol on ledger preview ([383648f](383648fb59))
2026-02-17 14:12:38 +00:00
ruthra kumar
72bb3e26cd Merge pull request #52730 from frappe/version-15-hotfix
chore: release v15
2026-02-17 19:38:01 +05:30
rohitwaghchaure
128c2bf8b9 Merge pull request #52739 from frappe/mergify/bp/version-15-hotfix/pr-52729
feat: Negative Batch report (backport #52729)
2026-02-17 17:17:42 +05:30
Mihir Kandoi
7aa46af0c3 Merge pull request #52735 from frappe/mergify/bp/version-15-hotfix/pr-52733
fix: allow sequence ID edit in BOM if routing is not set (backport #52733)
2026-02-17 16:39:01 +05:30
Rohit Waghchaure
631363632b feat: Negative Batch report
(cherry picked from commit 34edbed00b)
2026-02-17 11:04:17 +00:00
Mihir Kandoi
3e3c489178 Merge pull request #52737 from frappe/mergify/bp/version-15-hotfix/pr-52677
fix: standalone credit/debit notes should not fetch any serial or bat… (backport #52677)
2026-02-17 16:25:58 +05:30
Mihir Kandoi
79c3bc9bcd fix: standalone credit/debit notes should not fetch any serial or batch by default
(cherry picked from commit 2017edca88)
2026-02-17 10:40:08 +00:00
Mihir Kandoi
c6682f130c chore: resolve conflicts 2026-02-17 16:07:49 +05:30
Mihir Kandoi
c425944bdf fix: allow sequence id edit in BOM if routing is not set
(cherry picked from commit 08529964b4)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_operation/bom_operation.json
2026-02-17 10:35:58 +00:00
Mihir Kandoi
0935b181bf Merge pull request #52721 from aerele/backport-52626
fix(manufacturing): add sales order fields in subassembly child table (backport #52626)
2026-02-17 13:42:26 +05:30
ruthra kumar
bc50b94a87 Merge pull request #52595 from ljain112/fix-tds-report-v15
fix: add base_tax_withholding_net_total to tax withholding report
2026-02-17 13:39:48 +05:30
Mihir Kandoi
02f16715e0 Merge pull request #52717 from frappe/mergify/bp/version-15-hotfix/pr-52716
fix: do not allow plant floor company and warehouse to be updated (backport #52716)
2026-02-17 12:42:30 +05:30
ruthra kumar
f0ab9b6e76 Merge pull request #52151 from aerele/fix-po-tax-withholding
fix(purchase order): re-calculate tax withholding during update items
2026-02-17 12:19:06 +05:30
Pandiyan37
d2dc0a4c9a test(manufacturing): add test to validate the sales order references for sub assembly items 2026-02-17 12:15:33 +05:30
Mihir Kandoi
e8b46d9815 chore: resolve conflicts 2026-02-17 12:13:33 +05:30
Mihir Kandoi
d6333c1562 fix: do not allow plant floor company and warehouse to be updated
(cherry picked from commit fd72132743)

# Conflicts:
#	erpnext/manufacturing/doctype/plant_floor/plant_floor.json
2026-02-17 06:42:15 +00:00
Pandiyan37
53e18a9beb fix(manufacturing): set sales order references in subassembly child table 2026-02-17 12:11:05 +05:30
Pandiyan37
0576752d3b fix(manufacturing): add sales order fields in subassembly child table 2026-02-17 12:10:08 +05:30
Mihir Kandoi
e3a2b310d8 Merge pull request #52714 from frappe/mergify/bp/version-15-hotfix/pr-52713
fix: production plan status (backport #52713)
2026-02-17 11:40:07 +05:30
Mihir Kandoi
97a6610c0c fix: production plan status
(cherry picked from commit b3e6b304e4)
2026-02-17 05:53:54 +00:00
ili.ad
9ec30319e4 fix(postgres): validate against period closing using MAX(period_end_date) (#51554)
* fix(postgres): validate against period closing using MAX(period_end_date)

* refactor: remove non-existent field

---------

Co-authored-by: Matt Howard <github.severity519@passmail.net>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2026-02-17 05:02:53 +00:00
rohitwaghchaure
e511503597 Merge pull request #52700 from rohitwaghchaure/fixed-negative-stock-validation-sle-v15
fix: consider sle for negative stock validation
2026-02-16 23:56:18 +05:30
Rohit Waghchaure
ca79f6478a fix: consider sle for negative stock validation 2026-02-16 23:29:59 +05:30
rohitwaghchaure
9ece276e76 Merge pull request #52695 from frappe/mergify/bp/version-15-hotfix/pr-52691
fix: cancel SABB if SLE cancelled from LCV (backport #52691)
2026-02-16 21:52:48 +05:30
mergify[bot]
4fe968961a fix(pos_invoice): add correct depends on condition (backport #52689) (#52693)
* 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)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json

* chore: resolve conflict

---------

Co-authored-by: Soham Kulkarni <77533095+sokumon@users.noreply.github.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2026-02-16 16:01:23 +00:00
Rohit Waghchaure
f2a77d178d fix: cancel SABB if SLE cancelled from LCV
(cherry picked from commit f23a49a25e)
2026-02-16 15:42:04 +00:00
rohitwaghchaure
dbbba7262b Merge pull request #52683 from frappe/mergify/bp/version-15-hotfix/pr-52681
fix: better validation for negative batch (backport #52681)
2026-02-16 16:03:59 +05:30
Rohit Waghchaure
85d18fa7a4 fix: better validation for negative batch
(cherry picked from commit a8636e4f59)
2026-02-16 09:46:46 +00:00
Mihir Kandoi
11a6c5b394 Merge pull request #52671 from frappe/mergify/bp/version-15-hotfix/pr-52670 2026-02-15 14:30:17 +05:30
Mihir Kandoi
e12871b408 fix: total weight does not update when updating items
(cherry picked from commit 63323a2611)
2026-02-15 08:45:04 +00:00
ruthra kumar
a75c2cb4a0 Merge pull request #52645 from frappe/mergify/bp/version-15-hotfix/pr-52644
refactor: use query builder for profitability analysis (backport #52644)
2026-02-12 14:39:35 +05:30
ruthra kumar
c18ed0862e refactor: use query builder for profitability analysis
(cherry picked from commit 5e34325604)
2026-02-12 08:54:48 +00:00
ruthra kumar
8bddefb18b Merge pull request #52641 from frappe/mergify/bp/version-15-hotfix/pr-52640
refactor: use query builder for sales person commission summary (backport #52640)
2026-02-12 12:52:37 +05:30
ruthra kumar
55448b7437 Merge pull request #50911 from Dharanidharan2813/fix/list-index-bp-50860
fix: Payment Terms auto-fetched in Sales Invoice Without enabling the (automatically_fetch_payment_terms) in Account settings  (backport #50149)
2026-02-12 12:51:08 +05:30
ruthra kumar
ac02af476a refactor: use query builder for sales person commission summary
(cherry picked from commit 7105e3fb69)
2026-02-12 07:07:25 +00:00
ruthra kumar
0a1b532f69 Merge pull request #52637 from frappe/mergify/bp/version-15-hotfix/pr-52619
feat: show formatted currency symbol on ledger preview (backport #52619)
2026-02-12 11:46:37 +05:30
Navin-S-R
383648fb59 feat: show formatted currency symbol on ledger preview
(cherry picked from commit 5c8cb1e7ec)
2026-02-12 05:58:43 +00:00
Dharanidharan S
13239a9dee fix(accounts-controller): handle empty items list 2026-02-12 11:18:44 +05:30
diptanilsaha
2c13b2cc22 test: fixed test_make_sales_invoice_with_terms 2026-02-12 11:18:44 +05:30
Diptanil Saha
4da44e2c3f chore: resolve linter issue 2026-02-12 11:18:44 +05:30
Diptanil Saha
a503460bd5 chore: resolve conflict 2026-02-12 11:18:44 +05:30
dharanidharan2813
78a3701f4c fix: Payment Terms auto-fetched in Sales Invoice even when automatically_fetch_payment_terms is disabled
(cherry picked from commit cf1d892d60)

# Conflicts:
#	erpnext/selling/doctype/sales_order/test_sales_order.py
2026-02-12 11:18:44 +05:30
ruthra kumar
eedb93b2d6 Merge pull request #52100 from frappe/mergify/bp/version-15-hotfix/pr-51739
fix(accounts): correct base grand total and rounded total mismatch (backport #51739)
2026-02-12 11:13:27 +05:30
Diptanil Saha
ea3042bc74 Merge pull request #52614 from frappe/mergify/bp/version-15-hotfix/pr-51155
fix: allow rename for market segment doctype (backport #51155)
2026-02-11 14:56:34 +05:30
diptanilsaha
b977366dcd chore: resolve conflict 2026-02-11 14:41:01 +05:30
diptanilsaha
0a4198718b fix: allow rename for market segment doctype
(cherry picked from commit f3142c4af6)

# Conflicts:
#	erpnext/crm/doctype/market_segment/market_segment.json
2026-02-11 08:59:24 +00:00
Frappe PR Bot
b340d7c6bb chore(release): Bumped to Version 15.97.0
# [15.97.0](https://github.com/frappe/erpnext/compare/v15.96.1...v15.97.0) (2026-02-11)

### Bug Fixes

* Added a missing option to the currency field (backport [#52528](https://github.com/frappe/erpnext/issues/52528)) ([#52586](https://github.com/frappe/erpnext/issues/52586)) ([a6f5b88](a6f5b88f9b))
* **buying:** add supplier group link filters in field level ([436cb8d](436cb8dbfc))
* email campaign timeout issue (backport [#51994](https://github.com/frappe/erpnext/issues/51994)) ([#52555](https://github.com/frappe/erpnext/issues/52555)) ([6c9681b](6c9681ba4c))
* enabling skip delivery option for order type maintenance ([a8f05ca](a8f05cadea))
* **gross profit report:** translate column Sales Invoice ([4e910d8](4e910d8a69))
* **gross-profit:** handle item group filters ([7cd9de2](7cd9de211f))
* **gross-profit:** handle returns outside sale period ([303dac2](303dac262c))
* handle gross profit and percentage for return invoices ([bde19ab](bde19ab010))
* **manufacturing:** fix chart period keys ([99f3a7e](99f3a7e4cf))
* **manufacturing:** handle None value for actual_end_date ([f965b35](f965b352c8))
* **map_current_doc:** prevent mutation of query args in get_query (backport [#52202](https://github.com/frappe/erpnext/issues/52202)) ([#52583](https://github.com/frappe/erpnext/issues/52583)) ([9519773](9519773c5c))
* merge taxes in purchase receipt when get items from multiple purchase invoices ([#51422](https://github.com/frappe/erpnext/issues/51422)) ([68338ab](68338abe07))
* **quotation:** ignore zero ordered_qty ([ad92c02](ad92c021f7))
* rate comparison in stock reco ([cacca81](cacca812ed))
* remove incorrect validation from email digest throwing spurious error (backport [#51827](https://github.com/frappe/erpnext/issues/51827)) ([#52582](https://github.com/frappe/erpnext/issues/52582)) ([b034f3d](b034f3d3db))
* resolve conflicts ([36e2cf4](36e2cf49f3))
* return None instead of 0 if valuation rate is falsy ([195f020](195f020636))
* stock balance report issue ([bda7220](bda7220b70))
* **stock:** add is group filter for warehouse fields ([5b7ee0a](5b7ee0af66))
* **stock:** ignore pos reserved batches for stock levels ([635a421](635a421807))
* **stock:** inward stock for pick list test record ([5a42ff0](5a42ff0c3c))
* **stock:** set source warehouse for issue type ([19dca36](19dca36dec))
* **stock:** update target field attribute ([9cfd704](9cfd704eef))
* validate asset movement transaction date (backport [#52340](https://github.com/frappe/erpnext/issues/52340)) ([#52560](https://github.com/frappe/erpnext/issues/52560)) ([eea8cb5](eea8cb5885))

### Features

* allow negative stock for the batch item ([4c094c3](4c094c3d86))
2026-02-11 04:58:18 +00:00
ruthra kumar
0bd3c3b566 Merge pull request #52598 from frappe/version-15-hotfix
chore: release v15
2026-02-11 10:26:50 +05:30
Dharanidharan S
8bdbb24d73 fix(accounts): correct base grand total and rounded total mismatch (#51739)
(cherry picked from commit d82c92a237)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js
2026-02-10 19:05:23 +05:30
ruthra kumar
442e46c80f Merge pull request #52603 from frappe/mergify/bp/version-15-hotfix/pr-52017
fix(gross-profit): handle returns outside the given sale period (backport #52017)
2026-02-10 18:24:27 +05:30
ruthra kumar
83a72b8b30 Merge pull request #52413 from frappe/mergify/bp/version-15-hotfix/pr-51745
fix(gross profit report): translate column Sales Invoice (backport #51745)
2026-02-10 18:23:04 +05:30
ruthra kumar
a50836ab07 Merge pull request #52553 from frappe/mergify/bp/version-15-hotfix/pr-52501
fix(quotation): ignore zero ordered_qty (backport #52501)
2026-02-10 18:22:08 +05:30
Kavin
e9c4762309 Merge pull request #52541 from frappe/mergify/bp/version-15-hotfix/pr-52516
fix(stock): ignore pos reserved batches for stock levels (backport #52516)
2026-02-10 18:21:21 +05:30
Navin-S-R
7cd9de211f fix(gross-profit): handle item group filters
(cherry picked from commit 047b278791)
2026-02-10 12:41:08 +00:00
Navin-S-R
da37fea583 test: fix test assertions to use index-based totals
(cherry picked from commit fdfa7bc963)
2026-02-10 12:41:08 +00:00
Navin-S-R
a912b78bb8 test: validate sales person wise gross profit
(cherry picked from commit 3ab978ab46)
2026-02-10 12:41:08 +00:00
Navin-S-R
8ba5ef683f test: validate return invoice profit and profit percentage
(cherry picked from commit 4da3d43013)
2026-02-10 12:41:08 +00:00
Navin-S-R
bde19ab010 fix: handle gross profit and percentage for return invoices
(cherry picked from commit 51709f032f)
2026-02-10 12:41:07 +00:00
Navin-S-R
303dac262c fix(gross-profit): handle returns outside sale period
(cherry picked from commit 67d8223f73)
2026-02-10 12:41:07 +00:00
Kavin
3e4bd3040a Merge pull request #52591 from aerele/backport-52527
fix(stock): correct warehouse mapping for material issue (backport #52527)
2026-02-10 15:33:53 +05:30
Kavin
ae490804f9 chore: fix failing pre-commit checks
- Remove empty line with spaces
2026-02-10 15:02:23 +05:30
ljain112
e57f3fe727 test: update expected values for tax withholding calculations in tests 2026-02-10 14:40:53 +05:30
ljain112
68099a9b5c fix: set base_tax_withholding_net_total for jv in tds report 2026-02-10 13:53:31 +05:30
Kavin
36e2cf49f3 fix: resolve conflicts
- Remove POS Settings configuration for version 15 backport.
2026-02-10 13:35:52 +05:30
ljain112
ed42d54989 fix: add base_tax_withholding_net_total to tax withholding report 2026-02-10 13:30:12 +05:30
ljain112
b740846b68 refactor: update labels for tax withholding reports columns to improve clarity 2026-02-10 13:08:35 +05:30
Pandiyan37
5a42ff0c3c fix(stock): inward stock for pick list test record 2026-02-10 12:14:54 +05:30
Pandiyan37
37ca45ea49 test(stock): add test to check from warehouse for issue type 2026-02-10 12:11:59 +05:30
Pandiyan37
19dca36dec fix(stock): set source warehouse for issue type 2026-02-10 12:10:34 +05:30
mergify[bot]
a6f5b88f9b fix: Added a missing option to the currency field (backport #52528) (#52586)
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 20:59:58 +00:00
mergify[bot]
6c9681ba4c fix: email campaign timeout issue (backport #51994) (#52555)
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>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2026-02-10 02:10:05 +05:30
mergify[bot]
9519773c5c fix(map_current_doc): prevent mutation of query args in get_query (backport #52202) (#52583)
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:16 +05:30
mergify[bot]
eea8cb5885 fix: validate asset movement transaction date (backport #52340) (#52560)
* 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)

# Conflicts:
#	erpnext/assets/doctype/asset_movement/test_asset_movement.py

* chore: fix conflicts

Removed unused imports and cleaned up code.

---------

Co-authored-by: Poojashree T R <159940572+22-poojashree@users.noreply.github.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2026-02-10 01:02:23 +05:30
Trusted Computer
b034f3d3db fix: remove incorrect validation from email digest throwing spurious error (backport #51827) (#52582) 2026-02-09 23:45:56 +05:30
rohitwaghchaure
cf851cfa56 Merge pull request #52557 from frappe/mergify/bp/version-15-hotfix/pr-52550
feat: allow negative stock for the batch item (backport #52550)
2026-02-09 20:07:43 +05:30
rohitwaghchaure
7f69556c45 chore: fix conflicts 2026-02-09 16:31:33 +05:30
Rohit Waghchaure
4c094c3d86 feat: allow negative stock for the batch item
(cherry picked from commit 376ab0e346)

# Conflicts:
#	erpnext/stock/doctype/stock_settings/stock_settings.json
2026-02-09 10:50:24 +00:00
ravibharathi656
ad92c021f7 fix(quotation): ignore zero ordered_qty
(cherry picked from commit 32ea37035e)
2026-02-09 10:38:44 +00:00
Sudharsanan11
aedab5c210 test(stock): add test to ignore pos reserved batches for stock levels
(cherry picked from commit 47ac67f7a2)
2026-02-09 06:28:42 +00:00
Sudharsanan11
635a421807 fix(stock): ignore pos reserved batches for stock levels
(cherry picked from commit 277ba9cb79)
2026-02-09 06:28:42 +00:00
Mihir Kandoi
816cbdea0d Merge pull request #52523 from frappe/mergify/bp/version-15-hotfix/pr-52497
fix: add is_group filter for supplier_group and warehouse fields (backport #52497)
2026-02-07 22:02:24 +05:30
Mihir Kandoi
66a4823640 chore: resolve conflicts 2026-02-07 21:45:35 +05:30
Sudharsanan11
5b7ee0af66 fix(stock): add is group filter for warehouse fields
(cherry picked from commit a9829f5f7b)
2026-02-07 16:11:16 +00:00
Sudharsanan11
436cb8dbfc 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:16 +00:00
Mihir Kandoi
ad2b8d2455 Merge pull request #52440 from frappe/mergify/bp/version-15-hotfix/pr-52416
fix(stock): update target field attribute (backport #52416)
2026-02-06 12:53:38 +05:30
Mihir Kandoi
2a1a7fd1f6 Merge pull request #52487 from frappe/mergify/bp/version-15-hotfix/pr-52219
fix: enabling skip delivery option for order type maintenance (backport #52219)
2026-02-06 12:46:22 +05:30
Pandiyan37
d0a8639a2d test(stock): testcase for different inventory dimension
(cherry picked from commit 21d0ee8db1)
2026-02-06 12:28:22 +05:30
Pandiyan37
9cfd704eef fix(stock): update target field attribute
(cherry picked from commit 7e08154217)
2026-02-06 12:28:22 +05:30
mergify[bot]
29b35494da Merge pull request #52483 from frappe/mergify/bp/version-15-hotfix/pr-52475
fix: do not show update stock flag unneccessarily (backport #52475)
2026-02-06 06:46:54 +00:00
Mihir Kandoi
292f17b1b0 chore: resolve conflicts 2026-02-06 12:12:59 +05:30
Mihir Kandoi
740dd878e9 chore: resolve conflicts 2026-02-06 12:12:24 +05:30
Nishka Gosalia
a8f05cadea fix: enabling skip delivery option for order type maintenance
(cherry picked from commit 1a22e3cb61)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.json
#	erpnext/selling/doctype/sales_order/test_sales_order.py
2026-02-06 06:39:47 +00:00
Frappe PR Bot
9c2b4f611d chore(release): Bumped to Version 15.96.1
## [15.96.1](https://github.com/frappe/erpnext/compare/v15.96.0...v15.96.1) (2026-02-05)

### Bug Fixes

* stock balance report issue ([3d525ad](3d525addbe))
2026-02-05 10:53:47 +00:00
rohitwaghchaure
350282f0cc Merge pull request #52463 from frappe/mergify/bp/version-15/pr-52462
fix: stock balance report issue (backport #52462)
2026-02-05 16:20:43 +05:30
Rohit Waghchaure
3d525addbe fix: stock balance report issue
(cherry picked from commit bda7220b70)
2026-02-05 10:20:21 +00:00
rohitwaghchaure
3cf10fafdf Merge pull request #52462 from rohitwaghchaure/fixed-stock-reco-balance-value-v15
fix: stock balance report issue
2026-02-05 15:49:33 +05:30
Rohit Waghchaure
bda7220b70 fix: stock balance report issue 2026-02-05 15:00:40 +05:30
ruthra kumar
c62c30f7a3 Merge pull request #52425 from frappe/mergify/bp/version-15-hotfix/pr-51990
refactor: use https over http while saving website link (backport #51990)
2026-02-05 11:13:42 +05:30
ruthra kumar
d91cf01970 refactor: patch partner_website for old data
(cherry picked from commit 8db29b0a81)

# Conflicts:
#	erpnext/patches.txt
2026-02-05 10:58:29 +05:30
Mihir Kandoi
da0776a38c Merge pull request #52429 from frappe/mergify/bp/version-15-hotfix/pr-52427 2026-02-04 20:18:15 +05:30
archielister
9efdcf208a fix for obtaining bom_no
(cherry picked from commit e4df0a393a)
2026-02-04 14:33:05 +00:00
ruthra kumar
fb525fec80 refactor: scrub http and use https in sales partner
(cherry picked from commit 8cf31548f2)
2026-02-04 12:32:22 +00:00
Mihir Kandoi
f31bb6ad4a Merge pull request #52420 from frappe/mergify/bp/version-15-hotfix/pr-51773
fix(manufacturing): refactor production analytics report (backport #51773)
2026-02-04 17:24:13 +05:30
Sudharsanan11
99f3a7e4cf fix(manufacturing): fix chart period keys
(cherry picked from commit 27091e5168)
2026-02-04 11:28:05 +00:00
Sudharsanan11
f965b352c8 fix(manufacturing): handle None value for actual_end_date
(cherry picked from commit 16f09141da)
2026-02-04 11:28:05 +00:00
elshafei-developer
4e910d8a69 fix(gross profit report): translate column Sales Invoice
(cherry picked from commit 3e39d13172)
2026-02-04 09:18:13 +00:00
Mihir Kandoi
d93ba985e4 Merge pull request #52407 from frappe/mergify/bp/version-15-hotfix/pr-52383
fix: rate comparison in stock reco (backport #52383)
2026-02-04 12:32:56 +05:30
Mihir Kandoi
195f020636 fix: return None instead of 0 if valuation rate is falsy
(cherry picked from commit e8d1e9d946)
2026-02-04 06:48:17 +00:00
Mihir Kandoi
cacca812ed fix: rate comparison in stock reco
(cherry picked from commit f1b4fe12a2)
2026-02-04 06:48:16 +00:00
ruthra kumar
4347efdf2c Merge pull request #52387 from frappe/mergify/bp/version-15-hotfix/pr-51422
fix: merge taxes in purchase receipt when get items from multiple purchase invoices (backport #51422)
2026-02-04 09:52:23 +05:30
NaviN
68338abe07 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)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
2026-02-04 07:19:28 +05:30
Frappe PR Bot
6ba8725940 chore(release): Bumped to Version 15.96.0
# [15.96.0](https://github.com/frappe/erpnext/compare/v15.95.2...v15.96.0) (2026-02-03)

### Bug Fixes

* add docstatus condition to get_sales_invoice_item function ([#51517](https://github.com/frappe/erpnext/issues/51517)) ([afc4c85](afc4c856f8))
* add missing param ([a61ad15](a61ad15998))
* add precision to rejected batch no qty calculation ([d5570f8](d5570f83d2))
* backport Switzerland VAT rates update to version-15 ([#52244](https://github.com/frappe/erpnext/issues/52244)) ([f5481dc](f5481dc7d5))
* **bank_account:** `is_company_account` related validations (backport [#51887](https://github.com/frappe/erpnext/issues/51887)) ([#51921](https://github.com/frappe/erpnext/issues/51921)) ([7226066](7226066772))
* **barcode:** failing request when item has both batch and serial ([19e0d75](19e0d75c22))
* correct exchange gain loss in ppr ([e42f8ff](e42f8ffd5d))
* duplicate account number (Indonesia COA) (backport [#52080](https://github.com/frappe/erpnext/issues/52080)) ([#52316](https://github.com/frappe/erpnext/issues/52316)) ([ac1f29d](ac1f29d5cb))
* hide close button on WO if WO is completed ([bd96868](bd96868736))
* imports ([528a482](528a482240))
* include credit notes in project gross margin calculation ([d9d48da](d9d48da505))
* journal auditing voucher print date to use posting_date ([6413ce4](6413ce467f))
* **mode of payment:** use valid syntax (backport [#51542](https://github.com/frappe/erpnext/issues/51542)) ([#52134](https://github.com/frappe/erpnext/issues/52134)) ([22869b6](22869b6f9d))
* negative stock for purchase return ([c30d76a](c30d76ae68))
* populate contact fields when creating quotation from customer ([78f8922](78f8922a9c))
* production plan not considering planning datetime when creating WO ([7b6c7c3](7b6c7c3e27))
* **profit and loss statement:** exclude non period columns ([32c5861](32c5861919))
* remove unneccessary check ([6a68155](6a681557a9))
* reset incoming rate in selling controller if there are changes in item ([c6937c8](c6937c8375))
* revert to old orm ([7e01ae9](7e01ae9e4a))
* **RFQ:** render email templates for preview and sending ([07c5622](07c56221a5))
* **stock:** add stock recon opening stock condition ([0cbb7f8](0cbb7f8714))
* **stock:** fetch batch wise valuation rate in get_items ([f1ba825](f1ba825818))
* **stock:** ignore packing slip while cancelling the sales invoice ([e6083a5](e6083a57de))
* **stock:** include subcontracting order qty while calculating the bin qty ([ba17fdd](ba17fdd072))
* **stock:** remove is_return condition on pos batch qty calculation ([a638dec](a638dece6b))
* **stock:** set incoming_rate with lcv rate for internal purchase ([41c592a](41c592a1a8))
* **subcontracting:** include item bom in supplied items grouping key ([3b12d60](3b12d60877))
* test cases ([2c74491](2c74491eb6))
* validate over ordering of quotation ([0e60750](0e60750bd8))
* validation when more than one FG items in repack stock entry ([fec3a8b](fec3a8b511))
* zero valuation rate if returning from different warehouse ([28929df](28929df0e8))

### Features

* **delivery-note:** add status indicator when document is partially billed ([e5e3b8a](e5e3b8a6ae))
* filter to display trial balance report without group account (backport [#48486](https://github.com/frappe/erpnext/issues/48486)) ([#52146](https://github.com/frappe/erpnext/issues/52146)) ([f48b4cd](f48b4cda50))
2026-02-03 17:18:56 +00:00
rohitwaghchaure
d0f96c48cf Merge pull request #52348 from frappe/version-15-hotfix
chore: release v15
2026-02-03 22:47:29 +05:30
ruthra kumar
12be1dca7d Merge pull request #52378 from frappe/mergify/bp/version-15-hotfix/pr-51651
fix: correct exchange gain loss in ppr (backport #51651)
2026-02-03 20:59:56 +05:30
Mihir Kandoi
2d2bbe5fc2 Merge pull request #52384 from frappe/mergify/bp/version-15-hotfix/pr-52259
fix(stock): include subcontracting order qty while calculating the bin qty (backport #52259)
2026-02-03 20:48:53 +05:30
Mihir Kandoi
a0da47d7f4 Merge pull request #52381 from frappe/mergify/bp/version-15-hotfix/pr-52374
fix(stock): fetch batch wise valuation rate in get_items (backport #52374)
2026-02-03 20:32:15 +05:30
Mihir Kandoi
c0116bcde5 chore: resolve conflicts 2026-02-03 20:30:40 +05:30
Sudharsanan11
ba17fdd072 fix(stock): include subcontracting order qty while calculating the bin qty
(cherry picked from commit de8f8ef9f4)

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
2026-02-03 14:55:08 +00:00
kavin-114
f1ba825818 fix(stock): fetch batch wise valuation rate in get_items
(cherry picked from commit c5df570262)
2026-02-03 14:47:36 +00:00
ravibharathi656
e42f8ffd5d fix: correct exchange gain loss in ppr
(cherry picked from commit 02e96039ac)
2026-02-03 14:35:42 +00:00
rohitwaghchaure
c9955ddb35 Merge pull request #52369 from rohitwaghchaure/fixed-zero-incoming-rate-58744
fix: zero valuation rate if returning from different warehouse
2026-02-03 19:10:37 +05:30
Rohit Waghchaure
28929df0e8 fix: zero valuation rate if returning from different warehouse 2026-02-03 18:54:36 +05:30
ruthra kumar
6027c25c48 Merge pull request #52366 from frappe/mergify/bp/version-15-hotfix/pr-52279
fix(profit and loss statement): exclude non period columns (backport #52279)
2026-02-03 17:47:03 +05:30
ravibharathi656
32c5861919 fix(profit and loss statement): exclude non period columns
(cherry picked from commit 6180e5eb53)
2026-02-03 11:59:45 +00:00
ruthra kumar
e7297f2fc0 Merge pull request #52364 from frappe/mergify/bp/version-15-hotfix/pr-52160
fix(stock): remove is_return condition on pos batch qty calculation (backport #52160)
2026-02-03 17:20:04 +05:30
ruthra kumar
d14d09c286 Merge pull request #52361 from frappe/mergify/bp/version-15-hotfix/pr-51997
Add partially billed status indicator (backport #51997)
2026-02-03 17:19:21 +05:30
kavin-114
5b5d0f56de test: add unit test case for pos reserved with return qty
(cherry picked from commit 12ec997027)
2026-02-03 11:11:33 +00:00
kavin-114
a638dece6b fix(stock): remove is_return condition on pos batch qty calculation
(cherry picked from commit 2c19c1fd06)
2026-02-03 11:11:32 +00:00
rohitwaghchaure
d479930bef Merge pull request #52335 from aerele/v15/pr-52281
fix(stock): add stock recon opening stock condition
2026-02-03 16:40:22 +05:30
rohitwaghchaure
edd2814fc4 Merge branch 'version-15' into version-15-hotfix 2026-02-03 16:36:52 +05:30
Dharanidharan2813
e5e3b8a6ae feat(delivery-note): add status indicator when document is partially billed
(cherry picked from commit 7767000ccf)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.json
2026-02-03 16:30:49 +05:30
rohitwaghchaure
a1b1011555 Merge pull request #52343 from rohitwaghchaure/fixed-negative-stock-error-for-purchase-return-v15
fix: negative stock for purchase return
2026-02-03 16:16:08 +05:30
Rohit Waghchaure
c30d76ae68 fix: negative stock for purchase return 2026-02-03 15:56:53 +05:30
ruthra kumar
2c9c5dcc85 Merge pull request #52336 from frappe/mergify/bp/version-15-hotfix/pr-52280
fix(stock): ignore packing slip while cancelling the sales invoice (backport #52280)
2026-02-03 13:59:09 +05:30
Sudharsanan11
e6083a57de fix(stock): ignore packing slip while cancelling the sales invoice
(cherry picked from commit c58887b44a)
2026-02-03 08:24:45 +00:00
kavin-114
0cbb7f8714 fix(stock): add stock recon opening stock condition 2026-02-03 13:41:21 +05:30
ruthra kumar
fa01eefd89 Merge pull request #52329 from frappe/mergify/bp/version-15-hotfix/pr-51655
fix: include credit notes in project gross margin calculation (backport #51655)
2026-02-03 12:19:41 +05:30
ravibharathi656
d9d48da505 fix: include credit notes in project gross margin calculation
(cherry picked from commit a378fee8e0)
2026-02-03 06:07:34 +00:00
Mihir Kandoi
e1c71e0b8f Merge pull request #52310 from frappe/mergify/bp/version-15-hotfix/pr-52246
fix: validation considers moving average by default instead of set va… (backport #52246)
2026-02-03 09:44:50 +05:30
Mihir Kandoi
a61ad15998 fix: add missing param 2026-02-03 09:29:46 +05:30
Mihir Kandoi
c114f8445b Merge pull request #52323 from frappe/mergify/bp/version-15-hotfix/pr-52184
fix(subcontracting): include item bom in supplied items grouping key (backport #52184)
2026-02-03 09:27:55 +05:30
Mihir Kandoi
c6127575f5 chore: resolve conflicts 2026-02-03 09:12:14 +05:30
Mihir Kandoi
12a2e98751 chore: resolve conflicts 2026-02-03 09:10:41 +05:30
Sudharsanan11
1d7ba16caf test(subcontracting): add test for consumed_qty calculation with similar finished goods
(cherry picked from commit 4d9412181c)
2026-02-03 03:39:49 +00:00
Sudharsanan11
3b12d60877 fix(subcontracting): include item bom in supplied items grouping key
(cherry picked from commit 0d372a62a1)

# Conflicts:
#	erpnext/controllers/subcontracting_controller.py
2026-02-03 03:39:49 +00:00
mergify[bot]
ac1f29d5cb fix: duplicate account number (Indonesia COA) (backport #52080) (#52316)
Co-authored-by: Apriliansyah Idris <apriliansyahidris@gmail.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
fix: duplicate account number (Indonesia COA) (#52080)
2026-02-02 19:37:52 +00:00
Solede
f5481dc7d5 fix: backport Switzerland VAT rates update to version-15 (#52244)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 00:58:42 +05:30
Mihir Kandoi
5f60c0e85e Merge pull request #52246 from mihir-kandoi/st58765
(cherry picked from commit 135a433018)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/test_delivery_note.py
2026-02-02 15:15:17 +00:00
Mihir Kandoi
7b059a9221 Merge pull request #52307 from frappe/mergify/bp/version-15-hotfix/pr-52304
fix: populate contact fields when creating quotation from customer (backport #52304)
2026-02-02 20:32:16 +05:30
Mihir Kandoi
78f8922a9c fix: populate contact fields when creating quotation from customer
(cherry picked from commit 75b2c2c83d)
2026-02-02 14:46:21 +00:00
Mihir Kandoi
09c56abeb0 Merge pull request #52301 from frappe/mergify/bp/version-15-hotfix/pr-52286
fix: reset incoming rate in selling controller if there are changes i… (backport #52286)
2026-02-02 20:03:13 +05:30
Mihir Kandoi
c6937c8375 fix: reset incoming rate in selling controller if there are changes in item
(cherry picked from commit 2d6b43fd54)
2026-02-02 14:17:43 +00:00
rohitwaghchaure
99ad34d686 Merge pull request #52239 from frappe/mergify/bp/version-15-hotfix/pr-52232
fix: validation when more than one FG items in repack stock entry (backport #52232)
2026-02-02 14:10:48 +05:30
ruthra kumar
09ab2653d1 Merge pull request #52283 from frappe/mergify/bp/version-15-hotfix/pr-52200
fix(accounts): correct date in Journal Auditing Voucher print format (backport #52200)
2026-02-02 12:54:47 +05:30
Tamal Majumdar
6413ce467f fix: journal auditing voucher print date to use posting_date
(cherry picked from commit 43e2495df8)
2026-02-02 07:21:21 +00:00
Mihir Kandoi
a5156f696c Merge pull request #52275 from frappe/mergify/bp/version-15-hotfix/pr-52274 2026-02-02 11:06:19 +05:30
Mihir Kandoi
528a482240 fix: imports 2026-02-02 10:51:27 +05:30
Mihir Kandoi
d9d4b9b117 test: over ordering of quotation items
(cherry picked from commit 53e58f6678)
2026-02-02 10:51:27 +05:30
Mihir Kandoi
a4ad4e8279 Merge pull request #52277 from frappe/mihir-kandoi-patch-1 2026-02-02 10:49:30 +05:30
Mihir Kandoi
d516110572 chore: fix py error on v15 2026-02-02 10:34:23 +05:30
Rohit Waghchaure
6d14cb0c6b chore: fix conflicts 2026-02-01 16:30:59 +05:30
Mihir Kandoi
d30dacce80 Merge pull request #52229 from frappe/mergify/bp/version-15-hotfix/pr-52222 2026-02-01 13:46:46 +05:30
Mihir Kandoi
7e01ae9e4a fix: revert to old orm 2026-02-01 10:26:14 +05:30
Mihir Kandoi
42f94f1aba chore: remove incorrect import 2026-01-31 20:36:46 +05:30
Mihir Kandoi
6a681557a9 fix: remove unneccessary check 2026-01-31 20:27:02 +05:30
Mihir Kandoi
7ab59aa094 chore: resolve conflicts 2026-01-31 20:25:00 +05:30
Mihir Kandoi
c3b92075f0 chore: resolve conflicts
Removed old patch entries and updated the list.
2026-01-31 20:20:36 +05:30
Mihir Kandoi
4017936bef chore: resolve conflicts 2026-01-31 20:19:07 +05:30
Mihir Kandoi
6cecae288c chore: resolve conflicts 2026-01-31 20:18:22 +05:30
Rohit Waghchaure
fec3a8b511 fix: validation when more than one FG items in repack stock entry
(cherry picked from commit 6423ce2fa7)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2026-01-31 07:16:29 +00:00
Mihir Kandoi
2c74491eb6 fix: test cases
(cherry picked from commit 36f1e3572c)
2026-01-30 14:00:29 +00:00
Mihir Kandoi
0e60750bd8 fix: validate over ordering of quotation
(cherry picked from commit 4cc306d2d8)

# Conflicts:
#	erpnext/controllers/status_updater.py
#	erpnext/patches.txt
#	erpnext/selling/doctype/quotation/quotation.py
#	erpnext/selling/doctype/quotation_item/quotation_item.json
2026-01-30 14:00:28 +00:00
Mihir Kandoi
4dfc5671b0 Merge pull request #52217 from frappe/mergify/bp/version-15-hotfix/pr-52209
fix: add precision to rejected batch no qty calculation (backport #52209)
2026-01-30 12:21:00 +05:30
Mihir Kandoi
003eb02e24 Merge pull request #52214 from frappe/mergify/bp/version-15-hotfix/pr-52213
fix: hide close button on WO if WO is completed (backport #52213)
2026-01-30 12:06:47 +05:30
Mihir Kandoi
d5570f83d2 fix: add precision to rejected batch no qty calculation
(cherry picked from commit 838d245215)
2026-01-30 06:35:43 +00:00
Mihir Kandoi
bd96868736 fix: hide close button on WO if WO is completed
(cherry picked from commit 6e17ccf499)
2026-01-30 06:29:12 +00:00
Mihir Kandoi
85f7196eb5 Merge pull request #52211 from frappe/mergify/bp/version-15-hotfix/pr-52210
fix(barcode): failing request when item has both batch and serial (backport #52210)
2026-01-30 11:52:42 +05:30
Mihir Kandoi
19e0d75c22 fix(barcode): failing request when item has both batch and serial
(cherry picked from commit 89f6f0f46f)
2026-01-30 06:17:25 +00:00
Frappe PR Bot
621558a30c chore(release): Bumped to Version 15.95.2
## [15.95.2](https://github.com/frappe/erpnext/compare/v15.95.1...v15.95.2) (2026-01-29)

### Bug Fixes

* **stock:** set incoming_rate with lcv rate for internal purchase ([6ea4f1a](6ea4f1a03d))
2026-01-29 12:50:23 +00:00
rohitwaghchaure
547fbec55f Merge pull request #52176 from frappe/mergify/bp/version-15/pr-52140
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch (backport #52007) (backport #52140)
2026-01-29 18:17:43 +05:30
rohitwaghchaure
d9bd42965a Merge pull request #52195 from frappe/mergify/bp/version-15/pr-52191
Add Landed Cost Voucher Amount in Internal Purchase Receipt (backport #52158) (backport #52191)
2026-01-29 18:17:32 +05:30
kavin-114
419df361a7 test: add unit test to check internal purchase with lcv
(cherry picked from commit dd4fd89ef8)
(cherry picked from commit 3ccd1b4a6c)
2026-01-29 12:26:35 +00:00
kavin-114
6ea4f1a03d fix(stock): set incoming_rate with lcv rate for internal purchase
(cherry picked from commit f0dccc3cd7)
(cherry picked from commit 41c592a1a8)
2026-01-29 12:26:34 +00:00
rohitwaghchaure
3570ab8868 Merge pull request #52191 from frappe/mergify/bp/version-15-hotfix/pr-52158
Add Landed Cost Voucher Amount in Internal Purchase Receipt (backport #52158)
2026-01-29 17:56:11 +05:30
kavin-114
3ccd1b4a6c test: add unit test to check internal purchase with lcv
(cherry picked from commit dd4fd89ef8)
2026-01-29 12:02:34 +00:00
kavin-114
41c592a1a8 fix(stock): set incoming_rate with lcv rate for internal purchase
(cherry picked from commit f0dccc3cd7)
2026-01-29 12:02:34 +00:00
mergify[bot]
65ed4e5cf6 Merge pull request #52140 from frappe/mergify/bp/version-15-hotfix/pr-52007
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch (backport #52007)
(cherry picked from commit ad8c8cb0e8)
2026-01-29 09:01:32 +00:00
mergify[bot]
ad8c8cb0e8 Merge pull request #52140 from frappe/mergify/bp/version-15-hotfix/pr-52007
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch (backport #52007)
2026-01-29 14:30:22 +05:30
Mihir Kandoi
7417432ddb Merge pull request #52167 from frappe/mergify/bp/version-15-hotfix/pr-52166
fix: production plan not considering planning datetime when creating WO (backport #52166)
2026-01-29 11:05:39 +05:30
Mihir Kandoi
7b6c7c3e27 fix: production plan not considering planning datetime when creating WO
(cherry picked from commit 4e19c7e8bd)
2026-01-29 05:20:41 +00:00
Aarol D'Souza
df857f8177 Merge pull request #52163 from frappe/mergify/bp/version-15-hotfix/pr-52092
fix(RFQ): render email templates for preview and sending (backport #52092)
2026-01-29 09:22:32 +05:30
AarDG10
43fc1ae4bf ci: minor text correction
(cherry picked from commit 37cdae2f34)
2026-01-29 03:37:44 +00:00
AarDG10
07c56221a5 fix(RFQ): render email templates for preview and sending
(cherry picked from commit 525b3960e1)
2026-01-29 03:37:44 +00:00
Navin-S-R
273029d0f0 fix: recalculate tax withholding during Purchase Order child update 2026-01-28 18:51:49 +05:30
mergify[bot]
f48b4cda50 feat: filter to display trial balance report without group account (backport #48486) (#52146)
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2026-01-28 11:40:18 +00:00
mergify[bot]
7226066772 fix(bank_account): is_company_account related validations (backport #51887) (#51921)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-01-28 10:22:09 +00:00
aymenit2008
afc4c856f8 fix: add docstatus condition to get_sales_invoice_item function (#51517) 2026-01-28 15:43:33 +05:30
mergify[bot]
22869b6f9d fix(mode of payment): use valid syntax (backport #51542) (#52134)
Co-authored-by: ervishnucs <ervishnucs369@gmail.com>
2026-01-28 15:10:31 +05:30
Frappe PR Bot
b268de4609 chore(release): Bumped to Version 15.95.1
## [15.95.1](https://github.com/frappe/erpnext/compare/v15.95.0...v15.95.1) (2026-01-28)

### Bug Fixes

* allow creation of DN in SI for items not having DN reference ([184fa88](184fa889c3))
* **asset capitalization:** update asset values using db_set ([74bf61e](74bf61e0c1))
* autofill warehouse for packed items ([0a87fa5](0a87fa5348))
* Bin reserved qty for production for extra material transfer ([b5d8477](b5d8477354))
* check the payment ledger entry has the dimension ([#51823](https://github.com/frappe/erpnext/issues/51823)) ([468ec80](468ec805f1))
* Ensure paid_amount is always numeric before calling allocate_amount_to_references (backport [#50935](https://github.com/frappe/erpnext/issues/50935)) ([#52035](https://github.com/frappe/erpnext/issues/52035)) ([9fce694](9fce694936))
* handle parent level project change ([7146c03](7146c0385c))
* handle undefined bank_transaction_mapping in quick entry ([d4195d3](d4195d31bf))
* job cards should not be deleted on close of WO ([8d06ee3](8d06ee3966))
* **journal-entry:** prevent submit failure due to double background queuing (backport [#52083](https://github.com/frappe/erpnext/issues/52083)) ([#52086](https://github.com/frappe/erpnext/issues/52086)) ([72a9b58](72a9b58b14))
* negative stock for purchae return ([f9fd0ff](f9fd0ffbae))
* **payment entry:** update currency symbol (backport [#51956](https://github.com/frappe/erpnext/issues/51956)) ([#52093](https://github.com/frappe/erpnext/issues/52093)) ([934b549](934b5494f0))
* **project:** add missing counter to project update naming series ([f61305a](f61305aa45))
* rejected qty in PR doesn't consider conversion factor ([83352b5](83352b5a34))
* **sales order:** set project at item level from parent ([a09b73e](a09b73e65d))
* **shipment:** user contact validation to use full name ([90dc22a](90dc22a57d))
* show message if image is removed from item description ([0c89cd5](0c89cd5524))
* **stock:** use purchase UOM in Supplier Quotation items ([dadd4b1](dadd4b1f95))
* strip whitespace in customer_name ([853faca](853facad96))
* swedish_address_template ([5e61922](5e6192249e))
* UOM of item not fetching in BOM ([14de520](14de520ebb))
* update country_wise_tax.json for Algerian Taxes (backport [#51878](https://github.com/frappe/erpnext/issues/51878)) ([#52037](https://github.com/frappe/erpnext/issues/52037)) ([d89ac99](d89ac99e76))
* validation to check at-least one raw material for manufacture entry ([650f874](650f874fbd))
2026-01-28 04:14:22 +00:00
ruthra kumar
44b726c2e3 Merge pull request #52104 from frappe/version-15-hotfix
chore: release v15
2026-01-28 09:43:01 +05:30
Mihir Kandoi
0c395725b7 Merge pull request #52123 from frappe/mergify/bp/version-15-hotfix/pr-51961
fix(sales order): set project at item level from parent (backport #51961)
2026-01-27 21:55:31 +05:30
SowmyaArunachalam
7146c0385c fix: handle parent level project change
(cherry picked from commit 543b6e51c0)
2026-01-27 16:24:06 +00:00
SowmyaArunachalam
e12564daa6 chore: use frappe.model.set_value
(cherry picked from commit 3b27f49d79)
2026-01-27 16:24:06 +00:00
SowmyaArunachalam
a09b73e65d fix(sales order): set project at item level from parent
(cherry picked from commit 9e51701e2a)
2026-01-27 16:24:05 +00:00
Mihir Kandoi
654a55260d Merge pull request #52121 from frappe/mergify/bp/version-15-hotfix/pr-52084
fix(shipment): user contact validation to use full name (backport #52084)
2026-01-27 21:28:34 +05:30
harrishragavan
90dc22a57d fix(shipment): user contact validation to use full name
(cherry picked from commit 3c6eb9a531)
2026-01-27 15:57:05 +00:00
Khushi Rawat
e826e03f9a Merge pull request #52073 from aerele/update-asset-purchase-amt
fix(asset capitalization): update asset values using db_set
2026-01-27 17:06:17 +05:30
ruthra kumar
de4e62e308 Merge pull request #52107 from frappe/mergify/bp/version-15-hotfix/pr-51823
fix: check the payment ledger entry has the dimension (backport #51823)
2026-01-27 16:27:32 +05:30
Vishnu Priya Baskaran
468ec805f1 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:26:52 +00:00
Mihir Kandoi
cd8c6eac7c Merge pull request #52096 from frappe/mergify/bp/version-15-hotfix/pr-52088
fix: show message if image is removed from item description (backport #52088)
2026-01-27 14:56:03 +05:30
Mihir Kandoi
90d6bb34dc chore: resolve conflicts 2026-01-27 14:38:19 +05:30
Mihir Kandoi
1545904693 Merge pull request #52099 from aerele/support/fix--58134 2026-01-27 12:50:11 +05:30
Pandiyan37
dadd4b1f95 fix(stock): use purchase UOM in Supplier Quotation items 2026-01-27 12:28:07 +05:30
Mihir Kandoi
0c89cd5524 fix: show message if image is removed from item description
(cherry picked from commit b49c679a50)

# Conflicts:
#	erpnext/stock/doctype/item/item.py
2026-01-27 06:50:16 +00:00
mergify[bot]
934b5494f0 fix(payment entry): update currency symbol (backport #51956) (#52093)
Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com>
fix(payment entry): update currency symbol (#51956)
2026-01-27 06:32:59 +00:00
mergify[bot]
72a9b58b14 fix(journal-entry): prevent submit failure due to double background queuing (backport #52083) (#52086)
Co-authored-by: V Shankar <shankarv292002@gmail.com>
fix(journal-entry): prevent submit failure due to double background queuing (#52083)
2026-01-27 05:52:23 +00:00
Navin-S-R
5cfd8d1930 refactor: avoid multiple db_set 2026-01-26 23:06:37 +05:30
Navin-S-R
74bf61e0c1 fix(asset capitalization): update asset values using db_set 2026-01-26 21:17:06 +05:30
Mihir Kandoi
c4b135e1a2 Merge pull request #52065 from frappe/mergify/bp/version-15-hotfix/pr-52064
fix: strip whitespace in customer_name (backport #52064)
2026-01-26 15:30:50 +05:30
Shankarv19bcr
853facad96 fix: strip whitespace in customer_name
(cherry picked from commit e5ba0e6401)
2026-01-26 09:46:51 +00:00
ruthra kumar
636e1ac1f1 Merge pull request #52039 from frappe/mergify/bp/version-15-hotfix/pr-51670
fix: handle undefined bank_transaction_mapping in quick entry (backport #51670)
2026-01-25 13:11:45 +05:30
ruthra kumar
df996b8fd3 Merge pull request #52054 from frappe/mergify/bp/version-15-hotfix/pr-52050
fix: swedish_address_template (backport #52050)
2026-01-25 13:09:18 +05:30
mahsem
5e6192249e fix: swedish_address_template
(cherry picked from commit 334e8ada30)
2026-01-25 05:22:25 +00:00
rohitwaghchaure
398e8d00ec Merge pull request #52052 from frappe/mergify/bp/version-15-hotfix/pr-52043
fix: UOM of item not fetching in BOM (backport #52043)
2026-01-25 10:50:52 +05:30
rohitwaghchaure
6be30bbd71 Merge pull request #51904 from frappe/mergify/bp/version-15-hotfix/pr-51900
fix: validation to check at-least one raw material for manufacture entry (backport #51900)
2026-01-25 10:45:52 +05:30
Rohit Waghchaure
14de520ebb fix: UOM of item not fetching in BOM
(cherry picked from commit ba8eadda52)
2026-01-25 05:14:50 +00:00
rohitwaghchaure
770d0e7f7f Merge pull request #52030 from frappe/mergify/bp/version-15-hotfix/pr-52024
fix: Bin reserved qty for production for extra material transfer (backport #52024)
2026-01-25 10:43:48 +05:30
rohitwaghchaure
c351d6b1c0 chore: fix conflicts
Removed old implementation of make_serialized_item function and updated its definition.
2026-01-24 13:51:54 +05:30
rohitwaghchaure
a4b099e481 chore: fix conflicts
Removed subcontracting order validation methods from stock entry.
2026-01-24 13:50:33 +05:30
rohitwaghchaure
624ec19305 chore: fix conflicts
Remove test for reserved serial and batch items and clean up related code.
2026-01-24 13:42:04 +05:30
Abdeali Chharchhoda
e1c3125efa refactor: use console.error for error logging in Plaid integration
(cherry picked from commit 9322095786)
2026-01-24 07:07:32 +00:00
Abdeali Chharchhoda
d4195d31bf fix: handle undefined bank_transaction_mapping in quick entry
(cherry picked from commit 8a1b8259bd)
2026-01-24 07:07:32 +00:00
Abdeali Chharchhoda
f349be0a00 refactor: remove redundant onload function for bank mapping table
(cherry picked from commit 7c7ba0154a)
2026-01-24 07:07:31 +00:00
mergify[bot]
d89ac99e76 fix: update country_wise_tax.json for Algerian Taxes (backport #51878) (#52037)
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:04 +00:00
mergify[bot]
9fce694936 fix: Ensure paid_amount is always numeric before calling allocate_amount_to_references (backport #50935) (#52035)
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:51 +05:30
Rohit Waghchaure
b5d8477354 fix: Bin reserved qty for production for extra material transfer
(cherry picked from commit f5378b6573)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
2026-01-23 15:45:30 +00:00
rohitwaghchaure
0e3d276348 Merge pull request #52014 from frappe/mergify/bp/version-15-hotfix/pr-52006
fix: negative stock for purchase return (backport #52006)
2026-01-23 13:23:00 +05:30
rohitwaghchaure
3489b65f1a chore: fix conflicts 2026-01-23 12:27:37 +05:30
rohitwaghchaure
c8a52ec43c chore: fix conflicts
Removed deprecated method for batch-wise total available quantity and adjusted stock value calculations.
2026-01-23 11:51:02 +05:30
Rohit Waghchaure
f9fd0ffbae fix: negative stock for purchae return
(cherry picked from commit d68a04ad16)

# Conflicts:
#	erpnext/stock/serial_batch_bundle.py
2026-01-23 06:03:47 +00:00
rohitwaghchaure
69dc9e81d5 Merge pull request #52004 from frappe/mergify/bp/version-15-hotfix/pr-51989
fix: autofill warehouse for packed items (backport #51989)
2026-01-22 23:56:40 +05:30
Sudharsanan11
0a87fa5348 fix: autofill warehouse for packed items
(cherry picked from commit 3f8a0a4833)
2026-01-22 17:28:03 +00:00
Mihir Kandoi
81e7e96cb6 Merge pull request #51977 from frappe/mergify/bp/version-15-hotfix/pr-51967
fix(project): add missing counter to project update naming series (backport #51967)
2026-01-22 11:45:58 +05:30
mergify[bot]
f7770c3225 Merge pull request #51979 from frappe/mergify/bp/version-15-hotfix/pr-51966
fix(customer): add customer group filters (backport #51966)
2026-01-22 05:16:45 +00:00
ravibharathi656
f61305aa45 fix(project): add missing counter to project update naming series
(cherry picked from commit 49e64f4e1c)
2026-01-22 04:52:56 +00:00
Mihir Kandoi
113a6e079a Merge pull request #51971 from frappe/mergify/bp/version-15-hotfix/pr-51968 2026-01-22 09:04:48 +05:30
mergify[bot]
c35426b9f9 Merge pull request #51969 from frappe/mergify/bp/version-15-hotfix/pr-51964
fix: create DN btn should not be shown if it cannot be created (backport #51964)
2026-01-21 17:27:37 +00:00
Mihir Kandoi
83352b5a34 fix: rejected qty in PR doesn't consider conversion factor
(cherry picked from commit 343ee9695b)
2026-01-21 17:20:45 +00:00
Mihir Kandoi
e54bb0da69 Merge pull request #51959 from frappe/mergify/bp/version-15-hotfix/pr-51947
fix: job cards should not be deleted on close of WO (backport #51947)
2026-01-21 16:02:01 +05:30
Mihir Kandoi
8d06ee3966 fix: job cards should not be deleted on close of WO
(cherry picked from commit c919b1de38)
2026-01-21 10:17:00 +00:00
Mihir Kandoi
6b4101d202 Merge pull request #51925 from frappe/mergify/bp/version-15-hotfix/pr-51909
fix: allow creation of DN in SI for items not having DN reference (backport #51909)
2026-01-21 15:41:21 +05:30
Mihir Kandoi
386567a6ea chore: resolve conflicts 2026-01-21 15:27:12 +05:30
Mihir Kandoi
d3440cf545 chore: resolve conflicts 2026-01-21 15:24:14 +05:30
mergify[bot]
11544818f1 Merge pull request #51950 from frappe/mergify/bp/version-15-hotfix/pr-51948
fix: warehouse permissions in MR incorrectly ignored (backport #51948)
2026-01-21 08:51:41 +00:00
Frappe PR Bot
1e16e751ee chore(release): Bumped to Version 15.95.0
# [15.95.0](https://github.com/frappe/erpnext/compare/v15.94.3...v15.95.0) (2026-01-20)

### Bug Fixes

* **accounts_controller:** make return message translatable ([8f6095d](8f6095d05f))
* **accounts:** add missing accounting dimensions in advance taxes and charges ([1d5f406](1d5f406930))
* add other charges in total ([3ef4fa5](3ef4fa51dc))
* allow disassemble stock entry without work order (backport [#51761](https://github.com/frappe/erpnext/issues/51761)) ([#51835](https://github.com/frappe/erpnext/issues/51835)) ([be20698](be2069883e))
* calculate net profit amount from root node accounts ([e9573b0](e9573b0b93))
* common_party_path ([#51826](https://github.com/frappe/erpnext/issues/51826)) ([6225217](62252170dd))
* docs_path ([b3df300](b3df300ea5))
* **manufacturing:** consider process loss qty while validating the work order ([4418fb4](4418fb48a9))
* **pos:** reapply set warehouse during cart update ([75b4a0a](75b4a0a89c))
* **postgres:** compute current month sales without DATE_FORMAT ([fbf4305](fbf4305028))
* **postgres:** fix v15 migration failures on Postgres ([#51481](https://github.com/frappe/erpnext/issues/51481)) ([eef26fe](eef26fea9a))
* prevent UOM from updating incorrectly while scanning barcode ([d196956](d196956307))
* **process statement of accounts:** allow renaming ([8b2778b](8b2778b29f))
* **process statement of accounts:** naming of reports ([054468a](054468a5ef))
* RFQ does not fetch html response ([90e8090](90e8090dcc))
* **sales analytics:** add curve filter ([c2995f6](c2995f6800))
* Show non-SLE vouchers with GL entries in Stock vs Account Value Comparison report ([6219d7d](6219d7d9a5))
* **stock entry:** calculate transferred quantity using transfer_qty (backport [#51656](https://github.com/frappe/erpnext/issues/51656)) ([#51675](https://github.com/frappe/erpnext/issues/51675)) ([1da781f](1da781f2ae))
* **stock:** resolve quantity issue when adding items via barcode scan ([c508ef5](c508ef5b82))
* **transaction.js:** use flt instead of cint for plc_conversion_rate ([f618bf2](f618bf212f))
* valuation rate for non batchwise valuation ([3008c7a](3008c7ad82))

### Features

* add new 2025 Charts of Accounts for France ([6af6fe8](6af6fe8204))
* **process statement of accounts:** added more frequency options for auto email ([546ab05](546ab05eb5))
* remove old French chart of accounts with code as nex 2025 is provided ([e568ab2](e568ab2255))

### Performance Improvements

* prevent duplicate reposting for the same item ([eff9595](eff9595e34))
2026-01-20 16:40:54 +00:00
ruthra kumar
cff3407a4b Merge pull request #51912 from frappe/version-15-hotfix
chore: release v15
2026-01-20 22:09:26 +05:30
mergify[bot]
502a262637 Merge pull request #51935 from frappe/mergify/bp/version-15-hotfix/pr-51934
fix: validation message in stock reco row idx (backport #51934)
2026-01-20 16:11:53 +00:00
rohitwaghchaure
8f112c5967 Merge pull request #51931 from frappe/mergify/bp/version-15-hotfix/pr-51930
Revert "perf: prevent duplicate reposting for the same item" (backport #51930)
2026-01-20 20:05:31 +05:30
rohitwaghchaure
dad7657853 Revert "perf: prevent duplicate reposting for the same item"
(cherry picked from commit 6e4b90055f)
2026-01-20 14:19:24 +00:00
rohitwaghchaure
ff84edcfad Merge pull request #51923 from frappe/mergify/bp/version-15-hotfix/pr-51920
perf: prevent duplicate reposting for the same item (backport #51920)
2026-01-20 18:01:07 +05:30
Mihir Kandoi
184fa889c3 fix: allow creation of DN in SI for items not having DN reference
(cherry picked from commit b691de0147)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2026-01-20 12:14:43 +00:00
Rohit Waghchaure
eff9595e34 perf: prevent duplicate reposting for the same item
(cherry picked from commit 7535931571)
2026-01-20 12:08:49 +00:00
ruthra kumar
8847e1c2bd Merge pull request #51915 from frappe/mergify/bp/version-15-hotfix/pr-51671
fix(accounts): add missing accounting dimensions in advance taxes and charges (backport #51671)
2026-01-20 17:21:07 +05:30
Nikhil Kothari
1d5f406930 fix(accounts): add missing accounting dimensions in advance taxes and charges
(cherry picked from commit 22e9cb4cf4)

# Conflicts:
#	erpnext/patches.txt
2026-01-20 17:03:46 +05:30
Rohit Waghchaure
650f874fbd fix: validation to check at-least one raw material for manufacture entry
(cherry picked from commit f003b3c378)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py
2026-01-20 08:25:57 +00:00
Mihir Kandoi
091272409e Merge pull request #51674 from aerele/v15-sales-analytics-curve-filter 2026-01-20 13:26:31 +05:30
ravibharathi656
c2995f6800 fix(sales analytics): add curve filter 2026-01-20 13:01:59 +05:30
ruthra kumar
39cd371fb6 Merge pull request #51892 from frappe/mergify/bp/version-15-hotfix/pr-51886
fix(accounts_controller): make return message translatable (backport #51886)
2026-01-20 08:30:46 +05:30
barredterra
8f6095d05f fix(accounts_controller): make return message translatable
(cherry picked from commit 0209f0fe29)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2026-01-20 08:17:09 +05:30
ruthra kumar
6b61eabf61 Merge pull request #51883 from frappe/mergify/bp/version-15-hotfix/pr-51830
fix(manufacturing): consider process loss qty while validating the work order (backport #51830)
2026-01-20 08:08:43 +05:30
ruthra kumar
25112468bc Merge pull request #51890 from frappe/mergify/bp/version-15-hotfix/pr-51561
fix: delete advance ledger entries  while reconciling payment entry (backport #51561)
2026-01-20 08:06:13 +05:30
Lakshit Jain
d27fe6f57a 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:23 +00:00
Diptanil Saha
e60064f6f1 Merge pull request #51885 from frappe/mergify/bp/version-15-hotfix/pr-49957
fix: process statement of accounts (backport #49957)
2026-01-19 22:19:58 +05:30
diptanilsaha
e621a51225 chore: resolve conflicts 2026-01-19 22:03:01 +05:30
diptanilsaha
054468a5ef fix(process statement of accounts): naming of reports
(cherry picked from commit 4a4c2188ec)

# Conflicts:
#	erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
#	erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html
2026-01-19 16:23:43 +00:00
diptanilsaha
546ab05eb5 feat(process statement of accounts): added more frequency options for auto email
(cherry picked from commit d610d1dccd)

# Conflicts:
#	erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
2026-01-19 16:23:42 +00:00
diptanilsaha
8b2778b29f fix(process statement of accounts): allow renaming
(cherry picked from commit dbab718aaa)

# Conflicts:
#	erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
2026-01-19 16:23:42 +00:00
Sudharsanan11
4418fb48a9 fix(manufacturing): consider process loss qty while validating the work order
(cherry picked from commit e6366e830c)
2026-01-19 16:18:35 +00:00
Diptanil Saha
cfafd39543 Merge pull request #51876 from frappe/mergify/bp/version-15-hotfix/pr-51595 2026-01-19 18:07:22 +05:30
Florian HENRY
794d005923 chore: re add older template
(cherry picked from commit b3efb3084f)
2026-01-19 12:23:31 +00:00
Florian HENRY
da19761fbd chore: fix bank account type
(cherry picked from commit 4fe1b214c1)
2026-01-19 12:23:31 +00:00
Florian HENRY
5fcda5f3ed chore: fix CASH acount type
(cherry picked from commit 6a876de838)
2026-01-19 12:23:31 +00:00
Florian HENRY
763cf6ae10 chore: fix bank acount type
(cherry picked from commit 765487a087)
2026-01-19 12:23:30 +00:00
Florian HENRY
b301be1a74 chore: add Expenses Included In Valuation account
(cherry picked from commit c519cd0268)
2026-01-19 12:23:30 +00:00
Florian HENRY
e568ab2255 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
61295e7d47 chore: Review PR #51595
(cherry picked from commit 6bdaeb983d)
2026-01-19 12:23:30 +00:00
Florian HENRY
6af6fe8204 feat: add new 2025 Charts of Accounts for France
(cherry picked from commit c81dee137f)
2026-01-19 12:23:30 +00:00
rohitwaghchaure
d0d776486e Merge pull request #51865 from frappe/mergify/bp/version-15-hotfix/pr-51769
fix(pos): reapply set warehouse during cart update (backport #51769)
2026-01-19 15:44:52 +05:30
ravibharathi656
75b4a0a89c fix(pos): reapply set warehouse during cart update
(cherry picked from commit 5a53c45321)
2026-01-19 10:07:47 +00:00
ruthra kumar
a2c86dbe01 Merge pull request #51838 from frappe/mergify/bp/version-15-hotfix/pr-51787
fix: recalculate taxes when item tax template changes after discount (backport #51787)
2026-01-19 14:36:59 +05:30
ljain112
2bf75a2c24 chore: resolve conflicts 2026-01-19 13:41:40 +05:30
ruthra kumar
2b38bc191e Merge pull request #51843 from frappe/mergify/bp/version-15-hotfix/pr-51826
fix: common_party_path (backport #51826)
2026-01-19 13:20:48 +05:30
mahsem
62252170dd 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:20 +00:00
ruthra kumar
c55512c9d3 Merge pull request #51840 from frappe/mergify/bp/version-15-hotfix/pr-51513
fix: calculate net profit amount from root node accounts (backport #51513)
2026-01-19 13:06:17 +05:30
mergify[bot]
be2069883e fix: allow disassemble stock entry without work order (backport #51761) (#51835)
* 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)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py

* chore: fix conflicts

Removed unused test functions related to stock entry and sample retention.

* chore: fix linters issue

---------

Co-authored-by: Smit Vora <smitvora203@gmail.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2026-01-19 12:55:03 +05:30
Navin-S-R
e9573b0b93 fix: calculate net profit amount from root node accounts
(cherry picked from commit c84986d00e)
2026-01-19 07:15:09 +00:00
Lakshit Jain
1d64373c26 Merge pull request #51787 from ljain112/fix-taxes-disc
fix: recalculate taxes when item tax template changes after discount
(cherry picked from commit f00aeec9b4)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2026-01-19 07:01:36 +00:00
ruthra kumar
6df80901b9 Merge pull request #51833 from frappe/mergify/bp/version-15-hotfix/pr-51742
fix: add other charges in total (backport #51742)
2026-01-19 11:34:12 +05:30
SowmyaArunachalam
3ef4fa51dc fix: add other charges in total
(cherry picked from commit 9406c07c42)
2026-01-19 05:45:16 +00:00
Mihir Kandoi
cce32507d9 Merge pull request #51820 from frappe/mergify/bp/version-15-hotfix/pr-51817
fix: prevent UOM from updating incorrectly while scanning barcode (backport #51817)
2026-01-18 15:11:05 +05:30
Pandiyan5273
d196956307 fix: prevent UOM from updating incorrectly while scanning barcode
(cherry picked from commit 30263b26a5)
2026-01-18 09:36:26 +00:00
ruthra kumar
4db62cab3b Merge pull request #51796 from frappe/mergify/bp/version-15-hotfix/pr-51555
fix(postgres): compute current month sales without DATE_FORMAT (backport #51555)
2026-01-16 17:15:41 +05:30
Matt Howard
fbf4305028 fix(postgres): compute current month sales without DATE_FORMAT
(cherry picked from commit 64f391adf7)
2026-01-16 11:28:48 +00:00
ili.ad
eef26fea9a fix(postgres): fix v15 migration failures on Postgres (#51481)
* fix(postgres): avoid DISTINCT(...) in repost allowed types query

* fix(postgres): rewrite update pick list patch to avoid UPDATE JOIN

* chore: linting changes

---------

Co-authored-by: Matt Howard <github.severity519@passmail.net>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2026-01-16 16:35:16 +05:30
Mihir Kandoi
043d208580 Merge pull request #51793 from frappe/mergify/bp/version-15-hotfix/pr-51790
fix(stock): resolve quantity issue when adding items via barcode scan (backport #51790)
2026-01-16 16:20:18 +05:30
Pandiyan5273
c508ef5b82 fix(stock): resolve quantity issue when adding items via barcode scan
(cherry picked from commit f959b2c59a)
2026-01-16 10:48:53 +00:00
mergify[bot]
1da781f2ae fix(stock entry): calculate transferred quantity using transfer_qty (backport #51656) (#51675)
* fix(stock entry): calculate transferred quantity using transfer_qty

(cherry picked from commit 4e6d86d6f0)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py

* test: allow from_warehouse while creating material request

(cherry picked from commit 7e99148357)

* test: validate transferred quantity for material transfer entry

(cherry picked from commit bf2ab32abf)

* chore: fix conflicts

---------

Co-authored-by: Navin-S-R <navin@aerele.in>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2026-01-16 10:42:01 +05:30
rohitwaghchaure
e2b53884fe Merge pull request #51771 from frappe/mergify/bp/version-15-hotfix/pr-51768
fix: Show non-SLE vouchers with GL entries in Stock vs Account Value … (backport #51768)
2026-01-15 20:59:48 +05:30
rohitwaghchaure
de46ac8b62 chore: fix conflicts 2026-01-15 19:27:46 +05:30
Rohit Waghchaure
6219d7d9a5 fix: Show non-SLE vouchers with GL entries in Stock vs Account Value Comparison report
(cherry picked from commit 1db9ce205f)

# Conflicts:
#	erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
2026-01-15 12:20:31 +00:00
rohitwaghchaure
eb0249310c Merge pull request #51751 from frappe/mergify/bp/version-15-hotfix/pr-51729
fix: valuation rate for non batchwise valuation (backport #51729)
2026-01-15 17:01:48 +05:30
Mihir Kandoi
dfb1722dc4 Merge pull request #51766 from aerele/rfq-email-refactor 2026-01-15 16:38:15 +05:30
Sudharsanan Ashok
c13f3ba695 Merge branch 'version-15-hotfix' into rfq-email-refactor 2026-01-15 15:36:20 +05:30
Mihir Kandoi
add635b9eb refactor: backport RFQ email refactor (#51503) 2026-01-15 15:33:03 +05:30
Mihir Kandoi
28a670434d Merge pull request #51763 from frappe/mergify/bp/version-15-hotfix/pr-51364
fix: RFQ does not fetch html response (backport #51364)
2026-01-15 12:12:59 +05:30
Mihir Kandoi
90e8090dcc fix: RFQ does not fetch html response
(cherry picked from commit da899913b8)
2026-01-15 06:17:42 +00:00
Mihir Kandoi
d983280de8 Merge pull request #51754 from frappe/mergify/bp/version-15-hotfix/pr-51753
fix: docs_path (backport #51753)
2026-01-14 21:30:54 +05:30
mahsem
b3df300ea5 fix: docs_path
(cherry picked from commit 7ef8c81caf)
2026-01-14 15:59:59 +00:00
rohitwaghchaure
0a363f879d chore: fix conflicts
Removed multiple test cases related to purchase receipts and negative stock handling.
2026-01-14 19:43:39 +05:30
Rohit Waghchaure
3008c7ad82 fix: valuation rate for non batchwise valuation
(cherry picked from commit b6312bca9c)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
2026-01-14 14:06:35 +00:00
Frappe PR Bot
d82ab066bd chore(release): Bumped to Version 15.94.3
## [15.94.3](https://github.com/frappe/erpnext/compare/v15.94.2...v15.94.3) (2026-01-14)

### Bug Fixes

* **transaction.js:** use flt instead of cint for plc_conversion_rate ([9819ed1](9819ed112b))
2026-01-14 12:17:05 +00:00
Diptanil Saha
df46841f82 Merge pull request #51749 from frappe/mergify/bp/version-15/pr-51747
fix(transaction.js): use flt instead of cint for plc_conversion_rate (backport #51730) (backport #51747)
2026-01-14 17:45:40 +05:30
Diptanil Saha
14d197d9eb chore: resolve conflict
(cherry picked from commit d5982cab03)
2026-01-14 12:12:56 +00:00
diptanilsaha
9819ed112b fix(transaction.js): use flt instead of cint for plc_conversion_rate
(cherry picked from commit 8b445e04e5)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
(cherry picked from commit f618bf212f)
2026-01-14 12:12:55 +00:00
Diptanil Saha
85f635ac4a Merge pull request #51747 from frappe/mergify/bp/version-15-hotfix/pr-51730
fix(transaction.js): use flt instead of cint for plc_conversion_rate (backport #51730)
2026-01-14 15:56:48 +05:30
Diptanil Saha
d5982cab03 chore: resolve conflict 2026-01-14 15:54:10 +05:30
diptanilsaha
f618bf212f fix(transaction.js): use flt instead of cint for plc_conversion_rate
(cherry picked from commit 8b445e04e5)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2026-01-14 10:22:15 +00:00
Frappe PR Bot
ddca3b5800 chore(release): Bumped to Version 15.94.2
## [15.94.2](https://github.com/frappe/erpnext/compare/v15.94.1...v15.94.2) (2026-01-13)

### Bug Fixes

* **accounting-dimension:** System-generated round-off GL entries fail to set the accounting dimension ([#51167](https://github.com/frappe/erpnext/issues/51167)) ([1179514](1179514118))
* **accounts:** correct sales order item deletion message for MR and PO linkage ([4c53af0](4c53af0494))
* allow all users of supplier to create purchase invoices ([8f1509d](8f1509dca1))
* **asset value adjustment:** skip cancelling revaluation journal entry if already cancelled ([dae6adf](dae6adfe13))
* **asset:** properly reset purchase reference and item fields ([ea0b768](ea0b76831f))
* **asset:** remove references for composite and existing asset ([c7f79d1](c7f79d16e9))
* change float types in payment entry reference table to currency ([d17deba](d17debabf7))
* closed WO becomes open when RM is returned ([7db6ae8](7db6ae8bda))
* correct uom reflecting in sales order when fetching from barcode ([3cc41cf](3cc41cf643))
* don't duplicate default income account to Item ([#50413](https://github.com/frappe/erpnext/issues/50413)) ([1cb22f9](1cb22f9d05)), closes [#48231](https://github.com/frappe/erpnext/issues/48231)
* ignore permissions when cancelling revaluation journal entry ([129457b](129457b2ce))
* incoming rate calculation ([01af6c8](01af6c8762))
* **minor:** hide target_qty field from the capitalization ([ed05b4c](ed05b4cc5c))
* move validation to before_cancel ([11d23e1](11d23e1a4a))
* negative stock issue for higher precision ([1bbeecf](1bbeecff12))
* **payment reconciliation:** handle adhoc payment returns ([#51311](https://github.com/frappe/erpnext/issues/51311)) ([159d1d6](159d1d61b5))
* pick list qty does not reset when pick list is cancelled ([f9be364](f9be364bd1))
* prevent manual cancellation of the linked Revaluation Journal Entry ([07de3f4](07de3f4391))
* remove posting date & time on SRE batch validation ([d3f2da0](d3f2da0d59))
* **stock:** enable allow on submit for tracking status field ([9d5a493](9d5a493609))

### Performance Improvements

* SABB taking time to save the record ([ee9debe](ee9debe581))
2026-01-13 15:02:59 +00:00
ruthra kumar
a8dbf981d8 Merge pull request #51711 from frappe/version-15-hotfix
chore: release v15
2026-01-13 20:31:27 +05:30
Khushi Rawat
b807f9318f Merge pull request #51721 from khushi8112/hide-target-qty-field
fix: hide target_qty field from the capitalization
2026-01-13 17:46:29 +05:30
khushi8112
a66129af29 chore: run pre-commit 2026-01-13 17:28:51 +05:30
khushi8112
ed05b4cc5c fix(minor): hide target_qty field from the capitalization 2026-01-13 17:26:32 +05:30
Khushi Rawat
00ac931722 Merge pull request #51717 from frappe/mergify/bp/version-15-hotfix/pr-51666
fix(asset value adjustment): skip cancelling revaluation journal entry if already cancelled (backport #51666)
2026-01-13 16:54:29 +05:30
khushi8112
3365bc3ba3 chore: rebase with v15 branch 2026-01-13 16:23:00 +05:30
Navin-S-R
11d23e1a4a fix: move validation to before_cancel
(cherry picked from commit d65cd605a1)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.py
2026-01-13 10:38:03 +00:00
Navin-S-R
07de3f4391 fix: prevent manual cancellation of the linked Revaluation Journal Entry
(cherry picked from commit 73b038084b)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.py
2026-01-13 10:38:03 +00:00
Navin-S-R
129457b2ce fix: ignore permissions when cancelling revaluation journal entry
(cherry picked from commit 500c44e3f5)
2026-01-13 10:38:03 +00:00
Navin-S-R
426516a1ee refactor(journal entry): replace raw SQL with query builder to unlink asset value adjustment
(cherry picked from commit 5f00239bba)
2026-01-13 10:38:02 +00:00
Navin-S-R
dae6adfe13 fix(asset value adjustment): skip cancelling revaluation journal entry if already cancelled
(cherry picked from commit b1704ccef1)

# Conflicts:
#	erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
2026-01-13 10:38:02 +00:00
Mihir Kandoi
f5cae2d60b Merge pull request #51657 from mihir-kandoi/v16 2026-01-13 10:55:15 +05:30
Mihir Kandoi
4e94e3726c chore: grammar fix 2026-01-13 10:50:15 +05:30
Mihir Kandoi
d7bf1a179a chore: update links 2026-01-13 10:43:34 +05:30
ruthra kumar
6632f3d446 Merge pull request #51332 from frappe/mergify/bp/version-15-hotfix/pr-50413
fix: don't duplicate default income account to Item (backport #50413)
2026-01-12 20:33:41 +05:30
Khushi Rawat
b909ec9388 Merge pull request #51686 from frappe/mergify/bp/version-15-hotfix/pr-51678
fix(asset): properly reset purchase reference and item fields (backport #51678)
2026-01-12 15:54:38 +05:30
khushi8112
ea0b76831f fix(asset): properly reset purchase reference and item fields
(cherry picked from commit 671610db1e)
2026-01-12 10:17:24 +00:00
Khushi Rawat
57c759dfcd Merge pull request #51677 from frappe/mergify/bp/version-15-hotfix/pr-51630
fix(asset): remove references  for composite and existing assets (backport #51630)
2026-01-12 13:03:47 +05:30
nivithamerlin
c7f79d16e9 fix(asset): remove references for composite and existing asset
(cherry picked from commit c1d50c492b)
2026-01-12 07:30:51 +00:00
ruthra kumar
940cfb58a7 Merge pull request #51665 from frappe/mergify/bp/version-15-hotfix/pr-51311
fix(payment reconciliation): handle adhoc payment returns (backport #51311)
2026-01-11 19:37:47 +05:30
NaviN
159d1d61b5 fix(payment reconciliation): handle adhoc payment returns (#51311)
* fix(payment reconciliation): handle reverse payments

* test: validate payment return gain or loss

* chore: typo

(cherry picked from commit cecd07bbf4)
2026-01-11 13:28:36 +00:00
Mihir Kandoi
d8232c4503 chore: v16 release announcement for v15 users 2026-01-10 22:22:10 +05:30
Mihir Kandoi
ae72b99846 Merge pull request #51654 from frappe/mergify/bp/version-15-hotfix/pr-51652 2026-01-10 18:27:48 +05:30
Mihir Kandoi
f9be364bd1 fix: pick list qty does not reset when pick list is cancelled
(cherry picked from commit 1d6d9c2040)
2026-01-10 12:44:16 +00:00
rohitwaghchaure
7ac55379ec Merge pull request #51632 from frappe/mergify/bp/version-15-hotfix/pr-51351
perf: SABB taking time to save the record (backport #51351)
2026-01-09 18:50:35 +05:30
Rohit Waghchaure
aa43715de6 chore: fix conflicts 2026-01-09 18:16:38 +05:30
Rohit Waghchaure
01af6c8762 fix: incoming rate calculation
(cherry picked from commit 8e143d68b4)
2026-01-09 12:17:32 +00:00
Rohit Waghchaure
ee9debe581 perf: SABB taking time to save the record
(cherry picked from commit 20320c4a6c)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
#	erpnext/stock/serial_batch_bundle.py
2026-01-09 12:17:32 +00:00
mergify[bot]
d83365734e Merge pull request #51624 from frappe/mergify/bp/version-15-hotfix/pr-50869
fix: do cancellation procedures on WO close (backport #50869)
2026-01-09 09:48:28 +00:00
ruthra kumar
1fb554c312 Merge pull request #51598 from frappe/mergify/bp/version-15-hotfix/pr-51534
fix(accounts): correct sales order item deletion message for MR and PO linkage (backport #51534)
2026-01-08 17:57:30 +05:30
Pandiyan5273
4c53af0494 fix(accounts): correct sales order item deletion message for MR and PO linkage
(cherry picked from commit 5a47503611)
2026-01-08 12:10:25 +00:00
rohitwaghchaure
e9c14e88df Merge pull request #51597 from frappe/mergify/bp/version-15-hotfix/pr-51574
fix(stock): enable allow on submit for tracking status field (backport #51574)
2026-01-08 16:50:29 +05:30
rohitwaghchaure
e6dbd06435 Merge pull request #51533 from nishkagosalia/gh-51381
fix: correct uom reflecting in sales order when fetching from..
2026-01-08 16:31:37 +05:30
Pandiyan5273
9d5a493609 fix(stock): enable allow on submit for tracking status field
(cherry picked from commit 1bfb62465f)
2026-01-08 11:01:03 +00:00
rohitwaghchaure
530c0b0bd6 Merge pull request #51588 from frappe/mergify/bp/version-15-hotfix/pr-51586
fix: negative stock issue for higher precision (backport #51586)
2026-01-08 15:03:48 +05:30
rohitwaghchaure
5193dbba9b chore: fix conflicts
Refactor test cases for delivery notes to handle negative stock and higher precision.
2026-01-08 14:45:39 +05:30
Mihir Kandoi
42658f7b1c Merge pull request #51587 from frappe/mergify/bp/version-15-hotfix/pr-51585
fix: closed WO becomes open when RM is returned (backport #51585)
2026-01-08 14:38:34 +05:30
Rohit Waghchaure
1bbeecff12 fix: negative stock issue for higher precision
(cherry picked from commit 87be020c78)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/test_delivery_note.py
2026-01-08 09:07:10 +00:00
Mihir Kandoi
7db6ae8bda fix: closed WO becomes open when RM is returned
(cherry picked from commit d0ba365aaa)
2026-01-08 08:53:28 +00:00
Mihir Kandoi
2bdd14c831 Merge pull request #51584 from frappe/mergify/bp/version-15-hotfix/pr-51583
fix: allow all users of supplier to create purchase invoices (backport #51583)
2026-01-08 13:47:24 +05:30
Frappe PR Bot
c805c7fac4 chore(release): Bumped to Version 15.94.1
## [15.94.1](https://github.com/frappe/erpnext/compare/v15.94.0...v15.94.1) (2026-01-08)

### Bug Fixes

* remove posting date & time on SRE batch validation ([69259c9](69259c9933))
2026-01-08 08:16:29 +00:00
rohitwaghchaure
7238636766 Merge pull request #51578 from frappe/mergify/bp/version-15/pr-51553
fix: remove posting date & time on SRE batch validation (backport #51553)
2026-01-08 13:45:06 +05:30
Mihir Kandoi
8f1509dca1 fix: allow all users of supplier to create purchase invoices
(cherry picked from commit 190204a939)
2026-01-08 08:02:13 +00:00
ruthra kumar
a7f59fece3 Merge pull request #51580 from frappe/mergify/bp/version-15-hotfix/pr-51167
fix(accounting-dimension): System-generated round-off GL entries fail to set the accounting dimension (backport #51167)
2026-01-08 12:21:21 +05:30
Logesh Periyasamy
1179514118 fix(accounting-dimension): System-generated round-off GL entries fail to set the accounting dimension (#51167)
* chore: remove disabled condition statement

* fix: add default dimension for round off gle

* fix: validate report type to handle opening entries roundoff

(cherry picked from commit bc63c85daf)
2026-01-08 06:36:27 +00:00
kavin-114
69259c9933 fix: remove posting date & time on SRE batch validation
(cherry picked from commit d3f2da0d59)
2026-01-08 05:51:19 +00:00
rohitwaghchaure
7aee6bdaf8 Merge pull request #51553 from aerele/support-52652
fix: remove posting date & time on SRE batch validation
2026-01-07 11:41:32 +05:30
ruthra kumar
f11fb0e45f Merge pull request #51549 from frappe/mergify/bp/version-15-hotfix/pr-51528
fix: change float types in payment entry reference table to currency (backport #51528)
2026-01-07 11:39:37 +05:30
trustedcomputer
d17debabf7 fix: change float types in payment entry reference table to currency
(cherry picked from commit 8ba71300db)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
2026-01-07 11:24:25 +05:30
Frappe PR Bot
0b565026a4 chore(release): Bumped to Version 15.94.0
# [15.94.0](https://github.com/frappe/erpnext/compare/v15.93.2...v15.94.0) (2026-01-07)

### Bug Fixes

* add company filters to project ([d6511b0](d6511b0045))
* **journal entry:** use submission_queue to perform submit and cancel actions for rows over 100 ([1d58e9b](1d58e9b91a))
* not able to submit backdated stock reco ([4b60979](4b6097914a))
* precision issue causing reservation error ([2d49cc9](2d49cc9ab2))
* resolve conflict ([dbd2964](dbd2964139))
* SABB not cancelled on cancel of Stock Reco ([eebd885](eebd88529f))
* **stock:** prevent excess stock reservation ([4d31012](4d31012df2))
* **stock:** remove item image to avoid setting the image of previous item ([6f1cfdb](6f1cfdb1de))
* **trial balance party:** add check for parties with zero credit and debit ([a0566c9](a0566c9e98))
* update filters on period closing voucher ([728a8b0](728a8b0b7d))

### Features

* add default-age-range in accounts settings (backport [#51458](https://github.com/frappe/erpnext/issues/51458)) ([#51531](https://github.com/frappe/erpnext/issues/51531)) ([582db48](582db48ca5))
* allow data import for asset repair doctype ([dc10ef4](dc10ef4287))
2026-01-07 05:01:14 +00:00
ruthra kumar
5fcf5d58f0 Merge pull request #51538 from frappe/version-15-hotfix
chore: release v15
2026-01-07 10:29:48 +05:30
rohitwaghchaure
fac865a1b4 Merge branch 'version-15' into version-15-hotfix 2026-01-07 09:58:13 +05:30
kavin-114
d3f2da0d59 fix: remove posting date & time on SRE batch validation 2026-01-07 01:00:29 +05:30
ruthra kumar
f8fb58feaf Merge pull request #51493 from frappe/mergify/bp/version-15-hotfix/pr-51326
fix(journal entry): use submission_queue to perform submit and cancel actions for rows over 100 (backport #51326)
2026-01-06 20:57:12 +05:30
ruthra kumar
9bba78f7a2 Merge pull request #51545 from frappe/mergify/bp/version-15-hotfix/pr-51424
fix(trial balance party): add check for parties with zero credit and debit (backport #51424)
2026-01-06 18:03:13 +05:30
Jatin3128
a0566c9e98 fix(trial balance party): add check for parties with zero credit and debit
(cherry picked from commit 83ddaf1696)
2026-01-06 12:18:06 +00:00
Khushi Rawat
64a7e3f683 Merge pull request #51541 from frappe/mergify/bp/version-15-hotfix/pr-51540
feat: allow data import for asset repair doctype (backport #51540)
2026-01-06 17:01:12 +05:30
Khushi Rawat
dbd2964139 fix: resolve conflict 2026-01-06 16:46:20 +05:30
khushi8112
dc10ef4287 feat: allow data import for asset repair doctype
(cherry picked from commit 49f1688a51)

# Conflicts:
#	erpnext/assets/doctype/asset_repair/asset_repair.json
2026-01-06 10:39:53 +00:00
Nishka Gosalia
3cc41cf643 fix: correct uom reflecting in sales order when fetching from barcode 2026-01-06 12:51:47 +05:30
ruthra kumar
582db48ca5 feat: add default-age-range in accounts settings (backport #51458) (#51531)
Merge pull request #51458 from aerele/default-age-range

feat: add default-age-range in accounts settings
(cherry picked from commit f8f82ccf31)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json

Co-authored-by: Sowmya <106989392+SowmyaArunachalam@users.noreply.github.com>
2026-01-06 12:43:35 +05:30
Sowmya
0452820ab0 Merge pull request #51458 from aerele/default-age-range
feat: add default-age-range in accounts settings
(cherry picked from commit f8f82ccf31)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
2026-01-06 11:33:25 +05:30
Navin-S-R
1d58e9b91a fix(journal entry): use submission_queue to perform submit and cancel actions for rows over 100
(cherry picked from commit fa8e80c6a0)
2026-01-05 06:52:05 +00:00
ruthra kumar
7bbcafed8d Merge pull request #51489 from frappe/mergify/bp/version-15-hotfix/pr-51457
fix: add company filters to project (backport #51457)
2026-01-05 11:19:33 +05:30
SowmyaArunachalam
d6511b0045 fix: add company filters to project
(cherry picked from commit 7c16db567b)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.js
2026-01-05 11:12:44 +05:30
ruthra kumar
0d790a6cd5 Merge pull request #51487 from frappe/mergify/bp/version-15-hotfix/pr-51467
fix: update filters on period closing voucher (backport #51467)
2026-01-05 10:53:31 +05:30
SowmyaArunachalam
728a8b0b7d fix: update filters on period closing voucher
(cherry picked from commit 7ab1e1f677)
2026-01-05 05:20:32 +00:00
rohitwaghchaure
d8cb65e440 Merge pull request #51477 from frappe/mergify/bp/version-15-hotfix/pr-51475
fix: SABB not cancelled on cancel of Stock Reco (backport #51475)
2026-01-03 16:34:11 +05:30
Rohit Waghchaure
eebd88529f fix: SABB not cancelled on cancel of Stock Reco
(cherry picked from commit b204853193)
2026-01-03 10:47:17 +00:00
Frappe PR Bot
1740fce6c8 chore(release): Bumped to Version 15.93.2
## [15.93.2](https://github.com/frappe/erpnext/compare/v15.93.1...v15.93.2) (2026-01-03)

### Bug Fixes

* not able to submit backdated stock reco ([9ef7d45](9ef7d45486))
2026-01-03 10:31:34 +00:00
rohitwaghchaure
a01dc0e205 Merge pull request #51471 from frappe/mergify/bp/version-15/pr-51470
fix: not able to submit backdated stock reco (backport #51468) (backport #51470)
2026-01-03 15:59:59 +05:30
rohitwaghchaure
1ec2cc3820 chore: fix conflicts
Removed unused on_discard method and cleaned up code.

(cherry picked from commit 46f3ab1c39)
2026-01-03 10:12:09 +00:00
Rohit Waghchaure
9ef7d45486 fix: not able to submit backdated stock reco
(cherry picked from commit cccd34b06a)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
(cherry picked from commit 4b6097914a)
2026-01-03 10:12:09 +00:00
rohitwaghchaure
02203ca534 Merge pull request #51470 from frappe/mergify/bp/version-15-hotfix/pr-51468
fix: not able to submit backdated stock reco (backport #51468)
2026-01-03 15:41:07 +05:30
rohitwaghchaure
46f3ab1c39 chore: fix conflicts
Removed unused on_discard method and cleaned up code.
2026-01-03 15:23:29 +05:30
Rohit Waghchaure
4b6097914a fix: not able to submit backdated stock reco
(cherry picked from commit cccd34b06a)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
2026-01-03 09:52:09 +00:00
rohitwaghchaure
decc27a446 Merge pull request #51411 from rohitwaghchaure/backport-4972
fix: precision issue causing reservation error
2026-01-01 09:51:50 +05:30
Rohit Waghchaure
2d49cc9ab2 fix: precision issue causing reservation error 2025-12-31 13:29:08 +05:30
rohitwaghchaure
5955b699c3 Merge pull request #51394 from frappe/mergify/bp/version-15-hotfix/pr-51375
fix(stock): prevent excess stock reservation (backport #51375)
2025-12-31 11:09:14 +05:30
rohitwaghchaure
c8d8ec91c4 Merge pull request #51395 from frappe/mergify/bp/version-15-hotfix/pr-51341
fix(stock): remove item image to avoid setting the image of previous item (backport #51341)
2025-12-31 11:04:29 +05:30
Frappe PR Bot
a857923853 chore(release): Bumped to Version 15.93.1
## [15.93.1](https://github.com/frappe/erpnext/compare/v15.93.0...v15.93.1) (2025-12-30)

### Bug Fixes

* **accounts-payable-summary:** add Show GL Balance check similar to A… (backport [#50802](https://github.com/frappe/erpnext/issues/50802)) ([#50805](https://github.com/frappe/erpnext/issues/50805)) ([a04f560](a04f560048))
* **bank reconciliation tool:** carry bank account to payment entry ([cd930c0](cd930c05b8))
* **bank reconciliation tool:** fix incorrect bank account field mapping ([9ef0e8b](9ef0e8beb7))
* expense_account query override in Purchase Receipt ([6f3904a](6f3904a20a))
* **payment entry:** clear party_name for internal transfer ([431e687](431e68741b))
* prevent reuse of serial no in manufacture and repack entry ([24f6f1e](24f6f1e434))
* **repost accounting ledger:** prevent preview generation when no vouchers are selected ([93c1a3f](93c1a3f8f3))
* start reposting accounting ledger after commit ([e6acdf3](e6acdf36e2))
* **stock:** remove total bar in chart view ([d9888d5](d9888d5195))
* updating base amounts through python for timesheet for v15 ([9d2e0f6](9d2e0f67d5))
* validate depreciation row values ([2f10b9c](2f10b9c510))
* validate party's existing transaction currency before merging ([1c40a61](1c40a61d23))

### Performance Improvements

* composite index for serial no ([507a561](507a561922))
* index for warehouse field ([4753594](4753594a26))
2025-12-30 13:35:04 +00:00
ruthra kumar
4f6499836e Merge pull request #51391 from frappe/version-15-hotfix
chore: release v15
2025-12-30 19:03:35 +05:30
Sudharsanan11
6f1cfdb1de fix(stock): remove item image to avoid setting the image of previous item
(cherry picked from commit 69e94248c1)
2025-12-30 11:21:11 +00:00
Sudharsanan11
4d31012df2 fix(stock): prevent excess stock reservation
(cherry picked from commit e1f9adf4e9)
2025-12-30 11:20:11 +00:00
ruthra kumar
944dacc12f Merge pull request #51393 from frappe/mergify/bp/version-15-hotfix/pr-51340
fix(bank reconciliation tool): carry bank account to payment entry (backport #51340)
2025-12-30 16:28:52 +05:30
ravibharathi656
9ef0e8beb7 fix(bank reconciliation tool): fix incorrect bank account field mapping
(cherry picked from commit 9dfb0fdcbb)
2025-12-30 10:28:40 +00:00
ravibharathi656
cd930c05b8 fix(bank reconciliation tool): carry bank account to payment entry
(cherry picked from commit 6fc9636642)
2025-12-30 10:28:39 +00:00
ruthra kumar
c42aa4f89b Merge pull request #51384 from frappe/mergify/bp/version-15-hotfix/pr-51368
fix: start reposting accounting ledger after commit (backport #51368)
2025-12-30 14:08:31 +05:30
Khushi Rawat
3f4ffcc955 Merge pull request #51387 from frappe/mergify/bp/version-15-hotfix/pr-51380
fix: expense_account query override in Purchase Receipt (backport #51380)
2025-12-30 13:00:18 +05:30
khushi8112
6f3904a20a fix: expense_account query override in Purchase Receipt
(cherry picked from commit 292a51c160)
2025-12-30 07:27:01 +00:00
Ponnusamy
e6acdf36e2 fix: start reposting accounting ledger after commit
(cherry picked from commit 469a1ade79)
2025-12-30 06:53:18 +00:00
ruthra kumar
9ee40351c5 Merge pull request #51378 from frappe/mergify/bp/version-15-hotfix/pr-51361
fix(payment entry): clear party_name for internal transfer (backport #51361)
2025-12-30 12:02:43 +05:30
ravibharathi656
431e68741b fix(payment entry): clear party_name for internal transfer
(cherry picked from commit aae0448e1f)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.js
2025-12-30 11:53:48 +05:30
ruthra kumar
f1c98df7bb Merge pull request #51372 from frappe/mergify/bp/version-15-hotfix/pr-51171
fix: validate party's existing transaction currency before merging (backport #51171)
2025-12-30 11:07:34 +05:30
Nabin Hait
1c40a61d23 fix: validate party's existing transaction currency before merging
(cherry picked from commit f48b90c600)
2025-12-30 04:52:39 +00:00
Mihir Kandoi
fdf80a6d02 Merge pull request #51319 from nishkagosalia/gh-50389-v15 2025-12-30 10:19:39 +05:30
rohitwaghchaure
b9c123bd89 Merge pull request #51350 from frappe/mergify/bp/version-15-hotfix/pr-50363
fix: prevent reuse of serial no in manufacture and repack entry (backport #50363)
2025-12-28 10:45:51 +05:30
Rohit Waghchaure
24f6f1e434 fix: prevent reuse of serial no in manufacture and repack entry
(cherry picked from commit 48b537dc8c)
2025-12-28 04:57:30 +00:00
Mihir Kandoi
f263a7f65c Merge pull request #51344 from frappe/mergify/bp/version-15-hotfix/pr-51330
fix(stock): remove total bar in chart view (backport #51330)
2025-12-26 22:04:12 +05:30
Sudharsanan11
d9888d5195 fix(stock): remove total bar in chart view
(cherry picked from commit 7df349844a)
2025-12-26 16:19:50 +00:00
rohitwaghchaure
52b3740eb1 Merge pull request #51329 from frappe/mergify/bp/version-15-hotfix/pr-51322
perf: composite index for serial no (backport #51322)
2025-12-25 15:43:25 +05:30
Raffael Meyer
1cb22f9d05 fix: don't duplicate default income account to Item (#50413)
* fix: don't duplicate default income account to Item

Only store _Default Income Account_ in **Item** if it's different from the **Company**'s  _Default Income Account_.

Resolves #48231

* refactor: move db call out of loop

* docs: add docstring

(cherry picked from commit b6cb9d4799)
2025-12-25 09:23:14 +00:00
Rohit Waghchaure
507a561922 perf: composite index for serial no
(cherry picked from commit 734d553338)
2025-12-25 03:40:05 +00:00
Mihir Kandoi
88e305f5a2 Merge pull request #51323 from frappe/mergify/bp/version-15-hotfix/pr-50826 2025-12-24 21:51:11 +05:30
Nishka Gosalia
9d2e0f67d5 fix: updating base amounts through python for timesheet for v15 2025-12-24 21:44:58 +05:30
Abdeali Chharchhoda
fd718833b1 refactor: optimize picked quantity updates using bulk_update
(cherry picked from commit 5f986e4032)
2025-12-24 16:06:19 +00:00
rohitwaghchaure
c7c938c259 Merge pull request #51313 from frappe/mergify/bp/version-15-hotfix/pr-51310
perf: index for warehouse field (backport #51310)
2025-12-24 15:30:23 +05:30
rohitwaghchaure
35ae839ab7 chore: fix conflicts 2025-12-24 15:06:48 +05:30
Rohit Waghchaure
4753594a26 perf: index for warehouse field
(cherry picked from commit 23c70332df)

# Conflicts:
#	erpnext/stock/doctype/serial_no/serial_no.json
2025-12-24 09:29:33 +00:00
Khushi Rawat
4e4d2cefda Merge pull request #51282 from khushi8112/validate-finance-books-row-values
fix: validate finance books row values
2025-12-24 14:52:15 +05:30
mergify[bot]
a04f560048 fix(accounts-payable-summary): add Show GL Balance check similar to A… (backport #50802) (#50805) 2025-12-24 13:11:28 +05:30
Diptanil Saha
c9d8c5b419 Merge pull request #51307 from frappe/mergify/bp/version-15-hotfix/pr-51304
fix(repost accounting ledger): prevent preview generation when no vouchers are selected (backport #51304)
2025-12-24 13:09:32 +05:30
diptanilsaha
93c1a3f8f3 fix(repost accounting ledger): prevent preview generation when no vouchers are selected
(cherry picked from commit bd9f5fca08)
2025-12-24 07:24:35 +00:00
Frappe PR Bot
926b4c7065 chore(release): Bumped to Version 15.93.0
# [15.93.0](https://github.com/frappe/erpnext/compare/v15.92.5...v15.93.0) (2025-12-23)

### Bug Fixes

* added limit ([73643de](73643de612))
* **buying:** add disabled filter for supplier ([0b6b73b](0b6b73b500))
* cascade projected quantity across multiple items in material requests ([dffd5d9](dffd5d9cdd))
* de-duplicate rows on disassembly with multiple manufacture entries ([68eeba4](68eeba41c1))
* do not hide primary-action for composite asset ([cbcfe6e](cbcfe6ec36))
* don't fetch qty as it's unused ([dd19b95](dd19b95113))
* incorrect current qty in stock reco (backport [#51152](https://github.com/frappe/erpnext/issues/51152)) ([#51158](https://github.com/frappe/erpnext/issues/51158)) ([89d6a8f](89d6a8f02e))
* limit condition to fetch serial nos ([425dcee](425dcee5bf))
* **manufacturing:** validate delivered qty in production plan ([c01f20d](c01f20da00))
* **payment entry:** set row id for 'On Previous Row Amount' or 'On Previous Row Total' charge type on tax table ([d7c50cf](d7c50cfa7c))
* **pegged currencies:** skip adding currencies_to_add items on  pegged_currency_item if source_currency or pegged_against currency doc does not exist (backport [#51188](https://github.com/frappe/erpnext/issues/51188)) ([#51203](https://github.com/frappe/erpnext/issues/51203)) ([8ef09c0](8ef09c0dc0))
* same serial number was picked in multiple sales invoices ([dc5faa8](dc5faa8b71))
* show company currency in asset depreciation schedule ([5b1795b](5b1795b0a5))
* **stock-report:** ignore reserved stock in batch qty calculation ([26a36d8](26a36d807e))
* **stock:** handle serial and batch nos for disassemble stock entry ([59aef4f](59aef4fc8c))
* **stock:** ignore reserved stock while calculating batch qty ([ac2402d](ac2402dd2a))
* support disassemble of RMs other than in BOM ([72d77a5](72d77a5e99))
* update batch_qty using get_batch_qty ([ca835c8](ca835c831b))
* use get_batch_qty to fetch batch data ([10b0da8](10b0da8bc8))
* use original logic for v15 - inverted wrt v16 ([0452b22](0452b22aa6))
* use serial and batch bundle to fetch incoming rate (backport [#51119](https://github.com/frappe/erpnext/issues/51119)) ([#51146](https://github.com/frappe/erpnext/issues/51146)) ([2d42904](2d42904bfb))
* use stock adjustment if the account has not set ([8a01a70](8a01a709a7))

### Features

* add redirect button on report ([fe80d1d](fe80d1d0e7))
* **report:** add batch qty update functionality in report ([57c356a](57c356a1cd))

### Performance Improvements

* optimize company monthly sales query using date range ([#48942](https://github.com/frappe/erpnext/issues/48942)) ([0488658](048865811c))
2025-12-23 16:42:06 +00:00
Mihir Kandoi
f29ad04eab Merge pull request #51280 from frappe/version-15-hotfix 2025-12-23 22:10:38 +05:30
Mihir Kandoi
8fa73b370a Merge pull request #51299 from frappe/mergify/bp/version-15-hotfix/pr-51256
fix(manufacturing): validate delivered qty in production plan (backport #51256)
2025-12-23 21:47:36 +05:30
Sudharsanan11
2645bf648d test(manufacturing): add test to validate planned qty
(cherry picked from commit 2073cb0106)
2025-12-23 16:02:09 +00:00
Sudharsanan11
c01f20da00 fix(manufacturing): validate delivered qty in production plan
(cherry picked from commit eda8a621c6)
2025-12-23 16:02:09 +00:00
Mihir Kandoi
d3f434b803 Merge pull request #51177 from aerele/fix/disassemble-serial-and-batch-bundle 2025-12-23 21:07:32 +05:30
Mihir Kandoi
0687b035b5 Merge pull request #51296 from frappe/mergify/bp/version-15-hotfix/pr-51225 2025-12-23 21:02:54 +05:30
Mihir Kandoi
04a98b2b64 Merge pull request #51142 from frappe/mergify/bp/version-15-hotfix/pr-51141
fix(buying): add disabled filter for supplier (backport #51141)
2025-12-23 20:50:26 +05:30
Mihir Kandoi
eae1886043 Merge branch 'mergify/bp/version-15-hotfix/pr-51141' of https://github.com/frappe/erpnext into mergify/bp/version-15-hotfix/pr-51141 2025-12-23 20:47:48 +05:30
Mihir Kandoi
5f295c5310 chore: resolve conflicts 2025-12-23 20:47:16 +05:30
SowmyaArunachalam
fe80d1d0e7 feat: add redirect button on report
(cherry picked from commit c0ac5f94b5)
2025-12-23 15:16:40 +00:00
Mihir Kandoi
5e7b674ee4 Merge pull request #51250 from frappe/mergify/bp/version-15-hotfix/pr-51215
fix: de-duplicate rows on disassembly with multiple manufacture entries (backport #51215)
2025-12-23 20:28:37 +05:30
rohitwaghchaure
4166c7ff47 Merge branch 'version-15' into version-15-hotfix 2025-12-23 18:15:55 +05:30
mergify[bot]
0f2fb54756 Merge pull request #51292 from frappe/mergify/bp/version-15-hotfix/pr-51285
fix(patch): handle currency exchange settings frankfurter api update for older versions (backport #51285)
2025-12-23 18:00:19 +05:30
rohitwaghchaure
9409155594 Merge pull request #51289 from frappe/mergify/bp/version-15-hotfix/pr-51276
fix: use stock adjustment if the account has not set (backport #51276)
2025-12-23 17:38:02 +05:30
Rohit Waghchaure
8a01a709a7 fix: use stock adjustment if the account has not set
(cherry picked from commit 9bbcbe0ac3)
2025-12-23 11:38:10 +00:00
Khushi Rawat
cc1f38010d Merge pull request #51284 from khushi8112/do-not-disable-primary-action-button-bp-51205
fix: do not hide primary-action for composite asset
2025-12-23 16:43:51 +05:30
Smit Vora
f0aefa4274 chore: v15 compatible get-all query 2025-12-23 16:22:06 +05:30
khushi8112
cbcfe6ec36 fix: do not hide primary-action for composite asset 2025-12-23 16:02:50 +05:30
khushi8112
6ff002dbe3 refactor: split long function into smaller 2025-12-23 15:44:43 +05:30
khushi8112
2f10b9c510 fix: validate depreciation row values 2025-12-23 15:32:02 +05:30
Khushi Rawat
83b1e037cb Merge pull request #51198 from aerele/repost-asset-sales-voucher
fix: avoid creating multiple asset depreciations while reposting asset sales invoice
2025-12-23 14:16:20 +05:30
Frappe PR Bot
5007abf7ae chore(release): Bumped to Version 15.92.5
## [15.92.5](https://github.com/frappe/erpnext/compare/v15.92.4...v15.92.5) (2025-12-23)

### Bug Fixes

* bumped version ([6df222a](6df222a1ca))
2025-12-23 08:16:22 +00:00
rohitwaghchaure
d31dd1a023 Merge pull request #51277 from rohitwaghchaure/fixed-bumped-version-v15
fix: bumped version
2025-12-23 13:44:58 +05:30
Rohit Waghchaure
6df222a1ca fix: bumped version 2025-12-23 13:27:08 +05:30
rohitwaghchaure
265da1056d Merge pull request #51272 from rohitwaghchaure/fixed-bumped-version
chore(release): Bumped to Version 15.92.5
2025-12-23 12:59:54 +05:30
Rohit Waghchaure
670beae048 chore(release): Bumped to Version 15.92.5 2025-12-23 12:49:12 +05:30
rohitwaghchaure
6b2a077bec Merge pull request #51270 from frappe/mergify/bp/version-15/pr-51260
Revert "fix: performance of the reposting" (backport #51258) (backport #51260)
2025-12-23 12:37:30 +05:30
rohitwaghchaure
43831e9785 chore: fix linters issue
(cherry picked from commit e9c37642c8)
(cherry picked from commit c095938e69)
2025-12-23 06:49:20 +00:00
rohitwaghchaure
7187992170 chore: fix test case
(cherry picked from commit d191b80587)
(cherry picked from commit aefde87a0c)
2025-12-23 06:49:19 +00:00
rohitwaghchaure
f3d0a91fb3 Revert "fix: performance of the reposting"
(cherry picked from commit 280558efa2)
(cherry picked from commit c89fe9f1ca)
2025-12-23 06:49:19 +00:00
rohitwaghchaure
f318a3658d Merge pull request #51265 from frappe/mergify/bp/version-15-hotfix/pr-51251
fix: order by to fetch serial nos
2025-12-22 18:23:49 +05:30
rohitwaghchaure
8f52f14505 chore: fix conflicts
Refactor based_on retrieval method and remove unused fields.
2025-12-22 18:04:47 +05:30
Rohit Waghchaure
425dcee5bf fix: limit condition to fetch serial nos
(cherry picked from commit da4b78491d)

# Conflicts:
#	erpnext/stock/get_item_details.py
2025-12-22 12:16:04 +00:00
rohitwaghchaure
06aded08ae Merge pull request #51260 from frappe/mergify/bp/version-15-hotfix/pr-51258
Revert "fix: performance of the reposting" (backport #51258)
2025-12-22 17:15:12 +05:30
rohitwaghchaure
c095938e69 chore: fix linters issue
(cherry picked from commit e9c37642c8)
2025-12-22 11:12:50 +00:00
rohitwaghchaure
aefde87a0c chore: fix test case
(cherry picked from commit d191b80587)
2025-12-22 11:12:50 +00:00
rohitwaghchaure
c89fe9f1ca Revert "fix: performance of the reposting"
(cherry picked from commit 280558efa2)
2025-12-22 11:12:50 +00:00
Frappe PR Bot
658a7c536d chore(release): Bumped to Version 15.92.4
## [15.92.4](https://github.com/frappe/erpnext/compare/v15.92.3...v15.92.4) (2025-12-22)

### Bug Fixes

* added limit ([0e73c12](0e73c12add))
* same serial number was picked in multiple sales invoices ([05ad50f](05ad50f98b))
2025-12-22 08:35:29 +00:00
rohitwaghchaure
94c430cc6e Merge pull request #51254 from frappe/mergify/bp/version-15/pr-51247
fix: same serial number was picked in multiple sales invoices (backport #51244) (backport #51247)
2025-12-22 14:04:07 +05:30
rohitwaghchaure
0e73c12add fix: added limit
(cherry picked from commit 73643de612)
2025-12-22 07:38:26 +00:00
rohitwaghchaure
aba3d7821c chore: fix conflicts
Removed logic for handling reserved serial numbers in sales invoices.

(cherry picked from commit c77c426652)
2025-12-22 07:38:25 +00:00
Rohit Waghchaure
05ad50f98b fix: same serial number was picked in multiple sales invoices
(cherry picked from commit 61c31f0cd0)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
(cherry picked from commit dc5faa8b71)
2025-12-22 07:38:25 +00:00
rohitwaghchaure
5200739d7b Merge pull request #51247 from frappe/mergify/bp/version-15-hotfix/pr-51244
fix: same serial number was picked in multiple sales invoices (backport #51244)
2025-12-22 13:07:47 +05:30
rohitwaghchaure
73643de612 fix: added limit 2025-12-22 12:02:38 +05:30
rohitwaghchaure
fab49e41a6 Merge pull request #51245 from frappe/mergify/bp/version-15-hotfix/pr-51242
fix(stock-report): ignore reserved stock in batch qty calculation (backport #51242)
2025-12-22 11:46:51 +05:30
Smit Vora
bb00bb83f8 test: ensure full qty reversal for items outside of BOM on disassemble
(cherry picked from commit 5b3d2c0d02)
2025-12-22 06:09:08 +00:00
Smit Vora
72d77a5e99 fix: support disassemble of RMs other than in BOM
(cherry picked from commit ce123f1a89)
2025-12-22 06:09:07 +00:00
Smit Vora
16112630ea test: ensure no regression after save and submit on disassemble
(cherry picked from commit 18ac589796)
2025-12-22 06:09:07 +00:00
Smit Vora
dd19b95113 fix: don't fetch qty as it's unused
(cherry picked from commit df13308663)
2025-12-22 06:09:07 +00:00
Smit Vora
68eeba41c1 fix: de-duplicate rows on disassembly with multiple manufacture entries
(cherry picked from commit a091e47bd7)
2025-12-22 06:09:06 +00:00
rohitwaghchaure
c77c426652 chore: fix conflicts
Removed logic for handling reserved serial numbers in sales invoices.
2025-12-22 11:37:19 +05:30
Rohit Waghchaure
dc5faa8b71 fix: same serial number was picked in multiple sales invoices
(cherry picked from commit 61c31f0cd0)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
2025-12-22 04:09:59 +00:00
Pugazhendhi Velu
26a36d807e fix(stock-report): ignore reserved stock in batch qty calculation
(cherry picked from commit 9a1f551e53)
2025-12-21 16:57:51 +00:00
rohitwaghchaure
432b33ac5f Merge pull request #51223 from frappe/mergify/bp/version-15-hotfix/pr-49951
feat(report): add batch qty update functionality in report (backport #49951)
2025-12-21 22:26:22 +05:30
Pugazhendhi Velu
ca835c831b fix: update batch_qty using get_batch_qty
(cherry picked from commit 15d9d8b719)
2025-12-19 14:56:18 +00:00
Pugazhendhi Velu
10b0da8bc8 fix: use get_batch_qty to fetch batch data
(cherry picked from commit cf03d03033)
2025-12-19 14:56:18 +00:00
Pugazhendhi Velu
e7fcacbe69 refactor: fetch batch qty difference in a single db query
(cherry picked from commit 9cc77934a6)
2025-12-19 14:56:18 +00:00
Pugazhendhi Velu
57c356a1cd feat(report): add batch qty update functionality in report
(cherry picked from commit f40c492a05)
2025-12-19 14:56:18 +00:00
Frappe PR Bot
6e7de0ac47 chore(release): Bumped to Version 15.92.3
## [15.92.3](https://github.com/frappe/erpnext/compare/v15.92.2...v15.92.3) (2025-12-19)

### Bug Fixes

* **stock:** ignore reserved stock while calculating batch qty ([35478bb](35478bbf91))
2025-12-19 13:25:33 +00:00
rohitwaghchaure
2277b1aff5 Merge pull request #51221 from frappe/mergify/bp/version-15/pr-51220
fix(stock): ignore reserved stock while calculating batch qty (backport #51214) (backport #51220)
2025-12-19 18:54:08 +05:30
rohitwaghchaure
58c793f14e chore: fix conflicts
Removed logic for handling reserved stock when calculating batch quantity.

(cherry picked from commit 9ade0725e8)
2025-12-19 12:55:11 +00:00
Sudharsanan11
08cd08adcd test(stock): add test for ignore reserve stock
(cherry picked from commit 4d8ec5f54c)
(cherry picked from commit b20405dbf2)
2025-12-19 12:55:11 +00:00
Sudharsanan11
35478bbf91 fix(stock): ignore reserved stock while calculating batch qty
(cherry picked from commit b23c6e2687)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
(cherry picked from commit ac2402dd2a)
2025-12-19 12:55:11 +00:00
rohitwaghchaure
40481508f1 Merge pull request #51220 from frappe/mergify/bp/version-15-hotfix/pr-51214
fix(stock): ignore reserved stock while calculating batch qty (backport #51214)
2025-12-19 18:24:37 +05:30
rohitwaghchaure
9ade0725e8 chore: fix conflicts
Removed logic for handling reserved stock when calculating batch quantity.
2025-12-19 18:07:42 +05:30
Sudharsanan11
b20405dbf2 test(stock): add test for ignore reserve stock
(cherry picked from commit 4d8ec5f54c)
2025-12-19 12:31:09 +00:00
Sudharsanan11
ac2402dd2a fix(stock): ignore reserved stock while calculating batch qty
(cherry picked from commit b23c6e2687)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
2025-12-19 12:31:09 +00:00
Smit Vora
c827fc3259 Merge pull request #51103 from frappe/mergify/bp/version-15-hotfix/pr-50788
fix: cascade projected quantity across multiple items in material requests (backport #50788)
2025-12-19 14:07:50 +05:30
Smit Vora
f13db03c9b test: make corrections to tests based on v15 functionality 2025-12-19 10:33:44 +05:30
Frappe PR Bot
54ed428225 chore(release): Bumped to Version 15.92.2
## [15.92.2](https://github.com/frappe/erpnext/compare/v15.92.1...v15.92.2) (2025-12-18)

### Bug Fixes

* **pegged currencies:** skip adding currencies_to_add items on  pegged_currency_item if source_currency or pegged_against currency doc does not exist (backport [#51188](https://github.com/frappe/erpnext/issues/51188)) ([#51203](https://github.com/frappe/erpnext/issues/51203)) ([195f902](195f90232d))
2025-12-18 12:38:51 +00:00
Diptanil Saha
c046dad2c3 Merge pull request #51206 from frappe/mergify/bp/version-15/pr-51203
fix(pegged currencies): skip adding currencies_to_add items on  pegged_currency_item if source_currency or pegged_against currency doc does not exist (backport #51188)
2025-12-18 18:07:29 +05:30
mergify[bot]
195f90232d fix(pegged currencies): skip adding currencies_to_add items on pegged_currency_item if source_currency or pegged_against currency doc does not exist (backport #51188) (#51203)
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
fix(pegged currencies): skip adding currencies_to_add items on  pegged_currency_item if source_currency or pegged_against currency doc does not exist (#51188)

(cherry picked from commit 8ef09c0dc0)
2025-12-18 12:18:08 +00:00
mergify[bot]
8ef09c0dc0 fix(pegged currencies): skip adding currencies_to_add items on pegged_currency_item if source_currency or pegged_against currency doc does not exist (backport #51188) (#51203)
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
fix(pegged currencies): skip adding currencies_to_add items on  pegged_currency_item if source_currency or pegged_against currency doc does not exist (#51188)
2025-12-18 17:05:39 +05:30
Mihir Kandoi
7f91f95f95 chore: resolve conflicts 2025-12-18 15:37:23 +05:30
Navin-S-R
696a0892fa refactor: improve asset depreciation handling during asset sales 2025-12-18 13:27:06 +05:30
Sudharsanan11
59aef4fc8c fix(stock): handle serial and batch nos for disassemble stock entry 2025-12-17 16:16:54 +05:30
Khushi Rawat
99cd7cf63e Merge pull request #51164 from frappe/mergify/bp/version-15-hotfix/pr-51156
fix: show company currency in asset depreciation schedule (backport #51156)
2025-12-17 15:51:27 +05:30
Diptanil Saha
44082cae72 Merge pull request #51170 from frappe/mergify/bp/version-15-hotfix/pr-51169
fix(payment entry): set row id for 'On Previous Row Amount' or 'On Previous Row Total' charge type on tax table (backport #51169)
2025-12-17 15:32:05 +05:30
diptanilsaha
d7c50cfa7c fix(payment entry): set row id for 'On Previous Row Amount' or 'On Previous Row Total' charge type on tax table
(cherry picked from commit 848f8d6b1f)
2025-12-17 09:52:54 +00:00
Frappe PR Bot
1e52738150 chore(release): Bumped to Version 15.92.1
## [15.92.1](https://github.com/frappe/erpnext/compare/v15.92.0...v15.92.1) (2025-12-17)

### Bug Fixes

* incorrect current qty in stock reco (backport [#51152](https://github.com/frappe/erpnext/issues/51152)) ([#51158](https://github.com/frappe/erpnext/issues/51158)) ([552c5b5](552c5b5911))
2025-12-17 09:34:54 +00:00
rohitwaghchaure
cbd0a76645 Merge pull request #51161 from frappe/mergify/bp/version-15/pr-51158
fix: incorrect current qty in stock reco (backport #51152) (backport #51158)
2025-12-17 15:03:25 +05:30
sudarshan-g
5b1795b0a5 fix: show company currency in asset depreciation schedule
(cherry picked from commit e32f898dd7)
2025-12-17 09:04:31 +00:00
mergify[bot]
552c5b5911 fix: incorrect current qty in stock reco (backport #51152) (#51158)
* fix: incorrect current qty in stock reco (#51152)

(cherry picked from commit dec474ef3a)

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit 89d6a8f02e)
2025-12-17 08:17:20 +00:00
mergify[bot]
89d6a8f02e fix: incorrect current qty in stock reco (backport #51152) (#51158)
* fix: incorrect current qty in stock reco (#51152)

(cherry picked from commit dec474ef3a)

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-12-17 13:46:31 +05:30
Diptanil Saha
069262dd4d Merge pull request #51148 from frappe/mergify/bp/version-15-hotfix/pr-48942 2025-12-17 11:55:23 +05:30
Yash Chaubey
048865811c perf: optimize company monthly sales query using date range (#48942)
* perf: optimize company monthly sales query using date range instead of DATE_FORMAT

* perf: optimize company monthly sales query using date range

(cherry picked from commit 4ede97ae2b)
2025-12-17 06:07:27 +00:00
mergify[bot]
2d42904bfb fix: use serial and batch bundle to fetch incoming rate (backport #51119) (#51146)
Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: use serial and batch bundle to fetch incoming rate (#51119)
2025-12-16 18:24:39 +01:00
Frappe PR Bot
66b2b89bcd chore(release): Bumped to Version 15.92.0
# [15.92.0](https://github.com/frappe/erpnext/compare/v15.91.3...v15.92.0) (2025-12-16)

### Bug Fixes

* **accounts:** handle drop ship in company linked address validation ([b340d7d](b340d7d4f4))
* add link filters for item group in quickentry ([981c9c7](981c9c76c1))
* add missing query key in 'Reports To' field filter ([e1dc80b](e1dc80b6d8))
* add validation for transferred qty and handle MR transfer status for in-transit entry. (backport [#50683](https://github.com/frappe/erpnext/issues/50683)) ([#51134](https://github.com/frappe/erpnext/issues/51134)) ([6a6398a](6a6398a392))
* **currency exchange settings:** added backward compatibility for frankfurter api ([8d32ba9](8d32ba9a2e))
* delayed tasks summary chart color ([325fc61](325fc619dc))
* ensure fresh `grand_total_diff` is used for each calculation ([2d198e6](2d198e698a))
* ensure type on method parameter ([16c8b74](16c8b74d52))
* incorrect invoice qty ([ebbecdb](ebbecdba23))
* **manufacturing:** add validation for disassemble qty ([cdc0429](cdc04292f2))
* **manufacturing:** get items for disassembly order ([279cf6f](279cf6fe00))
* mark navbar item as translatable ([ec3a226](ec3a226a83))
* only show net gl balance as opening in general ledger ([0d5e45b](0d5e45bb7c))
* **payment entry:** fetch gain loss account from company boot ([c01e40d](c01e40da3c))
* precision issue on job card submission ([4ee4a57](4ee4a57f72))
* preserve user-entered exchange rates in ERR journal entries ([fa04e36](fa04e368d3))
* prevent dispatch address copying on drop ship ([5d5dff9](5d5dff9103))
* prevent self in "Reports To" dropdown (UI-level check) ([9e8bb9b](9e8bb9b235))
* putaway rule not applying on serial nos ([df820ae](df820aece6))
* re-calculate outstanding / write-off amount during submission ([5bfdc01](5bfdc010f3))
* **Rename Tool:** use "Link" field instead of "Select" ([2aff169](2aff16928c))
* **Rename Tool:** use "Link" field instead of "Select" (backport [#50995](https://github.com/frappe/erpnext/issues/50995)) ([#51138](https://github.com/frappe/erpnext/issues/51138)) ([53bb2cf](53bb2cf7c0))
* Serial/Batches not fetching when creating Material Transfer from Purchase Receipt ([f3c70a6](f3c70a66b5))
* **share balance:** use currency field instead of int for rate and amount ([a8ed281](a8ed2815a4))
* Short circuit guest perm checks ([dab8ac7](dab8ac7b1d))
* stock ageing report ([d098572](d09857294c))
* **stock:** remove total bar in chart view ([918f8ca](918f8ca79b))
* **subcontract:** ignore BOM qty validation for alternative items (backport [#51122](https://github.com/frappe/erpnext/issues/51122)) ([#51135](https://github.com/frappe/erpnext/issues/51135)) ([2c9c6c3](2c9c6c3798))
* **trial_balance:** remove hardcoded precision for currency values ([99b69c1](99b69c121e))
* use dummy translations for custom field labels ([#49875](https://github.com/frappe/erpnext/issues/49875)) ([088bbac](088bbac543))
* validate available stock with multiple dimensions (backport [#50937](https://github.com/frappe/erpnext/issues/50937)) ([#50983](https://github.com/frappe/erpnext/issues/50983)) ([98eeff8](98eeff8775))
* validate budget after cost center allocation ([a2b6e4a](a2b6e4a1c5))

### Features

* introduce extended bank transaction fields (backport [#50021](https://github.com/frappe/erpnext/issues/50021)) ([#51112](https://github.com/frappe/erpnext/issues/51112)) ([a61890e](a61890ec2b))

### Performance Improvements

* move all hourly/daily jobs to maintenance queue (backport [#47504](https://github.com/frappe/erpnext/issues/47504)) ([#51005](https://github.com/frappe/erpnext/issues/51005)) ([46ca347](46ca347578))
* sabb validate serial no ([#51132](https://github.com/frappe/erpnext/issues/51132)) ([3a9888a](3a9888aad9))

### Reverts

* changes to install_fixtures ([19dc26e](19dc26ea16))
2025-12-16 15:33:15 +00:00
ruthra kumar
d804d43ed0 Merge pull request #51124 from frappe/version-15-hotfix
chore: release v15
2025-12-16 21:01:44 +05:30
ruthra kumar
53bb2cf7c0 fix(Rename Tool): use "Link" field instead of "Select" (backport #50995) (#51138)
fix(Rename Tool): use "Link" field instead of "Select"

(cherry picked from commit ba9bbed038)

# Conflicts:
#	erpnext/utilities/doctype/rename_tool/rename_tool.json
#	erpnext/utilities/doctype/rename_tool/rename_tool.py

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-12-16 20:40:10 +05:30
Raffael Meyer
2aff16928c fix(Rename Tool): use "Link" field instead of "Select"
(cherry picked from commit ba9bbed038)

# Conflicts:
#	erpnext/utilities/doctype/rename_tool/rename_tool.json
#	erpnext/utilities/doctype/rename_tool/rename_tool.py
2025-12-16 20:23:15 +05:30
Diptanil Saha
630dcf072f Merge pull request #51144 from frappe/mergify/bp/version-15-hotfix/pr-51120
fix: add link filters for item group in quickentry (backport #51120)
2025-12-16 18:59:24 +05:30
Mihir Kandoi
b73eb47a43 Merge pull request #51140 from frappe/mergify/bp/version-15-hotfix/pr-51137 2025-12-16 18:31:52 +05:30
Afsal Syed
981c9c76c1 fix: add link filters for item group in quickentry
(cherry picked from commit 3bef6bf5ef)
2025-12-16 12:57:51 +00:00
mergify[bot]
6a6398a392 fix: add validation for transferred qty and handle MR transfer status for in-transit entry. (backport #50683) (#51134)
fix: add validation for transferred qty and handle MR transfer status for in-transit entry. (#50683)

* fix: add validation for transferred qty

* fix: modify if statement

* test: add unit test for mr transfer status in-transit entry

(cherry picked from commit 890316a793)

Co-authored-by: Logesh Periyasamy <logeshperiyasamy24@gmail.com>
2025-12-16 18:22:15 +05:30
Sudharsanan11
0b6b73b500 fix(buying): add disabled filter for supplier
(cherry picked from commit 6cc2290f6e)

# Conflicts:
#	erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
2025-12-16 12:51:40 +00:00
Mihir Kandoi
325fc619dc fix: delayed tasks summary chart color
(cherry picked from commit 38affb0562)
2025-12-16 12:44:30 +00:00
mergify[bot]
2c9c6c3798 fix(subcontract): ignore BOM qty validation for alternative items (backport #51122) (#51135)
fix(subcontract): ignore BOM qty validation for alternative items (#51122)

(cherry picked from commit 2f19244660)

Co-authored-by: Kavin <78342682+kavin-114@users.noreply.github.com>
2025-12-16 18:11:44 +05:30
rohitwaghchaure
3a9888aad9 perf: sabb validate serial no (#51132) 2025-12-16 17:37:39 +05:30
ruthra kumar
695ca39d84 Merge pull request #51129 from frappe/mergify/bp/version-15-hotfix/pr-51048
fix(payment entry): fetch gain loss account from company boot (backport #51048)
2025-12-16 17:00:49 +05:30
ruthra kumar
9032d4c3d6 Merge pull request #51110 from one-highflyer/fix/err-preserve-exchange-rate
fix: preserve user-entered exchange rates in ERR journal entries
2025-12-16 16:47:12 +05:30
ravibharathi656
c01e40da3c fix(payment entry): fetch gain loss account from company boot
(cherry picked from commit 8e54be7808)
2025-12-16 10:58:21 +00:00
ruthra kumar
552cb5c528 Merge pull request #51127 from frappe/mergify/bp/version-15-hotfix/pr-51123
fix: ensure type on method parameter (backport #51123)
2025-12-16 15:59:49 +05:30
ruthra kumar
e6ccb00d4c Merge pull request #50539 from frappe/mergify/bp/version-15-hotfix/pr-49875
fix: use dummy translations for custom field labels (backport #49875)
2025-12-16 15:54:27 +05:30
ruthra kumar
5b481d9235 Merge pull request #51071 from frappe/mergify/bp/version-15-hotfix/pr-51041
fix(trial_balance): remove hardcoded precision for currency values (backport #51041)
2025-12-16 15:40:02 +05:30
ruthra kumar
6b27b659e3 Merge pull request #51017 from frappe/mergify/bp/version-15-hotfix/pr-50948
fix(stock): remove total bar in chart view (backport #50948)
2025-12-16 15:36:04 +05:30
ruthra kumar
16c8b74d52 fix: ensure type on method parameter
(cherry picked from commit c055e86e51)
2025-12-16 10:03:53 +00:00
Mihir Kandoi
8dcb2f39c2 Merge pull request #51088 from aerele/v15-drop-ship-retain-address 2025-12-16 13:28:02 +05:30
ravibharathi656
5d5dff9103 fix: prevent dispatch address copying on drop ship 2025-12-16 13:10:38 +05:30
ruthra kumar
b529a6d00c Merge pull request #51116 from frappe/mergify/bp/version-15-hotfix/pr-51077
refactor: standardize cost_center updation across transactions (backport #51077)
2025-12-16 11:07:51 +05:30
Navin-S-R
41659a875b refactor: standardize cost_center updation across transactions
(cherry picked from commit c28f6f1856)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
2025-12-16 11:04:41 +05:30
mergify[bot]
a61890ec2b feat: introduce extended bank transaction fields (backport #50021) (#51112)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
Co-authored-by: 0xD0M1M0 <76812428+0xD0M1M0@users.noreply.github.com>
2025-12-15 23:04:16 +01:00
Imesha Sudasingha
fa04e368d3 fix: preserve user-entered exchange rates in ERR journal entries
The JE creation was overriding exchange_rate=1 with the system rate.
Set ignore_exchange_rate flag to preserve user values.
2025-12-15 20:23:06 +05:30
Venkatesh
98eeff8775 fix: validate available stock with multiple dimensions (backport #50937) (#50983)
* fix: validate available stock with multiple dimensions

* test: validate negative stock with multiple inventory dimensions

* chore: reset document_wise_inventory_dimensions
2025-12-15 19:07:06 +05:30
Smit Vora
0452b22aa6 fix: use original logic for v15 - inverted wrt v16 2025-12-15 17:20:57 +05:30
Smit Vora
64acf179db Merge pull request #51105 from frappe/mergify/bp/version-15-hotfix/pr-50782
fix: only show net balance as opening in general ledger (backport #50782)
2025-12-15 15:50:49 +05:30
Smit Vora
0d5e45bb7c fix: only show net gl balance as opening in general ledger
(cherry picked from commit b7c7e0746e)
2025-12-15 10:00:49 +00:00
Smit Vora
edcf24afa9 chore: resolve conflicts 2025-12-15 14:59:06 +05:30
Smit Vora
e403dfe73a test: add test for projected quantity cascading across multiple sales orders
(cherry picked from commit 92fdec9b92)
2025-12-15 09:08:47 +00:00
Smit Vora
dffd5d9cdd fix: cascade projected quantity across multiple items in material requests
(cherry picked from commit d344be32a0)

# Conflicts:
#	erpnext/manufacturing/doctype/production_plan/production_plan.py
2025-12-15 09:08:47 +00:00
Mihir Kandoi
fc86784eb1 Merge pull request #51096 from frappe/mergify/bp/version-15-hotfix/pr-49139 2025-12-15 03:50:07 +05:30
Anjali Patel
e1dc80b6d8 fix: add missing query key in 'Reports To' field filter
(cherry picked from commit cbfb14a654)
2025-12-14 20:26:21 +00:00
Anjali Patel
9e8bb9b235 fix: prevent self in "Reports To" dropdown (UI-level check)
Ensures employee cannot select themselves in the "Reports To" field via UI.
This complements server-side validation by improving UX.

(cherry picked from commit 608d38a172)
2025-12-14 20:26:20 +00:00
mergify[bot]
ae90ee3f17 Merge pull request #51087 from frappe/mergify/bp/version-15-hotfix/pr-51063
fix(transaction-deletion): Add virtual doctypes to the list of ignored doctypes (backport #51063)
2025-12-14 14:30:11 +05:30
Ankush Menat
dab8ac7b1d fix: Short circuit guest perm checks 2025-12-14 12:11:55 +05:30
rohitwaghchaure
ce769d3a2f Merge pull request #51082 from frappe/mergify/bp/version-15-hotfix/pr-51079
fix: stock ageing report (backport #51079)
2025-12-13 07:39:04 +05:30
Rohit Waghchaure
d09857294c fix: stock ageing report
(cherry picked from commit cb84ffd972)
2025-12-12 15:13:41 +00:00
Frappe PR Bot
3a74968ced chore(release): Bumped to Version 15.91.3
## [15.91.3](https://github.com/frappe/erpnext/compare/v15.91.2...v15.91.3) (2025-12-12)

### Bug Fixes

* **accounts:** handle drop ship in company linked address validation ([5d01cad](5d01cad1d5))
2025-12-12 08:57:31 +00:00
rohitwaghchaure
26725f4e53 Merge pull request #51075 from frappe/mergify/bp/version-15/pr-51072
fix(accounts): handle drop ship in company linked address validation (backport #51034) (backport #51072)
2025-12-12 14:26:09 +05:30
Khushi Rawat
030ce6d6a0 Merge pull request #51074 from frappe/mergify/bp/version-15-hotfix/pr-51070
fix: validate budget after cost center allocation (backport #51070)
2025-12-12 14:15:45 +05:30
Sudharsanan11
b46d93c709 test(accounts): add validation test for dispatch address with drop ship enabled
(cherry picked from commit f6a96e5563)
(cherry picked from commit 2263f9a477)
2025-12-12 08:37:56 +00:00
Sudharsanan11
5d01cad1d5 fix(accounts): handle drop ship in company linked address validation
(cherry picked from commit 2ec119e561)
(cherry picked from commit b340d7d4f4)
2025-12-12 08:37:56 +00:00
rohitwaghchaure
97b253740b Merge pull request #51072 from frappe/mergify/bp/version-15-hotfix/pr-51034
fix(accounts): handle drop ship in company linked address validation (backport #51034)
2025-12-12 14:07:15 +05:30
rohitwaghchaure
94c3d66f2f Merge pull request #51073 from frappe/mergify/bp/version-15-hotfix/pr-51069
fix: incorrect invoice qty (backport #51069)
2025-12-12 13:58:24 +05:30
khushi8112
a2b6e4a1c5 fix: validate budget after cost center allocation
(cherry picked from commit f9be8a46fb)
2025-12-12 08:22:40 +00:00
Rohit Waghchaure
ebbecdba23 fix: incorrect invoice qty
(cherry picked from commit 96cdb7d54f)
2025-12-12 08:07:11 +00:00
Sudharsanan11
2263f9a477 test(accounts): add validation test for dispatch address with drop ship enabled
(cherry picked from commit f6a96e5563)
2025-12-12 08:05:59 +00:00
Sudharsanan11
b340d7d4f4 fix(accounts): handle drop ship in company linked address validation
(cherry picked from commit 2ec119e561)
2025-12-12 08:05:58 +00:00
Navin-S-R
99b69c121e fix(trial_balance): remove hardcoded precision for currency values
(cherry picked from commit a8af04f6fc)
2025-12-12 07:40:41 +00:00
rohitwaghchaure
860486bb34 Merge pull request #51068 from frappe/mergify/bp/version-15-hotfix/pr-51047
fix(manufacturing): get items for disassembly order (backport #51047)
2025-12-12 13:10:36 +05:30
rohitwaghchaure
1693e3ef3f chore: fix conflicts 2025-12-12 11:53:18 +05:30
Sudharsanan11
279cf6fe00 fix(manufacturing): get items for disassembly order
(cherry picked from commit 99148a2aba)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py
2025-12-12 06:15:12 +00:00
Sudharsanan11
cdc04292f2 fix(manufacturing): add validation for disassemble qty
(cherry picked from commit 86d6facab3)
2025-12-12 06:15:11 +00:00
Sagar Vora
6788b58d1c Merge pull request #51059 from frappe/mergify/bp/version-15-hotfix/pr-51057
fix: re-calculate outstanding / write-off amount during submission (backport #51057)
2025-12-11 23:26:06 +05:30
Sagar Vora
5bfdc010f3 fix: re-calculate outstanding / write-off amount during submission
(cherry picked from commit 09c9ac1b66)
2025-12-11 17:35:25 +00:00
Sagar Vora
0e7efd75cd Merge pull request #51053 from frappe/mergify/bp/version-15-hotfix/pr-51051
fix: ensure fresh `grand_total_diff` is used for each calculation (backport #51051)
2025-12-11 18:07:37 +05:30
Sagar Vora
2d198e698a fix: ensure fresh grand_total_diff is used for each calculation
(cherry picked from commit b3fdef8d19)
2025-12-11 12:35:29 +00:00
Diptanil Saha
857ab70f4e Merge pull request #51045 from frappe/mergify/bp/version-15-hotfix/pr-51037
fix(currency exchange settings): added backward compatibility for frankfurter api (backport #51037)
2025-12-11 16:28:03 +05:30
Frappe PR Bot
ca21f16db2 chore(release): Bumped to Version 15.91.2
## [15.91.2](https://github.com/frappe/erpnext/compare/v15.91.1...v15.91.2) (2025-12-11)

### Bug Fixes

* putaway rule not applying on serial nos ([23c82d4](23c82d410b))
* Serial/Batches not fetching when creating Material Transfer from Purchase Receipt ([1e0532f](1e0532f387))
2025-12-11 10:48:40 +00:00
rohitwaghchaure
5324000e2e Merge pull request #51043 from frappe/mergify/bp/version-15/pr-51036
fix: put-away rule not applying on serial nos (backport #51035) (backport #51036)
2025-12-11 16:17:10 +05:30
rohitwaghchaure
9d2055c620 Merge pull request #51042 from frappe/mergify/bp/version-15/pr-51029
fix: Serial/Batches not fetching when creating Material Transfer from Purchase Receipt (backport #51027) (backport #51029)
2025-12-11 16:16:29 +05:30
Diptanil Saha
113da4f512 chore: resolve conflict 2025-12-11 15:59:57 +05:30
diptanilsaha
8d32ba9a2e fix(currency exchange settings): added backward compatibility for frankfurter api
(cherry picked from commit 5c2bb66028)

# Conflicts:
#	erpnext/patches.txt
2025-12-11 10:26:45 +00:00
Rohit Waghchaure
23c82d410b fix: putaway rule not applying on serial nos
(cherry picked from commit 6bb0bdcdca)
(cherry picked from commit df820aece6)
2025-12-11 10:15:31 +00:00
rohitwaghchaure
580e825ec2 chore: fix conflicts
(cherry picked from commit c8565c47a2)
2025-12-11 10:15:29 +00:00
Rohit Waghchaure
1e0532f387 fix: Serial/Batches not fetching when creating Material Transfer from Purchase Receipt
(cherry picked from commit d16c50486a)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
(cherry picked from commit f3c70a66b5)
2025-12-11 10:15:29 +00:00
rohitwaghchaure
8f569d9711 Merge pull request #51036 from frappe/mergify/bp/version-15-hotfix/pr-51035
fix: put-away rule not applying on serial nos (backport #51035)
2025-12-11 15:43:51 +05:30
Rohit Waghchaure
df820aece6 fix: putaway rule not applying on serial nos
(cherry picked from commit 6bb0bdcdca)
2025-12-11 09:10:12 +00:00
rohitwaghchaure
6530cfe84b Merge pull request #51029 from frappe/mergify/bp/version-15-hotfix/pr-51027
fix: Serial/Batches not fetching when creating Material Transfer from Purchase Receipt (backport #51027)
2025-12-11 13:14:46 +05:30
rohitwaghchaure
c8565c47a2 chore: fix conflicts 2025-12-11 11:36:31 +05:30
Rohit Waghchaure
f3c70a66b5 fix: Serial/Batches not fetching when creating Material Transfer from Purchase Receipt
(cherry picked from commit d16c50486a)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
2025-12-11 05:29:24 +00:00
barredterra
115fd48bbf Merge remote-tracking branch 'upstream/version-15-hotfix' into mergify/bp/version-15-hotfix/pr-49875 2025-12-10 13:28:30 +01:00
Sudharsanan11
918f8ca79b fix(stock): remove total bar in chart view
(cherry picked from commit 198eb372e3)
2025-12-10 09:42:17 +00:00
mergify[bot]
46ca347578 perf: move all hourly/daily jobs to maintenance queue (backport #47504) (#51005)
perf: move all hourly/daily jobs to maintenance queue (#47504)

None of them need to strictly happen at 00:00 or *:00, so moving them all to maintenance queue which executes with same frequency but spaced out.

(cherry picked from commit a50251401f)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-12-10 06:19:50 +00:00
Diptanil Saha
8226502956 Merge pull request #51003 from frappe/mergify/bp/version-15-hotfix/pr-51001
fix(share balance): use currency field instead of int for rate and amount (backport #51001)
2025-12-10 10:38:42 +05:30
Mihir Kandoi
71e537b030 Merge pull request #51004 from frappe/mergify/bp/version-15-hotfix/pr-50952
fix: precision issue on job card submission (backport #50952)
2025-12-10 10:08:00 +05:30
Diptanil Saha
8fd3e8e22e chore: resolve conflict 2025-12-10 09:59:04 +05:30
Dany Robert
4ee4a57f72 fix: precision issue on job card submission
(cherry picked from commit 80730908c9)
2025-12-10 04:22:20 +00:00
diptanilsaha
a8ed2815a4 fix(share balance): use currency field instead of int for rate and amount
(cherry picked from commit 2fe5fad884)

# Conflicts:
#	erpnext/accounts/doctype/share_balance/share_balance.json
2025-12-10 04:21:07 +00:00
Frappe PR Bot
a2b676b340 chore(release): Bumped to Version 15.91.1
## [15.91.1](https://github.com/frappe/erpnext/compare/v15.91.0...v15.91.1) (2025-12-09)

### Bug Fixes

* add return status for delivery note ([ebb6296](ebb62966d3))
* Adjust asset purchase amounts based on docstatus ([a31fb2a](a31fb2ac6c))
* change is_return value in filter from Yes to 1 ([52e26b6](52e26b6da8))
* conflicts ([bd00a48](bd00a484ea))
* conflicts ([1427b4a](1427b4ac3f))
* cost center not reset ([8a3148e](8a3148eee6))
* ensure payment request button only shows for submitted invoices ([b4053ee](b4053ee0d8))
* fg qty uom in manufacture entry ([70d5726](70d57260d6))
* handle duplicate description in item-wise report ([1a278e7](1a278e7ca0))
* include return invoice discount in discount validation ([bf1c606](bf1c606610))
* incorrect condition ([d9e9f35](d9e9f35230))
* inward same serial / batches in disassembly which were used ([cfbd716](cfbd71693b))
* LCV is not changing the valuation of the repacked item ([8b22d9d](8b22d9d95e))
* missing attribute error when restoring asset ([bde209b](bde209b077))
* performance of the reposting ([8d734df](8d734df63b))
* **picklist:** calculate picked qty excluding the delivered qty ([3785ffe](3785ffe5c9))
* quality inspection showing Not Saved ([abe599a](abe599a49d))
* remove comment ([da88196](da88196a89))
* remove set_only_once from is_fixed_asset ([fd6e42e](fd6e42e15e))
* **sales invoice:** 100% additional discount gl issue with discount accounting ([bd6210a](bd6210a212))
* tds for customer and supplier in Journal Entry (backport [#49963](https://github.com/frappe/erpnext/issues/49963)) ([#50985](https://github.com/frappe/erpnext/issues/50985)) ([f2c556a](f2c556a6cc))
* untranslated string in job card ([b2f6d07](b2f6d07c25))
* variant items not fetched while making BOM for Variant Item ([176ce0d](176ce0d4d6))
2025-12-09 17:00:25 +00:00
Diptanil Saha
691db5b877 Merge pull request #50981 from frappe/version-15-hotfix 2025-12-09 22:28:48 +05:30
Diptanil Saha
7bec3d19ac Merge pull request #50977 from ljain112/fix-item-wise-sales-register
fix: handle duplicate description in item-wise report (backport #50979)
2025-12-09 21:47:16 +05:30
Diptanil Saha
3f85aa3aea Merge pull request #50997 from frappe/mergify/bp/version-15-hotfix/pr-50944
fix: include return invoice discount in discount validation (backport #50944)
2025-12-09 21:39:17 +05:30
Diptanil Saha
9ccf4900fe chore: resolve conflict 2025-12-09 20:52:33 +05:30
ravibharathi656
bf1c606610 fix: include return invoice discount in discount validation
(cherry picked from commit fab1ef5d76)

# Conflicts:
#	erpnext/controllers/taxes_and_totals.py
2025-12-09 15:18:08 +00:00
Mihir Kandoi
6c53d31f2d Merge pull request #50994 from frappe/mergify/bp/version-15-hotfix/pr-50912
fix: add return status for delivery note (backport #50912)
2025-12-09 20:07:22 +05:30
Mihir Kandoi
4de1af498b chore: resolve conflicts 2025-12-09 19:51:26 +05:30
Mihir Kandoi
c65409c348 Merge pull request #50993 from frappe/mergify/bp/version-15-hotfix/pr-50910
fix: validate picklist partial reserved qty (backport #50910)
2025-12-09 18:50:37 +05:30
Pugazhendhi Velu
422aec12cb test: add test for return status in delivery note
(cherry picked from commit 445a255a7f)
2025-12-09 13:06:52 +00:00
Pugazhendhi Velu
52e26b6da8 fix: change is_return value in filter from Yes to 1
(cherry picked from commit af212f520d)
2025-12-09 13:06:52 +00:00
Pugazhendhi Velu
ebb62966d3 fix: add return status for delivery note
(cherry picked from commit dec67eecad)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.py
2025-12-09 13:06:51 +00:00
Sudharsanan11
b05e2910d8 test(picklist): add test for reserved qty after partial delivery
(cherry picked from commit 758553b9fc)
2025-12-09 13:04:46 +00:00
Sudharsanan11
3785ffe5c9 fix(picklist): calculate picked qty excluding the delivered qty
(cherry picked from commit f5b75b27d7)
2025-12-09 13:04:45 +00:00
Diptanil Saha
a4ab198042 Merge pull request #50991 from frappe/mergify/bp/version-15-hotfix/pr-50970
fix: ensure payment request button only shows for submitted invoices (backport #50970)
2025-12-09 17:21:53 +05:30
Diptanil Saha
67c5249b38 Merge pull request #50988 from frappe/mergify/bp/version-15-hotfix/pr-50968 2025-12-09 17:18:10 +05:30
Diptanil Saha
af067d1c00 chore: resolve conflict 2025-12-09 17:17:27 +05:30
Abdeali Chharchhoda
b4053ee0d8 fix: ensure payment request button only shows for submitted invoices
(cherry picked from commit f26ee9e546)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
2025-12-09 11:42:22 +00:00
rohitwaghchaure
9f846e2636 Merge pull request #50990 from frappe/mergify/bp/version-15-hotfix/pr-50978
fix: performance of the reposting (backport #50978)
2025-12-09 16:54:21 +05:30
Rohit Waghchaure
8d734df63b fix: performance of the reposting
(cherry picked from commit 1bcfad8eb1)
2025-12-09 11:06:16 +00:00
Abdeali Chharchhoda
0998123e52 refactor: payment request status updates with bulk database operation
(cherry picked from commit 5154fa8259)
2025-12-09 10:59:11 +00:00
mergify[bot]
f2c556a6cc fix: tds for customer and supplier in Journal Entry (backport #49963) (#50985)
Co-authored-by: ljain112 <ljain112@gmail.com>
Co-authored-by: Smit Vora <smitvora203@gmail.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-12-09 16:26:36 +05:30
Khushi Rawat
b41612bea8 Merge pull request #50982 from khushi8112/missing-attribute-issue
fix: Missing attribute error
2025-12-09 15:31:08 +05:30
khushi8112
da88196a89 fix: remove comment 2025-12-09 15:13:40 +05:30
khushi8112
bde209b077 fix: missing attribute error when restoring asset 2025-12-09 15:10:16 +05:30
ljain112
1a278e7ca0 fix: handle duplicate description in item-wise report 2025-12-09 12:03:50 +05:30
rohitwaghchaure
1637cb4168 Merge pull request #50973 from frappe/mergify/bp/version-15-hotfix/pr-50972
fix: incorrect condition (backport #50972)
2025-12-08 20:27:10 +05:30
Rohit Waghchaure
d9e9f35230 fix: incorrect condition
(cherry picked from commit 264baf34f6)
2025-12-08 14:39:16 +00:00
rohitwaghchaure
cbc73148d3 Merge pull request #50969 from frappe/mergify/bp/version-15-hotfix/pr-50742
fix: inward same serial / batches in disassembly which were used (backport #50742)
2025-12-08 19:53:36 +05:30
rohitwaghchaure
60a18247e1 chore: fix conflicts 2025-12-08 19:09:52 +05:30
rohitwaghchaure
7cc0436083 chore: fix conflicts 2025-12-08 19:08:45 +05:30
rohitwaghchaure
f8eb48472e chore: fix conflicts 2025-12-08 19:07:13 +05:30
rohitwaghchaure
8074d396d0 chore: fix conflicts
Removed posting_datetime and type_of_transaction from the query.
2025-12-08 19:05:51 +05:30
Rohit Waghchaure
cfbd71693b fix: inward same serial / batches in disassembly which were used
(cherry picked from commit 95e6c72539)

# Conflicts:
#	erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2025-12-08 12:57:58 +00:00
Diptanil Saha
3a2d7d18a3 Merge pull request #50946 from frappe/mergify/bp/version-15-hotfix/pr-50931
fix(bulk transaction process): skip records creation if original records are marked 'On Hold' or 'Closed' (backport #50931)
2025-12-05 16:56:52 +05:30
Diptanil Saha
b55cefc54f Merge pull request #50945 from frappe/mergify/bp/version-15-hotfix/pr-50943
fix(sales invoice): 100% additional discount gl issue with discount accounting (backport #50943)
2025-12-05 16:48:44 +05:30
Diptanil Saha
05778bb81a chore: resolve conflict 2025-12-05 16:40:57 +05:30
Diptanil Saha
a70296e9b5 Merge pull request #50931 from diptanilsaha/gh-49357
(cherry picked from commit 31d55248e4)

# Conflicts:
#	erpnext/utilities/bulk_transaction.py
2025-12-05 11:03:16 +00:00
diptanilsaha
bd6210a212 fix(sales invoice): 100% additional discount gl issue with discount accounting
(cherry picked from commit d6bdbfe266)
2025-12-05 11:02:16 +00:00
Khushi Rawat
944c9ad0b3 Merge pull request #50924 from frappe/mergify/bp/version-15-hotfix/pr-50879
fix: remove set_only_once from is_fixed_asset field (backport #50879)
2025-12-04 13:04:50 +05:30
Khushi Rawat
bd00a484ea fix: conflicts 2025-12-04 12:48:50 +05:30
Khushi Rawat
1427b4ac3f fix: conflicts 2025-12-04 12:48:07 +05:30
ravibharathi656
fd6e42e15e fix: remove set_only_once from is_fixed_asset
(cherry picked from commit 70521fb9bf)

# Conflicts:
#	erpnext/stock/doctype/item/item.json
#	erpnext/stock/doctype/item/item.py
2025-12-04 06:44:46 +00:00
rohitwaghchaure
8b071c0d22 Merge pull request #50922 from frappe/mergify/bp/version-15-hotfix/pr-50913
fix: variant items not fetched while making BOM for Variant Item (backport #50913)
2025-12-04 11:43:57 +05:30
Rohit Waghchaure
176ce0d4d6 fix: variant items not fetched while making BOM for Variant Item
(cherry picked from commit a0256bd798)
2025-12-04 04:38:17 +00:00
rohitwaghchaure
dd888fc30a Merge pull request #50909 from frappe/mergify/bp/version-15-hotfix/pr-50905
fix: LCV is not changing the valuation of the repacked item (backport #50905)
2025-12-04 10:06:31 +05:30
Mihir Kandoi
789adaeabe Merge pull request #50908 from frappe/mergify/bp/version-15-hotfix/pr-50906
fix: untranslated string in job card (backport #50906)
2025-12-03 20:05:11 +05:30
rohitwaghchaure
2342f8d710 chore: fix conflicts
Removed test for purchase expense account and repost GL entries.
2025-12-03 18:38:40 +05:30
Rohit Waghchaure
8b22d9d95e fix: LCV is not changing the valuation of the repacked item
(cherry picked from commit ccbbc60585)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
2025-12-03 12:58:22 +00:00
rohitwaghchaure
626c799b60 Merge pull request #50907 from frappe/mergify/bp/version-15-hotfix/pr-50902
fix: fg qty uom in manufacture entry (backport #50902)
2025-12-03 18:22:15 +05:30
Mihir Kandoi
b2f6d07c25 fix: untranslated string in job card
(cherry picked from commit ec06f4a71b)
2025-12-03 12:40:35 +00:00
Mihir Kandoi
70d57260d6 fix: fg qty uom in manufacture entry
(cherry picked from commit d9a377108c)
2025-12-03 12:37:19 +00:00
Khushi Rawat
9062b90237 Merge pull request #50887 from 0xD0M1M0/patch-1
fix: Reduce asset value on asset capitalization cancelation
2025-12-03 15:01:41 +05:30
rohitwaghchaure
40467bc26c Merge pull request #50901 from frappe/mergify/bp/version-15-hotfix/pr-50896
fix: quality inspection showing Not Saved (backport #50896)
2025-12-03 14:52:51 +05:30
Rohit Waghchaure
abe599a49d fix: quality inspection showing Not Saved
(cherry picked from commit 3f78d6afed)
2025-12-03 08:08:37 +00:00
rohitwaghchaure
9975f5fe69 Merge pull request #50889 from frappe/mergify/bp/version-15-hotfix/pr-50888
fix: cost center not reset (backport #50888)
2025-12-02 22:37:08 +05:30
Frappe PR Bot
dab17c194c chore(release): Bumped to Version 15.91.0
# [15.91.0](https://github.com/frappe/erpnext/compare/v15.90.1...v15.91.0) (2025-12-02)

### Bug Fixes

* add validation for cancelled reposting entries ([085d685](085d685488))
* add validation for company linked address fields ([0aed8c0](0aed8c04c6))
* **barcode_scanner:** set serial and batch before item to prevent FIFO override ([7d7f929](7d7f929cfc))
* conflicts ([199e25e](199e25ec06))
* correct field name for subcontracted items in material request ([4b49080](4b49080bc4))
* do not override source document in serial no ([69c6b2f](69c6b2f463))
* **email campaign:** send emails using bcc ([b660b90](b660b90adc))
* **Employee:** add/delete user permission (backport [#47016](https://github.com/frappe/erpnext/issues/47016)) ([#50761](https://github.com/frappe/erpnext/issues/50761)) ([821f3f5](821f3f5884))
* enhance SalesOrderController setup method to call super.setup ([7805ccf](7805ccf176))
* exclude is_group records ([a444325](a444325bd1))
* include accounting dimensions in stock entries created during asset repair. ([26872c3](26872c3c25))
* incorrect positional param for `get_field_precision` util (backport [#50764](https://github.com/frappe/erpnext/issues/50764)) ([#50795](https://github.com/frappe/erpnext/issues/50795)) ([ff1ca9d](ff1ca9d480))
* item price not considering based on valid_upto ([dfda8e6](dfda8e6241))
* **Job Card:** avoid Type Error when completed_qty is None ([#50447](https://github.com/frappe/erpnext/issues/50447)) ([cac9eed](cac9eed306))
* label for warehouse based on material request type ([8ee7c47](8ee7c47fdf))
* mandatory depends on for the rejected inventory dimension field ([8c62080](8c620802f0))
* negative batch in subcontracting receipt ([5def006](5def006033))
* **payment reconciliation:** added a hint that posting date can be changed on exchange gain/loss reconcile dialog ([0e03607](0e0360781e))
* **payment-recon:** add validation for outstanding of dr_cr ([70feb50](70feb500f6))
* **pos:** add negative stock validation for product bundle ([46a49a1](46a49a134d))
* remove unused translation files (<100 lines) ([7f7c5f2](7f7c5f2381))
* resolve conflict ([bd795f5](bd795f5546))
* **stock entry:** use fg item expense account for direct manufacturing entry ([4ca5e9e](4ca5e9eef8))
* two primary buttons ([1d2fccf](1d2fccfc0b))
* use asset in against_voucher while posting gl entries for capitalized asset repairs ([80642ed](80642edf4f))
* use posting_date instead of bill_date from purchase invoice ([c12a560](c12a560c63))

### Features

* add stock uom read only field to stock reconciliation item doctype ([5711225](57112258e6))
2025-12-02 16:31:39 +00:00
Rohit Waghchaure
8a3148eee6 fix: cost center not reset
(cherry picked from commit 29f2ecbd6f)
2025-12-02 16:30:41 +00:00
Diptanil Saha
1f79242366 Merge pull request #50868 from frappe/version-15-hotfix 2025-12-02 22:00:12 +05:30
Diptanil Saha
3f673a6848 Merge pull request #50886 from frappe/mergify/bp/version-15-hotfix/pr-50882
fix: mandatory depends on for the rejected inventory dimension field (backport #50882)
2025-12-02 21:26:26 +05:30
Diptanil Saha
293f114c9d Merge pull request #50847 from barredterra/rm-unused-translations 2025-12-02 21:19:55 +05:30
Diptanil Saha
252cc89ec7 Merge pull request #50871 from frappe/mergify/bp/version-15-hotfix/pr-50846 2025-12-02 21:19:40 +05:30
Diptanil Saha
3eaccfe201 Merge pull request #50873 from frappe/mergify/bp/version-15-hotfix/pr-50773
fix: add validation for cancelled reposting entries (backport #50773)
2025-12-02 21:15:56 +05:30
Diptanil Saha
653bb1072f Merge pull request #50885 from frappe/mergify/bp/version-15-hotfix/pr-50864
fix: exclude is_group records (backport #50864)
2025-12-02 21:14:35 +05:30
Diptanil Saha
0a64e43e92 chore: resolve linter issue 2025-12-02 21:04:00 +05:30
diptanilsaha
020db922b7 chore: resolve conflicts 2025-12-02 20:56:54 +05:30
rohitwaghchaure
a67a11e933 Merge pull request #50884 from rohitwaghchaure/fixed-donot-override-source
fix: do not override source document in serial no
2025-12-02 20:55:22 +05:30
Rohit Waghchaure
8c620802f0 fix: mandatory depends on for the rejected inventory dimension field
(cherry picked from commit 5daa625fe8)
2025-12-02 15:10:40 +00:00
0xD0M1M0
a31fb2ac6c fix: Adjust asset purchase amounts based on docstatus
allows cancelation
2025-12-02 16:09:56 +01:00
ravibharathi656
a444325bd1 fix: exclude is_group records
(cherry picked from commit e08805128b)

# Conflicts:
#	erpnext/setup/doctype/customer_group/customer_group.json
#	erpnext/setup/doctype/item_group/item_group.json
#	erpnext/setup/doctype/supplier_group/supplier_group.json
#	erpnext/setup/doctype/territory/territory.json
2025-12-02 15:07:39 +00:00
rohitwaghchaure
2f4b1341d2 Merge pull request #50878 from frappe/mergify/bp/version-15-hotfix/pr-50808
fix(stock entry): use fg item expense account for direct manufacturing entry (backport #50808)
2025-12-02 20:37:39 +05:30
Diptanil Saha
7640944bb9 Merge pull request #50881 from frappe/mergify/bp/version-15-hotfix/pr-50372
fix: add validation for company linked address field (backport #50372)
2025-12-02 20:36:22 +05:30
Rohit Waghchaure
69c6b2f463 fix: do not override source document in serial no 2025-12-02 20:34:55 +05:30
Pugazhendhi Velu
ef6f2389a0 test: add minimal test case
(cherry picked from commit e64b6db2eb)
2025-12-02 14:45:03 +00:00
Pugazhendhi Velu
1d4b97c619 test: add test for company linked address fields
(cherry picked from commit e10007c646)
2025-12-02 14:45:02 +00:00
Pugazhendhi Velu
0aed8c04c6 fix: add validation for company linked address fields
(cherry picked from commit 800a44a65f)
2025-12-02 14:45:02 +00:00
rohitwaghchaure
b1b6953aed chore: fix conflicts 2025-12-02 17:44:17 +05:30
rohitwaghchaure
b2ea5620b2 chore: fix conflicts
Removed the expense account assignment for subcontracting delivery.
2025-12-02 17:42:18 +05:30
rohitwaghchaure
743b179b08 Merge pull request #50877 from frappe/mergify/bp/version-15-hotfix/pr-50850
fix(barcode_scanner): set serial and batch before item to prevent FIFO override (backport #50850)
2025-12-02 17:41:31 +05:30
Pugazhendhi Velu
4553d04c38 test: add test for fg item expense account in direct manufacturing
(cherry picked from commit ba2411b4ee)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py
2025-12-02 12:03:37 +00:00
Pugazhendhi Velu
4ca5e9eef8 fix(stock entry): use fg item expense account for direct manufacturing entry
(cherry picked from commit ce1312764f)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2025-12-02 12:03:37 +00:00
Pugazhendhi Velu
7d7f929cfc fix(barcode_scanner): set serial and batch before item to prevent FIFO override
(cherry picked from commit 92ec633a5c)
2025-12-02 12:02:36 +00:00
l0gesh29
085d685488 fix: add validation for cancelled reposting entries
(cherry picked from commit d8fc369e38)
2025-12-02 11:47:36 +00:00
Diptanil Saha
0458c548ec chore: resolve conflict 2025-12-02 17:14:00 +05:30
Sudharsanan11
e5457f8bb7 test(pos): add test for product bundle negative stock validation
(cherry picked from commit 2612152456)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/pos_invoice.py
2025-12-02 11:39:09 +00:00
Sudharsanan11
46a49a134d fix(pos): add negative stock validation for product bundle
(cherry picked from commit 38b4536300)
2025-12-02 11:39:08 +00:00
Khushi Rawat
25a9327b14 Merge pull request #50862 from frappe/mergify/bp/version-15-hotfix/pr-50794
fix: use asset in against_voucher while posting gl entries for capitalised asset repairs (backport #50794)
2025-12-02 12:45:20 +05:30
Mihir Kandoi
d0d38214c5 Merge pull request #50790 from Abdeali099/fix-incorrect-fieldname 2025-12-02 12:30:48 +05:30
Khushi Rawat
991c46d058 Merge pull request #50858 from frappe/mergify/bp/version-15-hotfix/pr-50793
fix: include accounting dimensions in stock entries created during asset repair. (backport #50793)
2025-12-02 12:23:10 +05:30
rohitwaghchaure
ca9bd8b499 Merge pull request #50845 from frappe/mergify/bp/version-15-hotfix/pr-50844
fix: label for warehouse based on material request type (backport #50844)
2025-12-02 12:22:18 +05:30
Khushi Rawat
199e25ec06 fix: conflicts 2025-12-02 12:20:48 +05:30
Navin-S-R
f38fb68d62 chore: reload asset doc before assertEqual
(cherry picked from commit 8c35a6ecdd)
2025-12-02 06:37:54 +00:00
Navin-S-R
3a22d29d7b test: add unit test to validate capitalized asset repair gl entries being booked against the asset
(cherry picked from commit bcf6deec9a)

# Conflicts:
#	erpnext/assets/doctype/asset_repair/test_asset_repair.py
2025-12-02 06:37:54 +00:00
Navin S R
80642edf4f fix: use asset in against_voucher while posting gl entries for capitalized asset repairs
(cherry picked from commit a7e43eddad)
2025-12-02 06:37:54 +00:00
ljain112
9a3e1058f6 refactor: show_general ledger for consistency with other doctyoes
(cherry picked from commit cdbe8b909b)
2025-12-02 06:27:21 +00:00
ljain112
26872c3c25 fix: include accounting dimensions in stock entries created during asset repair.
(cherry picked from commit 147a5ee953)
2025-12-02 06:27:21 +00:00
rohitwaghchaure
dba3f3d335 chore: fix conflicts 2025-12-02 11:04:27 +05:30
barredterra
7f7c5f2381 fix: remove unused translation files (<100 lines)
These translate <=1% of available strings, so cannot be deemed useful.
2025-12-01 17:38:13 +01:00
Rohit Waghchaure
8ee7c47fdf fix: label for warehouse based on material request type
(cherry picked from commit 699e9b4452)

# Conflicts:
#	erpnext/stock/doctype/material_request/material_request.js
2025-12-01 15:58:39 +00:00
Raffael Meyer
441a2bcf38 chore: backport translations from develop (#50842) 2025-12-01 14:50:04 +00:00
mergify[bot]
821f3f5884 fix(Employee): add/delete user permission (backport #47016) (#50761)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-12-01 12:49:37 +01:00
Diptanil Saha
ca70e8e9a6 Merge pull request #50825 from frappe/mergify/bp/version-15-hotfix/pr-50797
fix(payment-recon): add validation for outstanding of dr_cr (backport #50797)
2025-12-01 13:27:15 +05:30
l0gesh29
70feb500f6 fix(payment-recon): add validation for outstanding of dr_cr
(cherry picked from commit 765f9a9bbf)
2025-12-01 07:41:28 +00:00
rohitwaghchaure
bf8b3d0546 Merge pull request #50818 from frappe/mergify/bp/version-15-hotfix/pr-50799
fix: negative batch in subcontracting receipt (backport #50799)
2025-12-01 12:50:42 +05:30
Diptanil Saha
07d8bc7852 Merge pull request #50819 from frappe/mergify/bp/version-15-hotfix/pr-50814
fix(email campaign): send emails using bcc (backport #50814)
2025-12-01 12:30:55 +05:30
diptanilsaha
b660b90adc fix(email campaign): send emails using bcc
(cherry picked from commit 7e8d19b0c8)
2025-12-01 06:17:15 +00:00
Rohit Waghchaure
5def006033 fix: negative batch in subcontracting receipt
(cherry picked from commit 71e46b3ef5)
2025-12-01 06:15:37 +00:00
Khushi Rawat
ade6acccfb Merge pull request #50800 from frappe/mergify/bp/version-15-hotfix/pr-50772
fix: use posting_date instead of bill_date from purchase invoice (backport #50772)
2025-11-29 00:28:11 +05:30
Khushi Rawat
bd795f5546 fix: resolve conflict 2025-11-28 17:26:33 +05:30
Navin S R
c12a560c63 fix: use posting_date instead of bill_date from purchase invoice
(cherry picked from commit 145d40dec8)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py
2025-11-28 11:49:33 +00:00
mergify[bot]
ff1ca9d480 fix: incorrect positional param for get_field_precision util (backport #50764) (#50795) 2025-11-28 08:50:03 +00:00
Abdeali Chharchhoda
4b49080bc4 fix: correct field name for subcontracted items in material request 2025-11-28 12:33:38 +05:30
Diptanil Saha
64e6b36d04 Merge pull request #50789 from frappe/mergify/bp/version-15-hotfix/pr-50642
fix(payment reconciliation): added a hint that posting date can be changed on exchange gain/loss reconcile dialog (backport #50642)
2025-11-28 11:54:32 +05:30
Jatin3128
0e0360781e fix(payment reconciliation): added a hint that posting date can be changed on exchange gain/loss reconcile dialog
(cherry picked from commit 4b612c64a8)
2025-11-28 06:22:00 +00:00
Mihir Kandoi
c62be10620 Merge pull request #50778 from frappe/mergify/bp/version-15-hotfix/pr-50777 2025-11-27 17:28:07 +05:30
Mihir Kandoi
fa541a2604 chore: make unnecessary field read only and show only when required
(cherry picked from commit aab7cd1ae6)
2025-11-27 11:41:54 +00:00
Diptanil Saha
5590b8d40b Merge pull request #50558 from efeone/pos_rate_issue 2025-11-27 16:33:50 +05:30
Mihir Kandoi
a32165016d Merge pull request #50774 from mihir-kandoi/gh50218 2025-11-27 14:56:44 +05:30
Mihir Kandoi
57112258e6 feat: add stock uom read only field to stock reconciliation item doctype 2025-11-27 14:39:19 +05:30
rohitwaghchaure
0bec404e69 Merge pull request #50770 from frappe/mergify/bp/version-15-hotfix/pr-50769
fix: two primary buttons (backport #50769)
2025-11-27 12:06:10 +05:30
Rohit Waghchaure
1d2fccfc0b fix: two primary buttons
(cherry picked from commit f68515210b)
2025-11-27 06:27:09 +00:00
Raffael Meyer
cac9eed306 fix(Job Card): avoid Type Error when completed_qty is None (#50447) 2025-11-26 13:16:47 +01:00
Frappe PR Bot
2bf12a6683 chore(release): Bumped to Version 15.90.1
## [15.90.1](https://github.com/frappe/erpnext/compare/v15.90.0...v15.90.1) (2025-11-26)

### Bug Fixes

* enhance SalesOrderController setup method to call super.setup ([38c4453](38c44533b3))
2025-11-26 09:12:23 +00:00
Diptanil Saha
6205be5e73 Merge pull request #50755 from frappe/mergify/bp/version-15/pr-50754
fix: enhance SalesOrderController setup method to call super.setup (backport #50752)
2025-11-26 14:40:58 +05:30
ljain112
38c44533b3 fix: enhance SalesOrderController setup method to call super.setup
(cherry picked from commit 563c2998ca)
(cherry picked from commit 7805ccf176)
2025-11-26 09:08:27 +00:00
Diptanil Saha
a6713b176b Merge pull request #50754 from frappe/mergify/bp/version-15-hotfix/pr-50752
fix: enhance SalesOrderController setup method to call super.setup (backport #50752)
2025-11-26 14:35:21 +05:30
ljain112
7805ccf176 fix: enhance SalesOrderController setup method to call super.setup
(cherry picked from commit 563c2998ca)
2025-11-26 09:02:56 +00:00
Frappe PR Bot
a66ce02520 chore(release): Bumped to Version 15.90.0
# [15.90.0](https://github.com/frappe/erpnext/compare/v15.89.2...v15.90.0) (2025-11-25)

### Bug Fixes

* add filter company and status to job card employee ([015f946](015f946a14))
* add missing translate function ([475eada](475eada727))
* add return status for purchase receipt ([8ccb9a5](8ccb9a5ad2))
* add validation for FG Items as per BOM qty (backport [#50579](https://github.com/frappe/erpnext/issues/50579)) ([#50715](https://github.com/frappe/erpnext/issues/50715)) ([1995291](1995291194))
* apply precision for scrap items amount ([5b60fbb](5b60fbbd30))
* **customer:** link contact and addresses if created from lead/opportunity/prospect ([b1d40de](b1d40de87e))
* ignore reserved batches from total available batches ([673b893](673b893942))
* incorrect query filter when selecting primary customer adr ([#50727](https://github.com/frappe/erpnext/issues/50727)) ([e8e09cf](e8e09cf8ea))
* **ledger-summary-report:** show party group and territory ([56f03ae](56f03aee02))
* **manufacturing:** apply precision for bom amount and rm_cost_per_qty ([2678694](2678694c5f))
* pick list status doesn't update when DN created from it and PL was created from SO ([2809c46](2809c46a6e))
* prevent pi status from changing on asset repair ([3f2081b](3f2081b440))
* pricing rule was ignoring time validity ([f62e5e6](f62e5e69b8))
* **product bundle:** fields reset if doc is new ([4ba4da0](4ba4da090d))
* **purchase_receipt:** add internal_and_external_links field to show purchase invoice connection count ([89fcdbf](89fcdbf56b))
* redundant message on bom save ([5b16740](5b1674018b))
* remove disabled warehouse in get_warehouses_based_on_account ([aa94c91](aa94c91c12))
* serial batch selector shown only once ([25cd230](25cd230471))
* show current company warehouse only in get material from bom MR ([1d6e3e4](1d6e3e4e7d))
* tests ([45bc218](45bc218acb))
* unhide zero val checkbox ([a247337](a24733791d))
* unknown column error ([2e9a0cb](2e9a0cb01c))
* use current_tax_amount value for base_total_taxes_and_charges ([7ed3c6d](7ed3c6d18a))
* validate sabb autocreation when disabled ([85c0c16](85c0c16964))
* validation for SABB deletion ([0bc98b6](0bc98b609f))

### Features

* **accounting-dimension:** add dynamic triggers for custom accounting dimensions ([#50621](https://github.com/frappe/erpnext/issues/50621)) ([2b7d586](2b7d58602d))
* modify accounting dimension as multiselect field ([6b6e017](6b6e017e36))
* **reports:** preserve accounting dimension filters while navigating between reports ([02a1f81](02a1f815da))
2025-11-25 15:02:30 +00:00
ruthra kumar
3c43b42a01 Merge pull request #50741 from frappe/version-15-hotfix
chore: release v15
2025-11-25 20:31:01 +05:30
ruthra kumar
8ed3a0ec65 Merge branch 'version-15' into version-15-hotfix 2025-11-25 20:14:37 +05:30
mergify[bot]
488d635dc9 chore: switched frankfurter domain from frankfurter.app to frankfurter.dev (backport #50734) (#50740)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2025-11-25 15:49:53 +05:30
ruthra kumar
08d230b3e3 Merge pull request #50731 from frappe/mergify/bp/version-15-hotfix/pr-50561
fix(ledger-summary-report): show party group and territory (backport #50561)
2025-11-25 12:33:49 +05:30
Mihir Kandoi
f383fafb15 Merge pull request #50728 from frappe/mergify/bp/version-15-hotfix/pr-50727
fix: incorrect query filter when selecting primary customer adr (backport #50727)
2025-11-25 12:14:17 +05:30
ruthra kumar
38124a7616 chore: resolve conflicts 2025-11-25 12:01:37 +05:30
l0gesh29
56cf5382f0 test: add party_group, territory in json
(cherry picked from commit 8f91919933)

# Conflicts:
#	erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py
2025-11-25 05:38:57 +00:00
l0gesh29
56f03aee02 fix(ledger-summary-report): show party group and territory
(cherry picked from commit 231479a6e2)

# Conflicts:
#	erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
2025-11-25 05:38:57 +00:00
Mihir Kandoi
841f5c24ad chore: resolve conflicts 2025-11-25 10:51:39 +05:30
ruthra kumar
e8051ba180 Merge pull request #50729 from frappe/mergify/bp/version-15-hotfix/pr-50621
feat(accounting-dimension): add dynamic triggers for custom accounting dimensions (backport #50621)
2025-11-25 10:26:47 +05:30
Logesh Periyasamy
2b7d58602d feat(accounting-dimension): add dynamic triggers for custom accounting dimensions (#50621)
* feat: add dynamic triggers for custom accounting dimensions

* feat: add accounting dimension trigger call in setup event

* chore: ignore cur_frm semgrep rules

* chore: move function to transaction.js

(cherry picked from commit 5e58e344b2)
2025-11-25 04:51:32 +00:00
Mihir Kandoi
e8e09cf8ea fix: incorrect query filter when selecting primary customer adr (#50727)
(cherry picked from commit c2b8b97d7d)

# Conflicts:
#	erpnext/selling/doctype/customer/customer.json
2025-11-25 04:49:20 +00:00
Khushi Rawat
188c633d6e Merge pull request #50544 from aerele/asset-repair-pi-status
fix: prevent pi status from changing on asset repair
2025-11-24 23:28:33 +05:30
mergify[bot]
1995291194 fix: add validation for FG Items as per BOM qty (backport #50579) (#50715)
Co-authored-by: Kavin <78342682+kavin0411@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
Co-authored-by: Kavin <78342682+kavin-114@users.noreply.github.com>
fix: add validation for FG Items as per BOM qty (#50579)
2025-11-24 11:18:18 +00:00
Mihir Kandoi
bb0d75eb78 Merge pull request #50717 from frappe/mergify/bp/version-15-hotfix/pr-50716
fix: add missing translate function (backport #50716)
2025-11-24 13:53:48 +05:30
El-Shafei H.
475eada727 fix: add missing translate function
(cherry picked from commit 56def01240)
2025-11-24 07:39:09 +00:00
mergify[bot]
6cffba9a71 Merge pull request #50713 from frappe/mergify/bp/version-15-hotfix/pr-50712 2025-11-24 07:17:43 +00:00
Mihir Kandoi
35f9f9f330 Merge pull request #50711 from frappe/mergify/bp/version-15-hotfix/pr-50661
fix(manufacturing): apply precision for bom amount and rm_cost_per_qty (backport #50661)
2025-11-24 11:02:50 +05:30
Pugazhendhi Velu
5b60fbbd30 fix: apply precision for scrap items amount
(cherry picked from commit 9194e6350a)
2025-11-24 05:08:42 +00:00
Pugazhendhi Velu
2678694c5f fix(manufacturing): apply precision for bom amount and rm_cost_per_qty
(cherry picked from commit 57f9353d90)
2025-11-24 05:08:42 +00:00
Mihir Kandoi
b6a80da457 Merge pull request #50709 from frappe/mergify/bp/version-15-hotfix/pr-50707
fix: unknown column error (backport #50707)
2025-11-23 19:54:21 +05:30
Mihir Kandoi
2e9a0cb01c fix: unknown column error
(cherry picked from commit 3b7d7aed4c)
2025-11-23 14:08:48 +00:00
Frappe PR Bot
38c1867ade chore(release): Bumped to Version 15.89.2
## [15.89.2](https://github.com/frappe/erpnext/compare/v15.89.1...v15.89.2) (2025-11-21)

### Bug Fixes

* use current_tax_amount value for base_total_taxes_and_charges ([c082eda](c082edabf4))
2025-11-21 17:34:52 +00:00
Diptanil Saha
a48b999af9 Merge pull request #50691 from frappe/mergify/bp/version-15/pr-50690
fix: use current_tax_amount value for base_total_taxes_and_charges (backport #50476) (backport #50690)
2025-11-21 23:03:28 +05:30
Pugazhendhi Velu
c082edabf4 fix: use current_tax_amount value for base_total_taxes_and_charges
(cherry picked from commit 5a3fcbedb5)
(cherry picked from commit 7ed3c6d18a)
2025-11-21 17:16:54 +00:00
Diptanil Saha
60ec7d0fb8 Merge pull request #50690 from frappe/mergify/bp/version-15-hotfix/pr-50476
fix: use current_tax_amount value for base_total_taxes_and_charges (backport #50476)
2025-11-21 22:31:13 +05:30
Pugazhendhi Velu
7ed3c6d18a fix: use current_tax_amount value for base_total_taxes_and_charges
(cherry picked from commit 5a3fcbedb5)
2025-11-21 16:41:52 +00:00
Mihir Kandoi
0a67d20ff8 Merge pull request #50671 from frappe/mergify/bp/version-15-hotfix/pr-50667
fix: pricing rule was ignoring time validity (backport #50667)
2025-11-21 13:08:25 +05:30
Mihir Kandoi
83c6d861eb Merge pull request #50670 from frappe/mergify/bp/version-15-hotfix/pr-50655
fix: pick list status doesn't update when DN created from it and PL w... (backport #50655)
2025-11-21 13:00:31 +05:30
Mihir Kandoi
f62e5e69b8 fix: pricing rule was ignoring time validity
(cherry picked from commit ffae7c4175)
2025-11-21 07:22:07 +00:00
Mihir Kandoi
45bc218acb fix: tests
(cherry picked from commit d26f8aa629)
2025-11-21 07:14:00 +00:00
Mihir Kandoi
2809c46a6e fix: pick list status doesn't update when DN created from it and PL was created from SO
(cherry picked from commit f7b3253683)
2025-11-21 07:14:00 +00:00
Diptanil Saha
ff1b83025a Merge pull request #50666 from frappe/mergify/bp/version-15-hotfix/pr-50665
fix(customer): link contact and addresses if created from lead/opportunity/prospect (backport #50665)
2025-11-21 07:03:57 +05:30
diptanilsaha
b1d40de87e fix(customer): link contact and addresses if created from lead/opportunity/prospect
(cherry picked from commit 310099f4cd)
2025-11-21 01:18:18 +00:00
barredterra
2c13c4746b Merge remote-tracking branch 'upstream/version-15-hotfix' into mergify/bp/version-15-hotfix/pr-49875 2025-11-21 00:46:43 +01:00
Mihir Kandoi
532031c21d Merge pull request #50650 from frappe/mergify/bp/version-15-hotfix/pr-50385
fix: remove disabled warehouse in get_warehouses_based_on_account (backport #50385)
2025-11-20 17:44:02 +05:30
Mihir Kandoi
cb70efb8ed Merge pull request #50653 from frappe/mergify/bp/version-15-hotfix/pr-50639
fix(product bundle): fields reset if doc is new (backport #50639)
2025-11-20 17:43:43 +05:30
Mihir Kandoi
ca0b4696ba Merge pull request #50652 from frappe/mergify/bp/version-15-hotfix/pr-50649
fix: unhide zero val checkbox in stock reco (backport #50649)
2025-11-20 17:43:26 +05:30
Mihir Kandoi
4ba4da090d fix(product bundle): fields reset if doc is new
(cherry picked from commit 7faee7edc2)
2025-11-20 10:42:53 +00:00
rohitwaghchaure
e3b2cc24b2 chore: fix conflicts 2025-11-20 16:12:41 +05:30
Mihir Kandoi
a24733791d fix: unhide zero val checkbox
(cherry picked from commit 20e0313a8c)

# Conflicts:
#	erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
2025-11-20 10:41:55 +00:00
Mihir Kandoi
aa94c91c12 fix: remove disabled warehouse in get_warehouses_based_on_account
(cherry picked from commit ff2d9bf4cb)
2025-11-20 10:33:54 +00:00
Mihir Kandoi
87ffbdf129 Merge pull request #50641 from frappe/mergify/bp/version-15-hotfix/pr-50502
fix(purchase_receipt): add internal_and_external_links field to show … (backport #50502)
2025-11-20 15:45:43 +05:30
rohitwaghchaure
5ef7b8c526 Merge pull request #50648 from frappe/mergify/bp/version-15-hotfix/pr-50646
fix: serial batch selector shown only once (backport #50646)
2025-11-20 15:12:51 +05:30
Mihir Kandoi
25cd230471 fix: serial batch selector shown only once
(cherry picked from commit aa6f09e9a9)
2025-11-20 09:38:37 +00:00
rohitwaghchaure
f05933e814 Merge pull request #50645 from frappe/mergify/bp/version-15-hotfix/pr-50644
fix: validation for SABB deletion (backport #50644)
2025-11-20 14:33:05 +05:30
Diptanil Saha
8df2612694 Merge pull request #50643 from frappe/mergify/bp/version-15-hotfix/pr-50289 2025-11-20 13:58:58 +05:30
Rohit Waghchaure
0bc98b609f fix: validation for SABB deletion
(cherry picked from commit dd4bef0706)
2025-11-20 08:19:48 +00:00
l0gesh29
02a1f815da feat(reports): preserve accounting dimension filters while navigating between reports
(cherry picked from commit fcfcaa76c6)
2025-11-20 07:46:55 +00:00
l0gesh29
6b6e017e36 feat: modify accounting dimension as multiselect field
(cherry picked from commit 3fcd8d84ac)
2025-11-20 07:46:55 +00:00
Karuppasamy B
89fcdbf56b fix(purchase_receipt): add internal_and_external_links field to show purchase invoice connection count
(cherry picked from commit 6c1620ab8c)
2025-11-20 07:03:55 +00:00
rohitwaghchaure
eb2571492f Merge pull request #50612 from aerele/fix/validate-reserved-stock
fix: ignore reserved batches from total available batches
2025-11-19 22:16:00 +05:30
Kavin
55f2f1c515 test: add unit test for reserved stock validation 2025-11-19 17:51:10 +05:30
Kavin
673b893942 fix: ignore reserved batches from total available batches 2025-11-19 17:51:10 +05:30
mergify[bot]
c85ce55f27 Merge pull request #50631 from frappe/mergify/bp/version-15-hotfix/pr-50629
fix: process loss % can be negative (backport #50629)
2025-11-19 11:30:39 +00:00
Mihir Kandoi
a19252e3b3 Merge pull request #50628 from frappe/mergify/bp/version-15-hotfix/pr-50627
fix: show current company warehouse only in get material from bom MR (backport #50627)
2025-11-19 16:29:17 +05:30
Mihir Kandoi
7ece6fd558 Merge pull request #50626 from frappe/mergify/bp/version-15-hotfix/pr-50625
fix: add filter company and status to job card employee (backport #50625)
2025-11-19 16:24:08 +05:30
Mihir Kandoi
1d6e3e4e7d fix: show current company warehouse only in get material from bom MR
(cherry picked from commit 3271eaaf0e)
2025-11-19 10:46:47 +00:00
Mihir Kandoi
015f946a14 fix: add filter company and status to job card employee
(cherry picked from commit 3ca3a6d9bb)
2025-11-19 10:42:32 +00:00
rohitwaghchaure
533a2dbc32 Merge pull request #50607 from frappe/mergify/bp/version-15-hotfix/pr-50512
fix: add return status for purchase receipt (backport #50512)
2025-11-19 14:18:12 +05:30
rohitwaghchaure
99aeb8ecd1 Merge pull request #50606 from frappe/mergify/bp/version-15-hotfix/pr-50486
fix: validate sabb autocreation when disabled (backport #50486)
2025-11-19 14:18:00 +05:30
Mihir Kandoi
371030f8d4 Merge pull request #50615 from frappe/mergify/bp/version-15-hotfix/pr-50614
fix: redundant message on bom save (backport #50614)
2025-11-19 12:53:50 +05:30
Mihir Kandoi
2550b44db8 chore: resolve conflicts 2025-11-19 12:38:47 +05:30
Mihir Kandoi
5b1674018b fix: redundant message on bom save
(cherry picked from commit 074f07694f)

# Conflicts:
#	erpnext/manufacturing/doctype/bom/bom.py
2025-11-19 07:06:11 +00:00
rohitwaghchaure
876dec5077 chore: fix conflicts 2025-11-18 18:05:08 +05:30
rohitwaghchaure
36e9aae9d0 chore: fix conflicts 2025-11-18 18:04:29 +05:30
Pugazhendhi Velu
8ccb9a5ad2 fix: add return status for purchase receipt
(cherry picked from commit 3a0e1e8ef9)
2025-11-18 12:33:18 +00:00
Kavin
85c0c16964 fix: validate sabb autocreation when disabled
(cherry picked from commit 3ca1940881)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
2025-11-18 12:29:01 +00:00
ravibharathi656
3f2081b440 fix: prevent pi status from changing on asset repair 2025-11-18 11:09:25 +05:30
Sherin KR
dfda8e6241 fix: item price not considering based on valid_upto 2025-11-17 14:34:26 +05:30
barredterra
ec3a226a83 fix: mark navbar item as translatable 2025-11-15 19:55:59 +01:00
barredterra
19dc26ea16 revert: changes to install_fixtures
I think this would be too breaking. Custom apps might expect the translated data to exist.
2025-11-15 19:51:05 +01:00
barredterra
e29a384f90 chore: resolve conflicts 2025-11-15 19:39:40 +01:00
Raffael Meyer
088bbac543 fix: use dummy translations for custom field labels (#49875)
(cherry picked from commit 9a989a84fb)

# Conflicts:
#	erpnext/setup/install.py
#	erpnext/setup/setup_wizard/operations/install_fixtures.py
2025-11-15 18:34:57 +00:00
313 changed files with 41188 additions and 2767 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

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.89.1"
__version__ = "15.98.0"
def get_default_company(user=None):

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": {

View File

@@ -93,6 +93,7 @@
"receivable_payable_remarks_length",
"accounts_receivable_payable_tuning_section",
"receivable_payable_fetch_method",
"default_ageing_range",
"column_break_ntmi",
"drop_ar_procedures",
"legacy_section",
@@ -306,7 +307,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"
@@ -657,6 +658,12 @@
"fieldname": "show_party_balance",
"fieldtype": "Check",
"label": "Show Party Balance"
},
{
"default": "30, 60, 90, 120",
"fieldname": "default_ageing_range",
"fieldtype": "Data",
"label": "Default Ageing Range"
}
],
"icon": "icon-cog",
@@ -664,7 +671,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-11-06 17:48:07.682837",
"modified": "2025-12-26 19:46:55.093717",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -694,4 +701,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -41,6 +41,7 @@ class AccountsSettings(Document):
check_supplier_invoice_uniqueness: DF.Check
create_pr_in_draft_status: DF.Check
credit_controller: DF.Link | None
default_ageing_range: DF.Data | None
delete_linked_ledger_entries: DF.Check
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
enable_common_party_accounting: DF.Check

View File

@@ -48,6 +48,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

@@ -52,31 +52,35 @@ 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 manadatory for company account"))
if not self.account:
frappe.throw(_("Company Account is mandatory"))
self.validate_account()
@deprecated
def validate_iban(self):
"""Kept for backward compatibility, will be removed in v16."""
validate_iban(self.iban, throw=True)
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:
frappe.db.set_value(

View File

@@ -304,6 +304,7 @@ def create_payment_entry_bts(
project=None,
cost_center=None,
allow_edit=None,
company_bank_account=None,
):
# Create a new payment entry based on the bank transaction
bank_transaction = frappe.db.get_values(
@@ -345,6 +346,9 @@ def create_payment_entry_bts(
pe.project = project
pe.cost_center = cost_center
if company_bank_account:
pe.bank_account = company_bank_account
pe.validate()
if allow_edit:

View File

@@ -38,7 +38,10 @@
"column_break_3czf",
"bank_party_name",
"bank_party_account_number",
"bank_party_iban"
"bank_party_iban",
"extended_bank_statement_section",
"included_fee",
"excluded_fee"
],
"fields": [
{
@@ -233,12 +236,32 @@
{
"fieldname": "column_break_oufv",
"fieldtype": "Column Break"
},
{
"fieldname": "extended_bank_statement_section",
"fieldtype": "Section Break",
"label": "Extended Bank Statement"
},
{
"fieldname": "included_fee",
"fieldtype": "Currency",
"label": "Included Fee",
"non_negative": 1,
"options": "currency"
},
{
"description": "On save, the Excluded Fee will be converted to an Included Fee.",
"fieldname": "excluded_fee",
"fieldtype": "Currency",
"label": "Excluded Fee",
"non_negative": 1,
"options": "currency"
}
],
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-10-23 17:32:58.514807",
"modified": "2025-12-07 20:49:18.600757",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@@ -32,6 +32,8 @@ class BankTransaction(Document):
date: DF.Date | None
deposit: DF.Currency
description: DF.SmallText | None
excluded_fee: DF.Currency
included_fee: DF.Currency
naming_series: DF.Literal["ACC-BTN-.YYYY.-"]
party: DF.DynamicLink | None
party_type: DF.Link | None
@@ -45,9 +47,11 @@ class BankTransaction(Document):
# end: auto-generated types
def before_validate(self):
self.handle_excluded_fee()
self.update_allocated_amount()
def validate(self):
self.validate_included_fee()
self.validate_duplicate_references()
self.validate_currency()
@@ -307,6 +311,40 @@ class BankTransaction(Document):
self.party_type, self.party = result
def validate_included_fee(self):
"""
The included_fee is only handled for withdrawals. An included_fee for a deposit, is not credited to the account and is
therefore outside of the deposit value and can be larger than the deposit itself.
"""
if self.included_fee and self.withdrawal:
if self.included_fee > self.withdrawal:
frappe.throw(_("Included fee is bigger than the withdrawal itself."))
def handle_excluded_fee(self):
# Include the excluded fee on validate to handle all further processing the same
excluded_fee = flt(self.excluded_fee)
if excluded_fee <= 0:
return
# Suppress a negative deposit (aka withdrawal), likely not intendend
if flt(self.deposit) > 0 and (flt(self.deposit) - excluded_fee) < 0:
frappe.throw(_("The Excluded Fee is bigger than the Deposit it is deducted from."))
# Enforce directionality
if flt(self.deposit) > 0 and flt(self.withdrawal) > 0:
frappe.throw(
_("Only one of Deposit or Withdrawal should be non-zero when applying an Excluded Fee.")
)
if flt(self.deposit) > 0:
self.deposit = flt(self.deposit) - excluded_fee
# A fee applied to deposit and withdrawal equal 0 become a withdrawal
elif flt(self.withdrawal) >= 0:
self.withdrawal = flt(self.withdrawal) + excluded_fee
self.included_fee = flt(self.included_fee) + excluded_fee
self.excluded_fee = 0
@frappe.whitelist()
def get_doctypes_for_bank_reconciliation():

View File

@@ -0,0 +1,133 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
class TestBankTransactionFees(FrappeTestCase):
def test_included_fee_throws(self):
"""A fee that's part of a withdrawal cannot be bigger than the
withdrawal itself."""
bt = frappe.new_doc("Bank Transaction")
bt.withdrawal = 100
bt.included_fee = 101
self.assertRaises(frappe.ValidationError, bt.validate_included_fee)
def test_included_fee_allows_equal(self):
"""A fee that's part of a withdrawal may be equal to the withdrawal
amount (only the fee was deducted from the account)."""
bt = frappe.new_doc("Bank Transaction")
bt.withdrawal = 100
bt.included_fee = 100
bt.validate_included_fee()
def test_included_fee_allows_for_deposit(self):
"""For deposits, a fee may be recorded separately without limiting the
received amount."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 10
bt.included_fee = 999
bt.validate_included_fee()
def test_excluded_fee_noop_when_zero(self):
"""When there is no excluded fee to apply, the amounts should remain
unchanged."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 100
bt.withdrawal = 0
bt.included_fee = 5
bt.excluded_fee = 0
bt.handle_excluded_fee()
self.assertEqual(bt.deposit, 100)
self.assertEqual(bt.withdrawal, 0)
self.assertEqual(bt.included_fee, 5)
self.assertEqual(bt.excluded_fee, 0)
def test_excluded_fee_throws_when_exceeds_deposit(self):
"""A fee deducted from an incoming payment must not exceed the incoming
amount (else it would be a withdrawal, a conversion we don't support)."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 10
bt.excluded_fee = 11
self.assertRaises(frappe.ValidationError, bt.handle_excluded_fee)
def test_excluded_fee_throws_when_both_deposit_and_withdrawal_are_set(self):
"""A transaction must be either incoming or outgoing when applying a
fee, not both."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 10
bt.withdrawal = 10
bt.excluded_fee = 1
self.assertRaises(frappe.ValidationError, bt.handle_excluded_fee)
def test_excluded_fee_deducts_from_deposit(self):
"""When a fee is deducted from an incoming payment, the net received
amount decreases and the fee is tracked as included."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 100
bt.withdrawal = 0
bt.included_fee = 2
bt.excluded_fee = 5
bt.handle_excluded_fee()
self.assertEqual(bt.deposit, 95)
self.assertEqual(bt.withdrawal, 0)
self.assertEqual(bt.included_fee, 7)
self.assertEqual(bt.excluded_fee, 0)
def test_excluded_fee_can_reduce_an_incoming_payment_to_zero(self):
"""A separately-deducted fee may reduce an incoming payment to zero,
while still tracking the fee."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 5
bt.withdrawal = 0
bt.included_fee = 0
bt.excluded_fee = 5
bt.handle_excluded_fee()
self.assertEqual(bt.deposit, 0)
self.assertEqual(bt.withdrawal, 0)
self.assertEqual(bt.included_fee, 5)
self.assertEqual(bt.excluded_fee, 0)
def test_excluded_fee_increases_outgoing_payment(self):
"""When a separately-deducted fee is provided for an outgoing payment,
the total money leaving increases and the fee is tracked."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 0
bt.withdrawal = 100
bt.included_fee = 2
bt.excluded_fee = 5
bt.handle_excluded_fee()
self.assertEqual(bt.deposit, 0)
self.assertEqual(bt.withdrawal, 105)
self.assertEqual(bt.included_fee, 7)
self.assertEqual(bt.excluded_fee, 0)
def test_excluded_fee_turns_zero_amount_into_withdrawal(self):
"""If only an excluded fee is provided, it should be treated as an
outgoing payment and the fee is then tracked as included."""
bt = frappe.new_doc("Bank Transaction")
bt.deposit = 0
bt.withdrawal = 0
bt.included_fee = 0
bt.excluded_fee = 5
bt.handle_excluded_fee()
self.assertEqual(bt.deposit, 0)
self.assertEqual(bt.withdrawal, 5)
self.assertEqual(bt.included_fee, 5)
self.assertEqual(bt.excluded_fee, 0)

View File

@@ -19,7 +19,7 @@ frappe.ui.form.on("Currency Exchange Settings", {
to: "{to_currency}",
};
add_param(frm, r.message, params, result);
} else if (frm.doc.service_provider == "frankfurter.app") {
} else if (["frankfurter.app", "frankfurter.dev"].includes(frm.doc.service_provider)) {
let result = ["rates", "{to_currency}"];
let params = {
base: "{from_currency}",

View File

@@ -78,7 +78,7 @@
"fieldname": "service_provider",
"fieldtype": "Select",
"label": "Service Provider",
"options": "frankfurter.app\nexchangerate.host\nCustom",
"options": "frankfurter.dev\nexchangerate.host\nCustom",
"reqd": 1
},
{
@@ -104,7 +104,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-18 08:32:26.895076",
"modified": "2025-11-25 13:03:41.896424",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",
@@ -141,8 +141,9 @@
"write": 1
}
],
"sort_field": "modified",
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -29,7 +29,7 @@ class CurrencyExchangeSettings(Document):
disabled: DF.Check
req_params: DF.Table[CurrencyExchangeSettingsDetails]
result_key: DF.Table[CurrencyExchangeSettingsResult]
service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"]
service_provider: DF.Literal["frankfurter.dev", "exchangerate.host", "Custom"]
url: DF.Data | None
use_http: DF.Check
# end: auto-generated types
@@ -60,7 +60,7 @@ class CurrencyExchangeSettings(Document):
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
self.append("req_params", {"key": "from", "value": "{from_currency}"})
self.append("req_params", {"key": "to", "value": "{to_currency}"})
elif self.service_provider == "frankfurter.app":
elif self.service_provider in ("frankfurter.dev", "frankfurter.app"):
self.set("result_key", [])
self.set("req_params", [])
@@ -105,11 +105,13 @@ class CurrencyExchangeSettings(Document):
@frappe.whitelist()
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
if service_provider and service_provider in ["exchangerate.host", "frankfurter.dev", "frankfurter.app"]:
if service_provider == "exchangerate.host":
api = "api.exchangerate.host/convert"
elif service_provider == "frankfurter.app":
api = "api.frankfurter.app/{transaction_date}"
elif service_provider == "frankfurter.dev":
api = "api.frankfurter.dev/v1/{transaction_date}"
protocol = "https://"
if use_http:

View File

@@ -252,7 +252,7 @@ class ExchangeRateRevaluation(Document):
company_currency = erpnext.get_company_currency(company)
precision = get_field_precision(
frappe.get_meta("Exchange Rate Revaluation Account").get_field("new_balance_in_base_currency"),
company_currency,
currency=company_currency,
)
if account_details:
@@ -486,6 +486,9 @@ class ExchangeRateRevaluation(Document):
journal_entry.posting_date = self.posting_date
journal_entry.multi_currency = 1
# Prevent JE from overriding user-entered exchange rates (e.g., rate of 1)
journal_entry.flags.ignore_exchange_rate = True
journal_entry_accounts = []
for d in accounts:
if not flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")):

View File

@@ -187,7 +187,6 @@ class GLEntry(Document):
account_type == "Profit and Loss"
and self.company == dimension.company
and dimension.mandatory_for_pl
and not dimension.disabled
and not self.is_cancelled
):
if not self.get(dimension.fieldname):
@@ -201,7 +200,6 @@ class GLEntry(Document):
account_type == "Balance Sheet"
and self.company == dimension.company
and dimension.mandatory_for_bs
and not dimension.disabled
and not self.is_cancelled
):
if not self.get(dimension.fieldname):
@@ -420,7 +418,7 @@ def update_against_account(voucher_type, voucher_no):
if not entries:
return
company_currency = erpnext.get_company_currency(entries[0].company)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), currency=company_currency)
accounts_debited, accounts_credited = [], []
for d in entries:

View File

@@ -20,6 +20,23 @@ frappe.ui.form.on("Journal Entry", {
"Unreconcile Payment Entries",
"Bank Transaction",
];
frm.trigger("set_queries");
},
set_queries(frm) {
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,
};
});
},
refresh: function (frm) {

View File

@@ -6,6 +6,7 @@ import json
import frappe
from frappe import _, msgprint, scrub
from frappe.core.doctype.submission_queue.submission_queue import queue_submission
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
import erpnext
@@ -33,6 +34,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
get_depr_schedule,
)
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.setup.utils import get_exchange_rate as _get_exchange_rate
class StockAccountInvalidTransaction(frappe.ValidationError):
@@ -170,16 +172,17 @@ class JournalEntry(AccountsController):
validate_docs_for_deferred_accounting([self.name], [])
def submit(self):
if len(self.accounts) > 100:
msgprint(_("The task has been enqueued as a background job."), alert=True)
self.queue_action("submit", timeout=4600)
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:
msgprint(_("The task has been enqueued as a background job."), alert=True)
self.queue_action("cancel", timeout=4600)
queue_submission(self, "_cancel")
else:
return self._cancel()
@@ -273,93 +276,7 @@ class JournalEntry(AccountsController):
)
def apply_tax_withholding(self):
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map
if not self.apply_tds or self.voucher_type not in ("Debit Note", "Credit Note"):
return
parties = [d.party for d in self.get("accounts") if d.party]
parties = list(set(parties))
if len(parties) > 1:
frappe.throw(_("Cannot apply TDS against multiple parties in one entry"))
account_type_map = get_account_type_map(self.company)
party_type = "supplier" if self.voucher_type == "Credit Note" else "customer"
doctype = "Purchase Invoice" if self.voucher_type == "Credit Note" else "Sales Invoice"
debit_or_credit = (
"debit_in_account_currency"
if self.voucher_type == "Credit Note"
else "credit_in_account_currency"
)
rev_debit_or_credit = (
"credit_in_account_currency"
if debit_or_credit == "debit_in_account_currency"
else "debit_in_account_currency"
)
party_account = get_party_account(party_type.title(), parties[0], self.company)
net_total = sum(
d.get(debit_or_credit)
for d in self.get("accounts")
if account_type_map.get(d.account) not in ("Tax", "Chargeable")
)
party_amount = sum(
d.get(rev_debit_or_credit) for d in self.get("accounts") if d.account == party_account
)
inv = frappe._dict(
{
party_type: parties[0],
"doctype": doctype,
"company": self.company,
"posting_date": self.posting_date,
"net_total": net_total,
}
)
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
inv, self.tax_withholding_category
)
if not tax_withholding_details:
return
accounts = []
for d in self.get("accounts"):
if d.get("account") == tax_withholding_details.get("account_head"):
d.update(
{
"account": tax_withholding_details.get("account_head"),
debit_or_credit: tax_withholding_details.get("tax_amount"),
}
)
accounts.append(d.get("account"))
if d.get("account") == party_account:
d.update({rev_debit_or_credit: party_amount - tax_withholding_details.get("tax_amount")})
if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append(
"accounts",
{
"account": tax_withholding_details.get("account_head"),
rev_debit_or_credit: tax_withholding_details.get("tax_amount"),
"against_account": parties[0],
},
)
to_remove = [
d
for d in self.get("accounts")
if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")
]
for d in to_remove:
self.remove(d)
JournalEntryTaxWithholding(self).apply()
def update_asset_value(self):
if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry":
@@ -533,12 +450,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"):
@@ -1281,6 +1213,230 @@ class JournalEntry(AccountsController):
frappe.throw(_("Accounts table cannot be blank."))
class JournalEntryTaxWithholding:
def __init__(self, journal_entry):
self.doc: JournalEntry = journal_entry
self.party = None
self.party_type = None
self.party_account = None
self.party_row = None
self.existing_tds_rows = []
self.precision = None
self.has_multiple_parties = False
# Direction fields based on party type
self.party_field = None # "credit" for Supplier, "debit" for Customer
self.reverse_field = None # opposite of party_field
def apply(self):
if not self._set_party_info():
return
self._setup_direction_fields()
self._reset_existing_tds()
if not self._should_apply_tds():
self._cleanup_duplicate_tds_rows(None)
return
if self.has_multiple_parties:
frappe.throw(_("Cannot apply TDS against multiple parties in one entry"))
net_total = self._calculate_net_total()
if net_total <= 0:
return
tds_details = self._get_tds_details(net_total)
if not tds_details or not tds_details.get("tax_amount"):
return
self._create_or_update_tds_row(tds_details)
self._update_party_amount(tds_details.get("tax_amount"), is_reversal=False)
self._recalculate_totals()
def _should_apply_tds(self):
return self.doc.apply_tds and self.doc.voucher_type in ("Debit Note", "Credit Note")
def _set_party_info(self):
for row in self.doc.get("accounts"):
if row.party_type in ("Customer", "Supplier") and row.party:
if self.party and row.party != self.party:
self.has_multiple_parties = True
if not self.party:
self.party = row.party
self.party_type = row.party_type
self.party_account = row.account
self.party_row = row
if row.get("is_tax_withholding_account"):
self.existing_tds_rows.append(row)
return bool(self.party)
def _setup_direction_fields(self):
"""
For Supplier (TDS): party has credit, TDS reduces credit
For Customer (TCS): party has debit, TCS increases debit
"""
if self.party_type == "Supplier":
self.party_field = "credit"
self.reverse_field = "debit"
else: # Customer
self.party_field = "debit"
self.reverse_field = "credit"
self.precision = self.doc.precision(self.party_field, self.party_row)
def _reset_existing_tds(self):
for row in self.existing_tds_rows:
# TDS amount is always in credit (liability to government)
tds_amount = flt(row.get("credit") - row.get("debit"), self.precision)
if not tds_amount:
continue
self._update_party_amount(tds_amount, is_reversal=True)
# zero_out_tds_row
row.update(
{
"credit": 0,
"credit_in_account_currency": 0,
"debit": 0,
"debit_in_account_currency": 0,
}
)
def _update_party_amount(self, amount, is_reversal=False):
amount = flt(amount, self.precision)
amount_in_party_currency = flt(amount / self.party_row.get("exchange_rate", 1), self.precision)
# Determine which field the party amount is in
active_field = self.party_field if self.party_row.get(self.party_field) else self.reverse_field
# If amount is in reverse field, flip the signs
if active_field == self.reverse_field:
amount = -amount
amount_in_party_currency = -amount_in_party_currency
# Direction multiplier based on party type:
# Customer (TCS): +1 (add to debit)
# Supplier (TDS): -1 (subtract from credit)
direction = 1 if self.party_type == "Customer" else -1
# Reversal inverts the direction
if is_reversal:
direction = -direction
adjustment = amount * direction
adjustment_in_party_currency = amount_in_party_currency * direction
active_field_account_currency = f"{active_field}_in_account_currency"
self.party_row.update(
{
active_field: flt(self.party_row.get(active_field) + adjustment, self.precision),
active_field_account_currency: flt(
self.party_row.get(active_field_account_currency) + adjustment_in_party_currency,
self.precision,
),
}
)
def _calculate_net_total(self):
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map
account_type_map = get_account_type_map(self.doc.company)
return flt(
sum(
d.get(self.reverse_field) - d.get(self.party_field)
for d in self.doc.get("accounts")
if account_type_map.get(d.account) not in ("Tax", "Chargeable")
and d.account != self.party_account
and not d.get("is_tax_withholding_account")
),
self.precision,
)
def _get_tds_details(self, net_total):
return get_party_tax_withholding_details(
frappe._dict(
{
"party_type": self.party_type,
"party": self.party,
"doctype": self.doc.doctype,
"company": self.doc.company,
"posting_date": self.doc.posting_date,
"tax_withholding_net_total": net_total,
"base_tax_withholding_net_total": net_total,
"grand_total": net_total,
}
),
self.doc.tax_withholding_category,
)
def _create_or_update_tds_row(self, tds_details):
tax_account = tds_details.get("account_head")
account_currency = get_account_currency(tax_account)
company_currency = frappe.get_cached_value("Company", self.doc.company, "default_currency")
exchange_rate = _get_exchange_rate(account_currency, company_currency, self.doc.posting_date)
tax_amount = flt(tds_details.get("tax_amount"), self.precision)
tax_amount_in_account_currency = flt(tax_amount / exchange_rate, self.precision)
# Find existing TDS row for this account
tax_row = None
for row in self.doc.get("accounts"):
if row.account == tax_account and row.get("is_tax_withholding_account"):
tax_row = row
break
if not tax_row:
tax_row = self.doc.append(
"accounts",
{
"account": tax_account,
"account_currency": account_currency,
"exchange_rate": exchange_rate,
"cost_center": tds_details.get("cost_center"),
"credit": 0,
"credit_in_account_currency": 0,
"debit": 0,
"debit_in_account_currency": 0,
"is_tax_withholding_account": 1,
},
)
# TDS/TCS is always credited (liability to government)
tax_row.update(
{
"credit": tax_amount,
"credit_in_account_currency": tax_amount_in_account_currency,
"debit": 0,
"debit_in_account_currency": 0,
}
)
self._cleanup_duplicate_tds_rows(tax_row)
def _cleanup_duplicate_tds_rows(self, current_tax_row):
rows_to_remove = [
row
for row in self.doc.get("accounts")
if row.get("is_tax_withholding_account") and row != current_tax_row
]
for row in rows_to_remove:
self.doc.remove(row)
def _recalculate_totals(self):
self.doc.set_amounts_in_company_currency()
self.doc.set_total_debit_credit()
self.doc.set_against_account()
@frappe.whitelist()
def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
@@ -1649,8 +1805,6 @@ def get_exchange_rate(
credit=None,
exchange_rate=None,
):
from erpnext.setup.utils import get_exchange_rate
account_details = frappe.get_cached_value(
"Account", account, ["account_type", "root_type", "account_currency", "company"], as_dict=1
)
@@ -1672,8 +1826,8 @@ def get_exchange_rate(
# The date used to retreive the exchange rate here is the date passed
# in as an argument to this function.
elif (not exchange_rate or flt(exchange_rate) == 1) and account_currency and posting_date:
exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
elif (not flt(exchange_rate) or flt(exchange_rate) == 1) and account_currency and posting_date:
exchange_rate = _get_exchange_rate(account_currency, company_currency, posting_date)
else:
exchange_rate = 1

View File

@@ -34,6 +34,7 @@
"reference_detail_no",
"advance_voucher_type",
"advance_voucher_no",
"is_tax_withholding_account",
"col_break3",
"is_advance",
"user_remark",
@@ -282,12 +283,19 @@
"options": "advance_voucher_type",
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "is_tax_withholding_account",
"fieldtype": "Check",
"label": "Is Tax Withholding Account",
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-10-27 13:48:32.805100",
"modified": "2025-11-27 12:23:33.157655",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -28,6 +28,7 @@ class JournalEntryAccount(Document):
debit_in_account_currency: DF.Currency
exchange_rate: DF.Float
is_advance: DF.Literal["No", "Yes"]
is_tax_withholding_account: DF.Check
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data

View File

@@ -7,7 +7,7 @@ frappe.ui.form.on("Mode of Payment", {
let d = locals[cdt][cdn];
return {
filters: [
["Account", "account_type", "in", "Bank, Cash, Receivable"],
["Account", "account_type", "in", ["Bank", "Cash", "Receivable"]],
["Account", "is_group", "=", 0],
["Account", "company", "=", d.company],
],

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) {
@@ -435,6 +445,7 @@ frappe.ui.form.on("Payment Entry", {
"paid_to",
"references",
"total_allocated_amount",
"party_name",
],
function (i, field) {
frm.set_value(field, null);
@@ -1118,7 +1129,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,
});
@@ -1302,15 +1313,14 @@ frappe.ui.form.on("Payment Entry", {
let row = (frm.doc.deductions || []).find((t) => t.is_exchange_gain_loss);
if (!row) {
const response = await get_company_defaults(frm.doc.company);
const company_defaults = frappe.get_doc(":Company", frm.doc.company);
const account =
response.message?.[account_fieldname] ||
company_defaults?.[account_fieldname] ||
(await prompt_for_missing_account(frm, account_fieldname));
row = frm.add_child("deductions");
row.account = account;
row.cost_center = response.message?.cost_center;
row.cost_center = company_defaults?.cost_center;
row.is_exchange_gain_loss = 1;
}
@@ -1521,18 +1531,14 @@ frappe.ui.form.on("Payment Entry", {
"Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"
);
d.row_id = "";
} else if (
(d.charge_type == "On Previous Row Amount" || d.charge_type == "On Previous Row Total") &&
d.row_id
) {
} else if (d.charge_type == "On Previous Row Amount" || d.charge_type == "On Previous Row Total") {
if (d.idx == 1) {
msg = __(
"Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"
);
d.charge_type = "";
} else if (!d.row_id) {
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
d.row_id = "";
d.row_id = d.idx - 1;
} else if (d.row_id && d.row_id >= d.idx) {
msg = __(
"Cannot refer row number greater than or equal to current row number for this Charge type"

View File

@@ -1800,7 +1800,7 @@ class PaymentEntry(AccountsController):
else:
self.total_taxes_and_charges += current_tax_amount
self.base_total_taxes_and_charges += tax.base_tax_amount
self.base_total_taxes_and_charges += current_tax_amount
if self.get("taxes"):
self.paid_amount_after_tax = self.get("taxes")[-1].base_total

View File

@@ -68,7 +68,7 @@
{
"columns": 2,
"fieldname": "total_amount",
"fieldtype": "Float",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Grand Total",
"print_hide": 1,
@@ -77,7 +77,7 @@
{
"columns": 2,
"fieldname": "outstanding_amount",
"fieldtype": "Float",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Outstanding",
"read_only": 1
@@ -85,7 +85,7 @@
{
"columns": 2,
"fieldname": "allocated_amount",
"fieldtype": "Float",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated"
},
@@ -174,7 +174,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-07-25 04:32:11.040025",
"modified": "2026-01-05 14:18:03.286224",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",

View File

@@ -18,12 +18,12 @@ class PaymentEntryReference(Document):
account_type: DF.Data | None
advance_voucher_no: DF.DynamicLink | None
advance_voucher_type: DF.Link | None
allocated_amount: DF.Float
allocated_amount: DF.Currency
bill_no: DF.Data | None
due_date: DF.Date | None
exchange_gain_loss: DF.Currency
exchange_rate: DF.Float
outstanding_amount: DF.Float
outstanding_amount: DF.Currency
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
@@ -34,7 +34,7 @@ class PaymentEntryReference(Document):
reconcile_effect_on: DF.Date | None
reference_doctype: DF.Link
reference_name: DF.DynamicLink
total_amount: DF.Float
total_amount: DF.Currency
# end: auto-generated types
@property

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
@@ -133,7 +134,6 @@ class PaymentLedgerEntry(Document):
account_type == "Profit and Loss"
and self.company == dimension.company
and dimension.mandatory_for_pl
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(
@@ -146,7 +146,6 @@ class PaymentLedgerEntry(Document):
account_type == "Balance Sheet"
and self.company == dimension.company
and dimension.mandatory_for_bs
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(

View File

@@ -334,7 +334,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
},
{
fieldtype: "HTML",
options: "<b> New Journal Entry will be posted for the difference amount </b>",
options: __(
"New Journal Entry will be posted for the difference amount. The Posting Date can be modified."
).bold(),
},
],
primary_action: () => {

View File

@@ -6,7 +6,7 @@ import frappe
from frappe import _, msgprint, qb
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.query_builder import Criterion
from frappe.query_builder import Case, Criterion
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
@@ -393,6 +393,9 @@ class PaymentReconciliation(Document):
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
def get_difference_amount(self, payment_entry, invoice, allocated_amount):
party_account_defaults = frappe.get_cached_value(
"Account", self.receivable_payable_account, ["account_type", "account_currency"], as_dict=True
)
allocated_amount_precision = get_field_precision(
frappe.get_meta("Payment Reconciliation Allocation").get_field("allocated_amount")
)
@@ -400,9 +403,9 @@ class PaymentReconciliation(Document):
frappe.get_meta("Payment Reconciliation Allocation").get_field("difference_amount")
)
difference_amount = 0
if frappe.get_cached_value(
"Account", self.receivable_payable_account, "account_currency"
) != frappe.get_cached_value("Company", self.company, "default_currency"):
if party_account_defaults.get("account_currency") != frappe.get_cached_value(
"Company", self.company, "default_currency"
):
if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
"exchange_rate", 1
):
@@ -414,7 +417,14 @@ class PaymentReconciliation(Document):
invoice.get("exchange_rate", 1) * flt(allocated_amount, allocated_amount_precision),
difference_amount_precision,
)
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
# Added If clause to handle return Adhoc payments for account type holders ("Payable")
if party_account_defaults.get("account_type") in ("Payable") and invoice.get(
"invoice_type"
) in ["Payment Entry", "Journal Entry"]:
difference_amount = allocated_amount_in_inv_rate - allocated_amount_in_ref_rate
else:
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
return difference_amount
@@ -677,6 +687,28 @@ class PaymentReconciliation(Document):
)
invoice_exchange_map.update(journals_map)
payment_entries = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Payment Entry"
]
payment_entries.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Payment Entry"]
)
if payment_entries:
pe = frappe.qb.DocType("Payment Entry")
query = (
frappe.qb.from_(pe)
.select(
pe.name,
Case()
.when(pe.payment_type == "Receive", pe.source_exchange_rate)
.else_(pe.target_exchange_rate)
.as_("exchange_rate"),
)
.where(pe.name.isin(payment_entries))
)
payment_entries = query.run(as_list=1)
invoice_exchange_map.update(payment_entries)
return invoice_exchange_map
def validate_allocation(self):
@@ -714,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):
@@ -765,6 +797,14 @@ class PaymentReconciliation(Document):
def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
for inv in dr_cr_notes:
if (
abs(frappe.db.get_value(inv.voucher_type, inv.voucher_no, "outstanding_amount"))
< inv.allocated_amount
):
frappe.throw(
_("{0} has been modified after you pulled it. Please pull it again.").format(inv.voucher_type)
)
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
reconcile_dr_or_cr = (

View File

@@ -2336,6 +2336,210 @@ class TestPaymentReconciliation(FrappeTestCase):
frappe.db.set_value("Company", self.company, default_settings)
def test_foreign_currency_reverse_payment_entry_against_payment_entry_for_customer(self):
transaction_date = nowdate()
customer = self.customer3
amount = 1000
exchange_rate_at_payment = 100
exchange_rate_at_reverse_payment = 95
# Receive amount from customer - 1,00,000
pe = self.create_payment_entry(amount=amount, posting_date=transaction_date, customer=customer)
pe.payment_type = "Receive"
pe.paid_from = self.debtors_eur
pe.paid_from_account_currency = "EUR"
pe.source_exchange_rate = exchange_rate_at_payment
pe.paid_amount = amount
pe.received_amount = exchange_rate_at_payment * amount
pe.paid_to = self.cash
pe.paid_to_account_currency = "INR"
pe = pe.save().submit()
# Pay amount to customer - 95,000
reverse_pe = self.create_payment_entry(
amount=amount, posting_date=transaction_date, customer=customer
)
reverse_pe.payment_type = "Pay"
reverse_pe.paid_from = self.cash
reverse_pe.paid_from_account_currency = "INR"
reverse_pe.target_exchange_rate = exchange_rate_at_reverse_payment
reverse_pe.paid_amount = exchange_rate_at_reverse_payment * amount
reverse_pe.received_amount = amount
reverse_pe.paid_to = self.debtors_eur
reverse_pe.paid_to_account_currency = "EUR"
reverse_pe.save().submit()
# Reconcile payments
pr = self.create_payment_reconciliation()
pr.party = customer
pr.receivable_payable_account = self.debtors_eur
pr.get_unreconciled_entries()
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(len(pr.get("payments")), 1)
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Check the difference_amount is a gain of 5000
self.assertEqual(flt(pr.allocation[0].get("difference_amount")), 5000.0)
pr.reconcile()
def test_foreign_currency_reverse_payment_entry_against_payment_entry_for_supplier(self):
transaction_date = nowdate()
self.supplier = "_Test Supplier USD"
amount = 1000
exchange_rate_at_payment = 100
exchange_rate_at_reverse_payment = 95
# Pay amount to supplier - 1,00,000
pe = self.create_payment_entry(amount=amount, posting_date=transaction_date)
pe.payment_type = "Pay"
pe.party_type = "Supplier"
pe.party = self.supplier
pe.paid_from = self.cash
pe.paid_from_account_currency = "INR"
pe.target_exchange_rate = exchange_rate_at_payment
pe.paid_amount = exchange_rate_at_payment * amount
pe.received_amount = amount
pe.paid_to = self.creditors_usd
pe.paid_to_account_currency = "USD"
pe.save().submit()
# Receive amount from supplier - 95,000
reverse_pe = self.create_payment_entry(amount=amount, posting_date=transaction_date)
reverse_pe.payment_type = "Receive"
reverse_pe.party_type = "Supplier"
reverse_pe.party = self.supplier
reverse_pe.paid_from = self.creditors_usd
reverse_pe.paid_from_account_currency = "USD"
reverse_pe.source_exchange_rate = exchange_rate_at_reverse_payment
reverse_pe.paid_amount = amount
reverse_pe.received_amount = exchange_rate_at_reverse_payment * amount
reverse_pe.paid_to = self.cash
reverse_pe.paid_to_account_currency = "INR"
reverse_pe = reverse_pe.save().submit()
# Reconcile payments
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.party = self.supplier
pr.receivable_payable_account = self.creditors_usd
pr.get_unreconciled_entries()
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(len(pr.get("payments")), 1)
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Check the difference_amount is a loss of 5000
self.assertEqual(flt(pr.allocation[0].get("difference_amount")), -5000.0)
pr.reconcile()
def test_foreign_currency_reverse_journal_entry_against_journal_entry_for_customer(self):
transaction_date = nowdate()
customer = self.customer3
amount = 1000
exchange_rate_at_payment = 95
exchange_rate_at_reverse_payment = 100
# Receive amount from customer - 95,000
je1 = self.create_journal_entry(self.cash, self.debtors_eur, amount, transaction_date)
je1.multi_currency = 1
je1.accounts[0].exchange_rate = 1
je1.accounts[0].debit_in_account_currency = exchange_rate_at_payment * amount
je1.accounts[0].debit = exchange_rate_at_payment * amount
je1.accounts[1].party_type = "Customer"
je1.accounts[1].party = customer
je1.accounts[1].exchange_rate = exchange_rate_at_payment
je1.accounts[1].credit_in_account_currency = amount
je1.accounts[1].credit = exchange_rate_at_payment * amount
je1.save()
je1.submit()
# Pay amount to customer - 1,00,000
je2 = self.create_journal_entry(self.debtors_eur, self.cash, amount, transaction_date)
je2.multi_currency = 1
je2.accounts[0].party_type = "Customer"
je2.accounts[0].party = customer
je2.accounts[0].exchange_rate = exchange_rate_at_reverse_payment
je2.accounts[0].debit_in_account_currency = amount
je2.accounts[0].debit = exchange_rate_at_reverse_payment * amount
je2.accounts[1].exchange_rate = 1
je2.accounts[1].credit_in_account_currency = exchange_rate_at_reverse_payment * amount
je2.accounts[1].credit = exchange_rate_at_reverse_payment * amount
je2.save()
je2.submit()
# Reconcile payments
pr = self.create_payment_reconciliation()
pr.party = customer
pr.receivable_payable_account = self.debtors_eur
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Check the difference_amount is a loss of 5000
self.assertEqual(flt(pr.allocation[0].difference_amount), -5000.0)
pr.reconcile()
def test_foreign_currency_reverse_journal_entry_against_journal_entry_for_supplier(self):
transaction_date = nowdate()
self.supplier = "_Test Supplier USD"
amount = 1000
exchange_rate_at_payment = 95
exchange_rate_at_reverse_payment = 100
# Pay amount to supplier - 95,000
je1 = self.create_journal_entry(self.creditors_usd, self.cash, amount, transaction_date)
je1.multi_currency = 1
je1.accounts[0].party_type = "Supplier"
je1.accounts[0].party = self.supplier
je1.accounts[0].exchange_rate = exchange_rate_at_payment
je1.accounts[0].debit_in_account_currency = amount
je1.accounts[0].debit = exchange_rate_at_payment * amount
je1.accounts[1].exchange_rate = 1
je1.accounts[1].credit = exchange_rate_at_payment * amount
je1.accounts[1].credit_in_account_currency = exchange_rate_at_payment * amount
je1.save()
je1.submit()
# Receive amount from supplier - 1,00,000
je2 = self.create_journal_entry(self.cash, self.creditors_usd, amount, transaction_date)
je2.multi_currency = 1
je2.accounts[0].exchange_rate = 1
je2.accounts[0].debit = exchange_rate_at_reverse_payment * amount
je2.accounts[0].debit_in_account_currency = exchange_rate_at_reverse_payment * amount
je2.accounts[1].party_type = "Supplier"
je2.accounts[1].party = self.supplier
je2.accounts[1].exchange_rate = exchange_rate_at_reverse_payment
je2.accounts[1].credit_in_account_currency = amount
je2.accounts[1].credit = exchange_rate_at_reverse_payment * amount
je2.save()
je2.submit()
# Reconcile payments
pr = self.create_payment_reconciliation()
pr.party_type = "Supplier"
pr.party = self.supplier
pr.receivable_payable_account = self.creditors_usd
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Check the difference_amount is a gain of 5000
self.assertEqual(flt(pr.allocation[0].difference_amount), 5000.0)
pr.reconcile()
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -545,6 +545,9 @@ def make_payment_request(**args):
if args.dt not in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST:
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
if args.dn and not isinstance(args.dn, str):
frappe.throw(_("Invalid parameter. 'dn' should be of type str"))
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
if not args.get("company"):
args.company = ref_doc.company
@@ -850,6 +853,7 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
)
referenced_payment_requests = {pr.name: pr for pr in referenced_payment_requests}
doc_updates = {}
for ref in references:
if not ref.payment_request:
@@ -875,7 +879,7 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
title=_("Invalid Allocated Amount"),
)
# update status
# determine status
if new_outstanding_amount == payment_request["grand_total"]:
status = "Initiated" if payment_request["payment_request_type"] == "Outward" else "Requested"
elif new_outstanding_amount == 0:
@@ -883,12 +887,15 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
elif new_outstanding_amount > 0:
status = "Partially Paid"
# update database
frappe.db.set_value(
"Payment Request",
ref.payment_request,
{"outstanding_amount": new_outstanding_amount, "status": status},
)
# prepare bulk update data
doc_updates[ref.payment_request] = {
"outstanding_amount": new_outstanding_amount,
"status": status,
}
# bulk update all payment requests
if doc_updates:
frappe.db.bulk_update("Payment Request", doc_updates)
def get_dummy_message(doc):

View File

@@ -13,9 +13,9 @@ frappe.ui.form.on("Period Closing Voucher", {
return {
filters: [
["Account", "company", "=", frm.doc.company],
["Account", "is_group", "=", "0"],
["Account", "is_group", "=", 0],
["Account", "freeze_account", "=", "No"],
["Account", "root_type", "in", "Liability, Equity"],
["Account", "root_type", "in", ["Liability", "Equity"]],
],
};
});

View File

@@ -18,12 +18,17 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.controllers.queries import item_query as _item_query
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.stock_ledger import is_negative_stock_allowed
class PartialPaymentValidationError(frappe.ValidationError):
pass
class ProductBundleStockValidationError(frappe.ValidationError):
pass
class POSInvoice(SalesInvoice):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -350,32 +355,67 @@ class POSInvoice(SalesInvoice):
for d in self.get("items"):
if not d.serial_and_batch_bundle:
available_stock, is_stock_item, is_negative_stock_allowed = get_stock_availability(
d.item_code, d.warehouse
)
if frappe.db.exists("Product Bundle", d.item_code):
(
availability,
is_stock_item,
is_negative_stock_allowed,
) = get_product_bundle_stock_availability(d.item_code, d.warehouse, d.stock_qty)
else:
availability, is_stock_item, is_negative_stock_allowed = get_stock_availability(
d.item_code, d.warehouse
)
if is_negative_stock_allowed:
continue
item_code, warehouse, _qty = (
frappe.bold(d.item_code),
frappe.bold(d.warehouse),
frappe.bold(d.qty),
)
if is_stock_item and flt(available_stock) <= 0:
frappe.throw(
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
d.idx, item_code, warehouse
),
title=_("Item Unavailable"),
)
elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
frappe.throw(
_("Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}.").format(
d.idx, item_code, warehouse
),
title=_("Item Unavailable"),
)
if isinstance(availability, list):
error_msgs = []
for item in availability:
if flt(item["available"]) < flt(item["required"]):
error_msgs.append(
_("<li>Packed Item {0}: Required {1}, Available {2}</li>").format(
frappe.bold(item["item_code"]),
frappe.bold(flt(item["required"], 2)),
frappe.bold(flt(item["available"], 2)),
)
)
if error_msgs:
frappe.throw(
_(
"<b>Row #{0}:</b> Bundle {1} in warehouse {2} has insufficient packed items:<br><div style='margin-top: 15px;'><ul style='line-height: 0.8;'>{3}</ul></div>"
).format(
d.idx,
frappe.bold(d.item_code),
frappe.bold(d.warehouse),
"<br>".join(error_msgs),
),
title=_("Insufficient Stock for Product Bundle Items"),
exc=ProductBundleStockValidationError,
)
else:
item_code, warehouse = frappe.bold(d.item_code), frappe.bold(d.warehouse)
if is_stock_item and flt(availability) <= 0:
frappe.throw(
_("Row #{0}: Item {1} has no stock in warehouse {2}.").format(
d.idx, item_code, warehouse
),
title=_("Item Out of Stock"),
)
elif is_stock_item and flt(availability) < flt(d.stock_qty):
frappe.throw(
_("Row #{0}: Item {1} in warehouse {2}: Available {3}, Needed {4}.").format(
d.idx,
item_code,
warehouse,
frappe.bold(flt(availability, 2)),
frappe.bold(flt(d.stock_qty, 2)),
),
title=_("Insufficient Stock"),
)
def validate_serialised_or_batched_item(self):
error_msg = []
@@ -763,8 +803,6 @@ class POSInvoice(SalesInvoice):
@frappe.whitelist()
def get_stock_availability(item_code, warehouse):
from erpnext.stock.stock_ledger import is_negative_stock_allowed
if frappe.db.get_value("Item", item_code, "is_stock_item"):
is_stock_item = True
bin_qty = get_bin_qty(item_code, warehouse)
@@ -781,6 +819,26 @@ def get_stock_availability(item_code, warehouse):
return 0, is_stock_item, False
def get_product_bundle_stock_availability(item_code, warehouse, item_qty):
is_stock_item = True
bundle = frappe.get_doc("Product Bundle", item_code)
availabilities = []
for bundle_item in bundle.items:
if frappe.get_value("Item", bundle_item.item_code, "is_stock_item"):
bin_qty = get_bin_qty(bundle_item.item_code, warehouse)
reserved_qty = get_pos_reserved_qty(bundle_item.item_code, warehouse)
available = bin_qty - reserved_qty
availabilities.append(
{
"item_code": bundle_item.item_code,
"required": bundle_item.qty * item_qty,
"available": available,
}
)
return availabilities, is_stock_item, is_negative_stock_allowed(item_code=item_code)
def get_bundle_availability(bundle_item_code, warehouse):
product_bundle = frappe.get_doc("Product Bundle", bundle_item_code)

View File

@@ -481,6 +481,7 @@ class TestPOSInvoice(unittest.TestCase):
rate=1000,
serial_no=[serial_nos[0]],
do_not_save=1,
ignore_sabb_validation=True,
)
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
@@ -837,6 +838,53 @@ class TestPOSInvoice(unittest.TestCase):
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,
@@ -956,6 +1004,7 @@ class TestPOSInvoice(unittest.TestCase):
qty=1,
rate=100,
do_not_submit=True,
ignore_sabb_validation=True,
)
self.assertRaises(frappe.ValidationError, pos_inv.submit)
@@ -964,6 +1013,84 @@ class TestPOSInvoice(unittest.TestCase):
frappe.db.rollback(save_point="before_test_delivered_serial_no_case")
frappe.set_user("Administrator")
def test_bundle_stock_availability_validation(self):
from erpnext.accounts.doctype.pos_invoice.pos_invoice import ProductBundleStockValidationError
from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
init_user_and_profile,
)
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.item.test_item import create_item
init_user_and_profile()
frappe.set_user("Administrator")
warehouse = "_Test Warehouse - _TC"
company = "_Test Company"
# Create stock sub-items
sub_item_a = "_Test Bundle SubA"
if not frappe.db.exists("Item", sub_item_a):
create_item(
item_code=sub_item_a,
is_stock_item=1,
)
sub_item_b = "_Test Bundle SubB"
if not frappe.db.exists("Item", sub_item_b):
create_item(
item_code=sub_item_b,
is_stock_item=1,
)
# Add initial stock: SubA=5, SubB=2
make_stock_entry(item_code=sub_item_a, target=warehouse, qty=5, company=company)
make_stock_entry(item_code=sub_item_b, target=warehouse, qty=2, company=company)
# Create Product Bundle: Test Bundle (SubA x2 + SubB x1)
bundle_item = "_Test Bundle"
if not frappe.db.exists("Item", bundle_item):
create_item(
item_code=bundle_item,
is_stock_item=0,
)
if not frappe.db.exists("Product Bundle", bundle_item):
make_product_bundle(parent=bundle_item, items=[sub_item_a, sub_item_b])
# Test Case 1: Sufficient stock (bundle qty=1: requires SubA=2 (<=5), SubB=1 (<=2)) -> No error
pos_inv_sufficient = create_pos_invoice(
item=bundle_item,
qty=1,
rate=100,
warehouse=warehouse,
pos_profile=self.pos_profile.name,
do_not_save=1,
)
pos_inv_sufficient.append("payments", {"mode_of_payment": "Cash", "amount": 100, "default": 1})
pos_inv_sufficient.insert()
pos_inv_sufficient.submit()
pos_inv_sufficient.cancel()
pos_inv_sufficient.delete()
# Test Case 2: Insufficient stock (reduce SubB to 1, bundle qty=2: requires SubB=2 >1) -> Error with details
make_stock_entry(item_code=sub_item_b, from_warehouse=warehouse, qty=1, company=company)
pos_inv_insufficient = create_pos_invoice(
item=bundle_item,
qty=2,
rate=100,
warehouse=warehouse,
pos_profile=self.pos_profile.name,
do_not_save=1,
)
pos_inv_insufficient.append("payments", {"mode_of_payment": "Cash", "amount": 200, "default": 1})
pos_inv_insufficient.save()
self.assertRaises(ProductBundleStockValidationError, pos_inv_insufficient.submit)
frappe.set_user("test@example.com")
def create_pos_invoice(**args):
args = frappe._dict(args)
@@ -1019,6 +1146,7 @@ def create_pos_invoice(**args):
"posting_time": pos_inv.posting_time,
"type_of_transaction": type_of_transaction,
"do_not_submit": True,
"ignore_sabb_validation": args.ignore_sabb_validation,
}
)
).name

View File

@@ -697,6 +697,7 @@ def get_sales_invoice_item(return_against_pos_invoice, pos_invoice_item):
& (SalesInvoice.is_return == 0)
& (SalesInvoiceItem.pos_invoice == return_against_pos_invoice)
& (SalesInvoiceItem.pos_invoice_item == pos_invoice_item)
& (SalesInvoice.docstatus == 1)
)
)

View File

@@ -243,10 +243,13 @@ def get_other_conditions(conditions, values, args):
if group_condition:
conditions += " and " + group_condition
if args.get("transaction_date"):
date = args.get("transaction_date") or frappe.get_value(
args.get("doctype"), args.get("name"), "posting_date", ignore=True
)
if date:
conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01')
and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
values["transaction_date"] = args.get("transaction_date")
values["transaction_date"] = date
if args.get("doctype") in [
"Quotation",

View File

@@ -412,8 +412,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
@@ -503,6 +504,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

@@ -13,7 +13,7 @@
</div>
{% endif %}
</div>
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
<h2 class="text-center">{{ _("GENERAL LEDGER") }}</h2>
<div>
{% if filters.party[0] == filters.party_name[0] %}
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{ filters.party_name[0] }}</b></h5>

View File

@@ -1,5 +1,6 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2020-05-22 16:46:18.712954",
"doctype": "DocType",
@@ -67,7 +68,7 @@
"fieldname": "frequency",
"fieldtype": "Select",
"label": "Frequency",
"options": "Weekly\nMonthly\nQuarterly"
"options": "Daily\nWeekly\nBiweekly\nMonthly\nQuarterly"
},
{
"fieldname": "company",
@@ -401,7 +402,7 @@
}
],
"links": [],
"modified": "2025-08-04 18:21:12.603623",
"modified": "2025-10-07 12:19:20.719898",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",

View File

@@ -8,7 +8,7 @@ import frappe
from frappe import _
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
from frappe.utils import add_days, add_months, format_date, getdate, today
from frappe.utils import add_days, add_months, add_to_date, format_date, getdate, today
from frappe.utils.jinja import validate_template
from frappe.utils.pdf import get_pdf
from frappe.www.printview import get_print_style
@@ -55,7 +55,7 @@ class ProcessStatementOfAccounts(Document):
enable_auto_email: DF.Check
filter_duration: DF.Int
finance_book: DF.Link | None
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
frequency: DF.Literal["Daily", "Weekly", "Biweekly", "Monthly", "Quarterly"]
from_date: DF.Date | None
ignore_cr_dr_notes: DF.Check
ignore_exchange_rate_revaluation_journals: DF.Check
@@ -529,8 +529,9 @@ def send_emails(document_name, from_scheduler=False, posting_date=None):
if doc.enable_auto_email and from_scheduler:
new_to_date = getdate(posting_date or today())
if doc.frequency == "Weekly":
new_to_date = add_days(new_to_date, 7)
if doc.frequency in ("Daily", "Weekly", "Biweekly"):
frequency = {"Daily": 1, "Weekly": 7, "Biweekly": 14}
new_to_date = add_days(new_to_date, frequency[doc.frequency])
else:
new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3)
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)

View File

@@ -6,228 +6,304 @@
.print-format td {
vertical-align:middle !important;
}
</style>
</style>
<div id="header-html" class="hidden-pdf">
{% if letter_head.content %}
<div class="letter-head text-center">{{ letter_head.content }}</div>
<hr style="height:2px;border-width:0;color:black;background-color:black;">
{% endif %}
</div>
<div id="footer-html" class="visible-pdf">
{% if letter_head.footer %}
<div class="letter-head-footer">
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
{{ letter_head.footer }}
</div>
{% endif %}
</div>
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
<h4 class="text-center">
{{ filters.customer_name }}
</h4>
<h6 class="text-center">
{% if (filters.tax_id) %}
{{ _("Tax Id: ") }}{{ filters.tax_id }}
{% endif %}
</h6>
<h5 class="text-center">
{{ _(filters.ageing_based_on) }}
{{ _("Until") }}
{{ frappe.format(filters.report_date, 'Date') }}
</h5>
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) %}
<strong>{{ _("Payment Terms") }}:</strong> {{ filters.payment_terms }}
{% endif %}
</div>
<div class="pull-right">
{% if(filters.credit_limit) %}
<strong>{{ _("Credit Limit") }}:</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
{% endif %}
</div>
</div>
{% if(filters.show_future_payments) %}
{% set balance_row = data.slice(-1).pop() %}
{% for i in report.columns %}
{% if i.fieldname == 'age' %}
{% set elem = i %}
{% endif %}
{% endfor %}
{% set start = report.columns.findIndex(elem) %}
{% set range1 = report.columns[start].label %}
{% set range2 = report.columns[start+1].label %}
{% set range3 = report.columns[start+2].label %}
{% set range4 = report.columns[start+3].label %}
{% set range5 = report.columns[start+4].label %}
{% set range6 = report.columns[start+5].label %}
{% if(balance_row) %}
<table class="table table-bordered table-condensed">
<caption class="text-right">(Amount in {{ data[0]["currency"] ~ "" }})</caption>
<colgroup>
<col style="width: 30mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup>
<thead>
<tr>
<th>{{ _(" ") }}</th>
<th>{{ _(range1) }}</th>
<th>{{ _(range2) }}</th>
<th>{{ _(range3) }}</th>
<th>{{ _(range4) }}</th>
<th>{{ _(range5) }}</th>
<th>{{ _(range6) }}</th>
<th>{{ _("Total") }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ _("Total Outstanding") }}</td>
<td class="text-right">
{{ format_number(balance_row["age"], null, 2) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }}
</td>
</tr>
<td>{{ _("Future Payments") }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
</td>
<tr class="cvs-footer">
<th class="text-left">{{ _("Cheques Required") }}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}</th>
</tr>
</tbody>
</table>
{% endif %}
<div id="header-html" class="hidden-pdf">
{% if letter_head.content %}
<div class="letter-head text-center">{{ letter_head.content }}</div>
<hr style="height:2px;border-width:0;color:black;background-color:black;">
{% endif %}
<table class="table table-bordered">
</div>
<div id="footer-html" class="visible-pdf">
{% if letter_head.footer %}
<div class="letter-head-footer">
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
{{ letter_head.footer }}
</div>
{% endif %}
</div>
<h2 class="text-center" style="margin-top:0">{{ _("STATEMENT OF ACCOUNTS") }}</h2>
<h4 class="text-center">
{{ filters.customer_name }}
</h4>
<h6 class="text-center">
{% if (filters.tax_id) %}
{{ _("Tax Id: {0}").format(filters.tax_id) }}
{% endif %}
</h6>
<h5 class="text-center">
{{ _("{0} until {1}").format(
_(filters.ageing_based_on),
frappe.format(filters.report_date, 'Date')
) }}
</h5>
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) %}
<strong>{{ _("Payment Terms:") }}</strong> {{ filters.payment_terms }}
{% endif %}
</div>
<div class="pull-right">
{% if(filters.credit_limit) %}
<strong>{{ _("Credit Limit:") }}</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
{% endif %}
</div>
</div>
{% if(filters.show_future_payments)%}
{% set balance_row = data[-1] %}
{% set ns = namespace(idx=None) %}
{% for i in report.columns %}
{% if i.fieldname == "age" and ns.idx is none %}
{% set ns.idx = loop.index0 %}
{% endif %}
{% endfor %}
{% set age = report.columns[ns.idx].label %}
{% set range1 = report.columns[ns.idx+1].label %}
{% set range2 = report.columns[ns.idx+2].label %}
{% set range3 = report.columns[ns.idx+3].label %}
{% set range4 = report.columns[ns.idx+4].label %}
{% set range5 = report.columns[ns.idx+5].label %}
{% if(balance_row) %}
<table class="table table-bordered table-condensed">
<caption class="text-right">{{ _("Amount in {0}").format(data[0]["currency"] ~ "") }}</caption>
<colgroup>
<col style="width: 30mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup>
<thead>
<tr>
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
<th style="width: 10%">{{ _("Date") }}</th>
<th style="width: 4%">{{ _("Age (Days)") }}</th>
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<th style="width: 14%">{{ _("Reference") }}</th>
<th style="width: 10%">{{ _("Sales Person") }}</th>
{% else %}
<th style="width: 24%">{{ _("Reference") }}</th>
{% endif %}
{% if not(filters.show_future_payments) %}
<th style="width: 20%">
{% if (filters.customer or filters.supplier or filters.customer_name) %}
{{ _("Remarks") }}
{% else %}
{{ _("Party") }}
{% endif %}
</th>
{% endif %}
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
{% if not(filters.show_future_payments) %}
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
<th style="width: 10%; text-align: right">
{% if report.report_name == "Accounts Receivable" %}
{{ _('Credit Note') }}
{% else %}
{{ _('Debit Note') }}
{% endif %}
</th>
{% endif %}
<th style="width: 10%; text-align: right">{{ _("Outstanding Amount") }}</th>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<th style="width: 12%">{{ _("Customer LPO No.") }}</th>
{% endif %}
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
{% endif %}
{% else %}
<th style="width: 40%">
{% if (filters.customer or filters.supplier or filters.customer_name) %}
{{ _("Remarks")}}
{% else %}
{{ _("Party") }}
{% endif %}
</th>
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
<th style="width: 15%">
{% if report.report_name == "Accounts Receivable Summary" %}
{{ _('Credit Note Amount') }}
{% else %}
{{ _('Debit Note Amount') }}
{% endif %}
</th>
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
{% endif %}
<th>{{ _(" ") }}</th>
<th>{{ _(age) }}</th>
<th>{{ _(range1) }}</th>
<th>{{ _(range2) }}</th>
<th>{{ _(range3) }}</th>
<th>{{ _(range4) }}</th>
<th>{{ _(range5) }}</th>
<th>{{ _("Total") }}</th>
</tr>
</thead>
<tbody>
{% for i in range(data|length) %}
<tr>
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
{% if(data[i]["party"]) %}
<td>{{ frappe.format((data[i]["posting_date"]), 'Date') }}</td>
<td style="text-align: right">{{ data[i]["age"] }}</td>
<td>
{% if not(filters.show_future_payments) %}
{{ data[i]["voucher_type"] }}
<br>
{% endif %}
{{ data[i]["voucher_no"] }}
</td>
<tr>
<td>{{ _("Total Outstanding") }}</td>
<td class="text-right">
{{ frappe.utils.flt(balance_row["age"], 2) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range1"], currency=balance_row["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range2"], currency=balance_row["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range3"], currency=balance_row["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range4"], currency=balance_row["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(balance_row["range5"], currency=balance_row["currency"]) }}
</td>
<td class="text-right">
{{ frappe.utils.fmt_money(frappe.utils.flt(balance_row["outstanding"]), currency=balance_row["currency"]) }}
</td>
</tr>
<td>{{ _("Future Payments") }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{{ frappe.utils.fmt_money(frappe.utils.flt(balance_row["future_amount"]), currency=balance_row["currency"]) }}
</td>
<tr class="cvs-footer">
<th class="text-left">{{ _("Cheques Required") }}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{{ frappe.utils.fmt_money(frappe.utils.flt(balance_row["outstanding"] - balance_row["future_amount"]), currency=balance_row["currency"]) }}</th>
</tr>
</tbody>
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<td>{{ data[i]["sales_person"] }}</td>
</table>
{% endif %}
{% endif %}
<table class="table table-bordered">
<thead>
<tr>
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
<th style="width: 10%">{{ _("Date") }}</th>
<th style="width: 4%">{{ _("Age (Days)") }}</th>
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<th style="width: 14%">{{ _("Reference") }}</th>
<th style="width: 10%">{{ _("Sales Person") }}</th>
{% else %}
<th style="width: 24%">{{ _("Reference") }}</th>
{% endif %}
{% if not(filters.show_future_payments) and filters.show_remarks %}
<th style="width: 20%">
{% if (filters.customer or filters.supplier or filters.customer_name) %}
{{ _("Remarks") }}
{% else %}
{{ _("Party") }}
{% endif %}
</th>
{% endif %}
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
{% if not(filters.show_future_payments) %}
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
<th style="width: 10%; text-align: right">
{% if report.report_name == "Accounts Receivable" %}
{{ _("Credit Note") }}
{% else %}
{{ _("Debit Note") }}
{% endif %}
</th>
{% endif %}
<th style="width: 10%; text-align: right">{{ _("Outstanding Amount") }}</th>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<th style="width: 12%">{{ _("Customer LPO No.") }}</th>
{% endif %}
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
{% endif %}
{% else %}
<th style="width: 40%">
{% if (filters.customer or filters.supplier or filters.customer_name) %}
{{ _("Remarks")}}
{% else %}
{{ _("Party") }}
{% endif %}
</th>
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
<th style="width: 15%">
{% if report.report_name == "Accounts Receivable Summary" %}
{{ _("Credit Note Amount") }}
{% else %}
{{ _("Debit Note Amount") }}
{% endif %}
</th>
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for i in range(data|length) %}
<tr>
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
{% if(data[i]["party"]) %}
<td>{{ frappe.format(data[i]["posting_date"], 'Date') }}</td>
<td style="text-align: right">{{ data[i]["age"] }}</td>
<td>
{% if not(filters.show_future_payments) %}
{{ data[i]["voucher_type"] }}
<br>
{% endif %}
{{ data[i]["voucher_no"] }}
</td>
{% if not (filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<td>{{ data[i]["sales_person"] }}</td>
{% endif %}
{% if not (filters.show_future_payments) and filters.show_remarks %}
<td>
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
{{ data[i]["party"] }}
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
<br> {{ data[i]["customer_name"] }}
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
<br> {{ data[i]["supplier_name"] }}
{% endif %}
{% endif %}
<div>
{% if data[i]["remarks"] %}
{{ _("Remarks") }}:
{{ data[i]["remarks"] }}
{% endif %}
</div>
</td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
{% if not(filters.show_future_payments) %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<td style="text-align: right">
{{ data[i]["po_no"] }}</td>
{% endif %}
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
{% endif %}
{% else %}
<td></td>
{% if not(filters.show_future_payments) %}
<td></td>
{% endif %}
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<td></td>
{% endif %}
<td></td>
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
{% if not(filters.show_future_payments) %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<td style="text-align: right">
{{ data[i]["po_no"] }}</td>
{% endif %}
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
{% endif %}
{% endif %}
{% else %}
{% if(data[i]["party"] or "&nbsp;") %}
{% if not(data[i]["is_total_row"]) %}
<td>
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
{% if(not(filters.customer | filters.supplier)) %}
{{ data[i]["party"] }}
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
<br> {{ data[i]["customer_name"] }}
@@ -235,132 +311,73 @@
<br> {{ data[i]["supplier_name"] }}
{% endif %}
{% endif %}
<div>
{% if data[i]["remarks"] %}
{{ _("Remarks") }}:
{{ data[i]["remarks"] }}
{% endif %}
</div>
<br>{{ _("Remarks") }}:
{{ data[i]["remarks"] }}
</td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
{% if not(filters.show_future_payments) %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<td style="text-align: right">
{{ data[i]["po_no"] }}</td>
{% endif %}
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
{% endif %}
{% else %}
<td></td>
{% if not(filters.show_future_payments) %}
<td></td>
{% endif %}
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
<td></td>
{% endif %}
<td></td>
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
{% if not(filters.show_future_payments) %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% if(filters.show_future_payments) %}
{% if(report.report_name == "Accounts Receivable") %}
<td style="text-align: right">
{{ data[i]["po_no"] }}</td>
{% endif %}
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
{% endif %}
{% endif %}
{% else %}
{% if(data[i]["party"] or "&nbsp;") %}
{% if not(data[i]["is_total_row"]) %}
<td>
{% if(not(filters.customer | filters.supplier)) %}
{{ data[i]["party"] }}
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
<br> {{ data[i]["customer_name"] }}
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
<br> {{ data[i]["supplier_name"] }}
{% endif %}
{% endif %}
<br>{{ _("Remarks") }}:
{{ data[i]["remarks"] }}
</td>
{% else %}
<td><b>{{ _("Total") }}</b></td>
{% endif %}
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
<td><b>{{ _("Total") }}</b></td>
{% endif %}
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
{% endif %}
</tr>
{% endfor %}
<td></td>
<td></td>
{% endif %}
</tr>
{% endfor %}
<td></td>
<td></td>
{% if (filters.show_future_payments) or filters.show_remarks %}
<td></td>
{% endif %}
{% if not(filters.show_future_payments) %}
<td></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
</tbody>
</table>
<br>
{% if ageing %}
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
{{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}}
</h4>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%">0 - 30 Days</th>
<th style="width: 25%">30 - 60 Days</th>
<th style="width: 25%">60 - 90 Days</th>
<th style="width: 25%">90 - 120 Days</th>
<th style="width: 20%">Above 120 Days</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
</tr>
</tbody>
</table>
{% endif %}
{% if terms_and_conditions %}
<div>
{{ terms_and_conditions }}
</div>
{% endif %}
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>
{% else %}
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
<td></td>
<td></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="future_amount"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="remaining_balance"), currency=data[0]["currency"]) }}</b></td>
{% endif %}
</tbody>
</table>
<br>
{% if ageing %}
<h4 class="text-center">
{{ _("Ageing Report based on {0} up to {1}").format(
ageing.ageing_based_on,
frappe.format(filters.report_date, "Date")
) }}
</h4>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%">{{ _("0 - 30 Days") }}</th>
<th style="width: 25%">{{ _("30 - 60 Days") }}</th>
<th style="width: 25%">{{ _("60 - 90 Days") }}</th>
<th style="width: 25%">{{ _("90 - 120 Days") }}</th>
<th style="width: 20%">{{ _("Above 120 Days") }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
</tr>
</tbody>
</table>
{% endif %}
{% if terms_and_conditions %}
<div>
{{ terms_and_conditions }}
</div>
{% endif %}
<p class="text-right text-muted">{{ _("Printed on {0}").format(frappe.utils.now()) }}</p>

View File

@@ -12,6 +12,7 @@ erpnext.buying.setup_buying_controller();
erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.BuyingController {
setup(doc) {
this.setup_accounting_dimension_triggers();
this.setup_posting_date_time_check();
super.setup(doc);
@@ -125,8 +126,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
}
if (doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
cur_frm.add_custom_button(
if (doc.docstatus == 1 && doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request();
@@ -574,17 +575,6 @@ cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function
};
};
cur_frm.cscript.cost_center = function (doc, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.cost_center) {
var cl = doc.items || [];
for (var i = 0; i < cl.length; i++) {
if (!cl[i].cost_center) cl[i].cost_center = d.cost_center;
}
}
refresh_field("items");
};
cur_frm.fields_dict["items"].grid.get_field("project").get_query = function (doc, cdt, cdn) {
return {
filters: [["Project", "status", "not in", "Completed, Cancelled"]],

View File

@@ -603,6 +603,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.pr_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
@@ -1659,7 +1660,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-08-04 19:19:11.380664",
"modified": "2026-02-05 20:45:16.964500",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -38,7 +38,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 import get_warehouse_account_map
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
@@ -2083,6 +2083,19 @@ def make_purchase_receipt(source_name, target_doc=None, args=None):
if isinstance(args, str):
args = json.loads(args)
def post_parent_process(source_parent, target_parent):
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):
target.qty = flt(obj.qty) - flt(obj.received_qty)
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
@@ -2122,7 +2135,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,
)

View File

@@ -115,6 +115,10 @@ class RepostAccountingLedger(Document):
def generate_preview(self):
from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns
if not self.vouchers:
frappe.msgprint(_("Add vouchers to generate preview."))
return
gl_columns = []
gl_data = []
@@ -142,6 +146,7 @@ class RepostAccountingLedger(Document):
account_repost_doc=self.name,
is_async=True,
job_name=job_name,
enqueue_after_commit=True,
)
frappe.msgprint(_("Repost has started in the background"))
else:
@@ -210,16 +215,22 @@ def start_repost(account_repost_doc=str) -> None:
def get_allowed_types_from_settings(child_doc: bool = False):
repost_docs = [
x.document_type
for x in frappe.db.get_all(
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
)
]
# Avoid DISTINCT(...) here: Frappe applies a default ORDER BY which breaks on Postgres
# when used with SELECT DISTINCT.
repost_docs = frappe.db.get_all(
"Repost Allowed Types",
filters={"allowed": True},
pluck="document_type",
)
# De-dupe while preserving order (first occurrence wins)
repost_docs = list(dict.fromkeys(repost_docs))
result = repost_docs
if repost_docs and child_doc:
result.extend(get_child_docs(repost_docs))
# Keep uniqueness after extending
result = list(dict.fromkeys(result))
return result
@@ -286,8 +297,11 @@ def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters
if txt:
filters.update({"document_type": ("like", f"%{txt}%")})
if allowed_types := frappe.db.get_all(
"Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1
):
return allowed_types
return []
allowed_types = frappe.db.get_all(
"Repost Allowed Types",
filters=filters,
pluck="document_type",
)
allowed_types = list(dict.fromkeys(allowed_types))
return [[dt] for dt in allowed_types]

View File

@@ -14,6 +14,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
erpnext.selling.SellingController
) {
setup(doc) {
this.setup_accounting_dimension_triggers();
this.setup_posting_date_time_check();
super.setup(doc);
this.frm.make_methods = {
@@ -42,6 +43,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) {
@@ -116,12 +118,20 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
return item.delivery_note ? true : false;
});
if (!from_delivery_note && !is_delivered_by_supplier) {
cur_frm.add_custom_button(
__("Delivery"),
cur_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.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")
);
}
}
}
@@ -647,10 +657,6 @@ cur_frm.cscript.expense_account = function (doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_rows(doc, cdt, cdn, "items", "expense_account");
};
cur_frm.cscript.cost_center = function (doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_rows(doc, cdt, cdn, "items", "cost_center");
};
cur_frm.set_query("debit_to", function (doc) {
return {
filters: {

View File

@@ -701,6 +701,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.dn_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"hide_days": 1,
@@ -2199,7 +2200,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-09-09 14:48:59.472826",
"modified": "2026-02-05 20:43:44.732805",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -362,21 +362,34 @@ class SalesInvoice(SellingController):
validate_docs_for_deferred_accounting([self.name], [])
def validate_fixed_asset(self):
for d in self.get("items"):
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
asset = frappe.get_doc("Asset", d.asset)
if self.doctype == "Sales Invoice" and self.docstatus == 1:
if self.update_stock:
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
if self.doctype != "Sales Invoice":
return
elif asset.status in ("Scrapped", "Cancelled", "Capitalized") or (
asset.status == "Sold" and not self.is_return
):
frappe.throw(
_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(
d.idx, d.asset, asset.status
for d in self.get("items"):
if d.is_fixed_asset:
if d.asset:
if not self.is_return:
asset_status = frappe.db.get_value("Asset", d.asset, "status")
if self.update_stock:
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
elif asset_status in ("Scrapped", "Cancelled", "Capitalized"):
frappe.throw(
_("Row #{0}: Asset {1} cannot be sold, it is already {2}").format(
d.idx, d.asset, asset_status
)
)
elif asset_status == "Sold" and not self.is_return:
frappe.throw(_("Row #{0}: Asset {1} is already sold").format(d.idx, d.asset))
elif not self.return_against:
frappe.throw(
_("Row #{0}: Return Against is required for returning asset").format(d.idx)
)
else:
frappe.throw(
_("Row #{0}: You must select an Asset for Item {1}.").format(d.idx, d.item_code),
title=_("Missing Asset"),
)
def validate_item_cost_centers(self):
for item in self.items:
@@ -465,6 +478,8 @@ class SalesInvoice(SellingController):
self.update_stock_reservation_entries()
self.update_stock_ledger()
self.process_asset_depreciation()
# this sequence because outstanding may get -ve
self.make_gl_entries()
@@ -561,6 +576,8 @@ class SalesInvoice(SellingController):
if self.update_stock == 1:
self.update_stock_ledger()
self.process_asset_depreciation()
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
@@ -1182,6 +1199,91 @@ class SalesInvoice(SellingController):
):
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def process_asset_depreciation(self):
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
self.depreciate_asset_on_sale()
else:
self.restore_asset()
self.update_asset()
def depreciate_asset_on_sale(self):
"""
Depreciate asset on sale or cancellation of return sales invoice
"""
disposal_date = self.get_disposal_date()
for d in self.get("items"):
if d.asset:
asset = frappe.get_doc("Asset", d.asset)
if asset.calculate_depreciation and asset.status != "Fully Depreciated":
depreciate_asset(asset, disposal_date, self.get_note_for_asset_sale(asset))
def get_note_for_asset_sale(self, asset):
return _("This schedule was created when Asset {0} was {1} through Sales Invoice {2}.").format(
get_link_to_form(asset.doctype, asset.name),
_("returned") if self.is_return else _("sold"),
get_link_to_form(self.doctype, self.get("name")),
)
def restore_asset(self):
"""
Restore asset on return or cancellation of original sales invoice
"""
for d in self.get("items"):
if d.asset:
asset = frappe.get_cached_doc("Asset", d.asset)
if asset.calculate_depreciation:
posting_date = self.get_disposal_date()
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
note = self.get_note_for_asset_return(asset)
reset_depreciation_schedule(asset, self.posting_date, note)
def get_note_for_asset_return(self, asset):
asset_link = get_link_to_form(asset.doctype, asset.name)
invoice_link = get_link_to_form(self.doctype, self.get("name"))
if self.is_return:
return _(
"This schedule was created when Asset {0} was returned through Sales Invoice {1}."
).format(asset_link, invoice_link)
else:
return _(
"This schedule was created when Asset {0} was restored due to Sales Invoice {1} cancellation."
).format(asset_link, invoice_link)
def update_asset(self):
"""
Update asset status, disposal date and asset activity on sale or return sales invoice
"""
def _update_asset(asset, disposal_date, note, asset_status=None):
frappe.db.set_value("Asset", d.asset, "disposal_date", disposal_date)
add_asset_activity(asset.name, note)
asset.set_status(asset_status)
disposal_date = self.get_disposal_date()
for d in self.get("items"):
if d.asset:
asset = frappe.get_cached_doc("Asset", d.asset)
if (self.is_return and self.docstatus == 1) or (not self.is_return and self.docstatus == 2):
note = _("Asset returned") if self.is_return else _("Asset sold")
asset_status, disposal_date = None, None
else:
note = _("Asset sold") if not self.is_return else _("Return invoice of asset cancelled")
asset_status = "Sold"
_update_asset(asset, disposal_date, note, asset_status)
def get_disposal_date(self):
if self.is_return:
disposal_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
else:
disposal_date = self.posting_date
return disposal_date
def make_gl_entries(self, gl_entries=None, from_repost=False):
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
@@ -1349,73 +1451,17 @@ class SalesInvoice(SellingController):
)
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")) or item.is_fixed_asset:
if (
flt(item.base_net_amount, item.precision("base_net_amount"))
or item.is_fixed_asset
or enable_discount_accounting
):
# Do not book income for transfer within same company
if self.is_internal_transfer():
continue
if item.is_fixed_asset:
asset = self.get_asset(item)
if (self.docstatus == 2 and not self.is_return) or (
self.docstatus == 1 and self.is_return
):
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", None)
add_asset_activity(asset.name, _("Asset returned"))
asset_status = asset.get_status()
if asset.calculate_depreciation and not asset_status == "Fully Depreciated":
posting_date = (
frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
if self.is_return
else self.posting_date
)
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
notes = _(
"This schedule was created when Asset {0} was returned through Sales Invoice {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.doctype, self.get("name")),
)
reset_depreciation_schedule(asset, self.posting_date, notes)
asset.reload()
else:
if asset.calculate_depreciation:
if not asset.status == "Fully Depreciated":
notes = _(
"This schedule was created when Asset {0} was sold through Sales Invoice {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.doctype, self.get("name")),
)
depreciate_asset(asset, self.posting_date, notes)
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", self.posting_date)
add_asset_activity(asset.name, _("Asset sold"))
for gle in fixed_asset_gl_entries:
gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item))
self.set_asset_status(asset)
if item.is_fixed_asset and item.asset:
self.get_gl_entries_for_fixed_asset(item, gl_entries)
else:
income_account = (
@@ -1451,17 +1497,31 @@ class SalesInvoice(SellingController):
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
gl_entries += super().get_gl_entries()
def get_asset(self, item):
if item.get("asset"):
asset = frappe.get_doc("Asset", item.asset)
def get_gl_entries_for_fixed_asset(self, item, gl_entries):
asset = frappe.get_cached_doc("Asset", item.asset)
if self.is_return:
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
else:
frappe.throw(
_("Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
title=_("Missing Asset"),
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
self.check_finance_books(item, asset)
return asset
for gle in fixed_asset_gl_entries:
gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item))
@property
def enable_discount_accounting(self):
@@ -2151,7 +2211,9 @@ def make_delivery_note(source_name, target_doc=None):
"cost_center": "cost_center",
},
"postprocess": update_item,
"condition": lambda doc: doc.delivered_by_supplier != 1,
"condition": lambda doc: doc.delivered_by_supplier != 1
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

@@ -2974,6 +2974,60 @@ class TestSalesInvoice(FrappeTestCase):
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()
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
def test_sales_invoice_with_discount_accounting_enabled(self):
discount_account = create_account(
@@ -4721,6 +4775,66 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(q[0][0], 1)
@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

@@ -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,7 +85,7 @@
],
"istable": 1,
"links": [],
"modified": "2024-01-23 16:20:06.436979",
"modified": "2026-02-16 20:46:34.592604",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",
@@ -95,4 +95,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -80,7 +80,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "rate",
"fieldtype": "Int",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -102,7 +102,7 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -199,7 +199,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Int",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -221,7 +221,7 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -324,7 +324,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-01-10 18:32:36.201124",
"modified": "2025-12-10 08:06:40.611761",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Share Balance",
@@ -339,4 +339,4 @@
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
}

View File

@@ -14,7 +14,7 @@ class ShareBalance(Document):
if TYPE_CHECKING:
from frappe.types import DF
amount: DF.Int
amount: DF.Currency
current_state: DF.Literal["", "Issued", "Purchased"]
from_no: DF.Int
is_company: DF.Check
@@ -22,7 +22,7 @@ class ShareBalance(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
rate: DF.Int
rate: DF.Currency
share_type: DF.Link
to_no: DF.Int
# end: auto-generated types

View File

@@ -85,6 +85,9 @@ def get_party_details(inv):
if inv.doctype == "Sales Invoice":
party_type = "Customer"
party = inv.customer
elif inv.doctype == "Journal Entry":
party_type = inv.party_type
party = inv.party
else:
party_type = "Supplier"
party = inv.supplier
@@ -155,7 +158,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
party_type, parties, inv, tax_details, posting_date, pan_no
)
if party_type == "Supplier":
if party_type == "Supplier" or inv.doctype == "Journal Entry":
tax_row = get_tax_row_for_tds(tax_details, tax_amount)
else:
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
@@ -346,7 +349,10 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
elif party_type == "Customer":
if tax_deducted:
# if already TCS is charged, then amount will be calculated based on 'Previous Row Total'
tax_amount = 0
if inv.doctype == "Sales Invoice":
tax_amount = 0
else:
tax_amount = inv.base_tax_withholding_net_total * tax_details.rate / 100
else:
# if no TCS has been charged in FY,
# then chargeable value is "prev invoices + advances - advance_adjusted" value which cross the threshold
@@ -718,7 +724,7 @@ def get_advance_adjusted_in_invoice(inv):
def get_invoice_total_without_tcs(inv, tax_details):
tcs_tax_row = [d for d in inv.taxes if d.account_head == tax_details.account_head]
tcs_tax_row = [d for d in inv.get("taxes") or [] if d.account_head == tax_details.account_head]
tcs_tax_row_amount = tcs_tax_row[0].base_tax_amount if tcs_tax_row else 0
return inv.grand_total - tcs_tax_row_amount

View File

@@ -848,6 +848,90 @@ class TestTaxWithholdingCategory(FrappeTestCase):
self.assertEqual(payment.taxes[0].tax_amount, 6000)
self.assertEqual(payment.taxes[0].allocated_amount, 6000)
def test_tds_on_journal_entry_for_supplier(self):
"""Test TDS deduction for Supplier in Debit Note"""
frappe.db.set_value(
"Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS"
)
jv = make_journal_entry_with_tax_withholding(
party_type="Supplier",
party="Test TDS Supplier",
voucher_type="Debit Note",
amount=50000,
save=False,
)
jv.apply_tds = 1
jv.tax_withholding_category = "Cumulative Threshold TDS"
jv.save()
# Again saving should not change tds amount
jv.user_remark = "Test TDS on Journal Entry for Supplier"
jv.save()
jv.submit()
# TDS = 50000 * 10% = 5000
self.assertEqual(len(jv.accounts), 3)
# Find TDS account row
tds_row = None
supplier_row = None
for row in jv.accounts:
if row.account == "TDS - _TC":
tds_row = row
elif row.party == "Test TDS Supplier":
supplier_row = row
self.assertEqual(tds_row.credit, 5000)
self.assertEqual(tds_row.debit, 0)
# Supplier amount should be reduced by TDS
self.assertEqual(supplier_row.credit, 45000)
jv.cancel()
def test_tcs_on_journal_entry_for_customer(self):
"""Test TCS collection for Customer in Credit Note"""
frappe.db.set_value(
"Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"
)
# Create Credit Note with amount exceeding threshold
jv = make_journal_entry_with_tax_withholding(
party_type="Customer",
party="Test TCS Customer",
voucher_type="Credit Note",
amount=50000,
save=False,
)
jv.apply_tds = 1
jv.tax_withholding_category = "Cumulative Threshold TCS"
jv.save()
# Again saving should not change tds amount
jv.user_remark = "Test TCS on Journal Entry for Customer"
jv.save()
jv.submit()
# Assert TCS calculation (10% on amount above threshold of 30000)
self.assertEqual(len(jv.accounts), 3)
# Find TCS account row
tcs_row = None
customer_row = None
for row in jv.accounts:
if row.account == "TCS - _TC":
tcs_row = row
elif row.party == "Test TCS Customer":
customer_row = row
# TCS should be credited (liability to government)
self.assertEqual(tcs_row.credit, 2000) # above threshold 20000*10%
self.assertEqual(tcs_row.debit, 0)
# Customer amount should be increased by TCS
self.assertEqual(customer_row.debit, 52000)
jv.cancel()
def cancel_invoices():
purchase_invoices = frappe.get_all(
@@ -996,6 +1080,88 @@ def create_payment_entry(**args):
return pe
def make_journal_entry_with_tax_withholding(
party_type,
party,
voucher_type,
amount,
cost_center=None,
posting_date=None,
save=True,
submit=False,
):
"""Helper function to create Journal Entry for tax withholding"""
if not cost_center:
cost_center = "_Test Cost Center - _TC"
jv = frappe.new_doc("Journal Entry")
jv.posting_date = posting_date or today()
jv.company = "_Test Company"
jv.voucher_type = voucher_type
jv.multi_currency = 0
if party_type == "Supplier":
# Debit Note: Expense Dr, Supplier Cr
expense_account = "Stock Received But Not Billed - _TC"
party_account = "Creditors - _TC"
jv.append(
"accounts",
{
"account": expense_account,
"cost_center": cost_center,
"debit_in_account_currency": amount,
"exchange_rate": 1,
},
)
jv.append(
"accounts",
{
"account": party_account,
"party_type": party_type,
"party": party,
"cost_center": cost_center,
"credit_in_account_currency": amount,
"exchange_rate": 1,
},
)
else: # Customer
# Credit Note: Customer Dr, Income Cr
party_account = "Debtors - _TC"
income_account = "Sales - _TC"
jv.append(
"accounts",
{
"account": party_account,
"party_type": party_type,
"party": party,
"cost_center": cost_center,
"debit_in_account_currency": amount,
"exchange_rate": 1,
},
)
jv.append(
"accounts",
{
"account": income_account,
"cost_center": cost_center,
"credit_in_account_currency": amount,
"exchange_rate": 1,
},
)
if save or submit:
jv.insert()
if submit:
jv.submit()
return jv
def create_records():
# create a new suppliers
for name in [

View File

@@ -12,6 +12,7 @@ from frappe.utils import cint, flt, formatdate, get_link_to_form, getdate, now
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
@@ -199,19 +200,20 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_r
for d in gl_map:
cost_center = d.get("cost_center")
cost_center_allocation = get_cost_center_allocation_data(
gl_map[0]["company"], gl_map[0]["posting_date"], cost_center
)
if not cost_center_allocation:
new_gl_map.append(d)
continue
# Validate budget against main cost center
if not from_repost:
validate_expense_against_budget(
d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
)
cost_center_allocation = get_cost_center_allocation_data(
gl_map[0]["company"], gl_map[0]["posting_date"], cost_center
)
if not cost_center_allocation:
new_gl_map.append(d)
continue
if d.account == round_off_account:
d.cost_center = cost_center_allocation[0][0]
new_gl_map.append(d)
@@ -289,7 +291,9 @@ def merge_similar_entries(gl_map, precision=None):
company_currency = erpnext.get_company_currency(company)
if not precision:
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
precision = get_field_precision(
frappe.get_meta("GL Entry").get_field("debit"), currency=company_currency
)
# filter zero debit and credit entries
merged_gl_map = filter(
@@ -412,7 +416,11 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle.flags.notify_update = False
gle.submit()
if not from_repost and gle.voucher_type != "Period Closing Voucher":
if (
not from_repost
and gle.voucher_type != "Period Closing Voucher"
and (gle.is_cancelled == 0 or gle.voucher_type == "Journal Entry")
):
validate_expense_against_budget(args)
@@ -605,6 +613,18 @@ def update_accounting_dimensions(round_off_gle):
for dimension in dimensions:
round_off_gle[dimension] = dimension_values.get(dimension)
else:
report_type = frappe.get_cached_value("Account", round_off_gle.account, "report_type")
for dimension in get_checks_for_pl_and_bs_accounts():
if (
round_off_gle.company == dimension.company
and (
(report_type == "Profit and Loss" and dimension.mandatory_for_pl)
or (report_type == "Balance Sheet" and dimension.mandatory_for_bs)
)
and dimension.default_dimension
):
round_off_gle[dimension.fieldname] = dimension.default_dimension
def get_round_off_account_and_cost_center(company, voucher_type, voucher_no, use_company_default=False):
@@ -784,12 +804,19 @@ def validate_against_pcv(is_opening, posting_date, company):
title=_("Invalid Opening Entry"),
)
last_pcv_date = frappe.db.get_value(
"Period Closing Voucher", {"docstatus": 1, "company": company}, "max(period_end_date)"
)
# Local import so you don't have to touch file-level imports
from frappe.query_builder.functions import Max
pcv = frappe.qb.DocType("Period Closing Voucher")
last_pcv_date = (
frappe.qb.from_(pcv)
.select(Max(pcv.period_end_date))
.where((pcv.docstatus == 1) & (pcv.company == company))
).run(pluck=True)[0]
if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
message = _("Books have been closed till the period ending on {0}").format(formatdate(last_pcv_date))
message = _("Books have been closed till the period ending on {0}.").format(formatdate(last_pcv_date))
message += "</br >"
message += _("You cannot create/amend any accounting entries till this date.")
frappe.throw(message, title=_("Period Closed"))

View File

@@ -1056,3 +1056,21 @@ def add_party_account(party_type, party, company, account):
def render_address(address, check_permissions=True):
return frappe.call(_render_address, address, check_permissions=check_permissions)
def validate_party_currency_before_merging(party_type, old_party, new_party):
for company in frappe.get_all("Company"):
old_party_currency = get_party_gle_currency(party_type, old_party, company.name)
new_party_currency = get_party_gle_currency(party_type, new_party, company.name)
if old_party_currency and new_party_currency and old_party_currency != new_party_currency:
frappe.throw(
_(
"Cannot merge {0} '{1}' into '{2}' as both have existing accounting entries in different currencies for company '{3}'."
).format(
party_type,
old_party,
new_party,
company.name,
)
)

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

@@ -26,16 +26,13 @@ frappe.query_reports["Accounts Payable"] = {
{
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "Link",
options: "Cost Center",
get_query: () => {
var company = frappe.query_report.get_filter_value("company");
return {
filters: {
company: company,
},
};
fieldtype: "MultiSelectList",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
options: "Cost Center",
},
{
fieldname: "party_account",
@@ -168,6 +165,10 @@ frappe.query_reports["Accounts Payable"] = {
var filters = report.get_values();
frappe.set_route("query-report", "Accounts Payable Summary", { company: filters.company });
});
if (frappe.boot.sysdefaults.default_ageing_range) {
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
}
},
};

View File

@@ -45,16 +45,13 @@ frappe.query_reports["Accounts Payable Summary"] = {
{
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "Link",
options: "Cost Center",
get_query: () => {
var company = frappe.query_report.get_filter_value("company");
return {
filters: {
company: company,
},
};
fieldtype: "MultiSelectList",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
options: "Cost Center",
},
{
fieldname: "party_type",
@@ -105,6 +102,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
label: __("Revaluation Journals"),
fieldtype: "Check",
},
{
fieldname: "show_gl_balance",
label: __("Show GL Balance"),
fieldtype: "Check",
},
],
onload: function (report) {
@@ -112,6 +114,10 @@ frappe.query_reports["Accounts Payable Summary"] = {
var filters = report.get_values();
frappe.set_route("query-report", "Accounts Payable", { company: filters.company });
});
if (frappe.boot.sysdefaults.default_ageing_range) {
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
}
},
};

View File

@@ -28,16 +28,13 @@ frappe.query_reports["Accounts Receivable"] = {
{
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "Link",
options: "Cost Center",
get_query: () => {
var company = frappe.query_report.get_filter_value("company");
return {
filters: {
company: company,
},
};
fieldtype: "MultiSelectList",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
options: "Cost Center",
},
{
fieldname: "party_type",
@@ -195,6 +192,10 @@ frappe.query_reports["Accounts Receivable"] = {
var filters = report.get_values();
frappe.set_route("query-report", "Accounts Receivable Summary", { company: filters.company });
});
if (frappe.boot.sysdefaults.default_ageing_range) {
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
}
},
};

View File

@@ -15,6 +15,7 @@ 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.accounts.utils import (
build_qb_match_conditions,
get_advance_payment_doctypes,
@@ -994,11 +995,7 @@ class ReceivablePayableReport:
self.add_accounting_dimensions_filters()
def get_cost_center_conditions(self):
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
cost_center_list = [
center.name
for center in frappe.get_list("Cost Center", filters={"lft": (">=", lft), "rgt": ("<=", rgt)})
]
cost_center_list = get_cost_centers_with_children(self.filters.cost_center)
self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list))
def add_common_filters(self):

View File

@@ -45,16 +45,13 @@ frappe.query_reports["Accounts Receivable Summary"] = {
{
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "Link",
options: "Cost Center",
get_query: () => {
var company = frappe.query_report.get_filter_value("company");
return {
filters: {
company: company,
},
};
fieldtype: "MultiSelectList",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
options: "Cost Center",
},
{
fieldname: "party_type",
@@ -140,6 +137,10 @@ frappe.query_reports["Accounts Receivable Summary"] = {
var filters = report.get_values();
frappe.set_route("query-report", "Accounts Receivable", { company: filters.company });
});
if (frappe.boot.sysdefaults.default_ageing_range) {
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
}
},
};

View File

@@ -53,7 +53,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
)
if self.filters.show_gl_balance:
gl_balance_map = get_gl_balance(self.filters.report_date, self.filters.company)
gl_balance_map = get_gl_balance(self.filters.report_date, self.filters.company, self.account_type)
for party, party_dict in self.party_total.items():
if flt(party_dict.outstanding, self.currency_precision) == 0:
@@ -206,11 +206,15 @@ class AccountsReceivableSummary(ReceivablePayableReport):
)
def get_gl_balance(report_date, company):
def get_gl_balance(report_date, company, account_type):
if account_type == "Payable":
balance_calc_fields = ["party", "SUM(credit - debit) AS balance"]
else:
balance_calc_fields = ["party", "SUM(debit - credit) AS balance"]
return frappe._dict(
frappe.db.get_all(
"GL Entry",
fields=["party", "sum(debit - credit)"],
fields=balance_calc_fields,
filters={"posting_date": ("<=", report_date), "is_cancelled": 0, "company": company},
group_by="party",
as_list=1,

View File

@@ -69,12 +69,18 @@ class PartyLedgerSummaryReport:
party_type = self.filters.party_type
doctype = qb.DocType(party_type)
party_details_fields = [
doctype.name.as_("party"),
f"{scrub(party_type)}_name",
f"{scrub(party_type)}_group",
]
if party_type == "Customer":
party_details_fields.append(doctype.territory)
conditions = self.get_party_conditions(doctype)
query = (
qb.from_(doctype)
.select(doctype.name.as_("party"), f"{scrub(party_type)}_name")
.where(Criterion.all(conditions))
)
query = qb.from_(doctype).select(*party_details_fields).where(Criterion.all(conditions))
from frappe.desk.reportview import build_match_conditions
@@ -153,6 +159,31 @@ class PartyLedgerSummaryReport:
credit_or_debit_note = "Credit Note" if self.filters.party_type == "Customer" else "Debit Note"
if self.filters.party_type == "Customer":
columns += [
{
"label": _("Customer Group"),
"fieldname": "customer_group",
"fieldtype": "Link",
"options": "Customer Group",
},
{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
},
]
else:
columns += [
{
"label": _("Supplier Group"),
"fieldname": "supplier_group",
"fieldtype": "Link",
"options": "Supplier Group",
}
]
columns += [
{
"label": _("Opening Balance"),
@@ -213,35 +244,6 @@ class PartyLedgerSummaryReport:
},
]
# Hidden columns for handling 'User Permissions'
if self.filters.party_type == "Customer":
columns += [
{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
"hidden": 1,
},
{
"label": _("Customer Group"),
"fieldname": "customer_group",
"fieldtype": "Link",
"options": "Customer Group",
"hidden": 1,
},
]
else:
columns += [
{
"label": _("Supplier Group"),
"fieldname": "supplier_group",
"fieldtype": "Link",
"options": "Supplier Group",
"hidden": 1,
}
]
return columns
def get_data(self):

View File

@@ -482,7 +482,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot
immutable_ledger = frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger")
def update_value_in_dict(data, key, gle):
def update_value_in_dict(data, key, gle, show_net_values=False):
data[key].debit += gle.debit
data[key].credit += gle.credit
@@ -493,10 +493,14 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot
data[key].debit_in_transaction_currency += gle.debit_in_transaction_currency
data[key].credit_in_transaction_currency += gle.credit_in_transaction_currency
if filters.get("show_net_values_in_party_account") and account_type_map.get(data[key].account) in (
"Receivable",
"Payable",
):
if (
filters.get("show_net_values_in_party_account")
and account_type_map.get(data[key].account)
in (
"Receivable",
"Payable",
)
) or show_net_values:
net_value = data[key].debit - data[key].credit
net_value_in_account_currency = (
data[key].debit_in_account_currency - data[key].credit_in_account_currency
@@ -526,11 +530,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot
if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
if not group_by_voucher_consolidated:
update_value_in_dict(gle_map[group_by_value].totals, "opening", gle)
update_value_in_dict(gle_map[group_by_value].totals, "closing", gle)
update_value_in_dict(gle_map[group_by_value].totals, "opening", gle, True)
update_value_in_dict(gle_map[group_by_value].totals, "closing", gle, True)
update_value_in_dict(totals, "opening", gle)
update_value_in_dict(totals, "closing", gle)
update_value_in_dict(totals, "opening", gle, True)
update_value_in_dict(totals, "closing", gle, True)
elif gle.posting_date <= to_date or (cstr(gle.is_opening) == "Yes" and show_opening_entries):
if not group_by_voucher_consolidated:

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

@@ -465,7 +465,7 @@ class TestGrossProfit(FrappeTestCase):
"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]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
@@ -642,21 +642,24 @@ class TestGrossProfit(FrappeTestCase):
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)
@@ -668,7 +671,7 @@ class TestGrossProfit(FrappeTestCase):
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]
@@ -677,3 +680,63 @@ class TestGrossProfit(FrappeTestCase):
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

@@ -5,7 +5,6 @@
import frappe
from frappe import _
from frappe.utils import flt
from pypika import Order
import erpnext
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (
@@ -16,7 +15,7 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
get_group_by_and_display_fields,
get_tax_accounts,
)
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
from erpnext.accounts.report.utils import get_values_for_columns
def execute(filters=None):
@@ -41,16 +40,6 @@ def _execute(filters=None, additional_table_columns=None):
tax_doctype="Purchase Taxes and Charges",
)
scrubbed_tax_fields = {}
for tax in tax_columns:
scrubbed_tax_fields.update(
{
tax + " Rate": frappe.scrub(tax + " Rate"),
tax + " Amount": frappe.scrub(tax + " Amount"),
}
)
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
data = []
@@ -100,8 +89,8 @@ def _execute(filters=None, additional_table_columns=None):
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(
{
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
f"{tax}_rate": item_tax.get("tax_rate", 0),
f"{tax}_amount": item_tax.get("tax_amount", 0),
}
)
total_tax += flt(item_tax.get("tax_amount"))

View File

@@ -6,7 +6,7 @@ import frappe
from frappe import _
from frappe.model.meta import get_field_precision
from frappe.query_builder import functions as fn
from frappe.utils import cstr, flt
from frappe.utils import flt
from frappe.utils.nestedset import get_descendants_of
from frappe.utils.xlsxutils import handle_html
@@ -32,16 +32,6 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
scrubbed_tax_fields = {}
for tax in tax_columns:
scrubbed_tax_fields.update(
{
tax + " Rate": frappe.scrub(tax + " Rate"),
tax + " Amount": frappe.scrub(tax + " Amount"),
}
)
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
so_dn_map = get_delivery_notes_against_sales_order(item_list)
@@ -102,8 +92,8 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(
{
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
f"{tax}_rate": item_tax.get("tax_rate", 0),
f"{tax}_amount": item_tax.get("tax_amount", 0),
}
)
if item_tax.get("is_other_charges"):
@@ -115,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,
}
)
@@ -546,9 +536,10 @@ def get_tax_accounts(
import json
item_row_map = {}
tax_columns = []
tax_columns = {}
invoice_item_row = {}
itemised_tax = {}
scrubbed_description_map = {}
add_deduct_tax = "charge_type"
tax_amount_precision = (
@@ -605,9 +596,14 @@ def get_tax_accounts(
tax_amount,
) in tax_details:
description = handle_html(description)
if description not in tax_columns and tax_amount:
scrubbed_description = scrubbed_description_map.get(description)
if not scrubbed_description:
scrubbed_description = frappe.scrub(description)
scrubbed_description_map[description] = scrubbed_description
if scrubbed_description not in tax_columns and tax_amount:
# as description is text editor earlier and markup can break the column convention in reports
tax_columns.append(description)
tax_columns[scrubbed_description] = description
if item_wise_tax_detail:
try:
@@ -641,7 +637,7 @@ def get_tax_accounts(
else tax_value
)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict(
itemised_tax.setdefault(d.name, {})[scrubbed_description] = frappe._dict(
{
"tax_rate": tax_rate,
"tax_amount": tax_value,
@@ -653,7 +649,7 @@ def get_tax_accounts(
continue
elif charge_type == "Actual" and tax_amount:
for d in invoice_item_row.get(parent, []):
itemised_tax.setdefault(d.name, {})[description] = frappe._dict(
itemised_tax.setdefault(d.name, {})[scrubbed_description] = frappe._dict(
{
"tax_rate": "NA",
"tax_amount": flt(
@@ -662,12 +658,14 @@ def get_tax_accounts(
}
)
tax_columns.sort()
for desc in tax_columns:
tax_columns_list = list(tax_columns.keys())
tax_columns_list.sort()
for scrubbed_desc in tax_columns_list:
desc = tax_columns[scrubbed_desc]
columns.append(
{
"label": _(desc + " Rate"),
"fieldname": frappe.scrub(desc + " Rate"),
"fieldname": f"{scrubbed_desc}_rate",
"fieldtype": "Float",
"width": 100,
}
@@ -676,7 +674,7 @@ def get_tax_accounts(
columns.append(
{
"label": _(desc + " Amount"),
"fieldname": frappe.scrub(desc + " Amount"),
"fieldname": f"{scrubbed_desc}_amount",
"fieldtype": "Currency",
"options": "currency",
"width": 100,
@@ -714,7 +712,7 @@ def get_tax_accounts(
},
]
return itemised_tax, tax_columns
return itemised_tax, tax_columns_list
def add_total_row(
@@ -807,5 +805,5 @@ def add_sub_total_row(item, total_row_map, group_by_value, tax_columns):
total_row["percent_gt"] += item["percent_gt"]
for tax in tax_columns:
total_row.setdefault(frappe.scrub(tax + " Amount"), 0.0)
total_row[frappe.scrub(tax + " Amount")] += flt(item[frappe.scrub(tax + " Amount")])
total_row.setdefault(f"{tax}_amount", 0.0)
total_row[f"{tax}_amount"] += flt(item[f"{tax}_amount"])

View File

@@ -159,11 +159,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

@@ -51,7 +51,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
entries = {}
for name, details in gle_map.items():
for entry in details:
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
tax_amount, total_amount, grand_total, base_total, base_tax_withholding_net_total = 0, 0, 0, 0, 0
tax_withholding_category, rate = None, None
bill_no, bill_date = "", ""
party = entry.party or entry.against
@@ -83,6 +83,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
# back calculate total amount from rate and tax_amount
base_total = min(flt(tax_amount / (rate / 100), precision=precision), values[0])
total_amount = grand_total = base_total
base_tax_withholding_net_total = total_amount
else:
if tax_amount and rate:
@@ -93,12 +94,16 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
grand_total = values[1]
base_total = values[2]
base_tax_withholding_net_total = total_amount
if voucher_type == "Purchase Invoice":
base_tax_withholding_net_total = values[0]
bill_no = values[3]
bill_date = values[4]
else:
total_amount += entry.credit
base_tax_withholding_net_total = total_amount
if tax_amount:
if party_map.get(party, {}).get("party_type") == "Supplier":
@@ -125,6 +130,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
"rate": rate,
"total_amount": total_amount,
"grand_total": grand_total,
"base_tax_withholding_net_total": base_tax_withholding_net_total,
"base_total": base_total,
"tax_amount": tax_amount,
"transaction_date": posting_date,
@@ -252,14 +258,14 @@ def get_columns(filters):
"width": 60,
},
{
"label": _("Total Amount"),
"fieldname": "total_amount",
"label": _("Tax Withholding Net Total"),
"fieldname": "base_tax_withholding_net_total",
"fieldtype": "Float",
"width": 120,
"width": 150,
},
{
"label": _("Base Total"),
"fieldname": "base_total",
"label": _("Taxable Amount"),
"fieldname": "total_amount",
"fieldtype": "Float",
"width": 120,
},
@@ -270,10 +276,16 @@ def get_columns(filters):
"width": 120,
},
{
"label": _("Grand Total"),
"label": _("Grand Total (Company Currency)"),
"fieldname": "base_total",
"fieldtype": "Float",
"width": 150,
},
{
"label": _("Grand Total (Transaction Currency)"),
"fieldname": "grand_total",
"fieldtype": "Float",
"width": 120,
"width": 170,
},
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
{

View File

@@ -35,9 +35,9 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
result = execute(filters)[1]
expected_values = [
# Check for JV totals using back calculation logic
[jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
[si.name, "TCS", 0.075, 1000, 0.52, 1000.52],
[jv.name, "TCS", 0.075, -10000.0, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 706.67, 2550.0, 0.53, 2550.53],
[si.name, "TCS", 0.075, 693.33, 1000.0, 0.52, 1000.52],
]
self.check_expected_values(result, expected_values)
@@ -55,8 +55,8 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
frappe._dict(company="_Test Company", party_type="Supplier", from_date=today(), to_date=today())
)[1]
expected_values = [
[inv_1.name, "TDS - 1", 10, 5000, 500, 5500],
[inv_2.name, "TDS - 2", 20, 5000, 1000, 6000],
[inv_1.name, "TDS - 1", 10, 5000, 5000, 500, 5500],
[inv_2.name, "TDS - 2", 20, 5000, 5000, 1000, 6000],
]
self.check_expected_values(result, expected_values)
@@ -107,8 +107,8 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
)[1]
expected_values = [
[inv_1.name, "TDS - 3", 10.0, 5000, 500, 4500],
[inv_2.name, "TDS - 3", 20.0, 5000, 1000, 4000],
[inv_1.name, "TDS - 3", 10.0, 5000, 5000, 500, 4500],
[inv_2.name, "TDS - 3", 20.0, 5000, 5000, 1000, 4000],
]
self.check_expected_values(result, expected_values)
@@ -120,6 +120,7 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
voucher.ref_no,
voucher.section_code,
voucher.rate,
voucher.base_tax_withholding_net_total,
voucher.base_total,
voucher.tax_amount,
voucher.grand_total,

View File

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

View File

@@ -47,22 +47,23 @@ frappe.query_reports["Trial Balance"] = {
{
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "Link",
options: "Cost Center",
get_query: function () {
var company = frappe.query_report.get_filter_value("company");
return {
doctype: "Cost Center",
filters: {
company: company,
},
};
fieldtype: "MultiSelectList",
get_data: function (txt) {
return frappe.db.get_link_options("Cost Center", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
options: "Cost Center",
},
{
fieldname: "project",
label: __("Project"),
fieldtype: "Link",
fieldtype: "MultiSelectList",
get_data: function (txt) {
return frappe.db.get_link_options("Project", txt, {
company: frappe.query_report.get_filter_value("company"),
});
},
options: "Project",
},
{
@@ -111,6 +112,12 @@ frappe.query_reports["Trial Balance"] = {
fieldtype: "Check",
default: 1,
},
{
fieldname: "show_group_accounts",
label: __("Show Group Accounts"),
fieldtype: "Check",
default: 1,
},
],
formatter: erpnext.financial_statements.formatter,
tree: true,

View File

@@ -15,6 +15,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
from erpnext.accounts.report.financial_statements import (
filter_accounts,
filter_out_zero_value_rows,
get_cost_centers_with_children,
set_gl_entries_by_account,
)
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
@@ -82,7 +83,7 @@ def validate_filters(filters):
def get_data(filters):
accounts = frappe.db.sql(
"""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
"""select name, account_number, parent_account, account_name, root_type, report_type, is_group, lft, rgt
from `tabAccount` where company=%s order by lft""",
filters.company,
@@ -103,10 +104,6 @@ def get_data(filters):
opening_balances = get_opening_balances(filters, ignore_is_opening)
# add filter inside list so that the query in financial_statements.py doesn't break
if filters.project:
filters.project = [filters.project]
set_gl_entries_by_account(
filters.company,
filters.from_date,
@@ -270,18 +267,12 @@ def get_opening_balance(
opening_balance = opening_balance.where(closing_balance.voucher_type != "Period Closing Voucher")
if filters.cost_center:
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
cost_center = frappe.qb.DocType("Cost Center")
opening_balance = opening_balance.where(
closing_balance.cost_center.isin(
frappe.qb.from_(cost_center)
.select("name")
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
)
closing_balance.cost_center.isin(get_cost_centers_with_children(filters.get("cost_center")))
)
if filters.project:
opening_balance = opening_balance.where(closing_balance.project == filters.project)
opening_balance = opening_balance.where(closing_balance.project.isin(filters.project))
if frappe.db.count("Finance Book"):
if filters.get("include_default_book_entries"):
@@ -402,13 +393,14 @@ def prepare_data(accounts, filters, parent_children_map, company_currency):
"from_date": filters.from_date,
"to_date": filters.to_date,
"currency": company_currency,
"is_group_account": d.is_group,
"account_name": (
f"{d.account_number} - {d.account_name}" if d.account_number else d.account_name
),
}
for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3)
row[key] = flt(d.get(key, 0.0))
if abs(row[key]) >= get_zero_cutoff(company_currency):
# ignore zero values
@@ -418,6 +410,10 @@ def prepare_data(accounts, filters, parent_children_map, company_currency):
data.append(row)
total_row = calculate_total_row(accounts, company_currency)
if not filters.get("show_group_accounts"):
data = hide_group_accounts(data)
data.extend([{}, total_row])
return data
@@ -497,3 +493,12 @@ def prepare_opening_closing(row):
row[valid_col] = 0.0
else:
row[reverse_col] = 0.0
def hide_group_accounts(data):
non_group_accounts_data = []
for d in data:
if not d.get("is_group_account"):
d.update(indent=0)
non_group_accounts_data.append(d)
return non_group_accounts_data

View File

@@ -81,5 +81,11 @@ frappe.query_reports["Trial Balance for Party"] = {
label: __("Show zero values"),
fieldtype: "Check",
},
{
fieldname: "exclude_zero_balance_parties",
label: __("Exclude Zero Balance Parties"),
fieldtype: "Check",
default: 1,
},
],
};

View File

@@ -75,20 +75,20 @@ def get_data(filters, show_party_name):
closing_debit, closing_credit = toggle_debit_credit(opening_debit + debit, opening_credit + credit)
row.update({"closing_debit": closing_debit, "closing_credit": closing_credit})
# totals
for col in total_row:
total_row[col] += row.get(col)
row.update({"currency": company_currency})
has_value = False
if opening_debit or opening_credit or debit or credit or closing_debit or closing_credit:
has_value = True
# Exclude zero balance parties if filter is set
if filters.get("exclude_zero_balance_parties") and not closing_debit and not closing_credit:
continue
if cint(filters.show_zero_values) or has_value:
data.append(row)
# Add total row
# totals
for col in total_row:
total_row[col] += row.get(col)
total_row.update({"party": "'" + _("Totals") + "'", "currency": company_currency})
data.append(total_row)

View File

@@ -511,6 +511,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
@@ -1867,6 +1868,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

@@ -80,6 +80,12 @@ frappe.ui.form.on("Asset", {
}
},
before_submit: function (frm) {
if (frm.doc.is_composite_asset && !frm.has_active_capitalization) {
frappe.throw(__("Please capitalize this asset before submitting."));
}
},
refresh: function (frm) {
frappe.ui.form.trigger("Asset", "is_existing_asset");
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
@@ -200,9 +206,10 @@ frappe.ui.form.on("Asset", {
asset: frm.doc.name,
},
callback: function (r) {
frm.has_active_capitalization = r.message;
if (!r.message) {
$(".primary-action").prop("hidden", true);
$(".form-message").text("Capitalize this asset to confirm");
$(".form-message").text(__("Capitalize this asset before submitting."));
frm.add_custom_button(__("Capitalize Asset"), function () {
frm.trigger("create_asset_capitalization");
@@ -228,26 +235,64 @@ frappe.ui.form.on("Asset", {
},
toggle_reference_doc: function (frm) {
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
frm.set_df_property("purchase_invoice", "read_only", 1);
frm.set_df_property("purchase_receipt", "read_only", 1);
} else if (frm.doc.is_existing_asset || frm.doc.is_composite_asset) {
frm.toggle_reqd("purchase_receipt", 0);
frm.toggle_reqd("purchase_invoice", 0);
} else if (frm.doc.purchase_receipt) {
// if purchase receipt link is set then set PI disabled
frm.toggle_reqd("purchase_invoice", 0);
frm.set_df_property("purchase_invoice", "read_only", 1);
} else if (frm.doc.purchase_invoice) {
// if purchase invoice link is set then set PR disabled
frm.toggle_reqd("purchase_receipt", 0);
frm.set_df_property("purchase_receipt", "read_only", 1);
} else {
frm.toggle_reqd("purchase_receipt", 1);
frm.set_df_property("purchase_receipt", "read_only", 0);
frm.toggle_reqd("purchase_invoice", 1);
frm.set_df_property("purchase_invoice", "read_only", 0);
const is_submitted = frm.doc.docstatus === 1;
const is_special_asset = frm.doc.is_existing_asset || frm.doc.is_composite_asset;
const clear_field = (field) => {
if (frm.doc[field]) {
frm.set_value(field, "");
}
};
["purchase_receipt", "purchase_receipt_item", "purchase_invoice", "purchase_invoice_item"].forEach(
(field) => {
frm.toggle_reqd(field, 0);
frm.set_df_property(field, "read_only", 0);
}
);
if (is_submitted) {
[
"purchase_receipt",
"purchase_receipt_item",
"purchase_invoice",
"purchase_invoice_item",
].forEach((field) => {
frm.set_df_property(field, "read_only", 1);
});
return;
}
if (is_special_asset) {
clear_field("purchase_receipt");
clear_field("purchase_receipt_item");
clear_field("purchase_invoice");
clear_field("purchase_invoice_item");
return;
}
if (frm.doc.purchase_receipt) {
frm.toggle_reqd("purchase_receipt_item", 1);
["purchase_invoice", "purchase_invoice_item"].forEach((field) => {
clear_field(field);
frm.set_df_property(field, "read_only", 1);
});
return;
}
if (frm.doc.purchase_invoice) {
frm.toggle_reqd("purchase_invoice_item", 1);
["purchase_receipt", "purchase_receipt_item"].forEach((field) => {
clear_field(field);
frm.set_df_property(field, "read_only", 1);
});
return;
}
frm.toggle_reqd("purchase_receipt", 1);
frm.toggle_reqd("purchase_invoice", 1);
},
make_journal_entry: function (frm) {
@@ -274,8 +319,14 @@ frappe.ui.form.on("Asset", {
const row = [
sch["idx"],
frappe.format(sch["schedule_date"], { fieldtype: "Date" }),
frappe.format(sch["depreciation_amount"], { fieldtype: "Currency" }),
frappe.format(sch["accumulated_depreciation_amount"], { fieldtype: "Currency" }),
frappe.format(sch["depreciation_amount"], {
fieldtype: "Currency",
options: "Company:company:default_currency",
}),
frappe.format(sch["accumulated_depreciation_amount"], {
fieldtype: "Currency",
options: "Company:company:default_currency",
}),
sch["journal_entry"] || "",
];
@@ -468,11 +519,9 @@ frappe.ui.form.on("Asset", {
is_composite_asset: function (frm) {
if (frm.doc.is_composite_asset) {
frm.set_value("gross_purchase_amount", 0);
frm.set_df_property("gross_purchase_amount", "read_only", 1);
} else {
frm.set_df_property("gross_purchase_amount", "read_only", 0);
}
frm.trigger("toggle_reference_doc");
},
@@ -536,7 +585,6 @@ frappe.ui.form.on("Asset", {
callback: function (r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
$(".primary-action").prop("hidden", false);
},
});
},

View File

@@ -229,7 +229,8 @@
"fieldtype": "Currency",
"label": "Net Purchase Amount",
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
"options": "Company:company:default_currency"
"options": "Company:company:default_currency",
"read_only_depends_on": "eval: doc.is_composite_asset"
},
{
"fieldname": "available_for_use_date",
@@ -596,7 +597,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2025-11-17 18:01:51.417942",
"modified": "2025-12-23 16:01:10.195932",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -69,7 +69,6 @@ class Asset(AccountsController):
default_finance_book: DF.Link | None
department: DF.Link | None
depr_entry_posting_status: DF.Literal["", "Successful", "Failed"]
depreciation_completed: DF.Check
depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"]
disposal_date: DF.Date | None
finance_books: DF.Table[AssetFinanceBook]
@@ -159,6 +158,10 @@ class Asset(AccountsController):
self.total_asset_cost = self.gross_purchase_amount + self.additional_asset_cost
self.status = self.get_status()
def before_submit(self):
if self.is_composite_asset and not has_active_capitalization(self.name):
frappe.throw(_("Please capitalize this asset before submitting."))
def on_submit(self):
self.validate_in_use_date()
self.make_asset_movement()
@@ -477,6 +480,7 @@ class Asset(AccountsController):
def set_depreciation_rate(self):
for d in self.get("finance_books"):
self.validate_asset_finance_books(d)
d.rate_of_depreciation = flt(
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
)
@@ -485,6 +489,10 @@ class Asset(AccountsController):
row.expected_value_after_useful_life = flt(
row.expected_value_after_useful_life, self.precision("gross_purchase_amount")
)
if flt(row.expected_value_after_useful_life) < 0:
frappe.throw(_("Row {0}: Expected Value After Useful Life cannot be negative").format(row.idx))
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
frappe.throw(
_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount").format(
@@ -500,50 +508,71 @@ class Asset(AccountsController):
title=_("Invalid Schedule"),
)
row.depreciation_start_date = get_last_day(self.available_for_use_date)
self.validate_depreciation_start_date(row)
self.validate_total_number_of_depreciations_and_frequency(row)
if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0
self.opening_number_of_booked_depreciations = 0
else:
depreciable_amount = flt(
flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life),
self.precision("gross_purchase_amount"),
)
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
self.validate_opening_depreciation_values(row)
def validate_depreciation_start_date(self, row):
if row.depreciation_start_date:
if getdate(row.depreciation_start_date) < getdate(self.purchase_date):
frappe.throw(
_("Opening Accumulated Depreciation must be less than or equal to {0}").format(
depreciable_amount
_("Row #{0}: Next Depreciation Date cannot be before Purchase Date").format(row.idx)
)
if getdate(row.depreciation_start_date) < getdate(self.available_for_use_date):
frappe.throw(
_("Row #{0}: Next Depreciation Date cannot be before Available-for-use Date").format(
row.idx
)
)
if self.opening_accumulated_depreciation:
if not self.opening_number_of_booked_depreciations:
frappe.throw(_("Please set Opening Number of Booked Depreciations"))
else:
self.opening_number_of_booked_depreciations = 0
if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations):
frappe.throw(
_(
"Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations"
).format(row.idx),
title=_("Invalid Schedule"),
)
if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date):
else:
frappe.throw(
_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date").format(
row.idx
_("Row #{0}: Depreciation Start Date is required").format(row.idx),
title=_("Invalid Schedule"),
)
def validate_total_number_of_depreciations_and_frequency(self, row):
if row.total_number_of_depreciations <= 0:
frappe.throw(
_("Row #{0}: Total Number of Depreciations must be greater than zero").format(row.idx)
)
if row.frequency_of_depreciation <= 0:
frappe.throw(_("Row #{0}: Frequency of Depreciation must be greater than zero").format(row.idx))
def validate_opening_depreciation_values(self, row):
row.expected_value_after_useful_life = flt(
row.expected_value_after_useful_life, self.precision("gross_purchase_amount")
)
depreciable_amount = flt(
flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life),
self.precision("gross_purchase_amount"),
)
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
frappe.throw(
_("Opening Accumulated Depreciation must be less than or equal to {0}").format(
depreciable_amount
)
)
if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(
self.available_for_use_date
):
if self.opening_accumulated_depreciation:
if not self.opening_number_of_booked_depreciations:
frappe.throw(_("Please set Opening Number of Booked Depreciations"))
else:
self.opening_number_of_booked_depreciations = 0
if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations):
frappe.throw(
_(
"Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date"
).format(row.idx)
"Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations"
).format(row.idx),
title=_("Invalid Schedule"),
)
def set_total_booked_depreciations(self):
@@ -640,7 +669,10 @@ class Asset(AccountsController):
def get_status(self):
"""Returns status based on whether it is draft, submitted, scrapped or depreciated"""
if self.docstatus == 0:
status = "Draft"
if self.is_composite_asset:
status = "Work In Progress"
else:
status = "Draft"
elif self.docstatus == 1:
status = "Submitted"
@@ -1206,7 +1238,7 @@ def get_values_from_purchase_doc(purchase_doc_name, item_code, doctype):
return {
"company": purchase_doc.company,
"purchase_date": purchase_doc.get("bill_date") or purchase_doc.get("posting_date"),
"purchase_date": purchase_doc.get("posting_date"),
"gross_purchase_amount": flt(first_item.base_net_amount),
"asset_quantity": first_item.qty,
"cost_center": first_item.cost_center or purchase_doc.get("cost_center"),

View File

@@ -537,6 +537,7 @@ def modify_depreciation_schedule_for_asset_repairs(asset, notes):
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.asset_doc = asset
asset_repair.modify_depreciation_schedule()
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, notes)

View File

@@ -197,6 +197,13 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
}
}
serial_and_batch_bundle(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if (cdt === "Asset Capitalization Stock Item") {
this.get_warehouse_details(row);
}
}
asset(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if (cdt === "Asset Capitalization Asset Item") {
@@ -410,6 +417,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
voucher_type: me.frm.doc.doctype,
voucher_no: me.frm.doc.name,
allow_zero_valuation: 1,
serial_and_batch_bundle: item.serial_and_batch_bundle,
},
},
callback: function (r) {

View File

@@ -177,6 +177,7 @@
"default": "1",
"fieldname": "target_qty",
"fieldtype": "Float",
"hidden": 1,
"label": "Target Qty"
},
{
@@ -290,10 +291,10 @@
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "dimension_col_break",
@@ -324,7 +325,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-01-08 13:14:33.008458",
"modified": "2026-01-13 17:25:01.352568",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization",
@@ -362,10 +363,11 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -76,6 +76,7 @@ class AssetCapitalization(StockController):
naming_series: DF.Literal["ACC-ASC-.YYYY.-"]
posting_date: DF.Date
posting_time: DF.Time
project: DF.Link | None
service_items: DF.Table[AssetCapitalizationServiceItem]
service_items_total: DF.Currency
set_posting_time: DF.Check
@@ -139,6 +140,7 @@ class AssetCapitalization(StockController):
self.make_gl_entries()
self.repost_future_sle_and_gle()
self.restore_consumed_asset_items()
self.update_target_asset()
def set_title(self):
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
@@ -362,6 +364,7 @@ class AssetCapitalization(StockController):
"voucher_no": self.name,
"company": self.company,
"allow_zero_valuation": cint(item.get("allow_zero_valuation_rate")),
"serial_and_batch_bundle": item.serial_and_batch_bundle,
}
)
@@ -607,11 +610,22 @@ class AssetCapitalization(StockController):
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
asset_doc = frappe.get_doc("Asset", self.target_asset)
asset_doc.gross_purchase_amount += total_target_asset_value
asset_doc.purchase_amount += total_target_asset_value
asset_doc.set_status("Work In Progress")
asset_doc.flags.ignore_validate = True
asset_doc.save()
if self.docstatus == 2:
gross_purchase_amount = asset_doc.gross_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
else:
gross_purchase_amount = asset_doc.gross_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(
{
"gross_purchase_amount": gross_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(
@@ -758,6 +772,7 @@ def get_consumed_stock_item_details(args):
"company": args.company,
"serial_no": args.serial_no,
"batch_no": args.batch_no,
"serial_and_batch_bundle": args.serial_and_batch_bundle,
}
)
out.update(get_warehouse_details(incoming_rate_args))

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.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(unittest.TestCase):
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

@@ -63,14 +63,7 @@ frappe.ui.form.on("Asset Repair", {
},
refresh: function (frm) {
if (frm.doc.docstatus) {
frm.add_custom_button(__("View General Ledger"), function () {
frappe.route_options = {
voucher_no: frm.doc.name,
};
frappe.set_route("query-report", "General Ledger");
});
}
frm.events.show_general_ledger(frm);
let sbb_field = frm.get_docfield("stock_items", "serial_and_batch_bundle");
if (sbb_field) {
@@ -134,6 +127,26 @@ frappe.ui.form.on("Asset Repair", {
frm.set_value("repair_cost", 0);
}
},
show_general_ledger: (frm) => {
if (frm.doc.docstatus > 0) {
frm.add_custom_button(
__("Accounting Ledger"),
function () {
frappe.route_options = {
voucher_no: frm.doc.name,
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");
},
__("View")
);
}
},
});
frappe.ui.form.on("Asset Repair Consumed Item", {

View File

@@ -1,5 +1,6 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2017-10-23 11:38:54.004355",
"doctype": "DocType",
@@ -250,7 +251,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-11-17 18:35:54.575265",
"modified": "2026-01-06 15:48:13.862505",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair",
@@ -264,6 +265,7 @@
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
@@ -279,6 +281,7 @@
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
@@ -295,4 +298,4 @@
"title_field": "asset_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -6,6 +6,9 @@ from frappe import _
from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_diff_in_hours
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.assets.doctype.asset.asset import get_asset_account
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
@@ -246,6 +249,12 @@ class AssetRepair(AccountsController):
)
stock_entry.asset_repair = self.name
accounting_dimensions = {
"cost_center": self.cost_center,
"project": self.project,
**{dimension: self.get(dimension) for dimension in get_accounting_dimensions()},
}
for stock_item in self.get("stock_items"):
self.validate_serial_no(stock_item)
@@ -257,8 +266,7 @@ class AssetRepair(AccountsController):
"qty": stock_item.consumed_quantity,
"basic_rate": stock_item.valuation_rate,
"serial_and_batch_bundle": stock_item.serial_and_batch_bundle,
"cost_center": self.cost_center,
"project": self.project,
**accounting_dimensions,
},
)
@@ -315,8 +323,8 @@ class AssetRepair(AccountsController):
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": self.completion_date,
"against_voucher_type": "Purchase Invoice",
"against_voucher": self.purchase_invoice,
"against_voucher_type": "Asset",
"against_voucher": self.asset,
"company": self.company,
},
item=self,

View File

@@ -4,6 +4,8 @@
import unittest
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, add_months, flt, get_first_day, nowdate, nowtime, today
from erpnext.assets.doctype.asset.asset import (
@@ -294,6 +296,31 @@ class TestAssetRepair(unittest.TestCase):
stock_entry = frappe.get_last_doc("Stock Entry")
self.assertEqual(stock_entry.asset_repair, asset_repair.name)
def test_gl_entries_with_capitalized_asset_repair(self):
asset = create_asset(is_existing_asset=1, calculate_depreciation=1, submit=1)
asset_repair = create_asset_repair(
asset=asset, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1
)
asset.reload()
GLEntry = qb.DocType("GL Entry")
res = (
qb.from_(GLEntry)
.select(Sum(GLEntry.debit_in_account_currency).as_("total_debit"))
.where(
(GLEntry.voucher_type == "Asset Repair")
& (GLEntry.voucher_no == asset_repair.name)
& (GLEntry.against_voucher_type == "Asset")
& (GLEntry.against_voucher == asset.name)
& (GLEntry.company == asset.company)
& (GLEntry.is_cancelled == 0)
)
).run(as_dict=True)
booked_value = res[0].total_debit if res else 0
self.assertEqual(asset.additional_asset_cost, asset_repair.repair_cost)
self.assertEqual(booked_value, asset_repair.repair_cost)
def num_of_depreciations(asset):
return asset.finance_books[0].total_number_of_depreciations

View File

@@ -57,7 +57,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,
@@ -159,6 +159,16 @@ class AssetValueAdjustment(Document):
self.db_set("journal_entry", je.name)
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:
revaluation_entry.flags.ignore_permissions = True
revaluation_entry.flags.via_asset_value_adjustment = True
revaluation_entry.cancel()
def update_asset(self, asset_value=None):
difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount
asset = self.update_asset_value_after_depreciation(difference_amount)

View File

@@ -36,6 +36,7 @@
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
"over_transfer_allowance",
"validate_consumed_qty",
"section_break_xcug",
"auto_create_subcontracting_order",
"column_break_izrr",
@@ -270,6 +271,14 @@
"label": "Fixed Outgoing Email Account",
"link_filters": "[[\"Email Account\",\"enable_outgoing\",\"=\",1]]",
"options": "Email Account"
},
{
"default": "0",
"depends_on": "eval:doc.backflush_raw_materials_of_subcontract_based_on == \"Material Transferred for Subcontract\"",
"description": "Raw materials consumed qty will be validated based on FG BOM required qty",
"fieldname": "validate_consumed_qty",
"fieldtype": "Check",
"label": "Validate Consumed Qty (as per BOM)"
}
],
"grid_page_length": 50,
@@ -278,7 +287,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-08-20 22:13:38.506889",
"modified": "2025-11-20 12:59:09.925862",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

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