Commit Graph

58941 Commits

Author SHA1 Message Date
Nabin Hait
017e09eaac test(opportunity): cover item details, auto-close and prospect sync
Add tests for get_item_details, auto_close_opportunity (a stale Replied
opportunity is closed, a recent one is not) and the Opportunity -> Prospect
opportunity sync. Opportunity controller coverage 62% -> 80%.
2026-06-22 12:45:12 +05:30
Nabin Hait
61c2e7ad6e test(opportunity): cover the mark-as-lost flow
declare_enquiry_lost had almost no coverage. Add tests that marking an
Opportunity as lost records the lost reasons, competitors and detailed
reason and sets status to Lost, and that it is blocked when an active
(submitted) Quotation exists.
2026-06-22 12:33:19 +05:30
Mihir Kandoi
c4bbf22c62 Merge pull request #56236 from mihir-kandoi/pg-test-stock-entry-timestamp
test(stock): order get_sle by posting_datetime, not MySQL timestamp()
2026-06-21 11:20:00 +05:30
Mihir Kandoi
1772ccc61a test(stock): order get_sle by posting_datetime, not MySQL timestamp()
test_stock_entry.get_sle ordered by `timestamp(posting_date, posting_time)`, a
MySQL-only two-arg function that errors on Postgres ("function timestamp(date, time)
does not exist"), so every test using get_sle (test_fifo, test_stock_entry_qty, ...)
failed to run on Postgres. Order by the precomputed `posting_datetime` column instead
(identical value on MariaDB, valid on both engines).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:00:50 +05:30
Mihir Kandoi
bc9503bdbf Merge pull request #56234 from mihir-kandoi/pg-stock-index-tests
test(stock): make Bin/Item index tests Postgres-valid (SHOW INDEX → db-agnostic helpers)
2026-06-21 10:53:48 +05:30
Mihir Kandoi
851e70b0f3 Merge pull request #56233 from mihir-kandoi/pg-timesheet-coalesce
fix(projects): use Coalesce for timesheet portal sales_invoice (not bitwise OR)
2026-06-21 10:51:25 +05:30
Mihir Kandoi
dd2f3d42a5 Merge pull request #56231 from mihir-kandoi/pg-rfq-transaction-list
fix(controllers): fix supplier-RFQ portal list query (wrong column + Postgres DISTINCT)
2026-06-21 10:35:33 +05:30
Mihir Kandoi
a927397eac Merge pull request #56230 from mihir-kandoi/pg-lost-quotations-intdiv
fix(selling): keep Lost Quotations % fractional on Postgres (integer division)
2026-06-21 10:29:28 +05:30
Mihir Kandoi
5a0e7f57a3 fix(projects): use Coalesce for timesheet portal sales_invoice (not bitwise OR)
get_timesheets_list selected `timesheet.sales_invoice | detail.sales_invoice`,
intending COALESCE (pick the parent timesheet's invoice, else the detail's) -- the
original raw SQL was COALESCE(ts.sales_invoice, tsd.sales_invoice). pypika's `|` is a
bitwise OR, not a coalesce:

- Postgres: `varchar | varchar` -> "operator does not exist" (hard error).
- MariaDB: bitwise OR coerces the operands to integers; with a NULL detail invoice the
  result is NULL, so the portal showed no invoice even when the timesheet was billed.

Replace with Coalesce(table.sales_invoice, child_table.sales_invoice).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:27:14 +05:30
Mihir Kandoi
9fe2202f39 test(stock): use db-agnostic index introspection in Item index test
test_index_creation used `frappe.db.sql("show index from tabItem")` and the MySQL-only
result key "Column_name". "SHOW INDEX" errors on Postgres, so the test could not run
there. Use the db-agnostic frappe.db.get_column_index("tabItem", column) (checking both
unique and non-unique single-column indexes) instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:25:32 +05:30
Mihir Kandoi
f66ef869fc test(stock): use db-agnostic index introspection in Bin index test
test_index_exists used `frappe.db.sql("show index from tabBin ...")`. "SHOW INDEX"
is MySQL-only syntax and errors on Postgres (syntax error at "from"), so the test
could not run there. Use the db-agnostic frappe.db.has_index("tabBin",
"unique_item_warehouse") instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:25:30 +05:30
Mihir Kandoi
42fffc4857 Merge pull request #56227 from mihir-kandoi/pg-general-ledger-alias
fix(accounts): General Ledger remarks alias Postgres-valid
2026-06-21 10:19:25 +05:30
Mihir Kandoi
de70333f8f Merge pull request #56229 from mihir-kandoi/pg-lcv-having
fix(stock): make Landed Cost Voucher vendor-invoice query Postgres-valid (HAVING→WHERE)
2026-06-21 10:18:54 +05:30
Mihir Kandoi
404d4413b5 Merge pull request #56228 from nishkagosalia/st-71960
fix: disarding stock entry fix
2026-06-21 10:16:42 +05:30
Mihir Kandoi
a7d9078bf4 fix(controllers): fix supplier-RFQ portal list query (wrong column + Postgres DISTINCT)
rfq_transaction_list had two defects introduced when it was converted to the query
builder:

1. `party.supplier == party[0]` compared supplier to a column literally named "0"
   (a stray index on the DocType, not the intended `parties[0]` value). This renders
   as `supplier = \`0\`` / `supplier = "0"` and errors on BOTH engines
   (MariaDB: Unknown column '0'; Postgres: column "0" does not exist), so the
   supplier portal RFQ list was completely broken.
2. SELECT DISTINCT ordered by `creation`, which is not in the select list. Postgres
   rejects this ("for SELECT DISTINCT, ORDER BY expressions must appear in select list").

Compare against `parties[0]` and add `creation` to the select list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:14:42 +05:30
Mihir Kandoi
0e88f59196 Merge pull request #56226 from mihir-kandoi/pg-item-variant-update-join
fix(controllers): make update_variant_attribute_values Postgres-valid (drop UPDATE..JOIN)
2026-06-21 10:13:50 +05:30
Mihir Kandoi
ed6a682779 fix(selling): keep Lost Quotations % fractional on Postgres (integer division)
The "Lost Quotations %" column computed Count(distinct) / total_quotations * 100,
where both operands are integers. Postgres does integer division on int/int, so any
group that is a strict minority of the total truncated to 0 (e.g. 1 of 4 -> 0%);
MariaDB always divides as decimal. Multiply by 100.0 before dividing so the division
is done in floating point on both engines.

The "Lost Value %" column already divided Sum(Currency)/Sum(Currency) (numeric), so it
was unaffected; left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:09:45 +05:30
Mihir Kandoi
016097da2e Merge pull request #56225 from mihir-kandoi/pg-projects-pg-validity
fix(projects): Project timeline GROUP BY Postgres-valid
2026-06-21 10:04:08 +05:30
Mihir Kandoi
acbf453def fix(accounts): make General Ledger remarks alias Postgres-valid
When Accounts Settings -> general_ledger_remarks_length is set, the GL report
adds `substr(remarks, 1, n) as 'remarks'` to its raw SQL. Postgres treats a
single-quoted column alias as a string literal and raises a syntax error, so
the General Ledger report is broken on Postgres whenever that setting is on.

Use a bare alias (`as remarks`). substr() itself is portable.

Adds a test that sets general_ledger_remarks_length and runs the report,
asserting it executes (and returns rows) on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:58:06 +05:30
Mihir Kandoi
7f8fa5b5a2 fix(stock): make Landed Cost Voucher vendor-invoice query Postgres-valid
get_vendor_invoice_query filtered unclaimed invoices with
.having(unclaimed_amount > 0), but the query has no GROUP BY/aggregate and
unclaimed_amount is a SELECT alias. Postgres rejects HAVING on a SELECT alias
(and HAVING without GROUP BY on a non-aggregated column); MariaDB allowed it.
Move the threshold into WHERE on the underlying expression.

Behaviour is identical on MariaDB (same rows); fixes a hard error on Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:55:51 +05:30
Mihir Kandoi
3d9b704730 Merge pull request #56224 from mihir-kandoi/pg-production-planning-report
fix(manufacturing): Production Planning report GROUP BY Postgres-valid
2026-06-21 09:54:49 +05:30
nishkagosalia
debe1855c6 fix: disarding stock entry fix 2026-06-21 09:54:20 +05:30
Mihir Kandoi
598864f0be fix(controllers): make update_variant_attribute_values Postgres-valid (drop UPDATE..JOIN)
update_variant_attribute_values (propagates renamed Item Attribute Values to
variant items) used a qb UPDATE ... JOIN. Postgres has no UPDATE..JOIN syntax,
so renaming an Item Attribute Value errored on Postgres.

Rewrite as a correlated UPDATE that restricts to variant items via a subquery
on the parent (item_variant_table.parent.isin(variant Items)) instead of
joining the Item table. MariaDB behaviour is unchanged.

Covered by the existing test_item.test_rename_attribute_value_updates_variants
and test_swapped_attribute_value_renames_update_variants, which errored on
Postgres before and now pass on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:46:43 +05:30
Mihir Kandoi
a483177690 fix(projects): make Project timeline GROUP BY Postgres-valid
get_timeline_data grouped Timesheet Detail by Date(from_time) but selected
UnixTimestamp(from_time) (the full timestamp, ungrouped). MariaDB
arbitrary-picks a row's timestamp; Postgres rejects it ("must appear in the
GROUP BY clause"), so the Project timeline (calendar heatmap) is broken on PG.

Select UnixTimestamp(Date(from_time)) — the day's epoch — which is the
timeline key and matches the GROUP BY. CurDate() - Interval(years=1) is
portable and kept as-is.

Adds a test (no coverage existed) that records a timesheet against a project
and asserts get_timeline_data returns day-bucketed counts, on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:44:48 +05:30
Mihir Kandoi
469d58d1f4 fix(manufacturing): make Production Planning report GROUP BY Postgres-valid
get_purchase_details grouped Purchase Order Item by (item_code, warehouse)
while selecting `qty` ungrouped/unaggregated. MariaDB arbitrary-picks one
row's qty; Postgres rejects the query ("must appear in the GROUP BY clause"),
so the report is broken on Postgres.

Sum the qty per item+warehouse ({"SUM": "qty"}). The column is the "Arrival
Qty" (quantity on order arriving) display figure; summing the open PO lines is
the meaningful planning number, and is deterministic vs MariaDB's arbitrary
single-line pick (which only differed when an item+warehouse had multiple open
PO lines).

Adds a test (no test file existed) that creates a Work Order plus two PO lines
for a BOM raw material and asserts the report runs and reports arrival_qty = 7
(3 + 4), on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 09:35:56 +05:30
Mihir Kandoi
f8359c91b2 Merge pull request #56221 from mihir-kandoi/pg-stock-entry
fix(stock): convert get_used_alternative_items to query builder (Postgres)
2026-06-21 08:09:27 +05:30
Mihir Kandoi
59dd3fe84e Merge pull request #56218 from mihir-kandoi/pg-stock-ledger
fix(stock): stock_ledger raw SQL → qb + case-insensitive serial matching (Postgres)
2026-06-21 08:00:17 +05:30
Mihir Kandoi
100eefc146 Merge pull request #56219 from mihir-kandoi/pg-repost-item-valuation
fix(stock): Repost Item Valuation dedup Postgres-valid (TIMESTAMP → CombineDatetime)
2026-06-21 07:59:23 +05:30
Mihir Kandoi
51448a2bda Merge pull request #56217 from mihir-kandoi/pg-controllers-buying-itemvariant-trends
refactor(controllers): buying_controller + item_variant + trends Postgres validity
2026-06-21 07:58:38 +05:30
Mihir Kandoi
d707fb541d Merge pull request #56216 from mihir-kandoi/pg-controllers-queries
refactor(controllers): queries.py search handlers raw SQL → qb (Postgres)
2026-06-21 07:55:35 +05:30
Mihir Kandoi
ea025b6b61 Merge pull request #56211 from mihir-kandoi/pg-project-update-daily-reminder
fix(projects): repair project_update daily_reminder + convert to ORM (Postgres)
2026-06-21 07:53:30 +05:30
Mihir Kandoi
025f0db7d7 Merge pull request #56215 from mihir-kandoi/pg-controllers-budget-subcon
fix(controllers): budget GROUP BY + subcontracting bool-OR Postgres validity
2026-06-21 07:52:00 +05:30
Mihir Kandoi
0d8abba0d8 Merge pull request #56214 from mihir-kandoi/pg-controllers-return-stock
refactor(controllers): sales/purchase return + stock_controller raw SQL → qb/ORM (Postgres)
2026-06-21 07:51:13 +05:30
Mihir Kandoi
f8ae4f99af Merge pull request #56212 from mihir-kandoi/pg-controllers-selling-status-website
refactor(controllers): selling/status_updater/website raw SQL → qb/ORM (Postgres)
2026-06-21 07:46:39 +05:30
Mihir Kandoi
f8f6c444c8 Merge pull request #56213 from mihir-kandoi/pg-customer-name-pg-extract
fix(selling): make Customer name de-duplication work on Postgres
2026-06-21 07:45:37 +05:30
Mihir Kandoi
a1f7bf8195 Merge pull request #56210 from mihir-kandoi/pg-authcontrol-boot
refactor(postgres): Authorization Control + startup boot raw SQL → qb/ORM
2026-06-21 07:44:55 +05:30
Mihir Kandoi
08dd8cb9da Merge pull request #56209 from mihir-kandoi/pg-stock-batch-report-item-attribute
fix(stock): Available Batch report GROUP BY + ItemAttribute raw SQL→qb (Postgres)
2026-06-21 07:43:07 +05:30
Mihir Kandoi
f14610e31b Merge pull request #56208 from mihir-kandoi/pg-work-order-stock-report-groupby
fix(manufacturing): make Work Order Stock report GROUP BY Postgres-valid
2026-06-21 07:42:06 +05:30
Mihir Kandoi
3ce0c23513 Merge pull request #56220 from mihir-kandoi/pg-item
fix(stock): convert item.py raw SQL → qb/ORM (Postgres)
2026-06-21 07:36:59 +05:30
Mihir Kandoi
e8acc00921 Merge pull request #56207 from mihir-kandoi/pg-buying-reports-groupby
fix(buying): make Procurement Tracker & PO Analysis reports Postgres-valid (GROUP BY)
2026-06-21 07:32:58 +05:30
Mihir Kandoi
74368bc744 fix(stock): convert get_used_alternative_items to query builder
get_used_alternative_items built its WHERE with f-string interpolation of
subcontract_order / subcontract_order_field / work_order (a SQL-injection risk)
and used a raw implicit comma cross-join. Convert to frappe.qb with an
inner_join on sted.parent == ste.name and parameterised conditions. The raw
SELECT listed sted.conversion_factor twice; the qb version selects it once.
Engine-portable and MariaDB-identical.

Surgical re-apply: the rest of stock_entry.py (the services/ package layout and
other develop-only logic) is untouched.

Adds a test that substitutes an alternative item in a work order's transfer
entry and asserts get_used_alternative_items returns the mapping, on MariaDB
and Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:32:54 +05:30
Mihir Kandoi
b85c79c3e0 fix(stock): convert item.py raw SQL to qb/ORM (Postgres-valid)
Convert the raw frappe.db.sql statements in Item to frappe.qb / ORM:
validate_barcode duplicate check (-> frappe.get_all), stock_ledger_created
(-> frappe.db.exists), update_item_price + the BOM/BOM Item/BOM Explosion
description updates + check_stock_uom_with_bin's Bin UOM update (-> frappe.qb
.update), on_trash Bin/Item Price deletes (-> frappe.db.delete),
check_stock_uom_with_bin's bin lookup (-> frappe.get_all with or_filters), and
get_uom_conv_factor's self-join (-> frappe.qb).

