* feat: capacity aware scheduling for production plan
* fix: do not apply incomplete schedule proposals
* fix: lock plan re-scheduling once work orders exist
* test: concurrent jobs across multiple machines with job capacity
* chore: fix linter and semgrep issues
* fix: readable subject for production plan schedule entries
* fix: persist computed start for item rows without explicit dates
* fix: block manual creation of production plan schedule entries
* chore: replace em-dashes with hyphens in design doc
* fix: cleared item-wise dates no longer constrain the schedule
* chore: format test file
The detailed-view chart collapsed every row into a single "today" column
and was additionally capped at 10 points, so the chart never matched the
report's date filters or the table data.
Two causes in get_detailed_view_chart_data:
1. `row.deliver_date` was a typo for `row.delivery_date` (the name used
everywhere else in this report). On a frappe._dict the missing
attribute resolves to None, so `getdate(None)` returned today and the
past-date filter silently compared every row against today instead of
its own delivery date.
2. A hard `if i == 10: break` truncated the chart to 10 date buckets.
Use the correct field name and drop the cap. The null check now runs
before the date comparison, since `getdate(None)` returning today meant
the original ordering could never filter a null delivery_date out.
Fixes#52632
The qty-sync fix corrects allocation going forward, but receipts billed
before it can keep understated billed_amt, per_billed, and status. The
earlier repair patch only selects over-billed PO items, so it never picks
these up.
Recompute every candidate PO item (multiple submitted receipts, PO-level
invoicing, no invoice-created receipts). update_billed_amount_based_on_po
only writes rows whose recomputed value differs, so already-correct items
are untouched and the patch stays idempotent. This also converges receipts
left with direct-only billed_amt by last-event-wins overwrites.
The amount-capped allocation branch reduced the remaining PO-invoiced
amount but left the invoiced qty untouched. A later receipt entering the
qty-proportional branch then divided by the stale qty and was under-billed:
PO 10 x 500, PO-level PI for 5 (2500), PR1 qty 3 with 500 billed directly
consumes 1000 (pool 2500 -> 1500, qty stuck at 5), PR2 qty 3 got
1500 * 3/5 = 900 instead of its full 1500. Scale the remaining qty by the
consumed fraction so both stay proportional.
Follow-up to #58021.
Move the insert-time check from before_insert to validate. before_insert
runs before set_new_name, so the validation message rendered the
warehouse name as None. validate runs after naming and only applies to
new documents via is_new().
Resolve inheritance through the parent's lft/rgt bounds instead of the
request-cached warehouse account map. The cached map can be stale within
a request (a parent created moments earlier is missing from it), which
made get_warehouse_account trigger a full nested-set rebuild_tree and
could falsely reject a child whose parent carries a valid account.
rebuild_tree enables auto_commit_on_many_writes, which must not run
inside a document insert.
A Purchase Receipt row created from a Purchase Invoice carries both
purchase_order_item and purchase_invoice_item, and its billed_amt is pinned
to the row amount by update_billing_status. Redistributing the PO-invoiced
pool over such rows zeroes the invoice-created receipt and flips it from
Completed to To Bill, so the repair leaves those PO Items untouched.
enable_auto_reserve_stock ran at the end of validate, after
make_packing_list. On a new Sales Order saved with auto_reserve_stock
enabled, packed item rows were built while the parent reserve_stock
flag was still unset, so they never inherited it. Since the stamping in
packed_item.py is gated on doc.is_new(), later saves could not repair
the rows either; only the client-side toggle could. Move the
auto-enable before packing list generation so packed rows are stamped
on first save.
sales invoice's update_current_stock ran one bin query per item row and one
per packed row. delivery note already batched the same work by warehouse, so
lift that into get_bin_qty_map in stock/utils.py and have both call it.
also batch the per-batch expiry_date lookup in get_batches_by_oldest, and drop
three now-unused per-row setters: delivery note's set_actual_qty (already dead
before this change), sales invoice item's set_actual_qty and packed item's
set_actual_and_projected_qty.
three cases: multiple warehouses get a column each plus the total qty
column, a selected group warehouse still expands to its children, and
multiple items report side by side while unselected items stay out.
the item and warehouse filters took one value at a time, so comparing a
few warehouses meant re-running the report for each one.
both are multiselectlist now, matching the stock balance report. the
warehouse column list unions the subtree of every selected warehouse,
and get_items passes a list through instead of wrapping it. plain string
values still work, so saved filters and existing callers are unaffected.
on_update reran toggle_hide_tax_id, toggle_editable_rate_for_bundle_items
and toggle_discount_accounting_fields on every save, rewriting 11
property setters and clearing the meta cache of five doctypes.
Gate each toggle on has_value_changed. Fresh installs save the settings
with pure defaults (set_single_defaults), so the gated-off state must
match the JSON schema: align sales_invoice.json and
sales_invoice_item.json with the values every saved site already has —
tax_id printed when hide_tax_id is off, discount accounts hidden while
discount accounting is disabled. Packed Item rate already matches.