Move status transitions and receiving progress into
buying/doctype/purchase_order/services/status.py:
- update_status (module-level whitelisted wrapper + list view) and
update_receiving_percentage (called by child_item_update) stay as
controller delegators
- check_modified_date moves into the service (internal to update_status)
No behaviour change.
Move drop-ship item handling into
buying/doctype/purchase_order/services/drop_ship.py:
- update_dropship_received_qty stays as a whitelisted controller
delegator (called from purchase_order.js)
- update_delivered_qty_in_sales_order, has_drop_ship_item and
set_received_qty_to_zero_for_drop_ship_items move into the service
(internal-only; on_cancel and the module-level update_status wrapper
repointed)
No behaviour change.
Move subcontracting integration into
buying/doctype/purchase_order/services/subcontracting.py:
- set_service_items_for_finished_goods (called by production plan
work-order planning) and can_update_items (onload + child_item_update)
stay as controller delegators
- validate_fg_item_for_subcontracting, auto_create_subcontracting_order
and update_subcontracting_order_status move into the service;
validate(), on_submit and update_status repointed
No behaviour change.
* feat: add item prices tab to Item doctype
* feat: item form pricing tab
* fix: remove action button for edit item price
* fix: prevent stale item price rendering after form navigation
* fix: remove stale call to deleted edit_prices_button function
* fix: item price list fixes
* fix: show filtered price list
* fix: show filtered price list
Avoids basename collision with the core SLE engine erpnext/stock/stock_ledger.py
(the service even imports make_sl_entries from it). File now maps 1:1 to its class,
StockLedgerService.
Phase 0 golden-master safety net for the stock_controller refactor. It served its
purpose (every extraction verified byte-identical GL + Stock Ledger output) and is
removed before shipping, mirroring the earlier GL characterization cleanup.
The preview feature serves both accounts and stock vouchers (SI/PI/PE + DN/PR/SE)
and its show_*_preview entry points live in controllers/stock_controller, so the
cohesive GL+SLE preview module belongs in controllers/, not stock/services/. Pure
move + import-path update; GL and stock previews stay together (shared get_columns/
get_data formatters; read-side, kept out of the write-path services).
Verified: ledger snapshots green; module resolves at new path.
Merge the stock exceptions into the existing app-wide erpnext/exceptions.py (under a
'# stock' section) instead of a separate erpnext/stock/exceptions.py, matching the
established convention. stock_controller still re-exports them for backward
compatibility; services import from erpnext.exceptions.
Verified: ledger snapshots, quality inspection suite, stock_entry batch-expiry stay green.
Re-audited the kept delegators for true external callers. Two had none:
- has_landed_cost_amount: no caller anywhere (the landed_cost_voucher.py free
function is what the composers use) — pure dead delegator, removed.
- validate_internal_transfer: only StockController.validate() called it; inline that
one hook to StockInternalTransferService(self).validate_internal_transfer() and
remove the delegator.
All other kept delegators have real external/subclass/run_method callers and remain
as the stock extension contract.
Verified: ledger snapshots + DN/PR internal-transfer suites stay green.
#8 ledger_preview: wrap the submit-in-memory dry run in a savepoint inside
get_accounting_ledger_preview / get_stock_ledger_preview and roll back to it in a
finally, so the preview never persists entries regardless of caller (previously
only the whitelisted show_*_preview wrappers' full rollback made it safe).
#9 exceptions: move BatchExpiredError and the QualityInspection* errors into a new
erpnext/stock/exceptions.py and re-export them from stock_controller for backward
compatibility (job_card and tests still import from the controller; identity is
preserved). Services now import from the neutral module instead of back from the
controller they were extracted out of.
#10 quality inspection: extract the duplicated doctype->inspection-field map into a
single INSPECTION_FIELDNAME_MAP constant in the service, consumed by both
validate_inspection and check_item_quality_inspection.
Verified: ledger snapshots, quality inspection suite, stock_entry batch-expiry test
stay green; preview smoke-tested to persist nothing and not roll back the caller.
validate_internal_transfer_qty stashed the value on a name-mangled instance
attribute (self.__inter_company_reference) that get_item_wise_inter_transfer_qty
read back, creating an implicit call-ordering contract: calling the latter on a
fresh service without the former first raised AttributeError. Compute it as a local
and pass it as a method argument, removing the hidden cross-method state.
Verified: ledger snapshots + PR internal-transfer suite stay green.
get_serialized_items had zero callers anywhere (Python, JS, run_method) on develop
and after the refactor; it was relocated into SerialBatchBundleService by mistake
instead of being dropped. Delete it — also removes a raw frappe.db.sql_list query
that duplicated the ORM helper get_serial_or_batch_items.
The @frappe.request_cache decorator keyed on `self`, which after the service
extraction is a transient SerialBatchBundleService built per delegated call, so the
request-wide dedup was lost and dead instances were pinned in request_cache. Use
frappe.get_cached_value on the Item instead: caching is keyed by the item (request-
local + redis), effective regardless of service-instance churn, and the redundant
frappe.db.exists query is dropped.
Verified: ledger snapshots + serial and batch bundle suite stay green.
Address code-review findings on the Phase-0 safety net:
- Per-test savepoint/rollback isolation so cumulative SLE fields
(qty_after_transaction, stock_value, valuation_rate) are deterministic
regardless of test order or leftover state (were order-coupled before).
- Backdate prerequisite stock to PREREQUISITE_DATE so balances are positive and
independent of the wall-clock date.
- Capture has_serial_and_batch_bundle (boolean linkage, not the volatile docname)
so a dropped serial/batch bundle link is caught.
- Add pr_batch_item and pr_serial_item scenarios to exercise SerialBatchBundleService
(the largest extraction, previously uncovered).
Goldens regenerated. Verified deterministic across repeated assert runs.
validate_putaway_capacity selected the 'disable' field but checked rule.get('disabled')
(always None), so disabled rules still enforced capacity and could wrongly raise
'Over Receipt'. Use the correct 'disable' key. Pre-existing bug surfaced during the
stock_controller refactor review.
* feat: create sales invoice from pick list
* test: cover sales invoice creation from pick list
* fix: require update stock for pick list invoices
* fix: return SI should not bother with pick list
These clusters are really other doctypes' logic parked on StockController, so they
move next to the doctype that owns them rather than into a stock service:
- set_landed_cost_voucher_amount / get_item_account_wise_lcv_entries /
has_landed_cost_amount -> landed_cost_voucher.py (free functions). Controller keeps
thin delegators (called as doc.X from 4 GL composers, buying_controller and the LCV
doctype).
- validate_putaway_capacity -> putaway_rule.py (free function, next to
get_available_putaway_capacity it already used). Controller keeps a delegator
(validate hook + Stock Entry/Reconciliation); prepare_over_receipt_message becomes a
private helper there.
Drops now-unused Sum/defaultdict imports from stock_controller.
Behaviour-preserving: ledger snapshots, putaway and landed-cost suites stay green.
Move internal-transfer warehouse/currency/packed-item/over-receipt-qty validation
into erpnext/stock/services/internal_transfer.py as a delegating service. This is
the stock-side counterpart to accounts/services/internal_transfer.py (party/rate/
pricing). validate_internal_transfer keeps a controller delegator (validate hook);
the other 7 methods are internal-only. The is_internal_transfer() predicate is
already consolidated on AccountsController.
Behaviour-preserving: ledger snapshots + DN/PR internal-transfer suites stay green.
Move quality-inspection validation (validate_inspection + validate_qi_presence/
submission/rejection) into erpnext/stock/services/quality_inspection.py as a
delegating service. validate_inspection keeps a controller delegator (called from
validate() and 3 other doctypes); the three row-level helpers are internal-only.
The whitelisted module fns check_item_quality_inspection / make_quality_inspections
stay in stock_controller (stable endpoint paths).
Behaviour-preserving: ledger snapshots + quality inspection suite stay green.
Relocate get_voucher_details, check_expense_account and get_debit_field_precision
from StockController to BaseStockGLComposer, where they are only used (by compose()
and AssetCapitalizationGLComposer). Call sites flipped from doc.X to self.X.
Inventory-account resolution (get_inventory_account_map/_dict, etc.) stays on the
controller: it is a doc-contract method called as doc.X from non-stock-composer code
(PI controller/composer, accounts/utils, repost_accounting_ledger), so it cannot fold
into BaseStockGLComposer. make_gl_entries / make_gl_entries_on_cancel / add_gl_entry
likewise stay (contract entry points).
Behaviour-preserving: ledger snapshots, subcontracting receipt and asset
capitalization suites stay green.
Move SLE building and reposting (get_sl_entries, update_inventory_dimensions,
get_stock_ledger_details, get_items_and_warehouses, make_sl_entries,
repost_future_sle_and_gle) into erpnext/stock/services/stock_ledger.py as a
delegating service. All six keep thin controller delegators (each has external
callers). The repost helper *functions* stay module-level in stock_controller
(imported widely); the service calls them. Also drop import orphaned by this and
the prior bundle extraction.
Behaviour-preserving: ledger characterization snapshots and the repost item
valuation suite stay green.
Move serial & batch bundle handling (creation, validation, return bundles,
teardown) out of StockController into erpnext/stock/services/serial_batch_bundle.py
as a delegating service. The controller keeps thin delegators for the 10 methods
reached from other doctypes or run_method; the 12 internal-only helpers live in
the service. make_bundle_for_material_transfer stays a module fn in stock_controller
(imported by stock/serial_batch_bundle.py).
Behaviour-preserving: ledger characterization snapshots and the full Serial and
Batch Bundle test suite stay green.
Phase 0 safety net for the stock_controller service refactor. Captures the
combined GL + Stock Ledger output of representative stock vouchers (DN, Stock
Entry, Stock Reconciliation, Purchase Receipt incl. returns/taxes) as golden
snapshots, so later phases can prove ledger behaviour stays byte-identical while
stock_controller is split into services.
Run: bench --site <site> run-tests --module erpnext.stock.test_ledger_characterization
Regenerate goldens: REGEN_LEDGER_SNAPSHOTS=1 (after intentional changes only).