row is a plain dict subclass, so row["item_code"] raised an unhandled
KeyError (500) when the payload had neither key. get_active_product_bundle
already returns None for falsy input, yielding an empty item list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The legacy item_code path now resolves the active bundle's name via
get_active_product_bundle (same filters as the old joined query) so
frappe.has_permission can validate the specific document on both
branches. The orphaned get_product_bundle_items helper is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The whitelisted get_items_from_product_bundle endpoint now verifies read
permission on Product Bundle (doc-level when a name is passed, doctype-
level for the legacy item_code path) so authenticated users can't
enumerate bundle components. The disabled-bundle test also restores the
disabled flag via addCleanup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Since Product Bundles became versioned, their names are PB-prefixed and
no longer double as the parent item code. The buying dialog kept passing
the picked bundle name as `item_code`, so the component lookup (which
filters `new_item_code`) matched nothing and the dialog silently added
no items.
The dialog now sends the selection as `product_bundle` and the endpoint
fetches that version's components by document name (rejecting
unsubmitted versions); passing `item_code` still resolves the parent
item's active version, preserving the legacy contract of the
whitelisted endpoint. The picker is also restricted to submitted
bundles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolution skips disabled bundles, transactions referencing a disabled
version are blocked, rows without an explicit version stop packing, and
the Item Where Used report surfaces the disabled flag on bundle rows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Un-deprecate the `disabled` checkbox: it is now editable (also after
submit) and parks a bundle version without ceding its active slot, so
re-enabling restores it without re-activation.
- `get_active_product_bundle` (the single resolution entry point) skips
disabled bundles, so every consumer stops treating the item as a bundle
while it is disabled
- the version pickers on transaction item rows and the buying "Get Items
from Product Bundle" dialog filter out disabled bundles
- an explicitly selected disabled version blocks the transaction with a
validation error instead of silently re-packing another version
- Product Bundle Balance report excludes disabled bundles
- list view indicator: Disabled (grey) / Active (green), falling back to
docstatus for drafts, cancelled and inactive submitted versions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stock Ageing iterates stock ledger entries through an unbuffered
(streaming) cursor. _get_batchwise_valuation() lazily queried
Batch.use_batchwise_valuation from inside that loop whenever a row
carried the legacy batch_no field, and the nested query invalidated
the active streaming result set — crashing the report (or silently
dropping the remaining rows, depending on the driver version).
Resolve the valuation flags in a single query before entering the
unbuffered cursor block; the lazy lookup now only serves callers that
pass stock ledger entries in directly, where no streaming is active.
Fixes https://github.com/frappe/erpnext/issues/55786
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 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 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