Compare commits

..

433 Commits

Author SHA1 Message Date
Frappe PR Bot
9397a57d4d chore(release): Bumped to Version 15.55.5
## [15.55.5](https://github.com/frappe/erpnext/compare/v15.55.4...v15.55.5) (2025-04-07)

### Bug Fixes

* set draft QC in purchase document on creation of qc ([bf3349a](bf3349a432))
* slow query ([b172ae0](b172ae0557))
2025-04-07 05:14:16 +00:00
rohitwaghchaure
d70050931b Merge pull request #46887 from frappe/mergify/bp/version-15/pr-46842
fix: set draft QC in purchase document on creation of qc (backport #46832) (backport #46842)
2025-04-07 10:42:55 +05:30
rohitwaghchaure
1875d69f60 Merge pull request #46888 from frappe/mergify/bp/version-15/pr-46880
fix: slow query (backport #46845) (backport #46880)
2025-04-07 10:42:41 +05:30
rohitwaghchaure
b2d71b44cf chore: fix conflicts
(cherry picked from commit 4bcf052220)
2025-04-07 04:56:07 +00:00
Rohit Waghchaure
b172ae0557 fix: slow query
(cherry picked from commit f82c8ea5eb)

# Conflicts:
#	erpnext/stock/deprecated_serial_batch.py
(cherry picked from commit 23dc9d5872)
2025-04-07 04:56:07 +00:00
Rohit Waghchaure
bf3349a432 fix: set draft QC in purchase document on creation of qc
(cherry picked from commit 2553dea78e)
(cherry picked from commit 54159b9e5e)
2025-04-07 04:55:58 +00:00
Frappe PR Bot
5dd99f896e chore(release): Bumped to Version 15.55.4
## [15.55.4](https://github.com/frappe/erpnext/compare/v15.55.3...v15.55.4) (2025-03-29)

### Bug Fixes

* valuation rate not updating for raw materials ([57e2619](57e2619cf1))
2025-03-29 20:07:32 +00:00
rohitwaghchaure
7579e00425 Merge pull request #46790 from frappe/mergify/bp/version-15/pr-46778
fix: valuation rate not updating for raw materials (backport #46760) (backport #46778)
2025-03-30 01:36:12 +05:30
rohitwaghchaure
c22869fed9 chore: fix conflicts
(cherry picked from commit 5079519863)
2025-03-29 15:31:21 +00:00
Rohit Waghchaure
57e2619cf1 fix: valuation rate not updating for raw materials
(cherry picked from commit 5af8378471)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
(cherry picked from commit 454dd3a2f1)
2025-03-29 15:31:21 +00:00
Frappe PR Bot
66d0ad1bc6 chore(release): Bumped to Version 15.55.3
## [15.55.3](https://github.com/frappe/erpnext/compare/v15.55.2...v15.55.3) (2025-03-29)

### Bug Fixes

* incorrect condition ([0721816](0721816763))
2025-03-29 07:03:05 +00:00
rohitwaghchaure
3395e7c2cd Merge pull request #46785 from frappe/mergify/bp/version-15/pr-46781
fix: incorrect condition (backport #46777) (backport #46781)
2025-03-29 12:31:36 +05:30
Rohit Waghchaure
0721816763 fix: incorrect condition
(cherry picked from commit 0c1a8e9c58)
(cherry picked from commit 502b8f25b3)
2025-03-29 06:48:03 +00:00
rohitwaghchaure
e45d0779ef Merge pull request #46752 from frappe/mergify/bp/version-15/pr-46749
Revert "perf: timeout while renaming cost center (backport #46641)" (backport #46749)
2025-03-27 13:13:17 +05:30
rohitwaghchaure
c6ce76170b Revert "perf: timeout while renaming cost center (backport #46641)"
(cherry picked from commit 326126e741)
2025-03-27 06:54:18 +00:00
Frappe PR Bot
de3e6922b5 chore(release): Bumped to Version 15.55.2
## [15.55.2](https://github.com/frappe/erpnext/compare/v15.55.1...v15.55.2) (2025-03-27)

### Bug Fixes

* do not use self object for setting party and party type ([7795030](7795030b7b))
2025-03-27 06:05:55 +00:00
ruthra kumar
ae6d3f27a2 Merge pull request #46747 from frappe/mergify/bp/version-15/pr-46719
fix: do not use self object for setting party and party type  (backport #46719)
2025-03-27 11:34:32 +05:30
ljain112
7795030b7b fix: do not use self object for setting party and party type
(cherry picked from commit 80b746d4dd)
2025-03-27 05:39:52 +00:00
Frappe PR Bot
9bac43acff chore(release): Bumped to Version 15.55.1
## [15.55.1](https://github.com/frappe/erpnext/compare/v15.55.0...v15.55.1) (2025-03-27)

### Bug Fixes

* decimal values causing incorrect batch picking ([1b6aeba](1b6aeba267))
* expense account in the stock entry ([e393ce9](e393ce9a47))
* slow query ([f3ba5a8](f3ba5a81ab))
2025-03-27 03:45:26 +00:00
rohitwaghchaure
1e987153c9 Merge pull request #46735 from frappe/mergify/bp/version-15/pr-46730
fix: expense account in the stock entry (backport #46710) (backport #46730)
2025-03-27 09:14:05 +05:30
rohitwaghchaure
d36a7c2389 Merge pull request #46741 from frappe/mergify/bp/version-15/pr-46734
fix: decimal values causing incorrect batch picking (backport #46733) (backport #46734)
2025-03-27 09:13:56 +05:30
rohitwaghchaure
b548cc411d Merge pull request #46742 from frappe/mergify/bp/version-15/pr-46740
fix: slow query (backport #46739) (backport #46740)
2025-03-27 09:13:47 +05:30
rohitwaghchaure
ad3f985dc4 chore: fix conflicts
(cherry picked from commit 41f20a9c64)
2025-03-26 17:09:42 +00:00
Rohit Waghchaure
f3ba5a81ab fix: slow query
(cherry picked from commit 5ddb36af87)

# Conflicts:
#	erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
(cherry picked from commit af0fb131a2)
2025-03-26 17:09:42 +00:00
Rohit Waghchaure
1b6aeba267 fix: decimal values causing incorrect batch picking
(cherry picked from commit 7bfe703b04)
(cherry picked from commit c5efddae16)
2025-03-26 16:43:35 +00:00
Rohit Waghchaure
e393ce9a47 fix: expense account in the stock entry
(cherry picked from commit 89569d4b32)
(cherry picked from commit 62f342ef8b)
2025-03-26 10:18:49 +00:00
Frappe PR Bot
8951efb457 chore(release): Bumped to Version 15.55.0
# [15.55.0](https://github.com/frappe/erpnext/compare/v15.54.5...v15.55.0) (2025-03-25)

### Bug Fixes

* add base_outstanding and base_paid_amount in payment schedule table ([412e6be](412e6be502))
* add patch to update base_outstanding and base_paid_amount ([c3221c4](c3221c4e93))
* correct accumulated depreciation calculation for disposed assets (backport [#46660](https://github.com/frappe/erpnext/issues/46660)) ([#46661](https://github.com/frappe/erpnext/issues/46661)) ([4df5f18](4df5f18d85))
* correct invoice order in payment reconcillaiton ([2a70791](2a70791bba))
* customer credit limit check based on `bypass_credit_limit_check` in Journal Entry ([6c443bd](6c443bd85a))
* date added to wrong patch ([2bfaf64](2bfaf64fff))
* do not validate if conversion rate is 1 for different currency ([391b5c4](391b5c4226))
* don't filter payment entries on Bank Account in Payment Clearance ([dc3b5e2](dc3b5e2f3a))
* **Payment Entry:** get contact details from existing contact ([#40556](https://github.com/frappe/erpnext/issues/40556)) ([f964178](f964178008))
* unwired order_by argument in get_transaction_list (backport [#46636](https://github.com/frappe/erpnext/issues/46636)) ([#46643](https://github.com/frappe/erpnext/issues/46643)) ([2ebea88](2ebea8866a))

### Features

* **accounting:** allow chart_of_account.get_chart to be whilelist ([e69c722](e69c722534))
* **projects:** add option to hide timesheets for project users ([#46173](https://github.com/frappe/erpnext/issues/46173)) ([3834d6f](3834d6fbce))
* repost accounting ledger for purchase receipt ([4edfc6f](4edfc6f125))

### Performance Improvements

* timeout while renaming cost center ([58eb184](58eb1849d7))
2025-03-25 13:50:33 +00:00
ruthra kumar
5db2a19778 Merge pull request #46715 from frappe/version-15-hotfix
chore: release v15
2025-03-25 19:16:44 +05:30
ruthra kumar
288206bdcd Merge pull request #46634 from frappe/mergify/bp/version-15-hotfix/pr-46627
fix: date added to wrong patch (backport #46627)
2025-03-25 18:45:21 +05:30
ruthra kumar
b3c3733286 chore: resolve conflict 2025-03-25 17:53:29 +05:30
ruthra kumar
b2b49446d4 Merge pull request #46713 from frappe/mergify/bp/version-15-hotfix/pr-46616
fix: do not validate if conversion rate is 1 for different currency (backport #46616)
2025-03-25 14:29:09 +05:30
ljain112
391b5c4226 fix: do not validate if conversion rate is 1 for different currency
(cherry picked from commit e8a66d03bc)
2025-03-25 08:42:31 +00:00
ruthra kumar
ce454d5202 Merge pull request #46707 from frappe/mergify/bp/version-15-hotfix/pr-46617
refactor: removed redundant message display for each item row cost center update (backport #46617)
2025-03-25 12:28:43 +05:30
ljain112
f93feb18fb refactor: removed redundant message display for each item row cost center update
(cherry picked from commit 4376ca5f1d)
2025-03-25 06:47:13 +00:00
ruthra kumar
0793213981 Merge pull request #46705 from frappe/mergify/bp/version-15-hotfix/pr-46622
feat: repost accounting ledger for purchase receipt (backport #46622)
2025-03-25 12:13:56 +05:30
ljain112
4edfc6f125 feat: repost accounting ledger for purchase receipt
(cherry picked from commit b36e356469)
2025-03-25 06:19:43 +00:00
mergify[bot]
98df0614ab ci: apply label "skip-release-notes" based on PR title (backport #46694) (#46697)
ci: apply label "skip-release-notes" based on PR title (#46694)

Workflow copied from frappe/frappe

(cherry picked from commit eb350012b0)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-03-24 16:55:11 +01:00
Sagar Vora
59d0ff493f Merge pull request #46688 from frappe/mergify/bp/version-15-hotfix/pr-46669
fix: don't filter payment entries on Bank Account in Payment Clearance (backport #46669)
2025-03-24 18:17:59 +05:30
vishakhdesai
dc3b5e2f3a fix: don't filter payment entries on Bank Account in Payment Clearance
(cherry picked from commit fa2fd5bf88)
2025-03-24 12:22:09 +00:00
ruthra kumar
058a2f0c42 Merge pull request #46677 from frappe/mergify/bp/version-15-hotfix/pr-46577
fix: customer credit limit check based on `bypass_credit_limit_check` in Journal Entry (backport #46577)
2025-03-24 13:58:10 +05:30
ljain112
6c443bd85a fix: customer credit limit check based on bypass_credit_limit_check in Journal Entry
(cherry picked from commit 8a84faebed)
2025-03-24 08:02:17 +00:00
ruthra kumar
a228d1edc5 Merge pull request #46672 from frappe/mergify/bp/version-15-hotfix/pr-46574
fix: correct invoice order in payment reconcillaiton (backport #46574)
2025-03-24 13:13:35 +05:30
ruthra kumar
87628835bf Merge pull request #46673 from frappe/mergify/bp/version-15-hotfix/pr-46386
feat(accounting/regional): allow chart_of_account.get_chart to be whilelist (backport #46386)
2025-03-24 13:13:06 +05:30
Florian HENRY
e69c722534 feat(accounting): allow chart_of_account.get_chart to be whilelist
(cherry picked from commit 49dcd96909)
2025-03-24 07:03:37 +00:00
ruthra kumar
326c37a051 chore: resolve conflict 2025-03-24 12:29:52 +05:30
ljain112
2a70791bba fix: correct invoice order in payment reconcillaiton
(cherry picked from commit 5c34a5aaed)

# Conflicts:
#	erpnext/accounts/utils.py
2025-03-24 06:54:24 +00:00
ruthra kumar
e5fb77c65f Merge pull request #46670 from frappe/mergify/bp/version-15-hotfix/pr-46440
fix: add base_outstanding and base_paid_amount in payment schedule table (backport #46440)
2025-03-24 12:13:51 +05:30
ruthra kumar
4a7d401dc5 chore: resolve conflict 2025-03-24 11:53:14 +05:30
Sugesh393
c3221c4e93 fix: add patch to update base_outstanding and base_paid_amount
(cherry picked from commit 7e92e4967a)
2025-03-24 06:14:41 +00:00
Sugesh393
412e6be502 fix: add base_outstanding and base_paid_amount in payment schedule table
(cherry picked from commit 6c2f9a563e)

# Conflicts:
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.json
2025-03-24 06:14:41 +00:00
mergify[bot]
4df5f18d85 fix: correct accumulated depreciation calculation for disposed assets (backport #46660) (#46661)
fix: correct accumulated depreciation calculation for disposed assets (#46660)

(cherry picked from commit eec2e7e833)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-03-23 18:53:49 +05:30
rohitwaghchaure
d58e527b6b Merge pull request #46647 from frappe/mergify/bp/version-15-hotfix/pr-46641
perf: timeout while renaming cost center (backport #46641)
2025-03-22 20:49:18 +05:30
Rohit Waghchaure
58eb1849d7 perf: timeout while renaming cost center
(cherry picked from commit 92be7cbbbf)
2025-03-21 12:38:51 +00:00
mergify[bot]
2ebea8866a fix: unwired order_by argument in get_transaction_list (backport #46636) (#46643)
* fix: unwired order_by argument

* lol on how it was updated from modified in both the places (version 15), but wasn't fixed

(cherry picked from commit 2c1077d332)

# Conflicts:
#	erpnext/controllers/website_list_for_contact.py

* fix: merge conflicts

* fix: sort by creation only

---------

Co-authored-by: Hussain Nagaria <hussainbhaitech@gmail.com>
Co-authored-by: Md Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com>
2025-03-21 16:27:05 +05:30
Smit Vora
4e65b7873d Merge pull request #46638 from frappe/mergify/bp/version-15-hotfix/pr-40556
fix(Payment Entry): get contact details from existing contact (backport #40556)
2025-03-21 15:22:44 +05:30
Smit Vora
7dc23d9733 chore: resolve conflicts #39748 2025-03-21 14:09:21 +05:30
David Arnold
f964178008 fix(Payment Entry): get contact details from existing contact (#40556)
(cherry picked from commit 462204fc65)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
2025-03-21 08:28:59 +00:00
Mihir Kandoi
2bfaf64fff fix: date added to wrong patch
(cherry picked from commit dc45c3b39c)

# Conflicts:
#	erpnext/patches.txt
2025-03-20 18:26:02 +00:00
ruthra kumar
6da00319e6 Merge pull request #46612 from frappe/mergify/bp/version-15-hotfix/pr-46173
feat(projects): add option to hide timesheets for project users (backport #46173)
2025-03-19 16:50:42 +05:30
Frappe PR Bot
35ac96f1ec chore(release): Bumped to Version 15.54.5
## [15.54.5](https://github.com/frappe/erpnext/compare/v15.54.4...v15.54.5) (2025-03-19)

### Bug Fixes

* add parenttype condition to payment schedule query in accounts receivable report (backport [#46370](https://github.com/frappe/erpnext/issues/46370)) ([#46499](https://github.com/frappe/erpnext/issues/46499)) ([32335da](32335da839))
* add validation to rename_subcontracting_fields patch ([bc408d9](bc408d979a))
* also consider CRM Deal as party type for ERPNext CRM Integration ([65a80cf](65a80cffe7))
* dashboard link for QC from PR ([426222d](426222d8e0))
* Debit and Credit not equal for Purchase Invoice ([46b6e62](46b6e621c2))
* debit in transaction currency ([8e19b46](8e19b46bd9))
* ensure qty conversion when creating production plan from SO ([8162fb3](8162fb3e5d))
* exclude current doc when checking for duplicate ([b638aed](b638aed758))
* fetch bom_no when updating items in sales order ([41d8b26](41d8b26dd2))
* fetch quality inspection parameter group ([cd0abba](cd0abbae51))
* get bom_no from sales order item and material request item ([e241810](e2418101ab))
* hide subcontracted qty field if PO is not subcontracted ([62feec5](62feec5cc3))
* incorrect production item and bom no in job card ([d071a6c](d071a6c900))
* not able to make PR against stand alone Debit Note ([d62960e](d62960e925))
* not able to select the item in the BOM ([59c653e](59c653ef3f))
* patch ([36ffc2e](36ffc2ee67))
* performance issue for item list view ([34d6e4b](34d6e4bdaa))
* remove duplicate ([e5b2801](e5b2801830))
* repost future sle and gle after capitalization ([#46576](https://github.com/frappe/erpnext/issues/46576)) ([2144f89](2144f89624))
* SABB validation for packed items ([2d6626e](2d6626e906))
* set correct currency for offset account gl entries ([e6dd3f3](e6dd3f3e64))
* set landed cost based on purchase invoice rate ([56bc26a](56bc26aecc))
* set stock adjustment account in difference account ([6202e30](6202e302b1))
* take function call outside loop ([ec1a3a1](ec1a3a1e6b))
* **Transaction Deletion Record:** sql syntax error while fetching lead address ([ea68cae](ea68caec7d))
* UOM conversion error when creating pick list from material transfer request ([2f3dcc2](2f3dcc2137))
* use base currency total ([3e2749d](3e2749d6d5))
* use party explicitly ([5dd5784](5dd5784716))
* use shipping_address_name for address validation in sales invoice ([#46473](https://github.com/frappe/erpnext/issues/46473)) ([38dabdf](38dabdf584))
* using `in` for lookup in list instead of directly assigning ([#46492](https://github.com/frappe/erpnext/issues/46492)) ([950656d](950656d6f7))
* valuation for moving average with batches ([5f1bb1f](5f1bb1f1ba))
* wrong field mapping ([be3e083](be3e083e7d))

### Performance Improvements

* faster count estimation (backport [#46550](https://github.com/frappe/erpnext/issues/46550)) ([#46551](https://github.com/frappe/erpnext/issues/46551)) ([01bab8f](01bab8f22b))
2025-03-19 11:18:07 +00:00
ruthra kumar
187ebaaecd Merge pull request #46582 from frappe/version-15-hotfix
chore: release v15
2025-03-19 16:46:43 +05:30
rohitwaghchaure
a2cb9c1791 Merge pull request #46615 from frappe/mergify/bp/version-15-hotfix/pr-46608
fix: fetch bom_no when updating items in sales order (backport #46608)
2025-03-19 16:31:38 +05:30
rohitwaghchaure
0d8842e387 Merge pull request #46614 from frappe/mergify/bp/version-15-hotfix/pr-46573
Fix set landed cost based on pi (backport #46573)
2025-03-19 16:31:09 +05:30
rohitwaghchaure
38213b31da chore: fix conflicts 2025-03-19 15:47:43 +05:30
Mihir Kandoi
e5b2801830 fix: remove duplicate
(cherry picked from commit 386df968c2)

# Conflicts:
#	erpnext/public/js/utils.js
2025-03-19 10:15:18 +00:00
Mihir Kandoi
41d8b26dd2 fix: fetch bom_no when updating items in sales order
(cherry picked from commit 508727a57a)

# Conflicts:
#	erpnext/public/js/utils.js
2025-03-19 10:15:18 +00:00
rohitwaghchaure
5b802ae527 chore: fix conflicts 2025-03-19 15:35:26 +05:30
rohitwaghchaure
1b8e8e92ae Merge pull request #46611 from frappe/mergify/bp/version-15-hotfix/pr-46595
fix: not able to make PR against stand alone Debit Note (backport #46595)
2025-03-19 15:28:57 +05:30
Mihir Kandoi
ec1a3a1e6b fix: take function call outside loop
(cherry picked from commit b3c400f998)
2025-03-19 09:52:28 +00:00
Mihir Kandoi
36ffc2ee67 fix: patch
(cherry picked from commit 7e669c0728)
2025-03-19 09:52:28 +00:00
Mihir Kandoi
56bc26aecc fix: set landed cost based on purchase invoice rate
(cherry picked from commit 75ab5f2bd0)

# Conflicts:
#	erpnext/patches.txt
2025-03-19 09:52:27 +00:00
Marc Ramser
3834d6fbce feat(projects): add option to hide timesheets for project users (#46173)
* feat: add option to hide timesheets for project users

* Added a new "Hide timesheets" checkbox field to Project User doctype that allows to control timesheet visibility for specific users. When enabled, the timesheets section will not be displayed on the project page for that user.

* Update projects.html

(cherry picked from commit f4aba561ce)
2025-03-19 08:05:42 +00:00
Rohit Waghchaure
d62960e925 fix: not able to make PR against stand alone Debit Note
(cherry picked from commit 6a52c30591)
2025-03-19 08:05:33 +00:00
ruthra kumar
c6de50b2a5 Merge pull request #46606 from frappe/mergify/bp/version-15-hotfix/pr-46596
fix: debit in transaction currency (backport #46596)
2025-03-19 12:11:09 +05:30
Rohit Waghchaure
8e19b46bd9 fix: debit in transaction currency
(cherry picked from commit e4acf20a62)
2025-03-19 06:26:26 +00:00
rohitwaghchaure
f6a4855e7f Merge pull request #46594 from frappe/mergify/bp/version-15-hotfix/pr-46593
test: test case for FIFO batch valuation (backport #46593)
2025-03-18 21:23:03 +05:30
Rohit Waghchaure
95718acc9a test: test case for FIFO batch valuation
(cherry picked from commit ad9ac1f058)
2025-03-18 13:50:36 +00:00
rohitwaghchaure
2528acd803 Merge pull request #46592 from frappe/mergify/bp/version-15-hotfix/pr-46588
fix: SABB validation for packed items (backport #46588)
2025-03-18 18:38:50 +05:30
Rohit Waghchaure
2d6626e906 fix: SABB validation for packed items
(cherry picked from commit 3756bf231b)
2025-03-18 12:03:18 +00:00
rohitwaghchaure
a07eb556cf Merge pull request #46584 from frappe/mergify/bp/version-15-hotfix/pr-46575
fix: fetch quality inspection parameter group (backport #46575)
2025-03-18 17:28:33 +05:30
rohitwaghchaure
48edc86845 Merge pull request #46587 from frappe/mergify/bp/version-15-hotfix/pr-46576
fix: repost future sle and gle after capitalization (backport #46576)
2025-03-18 17:27:56 +05:30
rohitwaghchaure
7f5ce4f29d Merge pull request #46585 from frappe/mergify/bp/version-15-hotfix/pr-46554
fix: add validation to rename_subcontracting_fields patch (backport #46554)
2025-03-18 17:27:20 +05:30
rohitwaghchaure
cd7056842d Merge pull request #46589 from frappe/mergify/bp/version-15-hotfix/pr-46579
fix: valuation for moving average with batches (backport #46579)
2025-03-18 17:26:41 +05:30
Rohit Waghchaure
5f1bb1f1ba fix: valuation for moving average with batches
(cherry picked from commit cdfbc73f4c)
2025-03-18 11:05:06 +00:00
rohitwaghchaure
79dacfdef8 Merge pull request #46560 from frappe/mergify/bp/version-15-hotfix/pr-46555
fix: performance issue for item list view (backport #46555)
2025-03-18 16:34:00 +05:30
Khushi Rawat
2144f89624 fix: repost future sle and gle after capitalization (#46576)
(cherry picked from commit 29d77aa19f)
2025-03-18 10:31:47 +00:00
Mihir Kandoi
bc408d979a fix: add validation to rename_subcontracting_fields patch
(cherry picked from commit 6c3117dc0d)
2025-03-18 10:30:41 +00:00
rohitwaghchaure
496f43e7e9 Merge pull request #46556 from frappe/st33974
fix: incorrect production item and bom no in job card
2025-03-18 15:58:26 +05:30
Mihir Kandoi
cd0abbae51 fix: fetch quality inspection parameter group
(cherry picked from commit 0a482c7ea8)
2025-03-18 10:27:30 +00:00
Smit Vora
004ecc53e5 Merge pull request #46578 from frappe/mergify/bp/version-15-hotfix/pr-46515
fix: ensure qty conversion when creating production plan from SO (backport #46515)
2025-03-18 15:00:34 +05:30
Smit Vora
8162fb3e5d fix: ensure qty conversion when creating production plan from SO
(cherry picked from commit 75882cc81c)
2025-03-18 08:56:21 +00:00
rohitwaghchaure
e8047ab2ca chore: fix conflicts 2025-03-18 12:01:11 +05:30
rohitwaghchaure
8dc371dac2 Merge pull request #46558 from frappe/mergify/bp/version-15-hotfix/pr-46553
fix: not able to select the item in the BOM (backport #46553)
2025-03-18 11:59:56 +05:30
rohitwaghchaure
0351faa8c0 Merge pull request #46559 from frappe/mergify/bp/version-15-hotfix/pr-46552
fix: Debit and Credit not equal for Purchase Invoice (backport #46552)
2025-03-18 11:59:38 +05:30
ruthra kumar
7e46845fea Merge pull request #46572 from frappe/mergify/bp/version-15-hotfix/pr-46566
fix: set correct currency for offset account gl entries (backport #46566)
2025-03-18 11:32:05 +05:30
ruthra kumar
e84333e5f2 Merge pull request #46571 from frappe/mergify/bp/version-15-hotfix/pr-46508
fix: use base currency total for UAE VAT 201 report (backport #46508)
2025-03-18 11:24:29 +05:30
ruthra kumar
e6dd3f3e64 fix: set correct currency for offset account gl entries
(cherry picked from commit c32e11e69d)
2025-03-18 05:45:13 +00:00
ruthra kumar
ec43ca97cb test: report ouput on foreign currency PI
(cherry picked from commit e80129627a)
2025-03-18 05:30:10 +00:00
ruthra kumar
3e2749d6d5 fix: use base currency total
(cherry picked from commit 46f4babcd0)
2025-03-18 05:30:09 +00:00
mergify[bot]
01bab8f22b perf: faster count estimation (backport #46550) (#46551)
perf: faster count estimation (#46550)

These count queries themselves take quite a long time. `estimate_count`
uses info_schema stats to guess the time.

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
(cherry picked from commit e47a87839b)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-03-17 21:23:30 +05:30
Ejaaz Khan
972c96b682 Merge pull request #46565 from frappe/mergify/bp/version-15-hotfix/pr-46564
refactor: remove default print format from sales invoice (backport #46564)
2025-03-17 20:52:46 +05:30
Ejaaz Khan
7cfd7e6539 refactor: remove default print format from sales invoice
(cherry picked from commit f10d1f2b1f)
2025-03-17 14:38:43 +00:00
ruthra kumar
bcd9fd090d Merge pull request #46562 from frappe/mergify/bp/version-15-hotfix/pr-46557
fix(Transaction Deletion Record): sql syntax error while deleting lead address (backport #46557)
2025-03-17 17:00:37 +05:30
venkat102
ea68caec7d fix(Transaction Deletion Record): sql syntax error while fetching lead address
(cherry picked from commit af0d6eeae8)
2025-03-17 11:09:38 +00:00
Rohit Waghchaure
34d6e4bdaa fix: performance issue for item list view
(cherry picked from commit d758fde881)

# Conflicts:
#	erpnext/stock/doctype/item_default/item_default.json
2025-03-17 11:08:38 +00:00
rohitwaghchaure
c7b8514c24 Merge pull request #46549 from frappe/mergify/bp/version-15-hotfix/pr-46504
fix: dashboard link for QC from PR (backport #46504)
2025-03-17 16:37:21 +05:30
Rohit Waghchaure
46b6e621c2 fix: Debit and Credit not equal for Purchase Invoice
(cherry picked from commit ecb31b7c9f)
2025-03-17 11:07:17 +00:00
Rohit Waghchaure
59c653ef3f fix: not able to select the item in the BOM
(cherry picked from commit 96d0cd23f1)
2025-03-17 11:07:16 +00:00
Mihir Kandoi
d071a6c900 fix: incorrect production item and bom no in job card 2025-03-17 16:06:54 +05:30
ruthra kumar
61e126901e Merge pull request #46523 from frappe/revert-46475-mergify/bp/version-15-hotfix/pr-46417
Revert "fix: error when creating delivery note from pick list (backport #46417)"
2025-03-17 15:15:10 +05:30
Rohit Waghchaure
426222d8e0 fix: dashboard link for QC from PR
(cherry picked from commit 551f89f14b)
2025-03-17 04:36:59 +00:00
Sagar Vora
ad24867699 Merge pull request #46534 from frappe/mergify/bp/version-15-hotfix/pr-46533
fix: exclude current doc when checking for duplicate (backport #46533)
2025-03-15 00:37:55 +05:30
Sagar Vora
b638aed758 fix: exclude current doc when checking for duplicate
(cherry picked from commit d8ef5e4d58)
2025-03-14 18:37:47 +00:00
Shariq Ansari
1a9873bc55 Merge pull request #46530 from frappe/mergify/bp/version-15-hotfix/pr-46529
fix: also consider CRM Deal as party type for ERPNext CRM Integration (backport #46529)
2025-03-14 16:07:27 +05:30
Shariq Ansari
65a80cffe7 fix: also consider CRM Deal as party type for ERPNext CRM Integration
(cherry picked from commit 04edbf7efe)
2025-03-14 10:34:11 +00:00
Mihir Kandoi
18e29de0c8 Merge pull request #46527 from frappe/mergify/bp/version-15-hotfix/pr-46513 2025-03-14 13:27:30 +05:30
Mihir Kandoi
e2418101ab fix: get bom_no from sales order item and material request item
(cherry picked from commit ac354505ef)
2025-03-14 07:36:47 +00:00
Mihir Kandoi
62c9181651 Merge pull request #46525 from frappe/mergify/bp/version-15-hotfix/pr-46514
fix: UOM conversion error when creating pick list from material trans… (backport #46514)
2025-03-14 13:04:43 +05:30
Mihir Kandoi
d0008ac6df Merge pull request #46524 from frappe/mergify/bp/version-15-hotfix/pr-46512
fix: hide subcontracted qty field if PO is not subcontracted (backport #46512)
2025-03-14 13:04:10 +05:30
Mihir Kandoi
be3e083e7d fix: wrong field mapping
(cherry picked from commit 8411e2e01f)
2025-03-14 07:13:46 +00:00
Mihir Kandoi
2f3dcc2137 fix: UOM conversion error when creating pick list from material transfer request
(cherry picked from commit 840ea070a9)
2025-03-14 07:13:46 +00:00
Mihir Kandoi
62feec5cc3 fix: hide subcontracted qty field if PO is not subcontracted
(cherry picked from commit 6e8521d761)
2025-03-14 07:12:44 +00:00
Nabin Hait
0852533751 Revert "fix: error when creating delivery note from pick list (backport #46417)" 2025-03-14 12:33:37 +05:30
ruthra kumar
99f59c0410 Merge pull request #46521 from frappe/mergify/bp/version-15-hotfix/pr-46497
fix: use `party` explicitly instead of party_field (backport #46497)
2025-03-14 11:08:05 +05:30
Sanket322
5dd5784716 fix: use party explicitly
(cherry picked from commit 5057e3fe30)
2025-03-14 04:52:33 +00:00
ruthra kumar
5ad3e5b5c8 Merge pull request #46520 from frappe/mergify/bp/version-15-hotfix/pr-46488
refactor: replace get_list with get_all for dynamic link child access (backport #46488)
2025-03-14 10:11:05 +05:30
Sugesh393
7fb26f802c refactor: replace get_list with get_all for dynamic link child access
(cherry picked from commit 8f7f0b81f6)
2025-03-14 04:22:25 +00:00
ruthra kumar
27d6659962 Merge pull request #46480 from frappe/mergify/bp/version-15-hotfix/pr-46473
fix: use shipping_address_name for address validation in sales invoice (backport #46473)
2025-03-14 09:50:37 +05:30
ruthra kumar
3737b4a300 refactor(test): unset billing address 2025-03-14 09:29:43 +05:30
ruthra kumar
1065e483b2 Merge pull request #46510 from aerele/fix/item-stock-difference-account
fix: set stock adjustment account in difference account (backport #45606)
2025-03-14 09:23:46 +05:30
Bhavan23
6202e302b1 fix: set stock adjustment account in difference account 2025-03-13 16:45:18 +05:30
mergify[bot]
2a788a4fb1 refactor: print receipt on order complete on pos (backport #46501) (#46507)
* refactor: print receipt on order complete on pos (#46501)

(cherry picked from commit 0552209310)

# Conflicts:
#	erpnext/selling/page/point_of_sale/pos_past_order_summary.js

* chore: resolve conflict

---------

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-03-13 14:30:38 +05:30
mergify[bot]
32335da839 fix: add parenttype condition to payment schedule query in accounts receivable report (backport #46370) (#46499)
Fix: add parenttype condition to payment schedule query in accounts receivable report (#46370)

fix: add parenttype condition to payment schedule query in accounts receivable report
(cherry picked from commit f311a0fc1c)

Co-authored-by: Shanuka Hewage <89955436+Shanuka-98@users.noreply.github.com>
2025-03-13 12:03:46 +05:30
ruthra kumar
c47cc0572b Merge pull request #46493 from frappe/mergify/bp/version-15-hotfix/pr-46492
fix: using `in` for lookup in list instead of directly assigning (backport #46492)
2025-03-13 08:41:22 +05:30
ruthra kumar
1ec971f805 Merge pull request #46496 from frappe/mergify/bp/version-15/pr-46423
Revert "fix: Show Credit Note amount in credit note column" (backport #46423)
2025-03-13 08:20:53 +05:30
ruthra kumar
a6e92d7d16 Merge pull request #46495 from frappe/mergify/bp/version-15-hotfix/pr-46423
Revert "fix: Show Credit Note amount in credit note column" (backport #46423)
2025-03-13 08:12:37 +05:30
ruthra kumar
4d7071299e Revert "fix: Show Credit Note amount in credit note column"
(cherry picked from commit 5a9767ca67)
2025-03-13 02:35:11 +00:00
ruthra kumar
0223651b5b Revert "fix: Show Credit Note amount in credit note column"
(cherry picked from commit 5a9767ca67)
2025-03-13 02:28:08 +00:00
Sanket Shah
950656d6f7 fix: using in for lookup in list instead of directly assigning (#46492)
fix: using in for lookup in list instead of assigning

Co-authored-by: Sanket322 <shahsanket322003.com>
(cherry picked from commit 38955af802)
2025-03-12 17:08:05 +00:00
Frappe PR Bot
08f47b626c chore(release): Bumped to Version 15.54.4
## [15.54.4](https://github.com/frappe/erpnext/compare/v15.54.3...v15.54.4) (2025-03-12)

### Bug Fixes

* `base_net_rate` Required to Check Valid Range (backport [#46332](https://github.com/frappe/erpnext/issues/46332)) ([#46382](https://github.com/frappe/erpnext/issues/46382)) ([877d5bd](877d5bd3aa))
* **account:** update account number from parent company ([428aedc](428aedc29c))
* Allow rename prospect doctype ([#46352](https://github.com/frappe/erpnext/issues/46352)) ([de46165](de46165768))
* auto email report creation ([#46343](https://github.com/frappe/erpnext/issues/46343)) ([5cc251a](5cc251a172))
* backport translations from develop ([#46428](https://github.com/frappe/erpnext/issues/46428)) ([9c70376](9c703765a1))
* calculate due date based on payment term (backport [#46416](https://github.com/frappe/erpnext/issues/46416)) ([#46479](https://github.com/frappe/erpnext/issues/46479)) ([7f14744](7f147446df))
* change fieldname for cash_flow to export (backport [#46353](https://github.com/frappe/erpnext/issues/46353)) ([#46366](https://github.com/frappe/erpnext/issues/46366)) ([23c4252](23c4252b9b))
* check if set_landed_cost_based_on_purchase_invoice_rate is enabled before running patch ([7047fe2](7047fe2681))
* clear cashe on employee hierarchy change to reflect updated permissions ([4dfdb2b](4dfdb2b0a1))
* consider account freeze date in recalculate_amount_difference_field patch ([8b67527](8b67527900))
* consider stock freeze date in recalculate_amount_difference_field patch ([8264d42](8264d42cd9))
* credit note creation during pos invoice consolidation (backport [#46277](https://github.com/frappe/erpnext/issues/46277)) ([#46469](https://github.com/frappe/erpnext/issues/46469)) ([a4b8b4c](a4b8b4c771))
* do not recalculate qty for batch items during reposting ([bac36f3](bac36f342d))
* doctype name ([1dcbdf3](1dcbdf3257))
* enable no copy for serial no field ([3f9df2f](3f9df2fb2d))
* error in production analytics report ([db6ae61](db6ae61935))
* error when creating delivery note from pick list ([#46417](https://github.com/frappe/erpnext/issues/46417)) ([0b92101](0b921016ff))
* filter batches that going to be zero ([ac97489](ac97489a32))
* incorrect category in list ([002685f](002685fc89))
* make 'company_tax_id' and 'company_fiscal_code' as mandatory ([229f4d3](229f4d3d92))
* not able to save work order with alternative item ([9554a49](9554a49bbd))
* **payment entry:** fetch default bank account based on company (backport [#46379](https://github.com/frappe/erpnext/issues/46379)) ([#46471](https://github.com/frappe/erpnext/issues/46471)) ([1371199](13711993fe))
* pricing rule not ignored in Sales Order ([#46248](https://github.com/frappe/erpnext/issues/46248)) ([8def42f](8def42f751))
* rare precision issue preventing submission of subcontracting order ([6419d02](6419d020a1))
* recalculate_amount_difference_field patch ([f247f02](f247f02e49))
* remove no copy for serial no field of purchase receipt item ([baa564f](baa564fc94))
* rename sla fields patch ([73f11cf](73f11cf19e))
* rename sla fields patch ([#46465](https://github.com/frappe/erpnext/issues/46465)) ([5edbd88](5edbd8851a))
* rename_sla_fields patch ([7bc7557](7bc7557018))
* run bank reconciliation as a background job to prevent request timeout ([739cd18](739cd18604))
* set correct account currency for deferred expence account in PI ([f96848a](f96848a3b9))
* show remaining qty on 'Complete Job' button instead of full qty ([79e6550](79e6550321))
* sla fields patch ([0d044bc](0d044bc5bb))
* stock balance in and out value ([c2001e9](c2001e9c67))
* **test:** incorrect transaction exchange rate in test case ([b76c968](b76c96820e))
* typo in sales_invoice_print ([b610621](b6106212c1))
* uom reverts to default upon selecting do not explode ([#45693](https://github.com/frappe/erpnext/issues/45693)) ([6b1d209](6b1d20970e))
* validate accounting dimension company in Journal Entry & Stock Entry (backport [#46204](https://github.com/frappe/erpnext/issues/46204)) ([#46369](https://github.com/frappe/erpnext/issues/46369)) ([c816f9b](c816f9bd0a))
* validate last_gl_update exists before comparing (backport [#46464](https://github.com/frappe/erpnext/issues/46464)) ([#46468](https://github.com/frappe/erpnext/issues/46468)) ([3cef94e](3cef94e2ed))
* validations and account type filter for `Tax Withholding Category` ([#46207](https://github.com/frappe/erpnext/issues/46207)) ([cc30a01](cc30a01898))
2025-03-12 14:37:29 +00:00
ruthra kumar
0283f7526c Merge pull request #46444 from frappe/version-15-hotfix
chore: release v15
2025-03-12 20:05:56 +05:30
ruthra kumar
3ad451dd6e Merge branch 'version-15' into version-15-hotfix 2025-03-12 19:48:08 +05:30
rohitwaghchaure
9e409bde2e Merge pull request #46486 from frappe/st33357
fix: enable no copy for serial no field
2025-03-12 19:39:32 +05:30
ruthra kumar
8459166323 Merge pull request #46482 from frappe/mergify/bp/version-15-hotfix/pr-46207
fix: validations and account type filter for `Tax Withholding Category` (backport #46207)
2025-03-12 17:46:04 +05:30
ruthra kumar
e1328de712 Merge pull request #46487 from frappe/mergify/bp/version-15-hotfix/pr-46251
fix(bank-reconciliation): run bank reconciliation as a background job (backport #46251)
2025-03-12 17:45:45 +05:30
mergify[bot]
d3a2350b3e fix(invoice):validate return invoice qty (backport #46451) (#46481)
fix(invoice):validate return invoice qty (#46451)

* fix(invoice): validate return quantity when update stock is unchecked

* test: add unit test for validating fully returned invoice quantity

(cherry picked from commit ba96c86576)

Co-authored-by: Bhavansathru <122002510+Bhavan23@users.noreply.github.com>
2025-03-12 17:23:26 +05:30
Bhavan23
739cd18604 fix: run bank reconciliation as a background job to prevent request timeout
(cherry picked from commit a29c6a5aea)
2025-03-12 11:52:26 +00:00
ruthra kumar
e61cc9b12e Merge pull request #46483 from frappe/mergify/bp/version-15-hotfix/pr-46248
fix: pricing rule not ignored in Sales Order (backport #46248)
2025-03-12 17:13:13 +05:30
Mihir Kandoi
baa564fc94 fix: remove no copy for serial no field of purchase receipt item 2025-03-12 17:11:14 +05:30
rohitwaghchaure
a55ec56fbf Merge pull request #46484 from frappe/45440
fix: show remaining qty on 'Complete Job' button instead of full qty
2025-03-12 16:44:57 +05:30
Mihir Kandoi
3f9df2fb2d fix: enable no copy for serial no field 2025-03-12 16:35:50 +05:30
mergify[bot]
7f147446df fix: calculate due date based on payment term (backport #46416) (#46479)
fix: calculate due date based on payment term (#46416)

(cherry picked from commit 9e808c832f)

Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
2025-03-12 16:30:14 +05:30
Sugesh G
8def42f751 fix: pricing rule not ignored in Sales Order (#46248)
* fix: pricing rule not ignored in Sales Order

* test: update parameter do_not_submit to do_not_save

(cherry picked from commit f8c659d8d5)
2025-03-12 10:58:47 +00:00
Mihir Kandoi
79e6550321 fix: show remaining qty on 'Complete Job' button instead of full qty 2025-03-12 16:27:33 +05:30
Priyansh Shah
cc30a01898 fix: validations and account type filter for Tax Withholding Category (#46207)
fix: validations and account type filter for tax withholding category
(cherry picked from commit d371236684)
2025-03-12 10:52:32 +00:00
Sugesh G
38dabdf584 fix: use shipping_address_name for address validation in sales invoice (#46473)
* fix: validate address and contact related to party

* fix: solve unboundlocal error

* refactor: improve variable scope

* refactor: translatable strings

* fix: use shipping_address_name for address validation in sales invoice

* test: add new unit test for address and contact validation

* chore: to avoid keyerror

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit 0bdb81db53)
2025-03-12 10:44:37 +00:00
Ninad Parikh
877d5bd3aa fix: base_net_rate Required to Check Valid Range (backport #46332) (#46382)
fix: `base_net_rate` required to check valid range
2025-03-12 16:12:44 +05:30
Mihir Kandoi
0b53bd3e9a Merge pull request #46475 from frappe/mergify/bp/version-15-hotfix/pr-46417
fix: error when creating delivery note from pick list (backport #46417)
2025-03-12 16:09:50 +05:30
Mihir Kandoi
3606fe8fba Merge pull request #46467 from frappe/mergify/bp/version-15-hotfix/pr-46465
fix: rename sla fields patch (backport #46465)
2025-03-12 16:09:37 +05:30
Mihir Kandoi
0b921016ff fix: error when creating delivery note from pick list (#46417)
(cherry picked from commit 67e9389a02)
2025-03-12 10:00:53 +00:00
rohitwaghchaure
4d49608a68 Merge pull request #46474 from frappe/mergify/bp/version-15-hotfix/pr-46470
fix: do not recalculate qty for batch items during reposting (backport #46470)
2025-03-12 12:39:06 +05:30
Rohit Waghchaure
bac36f342d fix: do not recalculate qty for batch items during reposting
(cherry picked from commit 0753c018d2)
2025-03-12 06:48:00 +00:00
mergify[bot]
a4b8b4c771 fix: credit note creation during pos invoice consolidation (backport #46277) (#46469)
* fix: credit note creation during pos invoice consolidation (#46277)

* fix: credit note creation during pos invoice consolidation

* fix: added check to skip merging empty list of return pos invoices

* fix: sql query

* fix: using return invoice name instead of return invoice object

* fix: added pos invoice field in sales invoice item

(cherry picked from commit 8ba4ac3b86)

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

* chore: resolve conflict

---------

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-03-12 11:41:41 +05:30
mergify[bot]
3cef94e2ed fix: validate last_gl_update exists before comparing (backport #46464) (#46468)
fix: validate last_gl_update exists before comparing (#46464)

(cherry picked from commit 0a5ca0c35f)

Co-authored-by: Dany Robert <rtdany10@gmail.com>
2025-03-12 11:05:24 +05:30
mergify[bot]
13711993fe fix(payment entry): fetch default bank account based on company (backport #46379) (#46471)
fix(payment entry): fetch default bank account based on company (#46379)

(cherry picked from commit b72f6f5a3d)

Co-authored-by: Venkatesh <47534423+venkat102@users.noreply.github.com>
2025-03-12 11:05:08 +05:30
Nabin Hait
24678b0e24 chore: conflicts 2025-03-12 10:38:26 +05:30
Mihir Kandoi
5edbd8851a fix: rename sla fields patch (#46465)
* fix: rename sla fields patch

* fix: rerun patch

(cherry picked from commit 8bce42e633)

# Conflicts:
#	erpnext/patches.txt
2025-03-12 05:04:52 +00:00
rohitwaghchaure
6582e4c5f9 Merge pull request #46462 from frappe/mergify/bp/version-15-hotfix/pr-46455
fix: error in production analytics report (backport #46455)
2025-03-12 09:50:35 +05:30
rohitwaghchaure
fd1c1ba35e Merge pull request #46461 from frappe/mergify/bp/version-15-hotfix/pr-46460
fix: rename sla fields patch (backport #46460)
2025-03-12 09:49:46 +05:30
Mihir Kandoi
db6ae61935 fix: error in production analytics report
(cherry picked from commit 03e66468f6)
2025-03-11 19:00:35 +00:00
Mihir Kandoi
73f11cf19e fix: rename sla fields patch
(cherry picked from commit b6c18849c5)
2025-03-11 18:57:17 +00:00
rohitwaghchaure
8f7dc827ea Merge pull request #46459 from frappe/mergify/bp/version-15-hotfix/pr-46452
fix: sla fields patch (backport #46452)
2025-03-12 00:01:35 +05:30
Mihir Kandoi
0d044bc5bb fix: sla fields patch
(cherry picked from commit d653899372)
2025-03-11 18:08:24 +00:00
rohitwaghchaure
14ee2d239a Merge pull request #46441 from frappe/mergify/bp/version-15-hotfix/pr-46436
fix: filter batches that going to be zero (backport #46436)
2025-03-11 23:38:21 +05:30
Raffael Meyer
fc8eeaf4f6 Merge pull request #46457 from frappe/mergify/bp/version-15-hotfix/pr-46453
ci: ignore PRs labeled with "skip-release-notes" when generating release notes (backport #46453)
2025-03-11 17:51:58 +01:00
barredterra
aad8c88532 ci: ignore PRs labeled with "skip-release-notes" when generating release notes
(cherry picked from commit 57007bf937)
2025-03-11 16:35:20 +00:00
ruthra kumar
e783536ba0 Merge pull request #46450 from frappe/mergify/bp/version-15-hotfix/pr-46398
chore: rename print and stationery account (backport #46398)
2025-03-11 18:12:19 +05:30
chethank1407
ee3feba386 chore: rename print and stationery account
(cherry picked from commit 615997b774)
2025-03-11 12:03:42 +00:00
ruthra kumar
cb9be11448 Merge pull request #46447 from frappe/mergify/bp/version-15-hotfix/pr-46344
fix: make 'company_tax_id' and 'company_fiscal_code' as mandatory (backport #46344)
2025-03-11 17:31:19 +05:30
ruthra kumar
31dc6021e2 chore: translatable strings
(cherry picked from commit 121798ba85)
2025-03-11 11:42:30 +00:00
Bhavan23
229f4d3d92 fix: make 'company_tax_id' and 'company_fiscal_code' as mandatory
(cherry picked from commit abd044eb0d)
2025-03-11 11:42:19 +00:00
ruthra kumar
c25862a85f Merge pull request #46446 from frappe/mergify/bp/version-15-hotfix/pr-45818
fix: set correct account currency for deferred expense account (backport #45818)
2025-03-11 16:50:55 +05:30
vishakhdesai
f96848a3b9 fix: set correct account currency for deferred expence account in PI
(cherry picked from commit 398083853c)
2025-03-11 11:06:14 +00:00
Rohit Waghchaure
ac97489a32 fix: filter batches that going to be zero
(cherry picked from commit aba512c1c6)
2025-03-11 08:02:49 +00:00
ruthra kumar
02ff406b7c Merge pull request #46439 from frappe/mergify/bp/version-15-hotfix/pr-46372
fix(account): update account number from parent company (backport #46372)
2025-03-11 11:19:33 +05:30
venkat102
428aedc29c fix(account): update account number from parent company
(cherry picked from commit 4a4894bc01)
2025-03-11 05:32:15 +00:00
ruthra kumar
6af24dca6e Merge pull request #46432 from frappe/mergify/bp/version-15-hotfix/pr-46427
fix: not able to save work order with alternative item (backport #46414) (backport #46427)
2025-03-11 11:02:04 +05:30
ruthra kumar
8980eb9b9d Merge pull request #46437 from frappe/mergify/bp/version-15-hotfix/pr-46426
fix: clear cashe on employee hierarchy change (backport #46426)
2025-03-11 10:58:50 +05:30
Asmita Hase
4dfdb2b0a1 fix: clear cashe on employee hierarchy change to reflect updated permissions
(cherry picked from commit 6789578b27)
2025-03-11 05:13:04 +00:00
Rohit Waghchaure
9554a49bbd fix: not able to save work order with alternative item
(cherry picked from commit 6ca1f9bc73)
(cherry picked from commit ac7fc608aa)
2025-03-10 17:44:57 +00:00
rohitwaghchaure
5b69445294 Merge pull request #46420 from frappe/mergify/bp/version-15-hotfix/pr-46418
fix: stock balance in and out value (backport #46418)
2025-03-10 23:13:37 +05:30
Raffael Meyer
9c703765a1 fix: backport translations from develop (#46428) 2025-03-10 14:32:19 +01:00
rohitwaghchaure
043539fcdb chore: fix conflicts 2025-03-10 17:37:29 +05:30
ruthra kumar
54a76d8932 Merge pull request #46425 from frappe/mergify/bp/version-15-hotfix/pr-46407
fix: typo in sales_invoice_print (backport #46407)
2025-03-10 16:55:28 +05:30
mahsem
b6106212c1 fix: typo in sales_invoice_print
(cherry picked from commit f7bcae83e4)
2025-03-10 11:18:23 +00:00
Rohit Waghchaure
c2001e9c67 fix: stock balance in and out value
(cherry picked from commit e917bd5334)

# Conflicts:
#	erpnext/stock/report/stock_balance/stock_balance.py
2025-03-10 10:06:09 +00:00
Frappe PR Bot
47429095a2 chore(release): Bumped to Version 15.54.3
## [15.54.3](https://github.com/frappe/erpnext/compare/v15.54.2...v15.54.3) (2025-03-09)

### Bug Fixes

* consider account freeze date in recalculate_amount_difference_field patch ([34f03d6](34f03d608a))
* consider stock freeze date in recalculate_amount_difference_field patch ([a18721d](a18721d21c))
2025-03-09 11:04:11 +00:00
rohitwaghchaure
2f3f87fe7e Merge pull request #46403 from frappe/mergify/bp/version-15/pr-46402
fix: consider account freeze date in recalculate_amount_difference_fi… (backport #46395) (backport #46402)
2025-03-09 16:32:52 +05:30
Mihir Kandoi
2b4dfca3ff chore: resolve conflicts
(cherry picked from commit 985fb5dfdc)
2025-03-08 17:16:43 +00:00
Mihir Kandoi
a18721d21c fix: consider stock freeze date in recalculate_amount_difference_field patch
(cherry picked from commit cd72532789)
(cherry picked from commit 8264d42cd9)
2025-03-08 17:16:43 +00:00
Mihir Kandoi
34f03d608a fix: consider account freeze date in recalculate_amount_difference_field patch
(cherry picked from commit 696f931678)

# Conflicts:
#	erpnext/patches/v15_0/recalculate_amount_difference_field.py
(cherry picked from commit 8b67527900)
2025-03-08 17:16:43 +00:00
rohitwaghchaure
b786cd30e6 Merge pull request #46402 from frappe/mergify/bp/version-15-hotfix/pr-46395
fix: consider account freeze date in recalculate_amount_difference_fi… (backport #46395)
2025-03-08 22:45:06 +05:30
Mihir Kandoi
985fb5dfdc chore: resolve conflicts 2025-03-08 22:22:12 +05:30
Mihir Kandoi
8264d42cd9 fix: consider stock freeze date in recalculate_amount_difference_field patch
(cherry picked from commit cd72532789)
2025-03-08 16:28:33 +00:00
Mihir Kandoi
8b67527900 fix: consider account freeze date in recalculate_amount_difference_field patch
(cherry picked from commit 696f931678)

# Conflicts:
#	erpnext/patches/v15_0/recalculate_amount_difference_field.py
2025-03-08 16:28:32 +00:00
Frappe PR Bot
2ce8299bc8 chore(release): Bumped to Version 15.54.2
## [15.54.2](https://github.com/frappe/erpnext/compare/v15.54.1...v15.54.2) (2025-03-08)

### Bug Fixes

* incorrect category in list ([3bdd4ce](3bdd4ce116))
* **test:** incorrect transaction exchange rate in test case ([3015628](3015628519))
2025-03-08 07:42:44 +00:00
ruthra kumar
84b03485d6 Merge pull request #46397 from frappe/mergify/bp/version-15/pr-46064
fix: Debit and Credit mismatch on transaction currency debit and credit values (backport #46064)
2025-03-08 13:11:25 +05:30
ruthra kumar
d0b14f1907 chore: resolve conflict 2025-03-08 12:28:49 +05:30
ruthra kumar
cd1803a74d refactor: internal transfer gl
(cherry picked from commit f1d8feec15)
2025-03-08 06:56:37 +00:00
ruthra kumar
a1cf27ec17 test: assert total debit and credit for trx currency
(cherry picked from commit 55d0636123)
2025-03-08 06:56:37 +00:00
ruthra kumar
61880a311a refactor: handle rounding diff for trx currency dr and cr
(cherry picked from commit 455a55b2ce)
2025-03-08 06:56:37 +00:00
ruthra kumar
2d290b153d refactor: trx currency dr and cr for tax rows and item rows
(cherry picked from commit 4cd3f3531c)
2025-03-08 06:56:36 +00:00
ruthra kumar
cacb720556 refactor: convert tax amount using exchange rate
(cherry picked from commit 7528ef147a)
2025-03-08 06:56:36 +00:00
ruthra kumar
3bdd4ce116 fix: incorrect category in list
(cherry picked from commit 6545467aec)
2025-03-08 06:56:36 +00:00
ruthra kumar
c479998cd6 chore: typo
(cherry picked from commit bc792c61e9)
2025-03-08 06:56:36 +00:00
ruthra kumar
501e388186 refactor: isolate to specific doctypes
(cherry picked from commit b348aa3b37)
2025-03-08 06:56:35 +00:00
ruthra kumar
af45ec0d6d refactor(test): save first to let the tax table populate
(cherry picked from commit 23d465805b)
2025-03-08 06:56:35 +00:00
ruthra kumar
3015628519 fix(test): incorrect transaction exchange rate in test case
(cherry picked from commit a31770d122)
2025-03-08 06:56:35 +00:00
ruthra kumar
8b6eea6349 refactor: set transaction currency and rate before gl map
(cherry picked from commit ceca5b4c72)
2025-03-08 06:56:34 +00:00
ruthra kumar
81c29e8f8c refactor: handle payment entry
(cherry picked from commit 5c86e3ce85)
2025-03-08 06:56:34 +00:00
ruthra kumar
04758d3de3 refactor: handle Journal entries
(cherry picked from commit 9f3847c0f8)
2025-03-08 06:56:34 +00:00
ruthra kumar
615b0c40a3 refactor: move utility method to controller
(cherry picked from commit d1d06885dc)
2025-03-08 06:56:34 +00:00
ruthra kumar
231abab321 refactor: set transaction currency dr/cr in sales invoice
(cherry picked from commit 3e292ef2cb)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2025-03-08 06:56:33 +00:00
ruthra kumar
fff3b1e84e refactor: handle stocked items
(cherry picked from commit ee93ed8c97)
2025-03-08 06:56:33 +00:00
ruthra kumar
5299a1032b refactor: handle stocked items
(cherry picked from commit 7ff3977394)
2025-03-08 06:56:33 +00:00
ruthra kumar
1e5fbc0a48 refactor: set tr currency dr & cr directly on parent document
(cherry picked from commit e9af567033)
2025-03-08 06:56:32 +00:00
ruthra kumar
5c47c35a0f refactor: use highest precision for storing exc rate
(cherry picked from commit b115bf2e2a)
2025-03-08 06:56:32 +00:00
ruthra kumar
2bf910a786 Merge pull request #46396 from frappe/mergify/bp/version-15-hotfix/pr-46064
fix: Debit and Credit mismatch on transaction currency debit and credit values (backport #46064)
2025-03-08 12:25:57 +05:30
ruthra kumar
0b8673777a chore: resolve conflict 2025-03-08 12:11:54 +05:30
ruthra kumar
8f4c1e7169 refactor: internal transfer gl
(cherry picked from commit f1d8feec15)
2025-03-08 06:36:37 +00:00
ruthra kumar
f303245fae test: assert total debit and credit for trx currency
(cherry picked from commit 55d0636123)
2025-03-08 06:36:37 +00:00
ruthra kumar
cd21e5c652 refactor: handle rounding diff for trx currency dr and cr
(cherry picked from commit 455a55b2ce)
2025-03-08 06:36:37 +00:00
ruthra kumar
57e0f73595 refactor: trx currency dr and cr for tax rows and item rows
(cherry picked from commit 4cd3f3531c)
2025-03-08 06:36:37 +00:00
ruthra kumar
2c73e31742 refactor: convert tax amount using exchange rate
(cherry picked from commit 7528ef147a)
2025-03-08 06:36:36 +00:00
ruthra kumar
002685fc89 fix: incorrect category in list
(cherry picked from commit 6545467aec)
2025-03-08 06:36:36 +00:00
ruthra kumar
38a3a43ba5 chore: typo
(cherry picked from commit bc792c61e9)
2025-03-08 06:36:36 +00:00
ruthra kumar
07f938cc10 refactor: isolate to specific doctypes
(cherry picked from commit b348aa3b37)
2025-03-08 06:36:35 +00:00
ruthra kumar
5c013172f9 refactor(test): save first to let the tax table populate
(cherry picked from commit 23d465805b)
2025-03-08 06:36:35 +00:00
ruthra kumar
b76c96820e fix(test): incorrect transaction exchange rate in test case
(cherry picked from commit a31770d122)
2025-03-08 06:36:35 +00:00
ruthra kumar
1d56931050 refactor: set transaction currency and rate before gl map
(cherry picked from commit ceca5b4c72)
2025-03-08 06:36:34 +00:00
ruthra kumar
66dc79ceb5 refactor: handle payment entry
(cherry picked from commit 5c86e3ce85)
2025-03-08 06:36:34 +00:00
ruthra kumar
00cbc89b5f refactor: handle Journal entries
(cherry picked from commit 9f3847c0f8)
2025-03-08 06:36:33 +00:00
ruthra kumar
d7baa451e3 refactor: move utility method to controller
(cherry picked from commit d1d06885dc)
2025-03-08 06:36:33 +00:00
ruthra kumar
e0b5386bf0 refactor: set transaction currency dr/cr in sales invoice
(cherry picked from commit 3e292ef2cb)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2025-03-08 06:36:33 +00:00
ruthra kumar
133dca1824 refactor: handle stocked items
(cherry picked from commit ee93ed8c97)
2025-03-08 06:36:32 +00:00
ruthra kumar
eb0df50f1b refactor: handle stocked items
(cherry picked from commit 7ff3977394)
2025-03-08 06:36:32 +00:00
ruthra kumar
5761cfb3d5 refactor: set tr currency dr & cr directly on parent document
(cherry picked from commit e9af567033)
2025-03-08 06:36:32 +00:00
ruthra kumar
568b582b6a refactor: use highest precision for storing exc rate
(cherry picked from commit b115bf2e2a)
2025-03-08 06:36:31 +00:00
Shariq Ansari
d115c1c156 Merge pull request #46365 from frappe/mergify/bp/version-15-hotfix/pr-46352
fix: Allow rename prospect doctype (backport #46352)
2025-03-07 17:12:22 +05:30
Nabin Hait
4e688778dc chore: linter issue 2025-03-07 10:28:52 +05:30
Mihir Kandoi
b5890c1d55 Merge pull request #46376 from frappe/mergify/bp/version-15-hotfix/pr-46374 2025-03-06 23:00:13 +05:30
Mihir Kandoi
6419d020a1 fix: rare precision issue preventing submission of subcontracting order
(cherry picked from commit 33b71544db)
2025-03-06 16:46:04 +00:00
Nabin Hait
60364f6dc9 chore: conflicts 2025-03-06 18:14:09 +05:30
mergify[bot]
23c4252b9b fix: change fieldname for cash_flow to export (backport #46353) (#46366)
fix: change fieldname for cash_flow to export (#46353)

fix: change fieldname for cash_flow

Co-authored-by: Sanket322 <shahsanket322003.com>
(cherry picked from commit 606dcb0ad1)

Co-authored-by: Sanket Shah <113279972+Sanket322@users.noreply.github.com>
2025-03-06 18:12:31 +05:30
mergify[bot]
c816f9bd0a fix: validate accounting dimension company in Journal Entry & Stock Entry (backport #46204) (#46369)
fix: validate accounting dimension company in Journal Entry & Stock Entry (#46204)

* fix: validate accounting dimension company in journal entry and stock entry

* test: update test cases to validate company-based accounting dimension

* fix(test): ensure 'Pick List' company matches 'Delivery Note' to prevent test failures

* chore: remove redundant lines of code

(cherry picked from commit 7b6ebad9e6)

Co-authored-by: Bhavansathru <122002510+Bhavan23@users.noreply.github.com>
2025-03-06 18:12:06 +05:30
Shariq Ansari
de46165768 fix: Allow rename prospect doctype (#46352)
fix: allow rename prospect doctype
(cherry picked from commit 884709deb8)

# Conflicts:
#	erpnext/crm/doctype/prospect/prospect.json
2025-03-06 11:50:08 +00:00
Justine Jay
5cc251a172 fix: auto email report creation (#46343)
* fix(financial_statements): mandatory based on filter_based_on value

* fix(financial_statements.js): include options for multiselect
2025-03-06 17:18:26 +05:30
Frappe PR Bot
7c8b34fd8f chore(release): Bumped to Version 15.54.1
## [15.54.1](https://github.com/frappe/erpnext/compare/v15.54.0...v15.54.1) (2025-03-06)

### Bug Fixes

* check if set_landed_cost_based_on_purchase_invoice_rate is enabled before running patch ([12bf31d](12bf31df87))
* recalculate_amount_difference_field patch ([041335f](041335f318))
* rename_sla_fields patch ([bb553c2](bb553c27ab))
2025-03-06 11:05:55 +00:00
rohitwaghchaure
83320c97fa Merge pull request #46360 from frappe/mergify/bp/version-15/pr-46357
fix: rename_sla_fields patch (backport #46355) (backport #46357)
2025-03-06 16:34:33 +05:30
rohitwaghchaure
b004865240 Merge pull request #46362 from frappe/mergify/bp/version-15/pr-46358
fix: recalculate_amount_difference_field patch (backport #46354) (backport #46358)
2025-03-06 16:34:20 +05:30
Mihir Kandoi
525780645a chore: resolve conflicts
(cherry picked from commit 8e65b0ec0c)
2025-03-06 10:42:01 +00:00
Mihir Kandoi
12bf31df87 fix: check if set_landed_cost_based_on_purchase_invoice_rate is enabled before running patch
(cherry picked from commit 95d1976931)

# Conflicts:
#	erpnext/patches/v15_0/recalculate_amount_difference_field.py
(cherry picked from commit 7047fe2681)
2025-03-06 10:42:01 +00:00
Mihir Kandoi
041335f318 fix: recalculate_amount_difference_field patch
(cherry picked from commit 0492b941ff)

# Conflicts:
#	erpnext/patches/v15_0/recalculate_amount_difference_field.py
(cherry picked from commit f247f02e49)
2025-03-06 10:42:00 +00:00
rohitwaghchaure
9c9c8c9356 Merge pull request #46358 from frappe/mergify/bp/version-15-hotfix/pr-46354
fix: recalculate_amount_difference_field patch (backport #46354)
2025-03-06 14:53:08 +05:30
Mihir Kandoi
8e65b0ec0c chore: resolve conflicts 2025-03-06 14:13:55 +05:30
Mihir Kandoi
bb553c27ab fix: rename_sla_fields patch
(cherry picked from commit e8d4a487c6)
(cherry picked from commit 7bc7557018)
2025-03-06 08:20:30 +00:00
rohitwaghchaure
7080e1422d Merge pull request #46357 from frappe/mergify/bp/version-15-hotfix/pr-46355
fix: rename_sla_fields patch (backport #46355)
2025-03-06 13:48:54 +05:30
Mihir Kandoi
7047fe2681 fix: check if set_landed_cost_based_on_purchase_invoice_rate is enabled before running patch
(cherry picked from commit 95d1976931)

# Conflicts:
#	erpnext/patches/v15_0/recalculate_amount_difference_field.py
2025-03-06 07:58:38 +00:00
Mihir Kandoi
f247f02e49 fix: recalculate_amount_difference_field patch
(cherry picked from commit 0492b941ff)

# Conflicts:
#	erpnext/patches/v15_0/recalculate_amount_difference_field.py
2025-03-06 07:58:37 +00:00
Mihir Kandoi
7bc7557018 fix: rename_sla_fields patch
(cherry picked from commit e8d4a487c6)
2025-03-06 07:49:12 +00:00
rohitwaghchaure
e72264f448 Merge pull request #46346 from frappe/mergify/bp/version-15-hotfix/pr-46339
fix: doctype name (backport #46339)
2025-03-05 21:44:40 +05:30
Rohit Waghchaure
1dcbdf3257 fix: doctype name
(cherry picked from commit d039310d80)
2025-03-05 15:48:13 +00:00
Mihir Kandoi
5d6730a059 Merge pull request #46345 from frappe/mergify/bp/version-15-hotfix/pr-45693
fix: uom reverts to default upon selecting do not explode (backport #45693)
2025-03-05 21:09:04 +05:30
Mihir Kandoi
6b1d20970e fix: uom reverts to default upon selecting do not explode (#45693)
* fix: uom reverts to default upon selecting do not explode

* fix: logical error failing tests

(cherry picked from commit 58ed697ba5)
2025-03-05 15:21:58 +00:00
Frappe PR Bot
528107e224 chore(release): Bumped to Version 15.54.0
# [15.54.0](https://github.com/frappe/erpnext/compare/v15.53.4...v15.54.0) (2025-03-05)

### Bug Fixes

* Accounting Period validation throwing for different companies ([6df9cf3](6df9cf327d))
* Add company filter at get_invoice method (backport [#46238](https://github.com/frappe/erpnext/issues/46238)) ([#46299](https://github.com/frappe/erpnext/issues/46299)) ([4f80ddd](4f80ddd834))
* Add permission check in POS's `Toggle Recent Orders` (backport [#46010](https://github.com/frappe/erpnext/issues/46010)) ([#46274](https://github.com/frappe/erpnext/issues/46274)) ([7759775](7759775ee6))
* adding cost center on pos invoice items while applying product discount (backport [#46082](https://github.com/frappe/erpnext/issues/46082)) ([#46322](https://github.com/frappe/erpnext/issues/46322)) ([9a433a6](9a433a6750))
* **asset depreciation schedules:** enable auto commit ([899e468](899e468f6a))
* auto allocation for negative amount outstanding for Customers in Payment Entry ([78a329e](78a329e573))
* Batch Price gets updated only if it is a billed item ([dbd47df](dbd47dff98))
* Batch Price gets updated only if it is a billed item ([8ed512f](8ed512f6c6))
* change voucher_type and voucher_no field type to data ([3a03865](3a03865a8f))
* Close and Reopen buttons dissapear after saving changes ([#46048](https://github.com/frappe/erpnext/issues/46048)) ([506dd3c](506dd3c6b9))
* consider journal entry and return invoice in paid_amount calculation (backport [#46129](https://github.com/frappe/erpnext/issues/46129)) ([#46319](https://github.com/frappe/erpnext/issues/46319)) ([836fd8f](836fd8fbc4))
* consolidate gl entries by project in General Ledger Report (backport [#46314](https://github.com/frappe/erpnext/issues/46314)) ([#46321](https://github.com/frappe/erpnext/issues/46321)) ([6aa8803](6aa8803068))
* Convert tuple of tuples to list of dicts for dot notation access ([#46062](https://github.com/frappe/erpnext/issues/46062)) ([ef19551](ef195513d0))
* delivery note from sales order uom conversion mistake ([d10add4](d10add4b1e))
* depreciation and balances report correction (backport [#46259](https://github.com/frappe/erpnext/issues/46259)) ([#46305](https://github.com/frappe/erpnext/issues/46305)) ([087dde5](087dde5873))
* discount accounting for v15 ([f609012](f609012f02))
* do not include opening invoices in billed items to be received report ([eee500f](eee500f20e))
* don't allow renaming account while system is actively in use (backport [#46176](https://github.com/frappe/erpnext/issues/46176)) ([#46210](https://github.com/frappe/erpnext/issues/46210)) ([faee8d6](faee8d6c5e))
* dont update rate of free item when batch is updated ([9e649d8](9e649d8522))
* error ([61d5680](61d5680c8d))
* exclude already consumed purchase receipt items from asset capitalization (backport [#46329](https://github.com/frappe/erpnext/issues/46329)) ([#46336](https://github.com/frappe/erpnext/issues/46336)) ([6c1ceff](6c1ceff8ee))
* exclude cancelled gl entries ([5e08386](5e083861a4))
* fiscal year error ([7b13d8c](7b13d8cd98))
* fixing test case ([9f4311e](9f4311e7fb))
* if invoice is return then add amount in proper column ([0a65217](0a65217423))
* Include additional account types for Expense Account in LCV (backport [#46206](https://github.com/frappe/erpnext/issues/46206)) ([#46296](https://github.com/frappe/erpnext/issues/46296)) ([88234bb](88234bbf9a))
* incorrect batch picked ([e94f0b1](e94f0b1cca))
* incorrect batch picked in the pick list (backport [#45761](https://github.com/frappe/erpnext/issues/45761)) ([#46315](https://github.com/frappe/erpnext/issues/46315)) ([5a3073c](5a3073c4c1))
* incorrectly billed amount in the purchase receipt ([c247cf8](c247cf888b))
* Naming of Purchase Amount (backport [#46051](https://github.com/frappe/erpnext/issues/46051)) ([#46324](https://github.com/frappe/erpnext/issues/46324)) ([aaf35c5](aaf35c5df9))
* only include submitted docs for internal received quantity validation (backport [#46262](https://github.com/frappe/erpnext/issues/46262)) ([#46304](https://github.com/frappe/erpnext/issues/46304)) ([5ae9faa](5ae9faab91))
* patch ([5e06e4a](5e06e4acce))
* patch ([8f2fdca](8f2fdcae88))
* patch path ([af49f5a](af49f5a8af))
* **patch:** Ensure SLE indexes (backport [#46131](https://github.com/frappe/erpnext/issues/46131)) ([#46135](https://github.com/frappe/erpnext/issues/46135)) ([f3cafef](f3cafef6a7))
* payment entry exchange gain loss issue ([3fb9033](3fb9033fb7))
* pos item detail serial no field (backport [#46211](https://github.com/frappe/erpnext/issues/46211)) ([#46212](https://github.com/frappe/erpnext/issues/46212)) ([de0dfbc](de0dfbca9a))
* pos item selection using serial no (backport [#46200](https://github.com/frappe/erpnext/issues/46200)) ([#46203](https://github.com/frappe/erpnext/issues/46203)) ([0f263bc](0f263bcff2))
* **pos:** get parent item group without user permission ([#46020](https://github.com/frappe/erpnext/issues/46020)) ([29f3aac](29f3aac925))
* production analytics report ([5668795](5668795884))
* rate changing on the deliver note ([75bc68b](75bc68b863))
* rearrange stock settings fields ([87703c6](87703c6511))
* removed mandatory property for the cost center field ([a94292a](a94292a69f))
* rename some sla fields ([c5717b9](c5717b983d))
* replacing serial and batch bundle on pos with auto fetch serial nos (backport [#46236](https://github.com/frappe/erpnext/issues/46236)) ([#46337](https://github.com/frappe/erpnext/issues/46337)) ([1e85f69](1e85f69072))
* **report:** allow `Closed` purchase orders to be visible ([20c4487](20c4487853))
* **report:** allow `Closed` sales orders to be visible ([8799af9](8799af9747))
* **report:** filter sales / purchase orders based on date filters ([2221bf1](2221bf1cba))
* revamp logic (split parent and child) ([7437cea](7437cea458))
* revert last commit ([a09c57f](a09c57f0d1))
* set landed cost based on purchase invoice rate ([fdaf5fa](fdaf5fafda))
* set taxes before calculating taxes and totals ([90dea42](90dea426d8))
* source warehouse not fetched in bom creator ([6157fed](6157fed71c))
* stock qty not recalculate on changing of the qty ([331798b](331798babc))
* stock reservation issue while making Purchase Invoice ([8bd7195](8bd71954f3))
* syntax error ([1790bcc](1790bcc6d1))
* syntax error ([bd48d39](bd48d391e4))
* syntax error ([cc535b7](cc535b7636))
* test case for debit note ([a8b31df](a8b31df65d))
* tests ([46b0734](46b0734d6f))
* tests ([d413039](d41303961c))
* translation DE ([ddcf79d](ddcf79da1d))
* use else instead of unnecessary elif ([6f760d1](6f760d197d))
* use valuation method from settings in stock ageing report (backport [#46068](https://github.com/frappe/erpnext/issues/46068)) ([#46297](https://github.com/frappe/erpnext/issues/46297)) ([d02d005](d02d005913))
* use value from currency exchange when exchange api is disabled (backport [#46137](https://github.com/frappe/erpnext/issues/46137)) ([#46309](https://github.com/frappe/erpnext/issues/46309)) ([ccc0358](ccc0358db6))
* **workspace:** enable is_query_report on purchase reports (backport [#46249](https://github.com/frappe/erpnext/issues/46249)) ([#46306](https://github.com/frappe/erpnext/issues/46306)) ([363129b](363129bcd4))
* Wrong Overdue Status in Sales Invoices (Floating-point arithmetic) (backport [#46146](https://github.com/frappe/erpnext/issues/46146)) ([#46310](https://github.com/frappe/erpnext/issues/46310)) ([1ff0858](1ff085876e))

### Features

* add new Closed and Stopped rows ([7749814](7749814571))
* add total weight in shipment ([#46049](https://github.com/frappe/erpnext/issues/46049)) ([171df3a](171df3aba5))
* create sales invoice print format ([#45403](https://github.com/frappe/erpnext/issues/45403)) ([1a382eb](1a382ebe86))
* **received items to be billed:** add company and date filters (backport [#46271](https://github.com/frappe/erpnext/issues/46271)) ([#46302](https://github.com/frappe/erpnext/issues/46302)) ([400f4f3](400f4f32ad))
* **Sales Invoice:** add items row via "Fetch Timesheet" (backport [#46071](https://github.com/frappe/erpnext/issues/46071)) ([#46311](https://github.com/frappe/erpnext/issues/46311)) ([1c6e464](1c6e4649bd))

### Performance Improvements

* don't track seen for POS Invoice (backport [#46187](https://github.com/frappe/erpnext/issues/46187)) ([#46189](https://github.com/frappe/erpnext/issues/46189)) ([41ab7f3](41ab7f3f7c))
* patch ([ee41e55](ee41e55343))
* replace if function in query ([d0b8e0d](d0b8e0da8d))
2025-03-05 13:31:02 +00:00
ruthra kumar
86b917b04c Merge pull request #46263 from frappe/version-15-hotfix
chore: release v15
2025-03-05 18:59:38 +05:30
ruthra kumar
9875489758 Merge pull request #46340 from frappe/mergify/bp/version-15-hotfix/pr-46020
fix(pos): get parent item group without user permission (backport #46020)
2025-03-05 18:40:04 +05:30
ruthra kumar
939cf321f7 Merge pull request #46338 from frappe/mergify/bp/version-15-hotfix/pr-45403
feat: create sales invoice print format (backport #45403)
2025-03-05 18:39:43 +05:30
Sugesh G
29f3aac925 fix(pos): get parent item group without user permission (#46020)
* fix(pos): get parent item group without user permission

* feat: add item group filter based on user permission

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
(cherry picked from commit 8caf7f275e)
2025-03-05 12:42:03 +00:00
Ejaaz Khan
1a382ebe86 feat: create sales invoice print format (#45403)
* feat: create sales invoice print format

* fix: linter issue

* style: remove border from table

* refactor: change label to uppercase and show taxes

* refactor: format date and add translation on label

* refactor: remove default header and format labels

* refactor: change label style and small fix

* chore: Qty in title case

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
(cherry picked from commit 38aa7cab8a)
2025-03-05 12:38:03 +00:00
mergify[bot]
6c1ceff8ee fix: exclude already consumed purchase receipt items from asset capitalization (backport #46329) (#46336)
* fix: exclude already consumed purchase receipt items from asset capitalization (#46329)

* feat: link purchase receipt row item to capitalization

* fix: avoid fetching already consumed stock and asset items during capitalization

* fix(patch): added patch to link purchase receipt item to stock item child table

* fix: added nosemgrep

* refactor: rename  to

(cherry picked from commit f50d479bfd)

# Conflicts:
#	erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
#	erpnext/patches.txt

* fix: resolved conflicts

* fix: resolved conflicts

---------

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-03-05 18:07:14 +05:30
mergify[bot]
1e85f69072 fix: replacing serial and batch bundle on pos with auto fetch serial nos (backport #46236) (#46337)
fix: replacing serial and batch bundle on pos with auto fetch serial nos (#46236)

* fix: replacing serial and batch bundle on pos with auto fetch serial nos

* fix: reserved serial no

added a check to look for serial no in reserved serial nos list before removing it as there might be a situation where an item is returned which was already consolidated.

(cherry picked from commit 35512d40bb)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-03-05 18:00:31 +05:30
rohitwaghchaure
1992c2639c Merge pull request #46333 from frappe/mergify/bp/version-15-hotfix/pr-46330
fix: Accounting Period validation throwing for different companies (backport #46330)
2025-03-05 17:03:24 +05:30
Mihir Kandoi
8f278ab7c7 Merge pull request #46317 from frappe/mergify/bp/version-15-hotfix/pr-45947
fix: set landed cost based on purchase invoice rate (backport #45947)
2025-03-05 16:58:41 +05:30
Mihir Kandoi
7a33bf41d8 Merge pull request #46331 from frappe/mergify/bp/version-15-hotfix/pr-46313
fix: production analytics report (backport #46313)
2025-03-05 16:45:33 +05:30
Rohit Waghchaure
6df9cf327d fix: Accounting Period validation throwing for different companies
(cherry picked from commit b1508efca2)
2025-03-05 11:11:04 +00:00
Mihir Kandoi
5e06e4acce fix: patch 2025-03-05 16:32:26 +05:30
Mihir Kandoi
7749814571 feat: add new Closed and Stopped rows
(cherry picked from commit 6cc3d67835)
2025-03-05 10:54:36 +00:00
Mihir Kandoi
6f760d197d fix: use else instead of unnecessary elif
(cherry picked from commit 961258a4ce)
2025-03-05 10:54:36 +00:00
Mihir Kandoi
5668795884 fix: production analytics report
(cherry picked from commit 772e9ecfaa)
2025-03-05 10:54:36 +00:00
mergify[bot]
aaf35c5df9 fix: Naming of Purchase Amount (backport #46051) (#46324)
fix: Naming of Purchase Amount (#46051)

* fix: Naming of Purchase Amount

* fix: linters

(cherry picked from commit 104f60cc57)

Co-authored-by: 0xD0M1M0 <76812428+0xD0M1M0@users.noreply.github.com>
2025-03-05 16:18:50 +05:30
mergify[bot]
6aa8803068 fix: consolidate gl entries by project in General Ledger Report (backport #46314) (#46321)
fix: consolidate gl entries by project in General Ledger Report (#46314)

(cherry picked from commit 1f685efcaf)

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
2025-03-05 16:18:42 +05:30
Ben Kebdani
506dd3c6b9 fix: Close and Reopen buttons dissapear after saving changes (#46048)
* fix: Close and Reopen buttons dissapear after saving changes

* style: linter issue

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-05 15:34:55 +05:30
mergify[bot]
9a433a6750 fix: adding cost center on pos invoice items while applying product discount (backport #46082) (#46322)
fix: adding cost center on pos invoice items while applying product discount (#46082)

(cherry picked from commit 926e4ecc4f)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-03-05 15:22:46 +05:30
mergify[bot]
e3ce17bd6e fix:[regional] Italian einvoice xml generated with wrong prices (#40254) (backport #45840) (#46318)
Merge pull request #45840 from gms-electronics/40254-italian-einvoice

fix:[regional] Italian einvoice xml generated with wrong prices (#40254)
(cherry picked from commit c5f90c823d)

Co-authored-by: Fab <fabian.thobe@mwv.vc>
2025-03-05 15:19:41 +05:30
mergify[bot]
836fd8fbc4 fix: consider journal entry and return invoice in paid_amount calculation (backport #46129) (#46319)
fix: consider journal entry and return invoice in paid_amount calculation (#46129)

* fix: consider journal entry and return invoice in paid_amount calculation

* test: add new unit test to consider journal entry and return invoice in paid_amount calculation

(cherry picked from commit 425fb12e91)

Co-authored-by: Sugesh G <73237300+Sugesh393@users.noreply.github.com>
2025-03-05 15:07:36 +05:30
Mihir Kandoi
fe8c9a3605 chore: resolve conflicts 2025-03-05 14:54:40 +05:30
Mihir Kandoi
941d67a0b6 chore: resolve conflicts 2025-03-05 14:48:41 +05:30
mergify[bot]
5a3073c4c1 fix: incorrect batch picked in the pick list (backport #45761) (#46315)
fix: incorrect batch picked in the pick list

(cherry picked from commit e1b7688a17)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2025-03-05 14:48:01 +05:30
Mihir Kandoi
8f2fdcae88 fix: patch
(cherry picked from commit 1230127d24)

# Conflicts:
#	erpnext/patches.txt
2025-03-05 09:02:36 +00:00
Mihir Kandoi
a09c57f0d1 fix: revert last commit
(cherry picked from commit 154e9813c4)

# Conflicts:
#	erpnext/patches.txt
2025-03-05 09:02:35 +00:00
Mihir Kandoi
7b13d8cd98 fix: fiscal year error
(cherry picked from commit 7cf8e498c4)
2025-03-05 09:02:34 +00:00
Mihir Kandoi
ee41e55343 perf: patch
(cherry picked from commit a41024813b)
2025-03-05 09:02:34 +00:00
Mihir Kandoi
fdaf5fafda fix: set landed cost based on purchase invoice rate
(cherry picked from commit 17d415b105)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
#	erpnext/patches.txt
#	erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
2025-03-05 09:02:34 +00:00
rohitwaghchaure
c85fd36960 Merge pull request #46308 from frappe/mergify/bp/version-15-hotfix/pr-45987
fix: rename some sla fields (backport #45987)
2025-03-05 14:30:54 +05:30
rohitwaghchaure
a4e5a46566 Merge pull request #46298 from frappe/mergify/bp/version-15-hotfix/pr-46046
perf: optimize query in project.py (backport #46046)
2025-03-05 14:30:26 +05:30
ruthra kumar
3c39888227 Merge pull request #46069 from frappe/mergify/bp/version-15-hotfix/pr-46049
feat: add total weight in shipment (backport #46049)
2025-03-05 14:23:49 +05:30
mergify[bot]
d5f07f06c7 refactor: rename subcontracting fields (backport #46226) (#46301)
* refactor: rename subcontracting fields

(cherry picked from commit b4f65154f5)

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-03-05 14:08:10 +05:30
mergify[bot]
ccc0358db6 fix: use value from currency exchange when exchange api is disabled (backport #46137) (#46309)
fix: use value from currency exchange when exchange api is disabled (#46137)

(cherry picked from commit 2d26bff870)

Co-authored-by: Venkatesh <47534423+venkat102@users.noreply.github.com>
2025-03-05 14:07:12 +05:30
mergify[bot]
1ff085876e fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic) (backport #46146) (#46310)
fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic) (#46146)

* fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic)

* style: after run pre-commit

(cherry picked from commit 89bcdd6fa5)

Co-authored-by: Diógenes Souza <103958767+devdiogenes@users.noreply.github.com>
2025-03-05 14:07:03 +05:30
mergify[bot]
1c6e4649bd feat(Sales Invoice): add items row via "Fetch Timesheet" (backport #46071) (#46311)
feat(Sales Invoice): add items row via "Fetch Timesheet" (#46071)

(cherry picked from commit 94547188bf)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-03-05 14:06:55 +05:30
Mihir Kandoi
e4e1be568b Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-45987 2025-03-05 13:59:59 +05:30
Mihir Kandoi
01db730714 Merge pull request #46300 from frappe/mergify/bp/version-15-hotfix/pr-46258
fix: delivery note from sales order uom conversion mistake (backport #46258)
2025-03-05 13:59:14 +05:30
ruthra kumar
dbcffa7ea4 Merge pull request #46235 from frappe/mergify/bp/version-15-hotfix/pr-46223
fix: dont update rate of free item when batch is updated (backport #46223)
2025-03-05 13:57:23 +05:30
ruthra kumar
36fa6bf15c chore: resolve conflict 2025-03-05 13:55:50 +05:30
mergify[bot]
4f80ddd834 fix: Add company filter at get_invoice method (backport #46238) (#46299)
fix: Add company filter at get_invoice method (#46238)

(cherry picked from commit a8d1cbc1c3)

Co-authored-by: Kunhi <kunhimohamed6@gmail.com>
2025-03-05 13:12:20 +05:30
mergify[bot]
400f4f32ad feat(received items to be billed): add company and date filters (backport #46271) (#46302)
feat(received items to be billed): add company and date filters (#46271)

* feat(received items to be billed): add company and date filters

* feat(delivered to be billed): add company and date filters

* feat: add company and date conditions

* chore: remove debugger

(cherry picked from commit 6117706ab5)

Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
2025-03-05 13:12:09 +05:30
mergify[bot]
363129bcd4 fix(workspace): enable is_query_report on purchase reports (backport #46249) (#46306)
fix(workspace): enable is_query_report on purchase reports (#46249)

* fix(workspace): enable is_query_report on purchase reports

* fix: resolved conflict

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
(cherry picked from commit 5513e24b00)

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-05 13:11:53 +05:30
mergify[bot]
087dde5873 fix: depreciation and balances report correction (backport #46259) (#46305)
fix: depreciation and balances report correction (#46259)

(cherry picked from commit 4a542b22a4)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-03-05 13:11:45 +05:30
mergify[bot]
5ae9faab91 fix: only include submitted docs for internal received quantity validation (backport #46262) (#46304)
fix: only include submitted docs for internal received quantity validation (#46262)

(cherry picked from commit 88fcdbb81e)

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
2025-03-05 13:11:34 +05:30
Mihir Kandoi
446a8fe096 chore: resolve conflicts 2025-03-05 13:08:47 +05:30
Mihir Kandoi
0b50f1a9c3 chore: fix pre-commit/linter error 2025-03-05 12:57:53 +05:30
Mihir Kandoi
f29c43811c chore: resolve conflicts 2025-03-05 12:55:10 +05:30
Mihir Kandoi
e4fbd22173 Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-46223 2025-03-05 12:51:01 +05:30
Mihir Kandoi
6b56724436 chore: resolve conflicts 2025-03-05 12:46:52 +05:30
Mihir Kandoi
af49f5a8af fix: patch path
(cherry picked from commit dcec446e55)

# Conflicts:
#	erpnext/patches.txt
2025-03-05 07:16:48 +00:00
Mihir Kandoi
46b0734d6f fix: tests
(cherry picked from commit 019303dd12)

# Conflicts:
#	erpnext/support/doctype/issue/issue.json
2025-03-05 07:16:47 +00:00
Mihir Kandoi
d41303961c fix: tests
(cherry picked from commit 1b831e9abd)
2025-03-05 07:16:47 +00:00
Mihir Kandoi
c5717b983d fix: rename some sla fields
(cherry picked from commit baa0dd1235)

# Conflicts:
#	erpnext/patches.txt
2025-03-05 07:16:46 +00:00
mergify[bot]
d02d005913 fix: use valuation method from settings in stock ageing report (backport #46068) (#46297)
fix: use valuation method from settings in stock ageing report

(cherry picked from commit da09c278c8)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-03-05 12:40:13 +05:30
Mihir Kandoi
d10add4b1e fix: delivery note from sales order uom conversion mistake
(cherry picked from commit 49a43d355d)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.py
2025-03-05 06:52:07 +00:00
mergify[bot]
88234bbf9a fix: Include additional account types for Expense Account in LCV (backport #46206) (#46296)
fix: Include additional account types for Expense Account in LCV (#46206)

fix: additional account types in filters for the Expense account selection
(cherry picked from commit 59e99f167d)

Co-authored-by: Priyansh Shah <108476017+priyanshshah2442@users.noreply.github.com>
2025-03-05 12:19:31 +05:30
Mihir Kandoi
d8a1d0e908 test: added test
(cherry picked from commit 6073f5a6f9)
2025-03-05 06:41:09 +00:00
Mihir Kandoi
7437cea458 fix: revamp logic (split parent and child)
(cherry picked from commit f7594e2ff9)
2025-03-05 06:41:08 +00:00
Mihir Kandoi
1790bcc6d1 fix: syntax error
(cherry picked from commit 2f1e253e19)
2025-03-05 06:41:08 +00:00
Mihir Kandoi
d0b8e0da8d perf: replace if function in query
(cherry picked from commit 5e66231ca4)
2025-03-05 06:41:08 +00:00
rohitwaghchaure
ab4b1d4356 Merge pull request #46019 from frappe/st30783
fix: source warehouse not fetched in bom creator
2025-03-05 12:06:31 +05:30
mergify[bot]
7759775ee6 fix: Add permission check in POS's Toggle Recent Orders (backport #46010) (#46274)
fix: use get_list to check permissions

(cherry picked from commit a08bc6b913)

Co-authored-by: Sanket322 <shahsanket322003.com>
2025-03-05 12:04:17 +05:30
mergify[bot]
1b00de1815 chore: erpnext.com -> frappe.io/erpnext (backport #46288) (#46290)
* chore: erpnext.com -> frappe.io/erpnext (#46288)

(cherry picked from commit 41fe30ea6e)

# Conflicts:
#	README.md

* Update README.md

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-03-05 11:35:52 +05:30
mergify[bot]
41ab7f3f7c perf: don't track seen for POS Invoice (backport #46187) (#46189)
* perf: don't track seen for POS Invoice (#46187)

This is a moving doctype. Do people even browse the list view?

It doesn't make much sense, either. POS INvoices are rarely "reviewed" by multiple users.

(cherry picked from commit ded0aab680)

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

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-03-05 11:28:22 +05:30
ruthra kumar
5049f80b7f Merge pull request #46278 from mhh008/version-15-hotfix
fix-de-transaltion
2025-03-05 11:01:42 +05:30
rohitwaghchaure
d650b23722 Merge pull request #46282 from frappe/mergify/bp/version-15-hotfix/pr-46279
fix: rate changing on the deliver note (backport #46279)
2025-03-04 23:24:30 +05:30
Rohit Waghchaure
75bc68b863 fix: rate changing on the deliver note
(cherry picked from commit 6f40849d55)
2025-03-04 16:12:37 +00:00
rohitwaghchaure
f37fe1781a Merge pull request #46280 from frappe/mergify/bp/version-15-hotfix/pr-46208
fix: stock reservation issue while making Purchase Invoice (backport #46208)
2025-03-04 21:41:53 +05:30
Rohit Waghchaure
8bd71954f3 fix: stock reservation issue while making Purchase Invoice
(cherry picked from commit 64985bffe0)
2025-03-04 14:41:41 +00:00
Lakshit Jain
0f86ed28bc Merge pull request #46276 from frappe/mergify/bp/version-15-hotfix/pr-46039
fix: Ensure new line is added regardless of postal code presence (backport #46039)
2025-03-04 19:14:17 +05:30
mhh008
ddcf79da1d fix: translation DE 2025-03-04 13:56:19 +01:00
Lakshit Jain
b5fcd682a6 Merge pull request #46268 from frappe/mergify/bp/version-15-hotfix/pr-45896
fix: auto allocation for negative amount outstanding for Customers in Payment Entry (backport #45896)
2025-03-04 18:17:15 +05:30
Lakshit Jain
29405498bd Merge pull request #46270 from frappe/mergify/bp/version-15-hotfix/pr-46260
fix: do not include opening invoices in billed items to be received report (backport #46260)
2025-03-04 18:16:42 +05:30
Sanket322
1630979f05 refactor: add new line ragardless of postal code
(cherry picked from commit 746adfd057)
2025-03-04 12:36:40 +00:00
Smit Vora
6ab7d98681 Merge pull request #46139 from frappe/mergify/bp/version-15-hotfix/pr-46117
fix(report): Allow `Closed` Purchase Orders to be Visible in Purchase Order Analysis Report (backport #46117)
2025-03-04 18:03:47 +05:30
ljain112
eee500f20e fix: do not include opening invoices in billed items to be received report
(cherry picked from commit c1ddf444c6)
2025-03-04 11:24:24 +00:00
ljain112
78a329e573 fix: auto allocation for negative amount outstanding for Customers in Payment Entry
(cherry picked from commit 6275b44a0b)
2025-03-04 11:20:12 +00:00
Smit Vora
83dcbec86a chore: resolve conflicts 2025-03-04 16:43:55 +05:30
Smit Vora
5b6ed1d077 Merge pull request #46253 from frappe/mergify/bp/version-15-hotfix/pr-46062
fix: Convert tuple of tuples to list of dicts for dot notation access (backport #46062)
2025-03-04 16:39:16 +05:30
Smit Vora
284c93ecc2 Merge pull request #46246 from frappe/mergify/bp/version-15-hotfix/pr-46192
fix: Exclude Cancelled GL Entries (backport #46192)
2025-03-04 16:38:38 +05:30
Smit Vora
33051ec17e Merge pull request #46245 from frappe/mergify/bp/version-15-hotfix/pr-45972
fix: Set Taxes Before Calculating Taxes and Totals (backport #45972)
2025-03-04 16:38:24 +05:30
ruthra kumar
9e54e2ea58 Merge pull request #46266 from frappe/mergify/bp/version-15-hotfix/pr-45751
fix: change voucher_type and voucher_name field type to data (backport #45751)
2025-03-04 16:12:40 +05:30
ruthra kumar
c09c4cc243 Merge pull request #46252 from frappe/mergify/bp/version-15-hotfix/pr-46070
fix: Batch Price gets updated only if it is a billed item (backport #46070)
2025-03-04 15:49:25 +05:30
ruthra kumar
489efda985 chore: resolve conflict 2025-03-04 15:47:03 +05:30
Sugesh393
3a03865a8f fix: change voucher_type and voucher_no field type to data
(cherry picked from commit f8ab021920)

# Conflicts:
#	erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
2025-03-04 10:10:54 +00:00
ruthra kumar
854632dd51 chore: resolve conflict 2025-03-04 15:03:03 +05:30
ruthra kumar
ef44ad79a0 Merge pull request #46261 from frappe/mergify/bp/version-15-hotfix/pr-46172
fix: Show Credit Note amount in credit note column (backport #46172)
2025-03-04 15:00:15 +05:30
Sanket322
a8b31df65d fix: test case for debit note
(cherry picked from commit 6719bbeb10)
2025-03-04 07:58:31 +00:00
Sanket322
9f4311e7fb fix: fixing test case
(cherry picked from commit 9b2b477ae0)
2025-03-04 07:58:31 +00:00
Sanket322
0a65217423 fix: if invoice is return then add amount in proper column
(cherry picked from commit ccb4bdbe4c)
2025-03-04 07:58:30 +00:00
Sanket Shah
ef195513d0 fix: Convert tuple of tuples to list of dicts for dot notation access (#46062)
fix: use as_dict to convert tuples into list of dict

Co-authored-by: Sanket322 <shahsanket322003.com>
(cherry picked from commit e4b0ab6656)
2025-03-03 16:09:45 +00:00
mergify[bot]
f3cafef6a7 fix(patch): Ensure SLE indexes (backport #46131) (#46135)
* fix(patch): Ensure SLE indexes (#46131)

Because of the way this change was pushed in parts, some sites don't see
this as "update" and don't have the new indexes.

(cherry picked from commit f62aa8fc57)

# Conflicts:
#	erpnext/patches.txt

* fix: resolved conflict

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-03-03 21:37:02 +05:30
ruthra kumar
6762dc3392 chore: linter fix
(cherry picked from commit 0a2193e458)

# Conflicts:
#	erpnext/stock/get_item_details.py
2025-03-03 14:27:25 +00:00
Nirmalrajaa K
dbd47dff98 fix: Batch Price gets updated only if it is a billed item
(cherry picked from commit 1a56b83054)

# Conflicts:
#	erpnext/stock/get_item_details.py
2025-03-03 14:27:25 +00:00
Nirmalrajaa K
8ed512f6c6 fix: Batch Price gets updated only if it is a billed item
(cherry picked from commit 9597b1a69e)

# Conflicts:
#	erpnext/stock/get_item_details.py
2025-03-03 14:27:25 +00:00
Marica
3a6ef2564e Merge pull request #46073 from 0xD0M1M0/fix-discount-accounting-v15
fix: discount accounting for v15
2025-03-03 19:46:51 +05:30
Smit Vora
35df539da3 chore: fix linters 2025-03-03 17:02:15 +05:30
Ninad1306
5e083861a4 fix: exclude cancelled gl entries
(cherry picked from commit 3251a331dd)
2025-03-03 11:28:57 +00:00
Ninad1306
41e9a10ab4 test: validate fetching of taxes based on taxes and charges template
(cherry picked from commit 196ef7ac4e)
2025-03-03 11:27:45 +00:00
Ninad1306
90dea426d8 fix: set taxes before calculating taxes and totals
(cherry picked from commit 0fd0695bbb)
2025-03-03 11:27:45 +00:00
Mihir Kandoi
bd48d391e4 fix: syntax error 2025-03-03 16:40:19 +05:30
rohitwaghchaure
9082ff6aa9 Merge pull request #46242 from frappe/mergify/bp/version-15-hotfix/pr-46239
fix: incorrectly billed amount in the purchase receipt (backport #46239)
2025-03-03 16:16:09 +05:30
rohitwaghchaure
ba0ce267c2 Merge pull request #46243 from frappe/mergify/bp/version-15-hotfix/pr-46231
fix: incorrect batch picked (backport #46231)
2025-03-03 16:15:43 +05:30
Mihir Kandoi
cc535b7636 fix: syntax error 2025-03-03 15:27:35 +05:30
Mihir Kandoi
81c7b8c273 chore: resolve conflicts 2025-03-03 15:05:44 +05:30
Rohit Waghchaure
e94f0b1cca fix: incorrect batch picked
(cherry picked from commit d2564cad68)
2025-03-03 09:24:24 +00:00
Rohit Waghchaure
c247cf888b fix: incorrectly billed amount in the purchase receipt
(cherry picked from commit a5271fdb2e)
2025-03-03 09:15:48 +00:00
Khushi Rawat
7409c140d4 Merge pull request #46237 from frappe/mergify/bp/version-15-hotfix/pr-46201
fix(asset depreciation schedules): enable auto commit (backport #46201)
2025-03-03 14:04:39 +05:30
Mihir Kandoi
7f4d553201 chore: resolve conflicts 2025-03-03 12:28:06 +05:30
venkat102
899e468f6a fix(asset depreciation schedules): enable auto commit
(cherry picked from commit a4b24f7451)
2025-03-03 06:42:02 +00:00
mergify[bot]
faee8d6c5e fix: don't allow renaming account while system is actively in use (backport #46176) (#46210)
fix: don't allow renaming account while system is actively in use (#46176)

(cherry picked from commit 999f1cf96d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-03-03 11:16:37 +05:30
Frappe PR Bot
171f966421 chore(release): Bumped to Version 15.53.4
## [15.53.4](https://github.com/frappe/erpnext/compare/v15.53.3...v15.53.4) (2025-03-03)

### Bug Fixes

* removed mandatory property for the cost center field ([8eddc09](8eddc09bba))
2025-03-03 05:12:02 +00:00
rohitwaghchaure
3f76a413f8 Merge pull request #46205 from frappe/mergify/bp/version-15/pr-46186
fix: removed mandatory property for the cost center field (backport #46186)
2025-03-03 10:40:35 +05:30
Mihir Kandoi
61d5680c8d fix: error
(cherry picked from commit 7c9c0c7776)

# Conflicts:
#	erpnext/stock/get_item_details.py
2025-03-03 04:46:28 +00:00
Mihir Kandoi
9e649d8522 fix: dont update rate of free item when batch is updated
(cherry picked from commit a3596f717b)

# Conflicts:
#	erpnext/stock/get_item_details.py
2025-03-03 04:46:28 +00:00
rohitwaghchaure
c0f736736e Merge pull request #46217 from frappe/mergify/bp/version-15-hotfix/pr-46202
fix: rearrange stock settings fields (backport #46202)
2025-03-03 10:15:36 +05:30
Frappe PR Bot
bcd02df6fd chore(release): Bumped to Version 15.53.3
## [15.53.3](https://github.com/frappe/erpnext/compare/v15.53.2...v15.53.3) (2025-03-03)

### Bug Fixes

* stock qty not recalculate on changing of the qty ([9186f13](9186f13458))
2025-03-03 04:02:37 +00:00
rohitwaghchaure
7a71c24d5c Merge pull request #46232 from frappe/mergify/bp/version-15/pr-46224
fix: stock qty not recalculate on changing of the qty (backport #46219) (backport #46224)
2025-03-03 09:31:16 +05:30
Rohit Waghchaure
9186f13458 fix: stock qty not recalculate on changing of the qty
(cherry picked from commit 464e3339fe)
(cherry picked from commit 331798babc)
2025-03-03 03:57:22 +00:00
rohitwaghchaure
c48e157a25 Merge pull request #46224 from frappe/mergify/bp/version-15-hotfix/pr-46219
fix: stock qty not recalculate on changing of the qty (backport #46219)
2025-03-03 08:24:05 +05:30
mergify[bot]
84ca0ada1b refactor: using function to unset grand total to default mode of payment in pos (backport #46228) (#46229)
refactor: using function to unset grand total to default mode of payment in pos (#46228)

(cherry picked from commit 62c3915ecb)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-03-03 01:59:13 +05:30
Rohit Waghchaure
331798babc fix: stock qty not recalculate on changing of the qty
(cherry picked from commit 464e3339fe)
2025-03-02 06:46:56 +00:00
rohitwaghchaure
93fed0ce86 chore: fix conflicts 2025-03-01 12:53:44 +05:30
Rohit Waghchaure
87703c6511 fix: rearrange stock settings fields
(cherry picked from commit 93f461c6f3)

# Conflicts:
#	erpnext/stock/doctype/stock_settings/stock_settings.json
2025-03-01 06:57:35 +00:00
mergify[bot]
de0dfbca9a fix: pos item detail serial no field (backport #46211) (#46212)
fix: pos item detail serial no field (#46211)

(cherry picked from commit d2fad44e89)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-28 23:24:49 +05:30
Rohit Waghchaure
8eddc09bba fix: removed mandatory property for the cost center field
(cherry picked from commit 079cf772aa)
2025-02-28 10:25:50 +00:00
mergify[bot]
0f263bcff2 fix: pos item selection using serial no (backport #46200) (#46203)
fix: pos item selection using serial no (#46200)

(cherry picked from commit 8fb09decd2)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-28 15:43:59 +05:30
ruthra kumar
a9b2f4885e Merge pull request #46197 from frappe/mergify/bp/version-15-hotfix/pr-46186
fix: removed mandatory property for the cost center field (backport #46186)
2025-02-28 13:56:09 +05:30
Rohit Waghchaure
a94292a69f fix: removed mandatory property for the cost center field
(cherry picked from commit 079cf772aa)
2025-02-28 08:03:51 +00:00
Sagar Vora
5b072c88a6 Merge pull request #46191 from frappe/mergify/bp/version-15-hotfix/pr-46177
fix: Payment Entry fixes (backport #46177)
2025-02-28 10:44:00 +05:30
vishakhdesai
3fb9033fb7 fix: payment entry exchange gain loss issue
(cherry picked from commit 2dbef23244)
2025-02-28 05:13:43 +00:00
Frappe PR Bot
d9c1b58fc3 chore(release): Bumped to Version 15.53.2
## [15.53.2](https://github.com/frappe/erpnext/compare/v15.53.1...v15.53.2) (2025-02-26)

### Bug Fixes

* check value as int ([97d3e86](97d3e8648b))
* enable fetch_timesheet_in_sales_invoice in test ([feb64cb](feb64cb9b5))
* incorrect stock value difference for adjustment entry ([96d44e3](96d44e362d))
* inventory dimension for maintence visit ([ec3b281](ec3b281a3b))
* no permission to get project settings in sales invoice ([b8281c3](b8281c34e2))
* pos opening entry dialog not saving on change data (backport [#46066](https://github.com/frappe/erpnext/issues/46066)) ([#46067](https://github.com/frappe/erpnext/issues/46067)) ([0ae2d61](0ae2d61974))
* **project settings:** add checkbox to auto fetch timesheet in sales invoice ([b3c1df8](b3c1df8561))
* **sales invoice:** check fetch_timesheet_in_sales_invoice enabled before fetching the timesheet ([b1095bb](b1095bb91b))
* valuation rate for batch ([c72dab4](c72dab49f4))
2025-02-26 12:58:53 +00:00
ruthra kumar
f93d1a2633 Merge pull request #46132 from frappe/version-15-hotfix
chore: release v15
2025-02-26 18:27:33 +05:30
ruthra kumar
bc53365620 Merge pull request #46159 from frappe/mergify/bp/version-15-hotfix/pr-46156
fix: no permission to get project settings in sales invoice (backport #46156)
2025-02-26 14:34:36 +05:30
venkat102
b8281c34e2 fix: no permission to get project settings in sales invoice
(cherry picked from commit 221f1468cb)
2025-02-26 08:22:24 +00:00
rohitwaghchaure
81ff16248e Merge pull request #46143 from frappe/mergify/bp/version-15-hotfix/pr-46097
fix: valuation rate for batch (backport #46097)
2025-02-26 10:16:10 +05:30
ruthra kumar
8bb085a055 Merge pull request #46142 from frappe/mergify/bp/version-15-hotfix/pr-45908
fix(projects settings): add checkbox to auto fetch timesheet in sales invoice (backport #45908)
2025-02-26 06:23:58 +05:30
Rohit Waghchaure
c72dab49f4 fix: valuation rate for batch
(cherry picked from commit b88305a95f)
2025-02-25 17:45:05 +00:00
ruthra kumar
feec16b682 chore: resolve conflicts 2025-02-25 22:53:14 +05:30
venkat102
feb64cb9b5 fix: enable fetch_timesheet_in_sales_invoice in test
(cherry picked from commit 5880f1d5c6)

# Conflicts:
#	erpnext/projects/doctype/timesheet/test_timesheet.py
2025-02-25 16:56:57 +00:00
venkat102
97d3e8648b fix: check value as int
(cherry picked from commit 43b13b91be)
2025-02-25 16:56:56 +00:00
venkat102
b1095bb91b fix(sales invoice): check fetch_timesheet_in_sales_invoice enabled before fetching the timesheet
(cherry picked from commit 914ad357fd)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
2025-02-25 16:56:56 +00:00
venkat102
b3c1df8561 fix(project settings): add checkbox to auto fetch timesheet in sales invoice
(cherry picked from commit 876082ea2f)

# Conflicts:
#	erpnext/projects/doctype/projects_settings/projects_settings.json
2025-02-25 16:56:56 +00:00
Ninad1306
2221bf1cba fix(report): filter sales / purchase orders based on date filters
(cherry picked from commit 936d7d4342)
2025-02-25 13:07:54 +00:00
Ninad1306
8799af9747 fix(report): allow Closed sales orders to be visible
(cherry picked from commit 2394e76e7d)

# Conflicts:
#	erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
2025-02-25 13:07:54 +00:00
Ninad1306
20c4487853 fix(report): allow Closed purchase orders to be visible
(cherry picked from commit 3b2879d3a1)

# Conflicts:
#	erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
2025-02-25 13:07:53 +00:00
0xD0M1M0
f609012f02 fix: discount accounting for v15 2025-02-21 22:26:39 +01:00
Ravibharathi
171df3aba5 feat: add total weight in shipment (#46049)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit 1ec182430d)

# Conflicts:
#	erpnext/stock/doctype/shipment/shipment.json
2025-02-21 14:16:46 +00:00
mergify[bot]
0ae2d61974 fix: pos opening entry dialog not saving on change data (backport #46066) (#46067)
fix: pos opening entry dialog not saving on change data (#46066)

(cherry picked from commit 8e6959dfad)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-21 17:53:59 +05:30
Frappe PR Bot
9e824fc4fe chore(release): Bumped to Version 15.53.1
## [15.53.1](https://github.com/frappe/erpnext/compare/v15.53.0...v15.53.1) (2025-02-21)

### Bug Fixes

* inventory dimension for maintence visit ([1d818e1](1d818e1510))
2025-02-21 10:01:58 +00:00
rohitwaghchaure
b6b47d6683 Merge pull request #46044 from frappe/mergify/bp/version-15/pr-46041
fix: inventory dimension for maintenance visit (backport #46037) (backport #46041)
2025-02-21 15:30:36 +05:30
Rohit Waghchaure
1d818e1510 fix: inventory dimension for maintence visit
(cherry picked from commit cd4ba69262)
(cherry picked from commit ec3b281a3b)
2025-02-20 07:37:38 +00:00
rohitwaghchaure
b00fa1dfee Merge pull request #46041 from frappe/mergify/bp/version-15-hotfix/pr-46037
fix: inventory dimension for maintenance visit (backport #46037)
2025-02-20 13:06:33 +05:30
Rohit Waghchaure
ec3b281a3b fix: inventory dimension for maintence visit
(cherry picked from commit cd4ba69262)
2025-02-20 06:13:19 +00:00
rohitwaghchaure
9318e4f0e8 Merge pull request #46028 from frappe/mergify/bp/version-15-hotfix/pr-46021
fix: incorrect stock value difference for adjustment entry (backport #46021)
2025-02-19 17:24:42 +05:30
Rohit Waghchaure
96d44e362d fix: incorrect stock value difference for adjustment entry
(cherry picked from commit df83e427a3)
2025-02-19 11:11:15 +00:00
Mihir Kandoi
6157fed71c fix: source warehouse not fetched in bom creator 2025-02-19 15:54:31 +05:30
202 changed files with 8624 additions and 1129 deletions

4
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
changelog:
exclude:
labels:
- skip-release-notes

View File

@@ -0,0 +1,30 @@
name: "Auto-label PRs based on title"
on:
pull_request_target:
types: [opened, reopened]
jobs:
add-label-if-prefix-matches:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Check PR title and add label if it matches prefixes
uses: actions/github-script@v7
continue-on-error: true
with:
script: |
const title = context.payload.pull_request.title.toLowerCase();
const prefixes = ['chore', 'ci', 'style', 'test', 'refactor'];
// Check if the PR title starts with any of the prefixes
if (prefixes.some(prefix => title.startsWith(prefix))) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['skip-release-notes']
});
}

View File

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

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _, throw
from frappe.utils import cint, cstr
from frappe.utils import add_to_date, cint, cstr, pretty_date
from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
import erpnext
@@ -481,6 +481,7 @@ def get_account_autoname(account_number, account_name, company):
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False):
_ensure_idle_system()
account = frappe.get_cached_doc("Account", name)
if not account:
return
@@ -501,7 +502,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
"name",
)
if old_name:
if old_name and not from_descendant:
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")
@@ -542,6 +543,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
@frappe.whitelist()
def merge_account(old, new):
_ensure_idle_system()
# Validate properties before merging
new_account = frappe.get_cached_doc("Account", new)
old_account = frappe.get_cached_doc("Account", old)
@@ -595,3 +597,31 @@ def sync_update_account_number_in_child(
for d in frappe.db.get_values("Account", filters=filters, fieldname=["company", "name"], as_dict=True):
update_account_number(d["name"], account_name, account_number, from_descendant=True)
def _ensure_idle_system():
# Don't allow renaming if accounting entries are actively being updated, there are two main reasons:
# 1. Correctness: It's next to impossible to ensure that renamed account is not being used *right now*.
# 2. Performance: Renaming requires locking out many tables entirely and severely degrades performance.
if frappe.flags.in_test:
return
last_gl_update = None
try:
# We also lock inserts to GL entry table with for_update here.
last_gl_update = frappe.db.get_value("GL Entry", {}, "modified", for_update=True, wait=False)
except frappe.QueryTimeoutError:
# wait=False fails immediately if there's an active transaction.
last_gl_update = add_to_date(None, seconds=-1)
if not last_gl_update:
return
if last_gl_update > add_to_date(None, minutes=-5):
frappe.throw(
_(
"Last GL Entry update was done {}. This operation is not allowed while system is actively being used. Please wait for 5 minutes before retrying."
).format(pretty_date(last_gl_update)),
title=_("System In Use"),
)

View File

@@ -116,6 +116,7 @@ def identify_is_group(child):
return is_group
@frappe.whitelist()
def get_chart(chart_template, existing_company=None):
chart = {}
if existing_company:

View File

@@ -98,7 +98,7 @@
"Office Maintenance Expenses": {},
"Office Rent": {},
"Postal Expenses": {},
"Print and Stationary": {},
"Print and Stationery": {},
"Rounded Off": {
"account_type": "Round Off"
},

View File

@@ -159,9 +159,6 @@ def get_payment_entries_for_bank_clearance(
as_dict=1,
)
if bank_account:
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql(
f"""
select
@@ -183,7 +180,6 @@ def get_payment_entries_for_bank_clearance(
"account": account,
"from": from_date,
"to": to_date,
"bank_account": bank_account,
},
as_dict=1,
)

View File

@@ -374,9 +374,36 @@ def auto_reconcile_vouchers(
to_reference_date=None,
):
frappe.flags.auto_reconcile_vouchers = True
reconciled, partially_reconciled = set(), set()
bank_transactions = get_bank_transactions(bank_account)
if len(bank_transactions) > 10:
frappe.enqueue(
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
queue="long",
bank_transactions=bank_transactions,
from_date=from_date,
to_date=to_date,
filter_by_reference_date=filter_by_reference_date,
from_reference_date=from_reference_date,
to_reference_date=to_reference_date,
)
frappe.msgprint(_("Auto Reconciliation has started in the background"))
else:
start_auto_reconcile(
bank_transactions,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
def start_auto_reconcile(
bank_transactions, from_date, to_date, filter_by_reference_date, from_reference_date, to_reference_date
):
reconciled, partially_reconciled = set(), set()
for transaction in bank_transactions:
linked_payments = get_linked_payments(
transaction.name,
@@ -414,7 +441,6 @@ def auto_reconcile_vouchers(
frappe.msgprint(title=_("Auto Reconciliation"), msg=alert_message, indicator=indicator)
frappe.flags.auto_reconcile_vouchers = False
return reconciled, partially_reconciled
def get_auto_reconcile_message(partially_reconciled, reconciled):

View File

@@ -128,7 +128,7 @@ class TestCouponCode(unittest.TestCase):
item_code="_Test Tesla Car",
rate=5000,
qty=1,
do_not_submit=True,
do_not_save=True,
)
self.assertEqual(so.items[0].rate, 5000)

View File

@@ -279,7 +279,8 @@
{
"fieldname": "transaction_exchange_rate",
"fieldtype": "Float",
"label": "Transaction Exchange Rate"
"label": "Transaction Exchange Rate",
"precision": "9"
},
{
"fieldname": "debit_in_transaction_currency",
@@ -357,7 +358,7 @@
"idx": 1,
"in_create": 1,
"links": [],
"modified": "2024-08-22 13:03:39.997475",
"modified": "2025-02-21 14:36:49.431166",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",

View File

@@ -141,6 +141,7 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
self.validate_company_in_accounting_dimension()
self.validate_advance_accounts()
if self.docstatus == 0:
@@ -575,8 +576,22 @@ class JournalEntry(AccountsController):
if customers:
from erpnext.selling.doctype.customer.customer import check_credit_limit
customer_details = frappe._dict(
frappe.db.get_all(
"Customer Credit Limit",
filters={
"parent": ["in", customers],
"parenttype": ["=", "Customer"],
"company": ["=", self.company],
},
fields=["parent", "bypass_credit_limit_check"],
as_list=True,
)
)
for customer in customers:
check_credit_limit(customer, self.company)
ignore_outstanding_sales_order = bool(customer_details.get(customer))
check_credit_limit(customer, self.company, ignore_outstanding_sales_order)
def validate_cheque_info(self):
if self.voucher_type in ["Bank Entry"]:
@@ -1059,14 +1074,15 @@ class JournalEntry(AccountsController):
gl_map = []
company_currency = erpnext.get_company_currency(self.company)
self.transaction_currency = company_currency
self.transaction_exchange_rate = 1
if self.multi_currency:
for row in self.get("accounts"):
if row.account_currency != company_currency:
self.currency = row.account_currency
self.conversion_rate = row.exchange_rate
# Journal assumes the first foreign currency as transaction currency
self.transaction_currency = row.account_currency
self.transaction_exchange_rate = row.exchange_rate
break
else:
self.currency = company_currency
for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
@@ -1091,6 +1107,18 @@ class JournalEntry(AccountsController):
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"transaction_currency": self.transaction_currency,
"transaction_exchange_rate": self.transaction_exchange_rate,
"debit_in_transaction_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
"credit_in_transaction_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,

View File

@@ -575,7 +575,7 @@ class TestJournalEntry(unittest.TestCase):
order_by="account",
)
expected = [
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 1.0},
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 85.0},
{"account": "_Test Receivable USD - _TC", "transaction_exchange_rate": 85.0},
]
self.assertEqual(expected, actual)
@@ -591,13 +591,14 @@ def make_journal_entry(
save=True,
submit=False,
project=None,
company=None,
):
if not cost_center:
cost_center = "_Test Cost Center - _TC"
jv = frappe.new_doc("Journal Entry")
jv.posting_date = posting_date or nowdate()
jv.company = "_Test Company"
jv.company = company or "_Test Company"
jv.user_remark = "test"
jv.multi_currency = 1
jv.set(

View File

@@ -258,6 +258,10 @@ frappe.ui.form.on("Payment Entry", {
frappe.flags.allocate_payment_amount = true;
},
validate: async function (frm) {
await frm.events.set_exchange_gain_loss_deduction(frm);
},
validate_company: (frm) => {
if (!frm.doc.company) {
frappe.throw({ message: __("Please select a Company first."), title: __("Mandatory") });
@@ -1893,8 +1897,6 @@ function prompt_for_missing_account(frm, account) {
(values) => resolve(values?.[account]),
__("Please Specify Account")
);
dialog.on_hide = () => resolve("");
});
}

View File

@@ -7,6 +7,7 @@ from functools import reduce
import frappe
from frappe import ValidationError, _, qb, scrub, throw
from frappe.model.meta import get_field_precision
from frappe.query_builder import Tuple
from frappe.query_builder.functions import Count
from frappe.utils import cint, comma_or, flt, getdate, nowdate
@@ -37,7 +38,7 @@ from erpnext.accounts.general_ledger import (
make_reverse_gl_entries,
process_gl_map,
)
from erpnext.accounts.party import get_party_account
from erpnext.accounts.party import complete_contact_details, get_party_account, set_contact_details
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
@@ -439,6 +440,12 @@ class PaymentEntry(AccountsController):
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
if self.party:
if not self.contact_person:
set_contact_details(
self, party=frappe._dict({"name": self.party}), party_type=self.party_type
)
else:
complete_contact_details(self)
if not self.party_balance:
self.party_balance = get_balance_on(
party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
@@ -736,16 +743,39 @@ class PaymentEntry(AccountsController):
outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
conversion_rate = frappe.db.get_value(key[2], {"name": key[1]}, "conversion_rate")
base_paid_amount_precision = get_field_precision(
frappe.get_meta("Payment Schedule").get_field("base_paid_amount")
)
base_outstanding_precision = get_field_precision(
frappe.get_meta("Payment Schedule").get_field("base_outstanding")
)
base_paid_amount = flt(
(allocated_amount - discounted_amt) * conversion_rate, base_paid_amount_precision
)
base_outstanding = flt(allocated_amount * conversion_rate, base_outstanding_precision)
if cancel:
frappe.db.sql(
"""
UPDATE `tabPayment Schedule`
SET
paid_amount = `paid_amount` - %s,
base_paid_amount = `base_paid_amount` - %s,
discounted_amount = `discounted_amount` - %s,
outstanding = `outstanding` + %s
outstanding = `outstanding` + %s,
base_outstanding = `base_outstanding` - %s
WHERE parent = %s and payment_term = %s""",
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
(
allocated_amount - discounted_amt,
base_paid_amount,
discounted_amt,
allocated_amount,
base_outstanding,
key[1],
key[0],
),
)
else:
if allocated_amount > outstanding:
@@ -761,10 +791,20 @@ class PaymentEntry(AccountsController):
UPDATE `tabPayment Schedule`
SET
paid_amount = `paid_amount` + %s,
base_paid_amount = `base_paid_amount` + %s,
discounted_amount = `discounted_amount` + %s,
outstanding = `outstanding` - %s
outstanding = `outstanding` - %s,
base_outstanding = `base_outstanding` - %s
WHERE parent = %s and payment_term = %s""",
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
(
allocated_amount - discounted_amt,
base_paid_amount,
discounted_amt,
allocated_amount,
base_outstanding,
key[1],
key[0],
),
)
def get_allocated_amount_in_transaction_currency(
@@ -1217,15 +1257,22 @@ class PaymentEntry(AccountsController):
self.set("remarks", "\n".join(remarks))
def set_transaction_currency_and_rate(self):
company_currency = erpnext.get_company_currency(self.company)
self.transaction_currency = company_currency
self.transaction_exchange_rate = 1
if self.paid_from_account_currency != company_currency:
self.transaction_currency = self.paid_from_account_currency
self.transaction_exchange_rate = self.source_exchange_rate
elif self.paid_to_account_currency != company_currency:
self.transaction_currency = self.paid_to_account_currency
self.transaction_exchange_rate = self.target_exchange_rate
def build_gl_map(self):
if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"):
self.setup_party_account_field()
company_currency = erpnext.get_company_currency(self.company)
if self.paid_from_account_currency != company_currency:
self.currency = self.paid_from_account_currency
elif self.paid_to_account_currency != company_currency:
self.currency = self.paid_to_account_currency
self.set_transaction_currency_and_rate()
gl_entries = []
self.add_party_gl_entries(gl_entries)
@@ -1304,6 +1351,9 @@ class PaymentEntry(AccountsController):
"cost_center": cost_center,
dr_or_cr + "_in_account_currency": d.allocated_amount,
dr_or_cr: allocated_amount_in_company_currency,
dr_or_cr + "_in_transaction_currency": d.allocated_amount
if self.transaction_currency == self.party_account_currency
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
},
item=self,
)
@@ -1348,6 +1398,9 @@ class PaymentEntry(AccountsController):
"account_currency": self.party_account_currency,
"cost_center": self.cost_center,
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr + "_in_transaction_currency": self.unallocated_amount
if self.party_account_currency == self.transaction_currency
else base_unallocated_amount / self.transaction_exchange_rate,
dr_or_cr: base_unallocated_amount,
},
item=self,
@@ -1365,6 +1418,7 @@ class PaymentEntry(AccountsController):
def make_advance_gl_entries(
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
):
self.set_transaction_currency_and_rate()
gl_entries = []
self.add_advance_gl_entries(gl_entries, entry)
@@ -1444,9 +1498,16 @@ class PaymentEntry(AccountsController):
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
base_allocated_amount = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict["account"] = account
args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict[dr_or_cr] = base_allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_transaction_currency"] = (
invoice.allocated_amount
if self.party_account_currency == self.transaction_currency
else base_allocated_amount / self.transaction_exchange_rate
)
args_dict.update(
{
"against_voucher_type": invoice.reference_doctype,
@@ -1464,8 +1525,13 @@ class PaymentEntry(AccountsController):
args_dict[dr_or_cr + "_in_account_currency"] = 0
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
args_dict["account"] = self.party_account
args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict[dr_or_cr] = base_allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_transaction_currency"] = (
invoice.allocated_amount
if self.party_account_currency == self.transaction_currency
else base_allocated_amount / self.transaction_exchange_rate
)
args_dict.update(
{
"against_voucher_type": "Payment Entry",
@@ -1487,6 +1553,9 @@ class PaymentEntry(AccountsController):
"account_currency": self.paid_from_account_currency,
"against": self.party if self.payment_type == "Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount,
"credit_in_transaction_currency": self.paid_amount
if self.paid_from_account_currency == self.transaction_currency
else self.base_paid_amount / self.transaction_exchange_rate,
"credit": self.base_paid_amount,
"cost_center": self.cost_center,
"post_net_value": True,
@@ -1502,6 +1571,9 @@ class PaymentEntry(AccountsController):
"account_currency": self.paid_to_account_currency,
"against": self.party if self.payment_type == "Receive" else self.paid_from,
"debit_in_account_currency": self.received_amount,
"debit_in_transaction_currency": self.received_amount
if self.paid_to_account_currency == self.transaction_currency
else self.base_received_amount / self.transaction_exchange_rate,
"debit": self.base_received_amount,
"cost_center": self.cost_center,
},
@@ -1537,6 +1609,8 @@ class PaymentEntry(AccountsController):
dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency
else d.tax_amount,
dr_or_cr + "_in_transaction_currency": base_tax_amount
/ self.transaction_exchange_rate,
"cost_center": d.cost_center,
"post_net_value": True,
},
@@ -1562,6 +1636,8 @@ class PaymentEntry(AccountsController):
rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency
else d.tax_amount,
rev_dr_or_cr + "_in_transaction_currency": base_tax_amount
/ self.transaction_exchange_rate,
"cost_center": self.cost_center,
"post_net_value": True,
},
@@ -1584,6 +1660,7 @@ class PaymentEntry(AccountsController):
"account_currency": account_currency,
"against": self.party or self.paid_from,
"debit_in_account_currency": d.amount,
"debit_in_transaction_currency": d.amount / self.transaction_exchange_rate,
"debit": d.amount,
"cost_center": d.cost_center,
},
@@ -1843,7 +1920,7 @@ class PaymentEntry(AccountsController):
allocated_positive_outstanding = paid_amount + allocated_negative_outstanding
elif self.party_type in ("Supplier", "Employee"):
elif self.party_type in ("Supplier", "Customer"):
if paid_amount > total_negative_outstanding:
if total_negative_outstanding == 0:
frappe.msgprint(
@@ -2842,7 +2919,7 @@ def get_payment_entry(
pe.party_type = party_type
pe.party = doc.get(scrub(party_type))
pe.contact_person = doc.get("contact_person")
pe.contact_email = doc.get("contact_email")
complete_contact_details(pe)
pe.ensure_supplier_is_not_blocked()
pe.paid_from = party_account if payment_type == "Receive" else bank.account
@@ -2854,7 +2931,9 @@ def get_payment_entry(
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
pe.bank_account = frappe.db.get_value("Bank Account", {"is_company_account": 1, "is_default": 1}, "name")
pe.bank_account = frappe.db.get_value(
"Bank Account", {"is_company_account": 1, "is_default": 1, "company": doc.company}, "name"
)
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
pe.project = doc.get("project") or reduce(
@@ -3337,13 +3416,14 @@ def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
"""Add loss on income discount in base currency."""
precision = doc.precision("total")
base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100)
positive_negative = -1 if pe.payment_type == "Pay" else 1
pe.append(
"deductions",
{
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(base_loss_on_income, precision),
"amount": flt(base_loss_on_income, precision) * positive_negative,
},
)
@@ -3355,6 +3435,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
tax_discount_loss = {}
base_total_tax_loss = 0
precision = doc.precision("tax_amount_after_discount_amount", "taxes")
positive_negative = -1 if pe.payment_type == "Pay" else 1
# The same account head could be used more than once
for tax in doc.get("taxes", []):
@@ -3377,7 +3458,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"account": account,
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(loss, precision),
"amount": flt(loss, precision) * positive_negative,
},
)

View File

@@ -282,6 +282,48 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
def test_payment_entry_against_payment_terms_with_discount_on_pi(self):
pi = make_purchase_invoice(do_not_save=1)
create_payment_terms_template_with_discount()
pi.payment_terms_template = "Test Discount Template"
frappe.db.set_value("Company", pi.company, "default_discount_account", "Write Off - _TC")
pi.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18,
},
)
pi.save()
pi.submit()
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
pe_with_tax_loss = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount")
self.assertEqual(pe_with_tax_loss.payment_type, "Pay")
self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 295.0)
self.assertEqual(pe_with_tax_loss.paid_amount, 265.5)
self.assertEqual(pe_with_tax_loss.difference_amount, 0)
self.assertEqual(pe_with_tax_loss.deductions[0].amount, -25.0) # Loss on Income
self.assertEqual(pe_with_tax_loss.deductions[1].amount, -4.5) # Loss on Tax
self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe.references[0].payment_term, "30 Credit Days with 10% Discount")
self.assertEqual(pe.payment_type, "Pay")
self.assertEqual(pe.references[0].allocated_amount, 295.0)
self.assertEqual(pe.paid_amount, 265.5)
self.assertEqual(pe.deductions[0].amount, -29.5)
self.assertEqual(pe.difference_amount, 0)
def test_payment_entry_against_payment_terms_with_discount(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template_with_discount()

View File

@@ -768,29 +768,39 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
def get_existing_paid_amount(doctype, name):
PL = frappe.qb.DocType("Payment Ledger Entry")
PLE = frappe.qb.DocType("Payment Ledger Entry")
PER = frappe.qb.DocType("Payment Entry Reference")
query = (
frappe.qb.from_(PL)
frappe.qb.from_(PLE)
.left_join(PER)
.on(
(PL.against_voucher_type == PER.reference_doctype)
& (PL.against_voucher_no == PER.reference_name)
& (PL.voucher_type == PER.parenttype)
& (PL.voucher_no == PER.parent)
(PLE.against_voucher_type == PER.reference_doctype)
& (PLE.against_voucher_no == PER.reference_name)
& (PLE.voucher_type == PER.parenttype)
& (PLE.voucher_no == PER.parent)
)
.select(
Abs(Sum(PLE.amount)).as_("total_amount"),
Abs(Sum(frappe.qb.terms.Case().when(PER.payment_request.isnotnull(), PLE.amount).else_(0))).as_(
"request_paid_amount"
),
)
.where(
(PLE.voucher_type.isin([doctype, "Journal Entry", "Payment Entry"]))
& (PLE.against_voucher_type == doctype)
& (PLE.against_voucher_no == name)
& (PLE.delinked == 0)
& (PLE.docstatus == 1)
& (PLE.amount < 0)
)
.select(Abs(Sum(PL.amount)).as_("total_paid_amount"))
.where(PL.against_voucher_type.eq(doctype))
.where(PL.against_voucher_no.eq(name))
.where(PL.amount < 0)
.where(PL.delinked == 0)
.where(PER.docstatus == 1)
.where(PER.payment_request.isnull())
)
response = query.run()
return response[0][0] if response[0] else 0
result = query.run()
ledger_amount = flt(result[0][0]) if result else 0
request_paid_amount = flt(result[0][1]) if result else 0
return ledger_amount - request_paid_amount
def get_gateway_details(args): # nosemgrep

View File

@@ -581,6 +581,34 @@ class TestPaymentRequest(FrappeTestCase):
pi.load_from_db()
self.assertEqual(pr_2.grand_total, pi.outstanding_amount)
def test_consider_journal_entry_and_return_invoice(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
si = create_sales_invoice(currency="INR", qty=5, rate=500)
je = make_journal_entry("_Test Cash - _TC", "Debtors - _TC", 500, save=False)
je.accounts[1].party_type = "Customer"
je.accounts[1].party = si.customer
je.accounts[1].reference_type = "Sales Invoice"
je.accounts[1].reference_name = si.name
je.accounts[1].credit_in_account_currency = 500
je.submit()
pe = get_payment_entry("Sales Invoice", si.name)
pe.paid_amount = 500
pe.references[0].allocated_amount = 500
pe.save()
pe.submit()
cr_note = create_sales_invoice(qty=-1, rate=500, is_return=1, return_against=si.name, do_not_save=1)
cr_note.update_outstanding_for_self = 0
cr_note.save()
cr_note.submit()
si.load_from_db()
pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1)
self.assertEqual(pr.grand_total, si.outstanding_amount)
def test_partial_paid_invoice_with_submitted_payment_entry(self):
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)

View File

@@ -24,7 +24,9 @@
"paid_amount",
"discounted_amount",
"column_break_3",
"base_payment_amount"
"base_payment_amount",
"base_outstanding",
"base_paid_amount"
],
"fields": [
{
@@ -155,19 +157,35 @@
"fieldtype": "Currency",
"label": "Payment Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fieldname": "base_outstanding",
"fieldtype": "Currency",
"label": "Outstanding (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"depends_on": "base_paid_amount",
"fieldname": "base_paid_amount",
"fieldtype": "Currency",
"label": "Paid Amount (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-16 13:57:06.382859",
"modified": "2025-03-11 11:06:51.792982",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1

View File

@@ -14,6 +14,8 @@ class PaymentSchedule(Document):
if TYPE_CHECKING:
from frappe.types import DF
base_outstanding: DF.Currency
base_paid_amount: DF.Currency
base_payment_amount: DF.Currency
description: DF.SmallText | None
discount: DF.Float

View File

@@ -139,7 +139,7 @@ class PeriodClosingVoucher(AccountsController):
self.cancel_gl_entries()
def make_gl_entries(self):
if self.get_gle_count_in_selected_period() > 5000:
if frappe.db.estimate_count("GL Entry") > 100_000:
frappe.enqueue(
process_gl_and_closing_entries,
doc=self,
@@ -154,16 +154,6 @@ class PeriodClosingVoucher(AccountsController):
else:
process_gl_and_closing_entries(self)
def get_gle_count_in_selected_period(self):
return frappe.db.count(
"GL Entry",
{
"posting_date": ["between", [self.period_start_date, self.period_end_date]],
"company": self.company,
"is_cancelled": 0,
},
)
def get_pcv_gl_entries(self):
self.pl_accounts_reverse_gle = []
self.closing_account_gle = []

View File

@@ -27,6 +27,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
company=company,
save=False,
)
jv1.company = company
@@ -39,6 +40,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account1="Cost of Goods Sold - TPC",
account2="Cash - TPC",
cost_center=cost_center,
company=company,
save=False,
)
jv2.company = company
@@ -156,6 +158,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
amount=400,
cost_center=cost_center,
posting_date="2021-03-15",
company=company,
)
jv.company = company
jv.finance_book = create_finance_book().name
@@ -198,6 +201,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
company=company,
save=False,
)
jv1.company = company
@@ -220,6 +224,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center1,
company=company,
save=False,
)
jv1.company = company
@@ -232,6 +237,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
company=company,
save=False,
)
jv2.company = company
@@ -261,6 +267,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
company=company,
save=False,
)

View File

@@ -1623,6 +1623,5 @@
"states": [],
"timeline_field": "customer",
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
"track_changes": 1
}

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc, map_doc
from frappe.query_builder import DocType
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
from frappe.utils.background_jobs import enqueue, is_job_enqueued
from frappe.utils.scheduler import is_scheduler_inactive
@@ -118,17 +119,18 @@ class POSInvoiceMergeLog(Document):
returns = [d for d in pos_invoice_docs if d.get("is_return") == 1]
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
sales_invoice, credit_note = "", ""
sales_invoice, credit_notes = "", {}
sales_invoice_doc = None
if sales:
sales_invoice_doc = self.process_merging_into_sales_invoice(sales)
sales_invoice = sales_invoice_doc.name
if returns:
credit_note = self.process_merging_into_credit_note(returns, sales_invoice_doc)
distinguished_returns = self.distinguish_return_pos_invoices(returns, sales_invoice_doc)
credit_notes = self.process_merging_into_credit_notes(distinguished_returns)
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_notes)
def on_cancel(self):
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
@@ -158,34 +160,50 @@ class POSInvoiceMergeLog(Document):
return sales_invoice
def process_merging_into_credit_note(self, data, sales_invoice_doc=None):
credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1
def process_merging_into_credit_notes(self, data):
credit_notes = {}
for key, value in data.items():
if not value:
continue
credit_note = self.merge_pos_invoice_into(credit_note, data)
referenes = {}
credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1
if sales_invoice_doc:
credit_note.return_against = sales_invoice_doc.name
credit_note = self.merge_pos_invoice_into(credit_note, value)
credit_note.return_against = key
for d in sales_invoice_doc.items:
referenes[d.item_code] = d.name
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date)
credit_note.posting_time = get_time(self.posting_time)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
credit_note.submit()
for d in credit_note.items:
d.sales_invoice_item = referenes.get(d.item_code)
self.consolidated_credit_note = credit_note.name
credit_notes[credit_note.name] = [d.name for d in value]
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date)
credit_note.posting_time = get_time(self.posting_time)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
credit_note.submit()
return credit_notes
self.consolidated_credit_note = credit_note.name
def distinguish_return_pos_invoices(self, data, sales_invoice_doc=None):
return_invoices = {}
return credit_note.name
return_invoices[sales_invoice_doc.name if sales_invoice_doc else None] = []
for doc in data:
sales_invoices_of_return_against = frappe.db.get_value(
"POS Invoice", doc.return_against, "consolidated_invoice"
)
if sales_invoices_of_return_against:
if sales_invoices_of_return_against in return_invoices:
return_invoices[sales_invoices_of_return_against].append(doc)
else:
return_invoices[sales_invoices_of_return_against] = [doc]
else:
return_invoices[sales_invoice_doc.name if sales_invoice_doc else None].append(doc)
return return_invoices
def merge_pos_invoice_into(self, invoice, data):
items, payments, taxes = [], [], []
@@ -211,33 +229,20 @@ class POSInvoiceMergeLog(Document):
loyalty_amount_sum += doc.loyalty_amount
for item in doc.get("items"):
found = False
for i in items:
if (
i.item_code == item.item_code
and not i.serial_and_batch_bundle
and not i.serial_no
and not i.batch_no
and i.uom == item.uom
and i.net_rate == item.net_rate
and i.warehouse == item.warehouse
):
found = True
i.qty = i.qty + item.qty
i.amount = i.amount + item.net_amount
i.net_amount = i.amount
i.base_amount = i.base_amount + item.base_net_amount
i.base_net_amount = i.base_amount
if not found:
item.rate = item.net_rate
item.amount = item.net_amount
item.base_amount = item.base_net_amount
item.price_list_rate = 0
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
if item.serial_and_batch_bundle:
si_item.serial_and_batch_bundle = item.serial_and_batch_bundle
items.append(si_item)
item.rate = item.net_rate
item.amount = item.net_amount
item.base_amount = item.base_net_amount
item.price_list_rate = 0
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
si_item.pos_invoice = doc.name
si_item.pos_invoice_item = item.name
if doc.is_return:
si_item.sales_invoice_item = get_sales_invoice_item(
doc.return_against, item.pos_invoice_item
)
if item.serial_and_batch_bundle:
si_item.serial_and_batch_bundle = item.serial_and_batch_bundle
items.append(si_item)
for tax in doc.get("taxes"):
found = False
@@ -327,16 +332,16 @@ class POSInvoiceMergeLog(Document):
return sales_invoice
def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_note=""):
def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_notes=None):
for doc in invoice_docs:
doc.load_from_db()
doc.update(
{
"consolidated_invoice": None
if self.docstatus == 2
else (credit_note if doc.is_return else sales_invoice)
}
)
inv = sales_invoice
if doc.is_return:
for key, value in credit_notes.items():
if doc.name in value:
inv = key
break
doc.update({"consolidated_invoice": None if self.docstatus == 2 else inv})
doc.set_status(update=True)
doc.save()
@@ -628,3 +633,26 @@ def get_error_message(message) -> str:
return message["message"]
except Exception:
return str(message)
def get_sales_invoice_item(return_against_pos_invoice, pos_invoice_item):
try:
SalesInvoice = DocType("Sales Invoice")
SalesInvoiceItem = DocType("Sales Invoice Item")
query = (
frappe.qb.from_(SalesInvoice)
.from_(SalesInvoiceItem)
.select(SalesInvoiceItem.name)
.where(
(SalesInvoice.name == SalesInvoiceItem.parent)
& (SalesInvoice.is_return == 0)
& (SalesInvoiceItem.pos_invoice == return_against_pos_invoice)
& (SalesInvoiceItem.pos_invoice_item == pos_invoice_item)
)
)
result = query.run(as_dict=True)
return result[0].name if result else None
except Exception:
return None

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _, msgprint, scrub, unscrub
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.document import Document
from frappe.utils import get_link_to_form, now
@@ -204,17 +205,41 @@ class POSProfile(Document):
def get_item_groups(pos_profile):
item_groups = []
pos_profile = frappe.get_cached_doc("POS Profile", pos_profile)
permitted_item_groups = get_permitted_nodes("Item Group")
if pos_profile.get("item_groups"):
# Get items based on the item groups defined in the POS profile
for data in pos_profile.get("item_groups"):
item_groups.extend(
["%s" % frappe.db.escape(d.name) for d in get_child_nodes("Item Group", data.item_group)]
[
"%s" % frappe.db.escape(d.name)
for d in get_child_nodes("Item Group", data.item_group)
if not permitted_item_groups or d.name in permitted_item_groups
]
)
if not item_groups and permitted_item_groups:
item_groups = ["%s" % frappe.db.escape(d) for d in permitted_item_groups]
return list(set(item_groups))
def get_permitted_nodes(group_type):
nodes = []
permitted_nodes = get_permitted_documents(group_type)
if not permitted_nodes:
return nodes
for node in permitted_nodes:
if frappe.db.get_value(group_type, node, "is_group"):
nodes.extend([d.name for d in get_child_nodes(group_type, node)])
else:
nodes.append(node)
return nodes
def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
return frappe.db.sql(

View File

@@ -454,8 +454,7 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
if pricing_rule.coupon_code_based == 1:
if not args.coupon_code:
return item_details
continue
coupon_code = frappe.db.get_value(
doctype="Coupon Code", filters={"pricing_rule": pricing_rule.name}, fieldname="name"
)

View File

@@ -871,6 +871,7 @@ class PurchaseInvoice(BuyingController):
self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
self.set_transaction_currency_and_rate_in_gl_map(gl_entries)
return gl_entries
def check_asset_cwip_enabled(self):
@@ -916,6 +917,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
else grand_total,
"credit_in_transaction_currency": grand_total,
"against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"project": self.project,
@@ -951,7 +953,7 @@ class PurchaseInvoice(BuyingController):
valuation_tax_accounts = [
d.account_head
for d in self.get("taxes")
if d.category in ("Valuation", "Total and Valuation")
if d.category in ("Valuation", "Valuation and Total")
and flt(d.base_tax_amount_after_discount_amount)
]
@@ -967,7 +969,6 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
if item.item_code:
frappe.get_cached_value("Item", item.item_code, "asset_category")
@@ -976,6 +977,7 @@ class PurchaseInvoice(BuyingController):
and self.auto_accounting_for_stock
and (item.item_code in stock_items or item.is_fixed_asset)
):
account_currency = get_account_currency(item.expense_account)
# warehouse account
warehouse_debit_amount = self.make_stock_adjustment_entry(
gl_entries, item, voucher_wise_stock_value, account_currency
@@ -991,6 +993,7 @@ class PurchaseInvoice(BuyingController):
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": item.net_amount,
},
warehouse_account[item.warehouse]["account_currency"],
item=item,
@@ -1011,6 +1014,7 @@ class PurchaseInvoice(BuyingController):
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
},
warehouse_account[item.from_warehouse]["account_currency"],
item=item,
@@ -1025,6 +1029,7 @@ class PurchaseInvoice(BuyingController):
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"debit_in_transaction_currency": item.net_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project,
@@ -1042,6 +1047,10 @@ class PurchaseInvoice(BuyingController):
"account": item.expense_account,
"against": self.supplier,
"debit": warehouse_debit_amount,
"debit_in_transaction_currency": flt(
warehouse_debit_amount / self.conversion_rate,
item.precision("net_amount"),
),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project or self.project,
@@ -1054,7 +1063,9 @@ class PurchaseInvoice(BuyingController):
# Amount added through landed-cost-voucher
if landed_cost_entries:
if (item.item_code, item.name) in landed_cost_entries:
for account, amount in landed_cost_entries[(item.item_code, item.name)].items():
for account, base_amount in landed_cost_entries[
(item.item_code, item.name)
].items():
gl_entries.append(
self.get_gl_dict(
{
@@ -1062,8 +1073,9 @@ class PurchaseInvoice(BuyingController):
"against": item.expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount["base_amount"]),
"credit_in_account_currency": flt(amount["amount"]),
"credit": flt(base_amount["base_amount"]),
"credit_in_account_currency": flt(base_amount["amount"]),
"credit_in_transaction_currency": item.net_amount,
"project": item.project or self.project,
},
item=item,
@@ -1086,6 +1098,7 @@ class PurchaseInvoice(BuyingController):
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost),
"credit_in_transaction_currency": item.net_amount,
},
warehouse_account[self.supplier_warehouse]["account_currency"],
item=item,
@@ -1099,7 +1112,8 @@ class PurchaseInvoice(BuyingController):
else item.deferred_expense_account
)
dummy, amount = self.get_amount_and_base_amount(item, None)
account_currency = get_account_currency(expense_account)
amount, base_amount = self.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
self.make_provisional_gl_entry(gl_entries, item)
@@ -1110,7 +1124,8 @@ class PurchaseInvoice(BuyingController):
{
"account": expense_account,
"against": self.supplier,
"debit": amount,
"debit": base_amount,
"debit_in_transaction_currency": amount,
"cost_center": item.cost_center,
"project": item.project or self.project,
},
@@ -1184,6 +1199,10 @@ class PurchaseInvoice(BuyingController):
"account": stock_rbnb,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"debit_in_transaction_currency": flt(
item.item_tax_amount / self.conversion_rate,
item.precision("item_tax_amount"),
),
"remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center,
"project": item.project or self.project,
@@ -1299,6 +1318,7 @@ class PurchaseInvoice(BuyingController):
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
@@ -1310,6 +1330,38 @@ class PurchaseInvoice(BuyingController):
warehouse_debit_amount = stock_amount
elif self.is_return and self.update_stock and self.is_internal_supplier and warehouse_debit_amount:
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate
stock_amount = (
net_rate
+ item.item_tax_amount
+ flt(item.landed_cost_voucher_amount)
+ flt(item.get("amount_difference_with_purchase_invoice"))
)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_adjustment_amt = stock_amount - warehouse_debit_amount
gl_entries.append(
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
return warehouse_debit_amount
def make_tax_gl_entries(self, gl_entries):
@@ -1332,6 +1384,7 @@ class PurchaseInvoice(BuyingController):
dr_or_cr + "_in_account_currency": base_amount
if account_currency == self.company_currency
else amount,
dr_or_cr + "_in_transaction_currency": amount,
"cost_center": tax.cost_center,
},
account_currency,
@@ -1378,6 +1431,10 @@ class PurchaseInvoice(BuyingController):
"cost_center": tax.cost_center,
"against": self.supplier,
"credit": applicable_amount,
"credit_in_transaction_currency": flt(
applicable_amount / self.conversion_rate,
frappe.get_precision("Purchase Invoice Item", "item_tax_amount"),
),
"remarks": self.remarks or _("Accounting Entry for Stock"),
},
item=tax,
@@ -1396,6 +1453,10 @@ class PurchaseInvoice(BuyingController):
"cost_center": tax.cost_center,
"against": self.supplier,
"credit": valuation_tax[tax.name],
"credit_in_transaction_currency": flt(
valuation_tax[tax.name] / self.conversion_rate,
frappe.get_precision("Purchase Invoice Item", "item_tax_amount"),
),
"remarks": self.remarks or _("Accounting Entry for Stock"),
},
item=tax,
@@ -1411,6 +1472,7 @@ class PurchaseInvoice(BuyingController):
"account": self.unrealized_profit_loss_account,
"against": self.supplier,
"credit": flt(self.total_taxes_and_charges),
"credit_in_transaction_currency": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center,
},
@@ -1460,6 +1522,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.base_paid_amount
if self.party_account_currency == self.company_currency
else self.paid_amount,
"debit_in_transaction_currency": self.paid_amount,
"against_voucher": self.return_against
if cint(self.is_return) and self.return_against
else self.name,
@@ -1481,6 +1544,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_paid_amount
if bank_account_currency == self.company_currency
else self.paid_amount,
"credit_in_transaction_currency": self.paid_amount,
"cost_center": self.cost_center,
},
bank_account_currency,
@@ -1505,6 +1569,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.base_write_off_amount
if self.party_account_currency == self.company_currency
else self.write_off_amount,
"debit_in_transaction_currency": self.write_off_amount,
"against_voucher": self.return_against
if cint(self.is_return) and self.return_against
else self.name,
@@ -1525,6 +1590,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_write_off_amount
if write_off_account_currency == self.company_currency
else self.write_off_amount,
"credit_in_transaction_currency": self.write_off_amount,
"cost_center": self.cost_center or self.write_off_cost_center,
},
item=self,

View File

@@ -2482,6 +2482,76 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
item.reload()
self.assertEqual(item.last_purchase_rate, 0)
def test_adjust_incoming_rate_from_pi_with_multi_currency_and_partial_billing(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
pr = make_purchase_receipt(
qty=10, rate=10, currency="USD", do_not_save=1, supplier="_Test Supplier USD"
)
pr.conversion_rate = 5300
pr.save()
pr.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
self.assertEqual(incoming_rate, 53000) # Asserting to confirm if the default calculation is correct
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 1 : Incoming rate should not change as only the qty has changed and not the rate (this was not the case before)
self.assertEqual(incoming_rate, 53000)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
row.rate = 9
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 2 : Rate in new PI is lower than PR, so incoming rate should also be lower
self.assertEqual(incoming_rate, 50350)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.qty = 1
row.rate = 12
pi.save()
pi.submit()
incoming_rate = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"incoming_rate",
)
# Test 3 : Rate in new PI is higher than PR, so incoming rate should also be higher
self.assertEqual(incoming_rate, 54766.667)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_opening_invoice_rounding_adjustment_validation(self):
pi = make_purchase_invoice(do_not_save=1)
pi.items[0].rate = 99.98
@@ -2569,6 +2639,50 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", original_value
)
def test_trx_currency_debit_credit_for_high_precision(self):
exc_rate = 0.737517516
pi = make_purchase_invoice(
currency="USD", conversion_rate=exc_rate, qty=1, rate=2000, do_not_save=True
)
pi.supplier = "_Test Supplier USD"
pi.save().submit()
expected = (
("_Test Account Cost for Goods Sold - _TC", 1475.04, 0.0, 2000.0, 0.0, "USD", exc_rate),
("_Test Payable USD - _TC", 0.0, 1475.04, 0.0, 2000.0, "USD", exc_rate),
)
actual = frappe.db.get_all(
"GL Entry",
filters={"voucher_no": pi.name},
fields=[
"account",
"debit",
"credit",
"debit_in_transaction_currency",
"credit_in_transaction_currency",
"transaction_currency",
"transaction_exchange_rate",
],
order_by="account",
as_list=1,
)
self.assertEqual(actual, expected)
def test_prevents_fully_returned_invoice_with_zero_quantity(self):
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
invoice = make_purchase_invoice(qty=10)
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = -10
return_doc.save().submit()
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = 0
self.assertRaises(StockOverReturnError, return_doc.save)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -461,7 +461,8 @@
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No"
"label": "Serial No",
"no_copy": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
@@ -975,7 +976,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-10-28 15:06:19.246141",
"modified": "2025-03-12 16:33:12.453290",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@@ -985,4 +986,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -8,6 +8,8 @@ from frappe import _, qb
from frappe.model.document import Document
from frappe.utils.data import comma_and
from erpnext.stock import get_warehouse_account_map
class RepostAccountingLedger(Document):
# begin: auto-generated types
@@ -97,6 +99,9 @@ class RepostAccountingLedger(Document):
doc = frappe.get_doc(x.voucher_type, x.voucher_no)
if doc.doctype in ["Payment Entry", "Journal Entry"]:
gle_map = doc.build_gl_map()
elif doc.doctype == "Purchase Receipt":
warehouse_account_map = get_warehouse_account_map(doc.company)
gle_map = doc.get_gl_entries(warehouse_account_map)
else:
gle_map = doc.get_gl_entries()
@@ -177,6 +182,14 @@ def start_repost(account_repost_doc=str) -> None:
doc.force_set_against_expense_account()
doc.make_gl_entries()
elif doc.doctype == "Purchase Receipt":
if not repost_doc.delete_cancelled_entries:
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.docstatus = 1
doc.make_gl_entries(from_repost=True)
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
if not repost_doc.delete_cancelled_entries:
doc.make_gl_entries(1)

View File

@@ -12,6 +12,8 @@ from erpnext.accounts.doctype.payment_request.payment_request import make_paymen
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, make_purchase_receipt
class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
@@ -204,9 +206,81 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
def test_06_repost_purchase_receipt(self):
from erpnext.accounts.doctype.account.test_account import create_account
provisional_account = create_account(
account_name="Provision Account",
parent_account="Current Liabilities - _TC",
company=self.company,
)
another_provisional_account = create_account(
account_name="Another Provision Account",
parent_account="Current Liabilities - _TC",
company=self.company,
)
company = frappe.get_doc("Company", self.company)
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()
test_cc = company.cost_center
default_expense_account = company.default_expense_account
item = make_item(properties={"is_stock_item": 0})
pr = make_purchase_receipt(company=self.company, item_code=item.name, rate=1000.0, qty=1.0)
pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
expected_pr_gles = [
{"account": provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
{"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
]
self.assertEqual(expected_pr_gles, pr_gl_entries)
# change the provisional account
frappe.db.set_value(
"Purchase Receipt Item",
pr.items[0].name,
"provisional_expense_account",
another_provisional_account,
)
repost_doc = frappe.new_doc("Repost Accounting Ledger")
repost_doc.company = self.company
repost_doc.delete_cancelled_entries = True
repost_doc.append("vouchers", {"voucher_type": pr.doctype, "voucher_no": pr.name})
repost_doc.save().submit()
pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
expected_pr_gles_after_repost = [
{"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
{"account": another_provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
]
self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost))
self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost)
# teardown
repost_doc.cancel()
repost_doc.delete()
pr.reload()
pr.cancel()
company.enable_provisional_accounting_for_non_stock_items = 0
company.default_provisional_account = None
company.save()
def update_repost_settings():
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
allowed_types = [
"Sales Invoice",
"Purchase Invoice",
"Payment Entry",
"Journal Entry",
"Purchase Receipt",
]
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
for x in allowed_types:
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})

View File

@@ -897,8 +897,16 @@ frappe.ui.form.on("Sales Invoice", {
project: function (frm) {
if (frm.doc.project) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project,
frappe.call({
method: "is_auto_fetch_timesheet_enabled",
doc: frm.doc,
callback: function (r) {
if (cint(r.message)) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project,
});
}
},
});
}
},
@@ -914,9 +922,25 @@ frappe.ui.form.on("Sales Invoice", {
}
const timesheets = await frm.events.get_timesheet_data(frm, kwargs);
if (kwargs.item_code) {
frm.events.add_timesheet_item(frm, kwargs.item_code, timesheets);
}
return frm.events.set_timesheet_data(frm, timesheets);
},
add_timesheet_item: function (frm, item_code, timesheets) {
const row = frm.add_child("items");
frappe.model.set_value(row.doctype, row.name, "item_code", item_code);
frappe.model.set_value(
row.doctype,
row.name,
"qty",
timesheets.reduce((a, b) => a + (b["billing_hours"] || 0.0), 0.0)
);
},
async get_timesheet_data(frm, kwargs) {
return frappe
.call({
@@ -1014,6 +1038,22 @@ frappe.ui.form.on("Sales Invoice", {
fieldtype: "Date",
reqd: 1,
},
{
label: __("Item Code"),
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
get_query: () => {
return {
query: "erpnext.controllers.queries.item_query",
filters: {
is_sales_item: 1,
customer: frm.doc.customer,
has_variants: 0,
},
};
},
},
{
fieldtype: "Column Break",
fieldname: "col_break_1",
@@ -1038,6 +1078,7 @@ frappe.ui.form.on("Sales Invoice", {
from_time: data.from_time,
to_time: data.to_time,
project: data.project,
item_code: data.item_code,
});
d.hide();
},

View File

@@ -2177,6 +2177,7 @@
"print_hide": 1
}
],
"grid_page_length": 50,
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
@@ -2187,7 +2188,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-02-06 15:59:54.636202",
"modified": "2025-03-17 19:32:31.809658",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2233,6 +2234,7 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount",
"show_name_in_global_search": 1,
"sort_field": "modified",
@@ -2242,4 +2244,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -678,7 +678,13 @@ class SalesInvoice(SellingController):
"Account", self.debit_to, "account_currency", cache=True
)
if not self.due_date and self.customer:
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
self.due_date = get_due_date(
self.posting_date,
"Customer",
self.customer,
self.company,
template_name=self.payment_terms_template,
)
super().set_missing_values(for_validate)
@@ -1090,11 +1096,15 @@ class SalesInvoice(SellingController):
timesheet.billing_amount = ts_doc.total_billable_amount
def update_timesheet_billing_for_project(self):
if not self.timesheets and self.project:
if not self.timesheets and self.project and self.is_auto_fetch_timesheet_enabled():
self.add_timesheet_data()
else:
self.calculate_billing_amount_for_timesheet()
@frappe.whitelist()
def is_auto_fetch_timesheet_enabled(self):
return frappe.db.get_single_value("Projects Settings", "fetch_timesheet_in_sales_invoice")
@frappe.whitelist()
def add_timesheet_data(self):
self.set("timesheets", [])
@@ -1237,6 +1247,7 @@ class SalesInvoice(SellingController):
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
self.set_transaction_currency_and_rate_in_gl_map(gl_entries)
return gl_entries
def make_customer_gl_entry(self, gl_entries):
@@ -1270,6 +1281,7 @@ class SalesInvoice(SellingController):
"debit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
else grand_total,
"debit_in_transaction_currency": grand_total,
"against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1301,6 +1313,9 @@ class SalesInvoice(SellingController):
if account_currency == self.company_currency
else flt(amount, tax.precision("tax_amount_after_discount_amount"))
),
"credit_in_transaction_currency": flt(
amount, tax.precision("tax_amount_after_discount_amount")
),
"cost_center": tax.cost_center,
},
account_currency,
@@ -1318,6 +1333,7 @@ class SalesInvoice(SellingController):
"against": self.customer,
"debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"debit_in_transaction_currency": flt(self.total_taxes_and_charges),
"cost_center": self.cost_center,
},
account_currency,
@@ -1416,6 +1432,7 @@ class SalesInvoice(SellingController):
if account_currency == self.company_currency
else flt(amount, item.precision("net_amount"))
),
"credit_in_transaction_currency": flt(amount, item.precision("net_amount")),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
@@ -1467,6 +1484,7 @@ class SalesInvoice(SellingController):
+ cstr(self.loyalty_redemption_account)
+ " for the Loyalty Program",
"credit": self.loyalty_amount,
"credit_in_transaction_currency": self.loyalty_amount,
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1481,6 +1499,7 @@ class SalesInvoice(SellingController):
"cost_center": self.cost_center or self.loyalty_redemption_cost_center,
"against": self.customer,
"debit": self.loyalty_amount,
"debit_in_transaction_currency": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer",
},
item=self,
@@ -1514,6 +1533,7 @@ class SalesInvoice(SellingController):
"credit_in_account_currency": payment_mode.base_amount
if self.party_account_currency == self.company_currency
else payment_mode.amount,
"credit_in_transaction_currency": payment_mode.amount,
"against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1533,6 +1553,7 @@ class SalesInvoice(SellingController):
"debit_in_account_currency": payment_mode.base_amount
if payment_mode_account_currency == self.company_currency
else payment_mode.amount,
"debit_in_transaction_currency": payment_mode.amount,
"cost_center": self.cost_center,
},
payment_mode_account_currency,
@@ -1557,6 +1578,7 @@ class SalesInvoice(SellingController):
"debit_in_account_currency": flt(self.base_change_amount)
if self.party_account_currency == self.company_currency
else flt(self.change_amount),
"debit_in_transaction_currency": flt(self.change_amount),
"against_voucher": self.return_against
if cint(self.is_return) and self.return_against
else self.name,
@@ -1575,6 +1597,7 @@ class SalesInvoice(SellingController):
"account": self.account_for_change_amount,
"against": self.customer,
"credit": self.base_change_amount,
"credit_in_transaction_currency": self.change_amount,
"cost_center": self.cost_center,
},
item=self,
@@ -1606,6 +1629,9 @@ class SalesInvoice(SellingController):
if self.party_account_currency == self.company_currency
else flt(self.write_off_amount, self.precision("write_off_amount"))
),
"credit_in_transaction_currency": flt(
self.write_off_amount, self.precision("write_off_amount")
),
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
@@ -1626,6 +1652,9 @@ class SalesInvoice(SellingController):
if write_off_account_currency == self.company_currency
else flt(self.write_off_amount, self.precision("write_off_amount"))
),
"debit_in_transaction_currency": flt(
self.write_off_amount, self.precision("write_off_amount")
),
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center,
},
write_off_account_currency,
@@ -1670,6 +1699,9 @@ class SalesInvoice(SellingController):
"credit_in_account_currency": flt(
self.rounding_adjustment, self.precision("rounding_adjustment")
),
"credit_in_transaction_currency": flt(
self.rounding_adjustment, self.precision("rounding_adjustment")
),
"credit": flt(
self.base_rounding_adjustment, self.precision("base_rounding_adjustment")
),
@@ -1933,13 +1965,16 @@ def is_overdue(doc, total):
"base_payment_amount" if doc.party_account_currency != doc.currency else "payment_amount"
)
payable_amount = sum(
payment.get(payment_amount_field)
for payment in doc.payment_schedule
if getdate(payment.due_date) < today
payable_amount = flt(
sum(
payment.get(payment_amount_field)
for payment in doc.payment_schedule
if getdate(payment.due_date) < today
),
doc.precision("outstanding_amount"),
)
return (total - outstanding_amount) < payable_amount
return flt(total - outstanding_amount, doc.precision("outstanding_amount")) < payable_amount
def get_discounting_status(sales_invoice):

View File

@@ -1819,17 +1819,6 @@ class TestSalesInvoice(FrappeTestCase):
for field in expected_gle:
self.assertEqual(expected_gle[field], gle[field])
def test_invoice_exchange_rate(self):
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=1,
do_not_save=1,
)
self.assertRaises(frappe.ValidationError, si.save)
def test_invalid_currency(self):
# Customer currency = USD
@@ -4246,6 +4235,31 @@ class TestSalesInvoice(FrappeTestCase):
doc = frappe.get_doc("Project", project.name)
self.assertEqual(doc.total_billed_amount, si.grand_total)
def test_total_billed_amount_with_different_projects(self):
# This test case is for checking the scenario where project is set at document level and for **some** child items only, not all
from copy import copy
si = create_sales_invoice(do_not_submit=True)
project = frappe.new_doc("Project")
project.company = "_Test Company"
project.project_name = "Test Total Billed Amount"
project.save()
si.project = project.name
si.items.append(copy(si.items[0]))
si.items.append(copy(si.items[0]))
si.items[0].project = project.name
si.items[1].project = project.name
# Not setting project on last item
si.items[1].insert()
si.items[2].insert()
si.submit()
project.reload()
self.assertIsNone(si.items[2].project)
self.assertEqual(project.total_billed_amount, 300)
def test_pos_returns_with_party_account_currency(self):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
@@ -4270,6 +4284,20 @@ class TestSalesInvoice(FrappeTestCase):
pos_return = make_sales_return(pos.name)
self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount)
def test_prevents_fully_returned_invoice_with_zero_quantity(self):
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
invoice = create_sales_invoice(qty=10)
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = -10
return_doc.save().submit()
return_doc = make_return_doc(invoice.doctype, invoice.name)
return_doc.items[0].qty = 0
self.assertRaises(StockOverReturnError, return_doc.save)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -105,6 +105,9 @@
"delivery_note",
"dn_detail",
"delivered_qty",
"column_break_vwhb",
"pos_invoice",
"pos_invoice_item",
"internal_transfer_section",
"purchase_order",
"column_break_92",
@@ -630,6 +633,7 @@
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1,
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text"
},
@@ -945,19 +949,41 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "pos_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "POS Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_vwhb",
"fieldtype": "Column Break"
},
{
"fieldname": "pos_invoice",
"fieldtype": "Link",
"label": "POS Invoice",
"no_copy": 1,
"options": "POS Invoice",
"print_hide": 1,
"search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-11-25 16:27:33.287341",
"modified": "2025-03-12 16:33:52.503777",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -64,6 +64,8 @@ class SalesInvoiceItem(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
pos_invoice: DF.Link | None
pos_invoice_item: DF.Data | None
price_list_rate: DF.Currency
pricing_rules: DF.SmallText | None
project: DF.Link | None

View File

@@ -13,17 +13,15 @@
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Voucher Type",
"options": "DocType"
"label": "Voucher Type"
},
{
"fieldname": "voucher_name",
"fieldtype": "Dynamic Link",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Voucher Name",
"options": "voucher_type"
"label": "Voucher Name"
},
{
"fieldname": "taxable_amount",
@@ -36,7 +34,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-01-13 13:40:41.479208",
"modified": "2025-02-05 16:39:14.863698",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",

View File

@@ -18,8 +18,8 @@ class TaxWithheldVouchers(Document):
parentfield: DF.Data
parenttype: DF.Data
taxable_amount: DF.Currency
voucher_name: DF.DynamicLink | None
voucher_type: DF.Link | None
voucher_name: DF.Data | None
voucher_type: DF.Data | None
# end: auto-generated types
pass

View File

@@ -10,6 +10,7 @@ frappe.ui.form.on("Tax Withholding Category", {
filters: {
company: child.company,
root_type: ["in", ["Asset", "Liability"]],
is_group: 0,
},
};
}

View File

@@ -36,27 +36,38 @@ class TaxWithholdingCategory(Document):
def validate(self):
self.validate_dates()
self.validate_accounts()
self.validate_companies_and_accounts()
self.validate_thresholds()
def validate_dates(self):
last_date = None
for d in self.get("rates"):
last_to_date = None
rates = sorted(self.get("rates"), key=lambda d: getdate(d.from_date))
for d in rates:
if getdate(d.from_date) >= getdate(d.to_date):
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))
# validate overlapping of dates
if last_date and getdate(d.to_date) < getdate(last_date):
if last_to_date and getdate(d.from_date) < getdate(last_to_date):
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
def validate_accounts(self):
existing_accounts = []
last_to_date = d.to_date
def validate_companies_and_accounts(self):
existing_accounts = set()
companies = set()
for d in self.get("accounts"):
# validate duplicate company
if d.get("company") in companies:
frappe.throw(_("Company {0} added multiple times").format(frappe.bold(d.get("company"))))
companies.add(d.get("company"))
# validate duplicate account
if d.get("account") in existing_accounts:
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
validate_account_head(d.idx, d.get("account"), d.get("company"))
existing_accounts.append(d.get("account"))
existing_accounts.add(d.get("account"))
def validate_thresholds(self):
for d in self.get("rates"):
@@ -436,6 +447,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
tax_details.get("tax_withholding_category"),
company,
),
as_dict=1,
)
for d in journal_entries_details:

View File

@@ -519,7 +519,7 @@ class TestTaxWithholdingCategory(FrappeTestCase):
payment = get_payment_entry(order.doctype, order.name)
payment.apply_tax_withholding_amount = 1
payment.tax_withholding_category = "Cumulative Threshold TDS"
payment.submit()
payment.save().submit()
self.assertEqual(payment.taxes[0].tax_amount, 4000)
def test_multi_category_single_supplier(self):

View File

@@ -81,6 +81,10 @@ def make_acc_dimensions_offsetting_entry(gl_map):
"credit_in_account_currency": credit,
"remarks": _("Offsetting for Accounting Dimension") + f" - {dimension.name}",
"against_voucher": None,
"account_currency": dimension.account_currency,
# Party Type and Party are restricted to Receivable and Payable accounts
"party_type": None,
"party": None,
}
)
offsetting_entry["against_voucher_type"] = None
@@ -108,6 +112,9 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
accounting_dimensions_to_offset = []
for acc_dimension in acc_dimensions:
values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
acc_dimension.account_currency = frappe.get_cached_value(
"Account", acc_dimension.offsetting_account, "account_currency"
)
if len(values) > 1:
accounting_dimensions_to_offset.append(acc_dimension)
@@ -430,7 +437,7 @@ def process_debit_credit_difference(gl_map):
voucher_no = gl_map[0].voucher_no
allowance = get_debit_credit_allowance(voucher_type, precision)
debit_credit_diff = get_debit_credit_difference(gl_map, precision)
debit_credit_diff, trx_cur_debit_credit_diff = get_debit_credit_difference(gl_map, precision)
if abs(debit_credit_diff) > allowance:
if not (
@@ -441,9 +448,9 @@ def process_debit_credit_difference(gl_map):
raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
make_round_off_gle(gl_map, debit_credit_diff, precision)
make_round_off_gle(gl_map, debit_credit_diff, trx_cur_debit_credit_diff, precision)
debit_credit_diff = get_debit_credit_difference(gl_map, precision)
debit_credit_diff, trx_cur_debit_credit_diff = get_debit_credit_difference(gl_map, precision)
if abs(debit_credit_diff) > allowance:
if not (
voucher_type == "Journal Entry"
@@ -455,14 +462,23 @@ def process_debit_credit_difference(gl_map):
def get_debit_credit_difference(gl_map, precision):
debit_credit_diff = 0.0
trx_cur_debit_credit_diff = 0
for entry in gl_map:
entry.debit = flt(entry.debit, precision)
entry.credit = flt(entry.credit, precision)
debit_credit_diff += entry.debit - entry.credit
debit_credit_diff = flt(debit_credit_diff, precision)
entry.debit_in_transaction_currency = flt(entry.debit_in_transaction_currency, precision)
entry.credit_in_transaction_currency = flt(entry.credit_in_transaction_currency, precision)
trx_cur_debit_credit_diff += (
entry.debit_in_transaction_currency - entry.credit_in_transaction_currency
)
return debit_credit_diff
debit_credit_diff = flt(debit_credit_diff, precision)
trx_cur_debit_credit_diff = flt(trx_cur_debit_credit_diff, precision)
return debit_credit_diff, trx_cur_debit_credit_diff
def get_debit_credit_allowance(voucher_type, precision):
@@ -489,7 +505,7 @@ def has_opening_entries(gl_map: list) -> bool:
return False
def make_round_off_gle(gl_map, debit_credit_diff, precision):
def make_round_off_gle(gl_map, debit_credit_diff, trx_cur_debit_credit_diff, precision):
round_off_account, round_off_cost_center, round_off_for_opening = get_round_off_account_and_cost_center(
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
)
@@ -534,6 +550,12 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
"credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
"debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
"credit": debit_credit_diff if debit_credit_diff > 0 else 0,
"debit_in_transaction_currency": abs(trx_cur_debit_credit_diff)
if trx_cur_debit_credit_diff < 0
else 0,
"credit_in_transaction_currency": trx_cur_debit_credit_diff
if trx_cur_debit_credit_diff > 0
else 0,
"cost_center": round_off_cost_center,
"party_type": None,
"party": None,

View File

@@ -279,9 +279,7 @@ def get_regional_address_details(party_details, doctype, company):
pass
def set_contact_details(party_details, party, party_type):
party_details.contact_person = get_default_contact(party_type, party.name)
def complete_contact_details(party_details):
if not party_details.contact_person:
party_details.update(
{
@@ -310,6 +308,11 @@ def set_contact_details(party_details, party, party_type):
party_details.update(contact_details)
def set_contact_details(party_details, party, party_type):
party_details.contact_person = get_default_contact(party_type, party.name)
complete_contact_details(party_details)
def set_other_values(party_details, party, party_type):
# copy
if party_type == "Customer":
@@ -572,12 +575,13 @@ def validate_party_accounts(doc):
@frappe.whitelist()
def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
def get_due_date(posting_date, party_type, party, company=None, bill_date=None, template_name=None):
"""Get due date from `Payment Terms Template`"""
due_date = None
if (bill_date or posting_date) and party:
due_date = bill_date or posting_date
template_name = get_payment_terms_template(party, party_type, company)
if not template_name:
template_name = get_payment_terms_template(party, party_type, company)
if template_name:
due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")

View File

@@ -0,0 +1,161 @@
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
{% if print_heading_template %}
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
{% else %}
{% endif %}
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
<div class="text-center" document-status="cancelled">
<h4 style="margin: 0px;">{{ _("CANCELLED") }}</h4>
</div>
{%- endif -%}
{%- endmacro -%}
{% for page in layout %}
<div class="page-break">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
</div>
<style>
.taxes-section .order-taxes.mt-5{
margin-top: 0px !important;
}
.taxes-section .order-taxes .border-btm.pb-5{
padding-bottom: 0px !important;
}
.print-format label{
color: #74808b;
font-size: 12px;
margin-bottom: 4px;
}
</style>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
<div class="row section-break" style="margin-bottom: 10px;">
<div class="col-xs-6 p-0">
<div class="col-xs-12 value text-uppercase"><b>{{ doc.customer }}</b></div>
<div class="col-xs-12">
{{ doc.address_display }}
</div>
<div class="col-xs-12">
{{ _("Contact: ")+doc.contact_display if doc.contact_display else '' }}
</div>
<div class="col-xs-12">
{{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }}
</div>
</div>
<div class="col-xs-3"></div>
<div class="col-xs-3" style="padding-left: 5px;">
<div>
<div><label>{{ _("Invoice ID") }}</label></div>
<div>{{ doc.name }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Invoice Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.posting_date) }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Due Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.due_date) }}</div>
</div>
</div>
</div>
<div class="section-break">
<table class="table table-bordered table-condensed mb-0" style="width: 100%; border-collapse: collapse; font-size: 12px;">
<colgroup>
<col style="width: 5%">
<col style="width: 45%">
<col style="width: 10%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr>
<th class="text-uppercase" style="text-align:center">{{ _("Sr") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Details") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Qty") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Rate") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Amount") }}</th>
</tr>
</thead>
{% for item in doc.items %}
<tr>
<td style="text-align:center">{{ loop.index }}</td>
<td>
<b>{{ item.item_code }}: {{ item.item_name }}</b>
{% if (item.description != item.item_name) %}
<br>{{ item.description }}
{% endif %}
</td>
<td style="text-align: center;">
{{ item.get_formatted("qty", 0) }}
{{ item.get_formatted("uom", 0) }}
</td>
<td style="text-align: right;">{{ item.get_formatted("net_rate", doc) }}</td>
<td style="text-align: right;">{{ item.get_formatted("net_amount", doc) }}</td>
</tr>
{% endfor %}
</table>
<!-- total -->
<div class="row">
<div class="col-xs-6">
<div>
<label>{{ _("Amount in Words") }}</label>
{{ doc.in_words }}
</div>
<div style="margin-top: 20px;">
<label>{{ _("Payment Status") }}</label>
{{ doc.status }}
</div>
</div>
<div class="col-xs-6">
<div class="row section-break">
<div class="col-xs-7"><div>{{ _("Sub Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("net_total", doc) }}</div>
</div>
<div>
{% for d in doc.taxes %}
{% if d.tax_amount %}
<div class="row">
<div class="col-xs-8"><div>{{ _(d.description) }}</div></div>
<div class="col-xs-4" style="text-align: right;">{{ d.get_formatted("tax_amount") }}</div>
</div>
{% endif %}
{% endfor %}
</div>
<div class="row">
<div class="col-xs-7"><div>{{ _("Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("grand_total", doc) }}</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row important data-field">
<div class="col-xs-12"><label>{{ _("Terms and Conditions") }}: </label></div>
<div class="col-xs-12">{{ doc.terms if doc.terms else '' }}</div>
</div>
</div>
</div>
</div>
{% endfor %}

View File

@@ -0,0 +1,32 @@
{
"absolute_value": 0,
"align_labels_right": 0,
"creation": "2025-01-22 16:23:51.012200",
"css": "",
"custom_format": 0,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "",
"font_size": 14,
"idx": 0,
"line_breaks": 0,
"margin_bottom": 0.0,
"margin_left": 0.0,
"margin_right": 0.0,
"margin_top": 0.0,
"modified": "2025-01-22 16:23:51.012200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Print",
"owner": "Administrator",
"page_number": "Hide",
"print_format_builder": 0,
"print_format_builder_beta": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

View File

@@ -517,10 +517,10 @@ class ReceivablePayableReport:
select
si.name, si.party_account_currency, si.currency, si.conversion_rate,
si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount,
ps.description, ps.paid_amount, ps.discounted_amount
ps.description, ps.paid_amount, ps.base_paid_amount, ps.discounted_amount
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
where
si.name = ps.parent and
si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and
si.name = %s and
si.is_return = 0
order by ps.paid_amount desc, due_date
@@ -540,20 +540,24 @@ class ReceivablePayableReport:
# Deduct that from paid amount pre allocation
row.paid -= flt(payment_terms_details[0].total_advance)
company_currency = frappe.get_value("Company", self.filters.get("company"), "default_currency")
# If single payment terms, no need to split the row
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
self.append_payment_term(row, payment_terms_details[0], original_row)
self.append_payment_term(row, payment_terms_details[0], original_row, company_currency)
return
for d in payment_terms_details:
term = frappe._dict(original_row)
self.append_payment_term(row, d, term)
self.append_payment_term(row, d, term, company_currency)
def append_payment_term(self, row, d, term):
if d.currency == d.party_account_currency:
def append_payment_term(self, row, d, term, company_currency):
invoiced = d.base_payment_amount
paid_amount = d.base_paid_amount
if company_currency == d.party_account_currency or self.filters.get("in_party_currency"):
invoiced = d.payment_amount
else:
invoiced = d.base_payment_amount
paid_amount = d.paid_amount
row.payment_terms.append(
term.update(
@@ -562,15 +566,15 @@ class ReceivablePayableReport:
"invoiced": invoiced,
"invoice_grand_total": row.invoiced,
"payment_term": d.description or d.payment_term,
"paid": d.paid_amount + d.discounted_amount,
"paid": paid_amount + d.discounted_amount,
"credit_note": 0.0,
"outstanding": invoiced - d.paid_amount - d.discounted_amount,
"outstanding": invoiced - paid_amount - d.discounted_amount,
}
)
)
if d.paid_amount:
row["paid"] -= d.paid_amount + d.discounted_amount
if paid_amount:
row["paid"] -= paid_amount + d.discounted_amount
def allocate_closing_to_term(self, row, term, key):
if row[key]:
@@ -729,11 +733,13 @@ class ReceivablePayableReport:
"company": self.filters.company,
"update_outstanding_for_self": 0,
}
or_filters = {}
for party_type in self.party_type:
if party_type := self.filters.party_type:
party_field = scrub(party_type)
if self.filters.get(party_field):
or_filters.update({party_field: self.filters.get(party_field)})
if parties := self.filters.get("party"):
or_filters.update({party_field: ["in", parties]})
self.return_entries = frappe._dict(
frappe.get_all(
doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1

View File

@@ -50,6 +50,7 @@ def get_group_by_asset_category_data(filters):
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
- flt(row.depreciation_eliminated_during_the_period)
- flt(row.depreciation_eliminated_via_reversal)
)
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
@@ -144,6 +145,130 @@ def get_asset_categories_for_grouped_by_category(filters):
)
def get_assets_for_grouped_by_category(filters):
condition = ""
if filters.get("asset_category"):
condition = f" and a.asset_category = '{filters.get('asset_category')}'"
finance_book_filter = ""
if filters.get("finance_book"):
finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s"
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)"
# nosemgrep
return frappe.db.sql(
f"""
SELECT results.asset_category,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category,
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
gle.debit
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
gle.credit
else
0
end), 0) as depreciation_eliminated_via_reversal,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
else
0
end), 0) as depreciation_eliminated_during_the_period,
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
gle.debit
else
0
end), 0) as depreciation_amount_during_the_period
from `tabGL Entry` gle
join `tabAsset` a on
gle.against_voucher = a.name
join `tabAsset Category Account` aca on
aca.parent = a.asset_category and aca.company_name = %(company)s
join `tabCompany` company on
company.name = %(company)s
where
a.docstatus=1
and a.company=%(company)s
and a.purchase_date <= %(to_date)s
and gle.is_cancelled = 0
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
{condition} {finance_book_filter}
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then
0
else
a.opening_accumulated_depreciation
end), 0) as accumulated_depreciation_as_on_from_date,
0 as depreciation_eliminated_via_reversal,
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
a.opening_accumulated_depreciation
else
0
end), 0) as depreciation_eliminated_during_the_period,
0 as depreciation_amount_during_the_period
from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
group by a.asset_category) as results
group by results.asset_category
""",
{
"to_date": filters.to_date,
"from_date": filters.from_date,
"company": filters.company,
"finance_book": filters.get("finance_book", ""),
},
as_dict=1,
)
def get_group_by_asset_data(filters):
data = []
asset_details = get_asset_details_for_grouped_by_category(filters)
assets = get_assets_for_grouped_by_asset(filters)
for asset_detail in asset_details:
row = frappe._dict()
row.update(asset_detail)
row.value_as_on_to_date = (
flt(row.value_as_on_from_date)
+ flt(row.value_of_new_purchase)
- flt(row.value_of_sold_asset)
- flt(row.value_of_scrapped_asset)
- flt(row.value_of_capitalized_asset)
)
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
row.accumulated_depreciation_as_on_to_date = (
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
- flt(row.depreciation_eliminated_during_the_period)
- flt(row.depreciation_eliminated_via_reversal)
)
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
row.accumulated_depreciation_as_on_from_date
)
row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
row.accumulated_depreciation_as_on_to_date
)
data.append(row)
return data
def get_asset_details_for_grouped_by_category(filters):
condition = ""
if filters.get("asset"):
@@ -223,123 +348,6 @@ def get_asset_details_for_grouped_by_category(filters):
)
def get_group_by_asset_data(filters):
data = []
asset_details = get_asset_details_for_grouped_by_category(filters)
assets = get_assets_for_grouped_by_asset(filters)
for asset_detail in asset_details:
row = frappe._dict()
row.update(asset_detail)
row.value_as_on_to_date = (
flt(row.value_as_on_from_date)
+ flt(row.value_of_new_purchase)
- flt(row.value_of_sold_asset)
- flt(row.value_of_scrapped_asset)
- flt(row.value_of_capitalized_asset)
)
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
row.accumulated_depreciation_as_on_to_date = (
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
- flt(row.depreciation_eliminated_during_the_period)
)
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
row.accumulated_depreciation_as_on_from_date
)
row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
row.accumulated_depreciation_as_on_to_date
)
data.append(row)
return data
def get_assets_for_grouped_by_category(filters):
condition = ""
if filters.get("asset_category"):
condition = f" and a.asset_category = '{filters.get('asset_category')}'"
finance_book_filter = ""
if filters.get("finance_book"):
finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s"
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)"
# nosemgrep
return frappe.db.sql(
f"""
SELECT results.asset_category,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category,
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
gle.debit
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
else
0
end), 0) as depreciation_eliminated_during_the_period,
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
gle.debit
else
0
end), 0) as depreciation_amount_during_the_period
from `tabGL Entry` gle
join `tabAsset` a on
gle.against_voucher = a.name
join `tabAsset Category Account` aca on
aca.parent = a.asset_category and aca.company_name = %(company)s
join `tabCompany` company on
company.name = %(company)s
where
a.docstatus=1
and a.company=%(company)s
and a.purchase_date <= %(to_date)s
and gle.debit != 0
and gle.is_cancelled = 0
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
{condition} {finance_book_filter}
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
0
else
a.opening_accumulated_depreciation
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
a.opening_accumulated_depreciation
else
0
end), 0) as depreciation_eliminated_during_the_period,
0 as depreciation_amount_during_the_period
from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
group by a.asset_category) as results
group by results.asset_category
""",
{
"to_date": filters.to_date,
"from_date": filters.from_date,
"company": filters.company,
"finance_book": filters.get("finance_book", ""),
},
as_dict=1,
)
def get_assets_for_grouped_by_asset(filters):
condition = ""
if filters.get("asset"):
@@ -354,6 +362,7 @@ def get_assets_for_grouped_by_asset(filters):
f"""
SELECT results.name as asset,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.name as name,
@@ -362,6 +371,11 @@ def get_assets_for_grouped_by_asset(filters):
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
gle.credit
else
0
end), 0) as depreciation_eliminated_via_reversal,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
@@ -385,18 +399,18 @@ def get_assets_for_grouped_by_asset(filters):
a.docstatus=1
and a.company=%(company)s
and a.purchase_date <= %(to_date)s
and gle.debit != 0
and gle.is_cancelled = 0
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
{finance_book_filter} {condition}
group by a.name
union
SELECT a.name as name,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then
0
else
a.opening_accumulated_depreciation
end), 0) as accumulated_depreciation_as_on_from_date,
0 as depreciation_as_on_from_date_credit,
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
a.opening_accumulated_depreciation
else
@@ -503,6 +517,12 @@ def get_columns(filters):
"fieldtype": "Currency",
"width": 270,
},
{
"label": _("Depreciation eliminated via reversal"),
"fieldname": "depreciation_eliminated_via_reversal",
"fieldtype": "Currency",
"width": 270,
},
{
"label": _("Net Asset value as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "net_asset_value_as_on_from_date",

View File

@@ -27,6 +27,7 @@ def get_report_filters(report_filters):
["Purchase Invoice", "docstatus", "=", 1],
["Purchase Invoice", "per_received", "<", 100],
["Purchase Invoice", "update_stock", "=", 0],
["Purchase Invoice", "is_opening", "!=", "Yes"],
]
if report_filters.get("purchase_invoice"):

View File

@@ -263,6 +263,7 @@ def get_actual_details(name, filters):
and ba.account=gl.account
and b.{budget_against} = gl.{budget_against}
and gl.fiscal_year between %s and %s
and gl.is_cancelled = 0
and b.{budget_against} = %s
and exists(
select

View File

@@ -307,6 +307,7 @@ class Deferred_Revenue_and_Expense_Report:
.where(
(inv.docstatus == 1)
& (deferred_flag_field == 1)
& (inv.company == self.filters.company)
& (
(
(self.period_list[0].from_date >= inv_item.service_start_date)

View File

@@ -2,5 +2,27 @@
// For license information, please see license.txt
frappe.query_reports["Delivered Items To Be Billed"] = {
filters: [],
filters: [
{
label: __("Company"),
fieldname: "company",
fieldtype: "Link",
options: "Company",
reqd: 1,
default: frappe.defaults.get_default("Company"),
},
{
label: __("As on Date"),
fieldname: "posting_date",
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.get_today(),
},
{
label: __("Delivery Note"),
fieldname: "delivery_note",
fieldtype: "Link",
options: "Delivery Note",
},
],
};

View File

@@ -3,6 +3,7 @@
from frappe import _
from pypika import Order
from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data
@@ -10,7 +11,7 @@ from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_d
def execute(filters=None):
columns = get_column()
args = get_args()
data = get_ordered_to_be_billed_data(args)
data = get_ordered_to_be_billed_data(args, filters)
return columns, data
@@ -76,13 +77,6 @@ def get_column():
"options": "Project",
"width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120,
},
]
@@ -92,5 +86,6 @@ def get_args():
"party": "customer",
"date": "posting_date",
"order": "name",
"order_by": "desc",
"order_by": Order.desc,
"reference_field": "delivery_note",
}

View File

@@ -630,7 +630,7 @@ def get_cost_centers_with_children(cost_centers):
def get_columns(periodicity, period_list, accumulated_values=1, company=None, cash_flow=False):
columns = [
{
"fieldname": "account",
"fieldname": "account" if not cash_flow else "section",
"label": _("Account") if not cash_flow else _("Section"),
"fieldtype": "Link",
"options": "Account",

View File

@@ -534,6 +534,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot
for dim in accounting_dimensions:
keylist.append(gle.get(dim))
keylist.append(gle.get("cost_center"))
keylist.append(gle.get("project"))
key = tuple(keylist)
if key not in consolidated_gle:
@@ -679,10 +680,11 @@ def get_columns(filters):
{"label": _("Against Account"), "fieldname": "against", "width": 120},
{"label": _("Party Type"), "fieldname": "party_type", "width": 100},
{"label": _("Party"), "fieldname": "party", "width": 100},
{"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100},
]
if filters.get("include_dimensions"):
columns.append({"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100})
for dim in get_accounting_dimensions(as_list=False):
columns.append(
{"label": _(dim.label), "options": dim.label, "fieldname": dim.fieldname, "width": 100}

View File

@@ -4,11 +4,12 @@
import frappe
from frappe.model.meta import get_field_precision
from frappe.query_builder.functions import IfNull, Round
from erpnext import get_default_currency
def get_ordered_to_be_billed_data(args):
def get_ordered_to_be_billed_data(args, filters=None):
doctype, party = args.get("doctype"), args.get("party")
child_tab = doctype + " Item"
precision = (
@@ -18,47 +19,57 @@ def get_ordered_to_be_billed_data(args):
or 2
)
project_field = get_project_field(doctype, party)
doctype = frappe.qb.DocType(doctype)
child_doctype = frappe.qb.DocType(child_tab)
return frappe.db.sql(
"""
Select
`{parent_tab}`.name, `{parent_tab}`.{date_field},
`{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
`{child_tab}`.item_code,
`{child_tab}`.base_amount,
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
(`{child_tab}`.base_amount -
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
`{child_tab}`.item_name, `{child_tab}`.description,
{project_field}, `{parent_tab}`.company
from
`{parent_tab}`, `{child_tab}`
where
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
and `{parent_tab}`.status not in ('Closed', 'Completed')
and `{child_tab}`.amount > 0
and (`{child_tab}`.base_amount -
round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
order by
`{parent_tab}`.{order} {order_by}
""".format(
parent_tab="tab" + doctype,
child_tab="tab" + child_tab,
precision=precision,
party=party,
date_field=args.get("date"),
project_field=project_field,
order=args.get("order"),
order_by=args.get("order_by"),
docname = filters.get(args.get("reference_field"), None)
project_field = get_project_field(doctype, child_doctype, party)
query = (
frappe.qb.from_(doctype)
.inner_join(child_doctype)
.on(doctype.name == child_doctype.parent)
.select(
doctype.name,
doctype[args.get("date")].as_("date"),
doctype[party],
doctype[party + "_name"],
child_doctype.item_code,
child_doctype.base_amount.as_("amount"),
(child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1)).as_("billed_amount"),
(child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0)).as_("returned_amount"),
(
child_doctype.base_amount
- (child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1))
- (child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0))
).as_("pending_amount"),
child_doctype.item_name,
child_doctype.description,
project_field,
)
.where(
(doctype.docstatus == 1)
& (doctype.status.notin(["Closed", "Completed"]))
& (doctype.company == filters.get("company"))
& (doctype.posting_date <= filters.get("posting_date"))
& (child_doctype.amount > 0)
& (
child_doctype.base_amount
- Round(child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1), precision)
- (child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0))
)
> 0
)
.orderby(doctype[args.get("order")], order=args.get("order_by"))
)
if docname:
query = query.where(doctype.name == docname)
def get_project_field(doctype, party):
return query.run(as_dict=True)
def get_project_field(doctype, child_doctype, party):
if party == "supplier":
doctype = doctype + " Item"
return "`tab%s`.project" % (doctype)
return child_doctype.project
return doctype.project

View File

@@ -2,5 +2,27 @@
// For license information, please see license.txt
frappe.query_reports["Received Items To Be Billed"] = {
filters: [],
filters: [
{
label: __("Company"),
fieldname: "company",
fieldtype: "Link",
options: "Company",
reqd: 1,
default: frappe.defaults.get_default("Company"),
},
{
label: __("As on Date"),
fieldname: "posting_date",
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.get_today(),
},
{
label: __("Purchase Receipt"),
fieldname: "purchase_receipt",
fieldtype: "Link",
options: "Purchase Receipt",
},
],
};

View File

@@ -3,6 +3,7 @@
from frappe import _
from pypika import Order
from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data
@@ -10,7 +11,7 @@ from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_d
def execute(filters=None):
columns = get_column()
args = get_args()
data = get_ordered_to_be_billed_data(args)
data = get_ordered_to_be_billed_data(args, filters)
return columns, data
@@ -76,13 +77,6 @@ def get_column():
"options": "Project",
"width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120,
},
]
@@ -92,5 +86,6 @@ def get_args():
"party": "supplier",
"date": "posting_date",
"order": "name",
"order_by": "desc",
"order_by": Order.desc,
"reference_field": "purchase_receipt",
}

View File

@@ -9,8 +9,8 @@ import frappe
import frappe.defaults
from frappe import _, qb, throw
from frappe.model.meta import get_field_precision
from frappe.query_builder import AliasedQuery, Criterion, Table
from frappe.query_builder.functions import Count, Sum
from frappe.query_builder import AliasedQuery, Case, Criterion, Table
from frappe.query_builder.functions import Count, Max, Sum
from frappe.query_builder.utils import DocType
from frappe.utils import (
add_days,
@@ -1417,7 +1417,7 @@ def repost_gle_for_stock_vouchers(
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers, company=company)
if repost_doc and repost_doc.gl_reposting_index:
# Restore progress
stock_vouchers = stock_vouchers[cint(repost_doc.gl_reposting_index) :]
@@ -1470,7 +1470,9 @@ def _delete_accounting_ledger_entries(voucher_type, voucher_no):
_delete_pl_entries(voucher_type, voucher_no)
def sort_stock_vouchers_by_posting_date(stock_vouchers: list[tuple[str, str]]) -> list[tuple[str, str]]:
def sort_stock_vouchers_by_posting_date(
stock_vouchers: list[tuple[str, str]], company=None
) -> list[tuple[str, str]]:
sle = frappe.qb.DocType("Stock Ledger Entry")
voucher_nos = [v[1] for v in stock_vouchers]
@@ -1481,7 +1483,12 @@ def sort_stock_vouchers_by_posting_date(stock_vouchers: list[tuple[str, str]]) -
.groupby(sle.voucher_type, sle.voucher_no)
.orderby(sle.posting_datetime)
.orderby(sle.creation)
).run(as_dict=True)
)
if company:
sles = sles.where(sle.company == company)
sles = sles.run(as_dict=True)
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
unknown_vouchers = set(stock_vouchers) - set(sorted_vouchers)
@@ -1967,6 +1974,15 @@ class QueryPaymentLedger:
.select(
ple.against_voucher_no.as_("voucher_no"),
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
Max(
Case().when(
(
(ple.voucher_no == ple.against_voucher_no)
& (ple.voucher_type == ple.against_voucher_type)
),
(ple.posting_date),
)
).as_("invoice_date"),
)
.where(ple.delinked == 0)
.where(Criterion.all(filter_on_against_voucher_no))
@@ -1974,7 +1990,7 @@ class QueryPaymentLedger:
.where(Criterion.all(self.dimensions_filter))
.where(Criterion.all(self.voucher_posting_date))
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
.orderby(ple.posting_date, ple.voucher_no)
.orderby(ple.invoice_date, ple.voucher_no)
.having(qb.Field("amount_in_account_currency") > 0)
.limit(self.limit)
.run()

View File

@@ -93,7 +93,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Accounts Payable",
"link_count": 0,
"link_to": "Accounts Payable",
@@ -103,7 +103,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Accounts Payable Summary",
"link_count": 0,
"link_to": "Accounts Payable Summary",
@@ -113,7 +113,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Purchase Register",
"link_count": 0,
"link_to": "Purchase Register",
@@ -123,7 +123,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Item-wise Purchase Register",
"link_count": 0,
"link_to": "Item-wise Purchase Register",
@@ -133,7 +133,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Purchase Order Analysis",
"link_count": 0,
"link_to": "Purchase Order Analysis",
@@ -143,7 +143,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Received Items To Be Billed",
"link_count": 0,
"link_to": "Received Items To Be Billed",
@@ -153,7 +153,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Supplier Ledger Summary",
"link_count": 0,
"link_to": "Supplier Ledger Summary",

View File

@@ -225,7 +225,7 @@
{
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
"label": "Net Purchase Amount",
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
"options": "Company:company:default_currency"
},
@@ -592,7 +592,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2025-02-11 16:01:56.140904",
"modified": "2025-02-20 14:09:05.421913",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -143,14 +143,19 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
}
}
set_consumed_stock_items_tagged_to_wip_composite_asset(asset) {
set_consumed_stock_items_tagged_to_wip_composite_asset(target_asset) {
var me = this;
if (asset) {
if (target_asset) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset",
args: {
asset: asset,
params: {
target_asset: target_asset,
finance_book: me.frm.doc.finance_book,
posting_date: me.frm.doc.posting_date,
posting_time: me.frm.doc.posting_time,
},
},
callback: function (r) {
if (!r.exc && r.message) {

View File

@@ -123,6 +123,7 @@ class AssetCapitalization(StockController):
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
self.make_gl_entries()
self.repost_future_sle_and_gle()
self.update_target_asset()
def on_cancel(self):
@@ -136,6 +137,7 @@ class AssetCapitalization(StockController):
)
self.update_stock_ledger()
self.make_gl_entries()
self.repost_future_sle_and_gle()
self.restore_consumed_asset_items()
def set_title(self):
@@ -856,7 +858,10 @@ def get_service_item_details(args):
@frappe.whitelist()
def get_items_tagged_to_wip_composite_asset(asset):
def get_items_tagged_to_wip_composite_asset(params):
if isinstance(params, str):
params = json.loads(params)
fields = [
"item_code",
"item_name",
@@ -871,25 +876,66 @@ def get_items_tagged_to_wip_composite_asset(asset):
"amount",
"is_fixed_asset",
"parent",
"name",
]
pr_items = frappe.get_all(
"Purchase Receipt Item", filters={"wip_composite_asset": asset, "docstatus": 1}, fields=fields
"Purchase Receipt Item",
filters={"wip_composite_asset": params.get("target_asset"), "docstatus": 1},
fields=fields,
)
stock_items = []
asset_items = []
for d in pr_items:
if not d.is_fixed_asset:
stock_items.append(frappe._dict(d))
stock_item = process_stock_item(d)
if stock_item:
stock_items.append(stock_item)
else:
asset_details = frappe.db.get_value(
"Asset",
{"item_code": d.item_code, "purchase_receipt": d.parent},
["name as asset", "asset_name"],
as_dict=1,
)
d.update(asset_details)
asset_items.append(frappe._dict(d))
asset_item = process_fixed_asset(d)
if asset_item:
asset_items.append(asset_item)
return stock_items, asset_items
def process_stock_item(d):
stock_capitalized = frappe.db.exists(
"Asset Capitalization Stock Item",
{
"purchase_receipt_item": d.name,
"parentfield": "stock_items",
"parenttype": "Asset Capitalization",
"docstatus": 1,
},
)
if stock_capitalized:
return None
stock_item_data = frappe._dict(d)
stock_item_data.purchase_receipt_item = d.name
return stock_item_data
def process_fixed_asset(d):
asset_details = frappe.db.get_value(
"Asset",
{
"item_code": d.item_code,
"purchase_receipt": d.parent,
"status": ("not in", ["Draft", "Scrapped", "Sold", "Capitalized"]),
},
["name as asset", "asset_name", "company"],
as_dict=1,
)
if asset_details:
asset_details.update(d)
asset_details.update(get_consumed_asset_details(asset_details))
d.update(asset_details)
return frappe._dict(d)
return None

View File

@@ -10,12 +10,13 @@
"column_break_3",
"warehouse",
"section_break_6",
"purchase_receipt_item",
"stock_qty",
"stock_uom",
"actual_qty",
"column_break_9",
"valuation_rate",
"amount",
"stock_uom",
"batch_and_serial_no_section",
"serial_and_batch_bundle",
"use_serial_batch_fields",
@@ -53,14 +54,14 @@
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"label": "Qty and Rate"
"label": "Purchase Details"
},
{
"columns": 1,
"fieldname": "stock_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty",
"label": "Quantity",
"non_negative": 1
},
{
@@ -172,18 +173,26 @@
{
"fieldname": "column_break_mbuv",
"fieldtype": "Column Break"
},
{
"fieldname": "purchase_receipt_item",
"fieldtype": "Data",
"hidden": 1,
"label": "Purchase Receipt Item"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-06-26 17:06:22.564438",
"modified": "2025-03-05 12:46:01.074742",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Stock Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1

View File

@@ -23,6 +23,7 @@ class AssetCapitalizationStockItem(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
purchase_receipt_item: DF.Data | None
serial_and_batch_bundle: DF.Link | None
serial_no: DF.Text | None
stock_qty: DF.Float

View File

@@ -404,7 +404,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
);
}
} else {
if (!doc.items.every((item) => item.qty == item.sco_qty)) {
if (!doc.items.every((item) => item.qty == item.subcontracted_quantity)) {
this.frm.add_custom_button(
__("Subcontracting Order"),
() => {

View File

@@ -898,7 +898,7 @@ def is_po_fully_subcontracted(po_name):
query = (
frappe.qb.from_(table)
.select(table.name)
.where((table.parent == po_name) & (table.qty != table.sco_qty))
.where((table.parent == po_name) & (table.qty != table.subcontracted_quantity))
)
return not query.run(as_dict=True)
@@ -945,7 +945,7 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
"material_request_item": "material_request_item",
},
"field_no_map": ["qty", "fg_item_qty", "amount"],
"condition": lambda item: item.qty != item.sco_qty,
"condition": lambda item: item.qty != item.subcontracted_quantity,
},
},
target_doc,

View File

@@ -920,6 +920,7 @@ class TestPurchaseOrder(FrappeTestCase):
automatically_fetch_payment_terms(enable=0)
def test_internal_transfer_flow(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
make_inter_company_purchase_invoice,
)
@@ -935,8 +936,16 @@ class TestPurchaseOrder(FrappeTestCase):
prepare_data_for_internal_transfer()
supplier = "_Test Internal Supplier 2"
create_cost_center(
cost_center_name="_Test Cost Center for perpetual inventory Account",
company="_Test Company with perpetual inventory",
)
mr = make_material_request(
qty=2, company="_Test Company with perpetual inventory", warehouse="Stores - TCP1"
qty=2,
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
cost_center="_Test Cost Center for perpetual inventory Account - TCP1",
)
po = create_purchase_order(
@@ -1076,9 +1085,9 @@ class TestPurchaseOrder(FrappeTestCase):
# Test - 2: Subcontracted Quantity for the PO Items of each line item should be updated accordingly
po.reload()
self.assertEqual(po.items[0].sco_qty, 5)
self.assertEqual(po.items[1].sco_qty, 0)
self.assertEqual(po.items[2].sco_qty, 12.5)
self.assertEqual(po.items[0].subcontracted_quantity, 5)
self.assertEqual(po.items[1].subcontracted_quantity, 0)
self.assertEqual(po.items[2].subcontracted_quantity, 12.5)
# Test - 3: Amount for both FG Item and its Service Item should be updated correctly based on change in Quantity
self.assertEqual(sco.items[0].amount, 2000)
@@ -1114,10 +1123,10 @@ class TestPurchaseOrder(FrappeTestCase):
# Test - 8: Subcontracted Quantity for each PO Item should be subtracted if SCO gets cancelled
po.reload()
self.assertEqual(po.items[2].sco_qty, 25)
self.assertEqual(po.items[2].subcontracted_quantity, 25)
sco.cancel()
po.reload()
self.assertEqual(po.items[2].sco_qty, 12.5)
self.assertEqual(po.items[2].subcontracted_quantity, 12.5)
sco = make_subcontracting_order(po.name)
sco.save()

View File

@@ -26,7 +26,7 @@
"quantity_and_rate",
"qty",
"stock_uom",
"sco_qty",
"subcontracted_quantity",
"col_break2",
"uom",
"conversion_factor",
@@ -913,7 +913,8 @@
},
{
"allow_on_submit": 1,
"fieldname": "sco_qty",
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
"fieldname": "subcontracted_quantity",
"fieldtype": "Float",
"label": "Subcontracted Quantity",
"no_copy": 1,
@@ -921,11 +922,12 @@
"read_only": 1
}
],
"grid_page_length": 50,
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-02-18 12:35:04.432636",
"modified": "2025-03-13 17:27:43.468602",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
@@ -933,9 +935,10 @@
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "item_name",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -80,10 +80,10 @@ class PurchaseOrderItem(Document):
sales_order_item: DF.Data | None
sales_order_packed_item: DF.Data | None
schedule_date: DF.Date
sco_qty: DF.Float
stock_qty: DF.Float
stock_uom: DF.Link
stock_uom_rate: DF.Currency
subcontracted_quantity: DF.Float
supplier_part_no: DF.Data | None
supplier_quotation: DF.Link | None
supplier_quotation_item: DF.Link | None

View File

@@ -19,6 +19,10 @@ frappe.query_reports["Purchase Order Analysis"] = {
width: "80",
reqd: 1,
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
on_change: (report) => {
report.set_filter_value("name", []);
report.refresh();
},
},
{
fieldname: "to_date",
@@ -27,6 +31,10 @@ frappe.query_reports["Purchase Order Analysis"] = {
width: "80",
reqd: 1,
default: frappe.datetime.get_today(),
on_change: (report) => {
report.set_filter_value("name", []);
report.refresh();
},
},
{
fieldname: "project",
@@ -38,13 +46,17 @@ frappe.query_reports["Purchase Order Analysis"] = {
{
fieldname: "name",
label: __("Purchase Order"),
fieldtype: "Link",
fieldtype: "MultiSelectList",
width: "80",
options: "Purchase Order",
get_query: () => {
return {
filters: { docstatus: 1 },
};
get_data: function (txt) {
let filters = { docstatus: 1 };
const from_date = frappe.query_report.get_filter_value("from_date");
const to_date = frappe.query_report.get_filter_value("to_date");
if (from_date && to_date) filters["transaction_date"] = ["between", [from_date, to_date]];
return frappe.db.get_link_options("Purchase Order", txt, filters);
},
},
{
@@ -52,9 +64,16 @@ frappe.query_reports["Purchase Order Analysis"] = {
label: __("Status"),
fieldtype: "MultiSelectList",
width: "80",
options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"],
options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed", "Closed"],
get_data: function (txt) {
let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"];
let status = [
"To Pay",
"To Bill",
"To Receive",
"To Receive and Bill",
"Completed",
"Closed",
];
let options = [];
for (let option of status) {
options.push({

View File

@@ -70,14 +70,16 @@ def get_data(filters):
po.company,
po_item.name,
)
.where((po_item.parent == po.name) & (po.status.notin(("Stopped", "Closed"))) & (po.docstatus == 1))
.where((po_item.parent == po.name) & (po.status.notin(("Stopped", "On Hold"))) & (po.docstatus == 1))
.groupby(po_item.name)
.orderby(po.transaction_date)
)
for field in ("company", "name"):
if filters.get(field):
query = query.where(po[field] == filters.get(field))
if filters.get("company"):
query = query.where(po.company == filters.get("company"))
if filters.get("name"):
query = query.where(po.name.isin(filters.get("name")))
if filters.get("from_date") and filters.get("to_date"):
query = query.where(po.transaction_date.between(filters.get("from_date"), filters.get("to_date")))

View File

@@ -194,6 +194,14 @@ class AccountsController(TransactionBase):
self.set_incoming_rate()
self.init_internal_values()
# Need to set taxes based on taxes_and_charges template
# before calculating taxes and totals
if self.meta.get_field("taxes_and_charges"):
self.validate_enabled_taxes_and_charges()
self.validate_tax_account_company()
self.set_taxes_and_charges()
if self.meta.get_field("currency"):
self.calculate_taxes_and_totals()
@@ -204,10 +212,6 @@ class AccountsController(TransactionBase):
self.validate_all_documents_schedule()
if self.meta.get_field("taxes_and_charges"):
self.validate_enabled_taxes_and_charges()
self.validate_tax_account_company()
self.validate_party()
self.validate_currency()
self.validate_party_account_currency()
@@ -252,8 +256,6 @@ class AccountsController(TransactionBase):
self.validate_deferred_income_expense_account()
self.set_inter_company_account()
self.set_taxes_and_charges()
if self.doctype == "Purchase Invoice":
self.calculate_paid_amount()
# apply tax withholding only if checked and applicable
@@ -269,6 +271,7 @@ class AccountsController(TransactionBase):
self.set_total_in_words()
self.set_default_letter_head()
self.validate_company_in_accounting_dimension()
self.validate_party_address_and_contact()
def set_default_letter_head(self):
if hasattr(self, "letter_head") and not self.letter_head:
@@ -417,7 +420,7 @@ class AccountsController(TransactionBase):
.where(doc_field.fieldname == "company")
).run(as_list=True)
dimension_list = sum(dimension_list, ["Project"])
dimension_list = sum(dimension_list, ["Project", "Cost Center"])
self.validate_company(dimension_list)
for child in self.get_all_children() or []:
@@ -439,6 +442,46 @@ class AccountsController(TransactionBase):
)
)
def validate_party_address_and_contact(self):
party_type, party = self.get_party()
if not (party_type and party):
return
if party_type == "Customer":
billing_address, shipping_address = (
self.get("customer_address"),
self.get("shipping_address_name"),
)
self.validate_party_address(party, party_type, billing_address, shipping_address)
elif party_type == "Supplier":
billing_address = self.get("supplier_address")
self.validate_party_address(party, party_type, billing_address)
self.validate_party_contact(party, party_type)
def validate_party_address(self, party, party_type, billing_address, shipping_address=None):
if billing_address or shipping_address:
party_address = frappe.get_all(
"Dynamic Link",
{"link_doctype": party_type, "link_name": party, "parenttype": "Address"},
pluck="parent",
)
if billing_address and billing_address not in party_address:
frappe.throw(_("Billing Address does not belong to the {0}").format(party))
elif shipping_address and shipping_address not in party_address:
frappe.throw(_("Shipping Address does not belong to the {0}").format(party))
def validate_party_contact(self, party, party_type):
if self.get("contact_person"):
contact = frappe.get_all(
"Dynamic Link",
{"link_doctype": party_type, "link_name": party, "parenttype": "Contact"},
pluck="parent",
)
if self.contact_person and self.contact_person not in contact:
frappe.throw(_("Contact Person does not belong to the {0}").format(party))
def validate_return_against_account(self):
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
@@ -821,11 +864,15 @@ class AccountsController(TransactionBase):
and item.get("use_serial_batch_fields")
)
):
if fieldname == "batch_no" and not item.batch_no and not item.is_free_item:
item.set("rate", ret.get("rate"))
item.set("price_list_rate", ret.get("price_list_rate"))
item.set(fieldname, value)
if fieldname == "batch_no" and item.batch_no and not item.is_free_item:
if ret.get("rate"):
item.set("rate", ret.get("rate"))
if not item.get("price_list_rate") and ret.get("price_list_rate"):
item.set("price_list_rate", ret.get("price_list_rate"))
elif fieldname in ["cost_center", "conversion_factor"] and not item.get(
fieldname
):
@@ -1113,20 +1160,19 @@ class AccountsController(TransactionBase):
)
# Update details in transaction currency
gl_dict.update(
{
"transaction_currency": self.get("currency") or self.company_currency,
"transaction_exchange_rate": item.get("exchange_rate", 1)
if self.doctype == "Journal Entry" and item
else self.get("conversion_rate", 1),
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
account_currency, gl_dict, "debit"
),
"credit_in_transaction_currency": self.get_value_in_transaction_currency(
account_currency, gl_dict, "credit"
),
}
)
if self.doctype not in ["Purchase Invoice", "Sales Invoice", "Journal Entry", "Payment Entry"]:
gl_dict.update(
{
"transaction_currency": self.get("currency") or self.company_currency,
"transaction_exchange_rate": self.get("conversion_rate", 1),
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
account_currency, gl_dict, "debit"
),
"credit_in_transaction_currency": self.get_value_in_transaction_currency(
account_currency, gl_dict, "credit"
),
}
)
if not args.get("against_voucher_type") and self.get("against_voucher_type"):
gl_dict.update({"against_voucher_type": self.get("against_voucher_type")})
@@ -2318,6 +2364,9 @@ class AccountsController(TransactionBase):
base_grand_total * flt(d.invoice_portion) / 100, d.precision("base_payment_amount")
)
d.outstanding = d.payment_amount
d.base_outstanding = flt(
d.payment_amount * self.get("conversion_rate"), d.precision("base_outstanding")
)
elif not d.invoice_portion:
d.base_payment_amount = flt(
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
@@ -2644,12 +2693,17 @@ class AccountsController(TransactionBase):
default_currency = erpnext.get_company_currency(self.company)
if not default_currency:
throw(_("Please enter default currency in Company Master"))
if (
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
or not self.conversion_rate
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
):
throw(_("Conversion rate cannot be 0 or 1"))
if not self.conversion_rate:
throw(_("Conversion rate cannot be 0"))
if self.currency == default_currency and flt(self.conversion_rate) != 1.00:
throw(_("Conversion rate must be 1.00 if document currency is same as company currency"))
if self.currency != default_currency and flt(self.conversion_rate) == 1.00:
frappe.msgprint(
_("Conversion rate is 1.00, but document currency is different from company currency")
)
def check_finance_books(self, item, asset):
if (
@@ -2752,6 +2806,11 @@ class AccountsController(TransactionBase):
elif self.doctype == "Payment Entry":
self.make_advance_payment_ledger_for_payment()
def set_transaction_currency_and_rate_in_gl_map(self, gl_entries):
for x in gl_entries:
x["transaction_currency"] = self.currency
x["transaction_exchange_rate"] = self.get("conversion_rate") or 1
@frappe.whitelist()
def get_tax_rate(account_head):
@@ -3281,6 +3340,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
"posting_date": parent_doc.transaction_date,
"tax_category": parent_doc.get("tax_category"),
"company": parent_doc.get("company"),
"base_net_rate": item.get("base_net_rate"),
}
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
@@ -3655,6 +3715,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if d.get("schedule_date") and parent_doctype == "Purchase Order":
child_item.schedule_date = d.get("schedule_date")
if d.get("bom_no") and parent_doctype == "Sales Order":
child_item.bom_no = d.get("bom_no")
if flt(child_item.price_list_rate):
if flt(child_item.rate) > flt(child_item.price_list_rate):
# if rate is greater than price_list_rate, set margin

View File

@@ -333,7 +333,7 @@ class BuyingController(SubcontractingController):
net_rate
+ item.item_tax_amount
+ flt(item.landed_cost_voucher_amount)
+ flt(item.get("rate_difference_with_purchase_invoice"))
+ flt(item.get("amount_difference_with_purchase_invoice"))
) / qty_in_stock_uom
else:
item.valuation_rate = 0.0

View File

@@ -837,6 +837,7 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
"posting_date": valid_from,
"tax_category": filters.get("tax_category"),
"company": company,
"base_net_rate": filters.get("base_net_rate"),
}
taxes = _get_item_tax_template(args, taxes, for_validate=True)

View File

@@ -25,9 +25,6 @@ def validate_return(doc):
if doc.return_against:
validate_return_against(doc)
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and not doc.update_stock:
return
validate_returned_items(doc)
@@ -118,7 +115,7 @@ def validate_returned_items(doc):
elif doc.doctype == "Delivery Note":
key = (d.item_code, d.get("dn_detail"))
if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0):
if d.item_code and (flt(d.qty) <= 0 or flt(d.get("received_qty")) <= 0):
if key not in valid_items:
frappe.msgprint(
_("Row # {0}: Returned Item {1} does not exist in {2} {3}").format(
@@ -160,6 +157,9 @@ def validate_returned_items(doc):
def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
fields = ["stock_qty"]
if (doc.doctype == "Purchase Invoice" or doc.doctype == "Sales Invoice") and not doc.update_stock:
fields = ["qty"]
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
if not args.get("return_qty_from_rejected_warehouse"):
fields.extend(["received_qty", "rejected_qty"])
@@ -169,13 +169,16 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
already_returned_data = already_returned_items.get(key) or {}
company_currency = erpnext.get_company_currency(doc.company)
stock_qty_precision = get_field_precision(
frappe.get_meta(doc.doctype + " Item").get_field("stock_qty"), company_currency
field_precision = get_field_precision(
frappe.get_meta(doc.doctype + " Item").get_field(
"stock_qty" if doc.get("update_stock", "") else "qty"
),
company_currency,
)
for column in fields:
returned_qty = (
flt(already_returned_data.get(column, 0), stock_qty_precision)
flt(already_returned_data.get(column, 0), field_precision)
if len(already_returned_data) > 0
else 0
)
@@ -190,17 +193,17 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
max_returnable_qty = flt(flt(reference_qty, stock_qty_precision) - returned_qty, stock_qty_precision)
max_returnable_qty = flt(flt(reference_qty, field_precision) - returned_qty, field_precision)
label = column.replace("_", " ").title()
if reference_qty:
if flt(args.get(column)) > 0:
frappe.throw(_("{0} must be negative in return document").format(label))
elif returned_qty >= reference_qty and args.get(column):
elif returned_qty >= reference_qty and args.get(column) >= 0:
frappe.throw(
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
)
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
elif abs(flt(current_stock_qty, field_precision)) > max_returnable_qty:
frappe.throw(
_("Row # {0}: Cannot return more than {1} for Item {2}").format(
args.idx, max_returnable_qty, args.item_code

View File

@@ -1234,7 +1234,7 @@ class StockController(AccountsController):
child_tab.item_code,
child_tab.qty,
)
.where(parent_tab.docstatus < 2)
.where(parent_tab.docstatus == 1)
)
if self.doctype == "Purchase Invoice":

View File

@@ -104,18 +104,18 @@ class SubcontractingController(StockController):
)
if (
self.doctype == "Subcontracting Order" and not item.sc_conversion_factor
self.doctype == "Subcontracting Order" and not item.subcontracting_conversion_factor
): # this condition will only be true if user has recently updated from develop branch
service_item_qty = frappe.get_value(
"Subcontracting Order Service Item",
filters={"purchase_order_item": item.purchase_order_item, "parent": self.name},
fieldname=["qty"],
)
item.sc_conversion_factor = service_item_qty / item.qty
item.subcontracting_conversion_factor = service_item_qty / item.qty
if self.doctype not in "Subcontracting Receipt" and item.qty > flt(
get_pending_sco_qty(self.purchase_order).get(item.purchase_order_item)
/ item.sc_conversion_factor,
get_pending_subcontracted_quantity(self.purchase_order).get(item.purchase_order_item)
/ item.subcontracting_conversion_factor,
frappe.get_precision("Purchase Order Item", "qty"),
):
frappe.throw(
@@ -545,7 +545,11 @@ class SubcontractingController(StockController):
def __get_batch_nos_for_bundle(self, qty, key):
available_batches = defaultdict(float)
precision = frappe.get_precision("Subcontracting Receipt Supplied Item", "consumed_qty")
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
if flt(batch_qty, precision) <= 0:
continue
qty_to_consumed = 0
if qty > 0:
if batch_qty >= qty:
@@ -1132,10 +1136,14 @@ def get_item_details(items):
return item_details
def get_pending_sco_qty(po_name):
def get_pending_subcontracted_quantity(po_name):
table = frappe.qb.DocType("Purchase Order Item")
query = frappe.qb.from_(table).select(table.name, table.qty, table.sco_qty).where(table.parent == po_name)
return {item.name: item.qty - item.sco_qty for item in query.run(as_dict=True)}
query = (
frappe.qb.from_(table)
.select(table.name, table.qty, table.subcontracted_quantity)
.where(table.parent == po_name)
)
return {item.name: item.qty - item.subcontracted_quantity for item in query.run(as_dict=True)}
@frappe.whitelist()

View File

@@ -931,6 +931,35 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
@change_settings("Accounts Settings", {"add_taxes_from_item_tax_template": 1})
def test_18_fetch_taxes_based_on_taxes_and_charges_template(self):
# Create a Sales Taxes and Charges Template
if not frappe.db.exists("Sales Taxes and Charges Template", "_Test Tax - _TC"):
doc = frappe.new_doc("Sales Taxes and Charges Template")
doc.company = self.company
doc.title = "_Test Tax"
doc.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "Sales Expenses - _TC",
"description": "Test taxes",
"rate": 9,
},
)
doc.insert()
# Create a Sales Invoice
sinv = frappe.new_doc("Sales Invoice")
sinv.customer = self.customer
sinv.company = self.company
sinv.currency = "INR"
sinv.taxes_and_charges = "_Test Tax - _TC"
sinv.append("items", {"item_code": "_Test Item", "qty": 1, "rate": 50})
sinv.insert()
self.assertEqual(sinv.total_taxes_and_charges, 4.5)
def test_20_journal_against_sales_invoice(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
@@ -2146,3 +2175,59 @@ class TestAccountsController(FrappeTestCase):
si_1 = create_sales_invoice(do_not_submit=True)
si_1.items[0].project = project.name
self.assertRaises(frappe.ValidationError, si_1.save)
def test_party_billing_and_shipping_address(self):
from erpnext.crm.doctype.prospect.test_prospect import make_address
customer_billing = make_address(address_title="Customer")
customer_billing.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
customer_billing.save()
supplier_billing = make_address(address_title="Supplier", address_line1="2", city="Ahmedabad")
supplier_billing.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
supplier_billing.save()
customer_shipping = make_address(
address_title="Customer", address_type="Shipping", address_line1="10"
)
customer_shipping.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
customer_shipping.save()
supplier_shipping = make_address(
address_title="Supplier", address_type="Shipping", address_line1="20", city="Ahmedabad"
)
supplier_shipping.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
supplier_shipping.save()
si = create_sales_invoice(do_not_save=True)
si.customer_address = supplier_billing.name
self.assertRaises(frappe.ValidationError, si.save)
si.customer_address = customer_billing.name
si.save()
si.shipping_address_name = supplier_shipping.name
self.assertRaises(frappe.ValidationError, si.save)
si.shipping_address_name = customer_shipping.name
si.reload()
si.save()
pi = make_purchase_invoice(do_not_save=True)
pi.supplier_address = customer_shipping.name
self.assertRaises(frappe.ValidationError, pi.save)
pi.supplier_address = supplier_shipping.name
pi.save()
def test_party_contact(self):
from frappe.contacts.doctype.contact.test_contact import create_contact
customer_contact = create_contact(name="Customer", salutation="Mr", save=False)
customer_contact.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
customer_contact.save()
supplier_contact = create_contact(name="Supplier", salutation="Mr", save=False)
supplier_contact.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
supplier_contact.save()
si = create_sales_invoice(do_not_save=True)
si.contact_person = supplier_contact.name
self.assertRaises(frappe.ValidationError, si.save)
si.contact_person = customer_contact.name
si.save()

View File

@@ -69,7 +69,7 @@ def get_transaction_list(
filters=None,
limit_start=0,
limit_page_length=20,
order_by="modified",
order_by="creation desc",
custom=False,
):
user = frappe.session.user
@@ -115,7 +115,7 @@ def get_transaction_list(
limit_page_length,
fields="name",
ignore_permissions=ignore_permissions,
order_by="modified desc",
order_by=order_by,
)
if custom:

View File

@@ -1,6 +1,7 @@
{
"actions": [],
"allow_events_in_timeline": 1,
"allow_rename": 1,
"autoname": "field:company_name",
"creation": "2021-08-19 00:21:06.995448",
"doctype": "DocType",
@@ -216,9 +217,10 @@
"options": "CRM Note"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-10-13 12:29:33.674561",
"modified": "2025-03-06 11:31:49.083983",
"modified_by": "Administrator",
"module": "CRM",
"name": "Prospect",
@@ -258,9 +260,9 @@
"share": 1
}
],
"sort_field": "modified",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "company_name",
"track_changes": 1
}
}

View File

@@ -4,7 +4,7 @@ app_publisher = "Frappe Technologies Pvt. Ltd."
app_description = """ERP made simple"""
app_icon = "fa fa-th"
app_color = "#e74c3c"
app_email = "info@erpnext.com"
app_email = "hello@frappe.io"
app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
app_logo_url = "/assets/erpnext/images/erpnext-logo.svg"
@@ -484,7 +484,7 @@ email_brand_image = "assets/erpnext/images/erpnext-logo.jpg"
default_mail_footer = """
<span>
Sent via
<a class="text-muted" href="https://erpnext.com?source=via_email_footer" target="_blank">
<a class="text-muted" href="https://frappe.io/erpnext?source=via_email_footer" target="_blank">
ERPNext
</a>
</span>

View File

@@ -440,12 +440,12 @@ class BOM(WebsiteGenerator):
"description": item and args["description"] or "",
"image": item and args["image"] or "",
"stock_uom": item and args["stock_uom"] or "",
"uom": item and args["stock_uom"] or "",
"conversion_factor": 1,
"uom": args["uom"] if args.get("uom") else item and args["stock_uom"] or "",
"conversion_factor": args["conversion_factor"] if args.get("conversion_factor") else 1,
"bom_no": args["bom_no"],
"rate": rate,
"qty": args.get("qty") or args.get("stock_qty") or 1,
"stock_qty": args.get("qty") or args.get("stock_qty") or 1,
"stock_qty": args.get("stock_qty") or args.get("qty") or 1,
"base_rate": flt(rate) * (flt(self.conversion_rate) or 1),
"include_item_in_manufacturing": cint(args.get("transfer_for_manufacture")),
"sourced_by_supplier": args.get("sourced_by_supplier", 0),
@@ -1371,7 +1371,7 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None):
},
)
def get_max_op_qty():
def get_max_operation_quantity():
from frappe.query_builder.functions import Sum
table = frappe.qb.DocType("Job Card")
@@ -1387,7 +1387,7 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None):
)
return min([d.qty for d in query.run(as_dict=True)], default=0)
def get_utilised_cc():
def get_utilised_corrective_cost():
from frappe.query_builder.functions import Sum
table = frappe.qb.DocType("Stock Entry")
@@ -1417,15 +1417,15 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None):
)
)
):
max_qty = get_max_op_qty() - work_order.produced_qty
remaining_cc = work_order.corrective_operation_cost - get_utilised_cc()
max_qty = get_max_operation_quantity() - work_order.produced_qty
remaining_corrective_cost = work_order.corrective_operation_cost - get_utilised_corrective_cost()
stock_entry.append(
"additional_costs",
{
"expense_account": expense_account,
"description": "Corrective Operation Cost",
"has_corrective_cost": 1,
"amount": remaining_cc / max_qty * flt(stock_entry.fg_completed_qty),
"amount": remaining_corrective_cost / max_qty * flt(stock_entry.fg_completed_qty),
},
)

View File

@@ -28,6 +28,8 @@ BOM_ITEM_FIELDS = [
"stock_uom",
"conversion_factor",
"do_not_explode",
"source_warehouse",
"allow_alternative_item",
]
@@ -291,7 +293,6 @@ class BOMCreator(Document):
"item": row.item_code,
"bom_type": "Production",
"quantity": row.qty,
"allow_alternative_item": 1,
"bom_creator": self.name,
"bom_creator_item": bom_creator_item,
}
@@ -315,7 +316,6 @@ class BOMCreator(Document):
item_args.update(
{
"bom_no": bom_no,
"allow_alternative_item": 1,
"allow_scrap_items": 1,
"include_item_in_manufacturing": 1,
}
@@ -428,6 +428,7 @@ def add_sub_assembly(**kwargs):
"do_not_explode": 1,
"is_expandable": 1,
"stock_uom": item_info.stock_uom,
"allow_alternative_item": kwargs.allow_alternative_item,
},
)

View File

@@ -15,6 +15,7 @@
"is_expandable",
"sourced_by_supplier",
"bom_created",
"allow_alternative_item",
"description_section",
"description",
"quantity_and_rate_section",
@@ -225,12 +226,18 @@
"label": "BOM Created",
"no_copy": 1,
"print_hide": 1
},
{
"default": "1",
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"label": "Allow Alternative Item"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-06-03 18:45:24.339532",
"modified": "2025-02-19 13:25:15.732496",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Creator Item",

View File

@@ -14,6 +14,7 @@ class BOMCreatorItem(Document):
if TYPE_CHECKING:
from frappe.types import DF
allow_alternative_item: DF.Check
amount: DF.Currency
base_amount: DF.Currency
base_rate: DF.Currency

View File

@@ -312,7 +312,7 @@ frappe.ui.form.on("Job Card", {
fieldtype: "Float",
label: __("Completed Quantity"),
fieldname: "qty",
default: frm.doc.for_quantity,
default: frm.doc.for_quantity - frm.doc.total_completed_qty,
},
(data) => {
frm.events.complete_job(frm, "Complete", data.qty);

View File

@@ -86,7 +86,6 @@
"search_index": 1
},
{
"fetch_from": "work_order.bom_no",
"fieldname": "bom_no",
"fieldtype": "Link",
"label": "BOM No",
@@ -281,7 +280,7 @@
"fieldtype": "Column Break"
},
{
"fetch_from": "work_order.production_item",
"fetch_from": "bom_no.item",
"fieldname": "production_item",
"fieldtype": "Link",
"label": "Production Item",
@@ -511,7 +510,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2023-06-28 19:23:14.345214",
"modified": "2025-03-17 15:55:11.143456",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",

View File

@@ -64,9 +64,7 @@ class JobCard(Document):
from erpnext.manufacturing.doctype.job_card_scheduled_time.job_card_scheduled_time import (
JobCardScheduledTime,
)
from erpnext.manufacturing.doctype.job_card_scrap_item.job_card_scrap_item import (
JobCardScrapItem,
)
from erpnext.manufacturing.doctype.job_card_scrap_item.job_card_scrap_item import JobCardScrapItem
from erpnext.manufacturing.doctype.job_card_time_log.job_card_time_log import JobCardTimeLog
actual_end_date: DF.Datetime | None
@@ -91,7 +89,7 @@ class JobCard(Document):
naming_series: DF.Literal["PO-JOB.#####"]
operation: DF.Link
operation_id: DF.Data | None
operation_row_number: DF.Literal
operation_row_number: DF.Literal[None]
posting_date: DF.Date | None
process_loss_qty: DF.Float
production_item: DF.Link | None

View File

@@ -363,8 +363,8 @@ class ProductionPlan(Document):
for item in items:
item.pending_qty = (
flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0) * item.conversion_factor
)
flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0)
) * item.conversion_factor
pi = frappe.qb.DocType("Packed Item")
@@ -425,6 +425,7 @@ class ProductionPlan(Document):
mr_item.item_code,
mr_item.warehouse,
mr_item.description,
mr_item.bom_no,
((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"),
)
.distinct()

View File

@@ -42,6 +42,7 @@ class TestWorkOrder(FrappeTestCase):
prepare_data_for_backflush_based_on_materials_transferred()
def tearDown(self):
frappe.local.future_sle = {}
frappe.db.rollback()
def check_planned_qty(self):
@@ -2601,6 +2602,109 @@ class TestWorkOrder(FrappeTestCase):
status = frappe.db.get_value("Serial No", row, "status")
self.assertEqual(status, "Consumed")
def test_work_order_valuation_auto_pick(self):
fg_item = "Test FG Item For Non Transfer Item Batch"
rm_item = "Test RM Item For Non Transfer Item Batch"
make_item(fg_item, {"is_stock_item": 1})
make_item(
rm_item,
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TST-BATCH-NTI-.###",
},
)
source_warehouse = "_Test Warehouse - _TC"
wip_warehouse = "Stores - _TC"
finished_goods_warehouse = create_warehouse("_Test Finished Goods Warehouse", company="_Test Company")
batches = make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse)
if not frappe.db.get_value("BOM", {"item": fg_item}):
make_bom(item=fg_item, raw_materials=[rm_item])
wo = make_wo_order_test_record(
item=fg_item,
qty=5,
source_warehouse=source_warehouse,
wip_warehouse=wip_warehouse,
fg_warehouse=finished_goods_warehouse,
)
stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 5))
stock_entry.items[0].batch_no = batches[1]
stock_entry.items[0].use_serial_batch_fields = 1
stock_entry.submit()
stock_entry.reload()
self.assertEqual(stock_entry.items[0].valuation_rate, 200)
original_value = frappe.db.get_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
)
original_based_on = frappe.db.get_single_value("Stock Settings", "pick_serial_and_batch_based_on")
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
frappe.db.set_single_value("Stock Settings", "pick_serial_and_batch_based_on", "Expiry")
stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 5))
stock_entry.items[0].use_serial_batch_fields = 1
stock_entry.submit()
stock_entry.reload()
batch_no = get_batch_from_bundle(stock_entry.items[0].serial_and_batch_bundle)
self.assertEqual(batch_no, batches[1])
self.assertEqual(stock_entry.items[0].valuation_rate, 200)
self.assertEqual(stock_entry.items[1].valuation_rate, 200)
frappe.db.set_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", original_value
)
frappe.db.set_single_value("Stock Settings", "pick_serial_and_batch_based_on", original_based_on)
def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
make_stock_entry as make_stock_entry_test_record,
)
batches = []
for qty, rate in ((5, 100), (5, 200)):
stock_entry = make_stock_entry_test_record(
item_code=rm_item,
target=source_warehouse,
qty=qty,
basic_rate=rate,
)
stock_entry.submit()
stock_entry.reload()
batch_no = get_batch_from_bundle(stock_entry.items[0].serial_and_batch_bundle)
batch_doc = frappe.get_doc("Batch", batch_no)
# keep early expiry date for the batch having rate 200
days = 10 if rate == 100 else 1
batch_doc.db_set("expiry_date", add_to_date(now(), days=days))
batches.append(batch_no)
stock_entry = make_stock_entry_test_record(
item_code=rm_item,
target=wip_warehouse,
qty=qty,
basic_rate=rate,
)
stock_entry.submit()
stock_entry.reload()
batch_no = get_batch_from_bundle(stock_entry.items[0].serial_and_batch_bundle)
batch_doc = frappe.get_doc("Batch", batch_no)
batch_doc.db_set("expiry_date", add_to_date(now(), days=10))
return batches
def make_operation(**kwargs):
kwargs = frappe._dict(kwargs)

View File

@@ -301,6 +301,12 @@ frappe.ui.form.on("Work Order", {
label: __("Sequence Id"),
read_only: 1,
},
{
fieldtype: "Link",
fieldname: "bom",
label: __("BOM"),
read_only: 1,
},
],
data: operations_data,
in_place_edit: true,
@@ -341,6 +347,7 @@ frappe.ui.form.on("Work Order", {
qty: pending_qty,
pending_qty: pending_qty,
sequence_id: data.sequence_id,
bom: data.bom,
});
}
}

View File

@@ -164,7 +164,7 @@ class WorkOrder(Document):
if self.source_warehouse:
self.set_warehouses()
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
validate_uom_is_integer(self, "stock_uom", ["required_qty"])
self.set_required_items(reset_only_qty=len(self.get("required_items")))
@@ -1616,7 +1616,7 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create
"posting_date": nowdate(),
"for_quantity": row.job_card_qty or work_order.get("qty", 0),
"operation_id": row.get("name"),
"bom_no": work_order.bom_no,
"bom_no": row.get("bom"),
"project": work_order.project,
"company": work_order.company,
"sequence_id": row.get("sequence_id"),

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _, scrub
from frappe.utils import getdate
from frappe.utils import getdate, today
from erpnext.stock.report.stock_analytics.stock_analytics import get_period, get_period_date_ranges
@@ -30,11 +30,12 @@ def get_columns(filters):
def get_periodic_data(filters, entry):
periodic_data = {
"All Work Orders": {},
"Not Started": {},
"Overdue": {},
"Pending": {},
"Completed": {},
"Closed": {},
"Stopped": {},
}
ranges = get_period_date_ranges(filters)
@@ -42,33 +43,24 @@ def get_periodic_data(filters, entry):
for from_date, end_date in ranges:
period = get_period(end_date, filters)
for d in entry:
if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date):
periodic_data = update_periodic_data(periodic_data, "All Work Orders", period)
if d.status == "Completed":
if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(
from_date
):
periodic_data = update_periodic_data(periodic_data, "Completed", period)
elif getdate(d.actual_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Pending", period)
elif getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
if getdate(from_date) <= getdate(d.creation) <= getdate(end_date) and d.status not in [
"Draft",
"Submitted",
"Completed",
"Cancelled",
]:
if d.status in ["Not Started", "Closed", "Stopped"]:
periodic_data = update_periodic_data(periodic_data, d.status, period)
elif getdate(today()) > getdate(d.planned_end_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
elif getdate(today()) < getdate(d.planned_end_date):
periodic_data = update_periodic_data(periodic_data, "Pending", period)
elif d.status == "In Process":
if getdate(d.actual_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Pending", period)
elif getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
elif d.status == "Not Started":
if getdate(d.planned_start_date) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
if (
getdate(from_date) <= getdate(d.actual_end_date) <= getdate(end_date)
and d.status == "Completed"
):
periodic_data = update_periodic_data(periodic_data, "Completed", period)
return periodic_data
@@ -88,10 +80,7 @@ def get_data(filters, columns):
"Work Order",
fields=[
"creation",
"modified",
"actual_start_date",
"actual_end_date",
"planned_start_date",
"planned_end_date",
"status",
],
@@ -100,7 +89,7 @@ def get_data(filters, columns):
periodic_data = get_periodic_data(filters, entry)
labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"]
labels = ["Not Started", "Overdue", "Pending", "Completed", "Closed", "Stopped"]
chart_data = get_chart_data(periodic_data, columns)
ranges = get_period_date_ranges(filters)
@@ -121,21 +110,23 @@ def get_data(filters, columns):
def get_chart_data(periodic_data, columns):
labels = [d.get("label") for d in columns[1:]]
all_data, not_start, overdue, pending, completed = [], [], [], [], []
not_start, overdue, pending, completed, closed, stopped = [], [], [], [], [], []
datasets = []
for d in labels:
all_data.append(periodic_data.get("All Work Orders").get(d))
not_start.append(periodic_data.get("Not Started").get(d))
overdue.append(periodic_data.get("Overdue").get(d))
pending.append(periodic_data.get("Pending").get(d))
completed.append(periodic_data.get("Completed").get(d))
closed.append(periodic_data.get("Closed").get(d))
stopped.append(periodic_data.get("Stopped").get(d))
datasets.append({"name": _("All Work Orders"), "values": all_data})
datasets.append({"name": _("Not Started"), "values": not_start})
datasets.append({"name": _("Overdue"), "values": overdue})
datasets.append({"name": _("Pending"), "values": pending})
datasets.append({"name": _("Completed"), "values": completed})
datasets.append({"name": _("Closed"), "values": closed})
datasets.append({"name": _("Stopped"), "values": stopped})
chart = {"data": {"labels": labels, "datasets": datasets}}
chart["type"] = "line"

View File

@@ -261,6 +261,7 @@ erpnext.patches.v14_0.show_loan_management_deprecation_warning
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
erpnext.patches.v14_0.update_proprietorship_to_individual
erpnext.patches.v15_0.rename_subcontracting_fields
[post_model_sync]
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
@@ -394,3 +395,10 @@ execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_post
erpnext.patches.v14_0.disable_add_row_in_gross_profit
erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment
erpnext.patches.v14_0.update_posting_datetime
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
erpnext.patches.v15_0.update_query_report
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
erpnext.patches.v15_0.recalculate_amount_difference_field #2025-03-18
erpnext.patches.v15_0.rename_sla_fields #2025-03-12
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices

View File

@@ -82,6 +82,9 @@ def get_asset_depreciation_schedules_map():
.orderby(ds.idx)
).run(as_dict=True)
if len(records) > 20000:
frappe.db.auto_commit_on_many_writes = True
asset_depreciation_schedules_map = frappe._dict()
for d in records:
asset_depreciation_schedules_map.setdefault((d.asset_name, cstr(d.finance_book)), []).append(d)

View File

@@ -0,0 +1,126 @@
import frappe
from frappe.query_builder.functions import Sum
from frappe.utils import flt, getdate
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import adjust_incoming_rate_for_pr
def execute():
if not frappe.db.get_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"):
return
for company in frappe.get_all("Company", pluck="name"):
table = frappe.qb.DocType("Purchase Receipt Item")
parent = frappe.qb.DocType("Purchase Receipt")
query = (
frappe.qb.from_(table)
.join(parent)
.on(table.parent == parent.name)
.select(
table.parent,
table.name,
table.amount,
table.billed_amt,
table.amount_difference_with_purchase_invoice,
table.rate,
table.qty,
parent.conversion_rate,
)
.where((table.docstatus == 1) & (parent.company == company))
)
posting_date = "2024-04-01"
# Get the last accounting period end date
accounting_period = frappe.get_all(
"Accounting Period", {"company": company}, ["end_date"], order_by="end_date desc", limit=1
)
if (
accounting_period
and accounting_period[0].end_date
and getdate(accounting_period[0].end_date) > getdate(posting_date)
):
posting_date = accounting_period[0].end_date
# Get the last period closing voucher end date
period_closing_voucher = frappe.get_all(
"Period Closing Voucher",
{"company": company, "docstatus": 1},
["period_end_date"],
order_by="period_end_date desc",
limit=1,
)
if (
period_closing_voucher
and period_closing_voucher[0].period_end_date
and getdate(period_closing_voucher[0].period_end_date) > getdate(posting_date)
):
posting_date = period_closing_voucher[0].period_end_date
acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
if acc_frozen_upto and getdate(acc_frozen_upto) > getdate(posting_date):
posting_date = acc_frozen_upto
stock_frozen_upto = frappe.db.get_single_value("Stock Settings", "stock_frozen_upto")
if stock_frozen_upto and getdate(stock_frozen_upto) > getdate(posting_date):
posting_date = stock_frozen_upto
try:
fiscal_year = get_fiscal_year(frappe.utils.datetime.date.today())
except Exception:
return
else:
if fiscal_year and getdate(fiscal_year[1]) > getdate(posting_date):
posting_date = fiscal_year[1]
query = query.where(parent.posting_date > posting_date)
if result := query.run(as_dict=True):
item_wise_billed_qty = get_billed_qty_against_purchase_receipt([item.name for item in result])
purchase_receipts = set()
precision = frappe.get_precision("Purchase Receipt Item", "amount")
for item in result:
adjusted_amt = 0.0
if (
item.billed_amt is not None
and item.amount is not None
and item_wise_billed_qty.get(item.name)
):
adjusted_amt = (
flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate)
) * item.qty
adjusted_amt = flt(
adjusted_amt * flt(item.conversion_rate),
precision,
)
if adjusted_amt != item.amount_difference_with_purchase_invoice:
frappe.db.set_value(
"Purchase Receipt Item",
item.name,
"amount_difference_with_purchase_invoice",
adjusted_amt,
update_modified=False,
)
purchase_receipts.add(item.parent)
for pr in purchase_receipts:
adjust_incoming_rate_for_pr(frappe.get_doc("Purchase Receipt", pr))
def get_billed_qty_against_purchase_receipt(pr_names):
table = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(table)
.select(table.pr_detail, Sum(table.qty).as_("qty"))
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
.groupby(table.pr_detail)
)
invoice_data = query.run(as_list=1)
if not invoice_data:
return frappe._dict()
return frappe._dict(invoice_data)

View File

@@ -0,0 +1,17 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
frappe.db.set_value(
"DocField",
{"parent": "Purchase Receipt Item", "fieldname": "rate_difference_with_purchase_invoice"},
"label",
"Amount Difference with Purchase Invoice",
)
rename_field(
"Purchase Receipt Item",
"rate_difference_with_purchase_invoice",
"amount_difference_with_purchase_invoice",
)
frappe.clear_cache(doctype="Purchase Receipt Item")

View File

@@ -0,0 +1,23 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import rename_fieldname
from frappe.model.utils.rename_field import rename_field
def execute():
doctypes = frappe.get_all("Service Level Agreement", pluck="document_type", distinct=True)
for doctype in doctypes:
if doctype == "Issue":
continue
if frappe.db.exists(
"Custom Field", {"name": doctype + "-resolution_by", "fieldname": "resolution_by"}
):
rename_fieldname(doctype + "-resolution_by", "sla_resolution_by")
if frappe.db.exists(
"Custom Field", {"name": doctype + "-resolution_date", "fieldname": "resolution_date"}
):
rename_fieldname(doctype + "-resolution_date", "sla_resolution_date")
rename_field("Issue", "resolution_by", "sla_resolution_by")
rename_field("Issue", "resolution_date", "sla_resolution_date")

View File

@@ -0,0 +1,14 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
if frappe.db.table_exists("Purchase Order Item") and frappe.db.has_column(
"Purchase Order Item", "sco_qty"
):
rename_field("Purchase Order Item", "sco_qty", "subcontracted_quantity")
if frappe.db.table_exists("Subcontracting Order Item") and frappe.db.has_column(
"Subcontracting Order Item", "sc_conversion_factor"
):
rename_field("Subcontracting Order Item", "sc_conversion_factor", "subcontracting_conversion_factor")

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