The one genuine Postgres break is validate_duplicate_item_in_stock_reconciliation:
its raw query used `HAVING records > 1`, referencing the SELECT alias, which
Postgres rejects. The qb version uses `HAVING Count("*") > 1`.

Surgical re-apply (not a whole-file port): develop's opening-stock-reconciliation
flow (set_opening_stock / create_opening_stock_reconciliation /
make_opening_stock_entry) is preserved, and get_timeline_data keeps develop's
CurDate()-Interval form (valid on both engines), so the Interval/CurDate/
SerialBatchCreation imports are retained.

Verified: test_item 38/38 on MariaDB. Added a merge-rename test exercising the
HAVING query (validate_duplicate_item_in_stock_reconciliation) which passes on
MariaDB and Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:30:07 +05:30
Mihir Kandoi
3f360dde3a fix(stock): convert stock_ledger raw SQL to qb + case-insensitive serial match (Postgres)
Convert four raw frappe.db.sql statements to frappe.qb:
- set_as_cancel (UPDATE -> frappe.qb.update)
- the invalid-serial-no incoming_rate lookup
- get_valuation_rate's last-valuation lookup
- get_future_sle_with_negative_qty

The serial-no comparisons (invalid-serial lookup and the get_stock_ledger_entries
condition builder, which stays raw) are wrapped in lower()/Lower() so serial
matching is case-insensitive on Postgres too -- MariaDB's collation already is,
so this is a no-op there. Deterministic creation/name tiebreakers are added to
the "ORDER BY posting_date DESC LIMIT 1" lookups so Postgres picks the same row
MariaDB did.

