purchase_receipt/services/gl_composer.py → PurchaseReceiptGLComposer(BaseStockGLComposer).
compose() orchestrates the four builder steps: _make_item_gl_entries,
_make_tax_gl_entries, set_gl_entry_for_purchase_expense (stays on doc),
update_regional_gl_entries (module-level).
_make_item_gl_entries preserves the original closure structure (six inner
functions: make_item_asset_inward_gl_entry, make_stock_received_but_not_billed_entry,
make_landed_cost_gl_entries, make_amount_difference_entry,
make_sub_contracting_gl_entries, make_divisional_loss_gl_entry); all doc
calls go through self.doc. _make_tax_gl_entries is a direct port.
Helpers that stay on the document: add_provisional_gl_entry (public —
PI composer calls it via purchase_receipt_doc.add_provisional_gl_entry),
add_gl_entry, get_item_account_wise_lcv_entries, update_assets,
is_landed_cost_booked_for_any_item.
PurchaseReceipt.get_gl_entries is now a 3-line shim; make_item_gl_entries
and make_tax_gl_entries removed from the class.
Verified: 29 GL snapshots byte-identical on test-erpnext-v17;
101 PR tests green on test-site-ai.
Extends the Phase-0 characterization suite with 3 PR scenarios:
pr_basic, pr_with_taxes, pr_return — all using _Test Company with
perpetual inventory (TCP1) so stock-received GL entries are produced.
Also refreshes se_material_issue.json (cumulative stock on test-erpnext-v17
shifted the outgoing valuation rate). 29 snapshots total, all green.
Stock Entry
stock_entry/services/gl_composer.py → StockEntryGLComposer(BaseStockGLComposer)
compose() calls super().compose() for the base warehouse↔expense GL pairs,
then adds additional-cost entries (_build_additional_cost_per_item_account +
_append_additional_cost_gl_entries) and LCV adjustments (_append_lcv_gl_entries).
get_item_account_wise_lcv_entries stays on StockController (called via self.doc).
StockEntry.get_gl_entries is now a 3-line shim.
Removed private helpers from StockEntry; dropped unused process_gl_map and
get_account_currency imports.
Stock Reconciliation
stock_reconciliation/services/gl_composer.py → StockReconciliationGLComposer(BaseStockGLComposer)
compose() guards cost_center and delegates to
super().compose(inventory_account_map, doc.expense_account, doc.cost_center).
StockReconciliation.get_gl_entries is now a 3-line shim.
Verified: 26 GL snapshots byte-identical on test-erpnext-v17;
89 SE tests and 33/34 SR tests green on test-site-ai
(1 pre-existing SR failure in test_serial_no_status_with_backdated_stock_reco,
unrelated to GL — IndexError in serial bundle setup).
Extends the Phase-0 characterization suite with 4 scenarios:
se_material_receipt, se_material_issue, se_material_transfer, sr_basic.
All use _Test Company with perpetual inventory (TCP1) so stock accounting
GL entries are produced. 26 snapshots total, all green on test-erpnext-v17.
Moves the StockController.get_gl_entries body into
erpnext/stock/services/base_stock_gl_composer.py → BaseStockGLComposer(BaseGLComposer).
compose(inventory_account_map, default_expense_account, default_cost_center) contains
all warehouse↔expense-account GL pair building and the internal-transfer rounding-diff
block; all helpers (get_inventory_account_dict, get_stock_ledger_details, etc.) remain
on self.doc and are called via doc.<method>.
StockController.get_gl_entries becomes a 3-line shim. Delivery Note, Stock Entry, and
Stock Reconciliation continue to work unchanged — DN inherits the shim directly; SE and
SR override and call super(), which now delegates to the composer.
Verified: 22 GL snapshots byte-identical on test-erpnext-v17.
Extends the Phase-0 characterization suite with 2 DN scenarios (basic
delivery and return) using _Test Company with perpetual inventory so
stock accounting GL entries are produced. Uses stock_entry_utils.make_stock_entry
directly (avoids importing test_delivery_note and its conflicting test-record deps).
Run: bench --site test-erpnext-v17 run-tests --module erpnext.accounts.test_gl_characterization
Move the Journal Entry GL assembly into a new JournalEntryGLComposer(
BaseGLComposer); compose() projects the accounts child rows into GL dicts,
mirroring the former build_gl_map, which is now a thin shim delegating to
the composer. Drop the now-unused get_advance_payment_doctypes import.
Extend the Phase-0 GL safety net with three representative Journal Entry
scenarios (basic two-line, multi-currency, against a Sales Invoice with
party and reference) ahead of moving JE onto the composer.
Move the Payment Entry GL row builders (party, bank, deductions, tax)
onto a new PaymentEntryGLComposer(BaseGLComposer); compose() mirrors the
former build_gl_map, which is now a thin shim delegating to the composer.
The builders operate on self.doc and shared helpers stay on the document.
Advance-posting builders are left on the controller; they post in a
separate pass and move with the advances service in a later phase.
Extend the Phase-0 GL safety net with five representative Payment Entry
scenarios (receive against SI, pay against PI, with deductions, with
taxes, multi-currency) ahead of moving PE onto the composer.
Move make_item_gl_entries, make_stock_adjustment_entry,
get_provisional_accounts, make_provisional_gl_entry, and
update_net_purchase_amount_for_linked_assets from PurchaseInvoice onto
PurchaseInvoiceGLComposer, completing the full GL builder migration.
purchase_invoice.py no longer contains any GL row-building logic;
PurchaseInvoiceGLComposer is the single authoritative source for all
PI GL entries, mirroring the SalesInvoiceGLComposer pattern.
All 12 GL characterization snapshots pass.
Move make_supplier_gl_entry, add_supplier_gl_entry, make_tax_gl_entries,
make_internal_transfer_gl_entries, make_gl_entries_for_tax_withholding,
make_payment_gl_entries, make_write_off_gl_entry, and
make_gle_for_rounding_adjustment from PurchaseInvoice onto
PurchaseInvoiceGLComposer.
compose() now calls self.X for all moved builders; the make_item cluster
(make_item_gl_entries, make_provisional_gl_entry, get_provisional_accounts,
update_net_purchase_amount_for_linked_assets, make_stock_adjustment_entry)
still lives on doc pending batch-2 migration.
All 12 GL characterization snapshots pass.
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).
Relocates all 11 Sales Invoice-specific GL entry builders from the document
onto SalesInvoiceGLComposer, operating on self.doc. The perpetual-inventory
super().get_gl_entries() call becomes super(SalesInvoice, doc).get_gl_entries().
Shared bucket-A helpers (get_gl_dict, make_discount_gl_entries, etc.) remain on
AccountsController for now, invoked via self.doc, until all doctypes use a
composer. No behavior change: Phase 0 snapshots and the SI tests covering
perpetual inventory, POS, write-off, returns, fixed assets, internal transfer
and loyalty all stay green.
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).
make_reverse_gl_entries passed adv_adj as the company argument to
check_freezing_date, so the freeze-date check silently no-op'd on
cancellation (no company matched). Pass company explicitly so
cancellations respect the freezing date like submissions do.
Adds a regression test covering cancellation after the freeze date.
Phase 1 of the accounts/controller refactor. Moves the six pure
list-level validators (validate_disabled_accounts, validate_accounting_period,
validate_cwip_accounts, check_freezing_date, validate_against_pcv,
validate_allowed_dimensions) out of general_ledger.py into the new
erpnext/accounts/services/gl_validator.py. general_ledger.py imports and
calls them at the existing sites; no behavior change (Phase 0 GL snapshots
remain byte-identical).
The debit/credit balance trio stays in general_ledger.py for now since
get_debit_credit_difference mutates entries and is interleaved with the
round-off repair.
Phased plan to decompose accounts_controller.py and the sales_invoice.py
monolith into composed services. Documents the frozen GL-layer design
(GLComposer / gl_validator / general_ledger sink), method bucketing, and
the 8-phase rollout.