* feat(invoices): add tooltip description to Update Stock checkbox
Adds a description below the Update Stock checkbox on both Sales Invoice
and Purchase Invoice so users understand when to use the field without
consulting documentation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(invoices): replace Update Stock description with hover info tooltip
Removes the inline description text and adds an ℹ icon next to the
Update Stock checkbox label on both Sales Invoice and Purchase Invoice.
Hovering the icon shows the contextual tooltip via Bootstrap tooltip.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(invoices): use Frappe native tooltip-content class for Update Stock icon
Replace Bootstrap .tooltip() (pure black bg) with Frappe's own
.tooltip-content CSS class so the hover tooltip matches the rest of
the ERPNext UI — uses var(--bg-dark-gray) and var(--text-dark).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(invoices): use frappe.ui.SidebarCard for Update Stock info tooltip
Replace custom CSS tooltip with the same SidebarCard + Popper approach
Frappe's InfoCard uses for field description tooltips — gives the native
ERPNext card appearance (white card, border, shadow) on hover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(invoices): use built-in field description for Update Stock tooltip
Replace custom SidebarCard JS tooltip with Frappe's native
description + show_description_on_click field property on the
update_stock field in Sales Invoice and Purchase Invoice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: remove duplicate description in purchase_invoice update_stock field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* revert: restore custom tooltip in purchase_invoice.js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* revert: remove all changes from purchase_invoice.js
Keep purchase_invoice.js identical to upstream develop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reduce from 2 alternate UOM columns to 1 (first alt UOM by idx)
- Fix broken translation strings: replace _(f"...{slot}") with
_("...") — f-strings inside _() are never extracted by bench
get-untranslated, breaking non-English installations
- Simplify fieldnames: alt_uom_1/alt_uom_1_bal_qty → alt_uom/alt_uom_bal_qty
- Add 4 test cases covering: single alt UOM, no alt UOM, disabled filter,
and multiple alt UOMs (first-wins behaviour)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The informational toast is not required for the feature to work.
The core fix (reqd removed from JSON, validation relaxed in bom.py)
is sufficient to allow zero qty on BOM secondary items.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a BOM secondary item has cost_allocation_per = 0 (the default), the
previous code unconditionally computed `0 / transfer_qty = 0`, wiping any
rate the user had entered for the item. Now the allocation formula only runs
when cost_allocation_per > 0, allowing the valuation-rate fallback (or a
manually entered rate) to apply instead.
Additionally, secondary items with transfer_qty = 0 now short-circuit the
entire rate pipeline: they get rate = 0 and amount = 0 immediately, avoiding
a ZeroDivisionError and the spurious "enter basic rate" prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Secondary output items in a BOM do not always guarantee output during
manufacture. The actual qty is only known when manufacturing completes,
so setting zero in the BOM is a valid way to express "output is
non-deterministic".
Changes:
- Remove `reqd: 1` from the qty field in BOM Secondary Item so that 0
is accepted as an explicit value (non_negative constraint is kept, so
negative values are still rejected).
- Relax validate_secondary_items() in bom.py to only reject qty that is
None/missing, not qty that is explicitly 0.
- Add a qty event handler in bom.js that shows a blue informational
alert when the user sets qty to 0, explaining that the actual output
will be recorded at manufacture time.
Fixes https://github.com/frappe/erpnext/issues/55401
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#52953
The Stock Balance report previously showed balance qty only in the item's
stock UOM. To view balance in an alternate UOM, users had to set the
"Include UOM" filter — which applies a single UOM to all items. This breaks
down when different items use different alternate UOMs (e.g., Pens in Box,
Ink in Milliliters).
This change adds a new "Show Alternate UOM Balance" checkbox filter. When
enabled, up to two alternate UOM columns are injected right after the
Balance Qty column:
Balance Qty | Alt UOM 1 | Balance Qty (Alt UOM 1) | Alt UOM 2 | Balance Qty (Alt UOM 2)
Each row resolves its own alternate UOMs from `tabUOM Conversion Detail`
(ordered by idx, excluding the item's stock UOM). The converted balance
qty is computed as: stock qty / conversion_factor.
Items with fewer than 2 alternate UOMs leave the extra columns blank.
The existing "Include UOM" filter behaviour is unchanged.
When a Purchase Invoice is created with `update_stock = 1`, the system
automatically replaces the item's expense account with the correct
inventory account for perpetual inventory. This is expected behaviour,
but a `frappe.msgprint` warning was being shown to the user:
"Expense Head changed to Stock In Hand because account Cost of Goods
Sold is not linked to warehouse Stores or it is not the default
inventory account."
The message is purely informational, provides no actionable guidance,
and confuses users who deliberately enable Update Stock. The underlying
account substitution logic is unchanged; only the popup is suppressed.
The two other `msgprint` calls (for the Purchase-Receipt-linked and
no-Purchase-Receipt flows) are intentionally preserved — those surface
a genuine change in behaviour that users may not expect.
Fixes: https://github.com/frappe/erpnext/issues/...
fix(bom): fetch routing operations when routing is selected
frm.doc.operations is always an array in Frappe, so !frm.doc.operations
was always false (empty array [] is truthy in JS), causing get_routing()
to never fire when a Routing is selected on a BOM with no existing
operations.
Changed the guard to !frm.doc.operations.length so the fetch triggers
correctly when the operations table is empty.
Also wired the same fetch into the with_operations handler so that
enabling the checkbox after a Routing is already set will populate
operations without requiring the user to re-select the Routing.
Co-authored-by: Umair Sayed <umairsayed@Umairs-MacBook-Air-2.local>
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>