From c8aedbfe33093df9e4868bec75dbbcf624cf57eb Mon Sep 17 00:00:00 2001 From: sayanthns Date: Sat, 27 Jun 2026 12:27:56 +0300 Subject: [PATCH] fix(dn-consolidation): carry DN price list to consolidated SI + SI->DN button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues on the net-off consolidation path (_build_consolidated_standard_si): (1) the SI never set selling_price_list, so it fell back to the customer/default list (e.g. Retail Price) instead of the DN's (e.g. Inter Company Price) — now carried over; item rates stay set explicitly from netted amounts so amounts don't change. (2) Connections tab can't show the source DNs (net-off merges rows → no per-row delivery_note/si_detail mapping; setting them trips validate_multiple_billing) — added a 'Delivery Notes' button (View group) on Sales Invoice linking to DNs filtered by custom_consolidated_si. DN->SI direction already works via the DN field. Regression test for price-list carryover added. Co-Authored-By: Claude Opus 4.8 --- rmax_custom/api/delivery_note.py | 7 ++++ .../public/js/sales_invoice_doctype.js | 32 +++++++++++++++++++ rmax_custom/tests/test_dn_consolidation.py | 29 +++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/rmax_custom/api/delivery_note.py b/rmax_custom/api/delivery_note.py index 248c230..5298e87 100644 --- a/rmax_custom/api/delivery_note.py +++ b/rmax_custom/api/delivery_note.py @@ -390,6 +390,13 @@ def _build_consolidated_standard_si(dns, buckets): si.customer_name = head.customer_name si.company = head.company si.currency = head.currency + # Carry the source DN's price list (e.g. "Inter Company Price") onto the SI. + # Without this the SI falls back to the customer/default selling price list + # (e.g. "Retail Price"), which mismatches the DNs. Item rates are still set + # explicitly from the netted DN amounts below, so this only fixes the + # price-list field + price_list_rate, it does not re-price the invoice. + if head.get("selling_price_list"): + si.selling_price_list = head.selling_price_list si.posting_date = frappe.utils.today() si.set_posting_time = 1 si.update_stock = 0 diff --git a/rmax_custom/public/js/sales_invoice_doctype.js b/rmax_custom/public/js/sales_invoice_doctype.js index 791f848..8112b7a 100644 --- a/rmax_custom/public/js/sales_invoice_doctype.js +++ b/rmax_custom/public/js/sales_invoice_doctype.js @@ -234,3 +234,35 @@ function _rmax_apply_branch_payment_accounts(frm, cdt, cdn) { }); } +// --------------------------------------------------------------------------- +// Consolidated Delivery Notes link (SI → source DNs) +// --------------------------------------------------------------------------- +// Net-off consolidation links the source DNs to this SI via the custom field +// `custom_consolidated_si` (the standard SI→DN item link is intentionally +// omitted — merged/netted rows can't map to a single DN row), so the +// Connections tab cannot surface them. This button gives the SI → source-DN +// navigation. The DN → SI direction already works via the DN's +// "Consolidated Sales Invoice" field. +frappe.ui.form.on("Sales Invoice", { + refresh: function (frm) { + if (frm.is_new()) return; + frappe.db + .count("Delivery Note", { + filters: { custom_consolidated_si: frm.doc.name }, + }) + .then(function (n) { + if (n) { + frm.add_custom_button( + __("Delivery Notes ({0})", [n]), + function () { + frappe.set_route("List", "Delivery Note", { + custom_consolidated_si: frm.doc.name, + }); + }, + __("View") + ); + } + }); + }, +}); + diff --git a/rmax_custom/tests/test_dn_consolidation.py b/rmax_custom/tests/test_dn_consolidation.py index 36f239f..ade54b6 100644 --- a/rmax_custom/tests/test_dn_consolidation.py +++ b/rmax_custom/tests/test_dn_consolidation.py @@ -100,6 +100,24 @@ def test_clubbed_dn_billing_status_flips_on_submit_and_reverts(self): dn.reload() self.assertEqual(dn.status, "To Bill") + def test_consolidated_si_inherits_dn_price_list(self): + # Consolidated SI must carry the source DN's selling price list (e.g. + # "Inter Company Price"), not fall back to the customer/default list. + pl = _ensure_selling_price_list("RMAX Consol PL") + dn = frappe.new_doc("Delivery Note") + dn.customer = self.customer + dn.company = self.company + dn.set_warehouse = self.warehouse + dn.selling_price_list = pl + dn.append("items", { + "item_code": self.item, "qty": 3, "rate": 10, + "warehouse": self.warehouse, "uom": "Nos", + }) + dn.insert(ignore_permissions=True) + dn.submit() + si = frappe.get_doc("Sales Invoice", consolidate_dns_to_si([dn.name])) + self.assertEqual(si.selling_price_list, pl) + def test_draft_si_delete_clears_stamp_and_succeeds(self): # Reproduces the bug: a DRAFT consolidated SI stamps the DN via # custom_consolidated_si; deleting it must clear the stamp (on_trash) @@ -150,6 +168,17 @@ def _ensure_item(item_code): return i.name +def _ensure_selling_price_list(name): + if not frappe.db.exists("Price List", name): + pl = frappe.new_doc("Price List") + pl.price_list_name = name + pl.selling = 1 + pl.enabled = 1 + pl.currency = "SAR" + pl.insert(ignore_permissions=True) + return name + + def _pick_default_company(): return frappe.db.get_single_value("Global Defaults", "default_company") \ or frappe.db.get_value("Company", {}, "name")