From 284e45011e836bed11e4d1159728652916d45ca4 Mon Sep 17 00:00:00 2001 From: Liuyang <0032@weyeahengine.com> Date: Thu, 24 Jul 2025 16:53:07 +0800 Subject: [PATCH 01/58] Fix: Procurement Tracker - No permission to read Employee #48765 --- .../buying/report/procurement_tracker/procurement_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index bd0798236b3..6f610d2dc20 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -51,7 +51,7 @@ def get_columns(filters): }, { "label": _("Requestor"), - "options": "Employee", + "options": "User", "fieldname": "requestor", "fieldtype": "Link", "width": 140, From f8ea4315517072418b01561a35bbdda13d2c37f5 Mon Sep 17 00:00:00 2001 From: Ayush Chaudhari Date: Tue, 29 Jul 2025 17:13:26 +0530 Subject: [PATCH 02/58] fix: change modified timestamp so migrations work (cherry picked from commit c18d565d3e021d6eb58d48d6a73cc2461eb58192) # Conflicts: # erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json --- .../doctype/job_card_scrap_item/job_card_scrap_item.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json index 5fb65698235..ed98aea4a98 100644 --- a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json @@ -70,7 +70,11 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2021-09-14 01:20:48.588052", +======= + "modified": "2025-07-29 13:09:57.323835", +>>>>>>> c18d565d3e (fix: change modified timestamp so migrations work) "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Scrap Item", From 184cabdcb021a4e8019c4324d1a2c757d647c29c Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Tue, 29 Jul 2025 21:02:04 +0530 Subject: [PATCH 03/58] chore: resolve conflict --- .../doctype/job_card_scrap_item/job_card_scrap_item.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json index ed98aea4a98..2eafd0ba81a 100644 --- a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json @@ -70,11 +70,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2021-09-14 01:20:48.588052", -======= "modified": "2025-07-29 13:09:57.323835", ->>>>>>> c18d565d3e (fix: change modified timestamp so migrations work) "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Scrap Item", @@ -84,4 +80,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From b48619078d8dd99533ad096e943ca8f47ea5dc04 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 29 Jul 2025 21:58:14 +0530 Subject: [PATCH 04/58] Update CODEOWNERS (cherry picked from commit b9b3302b69b5d15bb9c4033893d526e67ddb13be) # Conflicts: # CODEOWNERS --- CODEOWNERS | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 4a19fc871b5..5bdc509ad18 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,22 +3,36 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, +<<<<<<< HEAD erpnext/accounts/ @deepeshgarg007 @ruthra-kumar erpnext/assets/ @khushi8112 @deepeshgarg007 erpnext/regional @deepeshgarg007 @ruthra-kumar erpnext/selling @deepeshgarg007 @ruthra-kumar erpnext/support/ @deepeshgarg007 pos* +======= +erpnext/accounts/ @ruthra-kumar +erpnext/assets/ @khushi8112 +erpnext/regional @ruthra-kumar +erpnext/selling @ruthra-kumar +erpnext/support/ @ruthra-kumar +pos* @diptanilsaha +>>>>>>> b9b3302b69 (Update CODEOWNERS) -erpnext/buying/ @rohitwaghchaure +erpnext/buying/ @rohitwaghchaure @mihir-kandoi erpnext/maintenance/ @rohitwaghchaure -erpnext/manufacturing/ @rohitwaghchaure +erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi erpnext/quality_management/ @rohitwaghchaure -erpnext/stock/ @rohitwaghchaure -erpnext/subcontracting @rohitwaghchaure +erpnext/stock/ @rohitwaghchaure @mihir-kandoi +erpnext/subcontracting @mihir-kandoi +<<<<<<< HEAD erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure erpnext/patches/ @deepeshgarg007 +======= +erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi +erpnext/patches/ @ruthra-kumar +>>>>>>> b9b3302b69 (Update CODEOWNERS) .github/ @deepeshgarg007 pyproject.toml @akhilnarang From 7489c9159d15f730ef693e1fd7e4eb070adfcde3 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 29 Jul 2025 22:22:25 +0530 Subject: [PATCH 05/58] chore: resolve conflicts --- CODEOWNERS | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 5bdc509ad18..a0c68b87789 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,21 +3,12 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -<<<<<<< HEAD -erpnext/accounts/ @deepeshgarg007 @ruthra-kumar -erpnext/assets/ @khushi8112 @deepeshgarg007 -erpnext/regional @deepeshgarg007 @ruthra-kumar -erpnext/selling @deepeshgarg007 @ruthra-kumar -erpnext/support/ @deepeshgarg007 -pos* -======= erpnext/accounts/ @ruthra-kumar erpnext/assets/ @khushi8112 erpnext/regional @ruthra-kumar erpnext/selling @ruthra-kumar erpnext/support/ @ruthra-kumar pos* @diptanilsaha ->>>>>>> b9b3302b69 (Update CODEOWNERS) erpnext/buying/ @rohitwaghchaure @mihir-kandoi erpnext/maintenance/ @rohitwaghchaure @@ -26,13 +17,8 @@ erpnext/quality_management/ @rohitwaghchaure erpnext/stock/ @rohitwaghchaure @mihir-kandoi erpnext/subcontracting @mihir-kandoi -<<<<<<< HEAD -erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure -erpnext/patches/ @deepeshgarg007 -======= erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi erpnext/patches/ @ruthra-kumar ->>>>>>> b9b3302b69 (Update CODEOWNERS) -.github/ @deepeshgarg007 +.github/ @ruthra-kumar pyproject.toml @akhilnarang From db41b143179e36607a459be4d2e64d71334f50e3 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 30 Jul 2025 13:50:32 +0530 Subject: [PATCH 06/58] fix: do not recalculate depreciation on sale invoice cancellation for fully depreciated asset --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 829428eec86..97ded97dd2e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1370,8 +1370,9 @@ class SalesInvoice(SellingController): ) asset.db_set("disposal_date", None) add_asset_activity(asset.name, _("Asset returned")) + asset_status = asset.get_status() - if asset.calculate_depreciation: + if asset.calculate_depreciation and not asset_status == "Fully Depreciated": posting_date = ( frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") if self.is_return From 106f7ea1121674e08885182b4e95441e106150e6 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 30 Jul 2025 13:51:34 +0530 Subject: [PATCH 07/58] test: test asset status after sales invoice creation and cancellation --- erpnext/assets/doctype/asset/test_asset.py | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 609a6b9677d..78a7929a4fe 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -346,6 +346,33 @@ class TestAsset(AssetSetup): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + def test_asset_status_after_sales_invoice_cancel(self): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + asset = create_asset( + calculate_depreciation=1, + available_for_use_date="2020-04-01", + purchase_date="2020-04-01", + expected_value_after_useful_life=0, + total_number_of_depreciations=5, + opening_number_of_booked_depreciations=2, + frequency_of_depreciation=12, + depreciation_start_date="2023-03-31", + opening_accumulated_depreciation=24000, + gross_purchase_amount=60000, + submit=1, + ) + + si = create_sales_invoice( + item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23") + ) + asset.load_from_db() + self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + + si.cancel() + asset.load_from_db() + self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + def test_gle_made_by_asset_sale_for_existing_asset(self): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice From 652589f636fe2181a0e228fca0b97588bf19ded7 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Wed, 30 Jul 2025 15:22:29 +0530 Subject: [PATCH 08/58] fix: server error on opportunity summary by sales stage report (cherry picked from commit 830b3ba1e5af6d60a1aa843b9b745585c6b28ea2) --- .../opportunity_summary_by_sales_stage.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py index 2e8ae7e340e..23ec7d8c537 100644 --- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py @@ -153,7 +153,12 @@ class OpportunitySummaryBySalesStage: }[self.filters.get("based_on")] if self.filters.get("based_on") == "Opportunity Owner": - if d.get(based_on) == "[]" or d.get(based_on) is None or d.get(based_on) == "Not Assigned": + if ( + d.get(based_on) == "[]" + or d.get(based_on) is None + or d.get(based_on) == "Not Assigned" + or d.get(based_on) == "" + ): assignments = ["Not Assigned"] else: assignments = json.loads(d.get(based_on)) From 8cd90de70bbc9c89acba69683bee40e9cab870ad Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Tue, 15 Jul 2025 21:32:07 -0500 Subject: [PATCH 09/58] fix(accounts): allow default bank account per company (cherry picked from commit 982550b92ca21de601289fcc4048fb1e6efca03f) --- erpnext/accounts/doctype/bank_account/bank_account.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 4155f0edeef..5a728eb415b 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -109,6 +109,7 @@ class BankAccount(Document): "party_type": self.party_type, "party": self.party, "is_company_account": self.is_company_account, + "company": self.company, "is_default": 1, "disabled": 0, }, From 7bb127de6351dcac9082bcfdee7c7813b1a3df7a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 07:03:15 +0000 Subject: [PATCH 10/58] Merge pull request #48876 from frappe/mergify/bp/version-15-hotfix/pr-48873 fix: return conversion factor of variant and not template (backport #48873) --- erpnext/stock/get_item_details.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 879b4836ac9..8d04141d78c 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1287,12 +1287,12 @@ def get_conversion_factor(item_code, uom): if variant_of: filters["parent"] = ("in", (item_code, variant_of)) - conversion_factor = frappe.db.get_value("UOM Conversion Detail", filters, "conversion_factor") + conversion_factor = frappe.get_all("UOM Conversion Detail", filters, pluck="conversion_factor") if not conversion_factor: stock_uom = frappe.db.get_value("Item", item_code, "stock_uom") - conversion_factor = get_uom_conv_factor(uom, stock_uom) + conversion_factor = [get_uom_conv_factor(uom, stock_uom) or 1] - return {"conversion_factor": conversion_factor or 1.0} + return {"conversion_factor": conversion_factor[-1]} @frappe.whitelist() From 6f120294775f2f3b7281e821f224c81564c520ac Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 31 Jul 2025 14:04:02 +0530 Subject: [PATCH 11/58] fix: permission error on tests --- .github/workflows/patch_faux.yml | 4 +++- .github/workflows/server-tests-mariadb-faux.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/patch_faux.yml b/.github/workflows/patch_faux.yml index 7674631f41b..12491cdba2b 100644 --- a/.github/workflows/patch_faux.yml +++ b/.github/workflows/patch_faux.yml @@ -1,7 +1,6 @@ # Tests are skipped for these files but github doesn't allow "passing" hence this is required. name: Skipped Patch Test -permissions: none on: pull_request: @@ -12,6 +11,9 @@ on: - "**.html" - "**.csv" +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/server-tests-mariadb-faux.yml b/.github/workflows/server-tests-mariadb-faux.yml index b448b115081..d48a13d9b06 100644 --- a/.github/workflows/server-tests-mariadb-faux.yml +++ b/.github/workflows/server-tests-mariadb-faux.yml @@ -1,7 +1,6 @@ # Tests are skipped for these files but github doesn't allow "passing" hence this is required. name: Skipped Tests -permissions: {} on: pull_request: @@ -11,6 +10,9 @@ on: - "**.md" - "**.html" +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest From c9d69d9629c67cfae33a3570bc915939cc664e50 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 31 Jul 2025 14:19:44 +0530 Subject: [PATCH 12/58] fix: use checkout@v2 instead of v4 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f0abc70c5b..66efc178b1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Entire Repository - uses: actions/checkout@v4 + uses: actions/checkout@v2 with: fetch-depth: 0 persist-credentials: false From 2f4a9f283dab89815661d7f8dae321797487147b Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 31 Jul 2025 15:23:14 +0530 Subject: [PATCH 13/58] fix: failing subcontracting patch (cherry picked from commit bb434199441111f79b6e816b98a1ce0751230420) --- .../repost_gl_entries_with_no_account_subcontracting.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py b/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py index b2208667ea6..e7394f1071c 100644 --- a/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py +++ b/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py @@ -19,7 +19,7 @@ def execute(): if not item.cost_center: item.db_set("cost_center", cost_center) - doc.docstatus = 2 - doc.make_gl_entries_on_cancel() - doc.docstatus = 1 - doc.make_gl_entries() + doc.docstatus = 2 + doc.make_gl_entries_on_cancel() + doc.docstatus = 1 + doc.make_gl_entries() From ed927a2147b792c1dfc460bdc4eb4d3f90e28b8c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 31 Jul 2025 16:00:35 +0530 Subject: [PATCH 14/58] chore: add date so patch can rerun --- erpnext/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 362486a97bb..7bd30f81bc3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -415,5 +415,5 @@ erpnext.patches.v15_0.set_company_on_pos_inv_merge_log erpnext.patches.v15_0.rename_price_list_to_buying_price_list erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice -erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting +erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-07-31 execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction", 1) From 5de5a8bfd59c7e0640e10be6b9c003b3f102ebf1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:42:23 +0000 Subject: [PATCH 15/58] fix: prevent negative values in BOM fields (#48520, #48662) (backport #48696) (#48897) * fix: prevent negative values in BOM fields (#48520, #48662) (#48696) * fix: prevent negative values in BOM fields (#48520, #48662) * fix: applied non_negative validation using Desk UI for BOM fields (cherry picked from commit 3a80e116e8b50d46d41fbaf4fae1c85e95ceb220) # Conflicts: # erpnext/manufacturing/doctype/bom_operation/bom_operation.json # erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json * chore: resolve conflicts * chore: resolve conflicts --------- Co-authored-by: Vishist16 <101823906+Vishist16@users.noreply.github.com> Co-authored-by: Mihir Kandoi --- .../manufacturing/doctype/bom_operation/bom_operation.json | 4 +++- .../doctype/bom_scrap_item/bom_scrap_item.json | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 037a3fe0e81..3bcc4e896bd 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -65,6 +65,7 @@ "fieldname": "hour_rate", "fieldtype": "Currency", "label": "Hour Rate", + "non_negative": 1, "oldfieldname": "hour_rate", "oldfieldtype": "Currency", "options": "currency", @@ -78,6 +79,7 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Operation Time", + "non_negative": 1, "oldfieldname": "time_in_mins", "oldfieldtype": "Currency", "reqd": 1 @@ -194,7 +196,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-04 17:17:16.986941", + "modified": "2025-07-31 16:17:47.287117", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json index b2ef19b20f0..27001227974 100644 --- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json @@ -42,6 +42,7 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Qty", + "non_negative": 1, "reqd": 1 }, { @@ -49,6 +50,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Rate", + "non_negative": 1, "options": "currency" }, { @@ -92,7 +94,7 @@ ], "istable": 1, "links": [], - "modified": "2023-01-03 14:19:28.460965", + "modified": "2025-07-31 16:21:44.047007", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Scrap Item", @@ -103,4 +105,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From cb0addc122341f3d8aadc72e342a8dbc103f8ec3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:30:30 +0530 Subject: [PATCH 16/58] fix: multiple fixes for advance payment accounting (backport #48341) (#48896) * fix: multiple fixes for advance payment accounting (cherry picked from commit e70caedddcecf7ed7d1949b112b5cade746c5265) # Conflicts: # erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json # erpnext/accounts/doctype/payment_entry/payment_entry.py # erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json # erpnext/accounts/utils.py # erpnext/controllers/accounts_controller.py # erpnext/patches/v15_0/create_advance_payment_ledger_records.py * chore: resolve conflicts * fix: do not execute patch if no advance doctypes --------- Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com> Co-authored-by: ljain112 --- .../advance_payment_ledger_entry.json | 16 +- .../advance_payment_ledger_entry.py | 13 +- .../doctype/journal_entry/journal_entry.py | 102 +++++----- .../journal_entry_account.json | 22 ++- .../journal_entry_account.py | 4 +- .../doctype/payment_entry/payment_entry.py | 51 +++-- .../payment_entry/test_payment_entry.py | 4 +- .../payment_entry_reference.json | 24 ++- .../payment_entry_reference.py | 3 +- .../payment_ledger_entry.json | 2 +- .../test_payment_reconciliation.py | 130 +++++++++++++ .../payment_request/test_payment_request.py | 2 +- .../repost_accounting_ledger.py | 4 + .../repost_payment_ledger.py | 3 +- .../test_unreconcile_payment.py | 18 +- .../unreconcile_payment.py | 113 ++++++----- erpnext/accounts/general_ledger.py | 2 + erpnext/accounts/utils.py | 172 +++++++++++------ .../doctype/purchase_order/purchase_order.py | 1 + erpnext/controllers/accounts_controller.py | 103 ++-------- erpnext/patches.txt | 3 +- .../create_advance_payment_ledger_records.py | 180 ++++++++++++------ ...ledger_entries_against_advance_doctypes.py | 25 +++ erpnext/public/js/utils/unreconcile.js | 14 +- .../doctype/sales_order/sales_order.py | 1 + 25 files changed, 650 insertions(+), 362 deletions(-) create mode 100644 erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json index 290ed11c98e..5ad2479e858 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json @@ -12,7 +12,8 @@ "against_voucher_no", "amount", "currency", - "event" + "event", + "delinked" ], "fields": [ { @@ -68,12 +69,20 @@ "label": "Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "fieldname": "delinked", + "fieldtype": "Check", + "label": "DeLinked", + "read_only": 1 } ], + "grid_page_length": 50, "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-11-05 10:31:28.736671", + "modified": "2025-07-29 11:37:42.678556", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Payment Ledger Entry", @@ -107,7 +116,8 @@ "share": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py index 0ec2d411761..a9392fc9367 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.py @@ -1,9 +1,11 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from erpnext.accounts.utils import update_voucher_outstanding + class AdvancePaymentLedgerEntry(Document): # begin: auto-generated types @@ -19,9 +21,16 @@ class AdvancePaymentLedgerEntry(Document): amount: DF.Currency company: DF.Link | None currency: DF.Link | None + delinked: DF.Check event: DF.Data | None voucher_no: DF.DynamicLink | None voucher_type: DF.Link | None # end: auto-generated types - pass + def on_update(self): + if ( + self.against_voucher_type in ["Purchase Order", "Sales Order"] + and self.flags.update_outstanding == "Yes" + and not frappe.flags.is_reverse_depr_entry + ): + update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index d7386902c81..4341aef9a6a 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -191,8 +191,6 @@ class JournalEntry(AccountsController): self.validate_cheque_info() self.check_credit_limit() self.make_gl_entries() - self.make_advance_payment_ledger_entries() - self.update_advance_paid() self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() @@ -225,8 +223,6 @@ class JournalEntry(AccountsController): "Advance Payment Ledger Entry", ) self.make_gl_entries(1) - self.make_advance_payment_ledger_entries() - self.update_advance_paid() self.unlink_advance_entry_reference() self.unlink_asset_reference() self.unlink_inter_company_jv() @@ -237,18 +233,6 @@ class JournalEntry(AccountsController): def get_title(self): return self.pay_to_recd_from or self.accounts[0].account - def update_advance_paid(self): - advance_paid = frappe._dict() - advance_payment_doctypes = get_advance_payment_doctypes() - for d in self.get("accounts"): - if d.is_advance: - if d.reference_type in advance_payment_doctypes: - advance_paid.setdefault(d.reference_type, []).append(d.reference_name) - - for voucher_type, order_list in advance_paid.items(): - for voucher_no in list(set(order_list)): - frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid() - def validate_inter_company_accounts(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: doc = frappe.db.get_value( @@ -1094,49 +1078,65 @@ class JournalEntry(AccountsController): self.transaction_exchange_rate = row.exchange_rate break + advance_doctypes = get_advance_payment_doctypes() + for d in self.get("accounts"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): r = [d.user_remark, self.remark] r = [x for x in r if x] remarks = "\n".join(r) + row = { + "account": d.account, + "party_type": d.party_type, + "due_date": self.due_date, + "party": d.party, + "against": d.against_account, + "debit": flt(d.debit, d.precision("debit")), + "credit": flt(d.credit, d.precision("credit")), + "account_currency": d.account_currency, + "debit_in_account_currency": flt( + d.debit_in_account_currency, d.precision("debit_in_account_currency") + ), + "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, + "voucher_detail_no": d.reference_detail_no, + "cost_center": d.cost_center, + "project": d.project, + "finance_book": self.finance_book, + "advance_voucher_type": d.advance_voucher_type, + "advance_voucher_no": d.advance_voucher_no, + } + + if d.reference_type in advance_doctypes: + row.update( + { + "against_voucher_type": self.doctype, + "against_voucher": self.name, + "advance_voucher_type": d.reference_type, + "advance_voucher_no": d.reference_name, + } + ) + gl_map.append( self.get_gl_dict( - { - "account": d.account, - "party_type": d.party_type, - "due_date": self.due_date, - "party": d.party, - "against": d.against_account, - "debit": flt(d.debit, d.precision("debit")), - "credit": flt(d.credit, d.precision("credit")), - "account_currency": d.account_currency, - "debit_in_account_currency": flt( - d.debit_in_account_currency, d.precision("debit_in_account_currency") - ), - "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, - "voucher_detail_no": d.reference_detail_no, - "cost_center": d.cost_center, - "project": d.project, - "finance_book": self.finance_book, - }, + row, item=d, ) ) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 00fd7b9d0ab..45c2b4ce764 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -32,6 +32,8 @@ "reference_name", "reference_due_date", "reference_detail_no", + "advance_voucher_type", + "advance_voucher_no", "col_break3", "is_advance", "user_remark", @@ -263,12 +265,28 @@ "label": "Reference Detail No", "no_copy": 1, "search_index": 1 + }, + { + "fieldname": "advance_voucher_type", + "fieldtype": "Link", + "label": "Advance Voucher Type", + "no_copy": 1, + "options": "DocType", + "read_only": 1 + }, + { + "fieldname": "advance_voucher_no", + "fieldtype": "Dynamic Link", + "label": "Advance Voucher No", + "no_copy": 1, + "options": "advance_voucher_type", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-05 01:10:50.224840", + "modified": "2025-07-25 04:45:28.117715", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", @@ -279,4 +297,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py index 00c9dcb374b..b801ac8c9a5 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.py @@ -17,8 +17,9 @@ class JournalEntryAccount(Document): account: DF.Link account_currency: DF.Link | None account_type: DF.Data | None + advance_voucher_no: DF.DynamicLink | None + advance_voucher_type: DF.Link | None against_account: DF.Text | None - balance: DF.Currency bank_account: DF.Link | None cost_center: DF.Link | None credit: DF.Currency @@ -31,7 +32,6 @@ class JournalEntryAccount(Document): parentfield: DF.Data parenttype: DF.Data party: DF.DynamicLink | None - party_balance: DF.Currency party_type: DF.Link | None project: DF.Link | None reference_detail_no: DF.Data | None diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 6e8e5e02451..b034e48e3ec 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -117,12 +117,10 @@ class PaymentEntry(AccountsController): def on_submit(self): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) + self.update_payment_requests() self.make_gl_entries() self.update_outstanding_amounts() self.update_payment_schedule() - self.update_payment_requests() - self.make_advance_payment_ledger_entries() - self.update_advance_paid() # advance_paid_status depends on the payment request amount self.set_status() def validate_for_repost(self): @@ -222,13 +220,11 @@ class PaymentEntry(AccountsController): "Advance Payment Ledger Entry", ) super().on_cancel() + self.update_payment_requests(cancel=True) self.make_gl_entries(cancel=1) self.update_outstanding_amounts() self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) - self.update_payment_requests(cancel=True) - self.make_advance_payment_ledger_entries() - self.update_advance_paid() # advance_paid_status depends on the payment request amount self.set_status() def update_payment_requests(self, cancel=False): @@ -1366,23 +1362,27 @@ class PaymentEntry(AccountsController): 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, + "advance_voucher_type": d.advance_voucher_type, + "advance_voucher_no": d.advance_voucher_no, }, item=self, ) ) - if self.book_advance_payments_in_separate_party_account: - if d.reference_doctype in advance_payment_doctypes: - # Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine - gle.update( - { - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, - } - ) - else: - # Do not reference Invoices while Advance is in separate party account - gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) + if d.reference_doctype in advance_payment_doctypes: + # advance reference + gle.update( + { + "against_voucher_type": self.doctype, + "against_voucher": self.name, + "advance_voucher_type": d.reference_doctype, + "advance_voucher_no": d.reference_name, + } + ) + + elif self.book_advance_payments_in_separate_party_account: + # Do not reference Invoices while Advance is in separate party account + gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name}) else: gle.update( { @@ -1512,6 +1512,8 @@ class PaymentEntry(AccountsController): { "against_voucher_type": invoice.reference_doctype, "against_voucher": invoice.reference_name, + "advance_voucher_type": invoice.advance_voucher_type, + "advance_voucher_no": invoice.advance_voucher_no, "posting_date": posting_date, } ) @@ -1536,6 +1538,8 @@ class PaymentEntry(AccountsController): { "against_voucher_type": "Payment Entry", "against_voucher": self.name, + "advance_voucher_type": invoice.advance_voucher_type, + "advance_voucher_no": invoice.advance_voucher_no, } ) gle = self.get_gl_dict( @@ -1684,17 +1688,6 @@ class PaymentEntry(AccountsController): return flt(gl_dict.get(field, 0) / (conversion_rate or 1)) - def update_advance_paid(self): - if self.payment_type not in ("Receive", "Pay") or not self.party: - return - - advance_payment_doctypes = get_advance_payment_doctypes() - for d in self.get("references"): - if d.allocated_amount and d.reference_doctype in advance_payment_doctypes: - frappe.get_doc( - d.reference_doctype, d.reference_name, for_update=True - ).set_total_advance_paid() - def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name self.reference_date = nowdate() diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 1d0810852f0..da6c2eefae4 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -52,7 +52,7 @@ class TestPaymentEntry(FrappeTestCase): 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]] + (d[0], d) for d in [["Debtors - _TC", 0, 1000, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) @@ -84,7 +84,7 @@ class TestPaymentEntry(FrappeTestCase): expected_gle = dict( (d[0], d) - for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]] + for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]] ) self.validate_gl_entries(pe.name, expected_gle) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 57e401275fd..111de2ebd4e 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -22,7 +22,9 @@ "exchange_gain_loss", "account", "payment_request", - "payment_request_outstanding" + "payment_request_outstanding", + "advance_voucher_type", + "advance_voucher_no" ], "fields": [ { @@ -151,12 +153,28 @@ "fieldtype": "Date", "label": "Reconcile Effect On", "read_only": 1 + }, + { + "columns": 2, + "fieldname": "advance_voucher_type", + "fieldtype": "Link", + "label": "Advance Voucher Type", + "options": "DocType", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "advance_voucher_no", + "fieldtype": "Dynamic Link", + "label": "Advance Voucher No", + "options": "advance_voucher_type", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-01-13 15:56:18.895082", + "modified": "2025-07-25 04:32:11.040025", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", @@ -167,4 +185,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py index 1d869b92715..a5e0b21a9af 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py @@ -16,6 +16,8 @@ class PaymentEntryReference(Document): account: DF.Link | None account_type: DF.Data | None + advance_voucher_no: DF.DynamicLink | None + advance_voucher_type: DF.Link | None allocated_amount: DF.Float bill_no: DF.Data | None due_date: DF.Date | None @@ -26,7 +28,6 @@ class PaymentEntryReference(Document): parentfield: DF.Data parenttype: DF.Data payment_request: DF.Link | None - payment_request_outstanding: DF.Float payment_term: DF.Link | None payment_term_outstanding: DF.Float payment_type: DF.Data | None diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index 28c9529995d..30a2da24bbb 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -197,4 +197,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index bd5ba42b0b6..679eb90f19a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -2206,6 +2206,136 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(len(pr.get("payments")), 0) self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200) + def test_partial_advance_payment_with_closed_fiscal_year(self): + """ + Test Advance Payment partial reconciliation before period closing and partial after period closing + """ + default_settings = frappe.db.get_value( + "Company", + self.company, + [ + "book_advance_payments_in_separate_party_account", + "default_advance_paid_account", + "reconciliation_takes_effect_on", + ], + as_dict=True, + ) + first_fy_start_date = frappe.db.get_value("Fiscal Year", {"disabled": 0}, "min(year_start_date)") + prev_fy_start_date = add_years(first_fy_start_date, -1) + prev_fy_end_date = add_days(first_fy_start_date, -1) + + create_fiscal_year( + company=self.company, year_start_date=prev_fy_start_date, year_end_date=prev_fy_end_date + ) + + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": self.advance_payable_account, + "reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance", + }, + ) + + self.supplier = "_Test Supplier" + + # Create advance payment of 1000 (previous FY) + pe = self.create_payment_entry(amount=1000, posting_date=prev_fy_start_date) + pe.party_type = "Supplier" + pe.party = self.supplier + pe.payment_type = "Pay" + pe.paid_from = self.cash + pe.paid_to = self.advance_payable_account + pe.save().submit() + + # Create purchase invoice of 600 (previous FY) + pi1 = self.create_purchase_invoice(qty=1, rate=600, do_not_submit=True) + pi1.posting_date = prev_fy_start_date + pi1.set_posting_time = 1 + pi1.supplier = self.supplier + pi1.credit_to = self.creditors + pi1.save().submit() + + # Reconcile advance payment + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.party = self.supplier + pr.receivable_payable_account = self.creditors + pr.default_advance_account = self.advance_payable_account + pr.from_invoice_date = pr.to_invoice_date = pi1.posting_date + pr.from_payment_date = pr.to_payment_date = pe.posting_date + pr.get_unreconciled_entries() + invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi1.name] + payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # Verify partial reconciliation + pe.reload() + pi1.reload() + + self.assertEqual(len(pe.references), 1) + self.assertEqual(pe.references[0].allocated_amount, 600) + self.assertEqual(flt(pe.unallocated_amount), 400) + + self.assertEqual(pi1.outstanding_amount, 0) + self.assertEqual(pi1.status, "Paid") + + # Close accounting period for March (previous FY) + pcv = make_period_closing_voucher( + company=self.company, cost_center=self.cost_center, posting_date=prev_fy_end_date + ) + pcv.reload() + self.assertEqual(pcv.gle_processing_status, "Completed") + + # Change reconciliation setting to "Reconciliation Date" + frappe.db.set_value( + "Company", + self.company, + "reconciliation_takes_effect_on", + "Reconciliation Date", + ) + + # Create new purchase invoice for 400 in new fiscal year + pi2 = self.create_purchase_invoice(qty=1, rate=400, do_not_submit=True) + pi2.posting_date = today() + pi2.set_posting_time = 1 + pi2.supplier = self.supplier + pi2.currency = "INR" + pi2.credit_to = self.creditors + pi2.save() + pi2.submit() + + # Allocate 600 from advance payment to purchase invoice + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.party = self.supplier + pr.receivable_payable_account = self.creditors + pr.default_advance_account = self.advance_payable_account + pr.from_invoice_date = pr.to_invoice_date = pi2.posting_date + pr.from_payment_date = pr.to_payment_date = pe.posting_date + pr.get_unreconciled_entries() + invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi2.name] + payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + pe.reload() + pi2.reload() + + # Assert advance payment is fully allocated + self.assertEqual(len(pe.references), 2) + self.assertEqual(flt(pe.unallocated_amount), 0) + + # Assert new invoice is fully paid + self.assertEqual(pi2.outstanding_amount, 0) + self.assertEqual(pi2.status, "Paid") + + # Verify reconciliation dates are correct based on company setting + self.assertEqual(getdate(pe.references[0].reconcile_effect_on), getdate(pi1.posting_date)) + self.assertEqual(getdate(pe.references[1].reconcile_effect_on), getdate(pi2.posting_date)) + + frappe.db.set_value("Company", self.company, default_settings) + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index f38c58d6d32..c3f73f0fc12 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -328,7 +328,7 @@ class TestPaymentRequest(FrappeTestCase): self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount self.assertEqual(pe.references[0].allocated_amount, 800) - self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero + self.assertEqual(pe.references[0].outstanding_amount, 0) # Also for orders it will zero self.assertEqual(pe.references[0].payment_request, pr.name) so.load_from_db() diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 800f1647471..aec26b280c4 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -169,6 +169,10 @@ def start_repost(account_repost_doc=str) -> None: frappe.db.delete( "Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name} ) + frappe.db.delete( + "Advance Payment Ledger Entry", + filters={"voucher_type": doc.doctype, "voucher_no": doc.name}, + ) if doc.doctype in ["Sales Invoice", "Purchase Invoice"]: if not repost_doc.delete_cancelled_entries: diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py index 6b90300a899..6fd1b0f2bf2 100644 --- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py +++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py @@ -8,7 +8,7 @@ from frappe import _, qb from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn -from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry +from erpnext.accounts.utils import _delete_adv_pl_entries, _delete_pl_entries, create_payment_ledger_entry VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] @@ -16,6 +16,7 @@ VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None): if voucher_type and voucher_no and gle_map: _delete_pl_entries(voucher_type, voucher_no) + _delete_adv_pl_entries(voucher_type, voucher_no) create_payment_ledger_entry(gle_map, cancel=0) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 021703f6483..256d7870714 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -465,10 +465,26 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): self.assertEqual(len(pr.get("invoices")), 0) self.assertEqual(len(pr.get("payments")), 0) - # Assert 'Advance Paid' so.reload() self.assertEqual(so.advance_paid, 1000) + unreconcile = frappe.get_doc( + { + "doctype": "Unreconcile Payment", + "company": self.company, + "voucher_type": pe.doctype, + "voucher_no": pe.name, + } + ) + unreconcile.add_references() + unreconcile.allocations = [x for x in unreconcile.allocations if x.reference_name == si.name] + unreconcile.save().submit() + + # after unreconcilaition advance paid will be reduced + # Assert 'Advance Paid' + so.reload() + self.assertEqual(so.advance_paid, 0) + self.disable_advance_as_liability() def test_unreconcile_advance_from_journal_entry(self): diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index f7c826bf3fc..02cad39de8f 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -12,7 +12,6 @@ from frappe.utils.data import comma_and from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, - get_advance_payment_doctypes, unlink_ref_doc_from_payment_entries, update_voucher_outstanding, ) @@ -45,31 +44,12 @@ class UnreconcilePayment(Document): @frappe.whitelist() def get_allocations_from_payment(self): - allocated_references = [] - ple = qb.DocType("Payment Ledger Entry") - allocated_references = ( - qb.from_(ple) - .select( - ple.account, - ple.party_type, - ple.party, - ple.against_voucher_type.as_("reference_doctype"), - ple.against_voucher_no.as_("reference_name"), - Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"), - ple.account_currency, - ) - .where( - (ple.docstatus == 1) - & (ple.voucher_type == self.voucher_type) - & (ple.voucher_no == self.voucher_no) - & (ple.voucher_no != ple.against_voucher_no) - ) - .groupby(ple.against_voucher_type, ple.against_voucher_no) - .run(as_dict=True) + return get_linked_payments_for_doc( + company=self.company, + doctype=self.voucher_type, + docname=self.voucher_no, ) - return allocated_references - def add_references(self): allocations = self.get_allocations_from_payment() @@ -82,42 +62,43 @@ class UnreconcilePayment(Document): doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name) unlink_ref_doc_from_payment_entries(doc, self.voucher_no) cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no) + + # update outstanding amounts update_voucher_outstanding( - alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party + alloc.reference_doctype, + alloc.reference_name, + alloc.account, + alloc.party_type, + alloc.party, ) - if doc.doctype in get_advance_payment_doctypes(): - self.make_advance_payment_ledger(alloc) - doc.set_total_advance_paid() frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) - def make_advance_payment_ledger(self, alloc): - if alloc.allocated_amount > 0: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.voucher_type - doc.voucher_no = self.voucher_no - doc.against_voucher_type = alloc.reference_doctype - doc.against_voucher_no = alloc.reference_name - doc.amount = -1 * alloc.allocated_amount - doc.event = "Unreconcile" - doc.currency = alloc.account_currency - doc.flags.ignore_permissions = 1 - doc.save() - @frappe.whitelist() def doc_has_references(doctype: str | None = None, docname: str | None = None): + count = 0 if doctype in ["Sales Invoice", "Purchase Invoice"]: - return frappe.db.count( + count = frappe.db.count( "Payment Ledger Entry", filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]}, ) else: - return frappe.db.count( + count = frappe.db.count( "Payment Ledger Entry", filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]}, ) + count += frappe.db.count( + "Advance Payment Ledger Entry", + filters={ + "delinked": 0, + "voucher_no": docname, + "voucher_type": doctype, + "event": ["=", "Submit"], + }, + ) + + return count @frappe.whitelist() @@ -139,9 +120,12 @@ def get_linked_payments_for_doc( res = ( qb.from_(ple) .select( + ple.account, + ple.party_type, + ple.party, ple.company, - ple.voucher_type, - ple.voucher_no, + ple.voucher_type.as_("reference_doctype"), + ple.voucher_no.as_("reference_name"), Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"), ple.account_currency, ) @@ -163,19 +147,52 @@ def get_linked_payments_for_doc( qb.from_(ple) .select( ple.company, - ple.against_voucher_type.as_("voucher_type"), - ple.against_voucher_no.as_("voucher_no"), + ple.account, + ple.party_type, + ple.party, + ple.against_voucher_type.as_("reference_doctype"), + ple.against_voucher_no.as_("reference_name"), Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"), ple.account_currency, ) .where(Criterion.all(criteria)) .groupby(ple.against_voucher_no) ) + res = query.run(as_dict=True) + + res += get_linked_advances(company, _dn) + return res + return [] +def get_linked_advances(company, docname): + adv = qb.DocType("Advance Payment Ledger Entry") + criteria = [ + (adv.company == company), + (adv.delinked == 0), + (adv.voucher_no == docname), + (adv.event == "Submit"), + ] + + return ( + qb.from_(adv) + .select( + adv.company, + adv.against_voucher_type.as_("reference_doctype"), + adv.against_voucher_no.as_("reference_name"), + Abs(Sum(adv.amount)).as_("allocated_amount"), + adv.currency, + ) + .where(Criterion.all(criteria)) + .having(qb.Field("allocated_amount") > 0) + .groupby(adv.against_voucher_no) + .run(as_dict=True) + ) + + @frappe.whitelist() def create_unreconcile_doc_for_selection(selections=None): if selections: diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 20476b4164c..a0d08792d29 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -305,6 +305,8 @@ def get_merge_properties(dimensions=None): "project", "finance_book", "voucher_no", + "advance_voucher_type", + "advance_voucher_no", ] if dimensions: merge_properties.extend(dimensions) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 97b1bc9ab83..022dfb889a9 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -471,42 +471,27 @@ def reconcile_against_document( reconciled_entries[(row.voucher_type, row.voucher_no)].append(row) for key, entries in reconciled_entries.items(): - voucher_type = key[0] - voucher_no = key[1] + voucher_type, voucher_no = key - # cancel advance entry doc = frappe.get_doc(voucher_type, voucher_no) frappe.flags.ignore_party_validation = True - # When Advance is allocated from an Order to an Invoice - # whole ledger must be reposted - repost_whole_ledger = any([x.voucher_detail_no for x in entries]) - if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: - if repost_whole_ledger: - doc.make_gl_entries(cancel=1) - else: - doc.make_advance_gl_entries(cancel=1) - else: - _delete_pl_entries(voucher_type, voucher_no) - + reposting_rows = [] for entry in entries: check_if_advance_entry_modified(entry) validate_allocated_amount(entry) dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions) - # update ref in advance entry if voucher_type == "Journal Entry": - referenced_row, update_advance_paid = update_reference_in_journal_entry( - entry, doc, do_not_save=False - ) + referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False) # advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss # amount and account in args # referenced_row is used to deduplicate gain/loss journal - entry.update({"referenced_row": referenced_row}) + entry.update({"referenced_row": referenced_row.name}) doc.make_exchange_gain_loss_journal([entry], dimensions_dict) else: - referenced_row, update_advance_paid = update_reference_in_payment_entry( + referenced_row = update_reference_in_payment_entry( entry, doc, do_not_save=True, @@ -515,20 +500,16 @@ def reconcile_against_document( ) if referenced_row.get("outstanding_amount"): referenced_row.outstanding_amount -= flt(entry.allocated_amount) + + reposting_rows.append(referenced_row) + doc.save(ignore_permissions=True) - # re-submit advance entry - doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account: - # When Advance is allocated from an Order to an Invoice - # whole ledger must be reposted - if repost_whole_ledger: - doc.make_gl_entries() - else: - # both ledgers must be posted to for `Advance` in separate account feature - # TODO: find a more efficient way post only for the new linked vouchers - doc.make_advance_gl_entries() + for row in reposting_rows: + doc.make_advance_gl_entries(entry=row) else: + _delete_pl_entries(voucher_type, voucher_no) gl_map = doc.build_gl_map() # Make sure there is no overallocation from erpnext.accounts.general_ledger import process_debit_credit_difference @@ -545,11 +526,6 @@ def reconcile_against_document( entry.party_type, entry.party, ) - # update advance paid in Advance Receivable/Payable doctypes - if update_advance_paid: - for t, n in update_advance_paid: - frappe.get_doc(t, n).set_total_advance_paid() - frappe.flags.ignore_party_validation = False @@ -630,12 +606,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): """ jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0] - # Update Advance Paid in SO/PO since they might be getting unlinked - update_advance_paid = [] - - if jv_detail.get("reference_type") in ["Sales Order", "Purchase Order"]: - update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name)) - rev_dr_or_cr = ( "debit_in_account_currency" if d["dr_or_cr"] == "credit_in_account_currency" @@ -688,6 +658,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): new_row.is_advance = cstr(jv_detail.is_advance) new_row.docstatus = 1 + if jv_detail.get("reference_type") in get_advance_payment_doctypes(): + new_row.advance_voucher_type = jv_detail.get("reference_type") + new_row.advance_voucher_no = jv_detail.get("reference_name") + # will work as update after submit journal_entry.flags.ignore_validate_update_after_submit = True # Ledgers will be reposted by Reconciliation tool @@ -695,7 +669,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): if not do_not_save: journal_entry.save(ignore_permissions=True) - return new_row.name, update_advance_paid + return new_row def update_reference_in_payment_entry( @@ -714,7 +688,8 @@ def update_reference_in_payment_entry( "account": d.account, "dimensions": d.dimensions, } - update_advance_paid = [] + + advance_payment_doctypes = get_advance_payment_doctypes() # Update Reconciliation effect date in reference if payment_entry.book_advance_payments_in_separate_party_account: @@ -726,10 +701,6 @@ def update_reference_in_payment_entry( if d.voucher_detail_no: existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] - # Update Advance Paid in SO/PO since they are getting unlinked - if existing_row.get("reference_doctype") in ["Sales Order", "Purchase Order"]: - update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name)) - if d.allocated_amount <= existing_row.allocated_amount: existing_row.allocated_amount -= d.allocated_amount @@ -737,7 +708,13 @@ def update_reference_in_payment_entry( new_row.docstatus = 1 for field in list(reference_details): new_row.set(field, reference_details[field]) + + if existing_row.reference_doctype in advance_payment_doctypes: + new_row.advance_voucher_type = existing_row.reference_doctype + new_row.advance_voucher_no = existing_row.reference_name + row = new_row + else: new_row = payment_entry.append("references") new_row.docstatus = 1 @@ -771,7 +748,8 @@ def update_reference_in_payment_entry( payment_entry.flags.ignore_reposting_on_reconciliation = True if not do_not_save: payment_entry.save(ignore_permissions=True) - return row, update_advance_paid + + return row def get_reconciliation_effect_date(against_voucher_type, against_voucher, company, posting_date): @@ -949,6 +927,24 @@ def update_accounting_ledgers_after_reference_removal( ple_update_query = ple_update_query.where(ple.voucher_no == payment_name) ple_update_query.run() + # Advance Payment + adv = qb.DocType("Advance Payment Ledger Entry") + adv_ple = ( + qb.update(adv) + .set(adv.delinked, 1) + .set(adv.modified, now()) + .set(adv.modified_by, frappe.session.user) + .where(adv.delinked == 0) + .where( + ((adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no)) + | ((adv.voucher_type == ref_type) & (adv.voucher_no == ref_no)) + ) + ) + if payment_name: + adv_ple = adv_ple.where(adv.voucher_no == payment_name) + + adv_ple.run() + def remove_ref_from_advance_section(ref_doc: object = None): # TODO: this might need some testing @@ -985,6 +981,8 @@ def remove_ref_doc_link_from_jv( qb.update(jea) .set(jea.reference_type, None) .set(jea.reference_name, None) + .set(jea.advance_voucher_type, None) + .set(jea.advance_voucher_no, None) .set(jea.modified, now()) .set(jea.modified_by, frappe.session.user) .where((jea.reference_type == ref_type) & (jea.reference_name == ref_no)) @@ -1495,6 +1493,11 @@ def _delete_pl_entries(voucher_type, voucher_no): qb.from_(ple).delete().where((ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)).run() +def _delete_adv_pl_entries(voucher_type, voucher_no): + adv = qb.DocType("Advance Payment Ledger Entry") + qb.from_(adv).delete().where((adv.voucher_type == voucher_type) & (adv.voucher_no == voucher_no)).run() + + def _delete_gl_entries(voucher_type, voucher_no): gle = qb.DocType("GL Entry") qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run() @@ -1814,6 +1817,11 @@ def get_payment_ledger_entries(gl_entries, cancel=0): dr_or_cr *= -1 dr_or_cr_account_currency *= -1 + against_voucher_type = ( + gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type + ) + against_voucher_no = gle.against_voucher if gle.against_voucher else gle.voucher_no + ple = frappe._dict( doctype="Payment Ledger Entry", posting_date=gle.posting_date, @@ -1828,14 +1836,12 @@ def get_payment_ledger_entries(gl_entries, cancel=0): voucher_type=gle.voucher_type, voucher_no=gle.voucher_no, voucher_detail_no=gle.voucher_detail_no, - against_voucher_type=gle.against_voucher_type - if gle.against_voucher_type - else gle.voucher_type, - against_voucher_no=gle.against_voucher if gle.against_voucher else gle.voucher_no, + against_voucher_type=against_voucher_type, + against_voucher_no=against_voucher_no, account_currency=gle.account_currency, amount=dr_or_cr, amount_in_account_currency=dr_or_cr_account_currency, - delinked=True if cancel else False, + delinked=cancel, remarks=gle.remarks, ) @@ -1844,10 +1850,40 @@ def get_payment_ledger_entries(gl_entries, cancel=0): for dimension in dimensions_and_defaults[0]: ple[dimension.fieldname] = gle.get(dimension.fieldname) + if gle.advance_voucher_no: + # create advance entry + adv = get_advance_ledger_entry( + gle, against_voucher_type, against_voucher_no, dr_or_cr_account_currency, cancel + ) + + ple_map.append(adv) + ple_map.append(ple) + return ple_map +def get_advance_ledger_entry(gle, against_voucher_type, against_voucher_no, amount, cancel): + event = ( + "Submit" + if (against_voucher_type == gle.voucher_type and against_voucher_no == gle.voucher_no) + else "Adjustment" + ) + return frappe._dict( + doctype="Advance Payment Ledger Entry", + company=gle.company, + voucher_type=gle.voucher_type, + voucher_no=gle.voucher_no, + voucher_detail_no=gle.voucher_detail_no, + against_voucher_type=gle.advance_voucher_type, + against_voucher_no=gle.advance_voucher_no, + amount=amount, + currency=gle.account_currency, + event=event, + delinked=cancel, + ) + + def create_payment_ledger_entry( gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False ): @@ -1868,6 +1904,14 @@ def create_payment_ledger_entry( def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party): + if not voucher_type or not voucher_no: + return + + if voucher_type in ["Purchase Order", "Sales Order"]: + ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) + ref_doc.set_total_advance_paid() + return + ple = frappe.qb.DocType("Payment Ledger Entry") vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})] common_filter = [] @@ -1910,7 +1954,27 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa def delink_original_entry(pl_entry, partial_cancel=False): - if pl_entry: + if not pl_entry: + return + + if pl_entry.doctype == "Advance Payment Ledger Entry": + adv = qb.DocType("Advance Payment Ledger Entry") + + ( + qb.update(adv) + .set(adv.delinked, 1) + .set(adv.event, "Cancel") + .set(adv.modified, now()) + .set(adv.modified_by, frappe.session.user) + .where(adv.voucher_type == pl_entry.voucher_type) + .where(adv.voucher_no == pl_entry.voucher_no) + .where(adv.against_voucher_type == pl_entry.against_voucher_type) + .where(adv.against_voucher_no == pl_entry.against_voucher_no) + .where(adv.event == pl_entry.event) + .run() + ) + + else: ple = qb.DocType("Payment Ledger Entry") query = ( qb.update(ple) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index e29e695f48c..6419a4ebac2 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -505,6 +505,7 @@ class PurchaseOrder(BuyingController): self.ignore_linked_doctypes = ( "GL Entry", "Payment Ledger Entry", + "Advance Payment Ledger Entry", "Unreconcile Payment", "Unreconcile Payment Entries", ) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e0e53adeebd..c2fd009fc4a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -397,7 +397,6 @@ class AccountsController(TransactionBase): def on_trash(self): from erpnext.accounts.utils import delete_exchange_gain_loss_journal - self._remove_advance_payment_ledger_entries() self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() self.remove_serial_and_batch_bundle() @@ -426,6 +425,8 @@ class AccountsController(TransactionBase): (sle.voucher_type == self.doctype) & (sle.voucher_no == self.name) ).run() + self._remove_advance_payment_ledger_entries() + def remove_serial_and_batch_bundle(self): bundles = frappe.get_all( "Serial and Batch Bundle", @@ -2233,54 +2234,30 @@ class AccountsController(TransactionBase): def calculate_total_advance_from_ledger(self): adv = frappe.qb.DocType("Advance Payment Ledger Entry") - advance = ( - frappe.qb.from_(adv) - .select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount")) - .where( - (adv.against_voucher_type == self.doctype) - & (adv.against_voucher_no == self.name) - & (adv.company == self.company) - ) + return ( + qb.from_(adv) + .select(Abs(Sum(adv.amount)).as_("amount"), adv.currency.as_("account_currency")) + .where(adv.company == self.company) + .where(adv.delinked == 0) + .where(adv.against_voucher_type == self.doctype) + .where(adv.against_voucher_no == self.name) .run(as_dict=True) ) - return advance def set_total_advance_paid(self): advance = self.calculate_total_advance_from_ledger() - advance_paid, order_total = None, None + advance_paid = 0 if advance: advance = advance[0] advance_paid = flt(advance.amount, self.precision("advance_paid")) - formatted_advance_paid = fmt_money( - advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency - ) - if advance.account_currency: frappe.db.set_value( self.doctype, self.name, "party_account_currency", advance.account_currency ) - if advance.account_currency == self.currency: - order_total = self.get("rounded_total") or self.grand_total - precision = "rounded_total" if self.get("rounded_total") else "grand_total" - else: - order_total = self.get("base_rounded_total") or self.base_grand_total - precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total" - - formatted_order_total = fmt_money( - order_total, precision=self.precision(precision), currency=advance.account_currency - ) - - if self.currency == self.company_currency and advance_paid > order_total: - frappe.throw( - _( - "Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})" - ).format(formatted_advance_paid, self.name, formatted_order_total) - ) - - self.db_set("advance_paid", advance_paid) + self.db_set("advance_paid", advance_paid) @property def company_abbr(self): @@ -2924,64 +2901,6 @@ class AccountsController(TransactionBase): def get_advance_payment_doctypes(self) -> list: return _get_advance_payment_doctypes() - def make_advance_payment_ledger_for_journal(self): - advance_payment_doctypes = self.get_advance_payment_doctypes() - advance_doctype_references = [ - x for x in self.accounts if x.reference_type in advance_payment_doctypes - ] - - for x in advance_doctype_references: - # Looking for payments - dr_or_cr = ( - "credit_in_account_currency" - if x.account_type == "Receivable" - else "debit_in_account_currency" - ) - - amount = x.get(dr_or_cr) - if amount > 0: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.doctype - doc.voucher_no = self.name - doc.against_voucher_type = x.reference_type - doc.against_voucher_no = x.reference_name - doc.amount = amount if self.docstatus == 1 else -1 * amount - doc.event = "Submit" if self.docstatus == 1 else "Cancel" - doc.currency = x.account_currency - doc.flags.ignore_permissions = 1 - doc.save() - - def make_advance_payment_ledger_for_payment(self): - advance_payment_doctypes = self.get_advance_payment_doctypes() - advance_doctype_references = [ - x for x in self.references if x.reference_doctype in advance_payment_doctypes - ] - currency = ( - self.paid_from_account_currency - if self.payment_type == "Receive" - else self.paid_to_account_currency - ) - for x in advance_doctype_references: - doc = frappe.new_doc("Advance Payment Ledger Entry") - doc.company = self.company - doc.voucher_type = self.doctype - doc.voucher_no = self.name - doc.against_voucher_type = x.reference_doctype - doc.against_voucher_no = x.reference_name - doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount - doc.currency = currency - doc.event = "Submit" if self.docstatus == 1 else "Cancel" - doc.flags.ignore_permissions = 1 - doc.save() - - def make_advance_payment_ledger_entries(self): - if self.docstatus != 0: - if self.doctype == "Journal Entry": - self.make_advance_payment_ledger_for_journal() - elif self.doctype == "Payment Entry": - self.make_advance_payment_ledger_for_payment() - def set_transaction_currency_and_rate_in_gl_map(self, gl_entries): for x in gl_entries: x["transaction_currency"] = self.currency diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7bd30f81bc3..d87f595a500 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -361,7 +361,7 @@ erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22 erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16 -erpnext.patches.v15_0.create_advance_payment_ledger_records +erpnext.patches.v15_0.create_advance_payment_ledger_records #2025-07-04 # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 @@ -412,6 +412,7 @@ erpnext.patches.v15_0.drop_sle_indexes erpnext.patches.v15_0.update_pick_list_fields erpnext.patches.v15_0.update_pegged_currencies erpnext.patches.v15_0.set_company_on_pos_inv_merge_log +erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes erpnext.patches.v15_0.rename_price_list_to_buying_price_list erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice diff --git a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py index 8d247885cab..e155eaea1d1 100644 --- a/erpnext/patches/v15_0/create_advance_payment_ledger_records.py +++ b/erpnext/patches/v15_0/create_advance_payment_ledger_records.py @@ -1,63 +1,28 @@ import frappe -from frappe import qb -from frappe.query_builder.custom import ConstantColumn +from frappe.model.naming import _generate_random_string +from frappe.query_builder import Case +from frappe.utils import now_datetime +from erpnext.accounts.utils import get_advance_payment_doctypes -def get_advance_doctypes() -> list: - return frappe.get_hooks("advance_payment_doctypes") +DOCTYPE = "Advance Payment Ledger Entry" - -def get_payments_with_so_po_reference() -> list: - advance_payment_entries = [] - advance_doctypes = get_advance_doctypes() - per = qb.DocType("Payment Entry Reference") - payments_with_reference = ( - qb.from_(per) - .select(per.parent) - .distinct() - .where(per.reference_doctype.isin(advance_doctypes) & per.docstatus.eq(1)) - .run() - ) - if payments_with_reference: - pe = qb.DocType("Payment Entry") - advance_payment_entries = ( - qb.from_(pe) - .select(ConstantColumn("Payment Entry").as_("doctype")) - .select(pe.name) - .where(pe.name.isin(payments_with_reference) & pe.docstatus.eq(1)) - .run(as_dict=True) - ) - - return advance_payment_entries - - -def get_journals_with_so_po_reference() -> list: - advance_journal_entries = [] - advance_doctypes = get_advance_doctypes() - jea = qb.DocType("Journal Entry Account") - journals_with_reference = ( - qb.from_(jea) - .select(jea.parent) - .distinct() - .where(jea.reference_type.isin(advance_doctypes) & jea.docstatus.eq(1)) - .run() - ) - if journals_with_reference: - je = qb.DocType("Journal Entry") - advance_journal_entries = ( - qb.from_(je) - .select(ConstantColumn("Journal Entry").as_("doctype")) - .select(je.name) - .where(je.name.isin(journals_with_reference) & je.docstatus.eq(1)) - .run(as_dict=True) - ) - - return advance_journal_entries - - -def make_advance_ledger_entries(vouchers: list): - for x in vouchers: - frappe.get_doc(x.doctype, x.name).make_advance_payment_ledger_entries() +FIELDS = [ + "name", + "creation", + "modified", + "owner", + "modified_by", + "company", + "voucher_type", + "voucher_no", + "against_voucher_type", + "against_voucher_no", + "amount", + "currency", + "event", + "delinked", +] def execute(): @@ -65,9 +30,102 @@ def execute(): Description: Create Advance Payment Ledger Entry for all Payments made against Sales / Purchase Orders """ - frappe.db.truncate("Advance Payment Ledger Entry") - payment_entries = get_payments_with_so_po_reference() - make_advance_ledger_entries(payment_entries) + frappe.db.truncate(DOCTYPE) + advance_doctpyes = get_advance_payment_doctypes() - journals = get_journals_with_so_po_reference() - make_advance_ledger_entries(journals) + if not advance_doctpyes: + return + + make_advance_ledger_entries_for_payment_entries(advance_doctpyes) + make_advance_ledger_entries_for_journal_entries(advance_doctpyes) + + +def make_advance_ledger_entries_for_payment_entries(advance_doctpyes) -> list: + pe = frappe.qb.DocType("Payment Entry") + per = frappe.qb.DocType("Payment Entry Reference") + + entries = ( + frappe.qb.from_(per) + .inner_join(pe) + .on(pe.name == per.parent) + .select( + pe.company, + per.parenttype.as_("voucher_type"), + per.parent.as_("voucher_no"), + per.reference_doctype.as_("against_voucher_type"), + per.reference_name.as_("against_voucher_no"), + per.allocated_amount.as_("amount"), + Case() + .when(pe.payment_type == "Receive", pe.paid_from_account_currency) + .else_(pe.paid_to_account_currency) + .as_("currency"), + ) + .where(per.reference_doctype.isin(advance_doctpyes) & per.docstatus.eq(1)) + .run(as_dict=True) + ) + + if not entries: + return + + bulk_insert_advance_entries(entries) + + +def make_advance_ledger_entries_for_journal_entries(advance_doctpyes) -> list: + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + + entries = ( + frappe.qb.from_(jea) + .inner_join(je) + .on(je.name == jea.parent) + .select( + je.company, + jea.parenttype.as_("voucher_type"), + jea.parent.as_("voucher_no"), + jea.reference_type.as_("against_voucher_type"), + jea.reference_name.as_("against_voucher_no"), + Case() + .when(jea.account_type == "Receivable", jea.credit_in_account_currency) + .else_(jea.debit_in_account_currency) + .as_("amount"), + jea.account_currency.as_("currency"), + ) + .where(jea.reference_type.isin(advance_doctpyes) & jea.docstatus.eq(1)) + .run(as_dict=True) + ) + + if not entries: + return + + bulk_insert_advance_entries(entries) + + +def bulk_insert_advance_entries(entries): + details = [] + user = frappe.session.user + now = now_datetime() + for entry in entries: + if entry.amount < 0: + continue + details.append(get_values(user, now, entry)) + + frappe.db.bulk_insert(DOCTYPE, fields=FIELDS, values=details) + + +def get_values(user, now, entry): + return ( + _generate_random_string(10), + now, + now, + user, + user, + entry.company, + entry.voucher_type, + entry.voucher_no, + entry.against_voucher_type, + entry.against_voucher_no, + entry.amount * -1, + entry.currency, + "Submit", + 0, + ) diff --git a/erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py b/erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py new file mode 100644 index 00000000000..f5a20803c6e --- /dev/null +++ b/erpnext/patches/v15_0/update_payment_ledger_entries_against_advance_doctypes.py @@ -0,0 +1,25 @@ +import frappe + +from erpnext.accounts.utils import get_advance_payment_doctypes + +DOCTYPE = "Payment Ledger Entry" + + +def execute(): + """ + Description: + Set against_voucher as entry for Payment Ledger Entry against advance vouchers. + """ + advance_payment_doctypes = get_advance_payment_doctypes() + + if not advance_payment_doctypes: + return + ple = frappe.qb.DocType(DOCTYPE) + + ( + frappe.qb.update(ple) + .set(ple.against_voucher_type, ple.voucher_type) + .set(ple.against_voucher_no, ple.voucher_no) + .where(ple.against_voucher_type.isin(advance_payment_doctypes)) + .run() + ) diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js index 072b541753d..4ccbf0106d7 100644 --- a/erpnext/public/js/utils/unreconcile.js +++ b/erpnext/public/js/utils/unreconcile.js @@ -42,8 +42,8 @@ erpnext.accounts.unreconcile_payment = { selection_map = selections.map(function (elem) { return { company: elem.company, - voucher_type: elem.voucher_type, - voucher_no: elem.voucher_no, + voucher_type: elem.reference_doctype, + voucher_no: elem.reference_name, against_voucher_type: frm.doc.doctype, against_voucher_no: frm.doc.name, }; @@ -54,8 +54,8 @@ erpnext.accounts.unreconcile_payment = { company: elem.company, voucher_type: frm.doc.doctype, voucher_no: frm.doc.name, - against_voucher_type: elem.voucher_type, - against_voucher_no: elem.voucher_no, + against_voucher_type: elem.reference_doctype, + against_voucher_no: elem.reference_name, }; }); } @@ -69,7 +69,7 @@ erpnext.accounts.unreconcile_payment = { let child_table_fields = [ { label: __("Voucher Type"), - fieldname: "voucher_type", + fieldname: "reference_doctype", fieldtype: "Link", options: "DocType", in_list_view: 1, @@ -77,9 +77,9 @@ erpnext.accounts.unreconcile_payment = { }, { label: __("Voucher No"), - fieldname: "voucher_no", + fieldname: "reference_name", fieldtype: "Dynamic Link", - options: "voucher_type", + options: "reference_doctype", in_list_view: 1, read_only: 1, }, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b89e14b5bcf..b6a979dbc61 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -444,6 +444,7 @@ class SalesOrder(SellingController): "GL Entry", "Stock Ledger Entry", "Payment Ledger Entry", + "Advance Payment Ledger Entry", "Unreconcile Payment", "Unreconcile Payment Entries", ) From d862a742b0806a64deae378749cec721e6ff4461 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 1 Aug 2025 17:00:38 +0530 Subject: [PATCH 17/58] fix: include Sales Invoice in SABB validation for packed items (cherry picked from commit 2ce297aff8a75003f9dfa57c2c595cff119f8b4c) --- .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index f6162ab558b..a453457f919 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -145,7 +145,7 @@ class SerialandBatchBundle(Document): ) elif not frappe.db.exists("Stock Ledger Entry", {"voucher_detail_no": self.voucher_detail_no}): - if self.voucher_type == "Delivery Note" and frappe.db.exists( + if self.voucher_type in ["Delivery Note", "Sales Invoice"] and frappe.db.exists( "Packed Item", self.voucher_detail_no ): return From a96fa557041dbc6be6d0b28b9da773db2a6b2ef7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 2 Aug 2025 12:16:45 +0530 Subject: [PATCH 18/58] perf: process_gl_map causing performance issues in the reposting --- erpnext/controllers/stock_controller.py | 4 +++- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- .../doctype/subcontracting_receipt/subcontracting_receipt.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 81cf7fb80d7..e375c2c362c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -638,7 +638,9 @@ class StockController(AccountsController): ).format(wh, self.company) ) - return process_gl_map(gl_list, precision=precision) + return process_gl_map( + gl_list, precision=precision, from_repost=frappe.flags.through_repost_item_valuation + ) def get_debit_field_precision(self): if not frappe.flags.debit_field_precision: diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d9597fe413a..9e7dea631a9 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -443,7 +443,7 @@ class PurchaseReceipt(BuyingController): self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher) update_regional_gl_entries(gl_entries, self) - return process_gl_map(gl_entries) + return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) def make_item_gl_entries(self, gl_entries, warehouse_account=None): from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 37c0340d2bf..a1260c5ffdd 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1612,7 +1612,7 @@ class StockEntry(StockController): ) ) - return process_gl_map(gl_entries) + return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) def update_work_order(self): def _validate_work_order(pro_doc): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 00c1c08cd5c..23a1ab9ebf4 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -548,7 +548,7 @@ class SubcontractingReceipt(SubcontractingController): gl_entries = [] self.make_item_gl_entries(gl_entries, warehouse_account) - return process_gl_map(gl_entries) + return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation) def make_item_gl_entries(self, gl_entries, warehouse_account=None): warehouse_with_no_account = [] From 0207b82f8277fd250ecef6d51d649a5f484f6381 Mon Sep 17 00:00:00 2001 From: Kavin <78342682+kavin0411@users.noreply.github.com> Date: Sat, 2 Aug 2025 14:48:06 +0530 Subject: [PATCH 19/58] fix: set use_serial_batch_fields when creating PR from PO (cherry picked from commit a384c96617768d80f68915396ed34c12263be25b) --- erpnext/buying/doctype/purchase_order/purchase_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 6419a4ebac2..246690c3e39 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -720,6 +720,7 @@ def close_or_unclose_purchase_orders(names, status): def set_missing_values(source, target): target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") + target.run_method("set_use_serial_batch_fields") @frappe.whitelist() From 4ce4d345e7bbf0220add415812a84d54e30068a0 Mon Sep 17 00:00:00 2001 From: Dev Dusija Date: Tue, 22 Jul 2025 23:56:48 +0530 Subject: [PATCH 20/58] fix: account currency validation to exclude cancelled entries (cherry picked from commit c9c45fe89f4a9e90ad78bc34b6ca2024770e295b) --- erpnext/accounts/doctype/account/account.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 3e026e104ad..b9cde43653a 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -304,7 +304,9 @@ class Account(NestedSet): self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency") self.currency_explicitly_specified = False - gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency") + gl_currency = frappe.db.get_value( + "GL Entry", {"account": self.name, "is_cancelled": 0}, "account_currency" + ) if gl_currency and self.account_currency != gl_currency: if frappe.db.get_value("GL Entry", {"account": self.name}): From 56dca02cab854cb0569ace766ef078e564b40e83 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 19 Jun 2025 18:45:34 +0530 Subject: [PATCH 21/58] fix: include child doctypes in repostable accounting types (cherry picked from commit fbd8fd7d2245069340c164f2227c66d13d621513) --- .../accounting_dimension.py | 4 +--- .../repost_accounting_ledger.py | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 81937469f21..cdaaa6dd069 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -111,17 +111,15 @@ class AccountingDimension(Document): def make_dimension_in_accounting_doctypes(doc, doclist=None): if not doclist: doclist = get_doctypes_with_dimensions() - doc_count = len(get_accounting_dimensions()) count = 0 - repostable_doctypes = get_allowed_types_from_settings() + repostable_doctypes = get_allowed_types_from_settings(child_doc=True) for doctype in doclist: if (doc_count + 1) % 2 == 0: insert_after_field = "dimension_col_break" else: insert_after_field = "accounting_dimensions_section" - df = { "fieldname": doc.fieldname, "label": doc.label, diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index aec26b280c4..37863036492 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -5,6 +5,7 @@ import inspect import frappe from frappe import _, qb +from frappe.desk.form.linked_with import get_child_tables_of_doctypes from frappe.model.document import Document from frappe.utils.data import comma_and @@ -208,13 +209,29 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries() -def get_allowed_types_from_settings(): - return [ +def get_allowed_types_from_settings(child_doc: bool = False): + repost_docs = [ x.document_type for x in frappe.db.get_all( "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"] ) ] + result = repost_docs + + if repost_docs and child_doc: + result.extend(get_child_docs(repost_docs)) + + return result + + +def get_child_docs(doc: list) -> list: + child_doc = [] + doc = get_child_tables_of_doctypes(doc) + for child_list in doc.values(): + for child in child_list: + if child.get("child_table"): + child_doc.append(child["child_table"]) + return child_doc def validate_docs_for_deferred_accounting(sales_docs, purchase_docs): From e22f93f1fbf37f7e00d706ce2d909261ca3cdd69 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 19 Jun 2025 18:46:34 +0530 Subject: [PATCH 22/58] fix: enable allow_on_submit for accounting dimensions in allowed doctypes (cherry picked from commit 55e79c4dfd25ddee820fc516a86db8c7ac3f0677) --- .../repost_accounting_ledger_settings.py | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py index 14a070dc464..637234fea1e 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py @@ -1,9 +1,14 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs + class RepostAccountingLedgerSettings(Document): # begin: auto-generated types @@ -17,6 +22,24 @@ class RepostAccountingLedgerSettings(Document): from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes allowed_types: DF.Table[RepostAllowedTypes] - # end: auto-generated types - pass + # end: auto-generated types + def validate(self): + self.update_property_for_accounting_dimension() + + def update_property_for_accounting_dimension(self): + doctypes = [entry.document_type for entry in self.allowed_types if entry.allowed] + if not doctypes: + return + doctypes += get_child_docs(doctypes) + + set_allow_on_submit_for_dimension_fields(doctypes) + + +def set_allow_on_submit_for_dimension_fields(doctypes): + for dt in doctypes: + meta = frappe.get_meta(dt) + for dimension in get_accounting_dimensions(): + df = meta.get_field(dimension) + if df and not df.allow_on_submit: + frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1) From ad561772349ed999b6407ee329726feb2ce71301 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 19 Jun 2025 18:47:13 +0530 Subject: [PATCH 23/58] fix: include child doctypes in allow_on_submit patch for accounting dimensions (cherry picked from commit 1e37fd8991c68d7c36bbfa1c0b88c397ab200498) --- erpnext/patches.txt | 2 +- .../v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d87f595a500..3f678da4b2c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -356,8 +356,8 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation erpnext.patches.v14_0.update_zero_asset_quantity_field execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") erpnext.patches.v14_0.update_total_asset_cost_field +erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes #2025-06-19 erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool -erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22 erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16 diff --git a/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py index e75610d0a53..dddeae328c7 100644 --- a/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py +++ b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py @@ -9,6 +9,6 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger def execute(): - for dt in get_allowed_types_from_settings(): + for dt in get_allowed_types_from_settings(child_doc=True): for dimension in get_accounting_dimensions(): frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1) From 03550066a795d47ebecbf26823c6234e2435761d Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 2 Aug 2025 11:14:55 -0400 Subject: [PATCH 24/58] Revert "fix: set proper currency format" This reverts PR https://github.com/frappe/erpnext/pull/42458 This reverts commit 2533808f1e457cc87e3bc41cd9c0c97ad1f0c771. (cherry picked from commit 316470eee43e0b021bcbc5e9ed1d2750fb9b1af9) --- erpnext/accounts/report/payment_ledger/payment_ledger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py index 9dd5ae5c400..d4f0f0a107d 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.py +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py @@ -130,7 +130,6 @@ class PaymentLedger: ) def get_columns(self): - company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency") options = None self.columns.append( dict( @@ -195,7 +194,7 @@ class PaymentLedger: label=_("Amount"), fieldname="amount", fieldtype="Currency", - options=company_currency, + options="Company:company:default_currency", width="100", ) ) From 070190d07bd4a461b43cd1fc39babe4a530e154d Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 2 Aug 2025 11:38:47 -0400 Subject: [PATCH 25/58] fix: provide missing `company` in report records that require reference to `Company:company:default_currency` (cherry picked from commit 7f3905185c7e3d0f5b5b5ceccbdc4c51a9248697) --- erpnext/accounts/report/non_billed_report.py | 1 + erpnext/accounts/report/payment_ledger/payment_ledger.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py index c0ca604cc6d..6a6f58c74bb 100644 --- a/erpnext/accounts/report/non_billed_report.py +++ b/erpnext/accounts/report/non_billed_report.py @@ -46,6 +46,7 @@ def get_ordered_to_be_billed_data(args, filters=None): child_doctype.item_name, child_doctype.description, project_field, + doctype.company, ) .where( (doctype.docstatus == 1) diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py index d4f0f0a107d..03411cccd78 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.py +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py @@ -46,6 +46,7 @@ class PaymentLedger: against_voucher_no=ple.against_voucher_no, amount=ple.amount, currency=ple.account_currency, + company=ple.company, ) if self.filters.include_account_currency: From dc4b236951da22bcd182fc1f1330ad33d8ba012d Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 2 Aug 2025 12:23:12 -0400 Subject: [PATCH 26/58] fix: provide company for outstanding record. (cherry picked from commit 97959dbe75f54d20489ba08680021a1e3fd073f3) --- erpnext/accounts/report/payment_ledger/payment_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py index 03411cccd78..03080aeeed0 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.py +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py @@ -78,6 +78,7 @@ class PaymentLedger: against_voucher_no="Outstanding:", amount=total, currency=voucher_data[0].currency, + company=voucher_data[0].company, ) if self.filters.include_account_currency: From 5b0486ca2679c6c9dacecc463f55d909fffcf3d0 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 2 Aug 2025 12:29:40 -0400 Subject: [PATCH 27/58] fix: payment ledger voucher seperator row currencies (cherry picked from commit c03f1c25cf23828bd6f9112a5a480e9aeafbfba2) --- erpnext/accounts/report/payment_ledger/payment_ledger.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py index 03080aeeed0..7a2fdc4c925 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.py +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py @@ -87,7 +87,12 @@ class PaymentLedger: voucher_data.append(entry) # empty row - voucher_data.append(frappe._dict()) + voucher_data.append( + frappe._dict( + currency=voucher_data[0].currency, + company=voucher_data[0].company, + ) + ) self.data.extend(voucher_data) def build_conditions(self): From bab2e86c0136dc83431f7eadb56bb00e877477e1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 00:10:09 +0530 Subject: [PATCH 28/58] chore: remove wrongly configured 'pos*' assignment from CODEOWNERS (backport #48954) (#48955) chore: remove wrongly configured 'pos*' assignment from CODEOWNERS (#48954) (cherry picked from commit cc26d5da144e88adba3b36e18ee0c2efa86408f6) Co-authored-by: Diptanil Saha --- CODEOWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index a0c68b87789..50f5b524719 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,7 +8,6 @@ erpnext/assets/ @khushi8112 erpnext/regional @ruthra-kumar erpnext/selling @ruthra-kumar erpnext/support/ @ruthra-kumar -pos* @diptanilsaha erpnext/buying/ @rohitwaghchaure @mihir-kandoi erpnext/maintenance/ @rohitwaghchaure From dfa35363b934dee4e4eedbf237ee832dc1cf894b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 4 Aug 2025 19:23:38 +0530 Subject: [PATCH 29/58] chore: added now as default value for the posting time (cherry picked from commit b3cebd87c8c542dafdf8133a8ef10beb8e2f9fc6) # Conflicts: # erpnext/accounts/doctype/pos_invoice/pos_invoice.json # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json # erpnext/stock/doctype/delivery_note/delivery_note.json # erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 3 ++- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 3 ++- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 ++- erpnext/stock/doctype/delivery_note/delivery_note.json | 6 ++++-- erpnext/stock/doctype/delivery_note/delivery_note.py | 1 - .../stock/doctype/purchase_receipt/purchase_receipt.json | 5 +++-- erpnext/stock/doctype/stock_entry/stock_entry.json | 6 ++++-- .../doctype/stock_reconciliation/stock_reconciliation.json | 6 ++++-- 8 files changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index aaf5142362f..b96e29979a8 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -301,6 +301,7 @@ "search_index": 1 }, { + "default": "Now", "fieldname": "posting_time", "fieldtype": "Time", "label": "Posting Time", @@ -1573,7 +1574,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2025-07-17 16:51:40.886083", + "modified": "2025-08-04 22:22:31.471752", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index dfdcea5357a..83e704734ce 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -319,6 +319,7 @@ "search_index": 1 }, { + "default": "Now", "fieldname": "posting_time", "fieldtype": "Time", "label": "Posting Time", @@ -1650,7 +1651,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2025-04-09 16:49:22.175081", + "modified": "2025-08-04 19:19:11.380664", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 33df4882af9..07b97920ae2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -379,6 +379,7 @@ "search_index": 1 }, { + "default": "Now", "fieldname": "posting_time", "fieldtype": "Time", "hide_days": 1, @@ -2189,7 +2190,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2025-06-26 14:06:56.773552", + "modified": "2025-08-04 19:20:28.732039", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index e55b7f229dc..3a9cca1c418 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -258,6 +258,7 @@ "width": "100px" }, { + "default": "Now", "fieldname": "posting_time", "fieldtype": "Time", "label": "Posting Time", @@ -1395,7 +1396,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2024-11-26 12:44:28.258215", + "modified": "2025-08-04 19:20:47.724218", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", @@ -1486,6 +1487,7 @@ "write": 1 } ], + "row_format": "Dynamic", "search_fields": "status,customer,customer_name, territory,base_grand_total", "show_name_in_global_search": 1, "sort_field": "modified", @@ -1495,4 +1497,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 299f36a6ab7..cbd7a1d7f57 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -96,7 +96,6 @@ class DeliveryNote(SellingController): per_billed: DF.Percent per_installed: DF.Percent per_returned: DF.Percent - pick_list: DF.Link | None plc_conversion_rate: DF.Float po_date: DF.Date | None po_no: DF.SmallText | None diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index c3933780203..f275779ecc2 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -243,6 +243,7 @@ "width": "100px" }, { + "default": "Now", "fieldname": "posting_time", "fieldtype": "Time", "label": "Posting Time", @@ -1290,7 +1291,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2025-04-09 16:52:19.323878", + "modified": "2025-08-04 19:18:47.754957", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", @@ -1360,4 +1361,4 @@ "timeline_field": "supplier", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index f5fdfafe778..adec80dcab2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -218,6 +218,7 @@ "search_index": 1 }, { + "default": "Now", "fieldname": "posting_time", "fieldtype": "Time", "label": "Posting Time", @@ -697,7 +698,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-08-13 19:05:42.386955", + "modified": "2025-08-04 19:21:03.338958", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", @@ -763,6 +764,7 @@ "write": 1 } ], + "row_format": "Dynamic", "search_fields": "posting_date, from_warehouse, to_warehouse, purpose, remarks", "show_name_in_global_search": 1, "sort_field": "modified", @@ -770,4 +772,4 @@ "states": [], "title_field": "stock_entry_type", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index 9a854311100..4712b8aeb16 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -72,6 +72,7 @@ "reqd": 1 }, { + "default": "Now", "fieldname": "posting_time", "fieldtype": "Time", "in_list_view": 1, @@ -183,7 +184,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2022-05-11 09:10:26.327652", + "modified": "2025-08-04 19:21:20.179658", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", @@ -203,9 +204,10 @@ "write": 1 } ], + "row_format": "Dynamic", "search_fields": "posting_date", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From 8259a748f6495ba97233dd6b0c281d0f9a45312a Mon Sep 17 00:00:00 2001 From: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:23:56 +0530 Subject: [PATCH 30/58] Merge pull request #48575 from aerele/company-payment-gateway feat(payment gateway account): add company (cherry picked from commit 02380c3eabe6d0c79cfa555a5db97dae3ff0bb19) --- .../payment_gateway_account.js | 10 ++++++++ .../payment_gateway_account.json | 15 ++++++++++-- .../payment_gateway_account.py | 16 ++++++++----- .../payment_request/payment_request.js | 8 +++++++ .../payment_request/payment_request.py | 6 +++-- .../payment_request/test_payment_request.py | 24 ++++++++++++------- erpnext/accounts/utils.py | 1 + erpnext/patches.txt | 1 + .../add_company_payment_gateway_account.py | 7 ++++++ 9 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 erpnext/patches/v15_0/add_company_payment_gateway_account.py diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js index f15c2bdab21..ab9a50f8285 100644 --- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js +++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js @@ -8,4 +8,14 @@ frappe.ui.form.on("Payment Gateway Account", { frm.set_df_property("payment_gateway", "read_only", 1); } }, + + setup(frm) { + frm.set_query("payment_account", function () { + return { + filters: { + company: frm.doc.company, + }, + }; + }); + }, }); diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json index 12e6f5ef22d..fafd58c8a39 100644 --- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json +++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json @@ -6,6 +6,7 @@ "field_order": [ "payment_gateway", "payment_channel", + "company", "is_default", "column_break_4", "payment_account", @@ -70,11 +71,21 @@ "fieldtype": "Select", "label": "Payment Channel", "options": "\nEmail\nPhone" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-20 13:30:27.722852", + "modified": "2025-07-14 16:49:55.210352", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Gateway Account", @@ -95,4 +106,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py index f1bf363eca0..7c58949be8b 100644 --- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py +++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.py @@ -15,6 +15,7 @@ class PaymentGatewayAccount(Document): if TYPE_CHECKING: from frappe.types import DF + company: DF.Link currency: DF.ReadOnly | None is_default: DF.Check message: DF.SmallText | None @@ -24,7 +25,8 @@ class PaymentGatewayAccount(Document): # end: auto-generated types def autoname(self): - self.name = self.payment_gateway + " - " + self.currency + abbr = frappe.db.get_value("Company", self.company, "abbr") + self.name = self.payment_gateway + " - " + self.currency + " - " + abbr def validate(self): self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency") @@ -34,13 +36,15 @@ class PaymentGatewayAccount(Document): def update_default_payment_gateway(self): if self.is_default: - frappe.db.sql( - """update `tabPayment Gateway Account` set is_default = 0 - where is_default = 1 """ + frappe.db.set_value( + "Payment Gateway Account", + {"is_default": 1, "name": ["!=", self.name], "company": self.company}, + "is_default", + 0, ) def set_as_default_if_not_set(self): - if not frappe.db.get_value( - "Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name" + if not frappe.db.exists( + "Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name), "company": self.company} ): self.is_default = 1 diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index 50f96a4e2b6..5cca11ae2fd 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -9,6 +9,14 @@ frappe.ui.form.on("Payment Request", { query: "erpnext.setup.doctype.party_type.party_type.get_party_type", }; }); + + frm.set_query("payment_gateway_account", function () { + return { + filters: { + company: frm.doc.company, + }, + }; + }); }, }); diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 8bb120f1ab5..1a400878ba5 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -539,7 +539,9 @@ def make_payment_request(**args): if args.dt not in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST: frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt))) - ref_doc = frappe.get_doc(args.dt, args.dn) + ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn) + if not args.get("company"): + args.company = ref_doc.company gateway_account = get_gateway_details(args) or frappe._dict() grand_total = get_amount(ref_doc, gateway_account.get("payment_account")) @@ -782,7 +784,7 @@ def get_gateway_details(args): # nosemgrep """ Return gateway and payment account of default payment gateway """ - gateway_account = args.get("payment_gateway_account", {"is_default": 1}) + gateway_account = args.get("payment_gateway_account", {"is_default": 1, "company": args.company}) if gateway_account: return get_payment_gateway_account(gateway_account) diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index c3f73f0fc12..ae8ccffc639 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -28,12 +28,14 @@ payment_method = [ "payment_gateway": "_Test Gateway", "payment_account": "_Test Bank - _TC", "currency": "INR", + "company": "_Test Company", }, { "doctype": "Payment Gateway Account", "payment_gateway": "_Test Gateway", "payment_account": "_Test Bank USD - _TC", "currency": "USD", + "company": "_Test Company", }, ] @@ -46,7 +48,11 @@ class TestPaymentRequest(FrappeTestCase): for method in payment_method: if not frappe.db.get_value( "Payment Gateway Account", - {"payment_gateway": method["payment_gateway"], "currency": method["currency"]}, + { + "payment_gateway": method["payment_gateway"], + "currency": method["currency"], + "company": method["company"], + }, "name", ): frappe.get_doc(method).insert(ignore_permissions=True) @@ -60,7 +66,7 @@ class TestPaymentRequest(FrappeTestCase): dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com", - payment_gateway_account="_Test Gateway - INR", + payment_gateway_account="_Test Gateway - INR - _TC", ) self.assertEqual(pr.reference_doctype, "Sales Order") @@ -74,7 +80,7 @@ class TestPaymentRequest(FrappeTestCase): dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com", - payment_gateway_account="_Test Gateway - USD", + payment_gateway_account="_Test Gateway - USD - _TC", ) self.assertEqual(pr.reference_doctype, "Sales Invoice") @@ -95,7 +101,7 @@ class TestPaymentRequest(FrappeTestCase): party="_Test Supplier USD", recipient_id="user@example.com", mute_email=1, - payment_gateway_account="_Test Gateway - USD", + payment_gateway_account="_Test Gateway - USD - _TC", submit_doc=1, return_doc=1, ) @@ -119,7 +125,7 @@ class TestPaymentRequest(FrappeTestCase): dn=purchase_invoice.name, recipient_id="user@example.com", mute_email=1, - payment_gateway_account="_Test Gateway - USD", + payment_gateway_account="_Test Gateway - USD - _TC", return_doc=1, ) @@ -138,7 +144,7 @@ class TestPaymentRequest(FrappeTestCase): dn=purchase_invoice.name, recipient_id="user@example.com", mute_email=1, - payment_gateway_account="_Test Gateway - USD", + payment_gateway_account="_Test Gateway - USD - _TC", return_doc=1, ) @@ -162,7 +168,7 @@ class TestPaymentRequest(FrappeTestCase): dn=so_inr.name, recipient_id="saurabh@erpnext.com", mute_email=1, - payment_gateway_account="_Test Gateway - INR", + payment_gateway_account="_Test Gateway - INR - _TC", submit_doc=1, return_doc=1, ) @@ -184,7 +190,7 @@ class TestPaymentRequest(FrappeTestCase): dn=si_usd.name, recipient_id="saurabh@erpnext.com", mute_email=1, - payment_gateway_account="_Test Gateway - USD", + payment_gateway_account="_Test Gateway - USD - _TC", submit_doc=1, return_doc=1, ) @@ -228,7 +234,7 @@ class TestPaymentRequest(FrappeTestCase): dn=si_usd.name, recipient_id="saurabh@erpnext.com", mute_email=1, - payment_gateway_account="_Test Gateway - USD", + payment_gateway_account="_Test Gateway - USD - _TC", submit_doc=1, return_doc=1, ) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 022dfb889a9..d234a79422f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1339,6 +1339,7 @@ def create_payment_gateway_account(gateway, payment_channel="Email"): "payment_account": bank_account.name, "currency": bank_account.account_currency, "payment_channel": payment_channel, + "company": company, } ).insert(ignore_permissions=True, ignore_if_duplicate=True) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3f678da4b2c..f67007ecdae 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -418,3 +418,4 @@ erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-07-31 execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction", 1) +erpnext.patches.v15_0.add_company_payment_gateway_account diff --git a/erpnext/patches/v15_0/add_company_payment_gateway_account.py b/erpnext/patches/v15_0/add_company_payment_gateway_account.py new file mode 100644 index 00000000000..0c2f0c6f2d3 --- /dev/null +++ b/erpnext/patches/v15_0/add_company_payment_gateway_account.py @@ -0,0 +1,7 @@ +import frappe + + +def execute(): + for gateway_account in frappe.get_list("Payment Gateway Account", fields=["name", "payment_account"]): + company = frappe.db.get_value("Account", gateway_account.payment_account, "company") + frappe.db.set_value("Payment Gateway Account", gateway_account.name, "company", company) From 3603cdf45753118aa3ef2c87c87ad836f2ab882e Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Wed, 30 Jul 2025 16:38:33 +0530 Subject: [PATCH 31/58] fix: add doctype fieldname in condition (cherry picked from commit e2d63e4c32cba418e500a327110258659c8f6388) --- erpnext/accounts/utils.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index d234a79422f..36fe32b0381 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt +from collections import defaultdict from json import loads from typing import TYPE_CHECKING, Optional @@ -2420,25 +2421,37 @@ def sync_auto_reconcile_config(auto_reconciliation_job_trigger: int = 15): ).save() +def get_link_fields_grouped_by_option(doctype): + meta = frappe.get_meta(doctype) + link_fields_map = defaultdict(list) + + for df in meta.fields: + if df.fieldtype == "Link" and df.options and not df.ignore_user_permissions: + link_fields_map[df.options].append(df.fieldname) + + return link_fields_map + + def build_qb_match_conditions(doctype, user=None) -> list: match_filters = build_match_conditions(doctype, user, False) + link_fields_map = get_link_fields_grouped_by_option(doctype) criterion = [] apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions") if match_filters: - from frappe import qb - _dt = qb.DocType(doctype) for filter in match_filters: - for d, names in filter.items(): - fieldname = d.lower().replace(" ", "_") - field = _dt[fieldname] + for link_option, allowed_values in filter.items(): + fieldnames = link_fields_map.get(link_option, []) - cond = field.isin(names) - if not apply_strict_user_permissions: - cond = (Coalesce(field, "") == "") | field.isin(names) + for fieldname in fieldnames: + field = _dt[fieldname] + cond = field.isin(allowed_values) - criterion.append(cond) + if not apply_strict_user_permissions: + cond = (Coalesce(field, "") == "") | cond + + criterion.append(cond) return criterion From 5a984de69765f7bd4d0c9eda88adaa719eb2137c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 06:37:20 +0000 Subject: [PATCH 32/58] fix: failing subcontracting patch (backport #48940) (#48961) * fix: failing subcontracting patch (cherry picked from commit 14b47e81ce8dadb4a053dde1d0361615e4164d97) # Conflicts: # erpnext/patches.txt * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi --- erpnext/patches.txt | 2 +- ..._entries_with_no_account_subcontracting.py | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f67007ecdae..197596daa8d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -416,6 +416,6 @@ erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes erpnext.patches.v15_0.rename_price_list_to_buying_price_list erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice -erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-07-31 +erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-08-04 execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction", 1) erpnext.patches.v15_0.add_company_payment_gateway_account diff --git a/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py b/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py index e7394f1071c..0bb47973194 100644 --- a/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py +++ b/erpnext/patches/v15_0/repost_gl_entries_with_no_account_subcontracting.py @@ -2,24 +2,31 @@ import frappe def execute(): + def cancel_incorrect_gl_entries(gl_entries): + table = frappe.qb.DocType("GL Entry") + frappe.qb.update(table).set(table.is_cancelled, 1).where(table.name.isin(gl_entries)).run() + + def recreate_gl_entries(voucher_nos): + for doc in voucher_nos: + doc = frappe.get_doc("Subcontracting Receipt", doc) + for item in doc.supplied_items: + account, cost_center = frappe.db.get_values( + "Subcontracting Receipt Item", item.reference_name, ["expense_account", "cost_center"] + )[0] + + if not item.expense_account: + item.db_set("expense_account", account) + if not item.cost_center: + item.db_set("cost_center", cost_center) + + doc.make_gl_entries() + docs = frappe.get_all( "GL Entry", + fields=["name", "voucher_no"], filters={"voucher_type": "Subcontracting Receipt", "account": ["is", "not set"], "is_cancelled": 0}, - pluck="voucher_no", ) - for doc in docs: - doc = frappe.get_doc("Subcontracting Receipt", doc) - for item in doc.supplied_items: - account, cost_center = frappe.db.get_values( - "Subcontracting Receipt Item", item.reference_name, ["expense_account", "cost_center"] - )[0] - if not item.expense_account: - item.db_set("expense_account", account) - if not item.cost_center: - item.db_set("cost_center", cost_center) - - doc.docstatus = 2 - doc.make_gl_entries_on_cancel() - doc.docstatus = 1 - doc.make_gl_entries() + if docs: + cancel_incorrect_gl_entries([d.name for d in docs]) + recreate_gl_entries([d.voucher_no for d in docs]) From d9c1ef0926f29d381f74760ed8030636666d9645 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 5 Aug 2025 12:58:53 +0530 Subject: [PATCH 33/58] fix: remove api call to set default payments (cherry picked from commit 871b8473faa66f45ffbc2892fc390a9d6a7c8680) # Conflicts: # erpnext/public/js/controllers/taxes_and_totals.js --- erpnext/public/js/controllers/taxes_and_totals.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index c50e6e53928..a985e5faef5 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -928,15 +928,18 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.refresh_fields(); } - async set_default_payment(total_amount_to_pay, update_paid_amount) { + set_default_payment(total_amount_to_pay, update_paid_amount) { var me = this; var payment_status = true; if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) { +<<<<<<< HEAD let r = await frappe.db.get_value("POS Profile", this.frm.doc.pos_profile, "disable_grand_total_to_default_mop"); if (r.message.disable_grand_total_to_default_mop) { return; } +======= +>>>>>>> 871b8473fa (fix: remove api call to set default payments) $.each(this.frm.doc['payments'] || [], function(index, data) { if(data.default && payment_status && total_amount_to_pay > 0) { From e8ba2b1576a6f46cc34d8fbfc87bd83aa8117be5 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Tue, 5 Aug 2025 13:24:49 +0530 Subject: [PATCH 34/58] chore: resolve conflict --- erpnext/public/js/controllers/taxes_and_totals.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index a985e5faef5..be503a45d56 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -932,14 +932,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var me = this; var payment_status = true; if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) { -<<<<<<< HEAD - let r = await frappe.db.get_value("POS Profile", this.frm.doc.pos_profile, "disable_grand_total_to_default_mop"); - - if (r.message.disable_grand_total_to_default_mop) { - return; - } -======= ->>>>>>> 871b8473fa (fix: remove api call to set default payments) $.each(this.frm.doc['payments'] || [], function(index, data) { if(data.default && payment_status && total_amount_to_pay > 0) { From dc7ac3550eea936f82c41fb38081966142360890 Mon Sep 17 00:00:00 2001 From: Corentin Forler Date: Fri, 18 Jul 2025 16:17:42 +0200 Subject: [PATCH 35/58] fix: Use correct Attachments folder in code list import (cherry picked from commit bc2cb1737aa2b3a26a718e1cdc06f42278d102ae) --- erpnext/edi/doctype/code_list/code_list_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/edi/doctype/code_list/code_list_import.py b/erpnext/edi/doctype/code_list/code_list_import.py index 50df3be471e..ecabb256026 100644 --- a/erpnext/edi/doctype/code_list/code_list_import.py +++ b/erpnext/edi/doctype/code_list/code_list_import.py @@ -60,7 +60,7 @@ def import_genericode(): "doctype": "File", "attached_to_doctype": "Code List", "attached_to_name": code_list.name, - "folder": "Home/Attachments", + "folder": frappe.db.get_value("File", {"is_attachments_folder": 1}), "file_name": frappe.local.uploaded_filename, "file_url": frappe.local.uploaded_file_url, "is_private": 1, From 0d02d7086ce05063cc052710dc0711075445edce Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 30 Jul 2025 16:50:49 +0530 Subject: [PATCH 36/58] chore: correct description for `consider_party_ledger_amount` in Tax Withholding Category (cherry picked from commit f619bca2d6c36365291c1dd7366a8646f3480ca3) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json --- .../tax_withholding_category/tax_withholding_category.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index 153906ffe97..286b915b05e 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -75,7 +75,7 @@ }, { "default": "0", - "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach", + "description": "Only payment entries with apply tax withholding unchecked will be considered for checking cumulative threshold breach", "fieldname": "consider_party_ledger_amount", "fieldtype": "Check", "label": "Consider Entire Party Ledger Amount", @@ -102,10 +102,11 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-07-27 21:47:34.396071", + "modified": "2025-07-30 07:13:51.785735", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Category", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { @@ -148,4 +149,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 0fdd94441841640b2e3cb790adb99fac43b7bdfe Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Mon, 21 Jul 2025 15:44:03 +0530 Subject: [PATCH 37/58] refactor: process subscriptions in batch wise (cherry picked from commit 283d69c0bdcc0e7397f59871d92b65a227d34251) --- .../process_subscription.py | 20 +++++++++++++++++-- .../doctype/subscription/subscription.py | 10 +++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/process_subscription/process_subscription.py b/erpnext/accounts/doctype/process_subscription/process_subscription.py index b4d18a3c0a8..0cc3fae58a8 100644 --- a/erpnext/accounts/doctype/process_subscription/process_subscription.py +++ b/erpnext/accounts/doctype/process_subscription/process_subscription.py @@ -3,7 +3,7 @@ import frappe from frappe.model.document import Document -from frappe.utils import getdate +from frappe.utils import create_batch, getdate from erpnext.accounts.doctype.subscription.subscription import DateTimeLikeObject, process_all @@ -23,7 +23,23 @@ class ProcessSubscription(Document): # end: auto-generated types def on_submit(self): - process_all(subscription=self.subscription, posting_date=self.posting_date) + self.process_all_subscription() + + def process_all_subscription(self): + filters = {"status": ("!=", "Cancelled")} + + if self.subscription: + filters["name"] = self.subscription + + subscriptions = frappe.get_all("Subscription", filters, pluck="name") + + for subscription in create_batch(subscriptions, 500): + frappe.enqueue( + method="erpnext.accounts.doctype.subscription.subscription.process_all", + queue="long", + subscription=subscription, + posting_date=self.posting_date, + ) def create_subscription_process( diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 7323c819d7e..8c4d21ac3fb 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -756,18 +756,14 @@ def get_prorata_factor( return diff / plan_days -def process_all(subscription: str | None = None, posting_date: DateTimeLikeObject | None = None) -> None: +def process_all(subscription: list, posting_date: DateTimeLikeObject | None = None) -> None: """ Task to updates the status of all `Subscription` apart from those that are cancelled """ - filters = {"status": ("!=", "Cancelled")} - if subscription: - filters["name"] = subscription - - for subscription in frappe.get_all("Subscription", filters, pluck="name"): + for subscription_name in subscription: try: - subscription = frappe.get_doc("Subscription", subscription) + subscription = frappe.get_doc("Subscription", subscription_name) subscription.process(posting_date) frappe.db.commit() except frappe.ValidationError: From 19a8ddef866e201f02068b453f35919e16c5b334 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 24 Jul 2025 17:04:03 +0530 Subject: [PATCH 38/58] perf: process auto bank reconciliation in batches (cherry picked from commit 657de2cc7ea765bde23d6241c64e83fd10bca6ea) --- .../bank_reconciliation_tool.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 7f97c3677bd..3ce867dc96e 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -9,7 +9,7 @@ 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 frappe.utils import cint, create_batch, flt from erpnext import get_default_cost_center from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount @@ -377,16 +377,17 @@ def auto_reconcile_vouchers( 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, - ) + for bank_transaction_batch in create_batch(bank_transactions, 1000): + frappe.enqueue( + method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile", + queue="long", + bank_transactions=bank_transaction_batch, + 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( From eec327c02b7d6929653585e8ff5146b8159a1416 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 5 Aug 2025 14:39:26 +0530 Subject: [PATCH 39/58] fix: use maintenance_status filter for indicators (cherry picked from commit 1b674a1051d754bb63670ccbcb18c88c173be9db) --- .../asset_maintenance_log/asset_maintenance_log_list.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js index 13f2444742d..20e98631de6 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js @@ -3,13 +3,13 @@ frappe.listview_settings["Asset Maintenance Log"] = { has_indicator_for_draft: 1, get_indicator: function (doc) { if (doc.maintenance_status == "Planned") { - return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status]; + return [__(doc.maintenance_status), "orange", "maintenance_status,=," + doc.maintenance_status]; } else if (doc.maintenance_status == "Completed") { - return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status]; + return [__(doc.maintenance_status), "green", "maintenance_status,=," + doc.maintenance_status]; } else if (doc.maintenance_status == "Cancelled") { - return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status]; + return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status]; } else if (doc.maintenance_status == "Overdue") { - return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status]; + return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status]; } }, }; From 9d8cb2f57c91b653e2d09be7f5701b061b36ca28 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 5 Aug 2025 14:00:27 +0530 Subject: [PATCH 40/58] fix: submit depreciation schedule only for submitted asset (cherry picked from commit a4628c20241494cc0df8d6058b0d0ab9464408d6) # Conflicts: # erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py --- .../asset_depreciation_schedule.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index f9c4913b1a2..bdbf7322757 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -98,9 +98,30 @@ class AssetDepreciationSchedule(Document): ) def on_submit(self): + self.validate_asset() self.db_set("status", "Active") +<<<<<<< HEAD def before_cancel(self): +======= + def validate_asset(self): + asset = frappe.get_doc("Asset", self.asset) + if not asset.calculate_depreciation: + frappe.throw( + _("Asset {0} is not set to calculate depreciation.").format( + get_link_to_form("Asset", self.asset) + ) + ) + if asset.docstatus != 1: + frappe.throw( + _("Asset {0} is not submitted. Please submit the asset before proceeding.").format( + get_link_to_form("Asset", self.asset) + ) + ) + + def on_cancel(self): + self.db_set("status", "Cancelled") +>>>>>>> a4628c2024 (fix: submit depreciation schedule only for submitted asset) if not self.flags.should_not_cancel_depreciation_entries: self.cancel_depreciation_entries() From 37eaa07192f58314fef022442845af05834be499 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 5 Aug 2025 14:16:05 +0530 Subject: [PATCH 41/58] fix: validate if journal entry linked to schedule is in draft (cherry picked from commit d5edca202227d83b6694fcbca19d04f82189c184) --- .../asset_depreciation_schedule.py | 6 ++ .../doctype/item_price/test_records.json | 56 ------------------- 2 files changed, 6 insertions(+), 56 deletions(-) delete mode 100644 erpnext/stock/doctype/item_price/test_records.json diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index bdbf7322757..c6750ee99c4 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -128,6 +128,12 @@ class AssetDepreciationSchedule(Document): def cancel_depreciation_entries(self): for d in self.get("depreciation_schedule"): if d.journal_entry: + if d.journal_entry == "Draft": + frappe.throw( + _( + "Cannot cancel Asset Depreciation Schedule {0} as it has a draft journal entry {1}." + ).format(self.name, d.journal_entry) + ) frappe.get_doc("Journal Entry", d.journal_entry).cancel() def on_cancel(self): diff --git a/erpnext/stock/doctype/item_price/test_records.json b/erpnext/stock/doctype/item_price/test_records.json deleted file mode 100644 index afe5ad65b75..00000000000 --- a/erpnext/stock/doctype/item_price/test_records.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "doctype": "Item Price", - "item_code": "_Test Item", - "price_list": "_Test Price List", - "price_list_rate": 100, - "valid_from": "2017-04-18", - "valid_upto": "2017-04-26" - }, - { - "doctype": "Item Price", - "item_code": "_Test Item", - "price_list": "_Test Price List Rest of the World", - "price_list_rate": 10 - }, - { - "doctype": "Item Price", - "item_code": "_Test Item 2", - "price_list": "_Test Price List Rest of the World", - "price_list_rate": 20, - "valid_from": "2017-04-18", - "valid_upto": "2017-04-26", - "customer": "_Test Customer", - "uom": "_Test UOM" - }, - { - "doctype": "Item Price", - "item_code": "_Test Item Home Desktop 100", - "price_list": "_Test Price List", - "price_list_rate": 1000, - "valid_from": "2017-04-10", - "valid_upto": "2017-04-17" - }, - { - "doctype": "Item Price", - "item_code": "_Test Item Home Desktop Manufactured", - "price_list": "_Test Price List", - "price_list_rate": 1000, - "valid_from": "2017-04-10", - "valid_upto": "2017-04-17" - }, - { - "doctype": "Item Price", - "item_code": "_Test Item", - "price_list": "_Test Buying Price List", - "price_list_rate": 100, - "supplier": "_Test Supplier" - }, - { - "doctype": "Item Price", - "item_code": "_Test Item", - "price_list": "_Test Selling Price List", - "price_list_rate": 200, - "customer": "_Test Customer" - } -] From 137d2d4044664d46c9455235d55e7a120f3079dd Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 5 Aug 2025 14:19:53 +0530 Subject: [PATCH 42/58] chore: add mistakenly removed test records (cherry picked from commit f5a71c6b88b42019e694018348f05e6fa8fbe257) --- .../doctype/item_price/test_records.json | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 erpnext/stock/doctype/item_price/test_records.json diff --git a/erpnext/stock/doctype/item_price/test_records.json b/erpnext/stock/doctype/item_price/test_records.json new file mode 100644 index 00000000000..afe5ad65b75 --- /dev/null +++ b/erpnext/stock/doctype/item_price/test_records.json @@ -0,0 +1,56 @@ +[ + { + "doctype": "Item Price", + "item_code": "_Test Item", + "price_list": "_Test Price List", + "price_list_rate": 100, + "valid_from": "2017-04-18", + "valid_upto": "2017-04-26" + }, + { + "doctype": "Item Price", + "item_code": "_Test Item", + "price_list": "_Test Price List Rest of the World", + "price_list_rate": 10 + }, + { + "doctype": "Item Price", + "item_code": "_Test Item 2", + "price_list": "_Test Price List Rest of the World", + "price_list_rate": 20, + "valid_from": "2017-04-18", + "valid_upto": "2017-04-26", + "customer": "_Test Customer", + "uom": "_Test UOM" + }, + { + "doctype": "Item Price", + "item_code": "_Test Item Home Desktop 100", + "price_list": "_Test Price List", + "price_list_rate": 1000, + "valid_from": "2017-04-10", + "valid_upto": "2017-04-17" + }, + { + "doctype": "Item Price", + "item_code": "_Test Item Home Desktop Manufactured", + "price_list": "_Test Price List", + "price_list_rate": 1000, + "valid_from": "2017-04-10", + "valid_upto": "2017-04-17" + }, + { + "doctype": "Item Price", + "item_code": "_Test Item", + "price_list": "_Test Buying Price List", + "price_list_rate": 100, + "supplier": "_Test Supplier" + }, + { + "doctype": "Item Price", + "item_code": "_Test Item", + "price_list": "_Test Selling Price List", + "price_list_rate": 200, + "customer": "_Test Customer" + } +] From 497247d89acfc5e52bbd1782d18a31834267ea75 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 5 Aug 2025 14:34:39 +0530 Subject: [PATCH 43/58] chore: fetch docstatus to validate (cherry picked from commit d6fb99916e1d33ccd63675401211acc15a2bcb0a) --- .../asset_depreciation_schedule/asset_depreciation_schedule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index c6750ee99c4..c4180fa68c0 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -128,7 +128,8 @@ class AssetDepreciationSchedule(Document): def cancel_depreciation_entries(self): for d in self.get("depreciation_schedule"): if d.journal_entry: - if d.journal_entry == "Draft": + je_status = frappe.db.get_value("Journal Entry", d.journal_entry, "docstatus") + if je_status == 0: frappe.throw( _( "Cannot cancel Asset Depreciation Schedule {0} as it has a draft journal entry {1}." From 4f14651a4220d1ae9fde0e2d5364603ecc607369 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:06:57 +0530 Subject: [PATCH 44/58] chore: resolved conflicts --- .../asset_depreciation_schedule.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index c4180fa68c0..53a9d1dba09 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -101,9 +101,6 @@ class AssetDepreciationSchedule(Document): self.validate_asset() self.db_set("status", "Active") -<<<<<<< HEAD - def before_cancel(self): -======= def validate_asset(self): asset = frappe.get_doc("Asset", self.asset) if not asset.calculate_depreciation: @@ -121,7 +118,6 @@ class AssetDepreciationSchedule(Document): def on_cancel(self): self.db_set("status", "Cancelled") ->>>>>>> a4628c2024 (fix: submit depreciation schedule only for submitted asset) if not self.flags.should_not_cancel_depreciation_entries: self.cancel_depreciation_entries() From 8d1b599d5fa09a9e6e05179ae5acfe9a877284a7 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 5 Aug 2025 15:09:55 +0530 Subject: [PATCH 45/58] refactor(pos): disable grand total to default mode of payment --- .../page/point_of_sale/pos_controller.js | 1 + .../selling/page/point_of_sale/pos_payment.js | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 6905292420b..5e3218a67a2 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -421,6 +421,7 @@ erpnext.PointOfSale.Controller = class { init_payments() { this.payment = new erpnext.PointOfSale.Payment({ wrapper: this.$components_wrapper, + settings: this.settings, events: { get_frm: () => this.frm || {}, diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index b47c25e20bb..59f293a96b0 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -1,8 +1,9 @@ /* eslint-disable no-unused-vars */ erpnext.PointOfSale.Payment = class { - constructor({ events, wrapper }) { + constructor({ events, settings, wrapper }) { this.wrapper = wrapper; this.events = events; + this.disable_grand_total_to_default_mop = settings.disable_grand_total_to_default_mop; this.init_component(); } @@ -341,10 +342,11 @@ erpnext.PointOfSale.Payment = class { } render_payment_section() { + this.remove_grand_total_from_default_mop(); this.render_payment_mode_dom(); this.make_invoice_fields_control(); this.update_totals_section(); - this.unset_grand_total_to_default_mop(); + this.focus_on_default_mop(); } after_render() { @@ -446,7 +448,19 @@ erpnext.PointOfSale.Payment = class { this.attach_cash_shortcuts(doc); } + remove_grand_total_from_default_mop() { + if (!this.disable_grand_total_to_default_mop) return; + const doc = this.events.get_frm().doc; + const payments = doc.payments; + payments.forEach((p) => { + if (p.default) { + frappe.model.set_value(p.doctype, p.name, "amount", 0); + } + }); + } + focus_on_default_mop() { + if (this.disable_grand_total_to_default_mop) return; const doc = this.events.get_frm().doc; const payments = doc.payments; payments.forEach((p) => { @@ -629,19 +643,6 @@ erpnext.PointOfSale.Payment = class { .toLowerCase(); } - async unset_grand_total_to_default_mop() { - const doc = this.events.get_frm().doc; - let r = await frappe.db.get_value( - "POS Profile", - doc.pos_profile, - "disable_grand_total_to_default_mop" - ); - - if (!r.message.disable_grand_total_to_default_mop) { - this.focus_on_default_mop(); - } - } - validate_reqd_invoice_fields() { const doc = this.events.get_frm().doc; let validation_flag = true; From cee9f200ad254fa88c03c808926748fc5e8f7f2f Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 1 Aug 2025 17:01:11 +0530 Subject: [PATCH 46/58] fix(tax withholding details): avoid voucher duplication (cherry picked from commit 88370162437652fd16ffde3d77eaca2025e06c3c) --- .../tax_withholding_details/tax_withholding_details.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 4507fbdcb60..81dba55d609 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -45,6 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ gle_map = get_gle_map(tds_docs) out = [] + entries = {} for name, details in gle_map.items(): for entry in details: tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0 @@ -119,8 +120,13 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ "supplier_invoice_date": bill_date, } ) - out.append(row) + key = entry.voucher_no + if key in entries: + entries[key]["tax_amount"] += tax_amount + else: + entries[key] = row + out = list(entries.values()) out.sort(key=lambda x: (x["section_code"], x["transaction_date"])) return out From b964b122edab1f70ec99d5a17a807b534c0187a5 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Mon, 28 Jul 2025 13:54:02 +0530 Subject: [PATCH 47/58] feat: add show_amount_in_company_currency in gl report (cherry picked from commit 468e5e9b2e173ac0df49d7febe8fc0d3968217be) --- .../accounts/report/general_ledger/general_ledger.js | 5 +++++ .../accounts/report/general_ledger/general_ledger.py | 12 ++++++++++++ erpnext/accounts/report/utils.py | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index f27441bc3e2..cebdad3744f 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -197,6 +197,11 @@ frappe.query_reports["General Ledger"] = { label: __("Show Net Values in Party Account"), fieldtype: "Check", }, + { + fieldname: "show_amount_in_company_currency", + label: __("Show Credit / Debit in Company Currency"), + fieldtype: "Check", + }, { fieldname: "add_values_in_transaction_currency", label: __("Add Columns in Transaction Currency"), diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 91b244f94fa..2bec888729a 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -605,6 +605,18 @@ def get_columns(filters): company = filters.get("company") or get_default_company() filters["presentation_currency"] = currency = get_company_currency(company) + company_currency = get_company_currency(filters.get("company") or get_default_company()) + + if ( + filters.get("show_amount_in_company_currency") + and filters["presentation_currency"] != company_currency + ): + frappe.throw( + _( + f'Presentation Currency cannot be {frappe.bold(filters["presentation_currency"])} , When {frappe.bold("Show Credit / Debit in Company Currency")} is enabled.' + ) + ) + columns = [ { "label": _("GL Entry"), diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 136a0acbbb0..523fa0e0ee3 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -118,7 +118,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, filters=None): len(account_currencies) == 1 and account_currency == presentation_currency and not exchange_gain_or_loss - ): + ) and not filters.get("show_amount_in_company_currency"): entry["debit"] = debit_in_account_currency entry["credit"] = credit_in_account_currency else: From 35f826c4997ccdca0891605b17b518dca00937ff Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:54:11 +0530 Subject: [PATCH 48/58] chore: remove duplicate code --- .../asset_depreciation_schedule/asset_depreciation_schedule.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 53a9d1dba09..e1413096af3 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -133,9 +133,6 @@ class AssetDepreciationSchedule(Document): ) frappe.get_doc("Journal Entry", d.journal_entry).cancel() - def on_cancel(self): - self.db_set("status", "Cancelled") - def update_shift_depr_schedule(self): if not self.shift_based or self.docstatus != 0: return From 4a5e0b181fee4aea5feb1224184ed6b042d145e8 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Sat, 2 Aug 2025 13:29:33 -0600 Subject: [PATCH 49/58] chore: add translation function on remarks in make_journal_entry in asset.py (cherry picked from commit 119904e44f00faa3559ea03ecad601cf99ba8643) --- erpnext/assets/doctype/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 14d0d53ac69..79e266d14c2 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -1097,7 +1097,7 @@ def make_journal_entry(asset_name): je.voucher_type = "Depreciation Entry" je.naming_series = depreciation_series je.company = asset.company - je.remark = f"Depreciation Entry against asset {asset_name}" + je.remark = _("Depreciation Entry against asset {0}").format(asset_name) je.append( "accounts", From 2c9ee7ae148eedc5b00621ca965ee8bd9a4cb359 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Sat, 2 Aug 2025 13:43:18 -0600 Subject: [PATCH 50/58] chore: add translation function on remark in setup_journal_entry_metadata in depreciation.py (cherry picked from commit 803180d5de68bf53d717c2375e0edcd5b508c99b) # Conflicts: # erpnext/assets/doctype/asset/depreciation.py --- erpnext/assets/doctype/asset/depreciation.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index d888e549cea..f4059828795 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -276,8 +276,15 @@ def _make_journal_entry_for_depreciation( je.naming_series = depreciation_series je.posting_date = depr_schedule.schedule_date je.company = asset.company +<<<<<<< HEAD je.finance_book = asset_depr_schedule_doc.finance_book je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}" +======= + je.finance_book = depr_schedule_doc.finance_book + je.remark = _("Depreciation Entry against {0} worth {1}".format( + asset.name, depr_schedule.depreciation_amount + ) +>>>>>>> 803180d5de (chore: add translation function on remark in setup_journal_entry_metadata in depreciation.py) credit_entry = { "account": credit_account, From 78857cd798d06db95972e7ce8578354532c37945 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:50:33 +0530 Subject: [PATCH 51/58] fix: add missing parentheses (cherry picked from commit a60db40fd2d97863168fc8df815a60a36299f8cd) --- erpnext/assets/doctype/asset/depreciation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index f4059828795..8f26188e01a 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -281,7 +281,7 @@ def _make_journal_entry_for_depreciation( je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}" ======= je.finance_book = depr_schedule_doc.finance_book - je.remark = _("Depreciation Entry against {0} worth {1}".format( + je.remark = _("Depreciation Entry against {0} worth {1}").format( asset.name, depr_schedule.depreciation_amount ) >>>>>>> 803180d5de (chore: add translation function on remark in setup_journal_entry_metadata in depreciation.py) From 4a8465df9078b29bdbc72144e612e1e2edfb06fd Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:05:54 +0530 Subject: [PATCH 52/58] chore: resolved conflicts --- erpnext/assets/doctype/asset/depreciation.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 8f26188e01a..a0681cc2c56 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -276,15 +276,10 @@ def _make_journal_entry_for_depreciation( je.naming_series = depreciation_series je.posting_date = depr_schedule.schedule_date je.company = asset.company -<<<<<<< HEAD je.finance_book = asset_depr_schedule_doc.finance_book - je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}" -======= - je.finance_book = depr_schedule_doc.finance_book je.remark = _("Depreciation Entry against {0} worth {1}").format( asset.name, depr_schedule.depreciation_amount ) ->>>>>>> 803180d5de (chore: add translation function on remark in setup_journal_entry_metadata in depreciation.py) credit_entry = { "account": credit_account, From 83e9842dd3932e08d3084364aedd9a64376bda68 Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Mon, 4 Aug 2025 13:43:42 +0000 Subject: [PATCH 53/58] feat: add 'Manufacture' section to project dashboard and show linked Work Orders (cherry picked from commit 2729d7521d53851bdb868765d5be71213019592d) --- erpnext/projects/doctype/project/project_dashboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/projects/doctype/project/project_dashboard.py b/erpnext/projects/doctype/project/project_dashboard.py index 5d17efbabc5..8acd02108cc 100644 --- a/erpnext/projects/doctype/project/project_dashboard.py +++ b/erpnext/projects/doctype/project/project_dashboard.py @@ -14,5 +14,6 @@ def get_data(): {"label": _("Material"), "items": ["Material Request", "BOM", "Stock Entry"]}, {"label": _("Sales"), "items": ["Sales Order", "Delivery Note", "Sales Invoice"]}, {"label": _("Purchase"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]}, + {"label": _("Manufacture"), "items": ["Work Order"]}, ], } From 84e91e0c7cc0e1c3c6cbe5f191464c3e63694a22 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:06:43 +0000 Subject: [PATCH 54/58] feat: Add non-negative constraint to completed qty fields in job card and time log (backport #48946) (#48989) * feat: Add non-negative constraint to completed qty fields in job card and time log (cherry picked from commit c30665fda7efec8cc29056ce0c81e9a5e5471507) # Conflicts: # erpnext/manufacturing/doctype/job_card/job_card.json # erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json * chore: resolve conflicts * chore: resolve conflicts --------- Co-authored-by: KerollesFathy Co-authored-by: Mihir Kandoi --- erpnext/manufacturing/doctype/job_card/job_card.json | 5 +++-- .../doctype/job_card_time_log/job_card_time_log.json | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 39e7aac38ae..7f4fbdaef06 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -160,6 +160,7 @@ "fieldname": "total_completed_qty", "fieldtype": "Float", "label": "Total Completed Qty", + "non_negative": 1, "read_only": 1 }, { @@ -510,7 +511,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2025-03-17 15:55:11.143456", + "modified": "2025-08-04 15:47:54.514290", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", @@ -568,4 +569,4 @@ "states": [], "title_field": "operation", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json index 6a86214986a..0565d677e9b 100644 --- a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json @@ -42,7 +42,8 @@ "fieldname": "completed_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Completed Qty" + "label": "Completed Qty", + "non_negative": 1 }, { "fieldname": "employee", @@ -63,7 +64,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-05-21 12:40:55.765860", + "modified": "2025-08-04 15:47:11.748937", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Time Log", From 3e8deeed07df25c281e0b864a0e5daa1cef7a141 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 5 Aug 2025 10:53:38 +0530 Subject: [PATCH 55/58] fix(process statement of accounts): make date fields mandatory (cherry picked from commit 23bc180d983e206a8373119cf56e5391db21d407) --- .../process_statement_of_accounts.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index f5779cff84f..0b3095d84ab 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -78,18 +78,18 @@ "reqd": 1 }, { - "depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');", + "depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');", "fieldname": "from_date", "fieldtype": "Date", "label": "From Date", - "mandatory_depends_on": "eval:doc.frequency == '';" + "mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") " }, { - "depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');", + "depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');", "fieldname": "to_date", "fieldtype": "Date", "label": "To Date", - "mandatory_depends_on": "eval:doc.frequency == '';" + "mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") " }, { "fieldname": "cost_center", @@ -330,7 +330,8 @@ "depends_on": "eval:(doc.report == 'Accounts Receivable');", "fieldname": "posting_date", "fieldtype": "Date", - "label": "Posting Date" + "label": "Posting Date", + "mandatory_depends_on": "eval:(doc.report == 'Accounts Receivable');" }, { "depends_on": "eval: (doc.report == 'Accounts Receivable');", @@ -400,7 +401,7 @@ } ], "links": [], - "modified": "2025-07-08 16:52:12.602384", + "modified": "2025-08-04 18:21:12.603623", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", From 964f9275dcbd0728dc3669ec846af9be6a8ed4c8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:19:46 +0000 Subject: [PATCH 56/58] feat: add non-negative constraint to batch size and sub operation time fields (backport #48948) (#48991) * feat: add non-negative constraint to batch size and sub operation time fields (cherry picked from commit f4722d3b24fd6d0916b56e63b9882548fa650d74) # Conflicts: # erpnext/manufacturing/doctype/operation/operation.json # erpnext/manufacturing/doctype/sub_operation/sub_operation.json * chore: resolve conflicts * chore: resolve conflicts * chore: resolve conflicts --------- Co-authored-by: KerollesFathy Co-authored-by: Mihir Kandoi --- erpnext/manufacturing/doctype/operation/operation.json | 7 ++++--- .../manufacturing/doctype/sub_operation/sub_operation.json | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/operation/operation.json b/erpnext/manufacturing/doctype/operation/operation.json index 753552ce54e..65daa03a76b 100644 --- a/erpnext/manufacturing/doctype/operation/operation.json +++ b/erpnext/manufacturing/doctype/operation/operation.json @@ -70,7 +70,8 @@ "fieldname": "batch_size", "fieldtype": "Int", "label": "Batch Size", - "mandatory_depends_on": "create_job_card_based_on_batch_size" + "mandatory_depends_on": "create_job_card_based_on_batch_size", + "non_negative": 1 }, { "default": "0", @@ -104,7 +105,7 @@ "icon": "fa fa-wrench", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-11-24 19:15:24.357187", + "modified": "2025-08-04 16:14:57.659318", "modified_by": "Administrator", "module": "Manufacturing", "name": "Operation", @@ -137,4 +138,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json index 10cee32398a..c7530e41aca 100644 --- a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json @@ -24,7 +24,8 @@ "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, - "label": "Operation Time" + "label": "Operation Time", + "non_negative": 1 }, { "fieldname": "column_break_5", @@ -39,7 +40,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-07-15 16:39:41.635362", + "modified": "2025-08-04 16:15:11.425349", "modified_by": "Administrator", "module": "Manufacturing", "name": "Sub Operation", @@ -49,4 +50,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From e36e5027d7ab46876d67d205d220228c3e4f4533 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 1 Aug 2025 01:12:39 +0530 Subject: [PATCH 57/58] fix: do not split round off when there is a cost center allocation (cherry picked from commit f0df41d521c161a26257b0ea7321f5bcc67f05c7) --- erpnext/accounts/general_ledger.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a0d08792d29..0e97d6969bd 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -186,6 +186,15 @@ def process_gl_map(gl_map, merge_entries=True, precision=None, from_repost=False def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_repost=False): + round_off_account, default_currency = frappe.get_cached_value( + "Company", gl_map[0].company, ["round_off_account", "default_currency"] + ) + if not precision: + precision = get_field_precision( + frappe.get_meta("GL Entry").get_field("debit"), + currency=default_currency, + ) + new_gl_map = [] for d in gl_map: cost_center = d.get("cost_center") @@ -203,6 +212,11 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_r new_gl_map.append(d) continue + if d.account == round_off_account: + d.cost_center = cost_center_allocation[0][0] + new_gl_map.append(d) + continue + for sub_cost_center, percentage in cost_center_allocation: gle = copy.deepcopy(d) gle.cost_center = sub_cost_center From e822a744793afcc26b6d39915b9c861b3b4b383d Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 5 Aug 2025 17:58:33 +0530 Subject: [PATCH 58/58] test: add test for cost center allocation commercial rounding (cherry picked from commit dd24cce5098be0d3abb15600b5d374559120a9ea) # Conflicts: # erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py --- .../test_cost_center_allocation.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py index 4abc82d8bec..dc73242ac06 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -4,6 +4,8 @@ import unittest import frappe +from frappe.query_builder.functions import Sum +from frappe.tests.utils import change_settings from frappe.utils import add_days, today from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center @@ -190,6 +192,31 @@ class TestCostCenterAllocation(unittest.TestCase): coa2.cancel() jv.cancel() + @change_settings("System Settings", {"rounding_method": "Commercial Rounding"}) + def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + cca = create_cost_center_allocation( + "_Test Company", + "Main Cost Center 1 - _TC", + {"Sub Cost Center 2 - _TC": 50, "Sub Cost Center 3 - _TC": 50}, + ) + + si = create_sales_invoice(rate=145.65, cost_center="Main Cost Center 1 - _TC") + + gl_entry = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gl_entry) + .select(Sum(gl_entry.credit).as_("cr"), Sum(gl_entry.debit).as_("dr")) + .where(gl_entry.voucher_type == "Sales Invoice") + .where(gl_entry.voucher_no == si.name) + ).run(as_dict=1) + + self.assertEqual(gl_entries[0].cr, gl_entries[0].dr) + + si.cancel() + cca.cancel() + def create_cost_center_allocation( company,