refactor: introduce Purchase Invoice GL composer

Phase 3 of the accounts/controller refactor. Adds PurchaseInvoiceGLComposer;
PI'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.

After comparing the SI and PI compose() flows, BaseGLComposer is kept minimal:
the two differ in step order, builders, and per-doctype regional function, so a
shared template is not warranted. No behavior change (Phase 0 snapshots and PI
GL tests stay green).
This commit is contained in:
Nabin Hait
2026-05-27 08:13:10 +05:30
parent b5c96dfef0
commit 6467f07459
4 changed files with 54 additions and 29 deletions

View File

@@ -74,8 +74,8 @@ Moved the 6 pure list-level validators to `erpnext/accounts/services/gl_validato
### Phase 2 — Pilot composer on Sales Invoice only — DONE
Added `BaseGLComposer` (minimal: holds `self.doc`) and `SalesInvoiceGLComposer`. SI's `get_gl_entries` is a thin shim delegating to `SalesInvoiceGLComposer(self).compose()`. All 11 SI-specific row builders (make_customer/tax/item/internal_transfer/pos/loyalty/write_off/rounding GL entries, stock_delivered_but_not_billed, get_gl_entries_for_fixed_asset, get_gle_for_change_amount) moved onto the composer and operate on `self.doc`. The `super().get_gl_entries()` stock-expense call became `super(SalesInvoice, doc).get_gl_entries()` (MRO-faithful). Bucket-A shared helpers (`get_gl_dict`, `make_discount_gl_entries`, `make_precision_loss_gl_entry`, `set_transaction_currency_and_rate_in_gl_map`, `get_tax_amounts`, `get_amount_and_base_amount`) **stay on the controller** — they're still called via `self.doc` and only lift to `BaseGLComposer` once all doctypes use composers (can't move while other doctypes inherit them). Verified: 12 snapshots + 10 existing SI tests (perpetual `super()`, POS change, write-off, returns, fixed-asset disposal/regain, internal transfer, loyalty) all green.
### Phase 3 — Second doctype: Purchase Invoice (base earns its shape)
Add `PurchaseInvoiceGLComposer`; reshape `BaseGLComposer` from what SI + PI *actually* share. Two real consumers is the minimum to size the abstraction — prevents premature abstraction.
### Phase 3 — Second doctype: Purchase Invoice (base earns its shape) — IN PROGRESS
Added `PurchaseInvoiceGLComposer` (scaffolding: compose() = the moved get_gl_entries orchestration; PI.get_gl_entries is a thin shim). **Decision after comparing SI and PI: keep `BaseGLComposer` minimal** (`self.doc` + abstract `compose`). The two flows differ too much to share a template — different step order (SI tax→item, PI item→tax), different builders (SI: discount/loyalty/POS/SDBNB; PI: tax-withholding/payment/purchase-expense), and a per-doctype `make_regional_gl_entries`. Forcing a template would be hook-heavy and risk behavior changes. Revisit base-lifting only when a 3rd+ doctype reveals a real common shape. Verified: 12 snapshots + 6 existing PI GL tests (perpetual inventory, non-stock, return, update_stock, tax withholding, provisional) green. (PI row-builder method migration onto the composer, mirroring SI, still pending.)
### Phase 4 — Roll out composer to remaining GL-posting doctypes
Payment Entry, Journal Entry, Delivery Note, Stock Entry, etc. Mechanical now; one PR per doctype (or small batches), each snapshot-gated.