From 72fa7d0b1091e1a66450ebb4dc9fb6de1c8d3c1a Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Mon, 7 Sep 2026 05:49:17 +0530 Subject: [PATCH] feat: add subcontracting receipt ledger previews (#58698) --- erpnext/controllers/ledger_preview.py | 28 +++++++++---- .../subcontracting_receipt.js | 3 ++ .../test_subcontracting_receipt.py | 40 +++++++++++++++++++ 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/ledger_preview.py b/erpnext/controllers/ledger_preview.py index 413e02ae21a..06f3a9065f7 100644 --- a/erpnext/controllers/ledger_preview.py +++ b/erpnext/controllers/ledger_preview.py @@ -4,17 +4,22 @@ """Read-side GL / Stock Ledger preview helpers. A dry-run consumer of the posting path, shared across accounts and stock vouchers -(Sales/Purchase Invoice, Payment Entry, Delivery Note, Purchase Receipt, Stock -Entry): it submits-in-memory, reads the resulting GL/SLE entries and formats them -for the datatable preview, then rolls back. Lives separately from the posting -services it orchestrates. The whitelisted ``show_*_preview`` entry points stay on -``stock_controller`` (their dotted path is referenced from client JS). +(Sales/Purchase Invoice, Payment Entry, Delivery Note, Purchase Receipt, +Subcontracting Receipt, Stock Entry): it submits-in-memory, reads the resulting +GL/SLE entries and formats them for the datatable preview, then rolls back. Lives +separately from the posting services it orchestrates. The whitelisted +``show_*_preview`` entry points stay on ``stock_controller`` (their dotted path is +referenced from client JS). """ import frappe from erpnext import get_company_currency +STOCK_LEDGER_PREVIEW_DOCTYPES = frozenset( + ("Delivery Note", "Purchase Receipt", "Stock Entry", "Subcontracting Receipt") +) + def get_accounting_ledger_preview(doc, filters): from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns @@ -39,7 +44,8 @@ def get_accounting_ledger_preview(doc, filters): try: doc.docstatus = 1 - if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note", "Stock Entry"): + if doc.get("update_stock") or doc.doctype in STOCK_LEDGER_PREVIEW_DOCTYPES: + make_serial_and_batch_bundles_for_preview(doc) doc.update_stock_ledger() doc.make_gl_entries() @@ -82,13 +88,13 @@ def get_stock_ledger_preview(doc, filters): "stock_value_difference", ] - if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note", "Stock Entry"): + if doc.get("update_stock") or doc.doctype in STOCK_LEDGER_PREVIEW_DOCTYPES: # Dry run: submit in memory to materialise SLEs, read them, then roll back to # the savepoint so the preview never persists anything, regardless of caller. frappe.db.savepoint("ledger_preview") try: doc.docstatus = 1 - doc.make_bundle_using_old_serial_batch_fields() + make_serial_and_batch_bundles_for_preview(doc) doc.update_stock_ledger() columns = get_sl_columns(filters) @@ -125,6 +131,12 @@ def get_gl_entries_for_preview(doctype, docname, fields): return frappe.get_all("GL Entry", filters={"voucher_type": doctype, "voucher_no": docname}, fields=fields) +def make_serial_and_batch_bundles_for_preview(doc): + table_names = ("items", "supplied_items") if doc.doctype == "Subcontracting Receipt" else ("items",) + for table_name in table_names: + doc.make_bundle_using_old_serial_batch_fields(table_name) + + def get_columns(raw_columns, fields, currency): columns = [] for source_column in raw_columns: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 6cbd77fbbf8..d4630a44564 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -31,6 +31,9 @@ frappe.ui.form.on("Subcontracting Receipt", { frappe.dynamic_link = { doc: frm.doc, fieldname: "supplier", doctype: "Supplier" }; erpnext.toggle_serial_batch_fields(frm); + erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm); + erpnext.accounts.ledger_preview.show_stock_ledger_preview(frm); + if (frm.doc.docstatus === 1) { frm.add_custom_button( __("Stock Ledger"), diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 09fb084a4ca..a16b23f2343 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -9,6 +9,10 @@ from frappe.utils import add_days, cint, flt, nowtime, today import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account +from erpnext.controllers.ledger_preview import ( + get_accounting_ledger_preview, + get_stock_ledger_preview, +) from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.controllers.tests.test_subcontracting_controller import ( get_rm_items, @@ -374,6 +378,38 @@ class TestSubcontractingReceipt(ERPNextTestSuite): self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name)) frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def test_ledger_preview(self): + sco = get_subcontracting_order( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + ) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr = make_subcontracting_receipt(sco.name) + scr.save() + + gl_columns, gl_data = get_accounting_ledger_preview( + scr, frappe._dict(company=scr.company, include_dimensions=1) + ) + scr.reload() + sl_columns, sl_data = get_stock_ledger_preview(scr, frappe._dict(company=scr.company)) + + self.assertTrue(gl_columns) + self.assertTrue(gl_data) + self.assertTrue(sl_columns) + self.assertTrue(sl_data) + self.assertFalse(frappe.db.exists("GL Entry", {"voucher_type": scr.doctype, "voucher_no": scr.name})) + self.assertFalse( + frappe.db.exists("Stock Ledger Entry", {"voucher_type": scr.doctype, "voucher_no": scr.name}) + ) + def test_subcontracting_receipt_gl_entry_with_different_rm_expense_accounts(self): service_items = [ { @@ -1732,6 +1768,10 @@ class TestSubcontractingReceipt(ERPNextTestSuite): scr.items[0].batch_no = batch_no scr.save() + get_accounting_ledger_preview(scr, frappe._dict(company=scr.company, include_dimensions=1)) + scr.reload() + self.assertFalse(scr.items[0].serial_and_batch_bundle) + scr.submit() scr.reload() self.assertTrue(scr.items[0].serial_and_batch_bundle)