Move PR billing sync and provisional-entry cancellation into
accounts/doctype/purchase_invoice/services/billing_status.py:
- update_billing_status_in_pr, get_pr_details_billed_amt and
cancel_provisional_entries move into the service (internal-only;
on_submit/on_cancel and make_gl_entries repointed)
- the service imports the shared allocation helpers from
purchase_receipt/services/billing_status.py (PR owns the shared
buying billing logic)
- also repoints the validate_expense_account call in
validate_for_repost missed in the ExpenseAccountService commit
No behaviour change.
Move expense-account resolution into
accounts/doctype/purchase_invoice/services/expense_account.py:
- set_expense_account stays as a controller delegator (dispatched from
accounts_controller.py) and force_set_against_expense_account stays
(called by repost_accounting_ledger)
- validate_expense_account and set_against_expense_account move into the
service; validate() repointed (the unused force kwarg on
set_against_expense_account is dropped with the method)
Pre-existing raw SQL (SRBNB-booked-in-PR check) moved verbatim.
No behaviour change.
Move stock reservation on PR submission into
stock/doctype/purchase_receipt/services/stock_reservation.py:
- reserve_stock, reserve_stock_for_sales_order,
reserve_stock_for_production_plan and get_production_plan_references
move into the service (internal-only; on_submit repointed)
- delegates to the Sales Order controller contract
(create_stock_reservation_entries) and the shared StockReservation
class
No behaviour change.
Move provisional accounting for non-stock items into
stock/doctype/purchase_receipt/services/provisional_accounting.py:
- add_provisional_gl_entry stays as a controller delegator (called as a
doc method by both the PR and PI GL composers)
- validate_provisional_expense_account moves into the service;
validate() repointed
No behaviour change.
Move PR↔PI billed-amount allocation into
stock/doctype/purchase_receipt/services/billing_status.py. Purchase
Receipt owns the shared buying billing logic; Purchase Invoice imports
from the service module:
- update_billing_status stays as a controller delegator (called by
Purchase Invoice flows and v13 patches)
- the module-function family moves verbatim:
update_billed_amount_based_on_po, update_billing_percentage,
get_billed_amount_against_pr/_po,
get_purchase_receipts_against_po_details,
get_billed_qty_amount_against_purchase_receipt/_order,
adjust_incoming_rate_for_pr, get_item_wise_returned_qty
- imports repointed in purchase_invoice.py (top-level + lazy) and
patches/v15_0/recalculate_amount_difference_field.py
No behaviour change.
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.
Move packing slip / product bundle handling into
stock/doctype/delivery_note/services/packing.py:
- validate_packed_qty stays as a controller delegator (called via
hasattr contract in accounts/utils.py); has_unpacked_items stays
for onload/JS
- get_product_bundle_list and cancel_packing_slips move into the
service (internal-only; on_cancel repointed)
Pre-existing raw SQL in cancel_packing_slips moved verbatim.
No behaviour change.
Move billing status tracking and return invoicing into
stock/doctype/delivery_note/services/billing_status.py:
- update_status and update_billing_status stay as controller delegators
(whitelisted update_delivery_note_status wrapper, v13 patches and
Sales Invoice call them)
- make_return_invoice moves into the service (internal to on_submit)
- the update_billed_amount_based_on_so module function moves to the
service module; the sales_invoice.py import is repointed
- drops stale Document/DocType/Abs imports
Pre-existing raw SQL in update_billed_amount_based_on_so moved verbatim.
No behaviour change.
Move subcontracting (inward) integration into
selling/doctype/sales_order/services/subcontracting.py:
- can_update_items stays as a controller delegator (onload data +
child_item_update.py caller)
- validate_fg_item_for_subcontracting and
update_subcontracting_order_status move into the service; validate()
and StatusService.update_status repointed
No behaviour change.
Move delivery schedule management into
selling/doctype/sales_order/services/delivery_schedule.py:
- get_delivery_schedule and create_delivery_schedule stay as whitelisted
controller delegators (called from sales_order.js)
- update_delivery_date_based_on_schedule, delete_delivery_schedule_items
and delete_removed_delivery_schedule_items move into the service
(internal-only; on_submit/on_cancel repointed)
No behaviour change.
Move status computation and progress tracking into
selling/doctype/sales_order/services/status.py:
- update_status, update_delivery_status, update_picking_status and
set_indicator stay as controller delegators (whitelisted wrapper,
purchase_order/pick_list/child_item_update callers, portal contract)
- check_modified_date moves into the service (internal to update_status)
- the billing/delivery/advance status defaults in validate() move to
StatusService.set_default_statuses()
No behaviour change.
Move stock reservation logic out of the Sales Order controller into
selling/doctype/sales_order/services/stock_reservation.py:
- validate_reserved_stock, enable_auto_reserve_stock and the
get_unreserved_qty module function move into the service (internal-only,
callers repointed; stock_reservation_entry.py import updated)
- has_unreserved_stock, create/cancel_stock_reservation_entries and
update_reserved_qty stay on the controller as thin delegators
(whitelisted/JS-reachable or called from selling_controller and
child_item_update)
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.