Compare commits

..

825 Commits

Author SHA1 Message Date
Frappe PR Bot
7fb557197a chore(release): Bumped to Version 15.60.2
## [15.60.2](https://github.com/frappe/erpnext/compare/v15.60.1...v15.60.2) (2025-05-06)

### Bug Fixes

* 'time to resolve: failed' on issue (backport [#47406](https://github.com/frappe/erpnext/issues/47406)) ([#47407](https://github.com/frappe/erpnext/issues/47407)) ([21612fc](21612fc230))
* backward compatibility for renamed group_by filter on reports (backport [#47362](https://github.com/frappe/erpnext/issues/47362)) ([#47403](https://github.com/frappe/erpnext/issues/47403)) ([0e5c709](0e5c709f7b))
* change shipping address fetching condition ([0aabe4f](0aabe4fd1e))
* completed transactions showing in the list (backport [#47374](https://github.com/frappe/erpnext/issues/47374)) ([#47379](https://github.com/frappe/erpnext/issues/47379)) ([1ef7da8](1ef7da837f))
* do not allocate amount when ref's doctype or name are not set ([c2e36da](c2e36daa32))
* do not mandate depreciation account for assets without depreciation (backport [#47427](https://github.com/frappe/erpnext/issues/47427)) ([#47428](https://github.com/frappe/erpnext/issues/47428)) ([01e975b](01e975b481))
* not able to submit the stock entry ([#47383](https://github.com/frappe/erpnext/issues/47383)) ([035394a](035394ae6a))
* party name in Ledger Summary ([4fc14b3](4fc14b3097))
* precision issue ([b6908a7](b6908a79bd))
* rename unchanged group_by filter related to general ledger report (backport [#47366](https://github.com/frappe/erpnext/issues/47366)) ([#47405](https://github.com/frappe/erpnext/issues/47405)) ([8d1e855](8d1e855dc8))
* renaming group by fieldname and value in reports (backport [#47352](https://github.com/frappe/erpnext/issues/47352)) ([#47360](https://github.com/frappe/erpnext/issues/47360)) ([85a8adf](85a8adf804))
* show party type in due date exceeding message ([f73e99e](f73e99e9d2))
* stock reco recalculate qty not works for opening stock reco ([2bd30e3](2bd30e3c46))
* update accounts on change of mode of payment in sales invoice payment (backport [#47381](https://github.com/frappe/erpnext/issues/47381)) ([#47400](https://github.com/frappe/erpnext/issues/47400)) ([afb44a6](afb44a677c))
* validation for difference account ([f4a43d0](f4a43d07b0))
* warning message before changing the valuation method (backport [#47340](https://github.com/frappe/erpnext/issues/47340)) ([#47342](https://github.com/frappe/erpnext/issues/47342)) ([4ef2b77](4ef2b77973))
2025-05-06 14:11:12 +00:00
ruthra kumar
7733e417a4 Merge pull request #47429 from frappe/version-15-hotfix
chore: release v15
2025-05-06 19:39:35 +05:30
rohitwaghchaure
d8fb11009f Merge pull request #47440 from frappe/mergify/bp/version-15-hotfix/pr-47435
fix: stock reco recalculate qty not works for opening stock reco (backport #47435)
2025-05-06 18:45:43 +05:30
Rohit Waghchaure
2bd30e3c46 fix: stock reco recalculate qty not works for opening stock reco
(cherry picked from commit 97095c7d24)
2025-05-06 12:57:56 +00:00
rohitwaghchaure
fb56db1166 Merge pull request #47437 from frappe/mergify/bp/version-15-hotfix/pr-47397
fix: precision issue (backport #47397)
2025-05-06 17:50:26 +05:30
Rohit Waghchaure
b6908a79bd fix: precision issue
(cherry picked from commit 69bee93bfd)
2025-05-06 11:59:50 +00:00
mergify[bot]
f4551bb918 feat!: configure which rate is used to auto-update price list (backport #47417)
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
2025-05-06 17:00:03 +05:30
mergify[bot]
01e975b481 fix: do not mandate depreciation account for assets without depreciation (backport #47427) (#47428)
fix: do not mandate depreciation account for assets without depreciation (#47427)

(cherry picked from commit 51ea33e743)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-05-06 15:21:14 +05:30
ruthra kumar
91cbf2ec4f Merge pull request #47426 from frappe/mergify/bp/version-15-hotfix/pr-47337
fix: do not allocate amount when ref's doctype or name are not set (backport #47337)
2025-05-06 15:00:46 +05:30
Abdeali Chharchhoda
c2e36daa32 fix: do not allocate amount when ref's doctype or name are not set
(cherry picked from commit b9a02b466b)
2025-05-06 09:04:52 +00:00
ruthra kumar
74caf8134c Merge pull request #47416 from frappe/mergify/bp/version-15-hotfix/pr-47408
fix: show party type in due date exceeding message (backport #47408)
2025-05-06 14:32:52 +05:30
ruthra kumar
40faa7f7b9 chore: resolve conflicts and pass all parameters 2025-05-06 14:14:41 +05:30
Abdeali Chharchhoda
f73e99e9d2 fix: show party type in due date exceeding message
(cherry picked from commit b6d9134014)

# Conflicts:
#	erpnext/accounts/party.py
2025-05-06 06:28:48 +00:00
ruthra kumar
2d77e056bc Merge pull request #47414 from frappe/mergify/bp/version-15-hotfix/pr-47358
fix: change shipping address fetching condition (backport #47358)
2025-05-06 11:28:56 +05:30
Vimal
0aabe4fd1e fix: change shipping address fetching condition
(cherry picked from commit 0b4add2f2b)
2025-05-06 05:29:00 +00:00
mergify[bot]
8d1e855dc8 fix: rename unchanged group_by filter related to general ledger report (backport #47366) (#47405)
fix: rename unchanged group_by filter related to general ledger report (#47366)

(cherry picked from commit 3de249dcba)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-05-05 18:01:51 +05:30
mergify[bot]
0e5c709f7b fix: backward compatibility for renamed group_by filter on reports (backport #47362) (#47403)
fix: backward compatibility for renamed group_by filter on reports (#47362)

* fix: backward compatibility for renamed group_by filter in general ledger report

* fix: backward compatibility for renamed group_by filter in supplier quotation comparison report

(cherry picked from commit d4ffa54136)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-05-05 18:01:29 +05:30
mergify[bot]
21612fc230 fix: 'time to resolve: failed' on issue (backport #47406) (#47407)
fix: 'time to resolve: failed' on issue (#47406)

* fix: 'time to resolve: failed' on issue

* fix: sla_resolution_date

(cherry picked from commit 45393d51a2)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-05-05 17:50:36 +05:30
mergify[bot]
afb44a677c fix: update accounts on change of mode of payment in sales invoice payment (backport #47381) (#47400)
* fix: update accounts on change of mode of payment in sales invoice payment (#47381)

* fix: update accounts on change of mode of payment in sales invoice payment

* test: fixed tests

(cherry picked from commit 8067799692)

# Conflicts:
#	erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py
#	erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py

* chore: resolve conflict

* chore: remove unused library

* chore: resolve conflict

* chore: resolve conflict

* chore: resolve linter issue

---------

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-05-05 16:35:31 +05:30
ruthra kumar
4f6aee3f22 Merge pull request #47399 from frappe/mergify/bp/version-15-hotfix/pr-47145
refactor: make AR / AP report more memory efficient (backport #47145)
2025-05-05 16:03:36 +05:30
ruthra kumar
fc1b1ca5e2 chore: resolve conflict 2025-05-05 15:47:39 +05:30
ruthra kumar
f69b8d7e2d refactor: set default for fetch methods
(cherry picked from commit ca1e81e1b5)
2025-05-05 10:14:49 +00:00
ruthra kumar
2147441e64 refactor: use fetch method based on configuration
(cherry picked from commit b5bb6f3508)
2025-05-05 10:14:49 +00:00
ruthra kumar
5e5cf68b32 refactor: configurable fetch method for AR / AP report
(cherry picked from commit 66fd639b52)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
2025-05-05 10:14:49 +00:00
ruthra kumar
7c8245d299 refactor: use unbuffered cursor for fetching
(cherry picked from commit 08903459c2)
2025-05-05 10:14:48 +00:00
rohitwaghchaure
a4c9707fdf Merge pull request #47390 from frappe/mergify/bp/version-15-hotfix/pr-47376
fix: validation for difference account (backport #47376)
2025-05-05 14:11:07 +05:30
Rohit Waghchaure
f4a43d07b0 fix: validation for difference account
(cherry picked from commit fb819c558e)
2025-05-03 07:52:58 +00:00
Frappe PR Bot
c1ed750bcb chore(release): Bumped to Version 15.60.1
## [15.60.1](https://github.com/frappe/erpnext/compare/v15.60.0...v15.60.1) (2025-05-02)

### Bug Fixes

* not able to submit the stock entry ([#47383](https://github.com/frappe/erpnext/issues/47383)) ([73a418a](73a418a2bd))
2025-05-02 13:44:13 +00:00
rohitwaghchaure
1d139eb94a Merge pull request #47384 from frappe/mergify/bp/version-15/pr-47383
fix: not able to submit the stock entry (backport #47383)
2025-05-02 19:12:43 +05:30
rohitwaghchaure
73a418a2bd fix: not able to submit the stock entry (#47383)
(cherry picked from commit 035394ae6a)
2025-05-02 13:06:32 +00:00
rohitwaghchaure
035394ae6a fix: not able to submit the stock entry (#47383) 2025-05-02 18:34:47 +05:30
mergify[bot]
1ef7da837f fix: completed transactions showing in the list (backport #47374) (#47379)
fix: completed transactions showing in the list (#47374)

(cherry picked from commit 97db9da10e)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-05-02 15:28:45 +05:30
mergify[bot]
85a8adf804 fix: renaming group by fieldname and value in reports (backport #47352) (#47360)
* fix: renaming group by fieldname and value in reports (#47352)

* fix: renaming in general ledger report

* fix: renaming in supplier quotation comparison report

* fix: renaming group by to categorize by in process statement of accounts

* fix: added patch

* fix: patch update to all documents

* chore: added patches to patch.txt

* chore: removing patch from v14

(cherry picked from commit 13a84e7f82)

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

* chore: resolve conflict

---------

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-30 17:19:43 +05:30
Mihir Kandoi
d3445b3079 Merge pull request #47357 from frappe/mergify/bp/version-15-hotfix/pr-47336
refactor: portal query in timesheet.py (backport #47336)
2025-04-30 16:35:11 +05:30
Mihir Kandoi
ada7821a49 refactor: portal query in timesheet.py (#47336)
* refactor: portal query in timesheet.py

* fix: use criterion.any to fix query

(cherry picked from commit 4fc7a8b71d)
2025-04-30 10:45:02 +00:00
Nihantra C. Patel
3f7dcedf3d Merge pull request #47354 from frappe/mergify/bp/version-15-hotfix/pr-47351
fix: party name in Ledger Summary (backport #47351)
2025-04-30 14:05:47 +05:30
Nihantra Patel
4fc14b3097 fix: party name in Ledger Summary
(cherry picked from commit 70bc86a4c6)
2025-04-30 08:13:52 +00:00
mergify[bot]
4ef2b77973 fix: warning message before changing the valuation method (backport #47340) (#47342)
fix: warning message before changing the valuation method (#47340)

(cherry picked from commit ffdc4347e8)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-04-30 08:45:39 +05:30
Frappe PR Bot
96996bd8a9 chore(release): Bumped to Version 15.60.0
# [15.60.0](https://github.com/frappe/erpnext/compare/v15.59.0...v15.60.0) (2025-04-29)

### Bug Fixes

* add transaction_date in field_no_map when creating PO from SQ (backport [#47257](https://github.com/frappe/erpnext/issues/47257)) ([#47313](https://github.com/frappe/erpnext/issues/47313)) ([afb67f1](afb67f1f0c))
* allow selling asset at zero rate (backport [#47326](https://github.com/frappe/erpnext/issues/47326)) ([#47332](https://github.com/frappe/erpnext/issues/47332)) ([171b687](171b687611))
* allow to change valuation method from FIFO to Moving Average ([b2294ed](b2294ed6e3))
* allow to make quality inspection after Purchase / Delivery ([e0cea49](e0cea49236))
* cancel pos closing entry failure for return pos invoices (backport [#47248](https://github.com/frappe/erpnext/issues/47248)) ([#47249](https://github.com/frappe/erpnext/issues/47249)) ([10d843e](10d843e490))
* commas in rfq portal js ([954fec1](954fec16f4))
* compare total debit/credit with precision for Inter Company Journal Entry ([0927155](0927155171))
* consolidating pos invoices on the basis of accounting dimensions (backport [#46961](https://github.com/frappe/erpnext/issues/46961)) ([#47265](https://github.com/frappe/erpnext/issues/47265)) ([f8da159](f8da1599bb))
* correct query for dispatch_address; remove unnecessary code; increase reusability; ([ac3b2ba](ac3b2ba003))
* do not check for permission if values are not changed in employee doctype ([#47238](https://github.com/frappe/erpnext/issues/47238)) ([0caba9f](0caba9f70d))
* enable use serial / batch fields on batch selection ([925cc40](925cc40efa))
* enhance dispatch address query logic and add supplier address query ([290f0b9](290f0b94e5))
* fix sub assembly qty calculation in production plan when bom level >= 1 (backport [#47296](https://github.com/frappe/erpnext/issues/47296)) ([#47315](https://github.com/frappe/erpnext/issues/47315)) ([d15b7ca](d15b7ca9af))
* make asset quantity and amount editable (backport [#47226](https://github.com/frappe/erpnext/issues/47226)) ([#47227](https://github.com/frappe/erpnext/issues/47227)) ([c140fd0](c140fd0f12))
* map dispatch address correctly for inter company transactions ([d8c0e71](d8c0e7156e))
* missing else statement ([8a30a31](8a30a31302))
* **payment request:** get advance amount based on transaction currency ([c2235e2](c2235e2d17))
* **PE:** Set account types in get_payment_entry (backport [#47246](https://github.com/frappe/erpnext/issues/47246)) ([#47266](https://github.com/frappe/erpnext/issues/47266)) ([3e733f6](3e733f6ba1))
* prevent cancellation of last asset movement (backport [#47291](https://github.com/frappe/erpnext/issues/47291)) ([#47312](https://github.com/frappe/erpnext/issues/47312)) ([2edd12b](2edd12b26d))
* price currency in supplier quotation comparison ([6b1b30a](6b1b30a4a6))
* prohibit consolidated sales invoice return (backport [#47251](https://github.com/frappe/erpnext/issues/47251)) ([#47252](https://github.com/frappe/erpnext/issues/47252)) ([4bcea55](4bcea55563))
* QI reference not set if 'Action If Quality Inspection Is Not Sub… (backport [#47294](https://github.com/frappe/erpnext/issues/47294)) ([#47295](https://github.com/frappe/erpnext/issues/47295)) ([b0399fe](b0399fe948))
* Re-insert missing "Serial No Warranty Expiry" Report ([727c32d](727c32d789))
* remove invalid email account creation (backport [#47318](https://github.com/frappe/erpnext/issues/47318)) ([#47323](https://github.com/frappe/erpnext/issues/47323)) ([fc8a8b5](fc8a8b5433))
* remove use of cur_frm ([5c300b8](5c300b893b))
* **Rename Tool:** allow more than 500 rows (backport [#47117](https://github.com/frappe/erpnext/issues/47117)) ([#47225](https://github.com/frappe/erpnext/issues/47225)) ([c0ae133](c0ae1336f4))
* require email OR phone in shipment doctype not both (backport [#47300](https://github.com/frappe/erpnext/issues/47300)) ([#47330](https://github.com/frappe/erpnext/issues/47330)) ([0056fb1](0056fb1d0f))
* set billing hours to hours ([0763a8d](0763a8d42d))
* set billing hours to hours in timesheet (backport [#47289](https://github.com/frappe/erpnext/issues/47289)) ([#47290](https://github.com/frappe/erpnext/issues/47290)) ([74bdc82](74bdc82bfa))
* update additional cost and total asset cost after asset repair (backport [#47233](https://github.com/frappe/erpnext/issues/47233)) ([#47235](https://github.com/frappe/erpnext/issues/47235)) ([4a29a54](4a29a54804))
* update billing hours when hours is changed ([a9df1f5](a9df1f5f6b))
* update quantity validation using asset quantity field instead of… (backport [#46731](https://github.com/frappe/erpnext/issues/46731)) ([#47284](https://github.com/frappe/erpnext/issues/47284)) ([2e6112f](2e6112f21b))
* validate if from and to time are present on submission of job card ([#47325](https://github.com/frappe/erpnext/issues/47325)) ([d640c79](d640c79c1c))
* validation if no stock ledger entries against stock reco (backport [#47292](https://github.com/frappe/erpnext/issues/47292)) ([#47293](https://github.com/frappe/erpnext/issues/47293)) ([91bcefe](91bcefef8c))

### Features

* add dispatch address fields to purchase doctypes ([5f101e7](5f101e7635))
* add dispatch address support in party details and controllers ([1fe1563](1fe1563dab))
* add display dispatch address when dispatch address is selected ([93ea2f9](93ea2f93b6))
* change sabb qty automatically incase of internal transfer PR if sabb only has 1 batch ([#47256](https://github.com/frappe/erpnext/issues/47256)) ([9495a2a](9495a2ac9d))
2025-04-29 13:12:18 +00:00
ruthra kumar
a9e40bc0d8 Merge pull request #47328 from frappe/version-15-hotfix
chore: release v15
2025-04-29 18:40:46 +05:30
mergify[bot]
d15b7ca9af fix: fix sub assembly qty calculation in production plan when bom level >= 1 (backport #47296) (#47315)
* fix: fix sub assembly qty calculation in production plan when bom level >= 1

(cherry picked from commit bfc4ce1d5d)

* fix: logical error

(cherry picked from commit ee10afc074)

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-04-29 18:12:36 +05:30
mergify[bot]
171b687611 fix: allow selling asset at zero rate (backport #47326) (#47332)
fix: allow selling asset at zero rate (#47326)

(cherry picked from commit 05afad78fc)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-04-29 17:12:51 +05:30
ruthra kumar
cc8418b7e4 Merge pull request #47329 from frappe/mergify/bp/version-15-hotfix/pr-47325
fix: validate if from and to time are present on submission of job card (backport #47325)
2025-04-29 16:46:34 +05:30
mergify[bot]
0056fb1d0f fix: require email OR phone in shipment doctype not both (backport #47300) (#47330)
fix: require email OR phone in shipment doctype not both (#47300)

(cherry picked from commit fc02a6510e)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-04-29 16:27:56 +05:30
Mihir Kandoi
d640c79c1c fix: validate if from and to time are present on submission of job card (#47325)
(cherry picked from commit 7499c25a3c)
2025-04-29 10:52:56 +00:00
mergify[bot]
2edd12b26d fix: prevent cancellation of last asset movement (backport #47291) (#47312)
fix: prevent cancellation of last asset movement (#47291)

* fix: prevent cancellation of last asset movement

* test: movement cancellation

* fix: allow cancellation of asset movement when cancelling asset

(cherry picked from commit 9dee4ac891)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-04-29 12:51:18 +05:30
ruthra kumar
71482261c7 Merge pull request #47324 from frappe/mergify/bp/version-15-hotfix/pr-47241
fix: compare total debit/credit with precision for Inter Company Journal Entry (backport #47241)
2025-04-29 12:43:42 +05:30
Mihir Kandoi
9df5727ea4 Merge pull request #47317 from frappe/mergify/bp/version-15-hotfix/pr-47256
feat: change sabb qty automatically incase of internal transfer PR if… (backport #47256)
2025-04-29 12:40:53 +05:30
mergify[bot]
fc8a8b5433 fix: remove invalid email account creation (backport #47318) (#47323)
fix: remove invalid email account creation (#47318)

(cherry picked from commit 7423e4187f)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-29 12:40:45 +05:30
Mihir Kandoi
409831183b Merge pull request #47314 from frappe/mergify/bp/version-15-hotfix/pr-47234
fix: price currency in supplier quotation comparison (backport #47234)
2025-04-29 12:40:31 +05:30
mergify[bot]
afb67f1f0c fix: add transaction_date in field_no_map when creating PO from SQ (backport #47257) (#47313)
* fix: add transaction_date in field_no_map when creating PO from SQ

(cherry picked from commit 3790c6c551)

* fix: test case

(cherry picked from commit acd1529780)

# Conflicts:
#	erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py

* fix: remove unused import

(cherry picked from commit 9e640341fd)

# Conflicts:
#	erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py

* chore: fix conflicts

* chore: remove unused imports

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-04-29 12:40:10 +05:30
ruthra kumar
7bc39349b0 Merge pull request #47321 from frappe/mergify/bp/version-15-hotfix/pr-47231
fix(payment request): get advance amount based on transaction currency (backport #47231)
2025-04-29 12:25:54 +05:30
ljain112
0927155171 fix: compare total debit/credit with precision for Inter Company Journal Entry
(cherry picked from commit 5fe247557e)
2025-04-29 06:48:08 +00:00
venkat102
c2235e2d17 fix(payment request): get advance amount based on transaction currency
(cherry picked from commit b570d97b4d)
2025-04-29 06:37:54 +00:00
Mihir Kandoi
9495a2ac9d feat: change sabb qty automatically incase of internal transfer PR if sabb only has 1 batch (#47256)
* feat: change sabb qty automatically incase of internal transfer PR if sabb only has 1 batch

* fix: prevent creation of SABB on every save

* perf: optimize code

* fix: remove unnecessary conditon

* refactor: change if to elif

* fix: remove dn_item_qty and set to item.qty

* test: added test

(cherry picked from commit 47927b38a9)
2025-04-29 06:17:04 +00:00
Mihir Kandoi
cb61e0bd18 Merge pull request #47316 from frappe/mergify/bp/version-15-hotfix/pr-47302
fix: commas in rfq portal js (backport #47302)
2025-04-29 11:46:31 +05:30
Mihir Kandoi
954fec16f4 fix: commas in rfq portal js
(cherry picked from commit bd727e069b)
2025-04-29 06:07:50 +00:00
Mihir Kandoi
6b1b30a4a6 fix: price currency in supplier quotation comparison
(cherry picked from commit 88926eb2a7)
2025-04-29 06:06:38 +00:00
mergify[bot]
b0399fe948 fix: QI reference not set if 'Action If Quality Inspection Is Not Sub… (backport #47294) (#47295)
fix: QI reference not set if 'Action If Quality Inspection Is Not Sub… (#47294)

fix: qi reference not set if 'Action If Quality Inspection Is Not Submitted' is blank
(cherry picked from commit 0701a8cf5a)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-04-28 22:01:57 +05:30
mergify[bot]
91bcefef8c fix: validation if no stock ledger entries against stock reco (backport #47292) (#47293)
fix: validation if no stock ledger entries against stock reco (#47292)

(cherry picked from commit 3d36d0b1df)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-04-28 19:38:27 +05:30
mergify[bot]
74bdc82bfa fix: set billing hours to hours in timesheet (backport #47289) (#47290)
* fix: set billing hours to hours

(cherry picked from commit 0763a8d42d)

* fix: update billing hours when hours is changed

(cherry picked from commit a9df1f5f6b)

* fix: missing else statement

(cherry picked from commit 8a30a31302)

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-04-28 16:33:40 +05:30
rohitwaghchaure
5b32bb7468 Merge pull request #47289 from frappe/st37102-2
fix: set billing hours to hours in timesheet
2025-04-28 16:27:33 +05:30
Mihir Kandoi
8a30a31302 fix: missing else statement 2025-04-28 16:08:40 +05:30
Mihir Kandoi
a9df1f5f6b fix: update billing hours when hours is changed 2025-04-28 16:07:54 +05:30
Mihir Kandoi
0763a8d42d fix: set billing hours to hours 2025-04-28 15:43:13 +05:30
rohitwaghchaure
c02a4a4591 Merge pull request #47286 from frappe/mergify/bp/version-15-hotfix/pr-47285
fix: allow to make quality inspection after Purchase / Delivery (backport #47285)
2025-04-28 15:29:16 +05:30
ruthra kumar
1cbcf7532c Merge pull request #47281 from frappe/mergify/bp/version-15-hotfix/pr-46993
feat: add dispatch address fields to purchase doctypes (backport #46993)
2025-04-28 14:47:02 +05:30
Rohit Waghchaure
e0cea49236 fix: allow to make quality inspection after Purchase / Delivery
(cherry picked from commit fad1a32e63)
2025-04-28 09:00:14 +00:00
mergify[bot]
2e6112f21b fix: update quantity validation using asset quantity field instead of… (backport #46731) (#47284)
* fix: update quantity validation using asset quantity field instead of… (#46731)

* fix: update quantity validation using asset quantity field instead of total records

* fix: update throw message

(cherry picked from commit eae08bc619)

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

* chore: resolved conflicts

---------

Co-authored-by: l0gesh29 <logeshperiyasamy24@gmail.com>
Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-04-28 14:18:51 +05:30
ruthra kumar
feb4038c06 chore: resolve conflicts 2025-04-28 11:36:24 +05:30
Smit Vora
d8c0e7156e fix: map dispatch address correctly for inter company transactions
(cherry picked from commit ceaba4220b)
2025-04-28 05:28:20 +00:00
Smit Vora
7baa8f50fb refactor: set address details for transactions
(cherry picked from commit fb3b7d8c34)
2025-04-28 05:28:20 +00:00
Smit Vora
62261a276f refactor: address field position
(cherry picked from commit 8ccd7a3e61)

# Conflicts:
#	erpnext/public/scss/erpnext.scss
2025-04-28 05:28:20 +00:00
Karm Soni
ac3b2ba003 fix: correct query for dispatch_address; remove unnecessary code; increase reusability;
(cherry picked from commit 999ffe86a7)
2025-04-28 05:28:19 +00:00
Karm Soni
93ea2f93b6 feat: add display dispatch address when dispatch address is selected
(cherry picked from commit d12998e524)
2025-04-28 05:28:19 +00:00
Karm Soni
5c300b893b fix: remove use of cur_frm
(cherry picked from commit c4bd3123fb)
2025-04-28 05:28:19 +00:00
Karm Soni
290f0b94e5 fix: enhance dispatch address query logic and add supplier address query
(cherry picked from commit 9a859e54b6)
2025-04-28 05:28:18 +00:00
Karm Soni
1fe1563dab feat: add dispatch address support in party details and controllers
(cherry picked from commit 53d0b7be23)
2025-04-28 05:28:18 +00:00
Karm Soni
5f101e7635 feat: add dispatch address fields to purchase doctypes
(cherry picked from commit 54b5205221)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
#	erpnext/buying/doctype/purchase_order/purchase_order.json
2025-04-28 05:28:18 +00:00
mergify[bot]
3e733f6ba1 fix(PE): Set account types in get_payment_entry (backport #47246) (#47266)
Co-authored-by: Corentin Forler <10946971+cogk@users.noreply.github.com>
fix(PE): Set account types in get_payment_entry (#47246)
2025-04-27 15:24:59 +02:00
rohitwaghchaure
5c4dc7a16e Merge pull request #47277 from frappe/mergify/bp/version-15-hotfix/pr-47259
fix: enable use serial / batch fields on batch selection (backport #47259)
2025-04-27 18:41:32 +05:30
Rohit Waghchaure
925cc40efa fix: enable use serial / batch fields on batch selection
(cherry picked from commit a4471865a9)
2025-04-27 13:06:21 +00:00
rohitwaghchaure
d947beec88 Merge pull request #47276 from frappe/mergify/bp/version-15-hotfix/pr-47268
fix: allow to change valuation method from FIFO to Moving Average (backport #47268)
2025-04-27 18:23:16 +05:30
Rohit Waghchaure
b2294ed6e3 fix: allow to change valuation method from FIFO to Moving Average
(cherry picked from commit b454ed4b8f)
2025-04-27 12:16:36 +00:00
mergify[bot]
f8da1599bb fix: consolidating pos invoices on the basis of accounting dimensions (backport #46961) (#47265)
fix: consolidating pos invoices on the basis of accounting dimensions (#46961)

* fix: consolidating pos invoices on the basis of accounting dimensions

* fix: project field

(cherry picked from commit c85edc3346)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-25 17:59:15 +05:30
Marica
636e9e0998 Merge pull request #47261 from frappe/mergify/bp/version-15-hotfix/pr-47232
fix: Re-insert missing "Serial No Warranty Expiry" Report (backport #47232)
2025-04-25 15:13:55 +05:30
marination
727c32d789 fix: Re-insert missing "Serial No Warranty Expiry" Report
(cherry picked from commit deefac0abf)
2025-04-25 09:28:36 +00:00
mergify[bot]
4bcea55563 fix: prohibit consolidated sales invoice return (backport #47251) (#47252)
fix: prohibit consolidated sales invoice return (#47251)

(cherry picked from commit 483c4a3271)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-25 11:33:04 +05:30
mergify[bot]
10d843e490 fix: cancel pos closing entry failure for return pos invoices (backport #47248) (#47249)
fix: cancel pos closing entry failure for return pos invoices (#47248)

(cherry picked from commit c8ee5d9a4e)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-24 23:23:08 +05:30
Lakshit Jain
0caba9f70d fix: do not check for permission if values are not changed in employee doctype (#47238) 2025-04-24 17:03:30 +02:00
mergify[bot]
4a29a54804 fix: update additional cost and total asset cost after asset repair (backport #47233) (#47235)
fix: update additional cost and total asset cost after asset repair (#47233)

* fix: add consumed stock's cost to the asset value after repair

* fix: do not copy additional cost and total asset cost

(cherry picked from commit ed8a8532e1)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-04-24 16:36:53 +05:30
mergify[bot]
c140fd0f12 fix: make asset quantity and amount editable (backport #47226) (#47227)
fix: make asset quantity and amount editable (#47226)

(cherry picked from commit 0d53e6ed7c)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-04-24 11:36:18 +05:30
mergify[bot]
c0ae1336f4 fix(Rename Tool): allow more than 500 rows (backport #47117) (#47225)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Rename Tool): allow more than 500 rows (#47117)
2025-04-23 20:26:03 +02:00
Frappe PR Bot
4c3044cd70 chore(release): Bumped to Version 15.59.0
# [15.59.0](https://github.com/frappe/erpnext/compare/v15.58.2...v15.59.0) (2025-04-22)

### Bug Fixes

* `TypeError` in group field filter in supplier ledger summary ([3b349f4](3b349f44b1))
* add group by after user permission condition ([f07c3d9](f07c3d9124))
* backslash in url ([f6edd5a](f6edd5aa7d))
* change get_url_to_form to get_link_to_form ([982a68b](982a68b71a))
* cherry pick ([20aba54](20aba541c4))
* consider per_ordered instead of per_billed when creating PO from MR ([be154a4](be154a469f))
* correct error message in validate_internal_transfer_qty ([b8a7f6d](b8a7f6dac1))
* create default warehouse (backport [#47125](https://github.com/frappe/erpnext/issues/47125)) ([#47131](https://github.com/frappe/erpnext/issues/47131)) ([ad177e0](ad177e08b8))
* disbaled UOM showing in the list ([3d4f3e1](3d4f3e1be7))
* distributed discounts on si ([ad05e6d](ad05e6dec2))
* **Employee:** remove User Permissions if create_user_permission is unchecked ([7ab81b7](7ab81b7e54))
* expense account in stock entry ([2f1f229](2f1f229144))
* get total without rounding off tax amounts for distributing discount (backport [#47155](https://github.com/frappe/erpnext/issues/47155)) ([8050e65](8050e653ab))
* group sub assemblies in production plan ([73683b2](73683b2754))
* import error ([924e9b9](924e9b94b6))
* keep per_billed 100 for billed delivery note after return ([680c221](680c221f05))
* linter ([c58800a](c58800a929))
* logic and added test case ([b3e852a](b3e852adfc))
* Modify .json from desk to change `modified` ([1dc9812](1dc98124dc))
* only update User Permissions if a relevant field has changed ([b0f3d62](b0f3d62dd0))
* pos disable customer selection at payment (backport [#47169](https://github.com/frappe/erpnext/issues/47169)) ([#47170](https://github.com/frappe/erpnext/issues/47170)) ([7adba1f](7adba1f0f3))
* provision to recalculate the qty in the Bin ([5535eb4](5535eb4817))
* rate based on posting date in Tax Withholding Report ([9184c40](9184c40371))
* remove invalid parameter ([2431141](2431141062))
* remove unused import ([4fba4d4](4fba4d49d2))
* respect field "ignore_user_permissions" property in employee query ([a450ce2](a450ce25b9))
* respect mapped accounting dimensions ([846b24b](846b24ba52))
* revert unintended changes ([a09ab90](a09ab902e5))
* set correct paid/receive amount if doc currency is different from party account currency ([5dc63f9](5dc63f97a1))
* set default company address in Sales Doctype on change of company ([05d4c1e](05d4c1e6ca))
* show button only when RFQ is submitted ([9655bfa](9655bfa199))
* test cases ([dedb19e](dedb19e3e9))
* test cases error ([13d3b27](13d3b27a1f))
* update country wise fiscal year (backport [#47141](https://github.com/frappe/erpnext/issues/47141)) ([#47176](https://github.com/frappe/erpnext/issues/47176)) ([390780d](390780d871))
* use get_url_to_form instead ([ad35021](ad35021666))

### Features

* add button to show request for comparison report directly from RFQ ([cb2b956](cb2b9563e0))
* add unit tests for distributed_discount_amount ([6f6574c](6f6574c5ac))

### Reverts

* disable customer if creating from opportunity ([99735e0](99735e0af4))
2025-04-22 13:47:39 +00:00
ruthra kumar
f7efd006c2 Merge pull request #47204 from frappe/version-15-hotfix
chore: release v15
2025-04-22 19:16:15 +05:30
mergify[bot]
8050e653ab fix: get total without rounding off tax amounts for distributing discount (backport #47155)
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
2025-04-22 17:53:10 +05:30
ruthra kumar
2ba6c78e5e Merge pull request #47210 from frappe/mergify/bp/version-15-hotfix/pr-47178
fix: keep per_billed 100 for billed delivery note after return (backport #47178)
2025-04-22 17:30:33 +05:30
Sugesh393
04a1578b53 refactor: update base_outstanding calculation
(cherry picked from commit 02356029a8)
2025-04-22 11:42:13 +00:00
Sugesh393
2b05ccfa6f test: add new unit test to keep per_billed 100 for billed delivery note
(cherry picked from commit fe5898a151)
2025-04-22 11:42:12 +00:00
Sugesh393
680c221f05 fix: keep per_billed 100 for billed delivery note after return
(cherry picked from commit 8290a83591)
2025-04-22 11:42:12 +00:00
ruthra kumar
bbbbb4da55 Merge pull request #47209 from frappe/mergify/bp/version-15-hotfix/pr-47183
fix: backslash in url (backport #47183)
2025-04-22 17:07:23 +05:30
ruthra kumar
e2f8ca5f87 chore: resolve conflict 2025-04-22 16:46:57 +05:30
ruthra kumar
6671f4df41 Merge pull request #47208 from frappe/mergify/bp/version-15-hotfix/pr-46717
fix: respect field "ignore_user_permissions" property in employee query (backport #46717)
2025-04-22 16:40:28 +05:30
Mihir Kandoi
4fba4d49d2 fix: remove unused import
(cherry picked from commit c3d172fac3)
2025-04-22 10:46:37 +00:00
Mihir Kandoi
982a68b71a fix: change get_url_to_form to get_link_to_form
(cherry picked from commit 5d07beee61)
2025-04-22 10:46:37 +00:00
Mihir Kandoi
a09ab902e5 fix: revert unintended changes
(cherry picked from commit eaaf34cda6)

# Conflicts:
#	erpnext/stock/doctype/material_request/material_request.json
2025-04-22 10:46:37 +00:00
Mihir Kandoi
ad35021666 fix: use get_url_to_form instead
(cherry picked from commit 7a82b37f76)
2025-04-22 10:46:36 +00:00
Mihir Kandoi
f6edd5aa7d fix: backslash in url
(cherry picked from commit ecf15130ba)

# Conflicts:
#	erpnext/stock/doctype/material_request/material_request.json
2025-04-22 10:46:36 +00:00
ljain112
e752f3f914 chore: added test case for employee query with user permissions
(cherry picked from commit 4be975f87c)

# Conflicts:
#	erpnext/controllers/tests/test_queries.py
2025-04-22 16:00:00 +05:30
ruthra kumar
31af933c1c Merge pull request #47207 from frappe/mergify/bp/version-15-hotfix/pr-47171
fix: set correct paid/receive amount if doc currency is different from party account currency (backport #47171)
2025-04-22 15:58:28 +05:30
ljain112
a450ce25b9 fix: respect field "ignore_user_permissions" property in employee query
(cherry picked from commit 91d7bc55be)
2025-04-22 10:12:30 +00:00
Soham Kulkarni
e152c72a7b Merge pull request #47201 from frappe/mergify/bp/version-15-hotfix/pr-47175
fix: add grand_total to show correct status in quick list widget (backport #47175)
2025-04-22 15:41:49 +05:30
ljain112
5dc63f97a1 fix: set correct paid/receive amount if doc currency is different from party account currency
(cherry picked from commit 9612521894)
2025-04-22 10:07:17 +00:00
rohitwaghchaure
8bc00ffbb4 Merge pull request #47206 from frappe/mergify/bp/version-15-hotfix/pr-47184
feat: add button to view Supplier Quotation Comparison directly from RFQ (backport #47184)
2025-04-22 15:16:01 +05:30
Mihir Kandoi
9655bfa199 fix: show button only when RFQ is submitted
(cherry picked from commit ef57d2b328)
2025-04-22 09:43:41 +00:00
Mihir Kandoi
cb2b9563e0 feat: add button to show request for comparison report directly from RFQ
(cherry picked from commit b4aa88b59b)
2025-04-22 09:43:41 +00:00
ruthra kumar
bb71b91d4a Merge pull request #47203 from frappe/mergify/bp/version-15-hotfix/pr-47180
fix: set default company address in selling Doctype on change of company (backport #47180)
2025-04-22 15:12:20 +05:30
ljain112
05d4c1e6ca fix: set default company address in Sales Doctype on change of company
(cherry picked from commit a31075692c)
2025-04-22 08:59:04 +00:00
Soham Kulkarni
d7556069e4 Merge pull request #47175 from sokumon/purchase-receipt-quick-list
fix: add grand_total to show correct status in quick list widget
(cherry picked from commit 68ca4a77c9)
2025-04-22 08:52:51 +00:00
ruthra kumar
a3821cf182 Merge pull request #47199 from frappe/mergify/bp/version-15-hotfix/pr-47138
fix: rate based on posting date in Tax Withholding Report (backport #47138)
2025-04-22 14:07:08 +05:30
ruthra kumar
18e3171cc9 Merge pull request #47197 from frappe/mergify/bp/version-15-hotfix/pr-47191
fix: expense account in stock entry (backport #47191)
2025-04-22 13:34:01 +05:30
ljain112
1f8fce253d chore: added test case for date period in multiple tax withholding rules
(cherry picked from commit 515fe340a8)

# Conflicts:
#	erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
2025-04-22 13:31:28 +05:30
ljain112
9184c40371 fix: rate based on posting date in Tax Withholding Report
(cherry picked from commit a32a79e90a)
2025-04-22 07:59:58 +00:00
Rohit Waghchaure
2f1f229144 fix: expense account in stock entry
(cherry picked from commit 75874b4986)
2025-04-22 07:46:37 +00:00
ruthra kumar
393e4462f8 Merge pull request #47195 from frappe/mergify/bp/version-15-hotfix/pr-47124
fix: `TypeError` in group field filter in supplier ledger summary (backport #47124)
2025-04-22 12:44:05 +05:30
ljain112
3b349f44b1 fix: TypeError in group field filter in supplier ledger summary
(cherry picked from commit 872e94a316)
2025-04-22 06:51:57 +00:00
rohitwaghchaure
c430ce96b3 Merge pull request #47185 from frappe/mergify/bp/version-15-hotfix/pr-47144
fix: provision to recalculate the qty in the Bin (backport #47144)
2025-04-22 12:20:46 +05:30
rohitwaghchaure
7b5297bb7f Merge pull request #47188 from frappe/mergify/bp/version-15-hotfix/pr-47186
fix: disabled UOM showing in the list (backport #47186)
2025-04-22 12:20:34 +05:30
Rohit Waghchaure
3d4f3e1be7 fix: disbaled UOM showing in the list
(cherry picked from commit 3745825052)
2025-04-21 16:43:17 +00:00
Rohit Waghchaure
5535eb4817 fix: provision to recalculate the qty in the Bin
(cherry picked from commit 36081413d8)
2025-04-21 15:49:11 +00:00
ruthra kumar
af35e43555 Merge pull request #47095 from frappe/mergify/bp/version-15-hotfix/pr-47007
chore: Fix typo "Item Wise Tax Detail " (backport #47007)
2025-04-21 16:50:22 +05:30
ruthra kumar
f53a45cfef chore: resolve conflict 2025-04-21 16:30:20 +05:30
ruthra kumar
19045bcace Merge pull request #47177 from frappe/mergify/bp/version-15-hotfix/pr-47067
fix: correct error message in validate_internal_transfer_qty (backport #47067)
2025-04-21 16:24:11 +05:30
ljain112
b8a7f6dac1 fix: correct error message in validate_internal_transfer_qty
(cherry picked from commit 5063f1174e)
2025-04-21 10:28:27 +00:00
ruthra kumar
f2c3150687 Merge pull request #47013 from frappe/mergify/bp/version-15-hotfix/pr-45924
fix(Employee): remove User Permissions if create_user_permission is unchecked (backport #45924)
2025-04-21 15:55:03 +05:30
mergify[bot]
390780d871 fix: update country wise fiscal year (backport #47141) (#47176)
fix: update country wise fiscal year (#47141)

(cherry picked from commit cb2ad4acdb)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-21 14:26:47 +05:30
mergify[bot]
7adba1f0f3 fix: pos disable customer selection at payment (backport #47169) (#47170)
fix: pos disable customer selection at payment (#47169)

(cherry picked from commit f52cbf6165)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-21 11:56:54 +05:30
Sagar Vora
d6d042868d Merge pull request #43067 from frappe/mergify/bp/version-15-hotfix/pr-41721
fix: distributed discounts on si (backport #41721)
2025-04-19 16:35:12 +05:30
Sagar Vora
f764b9713b Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-41721 2025-04-19 16:18:37 +05:30
Sagar Vora
16877fade8 Merge pull request #47157 from frappe/mergify/bp/version-15-hotfix/pr-47154
fix: respect mapped accounting dimensions (backport #47154)
2025-04-19 13:04:01 +05:30
Sagar Vora
846b24ba52 fix: respect mapped accounting dimensions
(cherry picked from commit 7dbe27da19)
2025-04-19 07:22:45 +00:00
mergify[bot]
451b1a19a8 chore: migrate pre-commit config (backport #47132) (#47134)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-04-17 14:18:00 +02:00
mergify[bot]
ad177e08b8 fix: create default warehouse (backport #47125) (#47131)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: create default warehouse (#47125)
2025-04-17 13:40:05 +02:00
Frappe PR Bot
00a8503dd7 chore(release): Bumped to Version 15.58.2
## [15.58.2](https://github.com/frappe/erpnext/compare/v15.58.1...v15.58.2) (2025-04-17)

### Bug Fixes

* add group by after user permission condition ([0287f34](0287f34928))
2025-04-17 10:54:22 +00:00
ruthra kumar
76a2eb69a0 Merge pull request #47127 from frappe/mergify/bp/version-15/pr-47118
fix: add group by after user permission condition (backport #47118)
2025-04-17 16:22:18 +05:30
ruthra kumar
673694ae48 Merge pull request #47128 from frappe/mergify/bp/version-15-hotfix/pr-47118
fix: add group by after user permission condition (backport #47118)
2025-04-17 16:22:07 +05:30
venkat102
0287f34928 fix: add group by after user permission condition
(cherry picked from commit 756d496235)
2025-04-17 10:36:57 +00:00
venkat102
f07c3d9124 fix: add group by after user permission condition
(cherry picked from commit 756d496235)
2025-04-17 10:36:57 +00:00
rohitwaghchaure
d18266d839 Merge pull request #47048 from frappe/mergify/bp/version-15-hotfix/pr-46938
fix: group sub assemblies in production plan (backport #46938)
2025-04-17 15:31:08 +05:30
rohitwaghchaure
0c714e2bd9 Merge pull request #47114 from frappe/mergify/bp/version-15-hotfix/pr-47050
fix: consider per_ordered instead of per_billed when creating PO from MR (backport #47050)
2025-04-16 22:49:24 +05:30
Mihir Kandoi
20aba541c4 fix: cherry pick 2025-04-16 20:32:05 +05:30
Mihir Kandoi
be154a469f fix: consider per_ordered instead of per_billed when creating PO from MR
(cherry picked from commit 5a524854de)

# Conflicts:
#	erpnext/stock/doctype/material_request/material_request.js
2025-04-16 10:58:52 +00:00
Frappe PR Bot
741f29e57a chore(release): Bumped to Version 15.58.1
## [15.58.1](https://github.com/frappe/erpnext/compare/v15.58.0...v15.58.1) (2025-04-16)

### Bug Fixes

* revert [#46903](https://github.com/frappe/erpnext/issues/46903) - disable changing customer on opportunity ([5c04d18](5c04d183bf))
2025-04-16 05:15:20 +00:00
ruthra kumar
5090108718 Merge pull request #47110 from frappe/mergify/bp/version-15/pr-47108
revert: disable customer if creating from opportunity (backport #47108)
2025-04-16 10:44:01 +05:30
Shariq Ansari
5c04d183bf fix: revert #46903 - disable changing customer on opportunity
(cherry picked from commit fc16199a49)
2025-04-16 10:38:17 +05:30
Shariq Ansari
bb6d6c3edf Merge pull request #47109 from frappe/mergify/bp/version-15-hotfix/pr-47108 2025-04-16 10:22:33 +05:30
Shariq Ansari
99735e0af4 revert: disable customer if creating from opportunity
(cherry picked from commit fc16199a49)
2025-04-16 04:51:33 +00:00
Frappe PR Bot
929d177b7d chore(release): Bumped to Version 15.58.0
# [15.58.0](https://github.com/frappe/erpnext/compare/v15.57.5...v15.58.0) (2025-04-16)

### Bug Fixes

* added missing project field on pos profile ([8e9ddb7](8e9ddb7a69))
* allow to use batchwise valuation for moving average items ([e479975](e479975f54))
* backport translations from develop ([#47104](https://github.com/frappe/erpnext/issues/47104)) ([188c4f8](188c4f896a))
* batchwise valuation for MA item ([debfcdc](debfcdc61f))
* bypass validation during reposting ([01aad96](01aad96b10))
* child values for tree doctypes and query refactor ([537a8ef](537a8efe7a))
* clarify confirmation message ([35daf66](35daf669fe))
* condition for use_batchwise_valuation ([0ff7465](0ff7465e27))
* configuration to accept partial payment in pos invoice ([#47052](https://github.com/frappe/erpnext/issues/47052)) ([a944853](a944853b56))
* consider negative stock qty in stock reco ([603f737](603f737c99))
* correct doctype in item_wise_purchase register ([b2fb4fb](b2fb4fba51))
* correct function name ([393d245](393d2459b9))
* correct outstanding amount for invoice in dunning ([b7c3fa2](b7c3fa23d2))
* current batch qty showing zero in the stock reconciliation ([24c8a06](24c8a06520))
* enabled allow on submit for asset name field (backport [#47093](https://github.com/frappe/erpnext/issues/47093)) ([#47094](https://github.com/frappe/erpnext/issues/47094)) ([3f652bd](3f652bd4e1))
* fetch exchange rate while creating inter-company order and invoice ([aa0b93d](aa0b93d0b2))
* go for lower case "on" because we already have translations for that ([7cf83ff](7cf83ffce7))
* Group GLs by account for TB generation ([416d9bc](416d9bce2c))
* item code not showing in the error message ([663e2b7](663e2b7e6c))
* make report's "printed on" translatable ([18e9a98](18e9a9881c))
* map tax table while creating purchase order from sales order ([127c7b9](127c7b93ac))
* **Payment Entry:** set account type if missing (backport [#47069](https://github.com/frappe/erpnext/issues/47069)) ([#47070](https://github.com/frappe/erpnext/issues/47070)) ([8e02a9b](8e02a9bc28))
* precision issue on qty_to_be_reserved ([c8691b6](c8691b6516))
* recognize trigger from child table ([cf00d42](cf00d42799))
* Recreate Stock Ledgers issue ([b819e0a](b819e0a61b))
* remove get_items query.run outside of if condition ([e7b5303](e7b5303782))
* remove redundant letter head ([7896f8a](7896f8a855))
* removed display depends on ([00b2553](00b25537f4))
* resolved conflicts ([bde55d2](bde55d2a07))
* revert [#46900](https://github.com/frappe/erpnext/issues/46900) - against_voucher filter in general ledger ([da65f44](da65f44a47))
* test file for v15 ([cc7756d](cc7756dd49))
* translatability ([79ed02b](79ed02bb2c))
* update the modified date in for SLEs and GLs after rename ([21f0dcb](21f0dcbcc3))
* use source_fieldname to validate inventory dimension ([250b670](250b67076d))
* use the actual field label ([0c260ba](0c260baacd))
* wording ([db647a4](db647a4e42))

### Features

* Allow to Make Quality Inspection after Purchase / Delivery ([2e6ba91](2e6ba91589))
* available serial no report ([c472af8](c472af87b2))
* clear payment terms and schedule ([830290c](830290c859))
* fetch source_fieldname for inventory dimension ([2ed6c21](2ed6c211f9))
* **regional:** Address Template for Germany & Add Switzerland Template ([#46737](https://github.com/frappe/erpnext/issues/46737)) ([42479d9](42479d9a7f))
* update due date in payment schedule ([0b0a6b8](0b0a6b8cfa))

### Performance Improvements

* refactored customer ledger summary for performance ([50b2196](50b2196020))
* take query out of loop ([9af5052](9af50528f1))
2025-04-16 04:05:56 +00:00
ruthra kumar
7555d27d82 Merge pull request #47091 from frappe/version-15-hotfix
chore: release v15
2025-04-16 09:34:34 +05:30
ruthra kumar
4a7b91352b Merge pull request #47099 from frappe/mergify/bp/version-15-hotfix/pr-46770
fix: correct outstanding amount for invoice in dunning (backport #46770)
2025-04-16 07:44:32 +05:30
Raffael Meyer
188c4f896a fix: backport translations from develop (#47104) 2025-04-15 19:30:17 +02:00
rohitwaghchaure
9bdeacbbfa Merge pull request #47097 from frappe/mergify/bp/version-15-hotfix/pr-46973
fix: precision issue on qty_to_be_reserved (backport #46973)
2025-04-15 22:01:34 +05:30
ljain112
c42f76b15a chore: added test for Fetch Overdue Payments in dunning
(cherry picked from commit 3b613c44a6)

# Conflicts:
#	erpnext/accounts/doctype/dunning/test_dunning.py
2025-04-15 20:35:09 +05:30
Diptanil Saha
a944853b56 fix: configuration to accept partial payment in pos invoice (#47052) 2025-04-15 19:03:55 +05:30
ljain112
b7c3fa23d2 fix: correct outstanding amount for invoice in dunning
(cherry picked from commit c2bdd30e6d)
2025-04-15 12:21:58 +00:00
ruthra kumar
073a7a6ca6 Merge pull request #47096 from frappe/mergify/bp/version-15-hotfix/pr-47001
fix: fetch exchange rate while creating inter-company order and invoice (backport #47001)
2025-04-15 17:46:38 +05:30
Dany Robert
c8691b6516 fix: precision issue on qty_to_be_reserved
(cherry picked from commit 860699ee7b)

# Conflicts:
#	erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
2025-04-15 17:42:30 +05:30
venkat102
aa0b93d0b2 fix: fetch exchange rate while creating inter-company order and invoice
(cherry picked from commit 145a6c5e2a)
2025-04-15 12:07:54 +00:00
mergify[bot]
3f652bd4e1 fix: enabled allow on submit for asset name field (backport #47093) (#47094)
fix: enabled allow on submit for asset name field (#47093)

(cherry picked from commit e41720f1a3)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2025-04-15 17:33:58 +05:30
marination
1dc98124dc fix: Modify .json from desk to change modified
(cherry picked from commit be556167b1)

# Conflicts:
#	erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
2025-04-15 11:32:47 +00:00
Himanshu Shivhare
5cb8e5dfbd chore: Fix typo "Item Wise Tax Detail "
(cherry picked from commit 9624d56abd)
2025-04-15 11:32:46 +00:00
ruthra kumar
f816934a28 Merge pull request #47086 from frappe/mergify/bp/version-15-hotfix/pr-46640
perf: refactored customer ledger summary for performance (backport #46640)
2025-04-15 13:46:55 +05:30
ruthra kumar
1e340ccd9c chore: use correct Test class 2025-04-15 13:29:22 +05:30
ruthra kumar
21e94148db chore: resolve conflict 2025-04-15 13:20:56 +05:30
ruthra kumar
233a2c08a1 test: basic supplier ledger summary
(cherry picked from commit 71f0f7a0b5)
2025-04-15 13:20:56 +05:30
ruthra kumar
11566e20b5 test: basic output of customer ledger summary report
(cherry picked from commit 9a3a80dfd3)
2025-04-15 13:20:56 +05:30
ljain112
393d2459b9 fix: correct function name
(cherry picked from commit 038355f87b)
2025-04-15 13:20:56 +05:30
ljain112
537a8efe7a fix: child values for tree doctypes and query refactor
(cherry picked from commit fca46e0b2d)

# Conflicts:
#	erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
2025-04-15 13:20:56 +05:30
ljain112
50b2196020 perf: refactored customer ledger summary for performance
(cherry picked from commit e84e49345a)

# Conflicts:
#	erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
2025-04-15 13:20:56 +05:30
ruthra kumar
0ad8935f2c Merge pull request #47081 from frappe/mergify/bp/version-15-hotfix/pr-46789
fix: map tax table while creating purchase order from sales order (backport #46789)
2025-04-15 12:10:21 +05:30
ruthra kumar
148287d8a1 Merge pull request #46935 from frappe/mergify/bp/version-15-hotfix/pr-46171
Fix validation mismatch in inventory dimension fields (backport #46171)
2025-04-15 11:54:24 +05:30
ruthra kumar
f4854a9a02 Merge pull request #46816 from frappe/mergify/bp/version-15-hotfix/pr-46737
feat(regional): Address Template for Germany & Add Switzerland Template (backport #46737)
2025-04-15 11:51:17 +05:30
ruthra kumar
b7cbc66a28 chore: resolve conflict 2025-04-15 11:45:17 +05:30
Sugesh393
5a20b9e94f test: add unit test to validate tax values in Purchase Order from Sales Order
(cherry picked from commit a393195866)

# Conflicts:
#	erpnext/selling/doctype/sales_order/test_sales_order.py
2025-04-15 06:13:11 +00:00
Sugesh393
127c7b93ac fix: map tax table while creating purchase order from sales order
(cherry picked from commit 1e18569be7)
2025-04-15 06:13:11 +00:00
mergify[bot]
c77f7f4ff0 test(Payment Entry): account type is set (backport #47071) (#47073)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-04-14 19:26:11 +02:00
mergify[bot]
8e02a9bc28 fix(Payment Entry): set account type if missing (backport #47069) (#47070)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Payment Entry): set account type if missing (#47069)
2025-04-14 18:54:12 +02:00
rohitwaghchaure
640012429e Merge pull request #47063 from frappe/mergify/bp/version-15-hotfix/pr-47058
fix: consider negative stock qty in stock reconciliation (backport #47058)
2025-04-14 20:30:25 +05:30
Rohit Waghchaure
603f737c99 fix: consider negative stock qty in stock reco
(cherry picked from commit 15272d0e56)
2025-04-14 12:39:35 +00:00
ruthra kumar
4fd99ed763 Merge pull request #46773 from frappe/mergify/bp/version-15-hotfix/pr-46683
fix: Set complete contact details for `Employee` in PE (backport #46683)
2025-04-14 17:30:51 +05:30
Frappe PR Bot
2eb7a688cb chore(release): Bumped to Version 15.57.5
## [15.57.5](https://github.com/frappe/erpnext/compare/v15.57.4...v15.57.5) (2025-04-14)

### Bug Fixes

* revert [#46900](https://github.com/frappe/erpnext/issues/46900) - against_voucher filter in general ledger ([969c354](969c3549d5))
2025-04-14 08:24:20 +00:00
ruthra kumar
19a5b923b9 Merge pull request #47053 from frappe/mergify/bp/version-15/pr-47049
Revert "fix: remove against_voucher and against_voucher_type column from General Ledger Report" (backport #47049)
2025-04-14 13:52:55 +05:30
ruthra kumar
77bbaef5a6 Merge pull request #47055 from frappe/mergify/bp/version-15-hotfix/pr-47049
Revert "fix: remove against_voucher and against_voucher_type column from General Ledger Report" (backport #47049)
2025-04-14 13:52:05 +05:30
Mihir Kandoi
c58800a929 fix: linter 2025-04-14 13:33:48 +05:30
Mihir Kandoi
924e9b94b6 fix: import error 2025-04-14 13:28:06 +05:30
ruthra kumar
6bfb3c6905 chore: resolve conflict 2025-04-14 13:26:32 +05:30
ruthra kumar
a0908522c1 chore: resolve conflict 2025-04-14 13:21:33 +05:30
ruthra kumar
da65f44a47 fix: revert #46900 - against_voucher filter in general ledger
(cherry picked from commit adb331ef71)

# Conflicts:
#	erpnext/accounts/report/general_ledger/general_ledger.py
2025-04-14 07:50:28 +00:00
ruthra kumar
969c3549d5 fix: revert #46900 - against_voucher filter in general ledger
(cherry picked from commit adb331ef71)

# Conflicts:
#	erpnext/accounts/report/general_ledger/general_ledger.py
2025-04-14 07:49:32 +00:00
Mihir Kandoi
13d3b27a1f fix: test cases error
(cherry picked from commit 8df18762a9)
2025-04-14 06:41:28 +00:00
Mihir Kandoi
dedb19e3e9 fix: test cases
(cherry picked from commit a7394329ca)
2025-04-14 06:41:27 +00:00
Mihir Kandoi
b3e852adfc fix: logic and added test case
(cherry picked from commit f071255340)
2025-04-14 06:41:27 +00:00
Mihir Kandoi
73683b2754 fix: group sub assemblies in production plan
(cherry picked from commit f58abed935)
2025-04-14 06:41:27 +00:00
Frappe PR Bot
1da2577035 chore(release): Bumped to Version 15.57.4
## [15.57.4](https://github.com/frappe/erpnext/compare/v15.57.3...v15.57.4) (2025-04-13)

### Bug Fixes

* Group GLs by account for TB generation ([70fa366](70fa366216))
2025-04-13 14:13:43 +00:00
Deepesh Garg
e5d0ab6bab Merge pull request #47045 from frappe/mergify/bp/version-15/pr-47044
fix: Group GLs by account for TB generation
2025-04-13 19:42:19 +05:30
Deepesh Garg
70fa366216 fix: Group GLs by account for TB generation
(cherry picked from commit f894c6d275)
(cherry picked from commit 416d9bce2c)
2025-04-13 14:02:23 +00:00
Deepesh Garg
f1254e51e5 Merge pull request #47044 from frappe/mergify/bp/version-15-hotfix/pr-47043
fix: Group GLs by account for TB generation (backport #47043)
2025-04-13 19:31:29 +05:30
Deepesh Garg
416d9bce2c fix: Group GLs by account for TB generation
(cherry picked from commit f894c6d275)
2025-04-13 13:57:23 +00:00
Frappe PR Bot
065c9fa85f chore(release): Bumped to Version 15.57.3
## [15.57.3](https://github.com/frappe/erpnext/compare/v15.57.2...v15.57.3) (2025-04-12)

### Bug Fixes

* correct doctype in item_wise_purchase register ([cd68832](cd68832aa0))
2025-04-12 02:01:06 +00:00
ruthra kumar
bcb45ce8ef Merge pull request #47031 from frappe/mergify/bp/version-15/pr-47012
fix: correct doctype in item_wise_purchase register (backport #47012)
2025-04-12 07:29:44 +05:30
ruthra kumar
f5fd255e82 Merge pull request #47033 from frappe/mergify/bp/version-15-hotfix/pr-47012
fix: correct doctype in item_wise_purchase register (backport #47012)
2025-04-12 07:29:14 +05:30
ljain112
b2fb4fba51 fix: correct doctype in item_wise_purchase register
(cherry picked from commit b8b8dce733)
2025-04-12 01:42:25 +00:00
ljain112
cd68832aa0 fix: correct doctype in item_wise_purchase register
(cherry picked from commit b8b8dce733)
2025-04-12 01:41:15 +00:00
rohitwaghchaure
1b8a317de9 Merge pull request #47027 from frappe/mergify/bp/version-15-hotfix/pr-47026
fix: batchwise valuation for MA item (backport #47026)
2025-04-11 22:56:25 +05:30
Rohit Waghchaure
debfcdc61f fix: batchwise valuation for MA item
(cherry picked from commit 504b8c0a68)
2025-04-11 16:27:52 +00:00
Frappe PR Bot
8a33866a8c chore(release): Bumped to Version 15.57.2
## [15.57.2](https://github.com/frappe/erpnext/compare/v15.57.1...v15.57.2) (2025-04-11)

### Bug Fixes

* condition for use_batchwise_valuation ([18dd128](18dd128838))
* removed display depends on ([b8436db](b8436dbd21))
2025-04-11 15:16:34 +00:00
rohitwaghchaure
d3a987800b Merge pull request #47025 from frappe/mergify/bp/version-15/pr-47021
fix: removed display depends on (backport #47020) (backport #47021)
2025-04-11 20:45:07 +05:30
rohitwaghchaure
1e19bc7f3f Merge pull request #47024 from frappe/mergify/bp/version-15/pr-47023
fix: condition for use_batchwise_valuation (backport #47022) (backport #47023)
2025-04-11 20:44:54 +05:30
rohitwaghchaure
fa7675488d chore: fix conflicts 2025-04-11 20:26:43 +05:30
Rohit Waghchaure
b8436dbd21 fix: removed display depends on
(cherry picked from commit e0bf45e03b)
(cherry picked from commit 00b25537f4)

# Conflicts:
#	erpnext/stock/doctype/stock_settings/stock_settings.json
2025-04-11 14:24:37 +00:00
Rohit Waghchaure
18dd128838 fix: condition for use_batchwise_valuation
(cherry picked from commit cc171d9706)
(cherry picked from commit 0ff7465e27)
2025-04-11 14:24:31 +00:00
rohitwaghchaure
706092061b Merge pull request #47021 from frappe/mergify/bp/version-15-hotfix/pr-47020
fix: removed display depends on (backport #47020)
2025-04-11 19:53:43 +05:30
rohitwaghchaure
c6c8aa02eb Merge pull request #47023 from frappe/mergify/bp/version-15-hotfix/pr-47022
fix: condition for use_batchwise_valuation (backport #47022)
2025-04-11 19:53:22 +05:30
Rohit Waghchaure
0ff7465e27 fix: condition for use_batchwise_valuation
(cherry picked from commit cc171d9706)
2025-04-11 14:06:08 +00:00
Rohit Waghchaure
00b25537f4 fix: removed display depends on
(cherry picked from commit e0bf45e03b)
2025-04-11 13:46:39 +00:00
Frappe PR Bot
ff41ed534b chore(release): Bumped to Version 15.57.1
## [15.57.1](https://github.com/frappe/erpnext/compare/v15.57.0...v15.57.1) (2025-04-11)

### Bug Fixes

* allow to use batchwise valuation for moving average items ([dded682](dded682feb))
2025-04-11 12:22:21 +00:00
rohitwaghchaure
f2684d9b4c Merge pull request #47018 from frappe/mergify/bp/version-15/pr-47017
fix: allow to use batch-wise valuation for moving average items (backport #47015) (backport #47017)
2025-04-11 17:50:55 +05:30
rohitwaghchaure
6dee592ba7 Merge pull request #47004 from frappe/mergify/bp/version-15-hotfix/pr-47002
feat: Allow to Make Quality Inspection after Purchase / Delivery (backport #47002)
2025-04-11 17:26:53 +05:30
Rohit Waghchaure
dded682feb fix: allow to use batchwise valuation for moving average items
(cherry picked from commit 65ba79bb85)
(cherry picked from commit e479975f54)
2025-04-11 11:56:35 +00:00
rohitwaghchaure
8233aadd50 Merge pull request #47017 from frappe/mergify/bp/version-15-hotfix/pr-47015
fix: allow to use batch-wise valuation for moving average items (backport #47015)
2025-04-11 17:25:29 +05:30
Rohit Waghchaure
e479975f54 fix: allow to use batchwise valuation for moving average items
(cherry picked from commit 65ba79bb85)
2025-04-11 11:38:32 +00:00
barredterra
2431141062 fix: remove invalid parameter 2025-04-11 13:07:44 +02:00
Patrick Eissler
b0e8f85a27 refactor: make linter happy 2025-04-11 12:25:58 +02:00
Patrick Eissler
b0f3d62dd0 fix: only update User Permissions if a relevant field has changed 2025-04-11 12:25:26 +02:00
Patrick Eissler
e47d07d98c chore: use existing utility function 2025-04-11 12:24:25 +02:00
Patrick Eissler
7ab81b7e54 fix(Employee): remove User Permissions if create_user_permission is unchecked 2025-04-11 12:24:04 +02:00
rohitwaghchaure
5e1d3f77bb chore: fix conflicts 2025-04-11 15:35:36 +05:30
rohitwaghchaure
00ac8597b0 chore: fix conflicts 2025-04-11 15:34:45 +05:30
rohitwaghchaure
4163b18ec3 Merge pull request #47005 from frappe/mergify/bp/version-15-hotfix/pr-46997
fix: update the modified date in for SLEs and GLs after rename (backport #46997)
2025-04-11 14:43:02 +05:30
Rohit Waghchaure
21f0dcbcc3 fix: update the modified date in for SLEs and GLs after rename
(cherry picked from commit dc5a5ef258)
2025-04-10 12:10:48 +00:00
Rohit Waghchaure
2e6ba91589 feat: Allow to Make Quality Inspection after Purchase / Delivery
(cherry picked from commit 8eaa2afeb7)

# Conflicts:
#	erpnext/stock/doctype/stock_settings/stock_settings.json
2025-04-10 12:10:47 +00:00
rohitwaghchaure
14a502c956 Merge pull request #46998 from frappe/mergify/bp/version-15-hotfix/pr-46831
fix: current batch qty showing zero in the stock reconciliation (backport #46831)
2025-04-10 15:39:34 +05:30
Rohit Waghchaure
24c8a06520 fix: current batch qty showing zero in the stock reconciliation
(cherry picked from commit a5c62f8623)
2025-04-10 08:30:28 +00:00
Frappe PR Bot
1326ba5326 chore(release): Bumped to Version 15.57.0
# [15.57.0](https://github.com/frappe/erpnext/compare/v15.56.0...v15.57.0) (2025-04-10)

### Bug Fixes

* bypass validation during reposting ([eaaac6e](eaaac6e596))
* item code not showing in the error message ([837509a](837509ae47))
* Recreate Stock Ledgers issue ([3db70fe](3db70febe1))
* remove get_items query.run outside of if condition ([bff4902](bff4902993))
* test file for v15 ([88f1cf2](88f1cf2943))

### Features

* available serial no report ([0f6a7ed](0f6a7edb53))

### Performance Improvements

* take query out of loop ([c7fead6](c7fead6bb0))
2025-04-10 07:31:47 +00:00
rohitwaghchaure
d047c965c6 Merge pull request #46990 from frappe/mergify/bp/version-15/pr-46947
feat: available serial no report (backport #46383) (backport #46947)
2025-04-10 12:59:58 +05:30
rohitwaghchaure
72e9844d27 Merge pull request #46987 from frappe/mergify/bp/version-15/pr-46985
fix: item code not showing in the error message (backport #46933) (backport #46985)
2025-04-10 12:59:36 +05:30
rohitwaghchaure
528b7534f7 Merge pull request #46991 from frappe/mergify/bp/version-15/pr-46989
fix: Recreate Stock Ledgers issue (backport #46965) (backport #46989)
2025-04-10 12:59:23 +05:30
rohitwaghchaure
3141e4a54f Merge pull request #46992 from frappe/mergify/bp/version-15/pr-46988
fix: bypass validation during reposting (backport #46978) (backport #46988)
2025-04-10 12:59:02 +05:30
Rohit Waghchaure
eaaac6e596 fix: bypass validation during reposting
(cherry picked from commit 3697b9fd9b)
(cherry picked from commit 01aad96b10)
2025-04-10 06:52:18 +00:00
Rohit Waghchaure
3db70febe1 fix: Recreate Stock Ledgers issue
(cherry picked from commit 229a4cef45)
(cherry picked from commit b819e0a61b)
2025-04-10 06:52:17 +00:00
rohitwaghchaure
13f73b59df Merge pull request #46988 from frappe/mergify/bp/version-15-hotfix/pr-46978
fix: bypass validation during reposting (backport #46978)
2025-04-10 12:21:36 +05:30
rohitwaghchaure
b4efba80a7 Merge pull request #46989 from frappe/mergify/bp/version-15-hotfix/pr-46965
fix: Recreate Stock Ledgers issue (backport #46965)
2025-04-10 12:21:00 +05:30
Mihir Kandoi
88f1cf2943 fix: test file for v15
(cherry picked from commit cc7756dd49)
2025-04-10 06:14:40 +00:00
Mihir Kandoi
81f3f43e14 refactor: split and clean execute function to be more readable
(cherry picked from commit 036af54d54)
(cherry picked from commit 6fedb7b9db)
2025-04-10 06:14:40 +00:00
Mihir Kandoi
c7fead6bb0 perf: take query out of loop
(cherry picked from commit 26de902496)
(cherry picked from commit 9af50528f1)
2025-04-10 06:14:40 +00:00
Mihir Kandoi
bff4902993 fix: remove get_items query.run outside of if condition
(cherry picked from commit 80c17cc005)
(cherry picked from commit e7b5303782)
2025-04-10 06:14:39 +00:00
Mihir Kandoi
9cdd32ad6b refactor: import functions in new report instead of redundant code
(cherry picked from commit 501f07803e)
(cherry picked from commit 0ec026ec7e)
2025-04-10 06:14:39 +00:00
Mihir Kandoi
0f6a7edb53 feat: available serial no report
(cherry picked from commit 5592d8e87f)
(cherry picked from commit c472af87b2)
2025-04-10 06:14:39 +00:00
Rohit Waghchaure
b819e0a61b fix: Recreate Stock Ledgers issue
(cherry picked from commit 229a4cef45)
2025-04-10 06:13:44 +00:00
Rohit Waghchaure
01aad96b10 fix: bypass validation during reposting
(cherry picked from commit 3697b9fd9b)
2025-04-10 06:13:26 +00:00
Rohit Waghchaure
837509ae47 fix: item code not showing in the error message
(cherry picked from commit 86dee69c2f)
(cherry picked from commit 663e2b7e6c)
2025-04-10 06:02:32 +00:00
rohitwaghchaure
860aba47ef Merge pull request #46985 from frappe/mergify/bp/version-15-hotfix/pr-46933
fix: item code not showing in the error message (backport #46933)
2025-04-10 11:31:59 +05:30
rohitwaghchaure
1b6c4b6b4b Merge pull request #46947 from frappe/mergify/bp/version-15-hotfix/pr-46383
feat: available serial no report (backport #46383)
2025-04-10 11:29:02 +05:30
Rohit Waghchaure
663e2b7e6c fix: item code not showing in the error message
(cherry picked from commit 86dee69c2f)
2025-04-10 05:35:55 +00:00
Mihir Kandoi
cc7756dd49 fix: test file for v15 2025-04-09 20:47:10 +05:30
Diptanil Saha
d79de4a434 Merge pull request #46969 from frappe/mergify/bp/version-15-hotfix/pr-46964
fix: added missing project field on pos profile (backport #46964)
2025-04-09 15:57:57 +05:30
Diptanil Saha
b8ef83e1ea chore: resolve conflict 2025-04-09 15:39:15 +05:30
diptanilsaha
8e9ddb7a69 fix: added missing project field on pos profile
(cherry picked from commit 821d64241a)

# Conflicts:
#	erpnext/accounts/doctype/pos_profile/pos_profile.json
2025-04-09 10:05:23 +00:00
Raffael Meyer
d43ce7f8e0 Merge pull request #46960 from frappe/mergify/bp/version-15-hotfix/pr-46959
fix: interaction with due date / payment terms / payment schedule (backport #46959)
2025-04-08 21:34:53 +02:00
barredterra
b5353e4ad1 chore: add german translation 2025-04-08 21:19:34 +02:00
barredterra
14efeb4acd chore: add context
(cherry picked from commit c00f62d54a)
2025-04-08 19:01:35 +00:00
barredterra
35daf669fe fix: clarify confirmation message
(cherry picked from commit 57be8a85d6)
2025-04-08 19:01:34 +00:00
barredterra
0c260baacd fix: use the actual field label
(cherry picked from commit 8a4db69581)
2025-04-08 19:01:34 +00:00
barredterra
cf00d42799 fix: recognize trigger from child table
(cherry picked from commit c55c77f4e9)
2025-04-08 19:01:34 +00:00
barredterra
b06a86541b refactor: use doc parameter instead of this.frm.doc
(cherry picked from commit 87c21a89fe)
2025-04-08 19:01:34 +00:00
Raffael Meyer
f762f7ec3e Merge pull request #46957 from barredterra/update-due-date
feat: update due date in payment schedule
2025-04-08 20:47:13 +02:00
Raffael Meyer
12bd9af3aa Merge pull request #46826 from frappe/mergify/bp/version-15-hotfix/pr-40050
fix: handle due date change (backport #40050)
2025-04-08 20:33:22 +02:00
barredterra
0b0a6b8cfa feat: update due date in payment schedule
Partial backport of b629356b7c
2025-04-08 20:12:25 +02:00
barredterra
830290c859 feat: clear payment terms and schedule 2025-04-08 20:10:31 +02:00
barredterra
98b75aaa38 Merge remote-tracking branch 'upstream/version-15-hotfix' into mergify/bp/version-15-hotfix/pr-40050 2025-04-08 19:59:34 +02:00
Raffael Meyer
d319d89988 Merge pull request #46949 from frappe/mergify/bp/version-15-hotfix/pr-46913
fix: improve translatability of query report print formats (backport #46913)
2025-04-08 16:11:18 +02:00
barredterra
d94ebd0c78 chore: add missing german translation 2025-04-08 15:56:48 +02:00
barredterra
7896f8a855 fix: remove redundant letter head 2025-04-08 15:33:12 +02:00
barredterra
7cf83ffce7 fix: go for lower case "on" because we already have translations for that 2025-04-08 15:31:25 +02:00
barredterra
18e9a9881c fix: make report's "printed on" translatable 2025-04-08 15:30:54 +02:00
Frappe PR Bot
52257c946e chore(release): Bumped to Version 15.56.0
# [15.56.0](https://github.com/frappe/erpnext/compare/v15.55.5...v15.56.0) (2025-04-08)

### Bug Fixes

* **accounting:** update outstanding amount based on update_outstanding_for_self ([fb06f88](fb06f886d2))
* add `Not Cancelled` filter for `payment_entry` in Bank Transaction ([5d47db7](5d47db78e6))
* check payments against orders for getting request amount ([cf7252d](cf7252d3e7))
* condition to update the last puurchase rate ([353fa0c](353fa0cbc3))
* correct mapping(schedule_date) sales order to material request ([e2c8ed2](e2c8ed2afd))
* correct payment request amount ([23c76aa](23c76aa530))
* decimal values causing incorrect batch picking ([c5efdda](c5efddae16))
* do not use self object for setting party and party type ([d1311e6](d1311e619d))
* **Dunning:** undefined variable (backport [#46868](https://github.com/frappe/erpnext/issues/46868)) ([#46869](https://github.com/frappe/erpnext/issues/46869)) ([f63595c](f63595cf0c))
* empty party filter on change of party type in General Ledger Report. ([95cc282](95cc2827c6))
* expense account in the stock entry ([62f342e](62f342ef8b))
* Fix fieldtype in UnReconcile dialog ([7dfff8d](7dfff8d3a2))
* for deadlock issue keep status as In Progress ([34e66b1](34e66b1b27))
* ignore backflush setting on subcontracting return ([ca56150](ca56150918))
* improved rounding adjustment when applying discount (backport [#46720](https://github.com/frappe/erpnext/issues/46720)) ([7b864be](7b864bece8))
* include auto_reconcile_vouchers flag in background job ([26f93f5](26f93f57b8))
* incorrect condition ([502b8f2](502b8f25b3))
* inventory dimensions columns visibility depends on filter ([fe0e5c2](fe0e5c2d48))
* make message translatable (backport [#46863](https://github.com/frappe/erpnext/issues/46863)) ([#46866](https://github.com/frappe/erpnext/issues/46866)) ([d7bb4a2](d7bb4a288c))
* multiple Bank Reconciliation Tool issues ([#46644](https://github.com/frappe/erpnext/issues/46644)) ([e168483](e168483a58))
* **payment term:** allocate payment amount when payment term is fetched from order ([1b9980b](1b9980bb86))
* **portal:** context pay_amount for button ([b7ae17a](b7ae17aaaf))
* **portal:** payment amount for orders ([b0302d7](b0302d71b7))
* pos checking opened entry closed or not (backport [#46726](https://github.com/frappe/erpnext/issues/46726)) ([#46830](https://github.com/frappe/erpnext/issues/46830)) ([80f144a](80f144ac22))
* pos closed dialog on pos closing entry (backport [#46881](https://github.com/frappe/erpnext/issues/46881)) ([#46882](https://github.com/frappe/erpnext/issues/46882)) ([c1fe8f6](c1fe8f6000))
* pos opening entry's status not getting updated on cancel (backport [#46909](https://github.com/frappe/erpnext/issues/46909)) ([#46911](https://github.com/frappe/erpnext/issues/46911)) ([8b11d13](8b11d13cd4))
* remove against_voucher from General Ledger Report ([ba1e7e1](ba1e7e17fb))
* remove all serial/batch fields when use button is unselected ([13f1afa](13f1afa141))
* removed customer_group query in customer.js ([1aac8d3](1aac8d31f4))
* removed hardcoded search fields to fix performance issue ([48822f6](48822f6fee))
* resolve conflicts ([4d8984e](4d8984e4e9))
* restrict customer change if creating from opportunity ([2661147](26611475f6))
* set draft QC in purchase document on creation of qc ([54159b9](54159b9e5e))
* slow query ([23dc9d5](23dc9d5872))
* slow query ([af0fb13](af0fb131a2))
* stock entry repack amount calculation ([8c61639](8c61639062))
* Translate UnReconcile dialog title (backport [#46818](https://github.com/frappe/erpnext/issues/46818)) ([#46861](https://github.com/frappe/erpnext/issues/46861)) ([fcade5d](fcade5d8cd))
* update outstanding with precision ([e115409](e1154090f6))
* update payment amount if automatically_fetch_payment_terms is enabled ([ea289a4](ea289a40fb))
* update posting date before running validations ([2bf44dc](2bf44dc326))
* use `grand_total_diff` instead of `rounding_adjustment` in `taxes_and_totals` ([55b17b9](55b17b918f))
* use docstatus for status filter ([ab52524](ab52524f12))
* use get instead of dot operator to access dict value ([f2df8e5](f2df8e531d))
* use work_order bom_no if no bom present in operation ([c6979ab](c6979ab260))
* user permissions in sales and purchase report ([c705623](c705623fdc))
* validate if pos is opened before pos invoice creation (backport [#46907](https://github.com/frappe/erpnext/issues/46907)) ([#46910](https://github.com/frappe/erpnext/issues/46910)) ([999ab28](999ab28bf0))
* valuation rate not updating for raw materials ([454dd3a](454dd3a2f1))

### Features

* allow UOMs to select for which converstion rate defined in item master ([288aad6](288aad6f5d))
* **Customer:** add Dunning to dashboard ([1128b5f](1128b5f09c))
* option to recreate Stock Ledger Entries against stock transactions ([64fdcb7](64fdcb752d))

### Performance Improvements

* reduce query when validating any doc ([890abf6](890abf6b90))
* Stock entry cancel is slow ([1bdfd33](1bdfd33816))
2025-04-08 13:08:39 +00:00
ruthra kumar
c4ab17b947 Merge pull request #46940 from frappe/version-15-hotfix
chore: release v15
2025-04-08 18:37:09 +05:30
rohitwaghchaure
10cf575bba Merge pull request #46946 from frappe/mergify/bp/version-15-hotfix/pr-46942
fix: ignore backflush setting on subcontracting return (backport #46942)
2025-04-08 18:12:52 +05:30
Mihir Kandoi
6fedb7b9db refactor: split and clean execute function to be more readable
(cherry picked from commit 036af54d54)
2025-04-08 12:17:05 +00:00
Mihir Kandoi
9af50528f1 perf: take query out of loop
(cherry picked from commit 26de902496)
2025-04-08 12:17:05 +00:00
Mihir Kandoi
e7b5303782 fix: remove get_items query.run outside of if condition
(cherry picked from commit 80c17cc005)
2025-04-08 12:17:05 +00:00
Mihir Kandoi
0ec026ec7e refactor: import functions in new report instead of redundant code
(cherry picked from commit 501f07803e)
2025-04-08 12:17:04 +00:00
Mihir Kandoi
c472af87b2 feat: available serial no report
(cherry picked from commit 5592d8e87f)
2025-04-08 12:17:04 +00:00
Mihir Kandoi
ca56150918 fix: ignore backflush setting on subcontracting return
(cherry picked from commit 7479e1ec32)
2025-04-08 12:15:44 +00:00
ruthra kumar
617bebfa8f Merge pull request #46945 from frappe/mergify/bp/version-15-hotfix/pr-46892
fix: use get instead of dot operator to access dict value to prevent no attribute error (backport #46892)
2025-04-08 17:20:45 +05:30
Mihir Kandoi
f2df8e531d fix: use get instead of dot operator to access dict value
(cherry picked from commit 7fb75f0482)
2025-04-08 11:34:11 +00:00
ruthra kumar
c47b3c3642 Merge pull request #46932 from frappe/mergify/bp/version-15-hotfix/pr-46626
fix: correct payment request amount (backport #46626)
2025-04-08 16:56:25 +05:30
ruthra kumar
e271933e43 chore: resolve conflict 2025-04-08 16:33:07 +05:30
ljain112
23c76aa530 fix: correct payment request amount
(cherry picked from commit 913c60d77b)

# Conflicts:
#	erpnext/accounts/doctype/payment_request/payment_request.py
2025-04-08 16:33:07 +05:30
ruthra kumar
bed960df36 Merge pull request #46939 from frappe/mergify/bp/version-15-hotfix/pr-39701
fix: check order paid amount before payment request (backport #39701)
2025-04-08 16:30:28 +05:30
ruthra kumar
36bc87270c Merge pull request #46936 from frappe/mergify/bp/version-15-hotfix/pr-46631
fix: update outstanding for self (backport #46631)
2025-04-08 16:21:49 +05:30
ljain112
4d8984e4e9 fix: resolve conflicts 2025-04-08 15:54:44 +05:30
ruthra kumar
4eb0f39af7 chore: pass doctype and name 2025-04-08 15:15:24 +05:30
Gursheen Anand
b7ae17aaaf fix(portal): context pay_amount for button
(cherry picked from commit 7efb5a8cb5)

# Conflicts:
#	erpnext/templates/pages/order.html
2025-04-08 09:33:20 +00:00
Gursheen Anand
b0302d71b7 fix(portal): payment amount for orders
(cherry picked from commit c18ff5bd25)

# Conflicts:
#	erpnext/templates/pages/order.py
2025-04-08 09:33:20 +00:00
Gursheen Anand
cf7252d3e7 fix: check payments against orders for getting request amount
(cherry picked from commit f7face43cd)

# Conflicts:
#	erpnext/accounts/doctype/payment_request/payment_request.py
2025-04-08 09:33:19 +00:00
ruthra kumar
d6b488f529 chore: resolve conflict 2025-04-08 14:36:15 +05:30
Bhavan23
88e11d6fd9 test: add unit test to validate outstanding amount for update_outstanding_for_self checkbox enabled
(cherry picked from commit 7b0882600a)
2025-04-08 08:59:04 +00:00
Bhavan23
fb06f886d2 fix(accounting): update outstanding amount based on update_outstanding_for_self
fix(accounting): against voucher has been already paid show proper message and update update_outstanding_for_self as 1

(cherry picked from commit 222f1834f1)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2025-04-08 08:59:04 +00:00
JK1117
250b67076d fix: use source_fieldname to validate inventory dimension
(cherry picked from commit daa5bebdd0)
2025-04-08 08:57:59 +00:00
JK1117
2ed6c211f9 feat: fetch source_fieldname for inventory dimension
(cherry picked from commit 4e63ee1a70)
2025-04-08 08:57:59 +00:00
rohitwaghchaure
bbc9cedf4d Merge pull request #46916 from frappe/mergify/bp/version-15-hotfix/pr-46898
fix: removed hardcoded search fields to fix performance issue (backport #46898)
2025-04-08 14:18:16 +05:30
rohitwaghchaure
b824038d37 chore: fix conflicts 2025-04-08 13:32:49 +05:30
ruthra kumar
b730fdd007 Merge pull request #46930 from frappe/mergify/bp/version-15-hotfix/pr-46821
fix: removed customer_group query in customer.js (backport #46821)
2025-04-08 13:09:37 +05:30
Shariq Ansari
df744135ff Merge pull request #46927 from frappe/mergify/bp/version-15-hotfix/pr-46903
fix: restrict customer change if creating from opportunity (backport #46903)
2025-04-08 13:08:34 +05:30
ljain112
1aac8d31f4 fix: removed customer_group query in customer.js
(cherry picked from commit f49adfdd98)
2025-04-08 07:28:31 +00:00
Shariq Ansari
26611475f6 fix: restrict customer change if creating from opportunity
(cherry picked from commit dc4819e897)
2025-04-08 06:37:36 +00:00
ruthra kumar
5b3ae9508a Merge pull request #46925 from frappe/mergify/bp/version-15-hotfix/pr-46709
fix: user permissions in sales and purchase report (backport #46709)
2025-04-08 11:52:46 +05:30
ruthra kumar
81f061634e Merge pull request #46926 from frappe/mergify/bp/version-15-hotfix/pr-46819
fix: Fix fieldtype in UnReconcile dialog (backport #46819)
2025-04-08 11:51:56 +05:30
rohitwaghchaure
1e4918fb17 Merge pull request #46918 from frappe/mergify/bp/version-15-hotfix/pr-46893
fix: inventory dimensions columns visibility depends on filter (backport #46893)
2025-04-08 11:48:20 +05:30
Corentin Forler
7dfff8d3a2 fix: Fix fieldtype in UnReconcile dialog
(cherry picked from commit 665645721b)
2025-04-08 05:56:37 +00:00
ljain112
c705623fdc fix: user permissions in sales and purchase report
(cherry picked from commit f4bc1dfd00)
2025-04-08 05:41:01 +00:00
ruthra kumar
6ba4c092ba Merge pull request #46921 from frappe/mergify/bp/version-15-hotfix/pr-46804
fix: update outstanding with precision (backport #46804)
2025-04-08 11:08:09 +05:30
ljain112
e1154090f6 fix: update outstanding with precision
(cherry picked from commit aadda9f606)
2025-04-08 04:29:48 +00:00
Rohit Waghchaure
fe0e5c2d48 fix: inventory dimensions columns visibility depends on filter
(cherry picked from commit 2b411fb7f5)
2025-04-08 03:21:15 +00:00
Rohit Waghchaure
48822f6fee fix: removed hardcoded search fields to fix performance issue
(cherry picked from commit 216bf2456e)

# Conflicts:
#	erpnext/manufacturing/doctype/bom/bom.py
2025-04-08 03:20:13 +00:00
Raffael Meyer
8276e8e8b3 chore: fix german translations (#46912) 2025-04-08 02:39:04 +02:00
mergify[bot]
8b11d13cd4 fix: pos opening entry's status not getting updated on cancel (backport #46909) (#46911)
fix: pos opening entry's status not getting updated on cancel (#46909)

(cherry picked from commit 6fae98afda)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-07 21:03:20 +05:30
mergify[bot]
999ab28bf0 fix: validate if pos is opened before pos invoice creation (backport #46907) (#46910)
fix: validate if pos is opened before pos invoice creation (#46907)

* fix: validate if pos is opened before pos invoice creation

* fix: added title on throw dialog

* test: fixed failing test

(cherry picked from commit 3de1b22480)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-07 20:54:23 +05:30
ruthra kumar
ed36fc0530 Merge pull request #46905 from frappe/mergify/bp/version-15-hotfix/pr-46895
fix: empty party filter on change of party type in General Ledger Report (backport #46895)
2025-04-07 17:56:07 +05:30
ljain112
95cc2827c6 fix: empty party filter on change of party type in General Ledger Report.
(cherry picked from commit 9c68bc22fa)
2025-04-07 12:13:16 +00:00
ruthra kumar
03aa5a1294 Merge pull request #46902 from frappe/mergify/bp/version-15-hotfix/pr-46900
fix: remove against_voucher and against_voucher_type column from General Ledger Report (backport #46900)
2025-04-07 17:36:42 +05:30
ljain112
ba1e7e17fb fix: remove against_voucher from General Ledger Report
(cherry picked from commit 6d1f119a0f)
2025-04-07 17:14:07 +05:30
ruthra kumar
6adf79b8a5 Merge pull request #46897 from frappe/mergify/bp/version-15-hotfix/pr-46728
fix: update posting date before running validations (backport #46728)
2025-04-07 14:49:14 +05:30
Dany Robert
2bf44dc326 fix: update posting date before running validations
(cherry picked from commit d04dbd8ed9)
2025-04-07 09:01:59 +00:00
ruthra kumar
1500691bca Merge pull request #46844 from frappe/mergify/bp/version-15-hotfix/pr-46823
chore: update links to Frappe School (backport #46823)
2025-04-07 11:45:25 +05:30
ruthra kumar
3d57916832 chore: resolve conflict 2025-04-07 11:28:39 +05:30
ruthra kumar
4e9a3b687f Merge pull request #46891 from frappe/mergify/bp/version-15-hotfix/pr-46637
fix(payment term): allocate payment amount when payment term is fetched from order (backport #46637)
2025-04-07 11:25:45 +05:30
rohitwaghchaure
3181dc8ed1 Merge pull request #46885 from frappe/mergify/bp/version-15-hotfix/pr-46836
fix: remove all serial/batch fields when use button is unselected (backport #46836)
2025-04-07 11:07:12 +05:30
venkat102
ea289a40fb fix: update payment amount if automatically_fetch_payment_terms is enabled
(cherry picked from commit 7bf1a39861)
2025-04-07 05:34:25 +00:00
venkat102
3b66c48479 test: validate payment schedule based on invoice amount
(cherry picked from commit 7785296573)
2025-04-07 05:34:25 +00:00
venkat102
1b9980bb86 fix(payment term): allocate payment amount when payment term is fetched from order
(cherry picked from commit 5618859bd8)
2025-04-07 05:34:25 +00:00
ruthra kumar
d54b39b3a7 Merge pull request #46889 from frappe/mergify/bp/version-15-hotfix/pr-46784
fix: correct mapping(schedule_date) sales order to material request (backport #46784)
2025-04-07 11:03:43 +05:30
MohsinAli
e2c8ed2afd fix: correct mapping(schedule_date) sales order to material request
(cherry picked from commit 732e950265)
2025-04-07 05:15:13 +00:00
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
ruthra kumar
0c6c654a39 Merge pull request #46886 from frappe/mergify/bp/version-15-hotfix/pr-46743
fix: include auto_reconcile_vouchers flag in background job (backport #46743)
2025-04-07 10:40:30 +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
venkat102
26f93f57b8 fix: include auto_reconcile_vouchers flag in background job
(cherry picked from commit 35fbbc2057)
2025-04-07 04:51:00 +00:00
Mihir Kandoi
13f1afa141 fix: remove all serial/batch fields when use button is unselected
(cherry picked from commit 22ffdb9e77)
2025-04-07 04:50:55 +00:00
rohitwaghchaure
f3eeb77ef3 Merge pull request #46838 from frappe/st35124
fix: use work_order bom_no if no bom present in operation
2025-04-07 10:18:20 +05:30
ruthra kumar
83b72dc1b3 Merge pull request #46883 from frappe/mergify/bp/version-15-hotfix/pr-46727
fix: use docstatus for status filter (backport #46727)
2025-04-07 10:18:08 +05:30
rethik
ab52524f12 fix: use docstatus for status filter
(cherry picked from commit 31e59354c9)
2025-04-07 04:43:47 +00:00
mergify[bot]
c1fe8f6000 fix: pos closed dialog on pos closing entry (backport #46881) (#46882)
fix: pos closed dialog on pos closing entry (#46881)

(cherry picked from commit 21954b9f9c)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-06 21:32:17 +05:30
rohitwaghchaure
79e8bfe213 Merge pull request #46880 from frappe/mergify/bp/version-15-hotfix/pr-46845
fix: slow query (backport #46845)
2025-04-06 18:52:15 +05:30
ruthra kumar
8d3fef3195 Merge pull request #46878 from frappe/mergify/bp/version-15-hotfix/pr-46716
feat(Customer): add Dunning to dashboard (backport #46716)
2025-04-06 16:52:07 +05:30
rohitwaghchaure
7c3467d1ff Merge pull request #46879 from frappe/mergify/bp/version-15-hotfix/pr-46875
perf: Stock entry cancel is slow (backport #46875)
2025-04-06 16:25:54 +05:30
rohitwaghchaure
4bcf052220 chore: fix conflicts 2025-04-06 16:25:20 +05:30
Rohit Waghchaure
23dc9d5872 fix: slow query
(cherry picked from commit f82c8ea5eb)

# Conflicts:
#	erpnext/stock/deprecated_serial_batch.py
2025-04-06 10:41:44 +00:00
Türker Tunalı
1bdfd33816 perf: Stock entry cancel is slow
Some queries still use "timestamp" function instead of "posting_datetime". In my instance single stock entry cancel ends with request timeout. Using "posting_datetime" field directly improves the situation.

cont: https://github.com/frappe/erpnext/pull/46293
(cherry picked from commit ddbb44c6a2)
2025-04-06 10:40:32 +00:00
rohitwaghchaure
c5710dcbe2 Merge pull request #46873 from frappe/mergify/bp/version-15-hotfix/pr-46853
fix: stock entry repack amount calculation (backport #46853)
2025-04-06 16:09:57 +05:30
barredterra
1128b5f09c feat(Customer): add Dunning to dashboard
(cherry picked from commit 638d825d8c)
2025-04-06 10:21:31 +00:00
ruthra kumar
8617ea5685 Merge pull request #46877 from frappe/mergify/bp/version-15-hotfix/pr-46658
chore: adjusted dimension placement in Accounts Payable (backport #46658)
2025-04-06 15:44:19 +05:30
Sruthy
6b3e64c0cc chore: adjusted dimension placement in Accounts Payable
(cherry picked from commit 361a55a703)
2025-04-06 10:10:33 +00:00
Rohit Waghchaure
8c61639062 fix: stock entry repack amount calculation
(cherry picked from commit 544ceb93cd)
2025-04-06 04:16:16 +00:00
rohitwaghchaure
51c18217fa Merge pull request #46842 from frappe/mergify/bp/version-15-hotfix/pr-46832
fix: set draft QC in purchase document on creation of qc (backport #46832)
2025-04-06 09:45:25 +05:30
Sagar Vora
679397528a Merge pull request #46871 from frappe/mergify/bp/version-15-hotfix/pr-46870
perf: reduce query when validating any doc (backport #46870)
2025-04-05 23:42:18 +05:30
Sagar Vora
890abf6b90 perf: reduce query when validating any doc
(cherry picked from commit b863296e53)
2025-04-05 18:10:06 +00:00
mergify[bot]
86853224c3 refactor(Payment Entry): reduce indentation (backport #46864) (#46867)
refactor(Payment Entry): reduce indentation (#46864)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-04-05 17:39:42 +02:00
mergify[bot]
d7bb4a288c fix: make message translatable (backport #46863) (#46866)
fix: make message translatable (#46863)

(cherry picked from commit 7d12e9afd4)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-04-05 17:37:36 +02:00
mergify[bot]
fcade5d8cd fix: Translate UnReconcile dialog title (backport #46818) (#46861)
fix: Translate UnReconcile dialog title

(cherry picked from commit f2cfb03c2c)

Co-authored-by: Corentin Forler <corentin@dokos.io>
2025-04-05 17:37:08 +02:00
mergify[bot]
f63595cf0c fix(Dunning): undefined variable (backport #46868) (#46869)
fix(Dunning): undefined variable (#46868)

(cherry picked from commit 04df09cfca)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-04-05 17:36:43 +02:00
ruthra kumar
e8efa6d331 Merge pull request #46857 from frappe/mergify/bp/version-15-hotfix/pr-46625
refactor: Update Bank Transaction `set_query` implemetation (backport #46625)
2025-04-05 07:36:37 +05:30
ruthra kumar
a6834f3875 chore: resolve conflict 2025-04-05 07:32:15 +05:30
Abdeali Chharchhoda
3dc29cbec8 chore: formatting
(cherry picked from commit 4ae11d4384)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/bank_transaction.js
2025-04-05 01:35:11 +00:00
Abdeali Chharchhoda
5d47db78e6 fix: add Not Cancelled filter for payment_entry in Bank Transaction
(cherry picked from commit 85dd1dd4c7)
2025-04-05 01:35:11 +00:00
Abdeali Chharchhoda
a19eece881 refactor: move payment_document query to setup
(cherry picked from commit 257802aeda)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/bank_transaction.js
2025-04-05 01:35:11 +00:00
Md Hussain Nagaria
2dfe13e183 chore: update links to Frappe School (#46823)
(cherry picked from commit ef4f662c31)

# Conflicts:
#	erpnext/accounts/workspace/accounting/accounting.json
#	erpnext/buying/workspace/buying/buying.json
#	erpnext/manufacturing/workspace/manufacturing/manufacturing.json
#	erpnext/projects/workspace/projects/projects.json
#	erpnext/selling/workspace/selling/selling.json
#	erpnext/stock/workspace/stock/stock.json
2025-04-02 06:46:29 +00:00
Rohit Waghchaure
54159b9e5e fix: set draft QC in purchase document on creation of qc
(cherry picked from commit 2553dea78e)
2025-04-01 14:17:39 +00:00
ljain112
bde55d2a07 fix: resolved conflicts 2025-04-01 18:51:10 +05:30
Sagar Vora
36aa308bce Merge pull request #46834 from frappe/mergify/bp/version-15-hotfix/pr-46829
fix: use `grand_total_diff` instead of `rounding_adjustment` in `taxes_and_totals` (backport #46829)
2025-04-01 14:47:03 +05:30
Mihir Kandoi
c6979ab260 fix: use work_order bom_no if no bom present in operation 2025-04-01 14:45:24 +05:30
mergify[bot]
80f144ac22 fix: pos checking opened entry closed or not (backport #46726) (#46830)
fix: pos checking opened entry closed or not (#46726)

* fix: pos checking opened entry closed or not

* fix: linter issue

* fix: linter issue

(cherry picked from commit 5d5b6acc79)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-04-01 13:54:29 +05:30
vishakhdesai
55b17b918f fix: use grand_total_diff instead of rounding_adjustment in taxes_and_totals
(cherry picked from commit fd252da6b1)
2025-04-01 13:34:30 +05:30
rohitwaghchaure
27ea95236f Merge pull request #46817 from frappe/mergify/bp/version-15-hotfix/pr-46815
feat: allow UOMs to select for which conversion rate defined in item (backport #46815)
2025-04-01 08:31:52 +05:30
rohitwaghchaure
c762a8903b Merge pull request #46824 from frappe/mergify/bp/version-15-hotfix/pr-46808
fix: condition to update the last purchase rate (backport #46808)
2025-04-01 08:31:30 +05:30
barredterra
db647a4e42 fix: wording 2025-03-31 21:38:34 +02:00
barredterra
79ed02bb2c fix: translatability
(cherry picked from commit 6d43d46fbc)
2025-03-31 19:23:41 +00:00
Rohit Waghchaure
353fa0cbc3 fix: condition to update the last puurchase rate
(cherry picked from commit bad901e7da)
2025-03-31 15:15:20 +00:00
rohitwaghchaure
2b854377b1 chore: fix conflicts 2025-03-31 20:43:34 +05:30
Rohit Waghchaure
288aad6f5d feat: allow UOMs to select for which converstion rate defined in item master
(cherry picked from commit b1dfbbe85e)

# Conflicts:
#	erpnext/stock/doctype/stock_settings/stock_settings.json
2025-03-31 10:53:11 +00:00
rohitwaghchaure
a60acbc592 Merge pull request #46807 from frappe/mergify/bp/version-15-hotfix/pr-46806
feat: option to recreate Stock Ledger Entries against stock transactions (backport #46806)
2025-03-31 16:06:42 +05:30
Marc Ramser
42479d9a7f feat(regional): Address Template for Germany & Add Switzerland Template (#46737)
* Add Address template for Switzerland

* Fix address template for germany

If an ERPNext instance is set to German and used by a business outside Germany (e.g., in Switzerland or Austria), customer addresses in Germany are displayed in their national format. However, for postal services, the international format (including the country) is required.".

This is just a workaround. IMHO the correct fix would be to check where the company is located and based on that use the national or the international template.

(cherry picked from commit 21b8ad6aa5)
2025-03-31 10:34:00 +00:00
Sagar Vora
62275d09da Merge pull request #46814 from frappe/mergify/bp/version-15-hotfix/pr-46812
fix: revert resetting `rounding_adjustment` (backport #46812)
2025-03-31 15:56:08 +05:30
Vishakh Desai
96af1ccffb Merge pull request #46812 from vishakhdesai/fix-taxes-and-totals
fix: revert resetting `rounding_adjustment`
(cherry picked from commit 3a9dca0563)
2025-03-31 10:25:00 +00:00
mergify[bot]
7b864bece8 fix: improved rounding adjustment when applying discount (backport #46720)
* fix: improved rounding adjustment when applying discount (#46720)

* fix: rounding adjustment in apply_discount_amount taxes_and_totals

* refactor: minor changes

* fix: set the rounding difference while calculating tax total in the last tax row and add test case

* fix: failing test case

* fix: made changes in get_total_for_discount_amount in taxes_and_totals

* fix: failing test cases

* fix: changes as per review

* refactor: remove unnecessary use of flt

* refactor: improve logic

* refactor: minor change

* refactor: minor changes

* fix: add a test case for applying discount with previous row total in taxes

* fix: failing test case

* refactor: flatter code, remove `flt` usage for accuracy

---------

Co-authored-by: Vishakh Desai <78500008+vishakhdesai@users.noreply.github.com>
Co-authored-by: Sagar Vora <sagar@resilient.tech>
2025-03-31 15:54:42 +05:30
rohitwaghchaure
604b185bd3 chore: fix conflicts 2025-03-31 15:05:10 +05:30
Rohit Waghchaure
64fdcb752d feat: option to recreate Stock Ledger Entries against stock transactions
(cherry picked from commit 218dbd6911)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
2025-03-31 08:44:38 +00:00
Sagar Vora
37adb9187f Merge pull request #46803 from frappe/mergify/bp/version-15-hotfix/pr-46644
fix: multiple Bank Reconciliation Tool issues (backport #46644)
2025-03-31 11:37:36 +05:30
Vishakh Desai
e168483a58 fix: multiple Bank Reconciliation Tool issues (#46644)
* fix: bank reconciliation tool issue

* refactor: separate Bank Transaction linking from other logic

* fix: delink old pe on update_after_submit in bank transaction

* fix: failing test case fixed

* fix: changes as per review

* refactor: rename `gles` to `gl_entries`

---------

Co-authored-by: Sagar Vora <sagar@resilient.tech>
(cherry picked from commit 646cf54679)
2025-03-31 05:57:08 +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
5315769b1f Merge pull request #46781 from frappe/mergify/bp/version-15-hotfix/pr-46777
fix: incorrect condition (backport #46777)
2025-03-29 12:17:09 +05:30
rohitwaghchaure
9fa5afd215 Merge pull request #46779 from frappe/mergify/bp/version-15-hotfix/pr-46775
fix: for deadlock issue keep status as In Progress (backport #46775)
2025-03-29 12:16:55 +05:30
rohitwaghchaure
88ff17f467 Merge pull request #46778 from frappe/mergify/bp/version-15-hotfix/pr-46760
fix: valuation rate not updating for raw materials (backport #46760)
2025-03-29 12:16:35 +05:30
Rohit Waghchaure
502b8f25b3 fix: incorrect condition
(cherry picked from commit 0c1a8e9c58)
2025-03-28 18:19:53 +00:00
rohitwaghchaure
5079519863 chore: fix conflicts 2025-03-28 23:47:43 +05:30
Rohit Waghchaure
34e66b1b27 fix: for deadlock issue keep status as In Progress
(cherry picked from commit e6ff7f0e9f)
2025-03-28 18:16:41 +00:00
Rohit Waghchaure
454dd3a2f1 fix: valuation rate not updating for raw materials
(cherry picked from commit 5af8378471)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
2025-03-28 18:15:42 +00:00
Abdeali Chharchhodawala
6f94ba599b Merge pull request #46683 from Abdeali099/set-employee-contact-details
fix: Set complete contact details for `Employee` in PE
(cherry picked from commit 8c9d630ee4)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.json
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
2025-03-28 12:49:20 +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
rohitwaghchaure
f3cff68713 Merge pull request #46749 from frappe/revert-46647-mergify/bp/version-15-hotfix/pr-46641
Revert "perf: timeout while renaming cost center (backport #46641)"
2025-03-27 12:23:13 +05:30
rohitwaghchaure
326126e741 Revert "perf: timeout while renaming cost center (backport #46641)" 2025-03-27 11:58:02 +05:30
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
ruthra kumar
b22d0a5804 Merge pull request #46746 from frappe/mergify/bp/version-15-hotfix/pr-46719
fix: do not use self object for setting party and party type  (backport #46719)
2025-03-27 11:34:17 +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
ljain112
d1311e619d fix: do not use self object for setting party and party type
(cherry picked from commit 80b746d4dd)
2025-03-27 05:38:38 +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
rohitwaghchaure
8b5ed20225 Merge pull request #46740 from frappe/mergify/bp/version-15-hotfix/pr-46739
fix: slow query (backport #46739)
2025-03-26 22:38:38 +05:30
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
rohitwaghchaure
af3b871989 Merge pull request #46734 from frappe/mergify/bp/version-15-hotfix/pr-46733
fix: decimal values causing incorrect batch picking (backport #46733)
2025-03-26 22:12:07 +05:30
rohitwaghchaure
41f20a9c64 chore: fix conflicts 2025-03-26 22:11:43 +05:30
Rohit Waghchaure
af0fb131a2 fix: slow query
(cherry picked from commit 5ddb36af87)

# Conflicts:
#	erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
2025-03-26 16:40:32 +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
Rohit Waghchaure
c5efddae16 fix: decimal values causing incorrect batch picking
(cherry picked from commit 7bfe703b04)
2025-03-26 10:13:40 +00:00
rohitwaghchaure
2ed29d06d3 Merge pull request #46730 from frappe/mergify/bp/version-15-hotfix/pr-46710
fix: expense account in the stock entry (backport #46710)
2025-03-26 12:16:59 +05:30
Rohit Waghchaure
62f342ef8b fix: expense account in the stock entry
(cherry picked from commit 89569d4b32)
2025-03-26 06:30:40 +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
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
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
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
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
Mihir Kandoi
6157fed71c fix: source warehouse not fetched in bom creator 2025-02-19 15:54:31 +05:30
David (aider)
6f6574c5ac feat: add unit tests for distributed_discount_amount
(cherry picked from commit a464bd861b)
2024-09-05 10:08:39 +00:00
David
ad05e6dec2 fix: distributed discounts on si
(cherry picked from commit 0bab6f34c1)

# Conflicts:
#	erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
#	erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
#	erpnext/selling/doctype/quotation_item/quotation_item.json
#	erpnext/selling/doctype/sales_order_item/sales_order_item.json
#	erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
2024-09-05 10:08:39 +00:00
345 changed files with 20897 additions and 3022 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

@@ -1,5 +1,5 @@
exclude: 'node_modules|.git'
default_stages: [commit]
default_stages: [pre-commit]
fail_fast: false

View File

@@ -62,7 +62,7 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB
## Learning and community
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
1. [Frappe School](https://school.frappe.io) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers.
4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users.

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.53.4"
__version__ = "15.60.2"
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

@@ -77,9 +77,12 @@
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"ignore_is_opening_check_for_reporting",
"column_break_lvjk",
"receivable_payable_remarks_length",
"accounts_receivable_payable_tuning_section",
"receivable_payable_fetch_method",
"legacy_section",
"ignore_is_opening_check_for_reporting",
"payment_request_settings",
"create_pr_in_draft_status"
],
@@ -532,6 +535,34 @@
"fieldtype": "Select",
"label": "Posting Date Inheritance for Exchange Gain / Loss",
"options": "Invoice\nPayment\nReconciliation Date"
},
{
"fieldname": "column_break_xrnd",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If enabled, Sales Invoice will be generated instead of POS Invoice in POS Transactions for real-time update of G/L and Stock Ledger.",
"fieldname": "use_sales_invoice_in_pos",
"fieldtype": "Check",
"label": "Use Sales Invoice"
},
{
"default": "Buffered Cursor",
"fieldname": "receivable_payable_fetch_method",
"fieldtype": "Select",
"label": "Data Fetch Method",
"options": "Buffered Cursor\nUnBuffered Cursor"
},
{
"fieldname": "accounts_receivable_payable_tuning_section",
"fieldtype": "Section Break",
"label": "Accounts Receivable / Payable Tuning"
},
{
"fieldname": "legacy_section",
"fieldtype": "Section Break",
"label": "Legacy Fields"
}
],
"icon": "icon-cog",
@@ -539,7 +570,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-01-23 13:15:44.077853",
"modified": "2025-05-05 12:29:38.302027",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -54,6 +54,7 @@ class AccountsSettings(Document):
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
post_change_gl_entries: DF.Check
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
role_allowed_to_over_bill: DF.Link | None

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

@@ -7,6 +7,9 @@ import frappe
from frappe.utils import add_months, getdate
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -191,11 +194,13 @@ def make_pos_sales_invoice():
customer = make_customer(customer="_Test Customer")
mode_of_payment = frappe.get_doc("Mode of Payment", "Wire Transfer")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank Clearance - _TC")
si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1)
si.set("payments", [])
si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank Clearance - _TC", "amount": 1000}
)
si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 1000})
si.insert()
si.submit()

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt
from erpnext import get_default_cost_center
@@ -373,10 +374,37 @@ def auto_reconcile_vouchers(
from_reference_date=None,
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
):
frappe.flags.auto_reconcile_vouchers = True
reconciled, partially_reconciled = set(), set()
for transaction in bank_transactions:
linked_payments = get_linked_payments(
transaction.name,
@@ -414,7 +442,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):
@@ -491,16 +518,23 @@ def subtract_allocations(gl_account, vouchers):
voucher_allocated_amounts = get_total_allocated_amount(voucher_docs)
for voucher in vouchers:
rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) or []
filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows))
if amount := None if not filtered_row else filtered_row[0]["total"]:
if amount := get_allocated_amount(voucher_allocated_amounts, voucher, gl_account):
voucher["paid_amount"] -= amount
copied.append(voucher)
return copied
def get_allocated_amount(voucher_allocated_amounts, voucher, gl_account):
if not (voucher_details := voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name")))):
return
if not (row := voucher_details.get(gl_account)):
return
return row.get("total")
def check_matching(
bank_account,
company,
@@ -770,26 +804,20 @@ def get_je_matching_query(
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
ref_condition = je.cheque_no == transaction.reference_number
ref_rank = frappe.qb.terms.Case().when(ref_condition, 1).else_(0)
amount_field = f"{cr_or_dr}_in_account_currency"
amount_equality = getattr(jea, amount_field) == transaction.unallocated_amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
filter_by_date = je.posting_date.between(from_date, to_date)
if cint(filter_by_reference_date):
filter_by_date = je.cheque_date.between(from_reference_date, to_reference_date)
query = (
subquery = (
frappe.qb.from_(jea)
.join(je)
.on(jea.parent == je.name)
.select(
(ref_rank + amount_rank + 1).as_("rank"),
Sum(getattr(jea, amount_field)).as_("paid_amount"),
ConstantColumn("Journal Entry").as_("doctype"),
je.name,
getattr(jea, amount_field).as_("paid_amount"),
je.cheque_no.as_("reference_no"),
je.cheque_date.as_("reference_date"),
je.pay_to_recd_from.as_("party"),
@@ -801,13 +829,26 @@ def get_je_matching_query(
.where(je.voucher_type != "Opening Entry")
.where(je.clearance_date.isnull())
.where(jea.account == common_filters.bank_account)
.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
.where(filter_by_date)
.groupby(je.name)
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
)
if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition)
subquery = subquery.where(je.cheque_no == transaction.reference_number)
ref_rank = frappe.qb.terms.Case().when(subquery.reference_no == transaction.reference_number, 1).else_(0)
amount_equality = subquery.paid_amount == transaction.unallocated_amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
query = (
frappe.qb.from_(subquery)
.select(
"*",
(ref_rank + amount_rank + 1).as_("rank"),
)
.where(amount_equality if exact_match else subquery.paid_amount > 0.0)
)
return query

View File

@@ -2,27 +2,6 @@
// For license information, please see license.txt
frappe.ui.form.on("Bank Transaction", {
onload(frm) {
frm.set_query("payment_document", "payment_entries", function () {
const payment_doctypes = frm.events.get_payment_doctypes(frm);
return {
filters: {
name: ["in", payment_doctypes],
},
};
});
},
refresh(frm) {
if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) {
frm.add_custom_button(__("Unreconcile Transaction"), () => {
frm.call("remove_payment_entries").then(() => frm.refresh());
});
}
},
bank_account: function (frm) {
set_bank_statement_filter(frm);
},
setup: function (frm) {
frm.set_query("party_type", function () {
return {
@@ -31,6 +10,41 @@ frappe.ui.form.on("Bank Transaction", {
},
};
});
frm.set_query("bank_account", function () {
return {
filters: { is_company_account: 1 },
};
});
frm.set_query("payment_document", "payment_entries", function () {
const payment_doctypes = frm.events.get_payment_doctypes(frm);
return {
filters: {
name: ["in", payment_doctypes],
},
};
});
frm.set_query("payment_entry", "payment_entries", function () {
return {
filters: {
docstatus: ["!=", 2],
},
};
});
},
refresh(frm) {
if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) {
frm.add_custom_button(__("Unreconcile Transaction"), () => {
frm.call("remove_payment_entries").then(() => frm.refresh());
});
}
},
bank_account: function (frm) {
set_bank_statement_filter(frm);
},
get_payment_doctypes: function () {
@@ -39,31 +53,6 @@ frappe.ui.form.on("Bank Transaction", {
},
});
frappe.ui.form.on("Bank Transaction Payments", {
payment_entries_remove: function (frm, cdt, cdn) {
update_clearance_date(frm, cdt, cdn);
},
});
const update_clearance_date = (frm, cdt, cdn) => {
if (frm.doc.docstatus === 1) {
frappe
.xcall("erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", {
doctype: cdt,
docname: cdn,
bt_name: frm.doc.name,
})
.then((e) => {
if (e == "success") {
frappe.show_alert({
message: __("Document {0} successfully uncleared", [e]),
indicator: "green",
});
}
});
}
};
function set_bank_statement_filter(frm) {
frm.set_query("bank_statement", function () {
return {

View File

@@ -5,7 +5,7 @@ import frappe
from frappe import _
from frappe.model.docstatus import DocStatus
from frappe.model.document import Document
from frappe.utils import flt
from frappe.utils import flt, getdate
class BankTransaction(Document):
@@ -84,16 +84,16 @@ class BankTransaction(Document):
if not self.payment_entries:
return
pe = []
references = set()
for row in self.payment_entries:
reference = (row.payment_document, row.payment_entry)
if reference in pe:
if reference in references:
frappe.throw(
_("{0} {1} is allocated twice in this Bank Transaction").format(
row.payment_document, row.payment_entry
)
)
pe.append(reference)
references.add(reference)
def update_allocated_amount(self):
allocated_amount = (
@@ -104,6 +104,19 @@ class BankTransaction(Document):
self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
def delink_old_payment_entries(self):
if self.flags.updating_linked_bank_transaction:
return
old_doc = self.get_doc_before_save()
payment_entry_names = set(pe.name for pe in self.payment_entries)
for old_pe in old_doc.payment_entries:
if old_pe.name in payment_entry_names:
continue
self.delink_payment_entry(old_pe)
def before_submit(self):
self.allocate_payment_entries()
self.set_status()
@@ -113,13 +126,14 @@ class BankTransaction(Document):
def before_update_after_submit(self):
self.validate_duplicate_references()
self.allocate_payment_entries()
self.update_allocated_amount()
self.delink_old_payment_entries()
self.allocate_payment_entries()
self.set_status()
def on_cancel(self):
for payment_entry in self.payment_entries:
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.delink_payment_entry(payment_entry)
self.set_status()
@@ -152,43 +166,55 @@ class BankTransaction(Document):
- 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date
"""
if self.flags.updating_linked_bank_transaction or not self.payment_entries:
return
remaining_amount = self.unallocated_amount
to_remove = []
payment_entry_docs = [(pe.payment_document, pe.payment_entry) for pe in self.payment_entries]
pe_bt_allocations = get_total_allocated_amount(payment_entry_docs)
gl_entries = get_related_bank_gl_entries(payment_entry_docs)
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0:
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
self,
payment_entry,
pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry))
or [],
for payment_entry in list(self.payment_entries):
if payment_entry.allocated_amount != 0:
continue
allocable_amount, should_clear, clearance_date = get_clearance_details(
self,
payment_entry,
pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry)) or {},
gl_entries.get((payment_entry.payment_document, payment_entry.payment_entry)) or {},
gl_bank_account,
)
if allocable_amount < 0:
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(allocable_amount))
if remaining_amount <= 0:
self.remove(payment_entry)
continue
if allocable_amount == 0:
if should_clear:
self.clear_linked_payment_entry(payment_entry, clearance_date=clearance_date)
self.remove(payment_entry)
continue
should_clear = should_clear and allocable_amount <= remaining_amount
payment_entry.allocated_amount = min(allocable_amount, remaining_amount)
remaining_amount = flt(
remaining_amount - payment_entry.allocated_amount,
self.precision("unallocated_amount"),
)
if payment_entry.payment_document == "Bank Transaction":
self.update_linked_bank_transaction(
payment_entry.payment_entry, payment_entry.allocated_amount
)
elif should_clear:
self.clear_linked_payment_entry(payment_entry, clearance_date=clearance_date)
if 0.0 == unallocated_amount:
if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry)
to_remove.append(payment_entry)
elif remaining_amount <= 0.0:
to_remove.append(payment_entry)
elif 0.0 < unallocated_amount <= remaining_amount:
payment_entry.allocated_amount = unallocated_amount
remaining_amount -= unallocated_amount
if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry)
elif 0.0 < unallocated_amount:
payment_entry.allocated_amount = remaining_amount
remaining_amount = 0.0
elif 0.0 > unallocated_amount:
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
for payment_entry in to_remove:
self.remove(payment_entry)
self.update_allocated_amount()
@frappe.whitelist()
def remove_payment_entries(self):
@@ -199,14 +225,64 @@ class BankTransaction(Document):
def remove_payment_entry(self, payment_entry):
"Clear payment entry and clearance"
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.delink_payment_entry(payment_entry)
self.remove(payment_entry)
def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
clearance_date = None if for_cancel else self.date
set_voucher_clearance(
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
)
def delink_payment_entry(self, payment_entry):
if payment_entry.payment_document == "Bank Transaction":
self.update_linked_bank_transaction(payment_entry.payment_entry, allocated_amount=None)
else:
self.clear_linked_payment_entry(payment_entry, clearance_date=None)
def clear_linked_payment_entry(self, payment_entry, clearance_date=None):
doctype = payment_entry.payment_document
docname = payment_entry.payment_entry
# might be a bank transaction
if doctype not in get_doctypes_for_bank_reconciliation():
return
if doctype == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=doctype, parent=docname),
"clearance_date",
clearance_date,
)
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
def update_linked_bank_transaction(self, bank_transaction_name, allocated_amount=None):
"""For when a second bank transaction has fixed another, e.g. refund"""
bt = frappe.get_doc(self.doctype, bank_transaction_name)
if allocated_amount:
bt.append(
"payment_entries",
{
"payment_document": self.doctype,
"payment_entry": self.name,
"allocated_amount": allocated_amount,
},
)
else:
pe = next(
(
pe
for pe in bt.payment_entries
if pe.payment_document == self.doctype and pe.payment_entry == self.name
),
None,
)
if not pe:
return
bt.flags.updating_linked_bank_transaction = True
bt.remove(pe)
bt.save()
def auto_set_party(self):
from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty
@@ -238,71 +314,107 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes")
def get_clearance_details(transaction, payment_entry, bt_allocations):
def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries, gl_bank_account):
"""
There should only be one bank gle for a voucher.
Could be none for a Bank Transaction.
But if a JE, could affect two banks.
Should only clear the voucher if all bank gles are allocated.
There should only be one bank gl entry for a voucher, except for JE.
For JE, there can be multiple bank gl entries for the same account.
In this case, the allocable_amount will be the sum of amounts of all gl entries of the account.
There will be no gl entry for a Bank Transaction so return the unallocated amount.
Should only clear the voucher if all bank gl entries are allocated.
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
unallocated_amount = min(
transaction.unallocated_amount,
get_paid_amount(payment_entry, transaction.currency, gl_bank_account),
)
unmatched_gles = len(gles)
latest_transaction = transaction
for gle in gles:
if gle["gl_account"] == gl_bank_account:
if gle["amount"] <= 0.0:
frappe.throw(
_("Voucher {0} value is broken: {1}").format(payment_entry.payment_entry, gle["amount"])
transaction_date = getdate(transaction.date)
if payment_entry.payment_document == "Bank Transaction":
bt = frappe.db.get_value(
"Bank Transaction",
payment_entry.payment_entry,
("unallocated_amount", "bank_account"),
as_dict=True,
)
if bt.bank_account != gl_bank_account:
frappe.throw(
_("Bank Account {} in Bank Transaction {} is not matching with Bank Account {}").format(
bt.bank_account, payment_entry.payment_entry, gl_bank_account
)
)
unmatched_gles -= 1
unallocated_amount = gle["amount"]
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"]:
unallocated_amount = gle["amount"] - a["total"]
if frappe.utils.getdate(transaction.date) < a["latest_date"]:
latest_transaction = frappe.get_doc("Bank Transaction", a["latest_name"])
else:
# Must be a Journal Entry affecting more than one bank
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"] and a["total"] == gle["amount"]:
unmatched_gles -= 1
return abs(bt.unallocated_amount), True, transaction_date
return unallocated_amount, unmatched_gles == 0, latest_transaction
if gl_bank_account not in gl_entries:
frappe.throw(
_("{} {} is not affecting bank account {}").format(
payment_entry.payment_document, payment_entry.payment_entry, gl_bank_account
)
)
allocable_amount = gl_entries.pop(gl_bank_account) or 0
if allocable_amount <= 0.0:
frappe.throw(
_("Invalid amount in accounting entries of {} {} for Account {}: {}").format(
payment_entry.payment_document, payment_entry.payment_entry, gl_bank_account, allocable_amount
)
)
matching_bt_allocaion = bt_allocations.pop(gl_bank_account, {})
allocable_amount = flt(
allocable_amount - matching_bt_allocaion.get("total", 0), transaction.precision("unallocated_amount")
)
should_clear = all(
gl_entries[gle_account] == bt_allocations.get(gle_account, {}).get("total", 0)
for gle_account in gl_entries
)
bt_allocation_date = matching_bt_allocaion.get("latest_date", None)
clearance_date = transaction_date if not bt_allocation_date else max(transaction_date, bt_allocation_date)
return allocable_amount, should_clear, clearance_date
def get_related_bank_gl_entries(doctype, docname):
def get_related_bank_gl_entries(docs):
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
return frappe.db.sql(
if not docs:
return {}
result = frappe.db.sql(
"""
SELECT
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
gle.account AS gl_account
FROM
`tabGL Entry` gle
LEFT JOIN
`tabAccount` ac ON ac.name=gle.account
WHERE
ac.account_type = 'Bank'
AND gle.voucher_type = %(doctype)s
AND gle.voucher_no = %(docname)s
AND is_cancelled = 0
""",
dict(doctype=doctype, docname=docname),
SELECT
gle.voucher_type AS doctype,
gle.voucher_no AS docname,
gle.account AS gl_account,
SUM(ABS(gle.credit_in_account_currency - gle.debit_in_account_currency)) AS amount
FROM
`tabGL Entry` gle
LEFT JOIN
`tabAccount` ac ON ac.name = gle.account
WHERE
ac.account_type = 'Bank'
AND (gle.voucher_type, gle.voucher_no) IN %(docs)s
AND gle.is_cancelled = 0
GROUP BY
gle.voucher_type, gle.voucher_no, gle.account
""",
{"docs": docs},
as_dict=True,
)
entries = {}
for row in result:
key = (row["doctype"], row["docname"])
if key not in entries:
entries[key] = {}
entries[key][row["gl_account"]] = row["amount"]
return entries
def get_total_allocated_amount(docs):
"""
Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date
along with the latest bank transaction date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
"""
if not docs:
@@ -311,11 +423,10 @@ def get_total_allocated_amount(docs):
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
SELECT total, latest_name, latest_date, gl_account, payment_document, payment_entry FROM (
SELECT total, latest_date, gl_account, payment_document, payment_entry FROM (
SELECT
ROW_NUMBER() OVER w AS rownum,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date,
ba.account AS gl_account,
btp.payment_document,
@@ -338,104 +449,14 @@ def get_total_allocated_amount(docs):
payment_allocation_details = {}
for row in result:
# Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"])
payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), []).append(row)
row["latest_date"] = getdate(row["latest_date"])
payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), {})[
row["gl_account"]
] = row
return payment_allocation_details
def get_paid_amount(payment_entry, currency, gl_bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
if payment_entry.payment_document == "Payment Entry":
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
if doc.payment_type == "Receive":
paid_amount_field = (
"received_amount" if doc.paid_to_account_currency == currency else "base_received_amount"
)
elif doc.payment_type == "Pay":
paid_amount_field = (
"paid_amount" if doc.paid_from_account_currency == currency else "base_paid_amount"
)
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, paid_amount_field
)
elif payment_entry.payment_document == "Journal Entry":
return abs(
frappe.db.get_value(
"Journal Entry Account",
{"parent": payment_entry.payment_entry, "account": gl_bank_account},
"sum(debit_in_account_currency-credit_in_account_currency)",
)
or 0
)
elif payment_entry.payment_document == "Expense Claim":
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed"
)
elif payment_entry.payment_document == "Loan Disbursement":
return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "disbursed_amount"
)
elif payment_entry.payment_document == "Loan Repayment":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value(
"Bank Transaction", payment_entry.payment_entry, ("deposit", "withdrawal")
)
return abs(flt(wth) - flt(dep))
else:
frappe.throw(
f"Please reconcile {payment_entry.payment_document}: {payment_entry.payment_entry} manually"
)
def set_voucher_clearance(doctype, docname, clearance_date, self):
if doctype in get_doctypes_for_bank_reconciliation():
if (
doctype == "Payment Entry"
and frappe.db.get_value("Payment Entry", docname, "payment_type") == "Internal Transfer"
and len(get_reconciled_bank_transactions(doctype, docname)) < 2
):
return
if doctype == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=doctype, parent=docname),
"clearance_date",
clearance_date,
)
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
elif doctype == "Bank Transaction":
# For when a second bank transaction has fixed another, e.g. refund
bt = frappe.get_doc(doctype, docname)
if clearance_date:
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
bt.add_payment_entries(vouchers)
bt.save()
else:
for pe in bt.payment_entries:
if pe.payment_document == self.doctype and pe.payment_entry == self.name:
bt.remove(pe)
bt.save()
break
def get_reconciled_bank_transactions(doctype, docname):
return frappe.get_all(
"Bank Transaction Payments",
@@ -444,13 +465,6 @@ def get_reconciled_bank_transactions(doctype, docname):
)
@frappe.whitelist()
def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt)
return docname
def remove_from_bank_transaction(doctype, docname):
"""Remove a (cancelled) voucher from all Bank Transactions."""
for bt_name in get_reconciled_bank_transactions(doctype, docname):

View File

@@ -12,6 +12,9 @@ from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool
get_linked_payments,
reconcile_vouchers,
)
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
@@ -434,15 +437,13 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
except frappe.DuplicateEntryError:
pass
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Wire Transfer"})
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
mode_of_payment.save()
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", gl_account)
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1
si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 109080})
si.insert()
si.submit()

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

@@ -220,6 +220,7 @@ def get_dunning_letter_text(dunning_type: str, doc: str | dict, language: str |
if not language:
language = doc.get("language")
letter_text = None
if language:
letter_text = frappe.db.get_value(
DOCTYPE, {"parent": dunning_type, "language": language}, FIELDS, as_dict=1

View File

@@ -1,6 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import json
import frappe
from frappe.model import mapper
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, nowdate, today
@@ -68,6 +71,36 @@ class TestDunning(FrappeTestCase):
dunning.reload()
self.assertEqual(dunning.status, "Resolved")
def test_fetch_overdue_payments(self):
"""
Create SI with overdue payment. Check if overdue payment is fetched in Dunning.
"""
si1 = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -1 * 6),
qty=1,
rate=100,
)
si2 = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -1 * 6),
qty=1,
rate=300,
)
dunning = create_dunning_from_sales_invoice(si1.name)
dunning.overdue_payments = []
method = "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning"
updated_dunning = mapper.map_docs(method, json.dumps([si1.name, si2.name]), dunning)
self.assertEqual(len(updated_dunning.overdue_payments), 2)
self.assertEqual(updated_dunning.overdue_payments[0].sales_invoice, si1.name)
self.assertEqual(updated_dunning.overdue_payments[0].outstanding, si1.outstanding_amount)
self.assertEqual(updated_dunning.overdue_payments[1].sales_invoice, si2.name)
self.assertEqual(updated_dunning.overdue_payments[1].outstanding, si2.outstanding_amount)
def test_dunning_and_payment_against_partially_due_invoice(self):
"""
Create SI with first installment overdue. Check impact of Dunning and Payment Entry.

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

@@ -7,7 +7,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.model.naming import set_name_from_naming_options
from frappe.utils import flt, fmt_money
from frappe.utils import flt, fmt_money, now
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -456,7 +456,7 @@ def rename_temporarily_named_docs(doctype):
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
newname = doc.name
frappe.db.sql(
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0 where name = %s",
(newname, oldname),
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
(newname, now(), oldname),
auto_commit=True,
)

View File

@@ -197,7 +197,7 @@ frappe.ui.form.on("Invoice Discounting", {
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
group_by: "Group by Voucher (Consolidated)",
categorize_by: "Categorize by Voucher (Consolidated)",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");

View File

@@ -35,7 +35,7 @@ frappe.ui.form.on("Journal Entry", {
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
finance_book: frm.doc.finance_book,
group_by: "",
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");

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:
@@ -249,11 +250,20 @@ class JournalEntry(AccountsController):
def validate_inter_company_accounts(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
doc = frappe.db.get_value(
"Journal Entry",
self.inter_company_journal_entry_reference,
["company", "total_debit", "total_credit"],
as_dict=True,
)
account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
if account_currency == previous_account_currency:
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
credit_precision = self.precision("total_credit")
debit_precision = self.precision("total_debit")
if (flt(self.total_credit, credit_precision) != flt(doc.total_debit, debit_precision)) or (
flt(self.total_debit, debit_precision) != flt(doc.total_credit, credit_precision)
):
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
def validate_depr_entry_voucher_type(self):
@@ -575,8 +585,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 +1083,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 +1116,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

@@ -3,8 +3,25 @@
import unittest
# test_records = frappe.get_test_records('Mode of Payment')
import frappe
class TestModeofPayment(unittest.TestCase):
pass
def set_default_account_for_mode_of_payment(mode_of_payment, company, account):
mode_of_payment.reload()
if frappe.db.exists(
"Mode of Payment Account", {"parent": mode_of_payment.mode_of_payment, "company": company}
):
frappe.db.set_value(
"Mode of Payment Account",
{"parent": mode_of_payment.mode_of_payment, "company": company},
"default_account",
account,
)
return
mode_of_payment.append("accounts", {"company": company, "default_account": account})
mode_of_payment.save()

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") });
@@ -407,7 +411,7 @@ frappe.ui.form.on("Payment Entry", {
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
group_by: "",
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");
@@ -1893,8 +1897,6 @@ function prompt_for_missing_account(frm, account) {
(values) => resolve(values?.[account]),
__("Please Specify Account")
);
dialog.on_hide = () => resolve("");
});
}

View File

@@ -203,14 +203,14 @@
"fieldtype": "Column Break"
},
{
"depends_on": "party",
"depends_on": "eval: doc.party && doc.party_type !== \"Employee\"",
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact",
"options": "Contact"
},
{
"depends_on": "contact_person",
"depends_on": "eval: (doc.contact_person || doc.party_type === \"Employee\") && doc.contact_email",
"fieldname": "contact_email",
"fieldtype": "Data",
"label": "Email",
@@ -809,7 +809,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2025-01-31 17:27:28.555246",
"modified": "2025-03-24 16:18:19.920701",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

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,11 @@ 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_default_contact,
get_party_account,
)
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
@@ -248,16 +253,18 @@ class PaymentEntry(AccountsController):
reference_names.add(key)
def set_bank_account_data(self):
if self.bank_account:
bank_data = get_bank_account_details(self.bank_account)
if not self.bank_account:
return
field = "paid_from" if self.payment_type == "Pay" else "paid_to"
bank_data = get_bank_account_details(self.bank_account)
self.bank = bank_data.bank
self.bank_account_no = bank_data.bank_account_no
field = "paid_from" if self.payment_type == "Pay" else "paid_to"
if not self.get(field):
self.set(field, bank_data.account)
self.bank = bank_data.bank
self.bank_account_no = bank_data.bank_account_no
if not self.get(field):
self.set(field, bank_data.account)
def validate_payment_type_with_outstanding(self):
total_outstanding = sum(d.allocated_amount for d in self.get("references"))
@@ -275,15 +282,16 @@ class PaymentEntry(AccountsController):
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
else:
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
return
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_as_per_payment_request(self):
"""
@@ -321,91 +329,89 @@ class PaymentEntry(AccountsController):
return False
def validate_allocated_amount_with_latest_data(self):
if self.references:
uniq_vouchers = set([(x.reference_doctype, x.reference_name) for x in self.references])
vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in uniq_vouchers]
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
"vouchers": vouchers,
"book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account,
},
validate=True,
)
if not self.references:
return
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
uniq_vouchers = {(x.reference_doctype, x.reference_name) for x in self.references}
vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in uniq_vouchers]
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
"vouchers": vouchers,
"book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account,
},
validate=True,
)
for idx, d in enumerate(self.get("references"), start=1):
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
# If term based allocation is enabled, throw
if (
d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name):
frappe.throw(
_(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
).format(frappe.bold(d.reference_name), frappe.bold(idx))
)
for idx, d in enumerate(self.get("references"), start=1):
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
latest = latest.get(d.payment_term) or latest.get(None)
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(
_(d.reference_doctype), d.reference_name
)
)
# The reference has already been partly paid
elif (
latest.outstanding_amount < latest.invoice_amount
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
and d.payment_term == ""
):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(_(d.reference_doctype), d.reference_name)
)
# If term based allocation is enabled, throw
if (
d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name):
frappe.throw(
_(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
).format(frappe.bold(d.reference_name), frappe.bold(idx))
)
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
latest = latest.get(d.payment_term) or latest.get(None)
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
elif (
latest.outstanding_amount < latest.invoice_amount
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
and d.payment_term == ""
):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(_(d.reference_doctype), d.reference_name)
)
if (
d.payment_term
and (
(flt(d.allocated_amount)) > 0
and latest.payment_term_outstanding
and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
)
and self.term_based_allocation_enabled_for_reference(
d.reference_doctype, d.reference_name
)
):
frappe.throw(
_(
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term)
)
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
if (
d.payment_term
and (
(flt(d.allocated_amount)) > 0
and latest.payment_term_outstanding
and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding))
)
and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name)
):
frappe.throw(
_(
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term)
)
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self):
for reference in self.references:
@@ -439,6 +445,12 @@ class PaymentEntry(AccountsController):
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
if self.party:
if self.party_type == "Employee":
self.contact_person = None
elif not self.contact_person:
self.contact_person = get_default_contact(self.party_type, self.party)
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
@@ -449,15 +461,25 @@ class PaymentEntry(AccountsController):
self.set(self.party_account_field, party_account)
self.party_account = party_account
if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance):
if self.paid_from and (
not self.paid_from_account_currency
or not self.paid_from_account_balance
or not self.paid_from_account_type
):
acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
self.paid_from_account_currency = acc.account_currency
self.paid_from_account_balance = acc.account_balance
self.paid_from_account_type = acc.account_type
if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance):
if self.paid_to and (
not self.paid_to_account_currency
or not self.paid_to_account_balance
or not self.paid_to_account_type
):
acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
self.paid_to_account_currency = acc.account_currency
self.paid_to_account_balance = acc.account_balance
self.paid_to_account_type = acc.account_type
self.party_account_currency = (
self.paid_from_account_currency
@@ -472,47 +494,48 @@ class PaymentEntry(AccountsController):
reference_exchange_details: dict | None = None,
) -> None:
for d in self.get("references"):
if d.allocated_amount:
if update_ref_details_only_for and (
(d.reference_doctype, d.reference_name) not in update_ref_details_only_for
):
if not d.allocated_amount:
continue
if update_ref_details_only_for and (
(d.reference_doctype, d.reference_name) not in update_ref_details_only_for
):
continue
ref_details = get_reference_details(
d.reference_doctype,
d.reference_name,
self.party_account_currency,
self.party_type,
self.party,
)
# Only update exchange rate when the reference is Journal Entry
if (
reference_exchange_details
and d.reference_doctype == reference_exchange_details.reference_doctype
and d.reference_name == reference_exchange_details.reference_name
):
ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
for field, value in ref_details.items():
if d.exchange_gain_loss:
# for cases where gain/loss is booked into invoice
# exchange_gain_loss is calculated from invoice & populated
# and row.exchange_rate is already set to payment entry's exchange rate
# refer -> `update_reference_in_payment_entry()` in utils.py
continue
ref_details = get_reference_details(
d.reference_doctype,
d.reference_name,
self.party_account_currency,
self.party_type,
self.party,
)
# Only update exchange rate when the reference is Journal Entry
if (
reference_exchange_details
and d.reference_doctype == reference_exchange_details.reference_doctype
and d.reference_name == reference_exchange_details.reference_name
):
ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
for field, value in ref_details.items():
if d.exchange_gain_loss:
# for cases where gain/loss is booked into invoice
# exchange_gain_loss is calculated from invoice & populated
# and row.exchange_rate is already set to payment entry's exchange rate
# refer -> `update_reference_in_payment_entry()` in utils.py
continue
if field == "exchange_rate" or not d.get(field) or force:
d.db_set(field, value)
if field == "exchange_rate" or not d.get(field) or force:
d.db_set(field, value)
def validate_payment_type(self):
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
frappe.throw(_("Payment Type must be one of Receive, Pay and Internal Transfer"))
def validate_party_details(self):
if self.party:
if not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))
if self.party and not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
@@ -522,12 +545,8 @@ class PaymentEntry(AccountsController):
if self.paid_from:
if self.paid_from_account_currency == self.company_currency:
self.source_exchange_rate = 1
else:
if ref_doc:
if self.paid_from_account_currency == ref_doc.currency:
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get(
"conversion_rate"
)
elif ref_doc and self.paid_from_account_currency == ref_doc.currency:
self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
if not self.source_exchange_rate:
self.source_exchange_rate = get_exchange_rate(
@@ -538,9 +557,8 @@ class PaymentEntry(AccountsController):
if self.paid_from_account_currency == self.paid_to_account_currency:
self.target_exchange_rate = self.source_exchange_rate
elif self.paid_to and not self.target_exchange_rate:
if ref_doc:
if self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
if ref_doc and self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate")
if not self.target_exchange_rate:
self.target_exchange_rate = get_exchange_rate(
@@ -571,63 +589,61 @@ class PaymentEntry(AccountsController):
elif d.reference_name:
if not frappe.db.exists(d.reference_doctype, d.reference_name):
frappe.throw(_("{0} {1} does not exist").format(d.reference_doctype, d.reference_name))
else:
ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name)
if d.reference_doctype != "Journal Entry":
if self.party != ref_doc.get(scrub(self.party_type)):
frappe.throw(
_("{0} {1} is not associated with {2} {3}").format(
_(d.reference_doctype), d.reference_name, _(self.party_type), self.party
)
)
else:
self.validate_journal_entry()
ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name)
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name)
or ref_doc.debit_to
)
elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to
elif self.party_type == "Employee":
ref_party_account = ref_doc.payable_account
if (
ref_party_account != self.party_account
and not self.book_advance_payments_in_separate_party_account
):
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
_(d.reference_doctype),
d.reference_name,
ref_party_account,
self.party_account,
)
)
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
frappe.throw(
_("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
title=_("Invalid Purchase Invoice"),
)
if ref_doc.docstatus != 1:
if d.reference_doctype != "Journal Entry":
if self.party != ref_doc.get(scrub(self.party_type)):
frappe.throw(
_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name)
_("{0} {1} is not associated with {2} {3}").format(
_(d.reference_doctype), d.reference_name, _(self.party_type), self.party
)
)
else:
self.validate_journal_entry()
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name)
or ref_doc.debit_to
)
elif self.party_type == "Supplier":
ref_party_account = ref_doc.credit_to
elif self.party_type == "Employee":
ref_party_account = ref_doc.payable_account
if (
ref_party_account != self.party_account
and not self.book_advance_payments_in_separate_party_account
):
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
_(d.reference_doctype),
d.reference_name,
ref_party_account,
self.party_account,
)
)
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
frappe.throw(
_("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
title=_("Invalid Purchase Invoice"),
)
if ref_doc.docstatus != 1:
frappe.throw(
_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name)
)
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry")
elif self.party_type in ["Shareholder", "Employee"]:
return ("Journal Entry",)
elif self.party_type == "Supplier":
return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry")
elif self.party_type == "Shareholder":
return ("Journal Entry",)
elif self.party_type == "Employee":
return ("Journal Entry",)
def validate_paid_invoices(self):
no_oustanding_refs = {}
@@ -693,37 +709,39 @@ class PaymentEntry(AccountsController):
invoice_paid_amount_map = {}
for ref in self.get("references"):
if ref.payment_term and ref.reference_name:
key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount
if not ref.payment_term or not ref.reference_name:
continue
if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all(
"Payment Schedule",
filters={"parent": ref.reference_name},
fields=[
"paid_amount",
"payment_amount",
"payment_term",
"discount",
"outstanding",
"discount_type",
],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
if not (term.discount_type and term.discount):
continue
key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount
if term.discount_type == "Percentage":
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
else:
invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all(
"Payment Schedule",
filters={"parent": ref.reference_name},
fields=[
"paid_amount",
"payment_amount",
"payment_term",
"discount",
"outstanding",
"discount_type",
],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
if not (term.discount_type and term.discount):
continue
if term.discount_type == "Percentage":
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
else:
invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1):
if not invoice_paid_amount_map.get(key):
@@ -736,16 +754,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 +802,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(
@@ -937,14 +988,14 @@ class PaymentEntry(AccountsController):
applicable_tax = 0
base_applicable_tax = 0
for tax in self.get("taxes"):
if not tax.included_in_paid_amount:
amount = -1 * tax.tax_amount if tax.add_deduct_tax == "Deduct" else tax.tax_amount
base_amount = (
-1 * tax.base_tax_amount if tax.add_deduct_tax == "Deduct" else tax.base_tax_amount
)
if tax.included_in_paid_amount:
continue
applicable_tax += amount
base_applicable_tax += base_amount
amount = -1 * tax.tax_amount if tax.add_deduct_tax == "Deduct" else tax.tax_amount
base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == "Deduct" else tax.base_tax_amount
applicable_tax += amount
base_applicable_tax += base_amount
self.paid_amount_after_tax = flt(
flt(self.paid_amount) + flt(applicable_tax), self.precision("paid_amount_after_tax")
@@ -1217,15 +1268,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 +1362,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 +1409,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 +1429,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 +1509,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 +1536,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 +1564,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 +1582,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 +1620,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 +1647,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,
},
@@ -1572,24 +1659,27 @@ class PaymentEntry(AccountsController):
def add_deductions_gl_entries(self, gl_entries):
for d in self.get("deductions"):
if d.amount:
account_currency = get_account_currency(d.account)
if account_currency != self.company_currency:
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
if not d.amount:
continue
gl_entries.append(
self.get_gl_dict(
{
"account": d.account,
"account_currency": account_currency,
"against": self.party or self.paid_from,
"debit_in_account_currency": d.amount,
"debit": d.amount,
"cost_center": d.cost_center,
},
item=d,
)
account_currency = get_account_currency(d.account)
if account_currency != self.company_currency:
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
gl_entries.append(
self.get_gl_dict(
{
"account": d.account,
"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,
},
item=d,
)
)
def get_party_account_for_taxes(self):
if self.payment_type == "Receive":
@@ -1843,7 +1933,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(
@@ -1904,7 +1994,7 @@ class PaymentEntry(AccountsController):
# Re allocate amount to those references which have PR set (Higher priority)
for ref in self.references:
if not ref.payment_request:
if not (ref.reference_doctype and ref.reference_name and ref.payment_request):
continue
# fetch outstanding_amount of `Reference` (Payment Term) and `Payment Request` to allocate new amount
@@ -1955,7 +2045,7 @@ class PaymentEntry(AccountsController):
)
# Re allocate amount to those references which have no PR (Lower priority)
for ref in self.references:
if ref.payment_request:
if ref.payment_request or not (ref.reference_doctype and ref.reference_name):
continue
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
@@ -2842,7 +2932,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
@@ -2851,10 +2941,14 @@ def get_payment_entry(
party_account_currency if payment_type == "Receive" else bank.account_currency
)
pe.paid_to_account_currency = party_account_currency if payment_type == "Pay" else bank.account_currency
pe.paid_from_account_type = frappe.db.get_value("Account", pe.paid_from, "account_type")
pe.paid_to_account_type = frappe.db.get_value("Account", pe.paid_to, "account_type")
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(
@@ -3208,26 +3302,25 @@ def set_paid_amount_and_received_amount(
if party_account_currency == bank.account_currency:
paid_amount = received_amount = abs(outstanding_amount)
else:
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
if payment_type == "Receive":
paid_amount = abs(outstanding_amount)
if bank_amount:
received_amount = bank_amount
else:
if bank and company_currency != bank.account_currency:
received_amount = paid_amount / doc.get("conversion_rate", 1)
else:
received_amount = paid_amount * doc.get("conversion_rate", 1)
# settings if it is for receive
paid_amount = abs(outstanding_amount)
if bank_amount:
received_amount = bank_amount
else:
received_amount = abs(outstanding_amount)
if bank_amount:
paid_amount = bank_amount
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
if bank and company_currency != bank.account_currency:
# doc currency can be different from bank currency
posting_date = doc.get("posting_date") or doc.get("transaction_date")
conversion_rate = get_exchange_rate(
bank.account_currency, party_account_currency, posting_date
)
received_amount = paid_amount / conversion_rate
else:
if bank and company_currency != bank.account_currency:
paid_amount = received_amount / doc.get("conversion_rate", 1)
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.get("conversion_rate", 1)
received_amount = paid_amount * doc.get("conversion_rate", 1)
# if payment type is pay, then paid amount and received amount are swapped
if payment_type == "Pay":
paid_amount, received_amount = received_amount, paid_amount
return paid_amount, received_amount
@@ -3337,13 +3430,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 +3449,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 +3472,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

@@ -49,6 +49,8 @@ class TestPaymentEntry(FrappeTestCase):
pe.insert()
pe.submit()
self.assertEqual(pe.paid_to_account_type, "Cash")
expected_gle = dict(
(d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
)
@@ -282,6 +284,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()
@@ -518,6 +562,8 @@ class TestPaymentEntry(FrappeTestCase):
pe.insert()
pe.submit()
self.assertEqual(pe.paid_from_account_type, "Bank")
outstanding_amount, status = frappe.db.get_value(
"Purchase Invoice", pi.name, ["outstanding_amount", "status"]
)

View File

@@ -1,9 +1,9 @@
import json
import frappe
from frappe import _, qb
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Abs, Sum
from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate
from frappe.utils.background_jobs import enqueue
@@ -12,7 +12,6 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_company_defaults,
get_payment_entry,
)
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
@@ -120,13 +119,13 @@ class PaymentRequest(Document):
title=_("Invalid Amount"),
)
existing_payment_request_amount = flt(
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
)
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
if not ref_amount:
frappe.throw(_("Payment Entry is already created"))
existing_payment_request_amount = flt(get_existing_payment_request_amount(ref_doc))
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
frappe.throw(
@@ -544,6 +543,8 @@ def make_payment_request(**args):
gateway_account = get_gateway_details(args) or frappe._dict()
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
if not grand_total:
frappe.throw(_("Payment Entry is already created"))
if args.loyalty_points and args.dt == "Sales Order":
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
@@ -554,19 +555,8 @@ def make_payment_request(**args):
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
grand_total = grand_total - loyalty_amount
bank_account = (
get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else ""
)
draft_payment_request = frappe.db.get_value(
"Payment Request",
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
)
# fetches existing payment request `grand_total` amount
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name)
existing_paid_amount = get_existing_paid_amount(ref_doc.doctype, ref_doc.name)
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc)
def validate_and_calculate_grand_total(grand_total, existing_payment_request_amount):
grand_total -= existing_payment_request_amount
@@ -578,7 +568,7 @@ def make_payment_request(**args):
if args.order_type == "Shopping Cart":
# If Payment Request is in an advanced stage, then create for remaining amount.
if get_existing_payment_request_amount(
ref_doc.doctype, ref_doc.name, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"]
ref_doc, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"]
):
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
else:
@@ -587,14 +577,10 @@ def make_payment_request(**args):
else:
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
if existing_paid_amount:
if ref_doc.party_account_currency == ref_doc.currency:
if ref_doc.conversion_rate:
grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate)
else:
grand_total -= flt(existing_paid_amount)
else:
grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate)
draft_payment_request = frappe.db.get_value(
"Payment Request",
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
)
if draft_payment_request:
frappe.db.set_value(
@@ -602,6 +588,11 @@ def make_payment_request(**args):
)
pr = frappe.get_doc("Payment Request", draft_payment_request)
else:
bank_account = (
get_party_bank_account(args.get("party_type"), args.get("party"))
if args.get("party_type")
else ""
)
pr = frappe.new_doc("Payment Request")
if not args.get("payment_request_type"):
@@ -675,22 +666,40 @@ def make_payment_request(**args):
def get_amount(ref_doc, payment_account=None):
"""get amount based on doctype"""
grand_total = 0
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
advance_amount = flt(ref_doc.advance_paid)
if ref_doc.party_account_currency != ref_doc.currency:
advance_amount = flt(flt(ref_doc.advance_paid) / ref_doc.conversion_rate)
grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - advance_amount
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
if (
dt == "Sales Invoice"
and ref_doc.is_pos
and ref_doc.payments
and any(
[
payment.type == "Phone" and payment.account == payment_account
for payment in ref_doc.payments
]
)
):
grand_total = sum(
[
payment.amount
for payment in ref_doc.payments
if payment.type == "Phone" and payment.account == payment_account
]
)
else:
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total)
grand_total = flt(ref_doc.outstanding_amount)
else:
grand_total = flt(
flt(ref_doc.base_rounded_total or ref_doc.base_grand_total) / ref_doc.conversion_rate
)
elif dt == "Sales Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
grand_total = pay.amount
break
grand_total = flt(flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate)
elif dt == "POS Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
@@ -699,10 +708,7 @@ def get_amount(ref_doc, payment_account=None):
elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
if grand_total > 0:
return flt(grand_total, get_currency_precision())
else:
frappe.throw(_("Payment Entry is already created"))
return flt(grand_total, get_currency_precision()) if grand_total > 0 else 0
def get_irequest_status(payment_requests: None | list = None) -> list:
@@ -745,7 +751,7 @@ def cancel_old_payment_requests(ref_dt, ref_dn):
frappe.db.set_value("Integration Request", ireq.name, "status", "Cancelled")
def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = None) -> list:
def get_existing_payment_request_amount(ref_doc, statuses: list | None = None) -> list:
"""
Return the total amount of Payment Requests against a reference document.
"""
@@ -753,9 +759,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
query = (
frappe.qb.from_(PR)
.select(Sum(PR.grand_total))
.where(PR.reference_doctype == ref_dt)
.where(PR.reference_name == ref_dn)
.select(Sum(PR.outstanding_amount))
.where(PR.reference_doctype == ref_doc.doctype)
.where(PR.reference_name == ref_doc.name)
.where(PR.docstatus == 1)
)
@@ -764,33 +770,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
response = query.run()
return response[0][0] if response[0] else 0
os_amount_in_transaction_currency = flt(response[0][0] if response[0] else 0)
if ref_doc.currency != ref_doc.party_account_currency:
os_amount_in_transaction_currency = flt(os_amount_in_transaction_currency / ref_doc.conversion_rate)
def get_existing_paid_amount(doctype, name):
PL = frappe.qb.DocType("Payment Ledger Entry")
PER = frappe.qb.DocType("Payment Entry Reference")
query = (
frappe.qb.from_(PL)
.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)
)
.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
return os_amount_in_transaction_currency
def get_gateway_details(args): # nosemgrep

View File

@@ -313,6 +313,16 @@ class TestPaymentRequest(FrappeTestCase):
self.assertEqual(pr.outstanding_amount, 800)
self.assertEqual(pr.grand_total, 1000)
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
make_payment_request,
dt="Sales Order",
dn=so.name,
mute_email=1,
submit_doc=1,
return_doc=1,
)
# complete payment
pe = pr.create_payment_entry()
@@ -331,7 +341,7 @@ class TestPaymentRequest(FrappeTestCase):
# creating a more payment Request must not allowed
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
re.compile(r"Payment Entry is already created"),
make_payment_request,
dt="Sales Order",
dn=so.name,
@@ -361,6 +371,17 @@ class TestPaymentRequest(FrappeTestCase):
self.assertEqual(pr.party_account_currency, "INR")
self.assertEqual(pr.status, "Initiated")
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
make_payment_request,
dt="Purchase Invoice",
dn=pi.name,
mute_email=1,
submit_doc=1,
return_doc=1,
)
# to make partial payment
pe = pr.create_payment_entry(submit=False)
pe.paid_amount = 2000
@@ -389,7 +410,7 @@ class TestPaymentRequest(FrappeTestCase):
# creating a more payment Request must not allowed
self.assertRaisesRegex(
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
re.compile(r"Payment Entry is already created"),
make_payment_request,
dt="Purchase Invoice",
dn=pi.name,
@@ -581,6 +602,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

@@ -47,7 +47,7 @@ frappe.ui.form.on("Period Closing Voucher", {
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
company: frm.doc.company,
group_by: "",
categorize_by: "",
show_cancelled_entries: frm.doc.docstatus === 2,
};
frappe.set_route("query-report", "General Ledger");

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

@@ -124,6 +124,11 @@ class POSClosingEntry(StatusUpdater):
def on_submit(self):
consolidate_pos_invoices(closing_entry=self)
frappe.publish_realtime(
f"poe_{self.pos_opening_entry}_closed",
self,
docname=f"POS Opening Entry/{self.pos_opening_entry}",
)
def on_cancel(self):
unconsolidate_pos_invoices(closing_entry=self)

View File

@@ -323,3 +323,15 @@ frappe.ui.form.on("POS Invoice", {
});
},
});
frappe.ui.form.on("Sales Invoice Payment", {
mode_of_payment: function (frm) {
frappe.call({
doc: frm.doc,
method: "set_account_for_mode_of_payment",
callback: function (r) {
refresh_field("payments");
},
});
},
});

View File

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

View File

@@ -196,6 +196,7 @@ class POSInvoice(SalesInvoice):
# run on validate method of selling controller
super(SalesInvoice, self).validate()
self.validate_pos_opening_entry()
self.validate_auto_set_posting_time()
self.validate_mode_of_payment()
self.validate_uom_is_integer("stock_uom", "stock_qty")
@@ -320,6 +321,18 @@ class POSInvoice(SalesInvoice):
_("Payment related to {0} is not completed").format(pay.mode_of_payment)
)
def validate_pos_opening_entry(self):
opening_entries = frappe.get_list(
"POS Opening Entry", filters={"pos_profile": self.pos_profile, "status": "Open", "docstatus": 1}
)
if len(opening_entries) == 0:
frappe.throw(
title=_("POS Opening Entry Missing"),
msg=_("No open POS Opening Entry found for POS Profile {0}.").format(
frappe.bold(self.pos_profile)
),
)
def validate_stock_availablility(self):
if self.is_return:
return
@@ -484,8 +497,11 @@ class POSInvoice(SalesInvoice):
def validate_full_payment(self):
invoice_total = flt(self.rounded_total) or flt(self.grand_total)
is_partial_payment_allowed = frappe.db.get_value(
"POS Profile", self.pos_profile, "allow_partial_payment"
)
if self.docstatus == 1:
if self.docstatus == 1 and not is_partial_payment_allowed:
if self.is_return and self.paid_amount != invoice_total:
frappe.throw(
msg=_("Partial Payment in POS Invoice is not allowed."), exc=PartialPaymentValidationError

View File

@@ -7,6 +7,9 @@ import unittest
import frappe
from frappe import _
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.pos_invoice.pos_invoice import PartialPaymentValidationError, make_sales_return
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -26,6 +29,14 @@ class TestPOSInvoice(unittest.TestCase):
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
frappe.db.sql("delete from `tabTax Rule`")
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
cls.test_user, cls.pos_profile = init_user_and_profile()
create_opening_entry(cls.pos_profile, cls.test_user)
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
def tearDown(self):
if frappe.session.user != "Administrator":
frappe.set_user("Administrator")
@@ -227,12 +238,8 @@ class TestPOSInvoice(unittest.TestCase):
pos = create_pos_invoice(qty=10, do_not_save=True)
pos.set("payments", [])
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
)
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 500})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 500, "default": 1})
pos.insert()
pos.submit()
@@ -270,9 +277,7 @@ class TestPOSInvoice(unittest.TestCase):
do_not_save=1,
)
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Cash", "amount": 1000, "default": 1})
pos.insert()
pos.submit()
@@ -312,9 +317,7 @@ class TestPOSInvoice(unittest.TestCase):
do_not_save=1,
)
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2000, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Cash", "amount": 2000, "default": 1})
pos.insert()
pos.submit()
@@ -325,9 +328,7 @@ class TestPOSInvoice(unittest.TestCase):
# partial return 1
pos_return1.get("items")[0].qty = -1
pos_return1.set("payments", [])
pos_return1.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
)
pos_return1.append("payments", {"mode_of_payment": "Cash", "amount": -1000, "default": 1})
pos_return1.paid_amount = -1000
pos_return1.submit()
pos_return1.reload()
@@ -344,9 +345,7 @@ class TestPOSInvoice(unittest.TestCase):
# partial return 2
pos_return2 = make_sales_return(pos.name)
pos_return2.set("payments", [])
pos_return2.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
)
pos_return2.append("payments", {"mode_of_payment": "Cash", "amount": -1000, "default": 1})
pos_return2.paid_amount = -1000
pos_return2.submit()
@@ -366,10 +365,8 @@ class TestPOSInvoice(unittest.TestCase):
)
pos.set("payments", [])
pos.append("payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 50})
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 50})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 60, "default": 1})
pos.insert()
pos.submit()
@@ -387,7 +384,7 @@ class TestPOSInvoice(unittest.TestCase):
pos_inv = create_pos_invoice(rate=10000, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 9000},
{"mode_of_payment": "Cash", "amount": 9000},
)
pos_inv.insert()
self.assertRaises(PartialPaymentValidationError, pos_inv.submit)
@@ -418,9 +415,7 @@ class TestPOSInvoice(unittest.TestCase):
do_not_save=1,
)
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
)
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
pos.insert()
pos.submit()
@@ -439,9 +434,7 @@ class TestPOSInvoice(unittest.TestCase):
do_not_save=1,
)
pos2.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
)
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
@@ -490,9 +483,7 @@ class TestPOSInvoice(unittest.TestCase):
do_not_save=1,
)
pos2.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
)
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
@@ -555,9 +546,7 @@ class TestPOSInvoice(unittest.TestCase):
)
pos.get("items")[0].has_serial_no = 1
pos.set("payments", [])
pos.append(
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
)
pos.append("payments", {"mode_of_payment": "Cash", "amount": 1000, "default": 1})
pos = pos.save().submit()
# make a return
@@ -603,7 +592,7 @@ class TestPOSInvoice(unittest.TestCase):
inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
{"mode_of_payment": "Cash", "amount": 10000},
)
inv.insert()
inv.submit()
@@ -635,7 +624,7 @@ class TestPOSInvoice(unittest.TestCase):
pos_inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
{"mode_of_payment": "Cash", "amount": 10000},
)
pos_inv.paid_amount = 10000
pos_inv.submit()
@@ -650,7 +639,7 @@ class TestPOSInvoice(unittest.TestCase):
inv.loyalty_amount = inv.loyalty_points * before_lp_details.conversion_factor
inv.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000 - inv.loyalty_amount},
{"mode_of_payment": "Cash", "amount": 10000 - inv.loyalty_amount},
)
inv.paid_amount = 10000
inv.submit()
@@ -671,12 +660,12 @@ class TestPOSInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 270})
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270})
pos_inv.save()
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 3200})
pos_inv2.save()
pos_inv2.submit()
@@ -697,7 +686,7 @@ class TestPOSInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
pos_inv.append(
"taxes",
{
@@ -714,7 +703,7 @@ class TestPOSInvoice(unittest.TestCase):
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
pos_inv2.additional_discount_percentage = 10
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 540})
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 540})
pos_inv2.append(
"taxes",
{
@@ -752,7 +741,7 @@ class TestPOSInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
pos_inv.append(
"taxes",
{
@@ -767,7 +756,7 @@ class TestPOSInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, pos_inv.submit)
pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 400})
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 400})
pos_inv2.append(
"taxes",
{
@@ -812,7 +801,7 @@ class TestPOSInvoice(unittest.TestCase):
pos_inv1 = create_pos_invoice(item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1)
pos_inv1.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 4500},
{"mode_of_payment": "Cash", "amount": 4500},
)
pos_inv1.items[0].batch_no = batch_no
pos_inv1.save()
@@ -833,7 +822,7 @@ class TestPOSInvoice(unittest.TestCase):
)
pos_inv2.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3000},
{"mode_of_payment": "Cash", "amount": 3000},
)
pos_inv2.save()
pos_inv2.submit()
@@ -873,7 +862,7 @@ class TestPOSInvoice(unittest.TestCase):
)
pos_inv1.append(
"payments",
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300},
{"mode_of_payment": "Cash", "amount": 300},
)
pos_inv1.save()
pos_inv1.submit()

View File

@@ -37,6 +37,7 @@
"column_break_19",
"discount_percentage",
"discount_amount",
"distributed_discount_amount",
"base_rate_with_margin",
"section_break1",
"rate",
@@ -847,11 +848,17 @@
{
"fieldname": "column_break_ciit",
"fieldtype": "Column Break"
},
{
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
}
],
"istable": 1,
"links": [],
"modified": "2024-05-07 15:56:53.343317",
"modified": "2024-05-07 15:56:54.343317",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",

View File

@@ -39,6 +39,7 @@ class POSInvoiceItem(Document):
description: DF.TextEditor
discount_amount: DF.Currency
discount_percentage: DF.Percent
distributed_discount_amount: DF.Currency
dn_detail: DF.Data | None
enable_deferred_revenue: DF.Check
expense_account: DF.Link | None

View File

@@ -2,12 +2,14 @@
# For license information, please see license.txt
import hashlib
import json
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 +120,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 +161,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 +230,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
@@ -297,10 +303,17 @@ class POSInvoiceMergeLog(Document):
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
accounting_dimensions_fields = [d.fieldname for d in accounting_dimensions]
dimension_values = frappe.db.get_value(
"POS Profile", {"name": invoice.pos_profile}, accounting_dimensions_fields, as_dict=1
"POS Profile",
{"name": invoice.pos_profile},
[*accounting_dimensions_fields, "cost_center", "project"],
as_dict=1,
)
for dimension in accounting_dimensions:
dimension_value = dimension_values.get(dimension.fieldname)
dimension_value = (
data[0].get(dimension.fieldname)
if data[0].get(dimension.fieldname)
else dimension_values.get(dimension.fieldname)
)
if not dimension_value and (dimension.mandatory_for_pl or dimension.mandatory_for_bs):
frappe.throw(
@@ -312,6 +325,14 @@ class POSInvoiceMergeLog(Document):
invoice.set(dimension.fieldname, dimension_value)
invoice.set(
"cost_center",
data[0].get("cost_center") if data[0].get("cost_center") else dimension_values.get("cost_center"),
)
invoice.set(
"project", data[0].get("project") if data[0].get("project") else dimension_values.get("project")
)
if self.merge_invoices_based_on == "Customer Group":
invoice.flags.ignore_pos_profile = True
invoice.pos_profile = ""
@@ -327,16 +348,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 and credit_notes:
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()
@@ -441,9 +462,34 @@ def get_invoice_customer_map(pos_invoices):
pos_invoice_customer_map.setdefault(customer, [])
pos_invoice_customer_map[customer].append(invoice)
for customer, invoices in pos_invoice_customer_map.items():
pos_invoice_customer_map[customer] = split_invoices_by_accounting_dimension(invoices)
return pos_invoice_customer_map
def split_invoices_by_accounting_dimension(pos_invoices):
# pos_invoices = {
# {'dim_field1': 'dim_field1_value1', 'dim_field2': 'dim_field2_value1'}: [],
# {'dim_field1': 'dim_field1_value2', 'dim_field2': 'dim_field2_value1'}: []
# }
pos_invoice_accounting_dimensions_map = {}
for invoice in pos_invoices:
dimension_fields = [d.fieldname for d in get_checks_for_pl_and_bs_accounts()]
accounting_dimensions = frappe.db.get_value(
"POS Invoice", invoice.pos_invoice, [*dimension_fields, "cost_center", "project"], as_dict=1
)
accounting_dimensions_dic_hash = hashlib.sha256(
json.dumps(accounting_dimensions).encode()
).hexdigest()
pos_invoice_accounting_dimensions_map.setdefault(accounting_dimensions_dic_hash, [])
pos_invoice_accounting_dimensions_map[accounting_dimensions_dic_hash].append(invoice)
return pos_invoice_accounting_dimensions_map
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
invoices = pos_invoices or (closing_entry and closing_entry.get("pos_transactions"))
if frappe.flags.in_test and not invoices:
@@ -527,20 +573,21 @@ def split_invoices(invoices):
def create_merge_logs(invoice_by_customer, closing_entry=None):
try:
for customer, invoices in invoice_by_customer.items():
for _invoices in split_invoices(invoices):
merge_log = frappe.new_doc("POS Invoice Merge Log")
merge_log.posting_date = (
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
)
merge_log.posting_time = (
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
)
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
merge_log.set("pos_invoices", _invoices)
merge_log.save(ignore_permissions=True)
merge_log.submit()
for customer, invoices_acc_dim in invoice_by_customer.items():
for invoices in invoices_acc_dim.values():
for _invoices in split_invoices(invoices):
merge_log = frappe.new_doc("POS Invoice Merge Log")
merge_log.posting_date = (
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
)
merge_log.posting_time = (
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
)
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
merge_log.set("pos_invoices", _invoices)
merge_log.save(ignore_permissions=True)
merge_log.submit()
if closing_entry:
closing_entry.set_status(update=True, status="Submitted")
closing_entry.db_set("error_message", "")
@@ -628,3 +675,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

@@ -455,3 +455,58 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_separate_consolidated_invoice_for_different_accounting_dimensions(self):
"""
Creating 3 POS Invoices where first POS Invoice has different Cost Center than the other two.
Consolidate the Invoices.
Check whether the first POS Invoice is consolidated with a separate Sales Invoice than the other two.
Check whether the second and third POS Invoice are consolidated with the same Sales Invoice.
"""
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
frappe.db.sql("delete from `tabPOS Invoice`")
create_cost_center(cost_center_name="_Test POS Cost Center 1", is_group=0)
create_cost_center(cost_center_name="_Test POS Cost Center 2", is_group=0)
try:
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.cost_center = "_Test POS Cost Center 1 - _TC"
pos_inv.save()
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv2.save()
pos_inv2.submit()
pos_inv3 = create_pos_invoice(rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv3.save()
pos_inv3.submit()
consolidate_pos_invoices()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv2.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv2.consolidated_invoice))
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")

View File

@@ -70,3 +70,6 @@ class POSOpeningEntry(StatusUpdater):
def on_submit(self):
self.set_status(update=True)
def on_cancel(self):
self.set_status(update=True)

View File

@@ -30,6 +30,7 @@
"allow_rate_change",
"allow_discount_change",
"disable_grand_total_to_default_mop",
"allow_partial_payment",
"section_break_23",
"item_groups",
"column_break_25",
@@ -56,7 +57,8 @@
"apply_discount_on",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
"dimension_col_break",
"project"
],
"fields": [
{
@@ -389,6 +391,20 @@
"fieldname": "disable_grand_total_to_default_mop",
"fieldtype": "Check",
"label": "Disable auto setting Grand Total to default Payment Mode"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Project"
},
{
"default": "0",
"fieldname": "allow_partial_payment",
"fieldtype": "Check",
"label": "Allow Partial Payment"
}
],
"icon": "icon-cog",
@@ -416,7 +432,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2025-01-29 13:12:30.796630",
"modified": "2025-04-14 15:58:20.497426",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
@@ -442,7 +458,8 @@
"role": "Accounts User"
}
],
"sort_field": "modified",
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

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
@@ -28,6 +29,7 @@ class POSProfile(Document):
account_for_change_amount: DF.Link | None
allow_discount_change: DF.Check
allow_partial_payment: DF.Check
allow_rate_change: DF.Check
applicable_for_users: DF.Table[POSProfileUser]
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
@@ -53,6 +55,7 @@ class POSProfile(Document):
payments: DF.Table[POSPaymentMethod]
print_format: DF.Link | None
print_receipt_on_order_complete: DF.Check
project: DF.Link | None
select_print_heading: DF.Link | None
selling_price_list: DF.Link | None
tax_category: DF.Link | None
@@ -204,17 +207,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

@@ -12,7 +12,7 @@
"posting_date",
"company",
"account",
"group_by",
"categorize_by",
"cost_center",
"territory",
"ignore_exchange_rate_revaluation_journals",
@@ -174,14 +174,6 @@
"fieldtype": "Date",
"label": "Start Date"
},
{
"default": "Group by Voucher (Consolidated)",
"depends_on": "eval:(doc.report == 'General Ledger');",
"fieldname": "group_by",
"fieldtype": "Select",
"label": "Group By",
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
},
{
"depends_on": "eval: (doc.report == 'General Ledger');",
"fieldname": "currency",
@@ -397,10 +389,18 @@
"fieldname": "show_remarks",
"fieldtype": "Check",
"label": "Show Remarks"
},
{
"default": "Categorize by Voucher (Consolidated)",
"depends_on": "eval:(doc.report == 'General Ledger');",
"fieldname": "categorize_by",
"fieldtype": "Select",
"label": "Categorize By",
"options": "\nCategorize by Voucher\nCategorize by Voucher (Consolidated)"
}
],
"links": [],
"modified": "2024-12-11 12:11:13.543134",
"modified": "2025-04-30 14:43:23.643006",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",
@@ -436,4 +436,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -44,6 +44,7 @@ class ProcessStatementOfAccounts(Document):
ageing_based_on: DF.Literal["Due Date", "Posting Date"]
based_on_payment_terms: DF.Check
body: DF.TextEditor | None
categorize_by: DF.Literal["", "Categorize by Voucher", "Categorize by Voucher (Consolidated)"]
cc_to: DF.TableMultiSelect[ProcessStatementOfAccountsCC]
collection_name: DF.DynamicLink | None
company: DF.Link
@@ -56,7 +57,6 @@ class ProcessStatementOfAccounts(Document):
finance_book: DF.Link | None
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
from_date: DF.Date | None
group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"]
ignore_cr_dr_notes: DF.Check
ignore_exchange_rate_revaluation_journals: DF.Check
include_ageing: DF.Check
@@ -204,7 +204,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency):
"party": [entry.customer],
"party_name": [entry.customer_name] if entry.customer_name else None,
"presentation_currency": presentation_currency,
"group_by": doc.group_by,
"categorize_by": doc.categorize_by,
"currency": doc.currency,
"project": [p.project_name for p in doc.project],
"show_opening_entries": 0,

View File

@@ -143,8 +143,10 @@
"contact_mobile",
"contact_email",
"company_shipping_address_section",
"shipping_address",
"dispatch_address",
"dispatch_address_display",
"column_break_126",
"shipping_address",
"shipping_address_display",
"company_billing_address_section",
"billing_address",
@@ -1546,7 +1548,7 @@
{
"fieldname": "company_shipping_address_section",
"fieldtype": "Section Break",
"label": "Company Shipping Address"
"label": "Shipping Address"
},
{
"fieldname": "column_break_126",
@@ -1627,13 +1629,28 @@
"fieldname": "update_outstanding_for_self",
"fieldtype": "Check",
"label": "Update Outstanding for Self"
},
{
"fieldname": "dispatch_address_display",
"fieldtype": "Text Editor",
"label": "Dispatch Address",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "dispatch_address",
"fieldtype": "Link",
"label": "Select Dispatch Address ",
"options": "Address",
"print_hide": 1
}
],
"grid_page_length": 50,
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-01-14 11:39:04.564610",
"modified": "2025-04-09 16:49:22.175081",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1688,6 +1705,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "posting_date, supplier, bill_no, base_grand_total, outstanding_amount",
"show_name_in_global_search": 1,
"sort_field": "modified",

View File

@@ -117,6 +117,8 @@ class PurchaseInvoice(BuyingController):
currency: DF.Link | None
disable_rounded_total: DF.Check
discount_amount: DF.Currency
dispatch_address: DF.Link | None
dispatch_address_display: DF.TextEditor | None
due_date: DF.Date | None
from_date: DF.Date | None
grand_total: DF.Currency
@@ -871,6 +873,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 +919,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 +955,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 +971,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 +979,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 +995,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 +1016,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 +1031,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 +1049,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 +1065,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 +1075,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 +1100,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 +1114,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 +1126,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 +1201,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 +1320,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 +1332,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 +1386,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 +1433,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 +1455,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 +1474,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 +1524,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 +1546,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 +1571,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 +1592,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

@@ -2094,7 +2094,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
1,
)
pi = make_pi_from_pr(pr.name)
self.assertEqual(pi.payment_schedule[0].payment_amount, 2500)
self.assertEqual(pi.payment_schedule[0].payment_amount, 1000)
automatically_fetch_payment_terms(enable=0)
frappe.db.set_value(
@@ -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,123 @@ 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 test_apply_discount_on_grand_total(self):
"""
To test if after applying discount on grand total,
the grand total is calculated correctly without any rounding errors
"""
invoice = make_purchase_invoice(qty=3, rate=100, do_not_save=True, do_not_submit=True)
invoice.append(
"items",
{
"item_code": "_Test Item",
"qty": 3,
"rate": 50.3,
},
)
invoice.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"rate": 15,
},
)
# the grand total here will be 518.54
invoice.disable_rounded_total = 1
# apply discount on grand total to adjust the grand total to 518
invoice.discount_amount = 0.54
invoice.save()
# check if grand total is 518 and not something like 517.99 due to rounding errors
self.assertEqual(invoice.grand_total, 518)
def test_apply_discount_on_grand_total_with_previous_row_total_tax(self):
"""
To test if after applying discount on grand total,
where the tax is calculated on previous row total, the grand total is calculated correctly
"""
invoice = make_purchase_invoice(qty=2, rate=100, do_not_save=True, do_not_submit=True)
invoice.extend(
"taxes",
[
{
"charge_type": "Actual",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"tax_amount": 100,
},
{
"charge_type": "On Previous Row Amount",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"row_id": 1,
"rate": 10,
},
{
"charge_type": "On Previous Row Total",
"account_head": "_Test Account VAT - _TC",
"description": "VAT",
"row_id": 1,
"rate": 10,
},
],
)
# the total here will be 340, so applying 40 discount
invoice.discount_amount = 40
invoice.save()
self.assertEqual(invoice.grand_total, 300)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -38,6 +38,7 @@
"column_break_30",
"discount_percentage",
"discount_amount",
"distributed_discount_amount",
"base_rate_with_margin",
"sec_break2",
"rate",
@@ -461,7 +462,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",
@@ -839,7 +841,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount || doc.distributed_discount_amount",
"fieldname": "section_break_26",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -970,12 +972,18 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1
},
{
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-10-28 15:06:19.246141",
"modified": "2025-03-12 16:33:13.453290",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@@ -985,4 +993,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -34,6 +34,7 @@ class PurchaseInvoiceItem(Document):
description: DF.TextEditor | None
discount_amount: DF.Currency
discount_percentage: DF.Percent
distributed_discount_amount: DF.Currency
enable_deferred_expense: DF.Check
expense_account: DF.Link | None
from_warehouse: DF.Link | None

View File

@@ -196,7 +196,7 @@
"fieldname": "item_wise_tax_detail",
"fieldtype": "Code",
"hidden": 1,
"label": "Item Wise Tax Detail ",
"label": "Item Wise Tax Detail",
"oldfieldname": "item_wise_tax_detail",
"oldfieldtype": "Small Text",
"print_hide": 1,
@@ -235,10 +235,11 @@
"read_only": 1
}
],
"grid_page_length": 50,
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-04-08 19:51:36.678551",
"modified": "2025-04-15 13:14:48.936047",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",

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

@@ -782,24 +782,6 @@ frappe.ui.form.on("Sales Invoice", {
};
};
},
// When multiple companies are set up. in case company name is changed set default company address
company: function (frm) {
if (frm.doc.company) {
frappe.call({
method: "erpnext.setup.doctype.company.company.get_default_company_address",
args: { name: frm.doc.company, existing_address: frm.doc.company_address || "" },
debounce: 2000,
callback: function (r) {
if (r.message) {
frm.set_value("company_address", r.message);
} else {
frm.set_value("company_address", "");
}
},
});
}
},
onload: function (frm) {
frm.redemption_conversion_factor = null;
},
@@ -922,9 +904,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({
@@ -1022,6 +1020,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",
@@ -1046,6 +1060,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();
},
@@ -1069,6 +1084,18 @@ frappe.ui.form.on("Sales Invoice Timesheet", {
},
});
frappe.ui.form.on("Sales Invoice Payment", {
mode_of_payment: function (frm) {
frappe.call({
doc: frm.doc,
method: "set_account_for_mode_of_payment",
callback: function (r) {
refresh_field("payments");
},
});
},
});
var set_timesheet_detail_rate = function (cdt, cdn, currency, timelog) {
frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_detail_rate",

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

@@ -267,8 +267,8 @@ class SalesInvoice(SellingController):
self.indicator_title = _("Paid")
def validate(self):
super().validate()
self.validate_auto_set_posting_time()
super().validate()
if not (self.is_pos or self.is_debit_note):
self.so_dn_required()
@@ -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)
@@ -745,10 +751,10 @@ class SalesInvoice(SellingController):
self.paid_amount = paid_amount
self.base_paid_amount = base_paid_amount
@frappe.whitelist()
def set_account_for_mode_of_payment(self):
for payment in self.payments:
if not payment.account:
payment.account = get_bank_cash_account(payment.mode_of_payment, self.company).get("account")
payment.account = get_bank_cash_account(payment.mode_of_payment, self.company).get("account")
def validate_time_sheets_are_submitted(self):
for data in self.timesheets:
@@ -1241,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):
@@ -1274,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,
@@ -1305,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,
@@ -1322,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,
@@ -1336,7 +1348,7 @@ class SalesInvoice(SellingController):
)
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")):
if flt(item.base_net_amount, item.precision("base_net_amount")) or item.is_fixed_asset:
# Do not book income for transfer within same company
if self.is_internal_transfer():
continue
@@ -1420,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,
},
@@ -1471,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,
@@ -1485,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,
@@ -1518,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,
@@ -1537,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,
@@ -1561,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,
@@ -1579,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,
@@ -1610,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,
@@ -1630,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,
@@ -1674,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")
),
@@ -1937,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):
@@ -2267,7 +2298,10 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
# Invert Addresses
update_address(target_doc, "supplier_address", "address_display", source_doc.company_address)
update_address(
target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
target_doc, "dispatch_address", "dispatch_address_display", source_doc.dispatch_address_name
)
update_address(
target_doc, "shipping_address", "shipping_address_display", source_doc.shipping_address_name
)
update_address(
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
@@ -2689,9 +2723,11 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False):
target.closing_text = letter_text.get("closing_text")
target.language = letter_text.get("language")
# update outstanding
# update outstanding from doc
if source.payment_schedule and len(source.payment_schedule) == 1:
target.overdue_payments[0].outstanding = source.get("outstanding_amount")
for row in target.overdue_payments:
if row.payment_schedule == source.payment_schedule[0].name:
row.outstanding = source.get("outstanding_amount")
target.validate()

View File

@@ -12,6 +12,9 @@ from frappe.utils import add_days, flt, format_date, getdate, nowdate, today
import erpnext
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import WarehouseMissingError
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
@@ -54,6 +57,11 @@ class TestSalesInvoice(FrappeTestCase):
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
create_internal_parties()
setup_accounts()
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
set_default_account_for_mode_of_payment(
mode_of_payment, "_Test Company with perpetual inventory", "_Test Bank - TCP1"
)
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def tearDown(self):
@@ -964,10 +972,8 @@ class TestSalesInvoice(FrappeTestCase):
pos.is_pos = 1
pos.update_stock = 1
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - TCP1", "amount": 50}
)
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - TCP1", "amount": 50})
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 50})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 50})
taxes = get_taxes_and_charges()
pos.taxes = []
@@ -996,10 +1002,8 @@ class TestSalesInvoice(FrappeTestCase):
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
)
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500})
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 500})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 500})
pos.insert()
pos.submit()
@@ -1042,10 +1046,8 @@ class TestSalesInvoice(FrappeTestCase):
pos.is_pos = 1
pos.update_stock = 1
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - TCP1", "amount": 50}
)
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - TCP1", "amount": 60})
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 50})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 60})
pos.write_off_outstanding_amount_automatically = 1
pos.insert()
@@ -1085,10 +1087,8 @@ class TestSalesInvoice(FrappeTestCase):
pos.is_pos = 1
pos.update_stock = 1
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - TCP1", "amount": 50}
)
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - TCP1", "amount": 40})
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 50})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 40})
pos.write_off_outstanding_amount_automatically = 1
pos.insert()
@@ -1102,7 +1102,7 @@ class TestSalesInvoice(FrappeTestCase):
pos = create_sales_invoice(do_not_save=True)
pos.is_pos = 1
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 100})
pos.save().submit()
self.assertEqual(pos.outstanding_amount, 0.0)
self.assertEqual(pos.status, "Paid")
@@ -1173,10 +1173,8 @@ class TestSalesInvoice(FrappeTestCase):
for tax in taxes:
pos.append("taxes", tax)
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - TCP1", "amount": 50}
)
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - TCP1", "amount": 60})
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 50})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 60})
pos.insert()
pos.submit()
@@ -1819,17 +1817,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
@@ -3915,10 +3902,8 @@ class TestSalesInvoice(FrappeTestCase):
pos = create_sales_invoice(qty=10, do_not_save=True)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
)
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500})
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 500})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 500})
pos.save().submit()
pos_return = make_sales_return(pos.name)
@@ -4246,6 +4231,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
@@ -4264,12 +4274,55 @@ class TestSalesInvoice(FrappeTestCase):
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.debit_to = "_Test Receivable USD - _TC"
pos.append("payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 20.35})
pos.append("payments", {"mode_of_payment": "Cash", "amount": 20.35})
pos.save().submit()
pos_return = make_sales_return(pos.name)
self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount)
def test_create_return_invoice_for_self_update(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.controllers.sales_and_purchase_return import make_return_doc
invoice = create_sales_invoice()
payment_entry = get_payment_entry(dt=invoice.doctype, dn=invoice.name)
payment_entry.reference_no = "test001"
payment_entry.reference_date = getdate()
payment_entry.save()
payment_entry.submit()
r_invoice = make_return_doc(invoice.doctype, invoice.name)
r_invoice.update_outstanding_for_self = 0
r_invoice.save()
self.assertEqual(r_invoice.update_outstanding_for_self, 1)
r_invoice.submit()
self.assertNotEqual(r_invoice.outstanding_amount, 0)
invoice.reload()
self.assertEqual(invoice.outstanding_amount, 0)
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

@@ -37,6 +37,7 @@
"column_break_19",
"discount_percentage",
"discount_amount",
"distributed_discount_amount",
"base_rate_with_margin",
"section_break1",
"rate",
@@ -105,6 +106,9 @@
"delivery_note",
"dn_detail",
"delivered_qty",
"column_break_vwhb",
"pos_invoice",
"pos_invoice_item",
"internal_transfer_section",
"purchase_order",
"column_break_92",
@@ -256,7 +260,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount || doc.distributed_discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -630,6 +634,7 @@
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1,
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text"
},
@@ -928,6 +933,12 @@
"fieldname": "column_break_ytgd",
"fieldtype": "Column Break"
},
{
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
},
{
"fieldname": "available_quantity_section",
"fieldtype": "Section Break",
@@ -945,19 +956,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:55.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

@@ -40,6 +40,7 @@ class SalesInvoiceItem(Document):
discount_account: DF.Link | None
discount_amount: DF.Currency
discount_percentage: DF.Percent
distributed_discount_amount: DF.Currency
dn_detail: DF.Data | None
enable_deferred_revenue: DF.Check
expense_account: DF.Link | None
@@ -64,6 +65,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

@@ -71,6 +71,7 @@ def get_party_details(
party_address=None,
company_address=None,
shipping_address=None,
dispatch_address=None,
pos_profile=None,
):
if not party:
@@ -92,6 +93,7 @@ def get_party_details(
party_address,
company_address,
shipping_address,
dispatch_address,
pos_profile,
)
@@ -111,6 +113,7 @@ def _get_party_details(
party_address=None,
company_address=None,
shipping_address=None,
dispatch_address=None,
pos_profile=None,
):
party_details = frappe._dict(
@@ -134,6 +137,7 @@ def _get_party_details(
party_address,
company_address,
shipping_address,
dispatch_address,
ignore_permissions=ignore_permissions,
)
set_contact_details(party_details, party, party_type)
@@ -191,34 +195,51 @@ def set_address_details(
party_address=None,
company_address=None,
shipping_address=None,
dispatch_address=None,
*,
ignore_permissions=False,
):
billing_address_field = (
# party_billing
party_billing_field = (
"customer_address" if party_type in ["Lead", "Prospect"] else party_type.lower() + "_address"
)
party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
party_details[party_billing_field] = party_address or get_default_address(party_type, party.name)
if doctype:
party_details.update(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
get_fetch_values(doctype, party_billing_field, party_details[party_billing_field])
)
# address display
party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
# shipping address
if party_type in ["Customer", "Lead"]:
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
party_type, party.name
)
party_details.shipping_address = render_address(
party_details["shipping_address_name"], check_permissions=not ignore_permissions
)
if doctype:
party_details.update(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
)
party_details.address_display = render_address(
party_details[party_billing_field], check_permissions=not ignore_permissions
)
# party_shipping
if party_type in ["Customer", "Lead"]:
party_shipping_field = "shipping_address_name"
party_shipping_display = "shipping_address"
default_shipping = shipping_address
else:
# Supplier
party_shipping_field = "dispatch_address"
party_shipping_display = "dispatch_address_display"
default_shipping = dispatch_address
party_details[party_shipping_field] = default_shipping or get_party_shipping_address(
party_type, party.name
)
party_details[party_shipping_display] = render_address(
party_details[party_shipping_field], check_permissions=not ignore_permissions
)
if doctype:
party_details.update(
get_fetch_values(doctype, party_shipping_field, party_details[party_shipping_field])
)
# company_address
if company_address:
party_details.company_address = company_address
else:
@@ -256,22 +277,20 @@ def set_address_details(
**get_fetch_values(doctype, "shipping_address", party_details.billing_address),
)
party_address, shipping_address = (
party_details.get(billing_address_field),
party_details.shipping_address_name,
party_billing, party_shipping = (
party_details.get(party_billing_field),
party_details.get(party_shipping_field),
)
party_details["tax_category"] = get_address_tax_category(
party.get("tax_category"),
party_address,
shipping_address if party_type != "Supplier" else party_address,
party.get("tax_category"), party_billing, party_shipping
)
if doctype in TRANSACTION_TYPES:
with temporary_flag("company", company):
get_regional_address_details(party_details, doctype, company)
return party_address, shipping_address
return party_billing, party_shipping
@erpnext.allow_regional
@@ -279,35 +298,56 @@ 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):
contact_details = frappe._dict()
if not party_details.contact_person:
party_details.update(
{
"contact_person": None,
"contact_display": None,
"contact_email": None,
"contact_mobile": None,
"contact_phone": None,
"contact_designation": None,
"contact_department": None,
}
if party_details.party_type == "Employee":
contact_details = frappe.db.get_value(
"Employee",
party_details.party,
[
"employee_name as contact_display",
"prefered_email as contact_email",
"cell_number as contact_mobile",
"designation as contact_designation",
"department as contact_department",
],
as_dict=True,
)
contact_details.update({"contact_person": None, "contact_phone": None})
elif party_details.contact_person:
contact_details = frappe.db.get_value(
"Contact",
party_details.contact_person,
[
"name as contact_person",
"full_name as contact_display",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
],
as_dict=True,
)
else:
fields = [
"name as contact_person",
"full_name as contact_display",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
]
contact_details = {
"contact_person": None,
"contact_display": None,
"contact_email": None,
"contact_mobile": None,
"contact_phone": None,
"contact_designation": None,
"contact_department": None,
}
contact_details = frappe.db.get_value("Contact", party_details.contact_person, fields, as_dict=True)
party_details.update(contact_details)
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):
@@ -572,12 +612,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")
@@ -616,34 +657,34 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
return due_date
def validate_due_date(posting_date, due_date, bill_date=None, template_name=None):
def validate_due_date(posting_date, due_date, bill_date=None, template_name=None, doctype=None):
if getdate(due_date) < getdate(posting_date):
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
else:
if not template_name:
return
validate_due_date_with_template(posting_date, due_date, bill_date, template_name, doctype)
default_due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
"%Y-%m-%d"
)
if not default_due_date:
return
def validate_due_date_with_template(posting_date, due_date, bill_date, template_name, doctype=None):
if not template_name:
return
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
is_credit_controller = (
frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
default_due_date = format(get_due_date_from_template(template_name, posting_date, bill_date))
if not default_due_date:
return
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
if frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles():
party_type = "supplier" if doctype == "Purchase Invoice" else "customer"
msgprint(
_("Note: Due Date exceeds allowed {0} credit days by {1} day(s)").format(
party_type, date_diff(due_date, default_due_date)
)
)
if is_credit_controller:
msgprint(
_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)").format(
date_diff(due_date, default_due_date)
)
)
else:
frappe.throw(
_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date))
)
else:
frappe.throw(_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date)))
@frappe.whitelist()
@@ -767,9 +808,9 @@ def validate_account_party_type(self):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if account_type and (account_type not in ["Receivable", "Payable", "Equity"]):
frappe.throw(
_(
"Party Type and Party can only be set for Receivable / Payable account<br><br>" "{0}"
).format(self.account)
_("Party Type and Party can only be set for Receivable / Payable account<br><br>{0}").format(
self.account
)
)
@@ -890,12 +931,16 @@ def get_party_shipping_address(doctype: str, name: str) -> str | None:
["is_shipping_address", "=", 1],
["address_type", "=", "Shipping"],
],
pluck="name",
limit=1,
fields=["name", "is_shipping_address"],
order_by="is_shipping_address DESC",
)
return shipping_addresses[0] if shipping_addresses else None
if shipping_addresses and shipping_addresses[0].is_shipping_address == 1:
return shipping_addresses[0].name
if len(shipping_addresses) == 1:
return shipping_addresses[0].name
else:
return None
def get_partywise_advanced_payment_amount(

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

@@ -164,7 +164,7 @@ frappe.query_reports["Accounts Payable"] = {
},
};
erpnext.utils.add_dimensions("Accounts Payable", 9);
erpnext.utils.add_dimensions("Accounts Payable", 10);
function get_party_type_options() {
let options = [];

View File

@@ -282,4 +282,4 @@
{% } %}
</tbody>
</table>
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>

View File

@@ -54,6 +54,10 @@ class ReceivablePayableReport:
self.filters.range = "30, 60, 90, 120"
self.ranges = [num.strip() for num in self.filters.range.split(",") if num.strip().isdigit()]
self.range_numbers = [num for num in range(1, len(self.ranges) + 2)]
self.ple_fetch_method = (
frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method")
or "Buffered Cursor"
) # Fail Safe
def run(self, args):
self.filters.update(args)
@@ -90,10 +94,7 @@ class ReceivablePayableReport:
self.skip_total_row = 1
def get_data(self):
self.get_ple_entries()
self.get_sales_invoices_or_customers_based_on_sales_person()
self.voucher_balance = OrderedDict()
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
# Build delivery note map against all sales invoices
self.build_delivery_note_map()
@@ -110,12 +111,40 @@ class ReceivablePayableReport:
# Get Exchange Rate Revaluations
self.get_exchange_rate_revaluations()
self.prepare_ple_query()
self.data = []
self.voucher_balance = OrderedDict()
if self.ple_fetch_method == "Buffered Cursor":
self.fetch_ple_in_buffered_cursor()
elif self.ple_fetch_method == "UnBuffered Cursor":
self.fetch_ple_in_unbuffered_cursor()
self.build_data()
def fetch_ple_in_buffered_cursor(self):
self.ple_entries = frappe.db.sql(self.ple_query.get_sql(), as_dict=True)
for ple in self.ple_entries:
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
# This is unavoidable. Initialization and allocation cannot happen in same loop
for ple in self.ple_entries:
self.update_voucher_balance(ple)
self.build_data()
delattr(self, "ple_entries")
def fetch_ple_in_unbuffered_cursor(self):
self.ple_entries = []
with frappe.db.unbuffered_cursor():
for ple in frappe.db.sql(self.ple_query.get_sql(), as_dict=True, as_iterator=True):
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
self.ple_entries.append(ple)
# This is unavoidable. Initialization and allocation cannot happen in same loop
for ple in self.ple_entries:
self.update_voucher_balance(ple)
delattr(self, "ple_entries")
def build_voucher_dict(self, ple):
return frappe._dict(
@@ -136,26 +165,22 @@ class ReceivablePayableReport:
outstanding_in_account_currency=0.0,
)
def init_voucher_balance(self):
# build all keys, since we want to exclude vouchers beyond the report date
for ple in self.ple_entries:
# get the balance object for voucher_type
def init_voucher_balance(self, ple):
if self.filters.get("ignore_accounts"):
key = (ple.voucher_type, ple.voucher_no, ple.party)
else:
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
if self.filters.get("ignore_accounts"):
key = (ple.voucher_type, ple.voucher_no, ple.party)
else:
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
if key not in self.voucher_balance:
self.voucher_balance[key] = self.build_voucher_dict(ple)
if key not in self.voucher_balance:
self.voucher_balance[key] = self.build_voucher_dict(ple)
if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no:
self.voucher_balance[key].cost_center = ple.cost_center
if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no:
self.voucher_balance[key].cost_center = ple.cost_center
self.get_invoices(ple)
self.get_invoices(ple)
if self.filters.get("group_by_party"):
self.init_subtotal_row(ple.party)
if self.filters.get("group_by_party"):
self.init_subtotal_row(ple.party)
if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"):
self.init_subtotal_row("Total")
@@ -517,10 +542,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 +565,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 +591,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 +758,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
@@ -772,7 +803,7 @@ class ReceivablePayableReport:
)
row["range" + str(index + 1)] = row.outstanding
def get_ple_entries(self):
def prepare_ple_query(self):
# get all the GL entries filtered by the given filters
self.prepare_conditions()
@@ -825,7 +856,7 @@ class ReceivablePayableReport:
else:
query = query.orderby(self.ple.posting_date, self.ple.party)
self.ple_entries = query.run(as_dict=True)
self.ple_query = query
def get_sales_invoices_or_customers_based_on_sales_person(self):
if self.filters.get("sales_person"):

View File

@@ -21,7 +21,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False, **args):
frappe.set_user("Administrator")
si = create_sales_invoice(
item=self.item,
@@ -34,6 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
rate=100,
price_list_rate=100,
do_not_save=1,
**args,
)
if not no_payment_schedule:
si.append(
@@ -108,7 +109,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note])
pos_inv.cancel()
def test_accounts_receivable(self):
def test_accounts_receivable_with_payment(self):
filters = {
"company": self.company,
"based_on_payment_terms": 1,
@@ -145,11 +146,15 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
cr_note = self.create_credit_note(si.name, do_not_submit=True)
cr_note.update_outstanding_for_self = False
cr_note.save().submit()
# as the invoice partially paid and returning the full amount so the outstanding amount should be True
self.assertEqual(cr_note.update_outstanding_for_self, True)
report = execute(filters)
expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to]
expected_data_after_credit_note = [0, 0, 100, 0, -100, self.debit_to]
row = report[1][0]
row = report[1][-1]
self.assertEqual(
expected_data_after_credit_note,
[
@@ -162,6 +167,99 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
],
)
def test_accounts_receivable_without_payment(self):
filters = {
"company": self.company,
"based_on_payment_terms": 1,
"report_date": today(),
"range": "30, 60, 90, 120",
"show_remarks": True,
}
# check invoice grand total and invoiced column's value for 3 payment terms
si = self.create_sales_invoice()
report = execute(filters)
expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks])
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
cr_note = self.create_credit_note(si.name, do_not_submit=True)
cr_note.update_outstanding_for_self = False
cr_note.save().submit()
self.assertEqual(cr_note.update_outstanding_for_self, False)
report = execute(filters)
row = report[1]
self.assertTrue(len(row) == 0)
def test_accounts_receivable_with_partial_payment(self):
filters = {
"company": self.company,
"based_on_payment_terms": 1,
"report_date": today(),
"range": "30, 60, 90, 120",
"show_remarks": True,
}
# check invoice grand total and invoiced column's value for 3 payment terms
si = self.create_sales_invoice(qty=2)
report = execute(filters)
expected_data = [[200, 60, "No Remarks"], [200, 100, "No Remarks"], [200, 40, "No Remarks"]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks])
# check invoice grand total, invoiced, paid and outstanding column's value after payment
self.create_payment_entry(si.name)
report = execute(filters)
expected_data_after_payment = [[200, 60, 40, 20], [200, 100, 0, 100], [200, 40, 0, 40]]
for i in range(3):
row = report[1][i - 1]
self.assertEqual(
expected_data_after_payment[i - 1],
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
)
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
cr_note = self.create_credit_note(si.name, do_not_submit=True)
cr_note.update_outstanding_for_self = False
cr_note.save().submit()
self.assertFalse(cr_note.update_outstanding_for_self)
report = execute(filters)
expected_data_after_credit_note = [
[200, 100, 0, 80, 20, self.debit_to],
[200, 40, 0, 0, 40, self.debit_to],
]
for i in range(2):
row = report[1][i - 1]
self.assertEqual(
expected_data_after_credit_note[i - 1],
[
row.invoice_grand_total,
row.invoiced,
row.paid,
row.credit_note,
row.outstanding,
row.party_account,
],
)
def test_cr_note_flag_to_update_self(self):
filters = {
"company": self.company,

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

@@ -1,6 +1,3 @@
<div style="margin-bottom: 7px;">
{%= frappe.boot.letter_heads[frappe.defaults.get_default("letter_head")] %}
</div>
<h2 class="text-center">{%= __("Bank Reconciliation Statement") %}</h2>
<h4 class="text-center">{%= filters.account && (filters.account + ", "+filters.report_date) || "" %} {%= filters.company %}</h4>
<hr>
@@ -46,4 +43,4 @@
{% } %}
</tbody>
</table>
<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>

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

@@ -9,6 +9,7 @@ frappe.query_reports["Customer Ledger Summary"] = {
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1,
},
{
fieldname: "from_date",

View File

@@ -4,7 +4,19 @@
import frappe
from frappe import _, qb, scrub
from frappe.query_builder import Criterion, Tuple
from frappe.query_builder.functions import IfNull
from frappe.utils import getdate, nowdate
from frappe.utils.nestedset import get_descendants_of
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
TREE_DOCTYPES = frozenset(
["Customer Group", "Terrirtory", "Supplier Group", "Sales Partner", "Sales Person", "Cost Center"]
)
class PartyLedgerSummaryReport:
@@ -13,59 +25,110 @@ class PartyLedgerSummaryReport:
self.filters.from_date = getdate(self.filters.from_date or nowdate())
self.filters.to_date = getdate(self.filters.to_date or nowdate())
if not self.filters.get("company"):
self.filters["company"] = frappe.db.get_single_value("Global Defaults", "default_company")
def run(self, args):
if self.filters.from_date > self.filters.to_date:
frappe.throw(_("From Date must be before To Date"))
self.filters.party_type = args.get("party_type")
self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
self.validate_filters()
self.get_party_details()
if not self.parties:
return [], []
self.get_gl_entries()
self.get_additional_columns()
self.get_return_invoices()
self.get_party_adjustment_amounts()
self.party_naming_by = frappe.db.get_single_value(args.get("naming_by")[0], args.get("naming_by")[1])
columns = self.get_columns()
data = self.get_data()
return columns, data
def get_additional_columns(self):
def validate_filters(self):
if not self.filters.get("company"):
frappe.throw(_("{0} is mandatory").format(_("Company")))
if self.filters.from_date > self.filters.to_date:
frappe.throw(_("From Date must be before To Date"))
self.update_hierarchical_filters()
def update_hierarchical_filters(self):
for doctype in TREE_DOCTYPES:
key = scrub(doctype)
if self.filters.get(key):
self.filters[key] = get_children(doctype, self.filters[key])
def get_party_details(self):
"""
Additional Columns for 'User Permission' based access control
"""
self.parties = []
self.party_details = frappe._dict()
party_type = self.filters.party_type
if self.filters.party_type == "Customer":
self.territories = frappe._dict({})
self.customer_group = frappe._dict({})
doctype = qb.DocType(party_type)
conditions = self.get_party_conditions(doctype)
query = (
qb.from_(doctype)
.select(doctype.name.as_("party"), f"{scrub(party_type)}_name")
.where(Criterion.all(conditions))
)
customer = qb.DocType("Customer")
result = (
frappe.qb.from_(customer)
.select(
customer.name, customer.territory, customer.customer_group, customer.default_sales_partner
)
.where(customer.disabled == 0)
.run(as_dict=True)
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions(party_type)
if match_conditions:
query += "and" + match_conditions
party_details = frappe.db.sql(query, params, as_dict=True)
for row in party_details:
self.parties.append(row.party)
self.party_details[row.party] = row
def get_party_conditions(self, doctype):
conditions = []
group_field = "customer_group" if self.filters.party_type == "Customer" else "supplier_group"
if self.filters.party:
conditions.append(doctype.name == self.filters.party)
if self.filters.territory:
conditions.append(doctype.territory.isin(self.filters.territory))
if self.filters.get(group_field):
conditions.append(doctype[group_field].isin(self.filters.get(group_field)))
if self.filters.payment_terms_template:
conditions.append(doctype.payment_terms == self.filters.payment_terms_template)
if self.filters.sales_partner:
conditions.append(doctype.default_sales_partner.isin(self.filters.sales_partner))
if self.filters.sales_person:
sales_team = qb.DocType("Sales Team")
sales_invoice = qb.DocType("Sales Invoice")
customers = (
qb.from_(sales_team)
.select(sales_team.parent)
.where(sales_team.sales_person.isin(self.filters.sales_person))
.where(sales_team.parenttype == "Customer")
) + (
qb.from_(sales_team)
.join(sales_invoice)
.on(sales_team.parent == sales_invoice.name)
.select(sales_invoice.customer)
.where(sales_team.sales_person.isin(self.filters.sales_person))
.where(sales_team.parenttype == "Sales Invoice")
)
for x in result:
self.territories[x.name] = x.territory
self.customer_group[x.name] = x.customer_group
else:
self.supplier_group = frappe._dict({})
supplier = qb.DocType("Supplier")
result = (
frappe.qb.from_(supplier)
.select(supplier.name, supplier.supplier_group)
.where(supplier.disabled == 0)
.run(as_dict=True)
)
conditions.append(doctype.name.isin(customers))
for x in result:
self.supplier_group[x.name] = x.supplier_group
return conditions
def get_columns(self):
columns = [
@@ -81,10 +144,10 @@ class PartyLedgerSummaryReport:
if self.party_naming_by == "Naming Series":
columns.append(
{
"label": _(self.filters.party_type + "Name"),
"label": _(self.filters.party_type + " Name"),
"fieldtype": "Data",
"fieldname": "party_name",
"width": 110,
"width": 150,
}
)
@@ -188,12 +251,14 @@ class PartyLedgerSummaryReport:
self.party_data = frappe._dict({})
for gle in self.gl_entries:
party_details = self.party_details.get(gle.party)
party_name = party_details.get(f"{scrub(self.filters.party_type)}_name", "")
self.party_data.setdefault(
gle.party,
frappe._dict(
{
"party": gle.party,
"party_name": gle.party_name,
**party_details,
"party_name": party_name,
"opening_balance": 0,
"invoiced_amount": 0,
"paid_amount": 0,
@@ -204,12 +269,6 @@ class PartyLedgerSummaryReport:
),
)
if self.filters.party_type == "Customer":
self.party_data[gle.party].update({"territory": self.territories.get(gle.party)})
self.party_data[gle.party].update({"customer_group": self.customer_group.get(gle.party)})
else:
self.party_data[gle.party].update({"supplier_group": self.supplier_group.get(gle.party)})
amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
self.party_data[gle.party].closing_balance += amount
@@ -246,164 +305,120 @@ class PartyLedgerSummaryReport:
return out
def get_gl_entries(self):
conditions = self.prepare_conditions()
join = join_field = ""
if self.filters.party_type == "Customer":
join_field = ", p.customer_name as party_name"
join = "left join `tabCustomer` p on gle.party = p.name"
elif self.filters.party_type == "Supplier":
join_field = ", p.supplier_name as party_name"
join = "left join `tabSupplier` p on gle.party = p.name"
self.gl_entries = frappe.db.sql(
f"""
select
gle.posting_date, gle.party, gle.voucher_type, gle.voucher_no, gle.against_voucher_type,
gle.against_voucher, gle.debit, gle.credit, gle.is_opening {join_field}
from `tabGL Entry` gle
{join}
where
gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
and gle.posting_date <= %(to_date)s {conditions}
order by gle.posting_date
""",
self.filters,
as_dict=True,
gle = qb.DocType("GL Entry")
query = (
qb.from_(gle)
.select(
gle.posting_date,
gle.party,
gle.voucher_type,
gle.voucher_no,
gle.debit,
gle.credit,
gle.is_opening,
)
.where(
(gle.docstatus < 2)
& (gle.is_cancelled == 0)
& (gle.party_type == self.filters.party_type)
& (IfNull(gle.party, "") != "")
& (gle.posting_date <= self.filters.to_date)
& (gle.party.isin(self.parties))
)
)
def prepare_conditions(self):
conditions = [""]
query = self.prepare_conditions(query)
self.gl_entries = query.run(as_dict=True)
def prepare_conditions(self, query):
gle = qb.DocType("GL Entry")
if self.filters.company:
conditions.append("gle.company=%(company)s")
query = query.where(gle.company == self.filters.company)
if self.filters.finance_book:
conditions.append("ifnull(finance_book,'') in (%(finance_book)s, '')")
query = query.where(IfNull(gle.finance_book, "") == self.filters.finance_book)
if self.filters.get("party"):
conditions.append("party=%(party)s")
if self.filters.cost_center:
query = query.where((gle.cost_center).isin(self.filters.cost_center))
if self.filters.party_type == "Customer":
if self.filters.get("customer_group"):
lft, rgt = frappe.get_cached_value(
"Customer Group", self.filters["customer_group"], ["lft", "rgt"]
)
if self.filters.project:
query = query.where((gle.project).isin(self.filters.project))
conditions.append(
f"""party in (select name from tabCustomer
where exists(select name from `tabCustomer Group` where lft >= {lft} and rgt <= {rgt}
and name=tabCustomer.customer_group))"""
)
accounting_dimensions = get_accounting_dimensions(as_list=False)
if self.filters.get("territory"):
lft, rgt = frappe.db.get_value("Territory", self.filters.get("territory"), ["lft", "rgt"])
if accounting_dimensions:
for dimension in accounting_dimensions:
if self.filters.get(dimension.fieldname):
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
self.filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, self.filters.get(dimension.fieldname)
)
query = query.where(
(gle[dimension.fieldname]).isin(self.filters.get(dimension.fieldname))
)
else:
query = query.where(
(gle[dimension.fieldname]).isin(self.filters.get(dimension.fieldname))
)
conditions.append(
f"""party in (select name from tabCustomer
where exists(select name from `tabTerritory` where lft >= {lft} and rgt <= {rgt}
and name=tabCustomer.territory))"""
)
if self.filters.get("payment_terms_template"):
conditions.append(
"party in (select name from tabCustomer where payment_terms=%(payment_terms_template)s)"
)
if self.filters.get("sales_partner"):
conditions.append(
"party in (select name from tabCustomer where default_sales_partner=%(sales_partner)s)"
)
if self.filters.get("sales_person"):
lft, rgt = frappe.db.get_value(
"Sales Person", self.filters.get("sales_person"), ["lft", "rgt"]
)
conditions.append(
"""exists(select name from `tabSales Team` steam where
steam.sales_person in (select name from `tabSales Person` where lft >= {} and rgt <= {})
and ((steam.parent = voucher_no and steam.parenttype = voucher_type)
or (steam.parent = against_voucher and steam.parenttype = against_voucher_type)
or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)
)
if self.filters.party_type == "Supplier":
if self.filters.get("supplier_group"):
conditions.append(
"""party in (select name from tabSupplier
where supplier_group=%(supplier_group)s)"""
)
return " and ".join(conditions)
return query
def get_return_invoices(self):
doctype = "Sales Invoice" if self.filters.party_type == "Customer" else "Purchase Invoice"
self.return_invoices = [
d.name
for d in frappe.get_all(
doctype,
filters={
"is_return": 1,
"docstatus": 1,
"posting_date": ["between", [self.filters.from_date, self.filters.to_date]],
},
)
]
filters = (
{
"is_return": 1,
"docstatus": 1,
"posting_date": ["between", [self.filters.from_date, self.filters.to_date]],
f"{scrub(self.filters.party_type)}": ["in", self.parties],
},
)
self.return_invoices = frappe.get_all(doctype, filters=filters, pluck="name")
def get_party_adjustment_amounts(self):
conditions = self.prepare_conditions()
account_type = "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
income_or_expense_accounts = frappe.db.get_all(
"Account", filters={"account_type": account_type, "company": self.filters.company}, pluck="name"
)
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
gl = qb.DocType("GL Entry")
if not income_or_expense_accounts:
# prevent empty 'in' condition
income_or_expense_accounts.append("")
else:
# escape '%' in account name
# ignoring frappe.db.escape as it replaces single quotes with double quotes
income_or_expense_accounts = [x.replace("%", "%%") for x in income_or_expense_accounts]
accounts_query = (
qb.from_(gl)
.select(gl.voucher_type, gl.voucher_no)
.where(
(gl.account.isin(income_or_expense_accounts))
& (gl.posting_date.gte(self.filters.from_date))
& (gl.posting_date.lte(self.filters.to_date))
)
)
gl_entries = frappe.db.sql(
f"""
select
posting_date, account, party, voucher_type, voucher_no, debit, credit
from
`tabGL Entry`
where
docstatus < 2 and is_cancelled = 0
and (voucher_type, voucher_no) in (
{accounts_query}
) and (voucher_type, voucher_no) in (
select voucher_type, voucher_no from `tabGL Entry` gle
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
)
""",
self.filters,
as_dict=True,
)
current_period_vouchers = set()
adjustment_voucher_entries = {}
self.party_adjustment_details = {}
self.party_adjustment_accounts = set()
adjustment_voucher_entries = {}
for gle in self.gl_entries:
if (
gle.is_opening != "Yes"
and gle.posting_date >= self.filters.from_date
and gle.posting_date <= self.filters.to_date
):
current_period_vouchers.add((gle.voucher_type, gle.voucher_no))
adjustment_voucher_entries.setdefault((gle.voucher_type, gle.voucher_no), []).append(gle)
if not current_period_vouchers:
return
gl = qb.DocType("GL Entry")
query = (
qb.from_(gl)
.select(gl.voucher_type, gl.voucher_no)
.where(
(gl.docstatus < 2)
& (gl.is_cancelled == 0)
& (gl.posting_date.gte(self.filters.from_date))
& (gl.posting_date.lte(self.filters.to_date))
& (Tuple((gl.voucher_type, gl.voucher_no)).isin(current_period_vouchers))
& (IfNull(gl.party, "") == "")
)
)
query = self.prepare_conditions(query)
gl_entries = query.run(as_dict=True)
for gle in gl_entries:
adjustment_voucher_entries.setdefault((gle.voucher_type, gle.voucher_no), [])
adjustment_voucher_entries[(gle.voucher_type, gle.voucher_no)].append(gle)
for voucher_gl_entries in adjustment_voucher_entries.values():
@@ -440,9 +455,16 @@ class PartyLedgerSummaryReport:
self.party_adjustment_details[party][account] += amount
def get_children(doctype, value):
children = get_descendants_of(doctype, value)
return [value, *children]
def execute(filters=None):
args = {
"party_type": "Customer",
"naming_by": ["Selling Settings", "cust_master_name"],
}
return PartyLedgerSummaryReport(filters).run(args)

View File

@@ -0,0 +1,152 @@
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, getdate, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.customer_ledger_summary.customer_ledger_summary import execute
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
class TestCustomerLedgerSummary(FrappeTestCase, AccountsTestMixin):
def setUp(self):
self.create_company()
self.create_customer()
self.create_item()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def create_sales_invoice(self, do_not_submit=False, **args):
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
posting_date=today(),
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
rate=100,
qty=10,
price_list_rate=100,
do_not_save=1,
**args,
)
si = si.save()
if not do_not_submit:
si = si.submit()
return si
def create_payment_entry(self, docname, do_not_submit=False):
pe = get_payment_entry("Sales Invoice", docname, bank_account=self.cash, party_amount=40)
pe.paid_from = self.debit_to
pe.insert()
if not do_not_submit:
pe.submit()
return pe
def create_credit_note(self, docname, do_not_submit=False):
credit_note = create_sales_invoice(
company=self.company,
customer=self.customer,
item=self.item,
qty=-1,
debit_to=self.debit_to,
cost_center=self.cost_center,
is_return=1,
return_against=docname,
do_not_submit=do_not_submit,
)
return credit_note
def test_ledger_summary_basic_output(self):
filters = {"company": self.company, "from_date": today(), "to_date": today()}
si = self.create_sales_invoice(do_not_submit=True)
si.save().submit()
expected = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 0,
"return_amount": 0,
"closing_balance": 1000.0,
"currency": "INR",
"customer_name": "_Test Customer",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected:
with self.subTest(field=field):
self.assertEqual(report[0].get(field), expected.get(field))
def test_summary_with_return_and_payment(self):
filters = {"company": self.company, "from_date": today(), "to_date": today()}
si = self.create_sales_invoice(do_not_submit=True)
si.save().submit()
expected = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 0,
"return_amount": 0,
"closing_balance": 1000.0,
"currency": "INR",
"customer_name": "_Test Customer",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected:
with self.subTest(field=field):
self.assertEqual(report[0].get(field), expected.get(field))
cr_note = self.create_credit_note(si.name, True)
cr_note.items[0].qty = -2
cr_note.save().submit()
expected_after_cr_note = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 0,
"return_amount": 200.0,
"closing_balance": 800.0,
"currency": "INR",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected_after_cr_note:
with self.subTest(field=field):
self.assertEqual(report[0].get(field), expected_after_cr_note.get(field))
pe = self.create_payment_entry(si.name, True)
pe.paid_amount = 500
pe.save().submit()
expected_after_cr_and_payment = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 500.0,
"return_amount": 200.0,
"closing_balance": 300.0,
"currency": "INR",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected_after_cr_and_payment:
with self.subTest(field=field):
self.assertEqual(report[0].get(field), expected_after_cr_and_payment.get(field))

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

@@ -67,5 +67,5 @@
</tbody>
</table>
<p class="text-right text-muted">
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}
</p>

View File

@@ -9,6 +9,7 @@ import re
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
from pypika.terms import ExistsCriterion
@@ -428,6 +429,7 @@ def set_gl_entries_by_account(
root_type=None,
ignore_closing_entries=False,
ignore_opening_entries=False,
group_by_account=False,
):
"""Returns a dict like { "account": [gl entries], ... }"""
gl_entries = []
@@ -459,6 +461,7 @@ def set_gl_entries_by_account(
root_type,
ignore_closing_entries,
last_period_closing_voucher[0].name,
group_by_account=group_by_account,
)
from_date = add_days(last_period_closing_voucher[0].period_end_date, 1)
ignore_opening_entries = True
@@ -473,6 +476,7 @@ def set_gl_entries_by_account(
root_type,
ignore_closing_entries,
ignore_opening_entries=ignore_opening_entries,
group_by_account=group_by_account,
)
if filters and filters.get("presentation_currency"):
@@ -495,16 +499,21 @@ def get_accounting_entries(
ignore_closing_entries=None,
period_closing_voucher=None,
ignore_opening_entries=False,
group_by_account=False,
):
gl_entry = frappe.qb.DocType(doctype)
query = (
frappe.qb.from_(gl_entry)
.select(
gl_entry.account,
gl_entry.debit,
gl_entry.credit,
gl_entry.debit_in_account_currency,
gl_entry.credit_in_account_currency,
gl_entry.debit if not group_by_account else Sum(gl_entry.debit).as_("debit"),
gl_entry.credit if not group_by_account else Sum(gl_entry.credit).as_("credit"),
gl_entry.debit_in_account_currency
if not group_by_account
else Sum(gl_entry.debit_in_account_currency).as_("debit_in_account_currency"),
gl_entry.credit_in_account_currency
if not group_by_account
else Sum(gl_entry.credit_in_account_currency).as_("credit_in_account_currency"),
gl_entry.account_currency,
)
.where(gl_entry.company == filters.company)
@@ -539,6 +548,9 @@ def get_accounting_entries(
if match_conditions:
query += "and" + match_conditions
if group_by_account:
query += " GROUP BY `account`"
return frappe.db.sql(query, params, as_dict=True)
@@ -630,7 +642,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

@@ -79,4 +79,4 @@
{% } %}
</tbody>
</table>
<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>

View File

@@ -49,7 +49,7 @@ frappe.query_reports["General Ledger"] = {
label: __("Voucher No"),
fieldtype: "Data",
on_change: function () {
frappe.query_report.set_filter_value("group_by", "Group by Voucher (Consolidated)");
frappe.query_report.set_filter_value("categorize_by", "Categorize by Voucher (Consolidated)");
},
},
{
@@ -66,7 +66,7 @@ frappe.query_reports["General Ledger"] = {
fieldtype: "Autocomplete",
options: Object.keys(frappe.boot.party_account_types),
on_change: function () {
frappe.query_report.set_filter_value("party", "");
frappe.query_report.set_filter_value("party", []);
},
},
{
@@ -112,29 +112,29 @@ frappe.query_reports["General Ledger"] = {
hidden: 1,
},
{
fieldname: "group_by",
label: __("Group by"),
fieldname: "categorize_by",
label: __("Categorize by"),
fieldtype: "Select",
options: [
"",
{
label: __("Group by Voucher"),
value: "Group by Voucher",
label: __("Categorize by Voucher"),
value: "Categorize by Voucher",
},
{
label: __("Group by Voucher (Consolidated)"),
value: "Group by Voucher (Consolidated)",
label: __("Categorize by Voucher (Consolidated)"),
value: "Categorize by Voucher (Consolidated)",
},
{
label: __("Group by Account"),
value: "Group by Account",
label: __("Categorize by Account"),
value: "Categorize by Account",
},
{
label: __("Group by Party"),
value: "Group by Party",
label: __("Categorize by Party"),
value: "Categorize by Party",
},
],
default: "Group by Voucher (Consolidated)",
default: "Categorize by Voucher (Consolidated)",
},
{
fieldname: "tax_id",

View File

@@ -63,13 +63,17 @@ def validate_filters(filters, account_details):
if not account_details.get(account):
frappe.throw(_("Account {0} does not exists").format(account))
if filters.get("account") and filters.get("group_by") == "Group by Account":
if not filters.get("categorize_by") and filters.get("group_by"):
filters["categorize_by"] = filters["group_by"]
filters["categorize_by"] = filters["categorize_by"].replace("Group by", "Categorize by")
if filters.get("account") and filters.get("categorize_by") == "Categorize by Account":
filters.account = frappe.parse_json(filters.get("account"))
for account in filters.account:
if account_details[account].is_group == 0:
frappe.throw(_("Can not filter based on Child Account, if grouped by Account"))
if filters.get("voucher_no") and filters.get("group_by") in ["Group by Voucher"]:
if filters.get("voucher_no") and filters.get("categorize_by") in ["Categorize by Voucher"]:
frappe.throw(_("Can not filter based on Voucher No, if grouped by Voucher"))
if filters.from_date > filters.to_date:
@@ -163,9 +167,9 @@ def get_gl_entries(filters, accounting_dimensions):
if filters.get("include_dimensions"):
order_by_statement = "order by posting_date, creation"
if filters.get("group_by") == "Group by Voucher":
if filters.get("categorize_by") == "Categorize by Voucher":
order_by_statement = "order by posting_date, voucher_type, voucher_no"
if filters.get("group_by") == "Group by Account":
if filters.get("categorize_by") == "Categorize by Account":
order_by_statement = "order by account, posting_date, creation"
if filters.get("include_default_book_entries"):
@@ -260,7 +264,7 @@ def get_conditions(filters):
if filters.get("voucher_no_not_in"):
conditions.append("voucher_no not in %(voucher_no_not_in)s")
if filters.get("group_by") == "Group by Party" and not filters.get("party_type"):
if filters.get("categorize_by") == "Categorize by Party" and not filters.get("party_type"):
conditions.append("party_type in ('Customer', 'Supplier')")
if filters.get("party_type"):
@@ -272,7 +276,7 @@ def get_conditions(filters):
if not (
filters.get("account")
or filters.get("party")
or filters.get("group_by") in ["Group by Account", "Group by Party"]
or filters.get("categorize_by") in ["Categorize by Account", "Categorize by Party"]
):
if not ignore_is_opening:
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
@@ -374,26 +378,26 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
# Opening for filtered account
data.append(totals.opening)
if filters.get("group_by") != "Group by Voucher (Consolidated)":
if filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
for _acc, acc_dict in gle_map.items():
# acc
if acc_dict.entries:
# opening
data.append({"debit_in_transaction_currency": None, "credit_in_transaction_currency": None})
if (not filters.get("group_by") and not filters.get("voucher_no")) or (
filters.get("group_by") and filters.get("group_by") != "Group by Voucher"
if (not filters.get("categorize_by") and not filters.get("voucher_no")) or (
filters.get("categorize_by") and filters.get("categorize_by") != "Categorize by Voucher"
):
data.append(acc_dict.totals.opening)
data += acc_dict.entries
# totals
if filters.get("group_by") or not filters.voucher_no:
if filters.get("categorize_by") or not filters.voucher_no:
data.append(acc_dict.totals.total)
# closing
if (not filters.get("group_by") and not filters.get("voucher_no")) or (
filters.get("group_by") and filters.get("group_by") != "Group by Voucher"
if (not filters.get("categorize_by") and not filters.get("voucher_no")) or (
filters.get("categorize_by") and filters.get("categorize_by") != "Categorize by Voucher"
):
data.append(acc_dict.totals.closing)
@@ -430,9 +434,9 @@ def get_totals_dict():
def group_by_field(group_by):
if group_by == "Group by Party":
if group_by == "Categorize by Party":
return "party"
elif group_by in ["Group by Voucher (Consolidated)", "Group by Account"]:
elif group_by in ["Categorize by Voucher (Consolidated)", "Categorize by Account"]:
return "account"
else:
return "voucher_no"
@@ -440,7 +444,7 @@ def group_by_field(group_by):
def initialize_gle_map(gl_entries, filters, totals_dict):
gle_map = OrderedDict()
group_by = group_by_field(filters.get("group_by"))
group_by = group_by_field(filters.get("categorize_by"))
for gle in gl_entries:
gle_map.setdefault(gle.get(group_by), _dict(totals=copy.deepcopy(totals_dict), entries=[]))
@@ -450,8 +454,8 @@ def initialize_gle_map(gl_entries, filters, totals_dict):
def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, totals):
entries = []
consolidated_gle = OrderedDict()
group_by = group_by_field(filters.get("group_by"))
group_by_voucher_consolidated = filters.get("group_by") == "Group by Voucher (Consolidated)"
group_by = group_by_field(filters.get("categorize_by"))
group_by_voucher_consolidated = filters.get("categorize_by") == "Categorize by Voucher (Consolidated)"
if filters.get("show_net_values_in_party_account"):
account_type_map = get_account_type_map(filters.get("company"))
@@ -534,6 +538,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 +684,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

@@ -155,7 +155,7 @@ class TestGeneralLedger(FrappeTestCase):
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"categorize_by": "Categorize by Voucher (Consolidated)",
}
)
)
@@ -246,7 +246,7 @@ class TestGeneralLedger(FrappeTestCase):
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"categorize_by": "Categorize by Voucher (Consolidated)",
"ignore_err": True,
}
)
@@ -261,7 +261,7 @@ class TestGeneralLedger(FrappeTestCase):
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"categorize_by": "Categorize by Voucher (Consolidated)",
"ignore_err": False,
}
)
@@ -308,7 +308,7 @@ class TestGeneralLedger(FrappeTestCase):
"from_date": si.posting_date,
"to_date": si.posting_date,
"account": [si.debit_to],
"group_by": "Group by Voucher (Consolidated)",
"categorize_by": "Categorize by Voucher (Consolidated)",
"ignore_cr_dr_notes": False,
}
)
@@ -325,7 +325,7 @@ class TestGeneralLedger(FrappeTestCase):
"from_date": si.posting_date,
"to_date": si.posting_date,
"account": [si.debit_to],
"group_by": "Group by Voucher (Consolidated)",
"categorize_by": "Categorize by Voucher (Consolidated)",
"ignore_cr_dr_notes": True,
}
)

View File

@@ -11,7 +11,7 @@ import erpnext
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (
add_sub_total_row,
add_total_row,
apply_group_by_conditions,
apply_order_by_conditions,
get_grand_total,
get_group_by_and_display_fields,
get_tax_accounts,
@@ -305,12 +305,6 @@ def apply_conditions(query, pi, pii, filters):
if filters.get("item_group"):
query = query.where(pii.item_group == filters.get("item_group"))
if not filters.get("group_by"):
query = query.orderby(pi.posting_date, order=Order.desc)
query = query.orderby(pii.item_group, order=Order.desc)
else:
query = apply_group_by_conditions(query, pi, pii, filters)
return query
@@ -372,7 +366,17 @@ def get_items(filters, additional_table_columns):
query = apply_conditions(query, pi, pii, filters)
return query.run(as_dict=True)
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions(doctype)
if match_conditions:
query += " and " + match_conditions
query = apply_order_by_conditions(query, pi, pii, filters)
return frappe.db.sql(query, params, as_dict=True)
def get_aii_accounts():

View File

@@ -384,27 +384,24 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None):
| (si.unrealized_profit_loss_account == filters.get("income_account"))
)
if not filters.get("group_by"):
query = query.orderby(si.posting_date, order=Order.desc)
query = query.orderby(sii.item_group, order=Order.desc)
else:
query = apply_group_by_conditions(query, si, sii, filters)
for key, value in (additional_conditions or {}).items():
query = query.where(si[key] == value)
return query
def apply_group_by_conditions(query, si, ii, filters):
if filters.get("group_by") == "Invoice":
query = query.orderby(ii.parent, order=Order.desc)
def apply_order_by_conditions(query, si, ii, filters):
if not filters.get("group_by"):
query += f" order by {si.posting_date} desc, {ii.item_group} desc"
elif filters.get("group_by") == "Invoice":
query += f" order by {ii.parent} desc"
elif filters.get("group_by") == "Item":
query = query.orderby(ii.item_code)
query += f" order by {ii.item_code}"
elif filters.get("group_by") == "Item Group":
query = query.orderby(ii.item_group)
query += f" order by {ii.item_group}"
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
query = query.orderby(si[frappe.scrub(filters.get("group_by"))])
filter_field = frappe.scrub(filters.get("group_by"))
query += f" order by {filter_field} desc"
return query
@@ -479,7 +476,17 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
query = apply_conditions(query, si, sii, filters, additional_conditions)
return query.run(as_dict=True)
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions("Sales Invoice")
if match_conditions:
query += " and " + match_conditions
query = apply_order_by_conditions(query, si, sii, filters)
return frappe.db.sql(query, params, as_dict=True)
def get_delivery_notes_against_sales_order(item_list):

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

@@ -397,7 +397,6 @@ def get_invoices(filters, additional_query_columns):
pi.mode_of_payment,
)
.where(pi.docstatus == 1)
.orderby(pi.posting_date, pi.name, order=Order.desc)
)
if additional_query_columns:
@@ -421,8 +420,17 @@ def get_invoices(filters, additional_query_columns):
)
query = query.where(pi.credit_to.isin(party_account))
invoices = query.run(as_dict=True)
return invoices
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions("Purchase Invoice")
if match_conditions:
query += " and " + match_conditions
query += " order by posting_date desc, name desc"
return frappe.db.sql(query, params, as_dict=True)
def get_conditions(filters, query, doctype):

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