From 09f866022b4784861505522dbdf84bf50e32ae29 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 3 Jul 2025 14:05:07 +0530 Subject: [PATCH 01/41] fix: job card material request/transfer buttons UI overlap --- .../doctype/job_card/job_card.js | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 690d7945beb..ba8cd6102f7 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -51,9 +51,13 @@ frappe.ui.form.on("Job Card", { let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; if (to_request || excess_transfer_allowed) { - frm.add_custom_button(__("Material Request"), () => { - frm.trigger("make_material_request"); - }); + frm.add_custom_button( + __("Material Request"), + () => { + frm.trigger("make_material_request"); + }, + __("Create") + ); } // check if any row has untransferred materials @@ -61,9 +65,13 @@ frappe.ui.form.on("Job Card", { let to_transfer = frm.doc.items.some((row) => row.transferred_qty < row.required_qty); if (to_transfer || excess_transfer_allowed) { - frm.add_custom_button(__("Material Transfer"), () => { - frm.trigger("make_stock_entry"); - }).addClass("btn-primary"); + frm.add_custom_button( + __("Material Transfer"), + () => { + frm.trigger("make_stock_entry"); + }, + __("Create") + ).addClass("btn-primary"); } } From b1abcd55775e0b38f5bd6f9aaf13e523076cbb62 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 4 Jul 2025 08:29:29 +0530 Subject: [PATCH 02/41] fix: unnecessary primary button --- erpnext/manufacturing/doctype/job_card/job_card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index ba8cd6102f7..f95946a1724 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -71,7 +71,7 @@ frappe.ui.form.on("Job Card", { frm.trigger("make_stock_entry"); }, __("Create") - ).addClass("btn-primary"); + ); } } From 3952f5d9137b432d565a9790fa7b97076db011cb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 12:05:55 +0530 Subject: [PATCH 03/41] chore: fix flaky test in Tax Withholding Details (backport #48375) (#48394) * chore: fix flaky test in Tax Withholding Details (cherry picked from commit 14a2f9852185b7920b1de6f7c9d15750973fb451) * fix: sort tax withhodling details report by section code and transaction date (cherry picked from commit 7ee2418f6093add5f4d0b01aa8abb4c048ff9c1c) * fix(test): flaky budget test case (cherry picked from commit 704223e5d0197e235b29981f9a97170ce1ef6965) * fix(test): import get_accumulated_monthly_budget --------- Co-authored-by: ljain112 Co-authored-by: ruthra kumar Co-authored-by: diptanilsaha --- .../accounts/doctype/budget/test_budget.py | 12 +++++++++-- .../tax_withholding_details.py | 2 +- .../test_tax_withholding_details.py | 20 ++++++++++--------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 6d9a6f51468..6d256382042 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -6,7 +6,11 @@ import unittest import frappe from frappe.utils import now_datetime, nowdate -from erpnext.accounts.doctype.budget.budget import BudgetError, get_actual_expense +from erpnext.accounts.doctype.budget.budget import ( + BudgetError, + get_accumulated_monthly_budget, + get_actual_expense, +) from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.utils import get_fiscal_year from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order @@ -96,6 +100,10 @@ class TestBudget(unittest.TestCase): frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year) + accumulated_limit = get_accumulated_monthly_budget( + budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount + ) + mr = frappe.get_doc( { "doctype": "Material Request", @@ -109,7 +117,7 @@ class TestBudget(unittest.TestCase): "uom": "_Test UOM", "warehouse": "_Test Warehouse - _TC", "schedule_date": nowdate(), - "rate": 100000, + "rate": accumulated_limit + 1, "expense_account": "_Test Account Cost for Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", } 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 ee52da291b1..4507fbdcb60 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -121,7 +121,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ ) out.append(row) - out.sort(key=lambda x: x["section_code"]) + out.sort(key=lambda x: (x["section_code"], x["transaction_date"])) return out diff --git a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py index 6eff81e7f42..853ae71abe3 100644 --- a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py @@ -67,11 +67,12 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase): mid_year = add_to_date(fiscal_year[1], months=6) tds_doc = frappe.get_doc("Tax Withholding Category", "TDS - 3") tds_doc.rates[0].to_date = mid_year + from_date = add_to_date(mid_year, days=1) tds_doc.append( "rates", { "tax_withholding_rate": 20, - "from_date": add_to_date(mid_year, days=1), + "from_date": from_date, "to_date": fiscal_year[2], "single_threshold": 1, "cumulative_threshold": 1, @@ -80,18 +81,19 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase): tds_doc.save() - inv_1 = make_purchase_invoice(rate=1000, do_not_submit=True) + inv_1 = make_purchase_invoice( + rate=1000, posting_date=add_to_date(fiscal_year[1], days=1), do_not_save=True, do_not_submit=True + ) + inv_1.set_posting_time = 1 inv_1.apply_tds = 1 - inv_1.tax_withholding_category = "TDS - 3" + inv_1.tax_withholding_category = tds_doc.name + inv_1.save() inv_1.submit() - inv_2 = make_purchase_invoice( - rate=1000, do_not_submit=True, posting_date=add_to_date(mid_year, days=1), do_not_save=True - ) + inv_2 = make_purchase_invoice(rate=1000, posting_date=from_date, do_not_save=True, do_not_submit=True) inv_2.set_posting_time = 1 - - inv_1.apply_tds = 1 - inv_2.tax_withholding_category = "TDS - 3" + inv_2.apply_tds = 1 + inv_2.tax_withholding_category = tds_doc.name inv_2.save() inv_2.submit() From f25097da1d979e6242d166c557f1fe595a09048f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 12:40:19 +0530 Subject: [PATCH 04/41] fix: pos recent order display customer code and name (backport #48379) (#48388) fix: pos recent order display customer code and name (#48379) (cherry picked from commit 5f721f01d3d33beaf39a1d9c5540d62ad855c1af) Co-authored-by: Diptanil Saha --- erpnext/public/scss/point-of-sale.scss | 27 ++++++++++++------- .../page/point_of_sale/point_of_sale.py | 2 +- .../page/point_of_sale/pos_past_order_list.js | 2 +- .../point_of_sale/pos_past_order_summary.js | 8 ++++-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index f51e83ffe31..c9e2361e842 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -1063,21 +1063,31 @@ justify-content: flex-end; padding-right: var(--padding-sm); - > .customer-name { - font-size: var(--text-2xl); - font-weight: 700; - } + > .customer-section { + margin-bottom: auto; - > .customer-email { - font-size: var(--text-md); - font-weight: 500; + > .customer-name { + font-size: var(--text-2xl); + font-weight: 700; + } + + > .customer-code { + font-size: var(--text-xs); + font-weight: 500; + color: var(--text-light); + } + + > .customer-email { + font-size: var(--text-md); + font-weight: 500; + } } > .cashier { font-size: var(--text-md); font-weight: 500; color: var(--gray-600); - margin-top: auto; + margin-top: var(--margin-md); } } @@ -1085,7 +1095,6 @@ display: flex; flex-direction: column; align-items: flex-end; - justify-content: space-between; > .paid-amount { font-size: var(--text-2xl); diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 89e001897dd..d8de762dcd3 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -338,7 +338,7 @@ def create_opening_voucher(pos_profile, company, balance_details): @frappe.whitelist() def get_past_order_list(search_term, status, limit=20): - fields = ["name", "grand_total", "currency", "customer", "posting_time", "posting_date"] + fields = ["name", "grand_total", "currency", "customer", "customer_name", "posting_time", "posting_date"] invoice_list = [] if search_term and status: diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js index dda44f25299..5ea58a43c09 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -106,7 +106,7 @@ erpnext.PointOfSale.PastOrderList = class { - ${frappe.ellipsis(invoice.customer, 20)} + ${frappe.ellipsis(invoice.customer_name, 20)}
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index cf775176c07..0a965c47f48 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -73,14 +73,18 @@ erpnext.PointOfSale.PastOrderSummary = class { get_upper_section_html(doc) { const { status } = doc; let indicator_color = ""; + const is_customer_naming_by_customer_name = frappe.sys_defaults.cust_master_name !== "Customer Name"; ["Paid", "Consolidated"].includes(status) && (indicator_color = "green"); status === "Draft" && (indicator_color = "red"); status === "Return" && (indicator_color = "grey"); return `
-
${doc.customer}
-
${this.customer_email}
+
+
${doc.customer_name}
+ ${is_customer_naming_by_customer_name ? `
${doc.customer}
` : ""} +
${this.customer_email}
+
${__("Sold by")}: ${doc.owner}
From cfedaf5dc1b7cfcaa82ee8eeaed78ea8c21d6208 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 3 Jul 2025 12:30:25 +0530 Subject: [PATCH 05/41] fix: rate not being fetched for product bundles in material request (cherry picked from commit 45c7bac2d00b073ee6d308b66843f3f384459da9) # Conflicts: # erpnext/stock/doctype/packed_item/packed_item.py --- erpnext/stock/doctype/packed_item/packed_item.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index eaeb04d568e..6261d746e7f 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -7,6 +7,7 @@ import json import frappe +import frappe.defaults from frappe.model.document import Document from frappe.utils import flt @@ -340,11 +341,24 @@ def on_doctype_update(): @frappe.whitelist() def get_items_from_product_bundle(row): +<<<<<<< HEAD row, items = json.loads(row), [] +======= + row, items = ItemDetailsCtx(json.loads(row)), [] + defaults = frappe.defaults.get_defaults() +>>>>>>> 45c7bac2d0 (fix: rate not being fetched for product bundles in material request) bundled_items = get_product_bundle_items(row["item_code"]) for item in bundled_items: - row.update({"item_code": item.item_code, "qty": flt(row["quantity"]) * flt(item.qty)}) + row.update( + { + "item_code": item.item_code, + "qty": flt(row["quantity"]) * flt(item.qty), + "conversion_rate": 1, + "price_list": defaults.buying_price_list, + "currency": defaults.currency, + } + ) items.append(get_item_details(row)) return items From 74948aabdae8edb7316be9ff8e7604229a963d04 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 3 Jul 2025 12:45:35 +0530 Subject: [PATCH 06/41] fix: LCV from PR order mismatch --- erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index c80bcc8123b..a70af1fc384 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -332,5 +332,6 @@ def get_pr_items(purchase_receipt): (pr_item.parent == purchase_receipt.receipt_document) & ((item.is_stock_item == 1) | (item.is_fixed_asset == 1)) ) + .orderby(pr_item.idx) .run(as_dict=True) ) From edef03ac226dde01463c3061bf9b3f979332d789 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 4 Jul 2025 12:49:45 +0530 Subject: [PATCH 07/41] chore: resolve conflict --- erpnext/stock/doctype/packed_item/packed_item.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 6261d746e7f..85badeea5ff 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -341,12 +341,8 @@ def on_doctype_update(): @frappe.whitelist() def get_items_from_product_bundle(row): -<<<<<<< HEAD row, items = json.loads(row), [] -======= - row, items = ItemDetailsCtx(json.loads(row)), [] defaults = frappe.defaults.get_defaults() ->>>>>>> 45c7bac2d0 (fix: rate not being fetched for product bundles in material request) bundled_items = get_product_bundle_items(row["item_code"]) for item in bundled_items: From 9a538c6843e44b1031a4739e1a5b8d5bfe712d32 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 4 Jul 2025 21:39:34 +0530 Subject: [PATCH 08/41] feat: add subject field to project (#48368) * feat: add subject field to project (cherry picked from commit 407fdab487f58b17c3f05f2f128957c87961b532) --- erpnext/projects/doctype/project/project.js | 6 ++++++ erpnext/projects/doctype/project/project.json | 13 +++++++++++-- erpnext/projects/doctype/project/project.py | 5 ++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 45baff562ce..449a9d87ff2 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -202,6 +202,12 @@ frappe.ui.form.on("Project", { }); }); }, + + collect_progress: function (frm) { + if (frm.doc.collect_progress && !frm.doc.subject) { + frm.set_value("subject", __("For project {0}, update your status", [frm.doc.name])); + } + }, }); function open_form(frm, doctype, child_doctype, parentfield) { diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 464b1c9d7a8..e2613465dd3 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -62,6 +62,7 @@ "day_to_send", "weekly_time_to_send", "column_break_45", + "subject", "message" ], "fields": [ @@ -447,6 +448,13 @@ "print_hide": 1, "reqd": 1, "set_only_once": 1 + }, + { + "depends_on": "collect_progress", + "fieldname": "subject", + "fieldtype": "Data", + "label": "Subject", + "mandatory_depends_on": "collect_progress" } ], "icon": "fa fa-puzzle-piece", @@ -454,7 +462,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2024-04-24 10:56:16.001032", + "modified": "2025-07-03 10:54:30.444139", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -501,6 +509,7 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "search_fields": "project_name,customer, status, priority, is_active", "show_name_in_global_search": 1, "sort_field": "modified", @@ -509,4 +518,4 @@ "timeline_field": "customer", "title_field": "project_name", "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index f7653d92d63..ebfff79531f 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -62,6 +62,7 @@ class Project(Document): sales_order: DF.Link | None second_email: DF.Time | None status: DF.Literal["Open", "Completed", "Cancelled"] + subject: DF.Data | None to_time: DF.Time | None total_billable_amount: DF.Currency total_billed_amount: DF.Currency @@ -606,8 +607,6 @@ def send_project_update_email_to_users(project): } ).insert() - subject = "For project %s, update your status" % (project) - incoming_email_account = frappe.db.get_value( "Email Account", dict(enable_incoming=1, default_incoming=1), "email_id" ) @@ -615,7 +614,7 @@ def send_project_update_email_to_users(project): frappe.sendmail( recipients=get_users_email(doc), message=doc.message, - subject=_(subject), + subject=doc.subject, reference_doctype=project_update.doctype, reference_name=project_update.name, reply_to=incoming_email_account, From a2436e4b6e8a677bb759b57c72f7df44560653bf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 20:10:27 +0200 Subject: [PATCH 09/41] fix(Quotation): hide buttons if user cannot use them (backport #48115) (#48405) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> fix(Quotation): hide buttons if user cannot use them (#48115) --- erpnext/selling/doctype/quotation/quotation.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 685b27ab3ef..da8476d6b1f 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -117,14 +117,15 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) { if ( - frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation || - !doc.valid_till || - frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0 + frappe.model.can_create("Sales Order") && + (frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation || + !doc.valid_till || + frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) ) { this.frm.add_custom_button(__("Sales Order"), () => this.make_sales_order(), __("Create")); } - if (doc.status !== "Ordered") { + if (doc.status !== "Ordered" && this.frm.has_perm("write")) { this.frm.add_custom_button(__("Set as Lost"), () => { this.frm.trigger("set_as_lost_dialog"); }); @@ -133,7 +134,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. cur_frm.page.set_inner_btn_group_as_primary(__("Create")); } - if (this.frm.doc.docstatus === 0) { + if (this.frm.doc.docstatus === 0 && frappe.model.can_read("Opportunity")) { this.frm.add_custom_button( __("Opportunity"), function () { From a4633d6e7545b684653e716af8f897ec8def2ed8 Mon Sep 17 00:00:00 2001 From: Lakshit Jain <108322669+ljain112@users.noreply.github.com> Date: Sun, 6 Jul 2025 14:55:45 +0530 Subject: [PATCH 10/41] fix: multiple fixes related Deferred Accounting (cherry picked from commit 277c1101fc291e1e4dcb7d2b092163e0a2deb46e) --- .../test_process_deferred_accounting.py | 13 ++++++++ .../sales_invoice/test_sales_invoice.py | 22 +++++++------ erpnext/accounts/general_ledger.py | 13 +++++++- .../deferred_revenue_and_expense.py | 31 ++++++++++++++++++- 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py index fddd9f83926..baf512210dc 100644 --- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py +++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py @@ -40,6 +40,13 @@ class TestProcessDeferredAccounting(unittest.TestCase): si.save() si.submit() + original_gle = [ + ["Debtors - _TC", 3000.0, 0, "2023-07-01"], + [deferred_account, 0.0, 3000, "2023-07-01"], + ] + + check_gl_entries(self, si.name, original_gle, "2023-07-01") + process_deferred_accounting = frappe.get_doc( dict( doctype="Process Deferred Accounting", @@ -63,6 +70,12 @@ class TestProcessDeferredAccounting(unittest.TestCase): ] check_gl_entries(self, si.name, expected_gle, "2023-07-01") + + # cancel the process deferred accounting document + process_deferred_accounting.cancel() + + # check if gl entries are cancelled + check_gl_entries(self, si.name, original_gle, "2023-07-01") change_acc_settings() def test_pda_submission_and_cancellation(self): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 35669308091..bae372250ea 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2497,6 +2497,10 @@ class TestSalesInvoice(FrappeTestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + @change_settings( + "Accounts Settings", + {"book_deferred_entries_based_on": "Days", "book_deferred_entries_via_journal_entry": 0}, + ) def test_deferred_revenue(self): deferred_account = create_account( account_name="Deferred Revenue", @@ -2551,6 +2555,10 @@ class TestSalesInvoice(FrappeTestCase): self.assertRaises(frappe.ValidationError, si.save) + @change_settings( + "Accounts Settings", + {"book_deferred_entries_based_on": "Months", "book_deferred_entries_via_journal_entry": 0}, + ) def test_fixed_deferred_revenue(self): deferred_account = create_account( account_name="Deferred Revenue", @@ -2558,10 +2566,6 @@ class TestSalesInvoice(FrappeTestCase): company="_Test Company", ) - acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") - acc_settings.book_deferred_entries_based_on = "Months" - acc_settings.save() - item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_revenue = 1 item.deferred_revenue_account = deferred_account @@ -2601,10 +2605,6 @@ class TestSalesInvoice(FrappeTestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") - acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") - acc_settings.book_deferred_entries_based_on = "Days" - acc_settings.save() - def test_validate_inter_company_transaction_address_links(self): def _validate_address_link(address, link_doctype, link_name): return frappe.db.get_value( @@ -2853,7 +2853,9 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(si.items[0].rate, rate) self.assertEqual(target_doc.items[0].rate, rate) - check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) + check_gl_entries( + self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1), voucher_type="Purchase Invoice" + ) def test_internal_transfer_gl_precision_issues(self): # Make a stock queue of an item with two valuations @@ -4587,6 +4589,8 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type=" ) gl_entries = q.run(as_dict=True) + doc.assertGreater(len(gl_entries), 0) + for i, gle in enumerate(gl_entries): doc.assertEqual(expected_gle[i][0], gle.account) doc.assertEqual(expected_gle[i][1], gle.debit) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index c5ee7400473..20476b4164c 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -692,7 +692,18 @@ def make_reverse_gl_entries( query.run() else: if not immutable_ledger_enabled: - set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) + gle_names = [x.get("name") for x in gl_entries] + + # if names are available, cancel only that set of entries + if not all(gle_names): + set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) + else: + frappe.db.sql( + """UPDATE `tabGL Entry` SET is_cancelled = 1, + modified=%s, modified_by=%s + where name in %s and is_cancelled = 0""", + (now(), frappe.session.user, tuple(gle_names)), + ) for entry in gl_entries: new_gle = copy.deepcopy(entry) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 1c85061a551..9a3c05e90f2 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -79,6 +79,14 @@ class Deferred_Item: return - estimated amount to post for given period Calculated based on already booked amount and item service period """ + if self.filters.book_deferred_entries_based_on == "Months": + # if the deferred entries are based on service period, use service start and end date + return self.calculate_monthly_amount(start_date, end_date) + + else: + return self.calculate_days_amount(start_date, end_date) + + def calculate_monthly_amount(self, start_date, end_date): total_months = ( (self.service_end_date.year - self.service_start_date.year) * 12 + (self.service_end_date.month - self.service_start_date.month) @@ -105,6 +113,19 @@ class Deferred_Item: return base_amount + def calculate_days_amount(self, start_date, end_date): + base_amount = 0 + total_days = date_diff(self.service_end_date, self.service_start_date) + 1 + total_booking_days = date_diff(end_date, start_date) + 1 + already_booked_amount = self.get_item_total() + + base_amount = flt(self.base_net_amount * total_booking_days / flt(total_days)) + + if base_amount + already_booked_amount > self.base_net_amount: + base_amount = self.base_net_amount - already_booked_amount + + return base_amount + def make_dummy_gle(self, name, date, amount): """ return - frappe._dict() of a dummy gle entry @@ -245,6 +266,10 @@ class Deferred_Revenue_and_Expense_Report: else: self.filters = frappe._dict(filters) + self.filters.book_deferred_entries_based_on = frappe.db.get_singles_value( + "Accounts Settings", "book_deferred_entries_based_on" + ) + self.period_list = None self.deferred_invoices = [] # holds period wise total for report @@ -289,7 +314,11 @@ class Deferred_Revenue_and_Expense_Report: .join(inv) .on(inv.name == inv_item.parent) .left_join(gle) - .on((inv_item.name == gle.voucher_detail_no) & (deferred_account_field == gle.account)) + .on( + (inv_item.name == gle.voucher_detail_no) + & (deferred_account_field == gle.account) + & (gle.is_cancelled == 0) + ) .select( inv.name.as_("doc"), inv.posting_date, From 26db582499f4ddbaabd0def2f092c65580feb8d9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 6 Jul 2025 15:25:59 +0530 Subject: [PATCH 11/41] fix: add company field on POS Invoice Merge Log (backport #48357) (#48414) * fix: add company field on POS Invoice Merge Log (cherry picked from commit 109658731b967d6944e322e549eb2c2fdfaa8920) # Conflicts: # erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json * fix: patch for updating company name on existing pos merge log records (cherry picked from commit d46b68230c61703db7aeb4564d311334593986a6) # Conflicts: # erpnext/patches.txt * fix: pass company on create_merge_logs (cherry picked from commit b4b473185f5e8509ee7f1a446ee0670b2b4c8891) * test: test company fetching from POS Closing Entry (cherry picked from commit 9548f341bf631ae72d6591324e0bdf7cbbc40fe3) # Conflicts: # erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py * chore: remove conflicts --------- Co-authored-by: Kavin <78342682+kavin0411@users.noreply.github.com> Co-authored-by: diptanilsaha --- .../pos_invoice_merge_log.json | 18 +++++++++++++++--- .../pos_invoice_merge_log.py | 6 +++--- erpnext/patches.txt | 1 + .../v15_0/set_company_on_pos_inv_merge_log.py | 12 ++++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 erpnext/patches/v15_0/set_company_on_pos_inv_merge_log.py diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json index a0594556474..7d9b90f1b65 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json @@ -5,6 +5,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "company", "posting_date", "posting_time", "merge_invoices_based_on", @@ -113,12 +114,22 @@ "label": "Posting Time", "no_copy": 1, "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-08-01 11:36:42.456429", + "modified": "2025-07-02 17:08:04.747202", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Merge Log", @@ -179,8 +190,9 @@ "write": 1 } ], - "sort_field": "modified", + "row_format": "Dynamic", + "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 3d6d03f7380..fedc6a7772d 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -28,11 +28,10 @@ class POSInvoiceMergeLog(Document): if TYPE_CHECKING: from frappe.types import DF - from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import ( - POSInvoiceReference, - ) + from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import POSInvoiceReference amended_from: DF.Link | None + company: DF.Link consolidated_credit_note: DF.Link | None consolidated_invoice: DF.Link | None customer: DF.Link @@ -584,6 +583,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): merge_log.posting_time = ( get_time(closing_entry.get("posting_time")) if closing_entry else nowtime() ) + merge_log.company = closing_entry.get("company") if closing_entry else None merge_log.customer = customer merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None merge_log.set("pos_invoices", _invoices) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 34140dc2c84..71cb012c1b8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -411,3 +411,4 @@ erpnext.patches.v14_0.update_full_name_in_contract 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 diff --git a/erpnext/patches/v15_0/set_company_on_pos_inv_merge_log.py b/erpnext/patches/v15_0/set_company_on_pos_inv_merge_log.py new file mode 100644 index 00000000000..8f83898a877 --- /dev/null +++ b/erpnext/patches/v15_0/set_company_on_pos_inv_merge_log.py @@ -0,0 +1,12 @@ +import frappe + + +def execute(): + pos_invoice_merge_logs = frappe.db.get_all( + "POS Invoice Merge Log", {"docstatus": 1}, ["name", "pos_closing_entry"] + ) + + for log in pos_invoice_merge_logs: + if log.pos_closing_entry and frappe.db.exists("POS Closing Entry", log.pos_closing_entry): + company = frappe.db.get_value("POS Closing Entry", log.pos_closing_entry, "company") + frappe.db.set_value("POS Invoice Merge Log", log.name, "company", company) From 8eede1d266f8c9fa31af2aadffaa999bc85eeb21 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 6 Jul 2025 21:40:26 +0530 Subject: [PATCH 12/41] fix: incorrect pending qty when creating PI from PO and PI rates differ from PO (backport #48173) (#48340) * fix: incorrect pending qty when creating PI from PO and PI rates differ from PO Co-authored-by: Mihir Kandoi --- .../doctype/purchase_order/purchase_order.py | 18 +++++++++++++----- .../purchase_order/test_purchase_order.py | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 43ef287854e..e29e695f48c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -807,11 +807,19 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions target.credit_to = get_party_account("Supplier", source.supplier, source.company) def update_item(obj, target, source_parent): - target.amount = flt(obj.amount) - flt(obj.billed_amt) - target.base_amount = target.amount * flt(source_parent.conversion_rate) - target.qty = ( - target.amount / flt(obj.rate) if (flt(obj.rate) and flt(obj.billed_amt)) else flt(obj.qty) - ) + def get_billed_qty(po_item_name): + from frappe.query_builder.functions import Sum + + table = frappe.qb.DocType("Purchase Invoice Item") + query = ( + frappe.qb.from_(table) + .select(Sum(table.qty).as_("qty")) + .where((table.docstatus == 1) & (table.po_detail == po_item_name)) + ) + return query.run(pluck="qty")[0] or 0 + + billed_qty = flt(get_billed_qty(obj.name)) + target.qty = flt(obj.qty) - billed_qty item = get_item_defaults(target.item_code, source_parent.company) item_group = get_item_group_defaults(target.item_code, source_parent.company) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index c6ee35d6090..206cbc0000e 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1286,6 +1286,25 @@ class TestPurchaseOrder(FrappeTestCase): self.assertFalse(po.per_billed) self.assertEqual(po.status, "To Receive and Bill") + @change_settings("Buying Settings", {"maintain_same_rate": 0}) + def test_purchase_invoice_creation_with_partial_qty(self): + po = create_purchase_order(qty=100, rate=10) + + pi = make_pi_from_po(po.name) + pi.items[0].qty = 42 + pi.items[0].rate = 7.5 + pi.submit() + + pi = make_pi_from_po(po.name) + self.assertEqual(pi.items[0].qty, 58) + self.assertEqual(pi.items[0].rate, 10) + pi.items[0].qty = 8 + pi.items[0].rate = 5 + pi.submit() + + pi = make_pi_from_po(po.name) + self.assertEqual(pi.items[0].qty, 50) + def create_po_for_sc_testing(): from erpnext.controllers.tests.test_subcontracting_controller import ( From 5cd36c318b4413cbe7054a2372faf097a923c0e7 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 7 Jul 2025 13:01:41 +0530 Subject: [PATCH 13/41] fix: item list and project not being set in work order when created from material request (cherry picked from commit 099a5fbad950c410d7d91dbfc59a8a7ce85bd0b4) --- erpnext/stock/doctype/material_request/material_request.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 956f47bb978..73eac8f2d1a 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -764,10 +764,11 @@ def raise_work_orders(material_request): "material_request_item": d.name, "planned_start_date": mr.transaction_date, "company": mr.company, + "project": d.project, } ) - wo_order.set_work_order_operations() + wo_order.get_items_and_operations_from_bom() wo_order.flags.ignore_validate = True wo_order.flags.ignore_mandatory = True wo_order.save() From f1621d15ffc6d09a68734fd5a4c26810f71296d7 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 7 Jul 2025 15:01:31 +0530 Subject: [PATCH 14/41] fix: address not found when creating internal PR from DN (cherry picked from commit 97c48ed6d2e60a03961fb96feb6db406a062bd0b) --- erpnext/stock/doctype/delivery_note/delivery_note.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 12182ae990c..37e2737391a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -1281,6 +1281,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): "doctype": target_doctype, "postprocess": update_details, "field_no_map": ["taxes_and_charges", "set_warehouse"], + "field_map": {"shipping_address_name": "shipping_address"}, }, doctype + " Item": { "doctype": target_doctype + " Item", From d4700e5560d912504fc894d8a0730d4519759392 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:50:04 +0530 Subject: [PATCH 15/41] feat: add price list field to material request (backport #48425) (#48429) * feat: add price list field to material request Co-authored-by: Mihir Kandoi --- erpnext/public/js/controllers/buying.js | 3 ++- .../doctype/material_request/material_request.js | 12 +++++++++++- .../doctype/material_request/material_request.json | 12 ++++++++++-- .../doctype/material_request/material_request.py | 5 +++++ erpnext/stock/doctype/packed_item/packed_item.py | 7 +++---- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 2b854d649d8..2162c000221 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -581,7 +581,8 @@ erpnext.buying.get_items_from_product_bundle = function(frm) { transaction_date: frm.doc.transaction_date || frm.doc.posting_date, ignore_pricing_rule: frm.doc.ignore_pricing_rule, doctype: frm.doc.doctype - } + }, + price_list: frm.doc.price_list, }, freeze: true, callback: function(r) { diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 18d8919a668..23b42b98b36 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -42,6 +42,15 @@ frappe.ui.form.on("Material Request", { }, }; }); + + frm.set_query("price_list", () => { + return { + filters: { + buying: 1, + enabled: 1, + }, + }; + }); }, onload: function (frm) { @@ -70,6 +79,7 @@ frappe.ui.form.on("Material Request", { }); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); + frm.doc.price_list = frappe.defaults.get_default("buying_price_list"); }, company: function (frm) { @@ -245,7 +255,7 @@ frappe.ui.form.on("Material Request", { from_warehouse: item.from_warehouse, warehouse: item.warehouse, doctype: frm.doc.doctype, - buying_price_list: frappe.defaults.get_default("buying_price_list"), + buying_price_list: frm.doc.price_list, currency: frappe.defaults.get_default("Currency"), name: frm.doc.name, qty: item.qty || 1, diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 8df61fe7fb8..4079288009a 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -16,6 +16,7 @@ "column_break_2", "transaction_date", "schedule_date", + "price_list", "amended_from", "warehouse_section", "scan_barcode", @@ -351,13 +352,19 @@ { "fieldname": "column_break_13", "fieldtype": "Column Break" + }, + { + "fieldname": "price_list", + "fieldtype": "Link", + "label": "Price List", + "options": "Price List" } ], "icon": "fa fa-ticket", "idx": 70, "is_submittable": 1, "links": [], - "modified": "2025-04-21 18:36:04.827917", + "modified": "2025-07-07 13:15:28.615984", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", @@ -425,10 +432,11 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "search_fields": "status,transaction_date", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", "states": [], "title_field": "title" -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 73eac8f2d1a..8c80f907093 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -8,6 +8,7 @@ import json import frappe +import frappe.defaults from frappe import _, msgprint from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum @@ -45,6 +46,7 @@ class MaterialRequest(BuyingController): naming_series: DF.Literal["MAT-MR-.YYYY.-"] per_ordered: DF.Percent per_received: DF.Percent + price_list: DF.Link | None scan_barcode: DF.Data | None schedule_date: DF.Date | None select_print_heading: DF.Link | None @@ -151,6 +153,9 @@ class MaterialRequest(BuyingController): self.reset_default_field_value("set_warehouse", "items", "warehouse") self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse") + if not self.price_list: + self.price_list = frappe.defaults.get_defaults().buying_price_list + def before_update_after_submit(self): self.validate_schedule_date() diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 85badeea5ff..3d3bfc7e010 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -340,9 +340,8 @@ def on_doctype_update(): @frappe.whitelist() -def get_items_from_product_bundle(row): +def get_items_from_product_bundle(row, price_list): row, items = json.loads(row), [] - defaults = frappe.defaults.get_defaults() bundled_items = get_product_bundle_items(row["item_code"]) for item in bundled_items: @@ -351,8 +350,8 @@ def get_items_from_product_bundle(row): "item_code": item.item_code, "qty": flt(row["quantity"]) * flt(item.qty), "conversion_rate": 1, - "price_list": defaults.buying_price_list, - "currency": defaults.currency, + "price_list": price_list, + "currency": frappe.defaults.get_defaults().currency, } ) items.append(get_item_details(row)) From ed77c15ebcb749336394c90e81498d72e2b59f99 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 7 Jul 2025 16:24:42 +0530 Subject: [PATCH 16/41] fix: fetch from parent optional in inventory dimension (cherry picked from commit 8aac6a6b18ee08dfb4722e620acf3ea9fbae89ab) # Conflicts: # erpnext/stock/doctype/inventory_dimension/inventory_dimension.json --- .../doctype/inventory_dimension/inventory_dimension.js | 6 ++++-- .../doctype/inventory_dimension/inventory_dimension.json | 8 ++++++-- .../doctype/inventory_dimension/inventory_dimension.py | 5 ----- .../inventory_dimension/test_inventory_dimension.py | 2 ++ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js index c819d17b1e7..1b5f4a5743f 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js @@ -75,7 +75,9 @@ frappe.ui.form.on("Inventory Dimension", { set_parent_fields(frm) { if (frm.doc.apply_to_all_doctypes) { - frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document); + let options = ["\n", frm.doc.reference_document]; + + frm.set_df_property("fetch_from_parent", "options", options); } else if (frm.doc.document_type && frm.doc.istable) { frappe.call({ method: "erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields", @@ -85,7 +87,7 @@ frappe.ui.form.on("Inventory Dimension", { }, callback: (r) => { if (r.message && r.message.length) { - frm.set_df_property("fetch_from_parent", "options", [""].concat(r.message)); + frm.set_df_property("fetch_from_parent", "options", ["\n"].concat(r.message)); } else { frm.set_df_property("fetch_from_parent", "hidden", 1); } diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index 9638c4e6289..f097535cf07 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -143,7 +143,6 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:!doc.apply_to_all_doctypes", "description": "Set fieldname from which you want to fetch the data from the parent form.", "fieldname": "fetch_from_parent", "fieldtype": "Select", @@ -189,7 +188,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-07-08 08:58:50.228211", + "modified": "2025-07-07 15:51:29.329064", "modified_by": "Administrator", "module": "Stock", "name": "Inventory Dimension", @@ -225,7 +224,12 @@ "role": "Stock User" } ], +<<<<<<< HEAD "sort_field": "modified", +======= + "row_format": "Dynamic", + "sort_field": "creation", +>>>>>>> 8aac6a6b18 (fix: fetch from parent optional in inventory dimension) "sort_order": "DESC", "states": [] } diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index b08a602fdf2..6f8571ea156 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -64,16 +64,11 @@ class InventoryDimension(Document): self.reset_value() self.set_source_and_target_fieldname() self.set_type_of_transaction() - self.set_fetch_value_from() def set_type_of_transaction(self): if self.apply_to_all_doctypes: self.type_of_transaction = "Both" - def set_fetch_value_from(self): - if self.apply_to_all_doctypes: - self.fetch_from_parent = self.reference_document - def do_not_update_document(self): if self.is_new() or not self.has_stock_ledger(): return diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index f8128ce0033..40b87b79f97 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -155,6 +155,8 @@ class TestInventoryDimension(FrappeTestCase): reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1 ) + inv_dimension.db_set("fetch_from_parent", "Rack") + self.assertEqual(inv_dimension.type_of_transaction, "Both") self.assertEqual(inv_dimension.fetch_from_parent, "Rack") From 6b41dc2fed0340ade26b6df4b50845d17f4dde1b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 22:36:48 +0200 Subject: [PATCH 17/41] fix: make labels in error message translatable (backport #48327) (#48436) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> fix: make labels in error message translatable (#48327) --- .../accounts_settings/accounts_settings.py | 4 +-- .../doctype/payment_entry/payment_entry.py | 2 +- .../payment_reconciliation.py | 2 +- .../doctype/pricing_rule/pricing_rule.py | 2 +- erpnext/accounts/utils.py | 2 +- erpnext/manufacturing/doctype/bom/bom.py | 2 +- .../doctype/work_order/work_order.py | 4 +-- .../doctype/email_digest/email_digest.py | 30 +++++++++---------- erpnext/stock/doctype/item/item.py | 4 +-- .../stock_ledger_entry/stock_ledger_entry.py | 2 +- .../stock_reservation_entry.py | 2 +- 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index d03ebed353e..7959f163871 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -147,8 +147,8 @@ class AccountsSettings(Document): if self.add_taxes_from_item_tax_template and self.add_taxes_from_taxes_and_charges_template: frappe.throw( _("You cannot enable both the settings '{0}' and '{1}'.").format( - frappe.bold(self.meta.get_label("add_taxes_from_item_tax_template")), - frappe.bold(self.meta.get_label("add_taxes_from_taxes_and_charges_template")), + frappe.bold(_(self.meta.get_label("add_taxes_from_item_tax_template"))), + frappe.bold(_(self.meta.get_label("add_taxes_from_taxes_and_charges_template"))), ), title=_("Auto Tax Settings Error"), ) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f96145b65d1..5c2dc7747bb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -568,7 +568,7 @@ class PaymentEntry(AccountsController): def validate_mandatory(self): for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"): if not self.get(field): - frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field))) + frappe.throw(_("{0} is mandatory").format(_(self.meta.get_label(field)))) def validate_reference_documents(self): valid_reference_doctypes = self.get_valid_reference_doctypes() diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 27001e9ab14..c77870d0886 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -589,7 +589,7 @@ class PaymentReconciliation(Document): def check_mandatory_to_fetch(self): for fieldname in ["company", "party_type", "party", "receivable_payable_account"]: if not self.get(fieldname): - frappe.throw(_("Please select {0} first").format(self.meta.get_label(fieldname))) + frappe.throw(_("Please select {0} first").format(_(self.meta.get_label(fieldname)))) def validate_entries(self): if not self.get("invoices"): diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index f0481ad63f6..21e5ad18d9b 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -169,7 +169,7 @@ class PricingRule(Document): tocheck = frappe.scrub(self.get("applicable_for", "")) if tocheck and not self.get(tocheck): - throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) + throw(_("{0} is required").format(_(self.meta.get_label(tocheck))), frappe.MandatoryError) if self.apply_rule_on_other: o_field = "other_" + frappe.scrub(self.apply_rule_on_other) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6fa0e3e9802..56133c036d3 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -169,7 +169,7 @@ def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None): if doc: doc.fiscal_year = years[0] else: - throw(_("{0} '{1}' not in Fiscal Year {2}").format(label, formatdate(date), fiscal_year)) + throw(_("{0} '{1}' not in Fiscal Year {2}").format(_(label), formatdate(date), fiscal_year)) @frappe.whitelist() diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 5a6edb703fa..bf6bf2530ce 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -987,7 +987,7 @@ class BOM(WebsiteGenerator): self.transfer_material_against = "Work Order" if not self.transfer_material_against and not self.is_new(): frappe.throw( - _("Setting {} is required").format(self.meta.get_label("transfer_material_against")), + _("Setting {0} is required").format(_(self.meta.get_label("transfer_material_against"))), title=_("Missing value"), ) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 176e955ee72..9b1bf28f997 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -390,7 +390,7 @@ class WorkOrder(Document): if qty > completed_qty: frappe.throw( _("{0} ({1}) cannot be greater than planned quantity ({2}) in Work Order {3}").format( - self.meta.get_label(fieldname), qty, completed_qty, self.name + _(self.meta.get_label(fieldname)), qty, completed_qty, self.name ), StockOverProductionError, ) @@ -1077,7 +1077,7 @@ class WorkOrder(Document): self.transfer_material_against = "Work Order" if not self.transfer_material_against: frappe.throw( - _("Setting {} is required").format(self.meta.get_label("transfer_material_against")), + _("Setting {0} is required").format(_(self.meta.get_label("transfer_material_against"))), title=_("Missing value"), ) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 610ef6205ee..c00b947b295 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -395,7 +395,7 @@ class EmailDigest(Document): label = get_link_to_report( "General Ledger", - self.meta.get_label("income"), + _(self.meta.get_label("income")), filters={ "from_date": self.future_from_date, "to_date": self.future_to_date, @@ -427,7 +427,7 @@ class EmailDigest(Document): filters = {"currency": self.currency} label = get_link_to_report( "Profit and Loss Statement", - label=self.meta.get_label(root_type + "_year_to_date"), + label=_(self.meta.get_label(root_type + "_year_to_date")), filters=filters, ) @@ -435,7 +435,7 @@ class EmailDigest(Document): filters = {"currency": self.currency} label = get_link_to_report( "Profit and Loss Statement", - label=self.meta.get_label(root_type + "_year_to_date"), + label=_(self.meta.get_label(root_type + "_year_to_date")), filters=filters, ) @@ -466,7 +466,7 @@ class EmailDigest(Document): label = get_link_to_report( "General Ledger", - self.meta.get_label("expenses_booked"), + _(self.meta.get_label("expenses_booked")), filters={ "company": self.company, "from_date": self.future_from_date, @@ -500,7 +500,7 @@ class EmailDigest(Document): label = get_link_to_report( "Sales Order", - label=self.meta.get_label("sales_orders_to_bill"), + label=_(self.meta.get_label("sales_orders_to_bill")), report_type="Report Builder", doctype="Sales Order", filters={ @@ -526,7 +526,7 @@ class EmailDigest(Document): label = get_link_to_report( "Sales Order", - label=self.meta.get_label("sales_orders_to_deliver"), + label=_(self.meta.get_label("sales_orders_to_deliver")), report_type="Report Builder", doctype="Sales Order", filters={ @@ -552,7 +552,7 @@ class EmailDigest(Document): label = get_link_to_report( "Purchase Order", - label=self.meta.get_label("purchase_orders_to_receive"), + label=_(self.meta.get_label("purchase_orders_to_receive")), report_type="Report Builder", doctype="Purchase Order", filters={ @@ -578,7 +578,7 @@ class EmailDigest(Document): label = get_link_to_report( "Purchase Order", - label=self.meta.get_label("purchase_orders_to_bill"), + label=_(self.meta.get_label("purchase_orders_to_bill")), report_type="Report Builder", doctype="Purchase Order", filters={ @@ -630,7 +630,7 @@ class EmailDigest(Document): "company": self.company, } label = get_link_to_report( - "Account Balance", label=self.meta.get_label(fieldname), filters=filters + "Account Balance", label=_(self.meta.get_label(fieldname)), filters=filters ) else: filters = { @@ -640,7 +640,7 @@ class EmailDigest(Document): "company": self.company, } label = get_link_to_report( - "Account Balance", label=self.meta.get_label(fieldname), filters=filters + "Account Balance", label=_(self.meta.get_label(fieldname)), filters=filters ) return {"label": label, "value": balance, "last_value": prev_balance} @@ -648,17 +648,17 @@ class EmailDigest(Document): if account_type == "Payable": label = get_link_to_report( "Accounts Payable", - label=self.meta.get_label(fieldname), + label=_(self.meta.get_label(fieldname)), filters={"report_date": self.future_to_date, "company": self.company}, ) elif account_type == "Receivable": label = get_link_to_report( "Accounts Receivable", - label=self.meta.get_label(fieldname), + label=_(self.meta.get_label(fieldname)), filters={"report_date": self.future_to_date, "company": self.company}, ) else: - label = self.meta.get_label(fieldname) + label = _(self.meta.get_label(fieldname)) return {"label": label, "value": balance, "last_value": prev_balance, "count": count} @@ -748,7 +748,7 @@ class EmailDigest(Document): label = get_link_to_report( "Quotation", - label=self.meta.get_label(fieldname), + label=_(self.meta.get_label(fieldname)), report_type="Report Builder", doctype="Quotation", filters={ @@ -779,7 +779,7 @@ class EmailDigest(Document): label = get_link_to_report( doc_type, - label=self.meta.get_label(fieldname), + label=_(self.meta.get_label(fieldname)), report_type="Report Builder", filters=filters, doctype=doc_type, diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 0536caf61a1..3409e9a559a 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -634,7 +634,7 @@ class Item(Document): if new_properties != [cstr(self.get(field)) for field in field_list]: msg = _("To merge, following properties must be same for both items") - msg += ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]) + msg += ": \n" + ", ".join([_(self.meta.get_label(fld)) for fld in field_list]) frappe.throw(msg, title=_("Cannot Merge"), exc=DataValidationError) def validate_duplicate_product_bundles_before_merge(self, old_name, new_name): @@ -981,7 +981,7 @@ class Item(Document): return if linked_doc := self._get_linked_submitted_documents(changed_fields): - changed_field_labels = [frappe.bold(self.meta.get_label(f)) for f in changed_fields] + changed_field_labels = [frappe.bold(_(self.meta.get_label(f))) for f in changed_fields] msg = _( "As there are existing submitted transactions against item {0}, you can not change the value of {1}." ).format(self.name, ", ".join(changed_field_labels)) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 11426aea51a..ddef027d0b8 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -192,7 +192,7 @@ class StockLedgerEntry(Document): mandatory = ["warehouse", "posting_date", "voucher_type", "voucher_no", "company"] for k in mandatory: if not self.get(k): - frappe.throw(_("{0} is required").format(self.meta.get_label(k))) + frappe.throw(_("{0} is required").format(_(self.meta.get_label(k)))) if self.voucher_type != "Stock Reconciliation" and not self.actual_qty: frappe.throw(_("Actual Qty is mandatory")) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index a66214c0aa7..5d6aa9f68f8 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -115,7 +115,7 @@ class StockReservationEntry(Document): ] for d in mandatory: if not self.get(d): - msg = _("{0} is required").format(self.meta.get_label(d)) + msg = _("{0} is required").format(_(self.meta.get_label(d))) frappe.throw(msg) def validate_group_warehouse(self) -> None: From 5df8ad6ef1456f2b14fea4f625ad1a8a38c13f55 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 8 Jul 2025 08:33:33 +0530 Subject: [PATCH 18/41] chore: fix conflicts --- .../doctype/inventory_dimension/inventory_dimension.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index f097535cf07..20250622fda 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -224,12 +224,7 @@ "role": "Stock User" } ], -<<<<<<< HEAD "sort_field": "modified", -======= - "row_format": "Dynamic", - "sort_field": "creation", ->>>>>>> 8aac6a6b18 (fix: fetch from parent optional in inventory dimension) "sort_order": "DESC", "states": [] } From 450061c7dbf01b3a33104c6dcc4649c29bdcf5f2 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Jun 2025 14:18:40 +0530 Subject: [PATCH 19/41] fix: update payment request outstanding on unreconciliation (cherry picked from commit 8098229b55c982dccc6a9aafc4ac4b3e8650d7cb) # Conflicts: # erpnext/accounts/doctype/payment_request/test_payment_request.py --- .../payment_request/payment_request.py | 3 +- .../payment_request/test_payment_request.py | 30 +++++ erpnext/accounts/utils.py | 107 +++++++++++------- 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 344248b57b6..8bb120f1ab5 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -829,8 +829,7 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False): if not references: return - precision = references[0].precision("allocated_amount") - + precision = frappe.get_precision("Payment Entry Reference", "allocated_amount") referenced_payment_requests = frappe.get_all( "Payment Request", filters={"name": ["in", {row.payment_request for row in references if row.payment_request}]}, diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index c9628bda433..a0f8aaca71a 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -645,6 +645,7 @@ def test_partial_paid_invoice_with_submitted_payment_entry(self): pe.submit() pe.cancel() +<<<<<<< HEAD pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "PURINV0002" pe.reference_date = frappe.utils.nowdate() @@ -656,3 +657,32 @@ def test_partial_paid_invoice_with_submitted_payment_entry(self): pi.load_from_db() pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1) self.assertEqual(pr.grand_total, pi.outstanding_amount) +======= + pi.load_from_db() + pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1) + self.assertEqual(pr.grand_total, pi.outstanding_amount) + + def test_payment_request_on_unreconcile(self): + pi = make_purchase_invoice(currency="INR", qty=1, rate=500) + pi.submit() + + pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1) + self.assertEqual(pr.grand_total, pi.outstanding_amount) + + pe = pr.create_payment_entry() + unreconcile = frappe.get_doc( + { + "doctype": "Unreconcile Payment", + "company": pe.company, + "voucher_type": pe.doctype, + "voucher_no": pe.name, + } + ) + unreconcile.add_references() + unreconcile.submit() + + pi.load_from_db() + pr.load_from_db() + + self.assertEqual(pr.grand_total, pi.outstanding_amount) +>>>>>>> 8098229b55 (fix: update payment request outstanding on unreconciliation) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 56133c036d3..786bfd6f8b7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -997,58 +997,79 @@ def remove_ref_doc_link_from_pe( per = qb.DocType("Payment Entry Reference") pay = qb.DocType("Payment Entry") - linked_pe = ( + query = ( qb.from_(per) - .select(per.parent) - .where((per.reference_doctype == ref_type) & (per.reference_name == ref_no) & (per.docstatus.lt(2))) - .run(as_list=1) - ) - linked_pe = convert_to_list(linked_pe) - # remove reference only from specified payment - linked_pe = [x for x in linked_pe if x == payment_name] if payment_name else linked_pe - - if linked_pe: - update_query = ( - qb.update(per) - .set(per.allocated_amount, 0) - .set(per.modified, now()) - .set(per.modified_by, frappe.session.user) - .where(per.docstatus.lt(2) & (per.reference_doctype == ref_type) & (per.reference_name == ref_no)) + .select("*") + .where( + (per.reference_doctype == ref_type) + & (per.reference_name == ref_no) + & (per.docstatus.lt(2)) + & (per.parenttype == "Payment Entry") ) + ) - if payment_name: - update_query = update_query.where(per.parent == payment_name) + # update reference only from specified payment + if payment_name: + query = query.where(per.parent == payment_name) - update_query.run() + reference_rows = query.run(as_dict=True) - for pe in linked_pe: - try: - pe_doc = frappe.get_doc("Payment Entry", pe) - pe_doc.set_amounts() + if not reference_rows: + return - # Call cancel on only removed reference - references = [ - x - for x in pe_doc.references - if x.reference_doctype == ref_type and x.reference_name == ref_no - ] - [pe_doc.make_advance_gl_entries(x, cancel=1) for x in references] + linked_pe = set() + row_names = set() - pe_doc.clear_unallocated_reference_document_rows() - pe_doc.validate_payment_type_with_outstanding() - except Exception: - msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name) - msg += "
" - msg += _("Please cancel payment entry manually first") - frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error")) + for row in reference_rows: + linked_pe.add(row.parent) + row_names.add(row.name) - qb.update(pay).set(pay.total_allocated_amount, pe_doc.total_allocated_amount).set( - pay.base_total_allocated_amount, pe_doc.base_total_allocated_amount - ).set(pay.unallocated_amount, pe_doc.unallocated_amount).set(pay.modified, now()).set( - pay.modified_by, frappe.session.user - ).where(pay.name == pe).run() + from erpnext.accounts.doctype.payment_request.payment_request import ( + update_payment_requests_as_per_pe_references, + ) - frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe))) + # Update payment request amount + update_payment_requests_as_per_pe_references(reference_rows, cancel=True) + + # Update allocated amounts and modified fields in one go + ( + qb.update(per) + .set(per.allocated_amount, 0) + .set(per.modified, now()) + .set(per.modified_by, frappe.session.user) + .where(per.name.isin(row_names)) + .where(per.parenttype == "Payment Entry") + .run() + ) + + for pe in linked_pe: + try: + pe_doc = frappe.get_doc("Payment Entry", pe) + pe_doc.set_amounts() + + # Call cancel on only removed reference + references = [x for x in pe_doc.references if x.name in row_names] + [pe_doc.make_advance_gl_entries(x, cancel=1) for x in references] + + pe_doc.clear_unallocated_reference_document_rows() + pe_doc.validate_payment_type_with_outstanding() + except Exception: + msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name) + msg += "
" + msg += _("Please cancel payment entry manually first") + frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error")) + + ( + qb.update(pay) + .set(pay.total_allocated_amount, pe_doc.total_allocated_amount) + .set(pay.base_total_allocated_amount, pe_doc.base_total_allocated_amount) + .set(pay.unallocated_amount, pe_doc.unallocated_amount) + .set(pay.modified, now()) + .set(pay.modified_by, frappe.session.user) + .where(pay.name == pe) + .run() + ) + frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe))) @frappe.whitelist() From a8448f9e6060b0938d864bb556d3aadaf550dab4 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Jun 2025 15:04:27 +0530 Subject: [PATCH 20/41] chore: fix test case for payment request (cherry picked from commit 31d12517f03ee6e68c6edb7eca8050e42749795d) --- .../doctype/payment_request/test_payment_request.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index a0f8aaca71a..807e5f5cba1 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -666,7 +666,13 @@ def test_partial_paid_invoice_with_submitted_payment_entry(self): pi = make_purchase_invoice(currency="INR", qty=1, rate=500) pi.submit() - pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1) + pr = make_payment_request( + dt=pi.doctype, + dn=pi.name, + mute_email=1, + submit_doc=True, + return_doc=True, + ) self.assertEqual(pr.grand_total, pi.outstanding_amount) pe = pr.create_payment_entry() From cc5c4a3f9abf6e7564fd52d37fb58895aa7fd1bd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 8 Jul 2025 10:17:43 +0530 Subject: [PATCH 21/41] chore: resolve conflict --- .../payment_request/test_payment_request.py | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 807e5f5cba1..f38c58d6d32 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -630,34 +630,28 @@ class TestPaymentRequest(FrappeTestCase): pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1) self.assertEqual(pr.grand_total, si.outstanding_amount) + def test_partial_paid_invoice_with_submitted_payment_entry(self): + pi = make_purchase_invoice(currency="INR", qty=1, rate=5000) + pi.save() + pi.submit() -def test_partial_paid_invoice_with_submitted_payment_entry(self): - pi = make_purchase_invoice(currency="INR", qty=1, rate=5000) - pi.save() - pi.submit() + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe.reference_no = "PURINV0001" + pe.reference_date = frappe.utils.nowdate() + pe.paid_amount = 2500 + pe.references[0].allocated_amount = 2500 + pe.save() + pe.submit() + pe.cancel() - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") - pe.reference_no = "PURINV0001" - pe.reference_date = frappe.utils.nowdate() - pe.paid_amount = 2500 - pe.references[0].allocated_amount = 2500 - pe.save() - pe.submit() - pe.cancel() + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe.reference_no = "PURINV0002" + pe.reference_date = frappe.utils.nowdate() + pe.paid_amount = 2500 + pe.references[0].allocated_amount = 2500 + pe.save() + pe.submit() -<<<<<<< HEAD - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") - pe.reference_no = "PURINV0002" - pe.reference_date = frappe.utils.nowdate() - pe.paid_amount = 2500 - pe.references[0].allocated_amount = 2500 - pe.save() - pe.submit() - - pi.load_from_db() - pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1) - self.assertEqual(pr.grand_total, pi.outstanding_amount) -======= pi.load_from_db() pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1) self.assertEqual(pr.grand_total, pi.outstanding_amount) @@ -691,4 +685,3 @@ def test_partial_paid_invoice_with_submitted_payment_entry(self): pr.load_from_db() self.assertEqual(pr.grand_total, pi.outstanding_amount) ->>>>>>> 8098229b55 (fix: update payment request outstanding on unreconciliation) From 9a99ccc166b2058e5cd88be28320dd5b86e8574b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 8 Jul 2025 09:56:56 +0530 Subject: [PATCH 22/41] Revert "fix: stock reco qty with inventory dimension (#47918)" This reverts commit 342cebc778ac02cf51870f24a13d667d5f1f9db3. (cherry picked from commit 8ba66c98332a8d23db94d7d9c05dff19e4b9be07) --- .../doctype/stock_reconciliation/stock_reconciliation.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index a7fb532f913..7bee191b56f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -898,10 +898,6 @@ class StockReconciliation(StockController): self.update_inventory_dimensions(row, data) - if self.docstatus == 1 and has_dimensions and (not row.batch_no or not row.serial_and_batch_bundle): - data.qty_after_transaction = data.actual_qty - data.actual_qty = 0.0 - return data def make_sle_on_cancel(self): From a336e19bb88fbd059ce7ecb26980c25f49d0b6e6 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 8 Jul 2025 11:50:43 +0530 Subject: [PATCH 23/41] fix: use default buying price list when price list is falsy (cherry picked from commit 27c73cf9e9cb432cd1755ec6e8439688bae52420) --- erpnext/stock/doctype/material_request/material_request.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 23b42b98b36..8f5d08fe946 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -255,7 +255,9 @@ frappe.ui.form.on("Material Request", { from_warehouse: item.from_warehouse, warehouse: item.warehouse, doctype: frm.doc.doctype, - buying_price_list: frm.doc.price_list, + buying_price_list: frm.doc.price_list + ? frm.doc.price_list + : frappe.defaults.get_default("buying_price_list"), currency: frappe.defaults.get_default("Currency"), name: frm.doc.name, qty: item.qty || 1, From 4545213adc2fa2f3783954a11b289952479e6760 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 1 Jul 2025 11:32:08 +0530 Subject: [PATCH 24/41] fix: valuation rate of raw materials in subcontracting receipt (cherry picked from commit 84ea6afd015ae42543e21caf40e0e5539e44962e) --- erpnext/controllers/subcontracting_controller.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index ba6f604960c..22a71734f3d 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -601,12 +601,15 @@ class SubcontractingController(StockController): rm_obj.use_serial_batch_fields = 1 self.__set_batch_nos(bom_item, item_row, rm_obj, qty) - if self.doctype == "Subcontracting Receipt" and not use_serial_batch_fields: - rm_obj.serial_and_batch_bundle = self.__set_serial_and_batch_bundle( - item_row, rm_obj, rm_obj.consumed_qty - ) + if self.doctype == "Subcontracting Receipt": + if not use_serial_batch_fields: + rm_obj.serial_and_batch_bundle = self.__set_serial_and_batch_bundle( + item_row, rm_obj, rm_obj.consumed_qty + ) - self.set_rate_for_supplied_items(rm_obj, item_row) + self.set_rate_for_supplied_items(rm_obj, item_row) + elif self.backflush_based_on == "BOM": + self.update_rate_for_supplied_items() def update_rate_for_supplied_items(self): if self.doctype != "Subcontracting Receipt": From 3f004db14fbf481cccca59d8a4344dbafec069c1 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Jun 2025 17:35:08 +0530 Subject: [PATCH 25/41] fix: cost center for payment entry against advance payment doctypes in accounts Payable/Receivable report (cherry picked from commit 8f19f1400468d6dc2762b62475b762f57b829f3d) --- .../report/accounts_receivable/accounts_receivable.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e87fa699908..5bf6163fc6a 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -62,6 +62,9 @@ class ReceivablePayableReport: frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method") or "Buffered Cursor" ) # Fail Safe + self.advance_payment_doctypes = frappe.get_hooks( + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") def run(self, args): self.filters.update(args) @@ -181,7 +184,10 @@ class ReceivablePayableReport: if key not in self.voucher_balance: self.voucher_balance[key] = self.build_voucher_dict(ple) - if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no: + if (ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no) or ( + ple.voucher_type in ("Payment Entry", "Journal Entry") + and ple.against_voucher_type in self.advance_payment_doctypes + ): self.voucher_balance[key].cost_center = ple.cost_center self.get_invoices(ple) From 82d03e2617f17fd3cfbbd3f68d3f7e20eb4c2334 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Jun 2025 17:49:56 +0530 Subject: [PATCH 26/41] refactor: function to fetch advance payment doctypes (cherry picked from commit 48e8e85617be17d0a6b812194ab3585140033221) # Conflicts: # erpnext/accounts/doctype/journal_entry/journal_entry.py # erpnext/accounts/doctype/payment_entry/payment_entry.py # erpnext/accounts/doctype/payment_request/payment_request.py # erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py # erpnext/accounts/utils.py # erpnext/controllers/accounts_controller.py --- .../doctype/journal_entry/journal_entry.py | 5 ++ .../doctype/payment_entry/payment_entry.py | 24 ++++++++++ .../payment_request/payment_request.py | 10 +++- .../unreconcile_payment.py | 5 ++ .../accounts_receivable.py | 7 ++- erpnext/accounts/utils.py | 22 +++++++++ erpnext/controllers/accounts_controller.py | 47 +++++++++++++++++++ 7 files changed, 118 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 58c0f23e05e..3e33ad58f34 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -24,6 +24,7 @@ from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, + get_advance_payment_doctypes, get_balance_on, get_stock_accounts, get_stock_and_account_balance, @@ -238,6 +239,10 @@ class JournalEntry(AccountsController): def update_advance_paid(self): advance_paid = frappe._dict() +<<<<<<< HEAD +======= + advance_payment_doctypes = get_advance_payment_doctypes() +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) for d in self.get("accounts"): if d.is_advance: if d.reference_type in frappe.get_hooks("advance_payment_doctypes"): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5c2dc7747bb..f74fafd6c4f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -46,7 +46,11 @@ from erpnext.accounts.party import ( from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, +<<<<<<< HEAD get_balance_on, +======= + get_advance_payment_doctypes, +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) get_outstanding_invoices, ) from erpnext.controllers.accounts_controller import ( @@ -1028,7 +1032,11 @@ class PaymentEntry(AccountsController): def calculate_base_allocated_amount_for_reference(self, d) -> float: base_allocated_amount = 0 +<<<<<<< HEAD if d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"): +======= + if d.reference_doctype in get_advance_payment_doctypes(): +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. # This is so there are no Exchange Gain/Loss generated for such doctypes @@ -1308,8 +1316,12 @@ class PaymentEntry(AccountsController): if not self.party_account: return +<<<<<<< HEAD advance_payment_doctypes = frappe.get_hooks("advance_payment_doctypes") +======= + advance_payment_doctypes = get_advance_payment_doctypes() +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) if self.payment_type == "Receive": against_account = self.paid_to else: @@ -1699,12 +1711,24 @@ class PaymentEntry(AccountsController): return flt(gl_dict.get(field, 0) / (conversion_rate or 1)) def update_advance_paid(self): +<<<<<<< HEAD if self.payment_type in ("Receive", "Pay") and self.party: for d in self.get("references"): if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"): frappe.get_doc( d.reference_doctype, d.reference_name, for_update=True ).set_total_advance_paid() +======= + 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_lazy_doc( + d.reference_doctype, d.reference_name, for_update=True + ).set_total_advance_paid() +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 8bb120f1ab5..b465fd47867 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -16,7 +16,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( ) from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate from erpnext.accounts.party import get_party_account, get_party_bank_account -from erpnext.accounts.utils import get_account_currency, get_currency_precision +from erpnext.accounts.utils import get_account_currency, get_advance_payment_doctypes, get_currency_precision from erpnext.utilities import payment_app_import_guard ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST = [ @@ -473,6 +473,14 @@ class PaymentRequest(Document): return create_stripe_subscription(gateway_controller, data) +<<<<<<< HEAD +======= + def update_reference_advance_payment_status(self): + if self.reference_doctype in get_advance_payment_doctypes(): + ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) + ref_doc.set_advance_payment_status() + +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) def _allocate_payment_request_to_pe_references(self, references): """ Allocate the Payment Request to the Payment Entry references based on\n diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 7612294a85c..fec45fe5642 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -12,6 +12,7 @@ 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, ) @@ -84,7 +85,11 @@ class UnreconcilePayment(Document): update_voucher_outstanding( alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party ) +<<<<<<< HEAD if doc.doctype in frappe.get_hooks("advance_payment_doctypes"): +======= + if doc.doctype in get_advance_payment_doctypes(): +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) doc.set_total_advance_paid() frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 5bf6163fc6a..9b5b91277ef 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -15,7 +15,11 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, get_dimension_with_children, ) -from erpnext.accounts.utils import get_currency_precision, get_party_types_from_account_type +from erpnext.accounts.utils import ( + get_advance_payment_doctypes, + get_currency_precision, + get_party_types_from_account_type, +) # This report gives a summary of all Outstanding Invoices considering the following @@ -88,6 +92,7 @@ class ReceivablePayableReport: self.party_details = {} self.invoices = set() self.skip_total_row = 0 + self.advance_payment_doctypes = get_advance_payment_doctypes() if self.filters.get("group_by_party"): self.previous_party = "" diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 786bfd6f8b7..9b3ac7b1ed6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -629,7 +629,12 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): # Update Advance Paid in SO/PO since they might be getting unlinked update_advance_paid = [] +<<<<<<< HEAD if jv_detail.get("reference_type") in ["Sales Order", "Purchase Order"]: +======= + + if jv_detail.get("reference_type") in get_advance_payment_doctypes(): +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name)) rev_dr_or_cr = ( @@ -736,7 +741,11 @@ def update_reference_in_payment_entry( existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] # Update Advance Paid in SO/PO since they are getting unlinked +<<<<<<< HEAD if existing_row.get("reference_doctype") in ["Sales Order", "Purchase Order"]: +======= + if existing_row.get("reference_doctype") in get_advance_payment_doctypes(): +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name)) if d.allocated_amount <= existing_row.allocated_amount: @@ -2256,6 +2265,19 @@ def get_party_types_from_account_type(account_type): return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name") +def get_advance_payment_doctypes(payment_type=None): + """ + Get list of advance payment doctypes based on type. + :param type: Optional, can be "receivable" or "payable". If not provided, returns both. + """ + if payment_type: + return frappe.get_hooks(f"advance_payment_{payment_type}_doctypes") or [] + + return (frappe.get_hooks("advance_payment_receivable_doctypes") or []) + ( + frappe.get_hooks("advance_payment_payable_doctypes") or [] + ) + + def run_ledger_health_checks(): health_monitor_settings = frappe.get_doc("Ledger Health Monitor") if health_monitor_settings.enable_health_monitor: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cf0707d9efa..1941de5841b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -51,6 +51,9 @@ from erpnext.accounts.utils import ( get_fiscal_years, validate_fiscal_year, ) +from erpnext.accounts.utils import ( + get_advance_payment_doctypes as _get_advance_payment_doctypes, +) from erpnext.buying.utils import update_last_purchase_rate from erpnext.controllers.print_settings import ( set_print_templates_for_item_table, @@ -386,9 +389,13 @@ class AccountsController(TransactionBase): adv = qb.DocType("Advance Payment Ledger Entry") qb.from_(adv).delete().where(adv.voucher_type.eq(self.doctype) & adv.voucher_no.eq(self.name)).run() +<<<<<<< HEAD advance_payment_doctypes = frappe.get_hooks("advance_payment_doctypes") if self.doctype in advance_payment_doctypes: +======= + if self.doctype in self.get_advance_payment_doctypes(): +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) qb.from_(adv).delete().where( adv.against_voucher_type.eq(self.doctype) & adv.against_voucher_no.eq(self.name) ).run() @@ -2272,6 +2279,41 @@ class AccountsController(TransactionBase): self.db_set("advance_paid", advance_paid) +<<<<<<< HEAD +======= + self.set_advance_payment_status() + + def set_advance_payment_status(self): + new_status = None + + PaymentRequest = frappe.qb.DocType("Payment Request") + paid_amount = frappe.get_value( + doctype="Payment Request", + filters={ + "reference_doctype": self.doctype, + "reference_name": self.name, + "docstatus": 1, + }, + fieldname=Sum(PaymentRequest.grand_total - PaymentRequest.outstanding_amount), + ) + + if not paid_amount: + if self.doctype in self.get_advance_payment_doctypes(payment_type="receivable"): + new_status = "Not Requested" if paid_amount is None else "Requested" + elif self.doctype in self.get_advance_payment_doctypes(payment_type="payable"): + new_status = "Not Initiated" if paid_amount is None else "Initiated" + else: + total_amount = self.get("rounded_total") or self.get("grand_total") + new_status = "Fully Paid" if paid_amount == total_amount else "Partially Paid" + + if new_status == self.advance_payment_status: + return + + self.db_set("advance_payment_status", new_status, update_modified=False) + self.set_status(update=True) + self.notify_update() + +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) @property def company_abbr(self): if not hasattr(self, "_abbr"): @@ -2911,8 +2953,13 @@ class AccountsController(TransactionBase): repost_ledger.insert() repost_ledger.submit() +<<<<<<< HEAD def get_advance_payment_doctypes(self) -> list: return frappe.get_hooks("advance_payment_doctypes") +======= + def get_advance_payment_doctypes(self, payment_type=None) -> list: + return _get_advance_payment_doctypes(payment_type=payment_type) +>>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) def make_advance_payment_ledger_for_journal(self): advance_payment_doctypes = self.get_advance_payment_doctypes() From a10e3948b2e696ce68e5ebc90675aa81f82e5b48 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Fri, 4 Jul 2025 19:57:22 +0530 Subject: [PATCH 27/41] fix: add selling price validation on update item (cherry picked from commit 327d067305ca6cbafd0785c8605f4f515a1d6d5e) --- erpnext/controllers/accounts_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cf0707d9efa..5466c9e0e72 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3965,6 +3965,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ).format(frappe.bold(parent.name)) ) else: # Sales Order + parent.validate_selling_price() parent.validate_for_duplicate_items() parent.validate_warehouse() parent.update_reserved_qty() From b2de9cdef21da2c7cecaae072d892d2e325320d0 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Thu, 3 Jul 2025 16:04:19 +0530 Subject: [PATCH 28/41] fix: consider empty string in previous doc validation (cherry picked from commit dd43594ad617005463cc9deea8036d1a24efbc81) --- erpnext/utilities/transaction_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index b7283529772..7e40fd681eb 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -65,7 +65,7 @@ class TransactionBase(StatusUpdater): frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name)) for field, condition in fields: - if prevdoc_values[field] is not None and field not in self.exclude_fields: + if prevdoc_values[field] not in [None, ""] and field not in self.exclude_fields: self.validate_value(field, condition, prevdoc_values[field], doc) def get_prev_doc_reference_details(self, reference_names, reference_doctype, fields): From 2ab1b42033c55aa5b3e5ca533d0b4ce3f5bf59b7 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Sun, 6 Jul 2025 15:03:06 +0530 Subject: [PATCH 29/41] refactor: remove duplicate reconciliation date logic (cherry picked from commit 398406082a373bb0dda00684a2ec4a85a9f8974f) --- .../doctype/payment_entry/payment_entry.py | 19 +-------- erpnext/accounts/utils.py | 39 +++++++++++-------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5c2dc7747bb..d4e6199ab4d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -48,6 +48,7 @@ from erpnext.accounts.utils import ( get_account_currency, get_balance_on, get_outstanding_invoices, + get_reconciliation_effect_date, ) from erpnext.controllers.accounts_controller import ( AccountsController, @@ -1492,23 +1493,7 @@ class PaymentEntry(AccountsController): else: # For backwards compatibility # Supporting reposting on payment entries reconciled before select field introduction - reconciliation_takes_effect_on = frappe.get_cached_value( - "Company", self.company, "reconciliation_takes_effect_on" - ) - if reconciliation_takes_effect_on == "Advance Payment Date": - posting_date = self.posting_date - elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": - date_field = "posting_date" - if invoice.reference_doctype in ["Sales Order", "Purchase Order"]: - date_field = "transaction_date" - posting_date = frappe.db.get_value( - invoice.reference_doctype, invoice.reference_name, date_field - ) - - if getdate(posting_date) < getdate(self.posting_date): - posting_date = self.posting_date - elif reconciliation_takes_effect_on == "Reconciliation Date": - posting_date = nowdate() + posting_date = get_reconciliation_effect_date(invoice, self.company, self.posting_date) frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date) dr_or_cr, account = self.get_dr_and_account_for_advances(invoice) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 786bfd6f8b7..47ade441e46 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -713,23 +713,8 @@ def update_reference_in_payment_entry( update_advance_paid = [] # Update Reconciliation effect date in reference - reconciliation_takes_effect_on = frappe.get_cached_value( - "Company", payment_entry.company, "reconciliation_takes_effect_on" - ) if payment_entry.book_advance_payments_in_separate_party_account: - if reconciliation_takes_effect_on == "Advance Payment Date": - reconcile_on = payment_entry.posting_date - elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": - date_field = "posting_date" - if d.against_voucher_type in ["Sales Order", "Purchase Order"]: - date_field = "transaction_date" - reconcile_on = frappe.db.get_value(d.against_voucher_type, d.against_voucher, date_field) - - if getdate(reconcile_on) < getdate(payment_entry.posting_date): - reconcile_on = payment_entry.posting_date - elif reconciliation_takes_effect_on == "Reconciliation Date": - reconcile_on = nowdate() - + reconcile_on = get_reconciliation_effect_date(d, payment_entry.company, payment_entry.posting_date) reference_details.update({"reconcile_effect_on": reconcile_on}) if d.voucher_detail_no: @@ -783,6 +768,28 @@ def update_reference_in_payment_entry( return row, update_advance_paid +def get_reconciliation_effect_date(reference, company, posting_date): + reconciliation_takes_effect_on = frappe.get_cached_value( + "Company", company, "reconciliation_takes_effect_on" + ) + + if reconciliation_takes_effect_on == "Advance Payment Date": + reconcile_on = posting_date + elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance": + date_field = "posting_date" + if reference.against_voucher_type in ["Sales Order", "Purchase Order"]: + date_field = "transaction_date" + reconcile_on = frappe.db.get_value( + reference.against_voucher_type, reference.against_voucher, date_field + ) + if getdate(reconcile_on) < getdate(posting_date): + reconcile_on = posting_date + elif reconciliation_takes_effect_on == "Reconciliation Date": + reconcile_on = nowdate() + + return reconcile_on + + def cancel_exchange_gain_loss_journal( parent_doc: dict | object, referenced_dt: str | None = None, referenced_dn: str | None = None ) -> None: From 00669661e50b78ce34c8fbc1355baa4beecd6217 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 8 Jul 2025 13:19:03 +0530 Subject: [PATCH 30/41] chore: resolve conflicts --- .../doctype/journal_entry/journal_entry.py | 5 +- .../doctype/payment_entry/payment_entry.py | 25 +--------- .../payment_request/payment_request.py | 10 +--- .../unreconcile_payment.py | 4 -- .../accounts_receivable.py | 4 +- erpnext/accounts/utils.py | 18 ++----- erpnext/controllers/accounts_controller.py | 48 +------------------ 7 files changed, 9 insertions(+), 105 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 3e33ad58f34..6c7fb5f5744 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -239,13 +239,10 @@ class JournalEntry(AccountsController): def update_advance_paid(self): advance_paid = frappe._dict() -<<<<<<< HEAD -======= advance_payment_doctypes = get_advance_payment_doctypes() ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) for d in self.get("accounts"): if d.is_advance: - if d.reference_type in frappe.get_hooks("advance_payment_doctypes"): + 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(): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f74fafd6c4f..739eda70cbb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -46,11 +46,8 @@ from erpnext.accounts.party import ( from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, -<<<<<<< HEAD - get_balance_on, -======= get_advance_payment_doctypes, ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) + get_balance_on, get_outstanding_invoices, ) from erpnext.controllers.accounts_controller import ( @@ -1032,11 +1029,7 @@ class PaymentEntry(AccountsController): def calculate_base_allocated_amount_for_reference(self, d) -> float: base_allocated_amount = 0 -<<<<<<< HEAD - if d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"): -======= if d.reference_doctype in get_advance_payment_doctypes(): ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. # This is so there are no Exchange Gain/Loss generated for such doctypes @@ -1316,12 +1309,7 @@ class PaymentEntry(AccountsController): if not self.party_account: return -<<<<<<< HEAD - advance_payment_doctypes = frappe.get_hooks("advance_payment_doctypes") - -======= advance_payment_doctypes = get_advance_payment_doctypes() ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) if self.payment_type == "Receive": against_account = self.paid_to else: @@ -1711,24 +1699,15 @@ class PaymentEntry(AccountsController): return flt(gl_dict.get(field, 0) / (conversion_rate or 1)) def update_advance_paid(self): -<<<<<<< HEAD - if self.payment_type in ("Receive", "Pay") and self.party: - for d in self.get("references"): - if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"): - frappe.get_doc( - d.reference_doctype, d.reference_name, for_update=True - ).set_total_advance_paid() -======= 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_lazy_doc( + frappe.get_doc( d.reference_doctype, d.reference_name, for_update=True ).set_total_advance_paid() ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index b465fd47867..8bb120f1ab5 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -16,7 +16,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( ) from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate from erpnext.accounts.party import get_party_account, get_party_bank_account -from erpnext.accounts.utils import get_account_currency, get_advance_payment_doctypes, get_currency_precision +from erpnext.accounts.utils import get_account_currency, get_currency_precision from erpnext.utilities import payment_app_import_guard ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST = [ @@ -473,14 +473,6 @@ class PaymentRequest(Document): return create_stripe_subscription(gateway_controller, data) -<<<<<<< HEAD -======= - def update_reference_advance_payment_status(self): - if self.reference_doctype in get_advance_payment_doctypes(): - ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) - ref_doc.set_advance_payment_status() - ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) def _allocate_payment_request_to_pe_references(self, references): """ Allocate the Payment Request to the Payment Entry references based on\n diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index fec45fe5642..e57b90f11f7 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -85,11 +85,7 @@ class UnreconcilePayment(Document): update_voucher_outstanding( alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party ) -<<<<<<< HEAD - if doc.doctype in frappe.get_hooks("advance_payment_doctypes"): -======= if doc.doctype in get_advance_payment_doctypes(): ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) doc.set_total_advance_paid() frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 9b5b91277ef..a2237ea9ac2 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -66,9 +66,7 @@ class ReceivablePayableReport: frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method") or "Buffered Cursor" ) # Fail Safe - self.advance_payment_doctypes = frappe.get_hooks( - "advance_payment_receivable_doctypes" - ) + frappe.get_hooks("advance_payment_payable_doctypes") + self.advance_payment_doctypes = get_advance_payment_doctypes() def run(self, args): self.filters.update(args) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9b3ac7b1ed6..2acd83dcc4f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -629,12 +629,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): # Update Advance Paid in SO/PO since they might be getting unlinked update_advance_paid = [] -<<<<<<< HEAD - if jv_detail.get("reference_type") in ["Sales Order", "Purchase Order"]: -======= - if jv_detail.get("reference_type") in get_advance_payment_doctypes(): ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) + 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 = ( @@ -741,11 +737,7 @@ def update_reference_in_payment_entry( existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] # Update Advance Paid in SO/PO since they are getting unlinked -<<<<<<< HEAD if existing_row.get("reference_doctype") in ["Sales Order", "Purchase Order"]: -======= - if existing_row.get("reference_doctype") in get_advance_payment_doctypes(): ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name)) if d.allocated_amount <= existing_row.allocated_amount: @@ -2265,17 +2257,13 @@ def get_party_types_from_account_type(account_type): return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name") -def get_advance_payment_doctypes(payment_type=None): +def get_advance_payment_doctypes(): """ Get list of advance payment doctypes based on type. :param type: Optional, can be "receivable" or "payable". If not provided, returns both. """ - if payment_type: - return frappe.get_hooks(f"advance_payment_{payment_type}_doctypes") or [] - return (frappe.get_hooks("advance_payment_receivable_doctypes") or []) + ( - frappe.get_hooks("advance_payment_payable_doctypes") or [] - ) + return frappe.get_hooks("advance_payment_doctypes") def run_ledger_health_checks(): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1941de5841b..d4a3facbacc 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -389,13 +389,7 @@ class AccountsController(TransactionBase): adv = qb.DocType("Advance Payment Ledger Entry") qb.from_(adv).delete().where(adv.voucher_type.eq(self.doctype) & adv.voucher_no.eq(self.name)).run() -<<<<<<< HEAD - advance_payment_doctypes = frappe.get_hooks("advance_payment_doctypes") - - if self.doctype in advance_payment_doctypes: -======= if self.doctype in self.get_advance_payment_doctypes(): ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) qb.from_(adv).delete().where( adv.against_voucher_type.eq(self.doctype) & adv.against_voucher_no.eq(self.name) ).run() @@ -2279,41 +2273,6 @@ class AccountsController(TransactionBase): self.db_set("advance_paid", advance_paid) -<<<<<<< HEAD -======= - self.set_advance_payment_status() - - def set_advance_payment_status(self): - new_status = None - - PaymentRequest = frappe.qb.DocType("Payment Request") - paid_amount = frappe.get_value( - doctype="Payment Request", - filters={ - "reference_doctype": self.doctype, - "reference_name": self.name, - "docstatus": 1, - }, - fieldname=Sum(PaymentRequest.grand_total - PaymentRequest.outstanding_amount), - ) - - if not paid_amount: - if self.doctype in self.get_advance_payment_doctypes(payment_type="receivable"): - new_status = "Not Requested" if paid_amount is None else "Requested" - elif self.doctype in self.get_advance_payment_doctypes(payment_type="payable"): - new_status = "Not Initiated" if paid_amount is None else "Initiated" - else: - total_amount = self.get("rounded_total") or self.get("grand_total") - new_status = "Fully Paid" if paid_amount == total_amount else "Partially Paid" - - if new_status == self.advance_payment_status: - return - - self.db_set("advance_payment_status", new_status, update_modified=False) - self.set_status(update=True) - self.notify_update() - ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) @property def company_abbr(self): if not hasattr(self, "_abbr"): @@ -2953,13 +2912,8 @@ class AccountsController(TransactionBase): repost_ledger.insert() repost_ledger.submit() -<<<<<<< HEAD def get_advance_payment_doctypes(self) -> list: - return frappe.get_hooks("advance_payment_doctypes") -======= - def get_advance_payment_doctypes(self, payment_type=None) -> list: - return _get_advance_payment_doctypes(payment_type=payment_type) ->>>>>>> 48e8e85617 (refactor: function to fetch advance payment doctypes) + return _get_advance_payment_doctypes() def make_advance_payment_ledger_for_journal(self): advance_payment_doctypes = self.get_advance_payment_doctypes() From f2d644ba298383cf3be54249bdc520ca7f8824fd Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Jun 2025 19:12:49 +0530 Subject: [PATCH 31/41] fix: update condition for blank tree fields in pricing rule (cherry picked from commit 7e0e9db4d28e0cf3fa428f29761420c05abe9012) --- .../doctype/pricing_rule/test_pricing_rule.py | 50 +++++++++++++++++++ .../accounts/doctype/pricing_rule/utils.py | 4 ++ 2 files changed, 54 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 3ad3d45ee47..123c17f9b75 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -205,6 +205,56 @@ class TestPricingRule(FrappeTestCase): details = get_item_details(args) self.assertEqual(details.get("discount_percentage"), 10) + def test_unset_group_condition(self): + """ + If args are not set for group condition, then pricing rule should not be applied. + """ + from erpnext.stock.get_item_details import get_item_details + + test_record = { + "doctype": "Pricing Rule", + "title": "_Test Pricing Rule", + "apply_on": "Item Code", + "items": [{"item_code": "_Test Item"}], + "currency": "USD", + "selling": 1, + "rate_or_discount": "Discount Percentage", + "rate": 0, + "discount_percentage": 10, + "applicable_for": "Territory", + "territory": "All Territories", + "company": "_Test Company", + } + frappe.get_doc(test_record.copy()).insert() + args = frappe._dict( + { + "item_code": "_Test Item", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Sales Order", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "order_type": "Sales", + "customer": "_Test Customer", + "name": None, + } + ) + + # without territory in customer + customer = frappe.get_doc("Customer", "_Test Customer") + territory = customer.territory + + customer.territory = None + customer.save() + + details = get_item_details(args) + self.assertEqual(details.get("discount_percentage"), 0) + + customer.territory = territory + customer.save() + def test_pricing_rule_for_variants(self): from erpnext.stock.get_item_details import get_item_details diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index bbf73e80809..de63ec3a8c4 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -223,6 +223,10 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): ) frappe.flags.tree_conditions[key] = condition + + elif allow_blank: + condition = f"ifnull({table}.{field}, '') = ''" + return condition From d0d1d63d31aa76748ee94d99ed0860e2be186a76 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Thu, 3 Jul 2025 16:03:21 +0530 Subject: [PATCH 32/41] revert: do not convert exchange gain/loss amount to foreign currency (cherry picked from commit c17ae703c7ff9143289c39181f5b4a05b047208b) # Conflicts: # erpnext/accounts/report/general_ledger/test_general_ledger.py --- .../general_ledger/test_general_ledger.py | 87 ------------------- erpnext/accounts/report/utils.py | 6 +- 2 files changed, 1 insertion(+), 92 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index e1ce1bd35a2..d2a7e8256b3 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -6,12 +6,9 @@ from frappe import qb from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import flt, today -from erpnext.accounts.doctype.account.test_account import create_account -from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.general_ledger.general_ledger import execute from erpnext.controllers.sales_and_purchase_return import make_return_doc -from erpnext.selling.doctype.customer.test_customer import create_internal_customer class TestGeneralLedger(FrappeTestCase): @@ -171,90 +168,6 @@ class TestGeneralLedger(FrappeTestCase): self.assertEqual(data[3]["debit"], 100) self.assertEqual(data[3]["credit"], 100) - @change_settings("Accounts Settings", {"delete_linked_ledger_entries": True}) - def test_debit_in_exchange_gain_loss_account(self): - company = "_Test Company" - - exchange_gain_loss_account = frappe.db.get_value("Company", "exchange_gain_loss_account") - if not exchange_gain_loss_account: - frappe.db.set_value( - "Company", company, "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC" - ) - - account_name = "_Test Receivable USD - _TC" - customer_name = "_Test Customer USD" - - sales_invoice = create_sales_invoice( - company=company, - customer=customer_name, - currency="USD", - debit_to=account_name, - conversion_rate=85, - posting_date=today(), - ) - - payment_entry = create_payment_entry( - company=company, - party_type="Customer", - party=customer_name, - payment_type="Receive", - paid_from=account_name, - paid_from_account_currency="USD", - paid_to="Cash - _TC", - paid_to_account_currency="INR", - paid_amount=10, - do_not_submit=True, - ) - payment_entry.base_paid_amount = 800 - payment_entry.received_amount = 800 - payment_entry.currency = "USD" - payment_entry.source_exchange_rate = 80 - payment_entry.append( - "references", - frappe._dict( - { - "reference_doctype": "Sales Invoice", - "reference_name": sales_invoice.name, - "total_amount": 10, - "outstanding_amount": 10, - "exchange_rate": 85, - "allocated_amount": 10, - "exchange_gain_loss": -50, - } - ), - ) - payment_entry.save() - payment_entry.submit() - - journal_entry = frappe.get_all( - "Journal Entry Account", filters={"reference_name": sales_invoice.name}, fields=["parent"] - ) - - columns, data = execute( - frappe._dict( - { - "company": company, - "from_date": today(), - "to_date": today(), - "include_dimensions": 1, - "include_default_book_entries": 1, - "account": ["_Test Exchange Gain/Loss - _TC"], - "categorize_by": "Categorize by Voucher (Consolidated)", - } - ) - ) - - entry = data[1] - self.assertEqual(entry["debit"], 50) - self.assertEqual(entry["voucher_type"], "Journal Entry") - self.assertEqual(entry["voucher_no"], journal_entry[0]["parent"]) - - payment_entry.cancel() - payment_entry.delete() - sales_invoice.reload() - sales_invoice.cancel() - sales_invoice.delete() - def test_ignore_exchange_rate_journals_filter(self): # create a new account with USD currency account_name = "Test Debtors USD" diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 02ba54604c4..2a72b10e4eb 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -107,11 +107,7 @@ def convert_to_presentation_currency(gl_entries, currency_info): credit_in_account_currency = flt(entry["credit_in_account_currency"]) account_currency = entry["account_currency"] - if ( - len(account_currencies) == 1 - and account_currency == presentation_currency - and (debit_in_account_currency or credit_in_account_currency) - ): + if len(account_currencies) == 1 and account_currency == presentation_currency: entry["debit"] = debit_in_account_currency entry["credit"] = credit_in_account_currency else: From 818ddc0b8c93b6ff31b7ef471a80504c526ac668 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 8 Jul 2025 13:27:10 +0530 Subject: [PATCH 33/41] fix: duplicate items being created when fetching items from warehouse in stock reco (cherry picked from commit 73f6c2955934c2282739438ccdd2c1cc954f2407) --- .../doctype/stock_reconciliation/stock_reconciliation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 7bee191b56f..fee71460ee5 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -1262,12 +1262,12 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None, ig itemwise_batch_data = get_itemwise_batch(warehouse, posting_date, company, item_code) for d in items: - if d.item_code in itemwise_batch_data: + if (d.item_code, d.warehouse) in itemwise_batch_data: valuation_rate = get_stock_balance( d.item_code, d.warehouse, posting_date, posting_time, with_valuation_rate=True )[1] - for row in itemwise_batch_data.get(d.item_code): + for row in itemwise_batch_data.get((d.item_code, d.warehouse)): if ignore_empty_stock and not row.qty: continue @@ -1399,7 +1399,7 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): columns, data = execute(filters) for row in data: - itemwise_batch_data.setdefault(row[0], []).append( + itemwise_batch_data.setdefault((row[0], row[3]), []).append( frappe._dict( { "item_code": row[0], From 500972e8aa455108037b8f35ad536acb70bbad10 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:05:35 +0530 Subject: [PATCH 34/41] refactor: remove do_reposting_for_each_stock_transaction feature (backport #48444) (#48452) * refactor: remove do_reposting_for_each_stock_transaction feature Co-authored-by: Mihir Kandoi --- erpnext/controllers/stock_controller.py | 7 +-- erpnext/stock/doctype/bin/bin.py | 2 +- .../stock_reposting_settings.json | 9 +--- .../stock_reposting_settings.py | 5 -- .../test_stock_reposting_settings.py | 48 ------------------- 5 files changed, 3 insertions(+), 68 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 667e07ab9ff..3ad596d71bd 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1662,14 +1662,9 @@ def is_reposting_pending(): ) -def future_sle_exists(args, sl_entries=None, allow_force_reposting=True): +def future_sle_exists(args, sl_entries=None): from erpnext.stock.utils import get_combine_datetime - if allow_force_reposting and frappe.db.get_single_value( - "Stock Reposting Settings", "do_reposting_for_each_stock_transaction" - ): - return True - key = (args.voucher_type, args.voucher_no) if not hasattr(frappe.local, "future_sle"): frappe.local.future_sle = {} diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 338fd863ffc..79e9776b91c 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -264,7 +264,7 @@ def update_qty(bin_name, args): actual_qty = bin_details.actual_qty or 0.0 # actual qty is not up to date in case of backdated transaction - if future_sle_exists(args, allow_force_reposting=False): + if future_sle_exists(args): actual_qty = get_actual_qty(args.get("item_code"), args.get("warehouse")) ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty")) diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json index cbbb0ce0990..3137bedfee4 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json @@ -13,7 +13,6 @@ "end_time", "limits_dont_apply_on", "item_based_reposting", - "do_reposting_for_each_stock_transaction", "errors_notification_section", "notify_reposting_error_to_role" ], @@ -66,18 +65,12 @@ "fieldname": "errors_notification_section", "fieldtype": "Section Break", "label": "Errors Notification" - }, - { - "default": "0", - "fieldname": "do_reposting_for_each_stock_transaction", - "fieldtype": "Check", - "label": "Do reposting for each Stock Transaction" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-04-24 12:19:40.204888", + "modified": "2025-07-08 11:27:46.659056", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reposting Settings", diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py index eb3d38bfbfc..50f39817fff 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py @@ -16,7 +16,6 @@ class StockRepostingSettings(Document): if TYPE_CHECKING: from frappe.types import DF - do_reposting_for_each_stock_transaction: DF.Check end_time: DF.Time | None item_based_reposting: DF.Check limit_reposting_timeslot: DF.Check @@ -30,10 +29,6 @@ class StockRepostingSettings(Document): def validate(self): self.set_minimum_reposting_time_slot() - def before_save(self): - if self.do_reposting_for_each_stock_transaction: - self.item_based_reposting = 1 - def set_minimum_reposting_time_slot(self): """Ensure that timeslot for reposting is at least 12 hours.""" if not self.limit_reposting_timeslot: diff --git a/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py index e53659c1735..a6dc72d7a42 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py +++ b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py @@ -38,51 +38,3 @@ class TestStockRepostingSettings(unittest.TestCase): users = get_recipients() self.assertTrue(user in users) - - def test_do_reposting_for_each_stock_transaction(self): - from erpnext.stock.doctype.item.test_item import make_item - from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - - frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 1) - if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"): - frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0) - - item = make_item( - "_Test item for reposting check for each transaction", properties={"is_stock_item": 1} - ).name - - stock_entry = make_stock_entry( - item_code=item, - qty=1, - rate=100, - stock_entry_type="Material Receipt", - target="_Test Warehouse - _TC", - ) - - riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name") - self.assertTrue(riv) - - frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0) - - def test_do_not_reposting_for_each_stock_transaction(self): - from erpnext.stock.doctype.item.test_item import make_item - from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - - frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0) - if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"): - frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0) - - item = make_item( - "_Test item for do not reposting check for each transaction", properties={"is_stock_item": 1} - ).name - - stock_entry = make_stock_entry( - item_code=item, - qty=1, - rate=100, - stock_entry_type="Material Receipt", - target="_Test Warehouse - _TC", - ) - - riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name") - self.assertFalse(riv) From b7b5f6acf3e833a93e86368001b3bb78a752a4cd Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 4 Jul 2025 19:20:50 +0530 Subject: [PATCH 35/41] fix: rename journal entry title on update (cherry picked from commit acb9829159c1519f6f28b2cf1314d6f4409572be) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 6c7fb5f5744..1f7c725aad7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -147,8 +147,8 @@ class JournalEntry(AccountsController): if self.docstatus == 0: self.apply_tax_withholding() - - self.title = self.get_title() + if self.is_new() or not self.title: + self.title = self.get_title() def validate_advance_accounts(self): journal_accounts = set([x.account for x in self.accounts]) @@ -1045,6 +1045,7 @@ class JournalEntry(AccountsController): def set_print_format_fields(self): bank_amount = party_amount = total_amount = 0.0 currency = bank_account_currency = party_account_currency = pay_to_recd_from = None + self.pay_to_recd_from = pay_to_recd_from party_type = None for d in self.get("accounts"): if d.party_type in ["Customer", "Supplier"] and d.party: From f81dba638016b6e7d15a897f3a39684cb6888caa Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 4 Jul 2025 20:03:25 +0530 Subject: [PATCH 36/41] chore: add none value (cherry picked from commit 9e633bddef216b49acefbd928f61610d45936072) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 1f7c725aad7..d05e9e3b2d1 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1044,8 +1044,9 @@ class JournalEntry(AccountsController): def set_print_format_fields(self): bank_amount = party_amount = total_amount = 0.0 - currency = bank_account_currency = party_account_currency = pay_to_recd_from = None - self.pay_to_recd_from = pay_to_recd_from + currency = ( + bank_account_currency + ) = party_account_currency = pay_to_recd_from = self.pay_to_recd_from = None party_type = None for d in self.get("accounts"): if d.party_type in ["Customer", "Supplier"] and d.party: From 177b23c624cf33ee3d9d606bd47e528002ce2130 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Fri, 4 Jul 2025 12:23:06 +0530 Subject: [PATCH 37/41] fix: get fiscal year based on date (cherry picked from commit efb8e7c0e4bcf1879d2ceb9d7fc214eebdfabc29) --- .../accounts/report/financial_statements.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 0d1e185382d..71e541692f8 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -9,7 +9,7 @@ import re import frappe from frappe import _ -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Max, Min, Sum from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate from pypika.terms import ExistsCriterion @@ -109,14 +109,19 @@ def get_period_list( def get_fiscal_year_data(from_fiscal_year, to_fiscal_year): - fiscal_year = frappe.db.sql( - """select min(year_start_date) as year_start_date, - max(year_end_date) as year_end_date from `tabFiscal Year` where - name between %(from_fiscal_year)s and %(to_fiscal_year)s""", - {"from_fiscal_year": from_fiscal_year, "to_fiscal_year": to_fiscal_year}, - as_dict=1, + from_year_start_date = frappe.get_cached_value("Fiscal Year", from_fiscal_year, "year_start_date") + to_year_end_date = frappe.get_cached_value("Fiscal Year", to_fiscal_year, "year_end_date") + + fy = frappe.qb.DocType("Fiscal Year") + + query = ( + frappe.qb.from_(fy) + .select(Min(fy.year_start_date).as_("year_start_date"), Max(fy.year_end_date).as_("year_end_date")) + .where(fy.year_start_date >= from_year_start_date) + .where(fy.year_end_date <= to_year_end_date) ) + fiscal_year = query.run(as_dict=True) return fiscal_year[0] if fiscal_year else {} From 536264896e5815c55b981189e16456207d6a2020 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 7 Jul 2025 14:43:04 +0530 Subject: [PATCH 38/41] fix: Add company validation to company related fields in Process Statement Of Accounts (cherry picked from commit 4e45e692476405eb312988edcb88b48b61103eef) --- .../process_statement_of_accounts.js | 14 +++++++ .../process_statement_of_accounts.py | 41 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js index 40534059711..57d0c59329c 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js @@ -54,6 +54,9 @@ frappe.ui.form.on("Process Statement Of Accounts", { }; }); frm.set_query("account", function () { + if (!frm.doc.company) { + frappe.throw(__("Please set Company")); + } return { filters: { company: frm.doc.company, @@ -61,6 +64,9 @@ frappe.ui.form.on("Process Statement Of Accounts", { }; }); frm.set_query("cost_center", function () { + if (!frm.doc.company) { + frappe.throw(__("Please set Company")); + } return { filters: { company: frm.doc.company, @@ -68,6 +74,9 @@ frappe.ui.form.on("Process Statement Of Accounts", { }; }); frm.set_query("project", function () { + if (!frm.doc.company) { + frappe.throw(__("Please set Company")); + } return { filters: { company: frm.doc.company, @@ -79,6 +88,11 @@ frappe.ui.form.on("Process Statement Of Accounts", { frm.set_value("to_date", frappe.datetime.get_today()); } }, + company: function (frm) { + frm.set_value("account", ""); + frm.set_value("cost_center", ""); + frm.set_value("project", ""); + }, report: function (frm) { let filters = { company: frm.doc.company, diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 714ed623796..bbf59aa7f02 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -82,6 +82,10 @@ class ProcessStatementOfAccounts(Document): # end: auto-generated types def validate(self): + self.validate_account() + self.validate_company_for_table("Cost Center") + self.validate_company_for_table("Project") + if not self.subject: self.subject = "Statement Of Accounts for {{ customer.customer_name }}" if not self.body: @@ -104,6 +108,43 @@ class ProcessStatementOfAccounts(Document): self.to_date = self.start_date self.from_date = add_months(self.to_date, -1 * self.filter_duration) + def validate_account(self): + if not self.account: + return + + if self.company != frappe.get_cached_value("Account", self.account, "company"): + frappe.throw( + _("Account {0} doesn't belong to Company {1}").format( + frappe.bold(self.account), + frappe.bold(self.company), + ) + ) + + def validate_company_for_table(self, doctype): + field = frappe.scrub(doctype) + if not self.get(field): + return + + fieldname = field + "_name" + + values = set(d.get(fieldname) for d in self.get(field)) + invalid_values = frappe.db.get_all( + doctype, filters={"name": ["in", values], "company": ["!=", self.company]}, pluck="name" + ) + + if invalid_values: + msg = _("

Following {0}s doesn't belong to Company {1} :

").format( + doctype, frappe.bold(self.company) + ) + + msg += ( + "
    " + + "".join(_("
  • {}
  • ").format(frappe.bold(row)) for row in invalid_values) + + "
" + ) + + frappe.throw(_(msg)) + def get_report_pdf(doc, consolidated=True): statement_dict = get_statement_dict(doc) From 5f63794739442a8155a25cb6427ec0c3cb17e3f1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 8 Jul 2025 16:50:45 +0530 Subject: [PATCH 39/41] chore: better label for checkbox (cherry picked from commit 8c2e40e2919f4ddb902f4514cd078ad84b6f238f) --- .../process_statement_of_accounts.json | 4 ++-- erpnext/accounts/report/general_ledger/general_ledger.js | 2 +- 2 files changed, 3 insertions(+), 3 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 4041a8ecad7..f5779cff84f 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 @@ -376,7 +376,7 @@ "default": "0", "fieldname": "ignore_exchange_rate_revaluation_journals", "fieldtype": "Check", - "label": "Ignore Exchange Rate Revaluation Journals" + "label": "Ignore Exchange Rate Revaluation and Gain / Loss Journals" }, { "default": "0", @@ -400,7 +400,7 @@ } ], "links": [], - "modified": "2025-04-30 14:43:23.643006", + "modified": "2025-07-08 16:52:12.602384", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 4039ea27ebd..f27441bc3e2 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -209,7 +209,7 @@ frappe.query_reports["General Ledger"] = { }, { fieldname: "ignore_err", - label: __("Ignore Exchange Rate Revaluation Journals"), + label: __("Ignore Exchange Rate Revaluation and Gain / Loss Journals"), fieldtype: "Check", }, { From 8371dafb1a4e11f5f2210d496f92490f65bb6ea9 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Thu, 26 Jun 2025 18:32:54 +0530 Subject: [PATCH 40/41] fix: add not specified key for None respresented customer_group and territory (cherry picked from commit 24cc711a70607f4699e3ce4230d7a87d7b5d762c) # Conflicts: # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py --- .../item_wise_sales_register/item_wise_sales_register.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index db93c0ef325..73d8c3f7d14 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -5,13 +5,14 @@ import frappe from frappe import _ from frappe.model.meta import get_field_precision +from frappe.query_builder import functions as fn from frappe.utils import cstr, flt from frappe.utils.nestedset import get_descendants_of from frappe.utils.xlsxutils import handle_html from pypika import Order from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments -from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns +from erpnext.accounts.report.utils import get_values_for_columns from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import ( get_customer_details, ) @@ -433,7 +434,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None): si.is_internal_customer, si.customer, si.remarks, - si.territory, + fn.IfNull(si.territory, "Not Specified").as_("territory"), si.company, si.base_net_total, sii.project, @@ -456,7 +457,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None): sii.base_net_rate, sii.base_net_amount, si.customer_name, - si.customer_group, + fn.IfNull(si.customer_group, "Not Specified").as_("customer_group"), sii.so_detail, si.update_stock, sii.uom, From 65c277fd271fdc4e80c2a1f592f14cf6c6ed0418 Mon Sep 17 00:00:00 2001 From: pugazhendhivelu Date: Thu, 3 Jul 2025 15:44:00 +0530 Subject: [PATCH 41/41] fix: update item reference in quality inspection (cherry picked from commit 9da501026590915a28d1f408537d38f817555c5e) # Conflicts: # erpnext/public/js/controllers/transaction.js --- erpnext/controllers/stock_controller.py | 3 +- erpnext/public/js/controllers/transaction.js | 7 ++- .../quality_inspection/quality_inspection.py | 63 ++++++------------- .../stock/doctype/stock_entry/stock_entry.js | 2 + 4 files changed, 28 insertions(+), 47 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 3ad596d71bd..81cf7fb80d7 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1648,8 +1648,9 @@ def make_quality_inspections(doctype, docname, items): "sample_size": flt(item.get("sample_size")), "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, "batch_no": item.get("batch_no"), + "child_row_reference": item.get("child_row_reference"), } - ).insert() + ) quality_inspection.save() inspections.append(quality_inspection.name) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3f6f9ca5e20..76ff4e8c49d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -371,6 +371,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "inspection_type": inspection_type, "reference_type": me.frm.doc.doctype, "reference_name": me.frm.doc.name, + "child_row_reference": row.doc.name, "item_code": row.doc.item_code, "description": row.doc.description, "item_serial_no": row.doc.serial_no ? row.doc.serial_no.split("\n")[0] : null, @@ -385,7 +386,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe docstatus: ["<", 2], inspection_type: inspection_type, reference_name: doc.name, - item_code: d.item_code + item_code: d.item_code, + child_row_reference : d.name } } }); @@ -2427,12 +2429,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe fields: fields, primary_action: function () { const data = dialog.get_values(); + const selected_data = data.items.filter(item => item?.__checked == 1 ); frappe.call({ method: "erpnext.controllers.stock_controller.make_quality_inspections", args: { doctype: me.frm.doc.doctype, docname: me.frm.doc.name, - items: data.items + items: selected_data, }, freeze: true, callback: function (r) { diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 021b7b1cf17..58aa18359df 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -97,51 +97,25 @@ class QualityInspection(Document): if self.reference_type == "Stock Entry": doctype = "Stock Entry Detail" - child_row_references = frappe.get_all( - doctype, - filters={"parent": self.reference_name, "item_code": self.item_code}, - pluck="name", - ) + child_doc = frappe.qb.DocType(doctype) + qi_doc = frappe.qb.DocType("Quality Inspection") - if not child_row_references: - return + child_row_references = ( + frappe.qb.from_(child_doc) + .left_join(qi_doc) + .on(child_doc.name == qi_doc.child_row_reference) + .select(child_doc.name) + .where( + (child_doc.item_code == self.item_code) + & (child_doc.parent == self.reference_name) + & (child_doc.docstatus < 2) + & (qi_doc.name.isnull()) + ) + .orderby(child_doc.idx) + ).run(pluck=True) - if len(child_row_references) == 1: + if len(child_row_references): self.child_row_reference = child_row_references[0] - else: - self.distribute_child_row_reference(child_row_references) - - def distribute_child_row_reference(self, child_row_references): - quality_inspections = frappe.get_all( - "Quality Inspection", - filters={ - "reference_name": self.reference_name, - "item_code": self.item_code, - "docstatus": ("<", 2), - }, - fields=["name", "child_row_reference", "docstatus"], - order_by="child_row_reference desc", - ) - - for row in quality_inspections: - if not child_row_references: - break - - if row.child_row_reference and row.child_row_reference in child_row_references: - child_row_references.remove(row.child_row_reference) - continue - - if row.docstatus == 1: - continue - - if row.name == self.name: - self.child_row_reference = child_row_references[0] - else: - frappe.db.set_value( - "Quality Inspection", row.name, "child_row_reference", child_row_references[0] - ) - - child_row_references.remove(child_row_references[0]) def validate_inspection_required(self): if frappe.db.get_single_value( @@ -413,7 +387,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql( f""" - SELECT item_code + SELECT distinct item_code, item_name, item_group FROM `tab{from_doctype}` WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s {qi_condition} {cond} {mcond} @@ -444,10 +418,11 @@ def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters limit_start=start, limit_page_length=page_len, filters={ - "docstatus": 1, + "docstatus": ("<", 2), "name": ("like", "%%%s%%" % txt), "item_code": filters.get("item_code"), "reference_name": ("in", [filters.get("reference_name", ""), ""]), + "child_row_reference": ("in", [filters.get("child_row_reference", ""), ""]), }, as_list=1, ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 63597dd3e72..51455ef0d24 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -179,6 +179,7 @@ frappe.ui.form.on("Stock Entry", { inspection_type: "Incoming", reference_type: frm.doc.doctype, reference_name: frm.doc.name, + child_row_reference: row.doc.name, item_code: row.doc.item_code, description: row.doc.description, item_serial_no: row.doc.serial_no ? row.doc.serial_no.split("\n")[0] : null, @@ -194,6 +195,7 @@ frappe.ui.form.on("Stock Entry", { filters: { item_code: d.item_code, reference_name: doc.name, + child_row_reference: d.name, }, }; });