From cf1817c1ea4cf778b9dec15dc53fc7cbc5382c11 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 27 May 2026 01:12:11 +0530 Subject: [PATCH] refactor: introduce GL composer and delegate SI get_gl_entries Phase 2 (pilot) of the accounts/controller refactor. Adds BaseGLComposer and SalesInvoiceGLComposer; Sales Invoice's get_gl_entries body moves into compose() and the method becomes a thin shim. Row-builder methods still live on the document (invoked via self.doc) and migrate onto the composer next. No behavior change (Phase 0 GL snapshots remain byte-identical). --- .../doctype/sales_invoice/sales_invoice.py | 33 +----------- .../sales_invoice/services/__init__.py | 0 .../sales_invoice/services/gl_composer.py | 51 +++++++++++++++++++ erpnext/accounts/services/base_gl_composer.py | 19 +++++++ 4 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 erpnext/accounts/doctype/sales_invoice/services/__init__.py create mode 100644 erpnext/accounts/doctype/sales_invoice/services/gl_composer.py create mode 100644 erpnext/accounts/services/base_gl_composer.py diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 86f84be0973..9495fd8202e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1576,38 +1576,9 @@ class SalesInvoice(SellingController): make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) def get_gl_entries(self, inventory_account_map=None): - from erpnext.accounts.general_ledger import merge_similar_entries + from erpnext.accounts.doctype.sales_invoice.services.gl_composer import SalesInvoiceGLComposer - gl_entries = [] - - self.make_customer_gl_entry(gl_entries) - - self.make_tax_gl_entries(gl_entries) - self.make_internal_transfer_gl_entries(gl_entries) - - self.make_item_gl_entries(gl_entries) - - disable_sdbnb_in_sr = frappe.get_cached_value("Company", self.company, "disable_sdbnb_in_sr") - - if not (self.is_return and disable_sdbnb_in_sr): - self.stock_delivered_but_not_billed_gl_entries(gl_entries) - - self.make_precision_loss_gl_entry(gl_entries) - self.make_discount_gl_entries(gl_entries) - - gl_entries = make_regional_gl_entries(gl_entries, self) - - # merge gl entries before adding pos entries - gl_entries = merge_similar_entries(gl_entries) - - self.make_loyalty_point_redemption_gle(gl_entries) - self.make_pos_gl_entries(gl_entries) - - self.make_write_off_gl_entry(gl_entries) - self.make_gle_for_rounding_adjustment(gl_entries) - - self.set_transaction_currency_and_rate_in_gl_map(gl_entries) - return gl_entries + return SalesInvoiceGLComposer(self).compose(inventory_account_map) def stock_delivered_but_not_billed_gl_entries(self, gl_entries): if self.update_stock or not cint(erpnext.is_perpetual_inventory_enabled(self.company)): diff --git a/erpnext/accounts/doctype/sales_invoice/services/__init__.py b/erpnext/accounts/doctype/sales_invoice/services/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/sales_invoice/services/gl_composer.py b/erpnext/accounts/doctype/sales_invoice/services/gl_composer.py new file mode 100644 index 00000000000..df6e58a6ee7 --- /dev/null +++ b/erpnext/accounts/doctype/sales_invoice/services/gl_composer.py @@ -0,0 +1,51 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + +from erpnext.accounts.services.base_gl_composer import BaseGLComposer + + +class SalesInvoiceGLComposer(BaseGLComposer): + """Assembles the GL entries for a Sales Invoice. + + Orchestration only for now: the voucher-specific row builders still live on + the Sales Invoice document and are invoked via ``self.doc``. They migrate + onto this composer in a later increment. + """ + + def compose(self, inventory_account_map=None): + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_regional_gl_entries + from erpnext.accounts.general_ledger import merge_similar_entries + + doc = self.doc + gl_entries = [] + + doc.make_customer_gl_entry(gl_entries) + + doc.make_tax_gl_entries(gl_entries) + doc.make_internal_transfer_gl_entries(gl_entries) + + doc.make_item_gl_entries(gl_entries) + + disable_sdbnb_in_sr = frappe.get_cached_value("Company", doc.company, "disable_sdbnb_in_sr") + + if not (doc.is_return and disable_sdbnb_in_sr): + doc.stock_delivered_but_not_billed_gl_entries(gl_entries) + + doc.make_precision_loss_gl_entry(gl_entries) + doc.make_discount_gl_entries(gl_entries) + + gl_entries = make_regional_gl_entries(gl_entries, doc) + + # merge gl entries before adding pos entries + gl_entries = merge_similar_entries(gl_entries) + + doc.make_loyalty_point_redemption_gle(gl_entries) + doc.make_pos_gl_entries(gl_entries) + + doc.make_write_off_gl_entry(gl_entries) + doc.make_gle_for_rounding_adjustment(gl_entries) + + doc.set_transaction_currency_and_rate_in_gl_map(gl_entries) + return gl_entries diff --git a/erpnext/accounts/services/base_gl_composer.py b/erpnext/accounts/services/base_gl_composer.py new file mode 100644 index 00000000000..bbe2474297e --- /dev/null +++ b/erpnext/accounts/services/base_gl_composer.py @@ -0,0 +1,19 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +"""Base class for per-document GL entry composers. + +A composer assembles the list of GL entry dicts for a single voucher. Unlike +the posting sink (``general_ledger.make_gl_entries``) and the stateless +validators (``gl_validator``), composing is stateful and per-document, so it is +modelled as a class holding the document being composed. Subclasses implement +``compose`` to return the voucher-specific list of GL entries. +""" + + +class BaseGLComposer: + def __init__(self, doc): + self.doc = doc + + def compose(self): + raise NotImplementedError