* test: cover the rows that have nothing left to order in the mrp report
a row whose requirement is already met by stock or by an order placed earlier
fails the order it is selected for, takes the rows beside it down with it, and
what rounding leaves behind of it is ordered as if it were a real quantity. the
work order made from a row of the schedule also has to keep the work in progress
warehouse the company keeps for it.
* fix: skip covered rows when ordering from the mrp report
a row whose requirement is already met by stock or by orders that were placed
earlier nets down to a required qty of zero. making an order from it threw
"Qty To Manufacture cannot be 0", and since nothing caught it, none of the other
selected rows were created either. such rows are now left alone, and selecting
only covered rows says so instead of failing.
the quantity ordered stays the one that is still needed. taking the planned qty
instead would order everything that stock and the open orders already cover. it
is read at the precision an order stores it in, so what is left of a covered row
after all the subtracting does not become an order line of its own.
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
Max() over a text column is a sort, and the engines sort text differently:
MariaDB's utf8mb4 collations fold case, the CI PostgreSQL orders by byte
value. MAX('abc','ABD') is 'ABD' on MariaDB and 'abc' on PostgreSQL --
confirmed on CI in the probe attached to #56241.
The parity effort wrapped many descriptive columns in Max() on the reasoning
that it returns the value MySQL picked arbitrarily. Where the column is
functionally dependent on the group key that holds and the wrap is a genuine
no-op. Where it genuinely varies -- description, item_name, uom and their
warehouses all describe a LINE, not the item -- it does not: MySQL picked a
row, not a maximum, and the sort now diverges between engines. Aggregating
each column separately can also pair one line's description with another's
warehouse, or a uom with the wrong conversion factor.
Take those columns from a single real line instead, the first by idx.
Only groups built from more than one line need it. Each query now also selects
Count(<line>.name).distinct(), and the representative pass returns immediately
when no group has more than one line -- in that case Max() of a single value
is already exact and collation cannot apply. A BOM with no repeated item
therefore issues no extra query at all, which matters because the explosion
and sub-assembly resolution recurse per sub-BOM. Genuine repeats are memoised
per request.
Sites covered: BOM explosion and sub-item queries, sub-assembly raw materials,
get_bom_items_as_dict, BOM Stock Analysis (both queries), Requested Items to
Order and Receive, Pending SO Items for Purchase Request, and Job Card
secondary items.
* fix(manufacturing): stop BOM Stock Analysis inflating both its sums
get_bom_data left-joined Bin on item_code alone and then summed over the
result. Bin holds one row per warehouse and BOM Item one row per line, so the
join is a cross product and each SUM counts the other side's rows:
Sum(qty_consumed_per_unit) x (number of warehouses holding the item)
Sum(bin.actual_qty) x (number of BOM lines carrying the item)
A component on two BOM lines, stocked in two warehouses, reported a per-unit
requirement of 10 instead of 5 and available stock of 20 instead of 10 --
wrong on both engines, and wrong in the single-line case too as soon as the
item sits in more than one warehouse.
Aggregate Bin to one row per item_code before joining, so neither sum can see
the other's duplicates. The warehouse filter moves into that subquery; it
previously sat in the outer WHERE against a left-joined column, which
silently made the join inner, so the join is now made inner explicitly when a
warehouse is given to keep items with no bin there excluded as before.
* test(manufacturing): cover the BOM Stock Analysis bin-join cross product
Component on two BOM lines, stocked in two warehouses: the join yields four
rows, so both sums are doubled. Asserts qty_per_unit is the sum of the lines'
own per-unit quantities and actual_qty the real total across warehouses.
Fails on the previous single-query form with 10.0 != 5.0.
arrival_qty summed the all-time ordered qty of every submitted PO line for the item+warehouse, so it grew monotonically with purchase history. Sum the pending qty (qty - received_qty) on open POs instead, and take the earliest schedule date from the same scope.
`get_work_orders` bounded a BETWEEN on the datetime columns `creation`
and `actual_end_date` with a bare date `to_date`, which MariaDB coerces
to midnight. Work orders created after 00:00:00 on the period's last day
were therefore dropped from the report (and made the new coverage test
fail on month-end CI runs). Extend `to_date` to end of day.