Surgical re-apply (not a whole-file port): develop's reposting valuation-recalc
clause (`recalculate_valuation_rate`) in update_entries_after and the
already-shipped Min()-wrapped get_items_to_be_repost GROUP BY are preserved. The
dynamic-condition / row-locking raw queries (get_previous_sle,
get_stock_ledger_entries builder, get_future_sle_with_negative_batch_qty, the
qty_shift UPDATE) are intentionally left raw.

Verified: full test_stock_ledger_entry suite 22/22 on MariaDB; added focused
tests for set_as_cancel / get_valuation_rate / get_future_sle_with_negative_qty
that pass on MariaDB and Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:08:21 +05:30
Mihir Kandoi
be213d9d3d fix(stock): make Repost Item Valuation dedup Postgres-valid (TIMESTAMP→CombineDatetime)
deduplicate_similar_repost used a raw UPDATE with the MySQL-only two-arg
TIMESTAMP(posting_date, posting_time) constructor, which is invalid on Postgres.

Convert the UPDATE to frappe.qb and replace TIMESTAMP() with CombineDatetime
on the column (portable, and preserves the original NULL semantics so rows with
a NULL posting_time stay excluded); the right-hand side is this document's own
always-set posting datetime, computed in Python via get_combine_datetime to
avoid wrapping literals in a SQL datetime function.

