When a component is listed more than once in a BOM pointing at different
sub-BOMs (e.g. one phantom, one not), two queries grouped the duplicate
lines into a single row and aggregated bom_no and is_phantom_item with
*independent* Max(). That could pair the phantom flag of one line with the
bom_no of another, so the consumer recursed into the wrong sub-BOM:
- sub_assembly_queries._sub_assembly_rm_query keys on (item_code, bom_no)
and recurses on is_phantom_item. An incoherent pair sent raw-material
resolution down the wrong sub-assembly BOM.
- bom_stock_analysis.get_bom_data: explode_phantom_boms recurses into
bom_no only when is_phantom_item is set; an incoherent pair exploded a
non-phantom sub-BOM as if it were phantom (or vice-versa).
Fix:
- sub_assembly_queries: group also by (bom_no, is_phantom_item) so each
distinct sub-BOM is its own coherent row.
- bom_stock_analysis: drop the two independent Max()es and attach a single
representative line (lowest idx) per item_code before exploding.
This was previously undefined SQL (loose GROUP BY); the fix makes MariaDB
and Postgres agree on a deterministic, coherent pairing. Other Max()-wrapped
columns are functionally dependent on the grouped item and keep their value
on both engines.
Tests (fail on the old code, pass on both engines):
- test_phantom_explosion_picks_coherent_sub_bom: duplicate-component BOM
explodes the phantom sub-BOM, not the lexically-larger non-phantom one.
- test_sub_assembly_rm_query_keeps_bom_no_phantom_pair_coherent: the query
returns one coherent row per distinct sub-BOM with the right phantom flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three Query Reports embedded double-quoted string literals and an unquoted table
identifier that error on PostgreSQL. Portability-only, no behaviour change on either engine:
- material_requests_for_which_supplier_quotations_are_not_created,
requested_items_to_be_transferred: double-quoted string literals ("Stopped",
"Material Transfer") -> single quotes (double quotes are identifiers on postgres, not strings).
- items_to_be_requested: quote the `tabBin` table identifier so postgres doesn't lower-case it.
(received_items_to_be_billed was dropped: it is a Script Report, so its `query` field is dead
code and the fix never reaches the DB.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3-way merged onto develop (preserving the get_party_bank_account import move).
get_subscription_details passes order_by="" so get_all does not inject the
doctype default sort the raw query never had.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3-way merged onto develop, preserving develop's set_exchange_rate(ref_doc=doc) change.
One portable raw query is intentionally kept (as on the source branch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The raw get_fiscal_years query had no ORDER BY (de-facto oldest-first); the
get_all port adds explicit order_by="name asc" so the Fiscal Year doctype
default (name DESC) does not reverse the report column order / cumulative values.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Most queries are straight raw-SQL -> query-builder ports. One rider:
get_future_payments_from_journal_entry sums future amounts with no GROUP BY,
so its non-aggregated identity columns (invoice_no/party/future_date/future_ref)
are wrapped in Max() to satisfy postgres strict GROUP BY. The summed amount is
unchanged; the attributed invoice/party label stays within MariaDB's existing
arbitrary-row indeterminacy for that already-aggregated single-row query.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>