Surgical re-apply: develop's recalculate_valuation_rate field /
_recalculate_valuation_rate method / repost() branch are left intact.

The existing test_repost_item_valuation.test_deduplication directly exercises
this UPDATE; it errors on develop's Postgres and now passes on MariaDB and
Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:06:16 +05:30
Mihir Kandoi
d065a18d16 fix(controllers): make trends queries Postgres-valid (SUM(CASE), GROUP BY)
The *_trends reports (Sales/Purchase Order/Invoice, Delivery Note, etc.) built
raw SQL that is invalid on Postgres:

- `SUM(IF(...))` -> `SUM(CASE WHEN ... ELSE NULL END)` (IF is MySQL-only).
- Loose GROUP BY: each based_on `group by` listed only the key column while the
  SELECT also returned name/territory/group/currency columns. Widen the GROUP BY
  to include every selected non-aggregated column so the query is valid on
  Postgres.
- Add a based_on_key (the first group-by column) for the group-by detail
  subqueries, which equate against a single column (a multi-column group_by
  spliced into an equality produced malformed SQL on both engines).

Behaviour note: widening the GROUP BY can split one based-on group into multiple
report rows when the snapshot columns (territory, renamed customer/item) differ
across transactions, vs MariaDB's previous one-arbitrary-row-per-group. Grand
totals are unchanged (calculate_total_row); per-group subtotals become
deterministic partial sums. This is the accepted widen-vs-arbitrary-pick
tradeoff.

Adds a test (no test file existed) running Sales Order Trends with a group_by,
exercising the widened GROUP BY / based_on_key / SUM(CASE) on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:02:29 +05:30
Mihir Kandoi
af2f53bee1 refactor(controllers): convert make_variant_item_code lookup to query builder
make_variant_item_code used a raw frappe.db.sql left join over Item Attribute /
Item Attribute Value. Convert to frappe.qb. The attribute_value comparison
casts the param with cstr() so Postgres does not error on `varchar = numeric`
for numeric attributes (where that side is irrelevant, since numeric_values == 1
already satisfies the OR). MariaDB-identical.

Surgical re-apply: develop's get_attribute_value_renames /
update_variant_attribute_values helpers and the Case import are preserved.
Covered by test_item_variant on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:02:29 +05:30
Mihir Kandoi
08f39c5345 refactor(controllers): convert BuyingController raw SQL lookups to ORM
- Asset Movement deletion: raw implicit-join select -> frappe.get_all on
  Asset Movement Item (pluck="parent").
- validate_item_type: raw `name in (...)` select -> frappe.get_all with an
  `in` filter (pluck="item_code").

Both are engine-portable, MariaDB-identical. Surgical re-apply: develop's
actual-tax distribution rewrite (distribute_actual_tax_amount / get_tax_details)
is preserved (the staging branch predated it).

validate_item_type runs on every Purchase Receipt validation (covered by
test_asset.test_purchase_asset on both engines); the Asset Movement deletion
is covered by the asset cancellation flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:02:29 +05:30
Mihir Kandoi
957f9d866a refactor(controllers): convert queries.py search handlers to qb/ORM (Postgres)
Convert eight raw frappe.db.sql search handlers to frappe.qb / frappe.qb.get_query
(which applies user-permission match conditions): employee_query, lead_query,
tax_account_query, bom, warehouse_query, get_batch_numbers, get_purchase_receipts
and get_purchase_invoices. Removes the MySQL-only get_match_cond/get_filters_cond
string building and ifnull usage.

The genuine Postgres break is get_project_name: it used CustomFunction("IF")
which emits a literal IF() (invalid on Postgres). Switch it to Case().

Surgical re-apply (not a whole-file port): develop's case-insensitive
Lower() ordering in item_query and get_project_name is preserved (the staging
branch reverted it), item_query is otherwise left untouched, and the Lower
import is retained.

Existing test_queries tests cover the converted handlers and now pass on
Postgres (test_project_query errors on develop). Adds smoke tests for the
three previously-untested handlers (batch numbers / purchase receipts /
purchase invoices).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:50:08 +05:30
Mihir Kandoi
8138f5aecd refactor(controllers): convert StockController future-SLE/GL checks to qb/ORM
- make_gl_entries_on_cancel: raw GL Entry existence select -> frappe.db.exists.
- future_sle_exists: raw GROUP BY count -> frappe.qb Count with Criterion.any,
  and get_conditions_to_validate_future_sle builds qb Criterion objects
  (warehouse == x & item_code.isin(...)) instead of escaped SQL strings.

Parity-preserving and valid on Postgres. Surgical re-apply: develop's
check_item_quality_inspection fix (`return items if doctype == "Stock Entry"
else []`) is preserved (the staging branch predated and would have reverted
it).

Adds a test asserting future_sle_exists detects a later SLE for the same
item/warehouse on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:40:30 +05:30
Mihir Kandoi
465446bb79 refactor(controllers): convert sales/purchase return lookups to qb/ORM
validate_returned_items used a raw frappe.db.sql with a string-built column
list (and a separate Packed Item select); get_already_returned_items used a
raw GROUP BY sum. Convert both to frappe.get_all / frappe.qb (Sum(Abs(...))
with an explicit groupby). The qb GROUP BY mirrors the original
`group by item_code, <field>`, so it is parity-preserving (not a behaviour
change) and valid on Postgres.

Surgical re-apply: develop's `is_debit_note = 0` credit-note fix in
make_return_doc is preserved (the staging branch predated and would have
reverted it).

Adds a test (Delivery Note -> sales return) exercising validate_returned_items
and get_already_returned_items on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:40:30 +05:30