Commit Graph

59153 Commits

Author SHA1 Message Date
Mihir Kandoi
a0bbca166f Merge pull request #56389 from frappe/revert-56330-pg-queries-locate-case
Revert "fix(controllers): case-insensitive employee/lead/bom search ranking on Postgres"
2026-06-23 22:29:50 +05:30
Mihir Kandoi
4fb781ae54 Merge pull request #56388 from frappe/revert-56380-pg-get-item-price-null-order
Revert "fix(stock): make get_item_price NULL ordering match across engines (Postgres)"
2026-06-23 22:26:45 +05:30
Mihir Kandoi
e4e6e52a4d Revert "fix(controllers): case-insensitive employee/lead/bom search ranking on Postgres" 2026-06-23 22:02:17 +05:30
Mihir Kandoi
c868de324d Revert "fix(stock): make get_item_price NULL ordering match across engines (P…"
This reverts commit 116ef44ddb.
2026-06-23 22:02:00 +05:30
Shllokkk
934b1ff7dd Merge pull request #56337 from Shllokkk/cust-supp-dashboard
fix: show contextual balance label on party dashboard for net balances
2026-06-23 21:22:35 +05:30
Diptanil Saha
0ab812c3ec feat(crm_settings): enable frappe crm data synchronization (#56268) 2026-06-23 21:14:03 +05:30
Mihir Kandoi
116ef44ddb fix(stock): make get_item_price NULL ordering match across engines (Postgres) (#56380)
get_item_price orders Item Price rows by valid_from DESC and takes LIMIT 1 to
pick the most-recent applicable price. NULL-valid_from rows are kept (the
transaction-date guard uses IfNull(valid_from, '2000-01-01')), and MariaDB
sorts NULL last for DESC while PostgreSQL defaults to NULLS FIRST — so when an
item/price_list/uom has both a dated price and a NULL-valid_from price,
PostgreSQL returns the NULL one and MariaDB the most-recent dated one, a silent
price divergence.

Wrap the sort key in IfNull(valid_from, '1900-01-01') so the NULL row sorts
last on both engines. MariaDB already placed it last for DESC, so its pick is
unchanged. Same NULL-ordering class fixed in point_of_sale.get_items (#56378).
2026-06-23 14:55:44 +00:00
Mihir Kandoi
8591a0b6ad Merge pull request #56378 from mihir-kandoi/pg-audit13-fixes
fix(postgres): three parity fixes — POS NULL ordering, traceability div-by-zero, LIKE on non-text
2026-06-23 20:10:20 +05:30
Mihir Kandoi
8960e3ff4a fix(accounts): cast non-text Account fields for LIKE filters (Postgres)
Financial Report Template calculation_formula filters are user-authored and
only validated for field existence + operator membership, not that a
like/ilike operator targets a text field. A filter such as
["is_group", "like", "1"] builds `is_group ILIKE '%1%'`; PostgreSQL has no
LIKE/ILIKE operator for a smallint/int/numeric column
(`operator does not exist: smallint ~~* unknown`) and aborts the report, while
MariaDB implicitly casts the numeric column to text and matches.

For like-family operators, cast a numeric/Check Account field to varchar
(`Cast_(field, "varchar")`), reproducing MariaDB's implicit numeric->text
coercion on both engines. Text-field filters (the normal account_name/
account_number case) are left untouched, so MariaDB output is unchanged.
2026-06-23 19:38:27 +05:30
Mihir Kandoi
3859919263 fix(stock): guard traceability qty division against a zero divisor (Postgres)
get_materials divides stock_entry_detail.qty by a CASE that returns
fg_completed_qty when it is > 0 and otherwise the injected sabb_data.qty. The
code explicitly anticipates fg_completed_qty <= 0 (the else branch), and
neither fg_completed_qty nor sabb_data.qty is constrained non-zero, so the
divisor can be 0. MariaDB returns NULL for x/0; PostgreSQL raises
`division by zero` and aborts the report. Wrapping the CASE in NullIf(..., 0)
makes the divisor NULL instead of 0 — unchanged on MariaDB, valid on Postgres.
2026-06-23 19:38:27 +05:30
Mihir Kandoi
20e6a6e149 fix(selling): make POS item-price NULL ordering match across engines (Postgres)
POS get_items keeps Item Price rows with a NULL valid_from (open-ended base
price) alongside dated rows, orders by valid_from DESC, then picks the first
matching UOM positionally via next()/[0]. MariaDB sorts NULL last for DESC, so
a dated override wins; PostgreSQL defaults to NULLS FIRST for DESC, so the
NULL-valid_from base price wins instead — the POS shows a different
price_list_rate/currency on the two engines for an item that has both an
undated standing price and a dated price.

Coalesce(valid_from, "1900-01-01") in the ORDER BY forces the NULL row to sort
last on both engines. MariaDB already placed it last for DESC, so its output is
unchanged; PostgreSQL now picks the same dated override.
2026-06-23 19:38:25 +05:30
Mihir Kandoi
3d00c93822 fix(stock): keep item-search ordering for Quality Inspection on Postgres (#56372)
The Quality Inspection item link search builds a distinct, paginated
get_query with order_by="items.item_code". frappe's db_query silently drops
the ORDER BY for a distinct query on Postgres, so with offset/limit the
results come back in a different order AND a different page slice than MariaDB.

Append the ordering to the built query instead of passing order_by: item_code
is already in the DISTINCT select list, so ORDER BY on it is valid under
DISTINCT on Postgres, and it now applies before LIMIT on both engines. MariaDB
output is unchanged (it was already ordered by item_code). The items child
field is guarded for None so a doctype without it degrades gracefully rather
than raising AttributeError.
2026-06-23 14:05:35 +00:00
Mihir Kandoi
9fdeb5f991 fix(accounts): make two Query Report SQLs valid on Postgres (loose GROUP BY + no-op ORDER BY) (#56369)
* fix(accounts): wrap loose finance_book in max() in Trial Balance (Simple) (Postgres)

The Trial Balance (Simple) Query Report selects `finance_book` but groups only
by `fiscal_year, company, posting_date, account`. PostgreSQL rejects the
non-grouped, non-aggregated column:

    column "tabGL Entry.finance_book" must appear in the GROUP BY clause or be
    used in an aggregate function

MariaDB tolerates it and returns an arbitrary finance_book per group.

Wrapping it in `max(finance_book)` keeps the row count identical (the GROUP BY
is unchanged) and makes PostgreSQL valid. Adding finance_book to GROUP BY would
split each group into N rows and change the MariaDB row count, so it is not an
option. This replaces MariaDB's previously arbitrary finance_book value with a
deterministic one (the only sanctioned MariaDB-output change); the row count is
preserved.

* fix(accounts): make Sales Partners Commission valid on Postgres (ORDER BY + div-by-zero)

The Sales Partners Commission Query Report had two PostgreSQL problems:

1. It ended with `ORDER BY "Total Commission:Currency:120"`, but the alias the
   query produces is `"Total Commission:Currency:170"` (width 170, not 120), so
   the ORDER BY never referenced a real output column. On MariaDB a double-quoted
   token is a string literal — a no-op sort that never errored. On PostgreSQL a
   double-quoted token is an identifier, so it errors with
   `column "Total Commission:Currency:120" does not exist` (and single-quoting it
   instead trips `non-integer constant in ORDER BY`). Since the clause was always
   a no-op on MariaDB, it is removed — MariaDB's group-order output is unchanged
   and the report runs on PostgreSQL.

2. `sum(total_commission)*100 / sum(amount_eligible_for_commission)` has an
   unguarded divisor: the inner query filters `total_commission`/`base_net_total`
   but not `amount_eligible_for_commission`, so a partner whose rows sum to 0
   there makes MariaDB return NULL but PostgreSQL raise `division by zero`. Wrap
   it in `NULLIF(sum(amount_eligible_for_commission), 0)` — NULL on both engines.

Both verified live on MariaDB and PostgreSQL.
2026-06-23 13:53:38 +00:00
Mihir Kandoi
4aef3aa5b3 Merge pull request #56368 from mihir-kandoi/pg-divzero-nullif-guards
fix: guard division-by-zero divisors across reports/doctypes (Postgres parity)
2026-06-23 19:21:12 +05:30
Mihir Kandoi
df6437c49d Merge pull request #56335 from aerele/fix/skip-over-allowance-for-non-stock-items
fix: skip over-allowance qty validation for non-stock items
2026-06-23 19:14:42 +05:30
Mihir Kandoi
247d574283 Merge pull request #56370 from mihir-kandoi/pg-report-bool-and-fieldcase
fix: two Postgres hard errors — Check-vs-bool and capital-cased fieldname
2026-06-23 19:04:35 +05:30
Mihir Kandoi
3b1b315bf4 Merge pull request #56364 from aerele/job-card-mandatory
fix(manufacturing): make item_code mandatory in Job Card Item
2026-06-23 19:02:16 +05:30
Mihir Kandoi
07a86b33e6 fix(stock): guard non-stock valuation-rate division against a zero divisor (Postgres)
The non-stock-item valuation rate divides Sum(base_net_amount) by
Sum(qty * conversion_factor) over Purchase Invoice Items. A line with qty 0
zeroes the divisor. MariaDB returns NULL for x/0 (the caller maps it via
`or 0.0`); PostgreSQL raises `division by zero` and aborts. Wrap the divisor in
NullIf(Sum(qty * conversion_factor), 0): unchanged on MariaDB, valid on Postgres.
2026-06-23 18:54:54 +05:30
Mihir Kandoi
abe9e8becc fix(setup): use the stored lower-case fieldname in the Sales Person lookup (Postgres)
deactivate_sales_person looks up `frappe.db.get_value("Sales Person",
{"Employee": employee})`. The Sales Person field is `employee` (lower case);
the lookup runs with ignore_permissions, so the capital-cased key reaches the
query as the column `"Employee"`. PostgreSQL matches quoted identifiers
case-sensitively and errors:

    column "Employee" does not exist

MariaDB resolves `Employee` to the `employee` column regardless of case, so
using the stored `{"employee": employee}` selects the same row on MariaDB and
is valid on PostgreSQL.
2026-06-23 18:45:27 +05:30
Mihir Kandoi
54cfeaf357 fix(selling): compare the selling Check field with 1, not a Python bool (Postgres)
Customer-wise Item Price builds `ip.selling.eq(True)`, which renders
`WHERE "selling" = true`. `selling` is a Check field (smallint on Postgres),
and PostgreSQL has no `smallint = boolean` operator:

    operator does not exist: smallint = boolean

MariaDB accepts it because `true` aliases to `1`. Comparing with the integer
`1` (`ip.selling.eq(1)`) selects identical rows on MariaDB and is valid on
PostgreSQL.
2026-06-23 18:45:26 +05:30
Mihir Kandoi
71685532bd Merge pull request #56367 from mihir-kandoi/pg-asset-depr-coalesce-reship
fix(accounts): stop coalescing a DATE with an int in Asset Depreciations report
2026-06-23 18:45:00 +05:30
Mihir Kandoi
727f8d0967 fix(stock): guard production-plan received-qty division against a zero divisor (Postgres)
update_received_qty_if_from_pp divides received_qty by (qty / fg_item_qty) over
Purchase Order Items. Both qty and fg_item_qty are Float with no non-zero
constraint, so a zero qty (or fg_item_qty) drives the divisor to 0.

MariaDB returns NULL for x/0 (dropped by the surrounding Sum); PostgreSQL
raises `division by zero` and aborts the Purchase Receipt submit/cancel.
Wrapping both divisors in NullIf(..., 0) makes the zero row contribute NULL on
both engines, leaving MariaDB output unchanged.
2026-06-23 18:41:44 +05:30
Mihir Kandoi
334f1cc6f0 fix(stock): guard incorrect-serial valuation-rate division against a zero qty (Postgres)
The Incorrect Serial No Valuation report computes
stock_value_difference / actual_qty for every matching Stock Ledger Entry. A
valuation-only Stock Reconciliation of serialized/batched stock writes an SLE
with actual_qty = 0 and a non-zero stock_value_difference, and the or_filters
(serial_no / serial_and_batch_bundle set) do not exclude it.

MariaDB returns NULL for x/0; PostgreSQL raises `division by zero` and aborts
the report. Using the get_all nested NULLIF form
{"DIV": ["stock_value_difference", {"NULLIF": ["actual_qty", 0]}]} yields NULL
on both engines, leaving MariaDB output unchanged.
2026-06-23 18:41:22 +05:30
Mihir Kandoi
d48396cb11 fix(selling): guard lost-value ratio against a zero total (Postgres)
The Lost Quotations report's lost-value ratio divides Sum(base_net_total) by
total_value, a scalar Sum(base_net_total) subquery over the same lost
quotations. If every lost quotation in the period is zero-amount, total_value
is 0 while the grouped query still returns rows.

MariaDB returns NULL for x/0; PostgreSQL raises `division by zero` and aborts
the report. Wrapping the divisor in NullIf(total_value, 0) yields the same NULL
column on MariaDB and no error on PostgreSQL. (The sibling count ratio divides
by Count >= 1 in any returned row and is unaffected.)
2026-06-23 17:49:27 +05:30
Mihir Kandoi
affd2fd95d fix(manufacturing): guard average bin valuation-rate division against a zero divisor (Postgres)
_get_avg_valuation_rate_from_bins divides Sum(stock_value) by Sum(actual_qty).
The `Count(name) > 0` guard only proves a Bin row exists; Sum(actual_qty) can
still be 0 (stock depleted, or per-warehouse quantities cancelling out), and
the outer IfNull catches only NULL, not a 0 divisor.

MariaDB returns NULL for x/0 (then IfNull -> 0.0); PostgreSQL raises
`division by zero` and aborts BOM costing. Wrapping the divisor in
NullIf(Sum(actual_qty), 0) keeps the identical 0.0 result on MariaDB and
avoids the error on PostgreSQL.
2026-06-23 17:49:26 +05:30
Mihir Kandoi
297153264b fix(accounts): guard last-GLE exchange-rate division against a zero divisor (Postgres)
calculate_exchange_rate_using_last_gle divides (debit - credit) by
(debit_in_account_currency - credit_in_account_currency). The GL row is
re-selected by (voucher_type, voucher_no, account) ordered by posting_date
WITHOUT the "(debit_in_account_currency > 0) | (credit_in_account_currency > 0)"
filter the first query used, so the chosen row can have equal/zero account-
currency amounts, making the divisor 0.

MariaDB returns NULL for x/0 (the caller maps it via `or 0.0`); PostgreSQL
raises `division by zero` and aborts. Wrapping the divisor in NullIf(divisor, 0)
yields NULL on both engines, so MariaDB output is unchanged and PostgreSQL no
longer errors.
2026-06-23 17:49:12 +05:30
Mihir Kandoi
27ec5eabc6 fix(accounts): stop coalescing a DATE with an int in Asset Depreciations report
The Asset Depreciations and Balances report tested disposal status with
IfNull(asset.disposal_date, 0) != 0 / == 0 — coalescing the DATE column
disposal_date with the integer 0. frappe.qb renders this as
COALESCE("disposal_date", 0); PostgreSQL requires COALESCE arguments to
share a type and raises:

    psycopg2.errors.DatatypeMismatch: COALESCE types date and integer
    cannot be matched

The predicate is in the WHERE/CASE of every query the report runs (both
group_by=Asset Category and group_by=Asset), so the whole report errored
on PostgreSQL. MariaDB's IFNULL(date, 0) is permissive and worked.

Replace each comparison with the null-test form already used elsewhere in
this same file: IfNull(disposal_date, 0) != 0 -> disposal_date.isnotnull(),
== 0 -> disposal_date.isnull(). Semantically identical (a stored date is
never 0), valid on both engines, MariaDB output unchanged.
2026-06-23 17:43:49 +05:30
Lakshit Jain
7b659ee6af fix: whitelist get_payment_terms api (#55850)
Co-authored-by: Abdeali Chharchhoda <abdealiking786@gmail.com>
2026-06-23 17:37:15 +05:30
Lakshit Jain
4de1064ef6 Merge pull request #56345 from vorasmit/fix-get-tax-rate
fix: whitelist `get_tax_rate`
2026-06-23 17:36:59 +05:30
Mihir Kandoi
f751f80158 ci(postgres): flag division by a possibly-zero divisor in the compat guard (#56363)
Adds the division-by-zero divergence class to the PG-compat review tooling:
on a divisor that the data can drive to 0 (e.g. Sum(a)/Sum(b)), MariaDB
returns NULL for division by zero while PostgreSQL raises `division by zero`
and aborts the query. The portable fix is to wrap the divisor in
NullIf(divisor, 0), which yields NULL on both engines (matching MariaDB).

- .greptile/config.json: add it to the "would ERROR on PostgreSQL" list.
- .github/POSTGRES_COMPATIBILITY.md: document it under §1 (hard breaks).
- .github/helper/postgres_compat.py: note it in the docstring as a
  deliberately-not-statically-checked semantic divergence (data-dependent,
  like integer-division intent), so it stays a reviewer/Greptile concern.

Tooling-only; no source query changes. The instance fix shipped in #56361.
2026-06-23 12:00:50 +00:00
Mihir Kandoi
c79febf403 fix(projects): drop redundant distinct from portal project list (Postgres ordering) (#56362)
get_project_list builds a single-table query (no joins) with fields="*",
which always selects the unique PK `name`, so distinct=True can never
deduplicate any rows. It is a no-op on the result set for both engines.

It is not a no-op on ordering, though: frappe.db drops the ORDER BY clause
for distinct queries on Postgres (Postgres requires every ORDER BY term to
appear in the select list under DISTINCT), so the website project list came
back unordered on Postgres while MariaDB returned it ordered by `order_by`.

Removing the redundant flag leaves the MariaDB result and order untouched
and restores the same ordering on Postgres.
2026-06-23 11:47:38 +00:00
Mihir Kandoi
453b5cee21 fix(stock): guard batchwise valuation-rate division against a zero divisor (Postgres) (#56361)
fix(stock): guard batchwise valuation-rate division against a zero divisor

get_valuation_rate's batchwise fallback selects
Sum(stock_value_difference) / Sum(actual_qty). When a batch's non-current
Stock Ledger Entries net to zero quantity (equal received and issued) the
divisor Sum(actual_qty) is 0. On MariaDB x/0 yields NULL and the caller's
`if last_valuation_rate and last_valuation_rate[0][0] is not None` check
falls through to the next strategy; on PostgreSQL float division by zero
raises `division by zero`, aborting the query (and the transaction).

Wrap the divisor in NullIf(Sum(actual_qty), 0) so a zero divisor yields
NULL on both engines, matching MariaDB and preserving the caller's
is-not-None fall-through. (stock_value_difference is Currency and actual_qty
is Float, so the division was already float — no integer-truncation change.)
2026-06-23 11:39:58 +00:00
pandiyan
d7e9a97f8a fix(manufacturing): make item_code mandatory in Job Card Item
The item_code field in the Job Card Item child table was optional,
allowing job cards to be saved without a raw material item linked.
Set reqd=1 in the JSON and update the Python type annotation accordingly.
2026-06-23 16:56:50 +05:30
Mihir Kandoi
2f4e78f09e ci(postgres): flag COALESCE/IfNull of a typed column with a mismatched-type literal (#56358)
ci(postgres): teach the guard about COALESCE(date, int) type mismatch

New class found by the whole-repo audit (the Asset Depreciations report fix in this PR): IfNull/Coalesce of a typed column with a different-typed literal -- e.g. IfNull(date_col, 0) -> COALESCE(date, integer), which PostgreSQL rejects (DatatypeMismatch). Added to the Greptile config and POSTGRES_COMPATIBILITY.md (not statically checkable without column types).
2026-06-23 16:52:48 +05:30
pandiyan
733e24faef test: add tests for non stock item over billing against so/po 2026-06-23 16:38:38 +05:30
Mihir Kandoi
6a237f323f Merge pull request #56349 from mihir-kandoi/pg-compat-tooling-audit-learnings
ci(postgres): teach the PG-compat tooling the audit 6-9 divergence classes
2026-06-23 14:35:33 +05:30
Mihir Kandoi
d032e93f87 Merge pull request #56344 from mihir-kandoi/pg-mps-leadtime-intdiv
fix(manufacturing): keep MPS cumulative lead-time fractional across engines
2026-06-23 13:40:18 +05:30
NaviN
d1ffac36c1 fix(payment reconciliation): honour user permissions on accounting dimensions (#55803) 2026-06-23 13:36:29 +05:30
Mihir Kandoi
331f383777 ci(postgres): match CAST AS CHAR with nested parens in the checker
The [^)]* span stopped at the first inner ')', so CAST(ABS(col) AS CHAR) slipped through. Use a non-greedy .+? with re.S; still zero production false positives (verified). Addresses review feedback.
2026-06-23 13:24:49 +05:30
Mihir Kandoi
d225532595 test(manufacturing): make MPS lead-time fixture idempotent
Delete any existing Item Lead Time for the test item before inserting, so the test is re-runnable on a shared database (addresses review feedback).
2026-06-23 13:20:58 +05:30
Nishka Gosalia
ce4e56336e Merge pull request #56350 from nishkagosalia/gh-56067
fix: handling default company in purchase transactions created from project
2026-06-23 12:37:37 +05:30
nishkagosalia
359717115f fix: company default handling in purchase transactions made from project 2026-06-23 12:33:31 +05:30
Mihir Kandoi
fc9544435e ci(postgres): teach the PG-compat tooling the audit 6-9 divergence classes
The whole-repo MariaDB<->PostgreSQL audits surfaced classes the checker and
review guide did not yet cover. Add them:

Static checker (.github/helper/postgres_compat.py) - new mechanical breaks:
- .rlike() / raw RLIKE: frappe rewrites REGEXP->~* on Postgres but NOT RLIKE.
- Cast(x, "char") / raw CAST AS CHAR: bare CHAR is character(1) on Postgres
  and truncates multi-digit values; use "varchar".
(Both flag zero production code; the only repo hit is in patches/, which the
hook already excludes.)

Greptile config + POSTGRES_COMPATIBILITY.md - new semantic/hard classes:
- aggregate (Sum/Count) selected next to bare columns with no GROUP BY at all.
- .like()/LIKE on a non-text column (bigint ILIKE) -> Cast_ to varchar.
- get_all(fields=["CapitalCase"]) identifier-case (extends the get_value case).
- bool into a Check column via qb.update().set() (extends set_value/db_set).
- int/int division: float a literal (col/1440 -> col/1440.0).
- Concat over a nullable column leaking a bare prefix on Postgres.
- clarify REGEXP/.regexp() is translated but RLIKE/.rlike() is not.
2026-06-23 12:22:22 +05:30
pandiyan
553b55b2f0 fix: skip qty over-allowance check for non-stock items only 2026-06-23 11:00:15 +05:30
Nabin Hait
ca908b69cf Merge pull request #56246 from nabinhait/commission-fields-depends-on-sales-partner
fix(selling): hide commission fields without a sales partner and stop copying them
2026-06-23 10:42:16 +05:30
vorasmit
48d5e8732b fix: whitelist get_tax_rate 2026-06-23 10:38:58 +05:30
Mihir Kandoi
28b5efcbe1 fix(manufacturing): keep MPS cumulative lead-time fractional across engines
get_item_lead_time in Master Production Schedule computes
manufacturing_time_in_mins / 1440 + purchase_time + buffer_time. As in the
MRP report, manufacturing_time_in_mins is an Int column and 1440 an int
literal, so the division truncates on PostgreSQL (720/1440 -> 0) while
MariaDB yields 0.5. The value is summed over the BOM tree, ceil'd, and
drives the planned order-release date, so it diverged by engine.

Use a float numerator (1440.0). MariaDB output is unchanged; PostgreSQL
now matches it.
2026-06-23 10:38:54 +05:30
Mihir Kandoi
b0b20edd3e Merge pull request #56343 from mihir-kandoi/pg-mrp-leadtime-intdiv
fix(manufacturing): keep MRP lead-time fractional across engines
2026-06-23 10:26:51 +05:30
Mihir Kandoi
060b0df55e Merge pull request #56342 from mihir-kandoi/pg-audit6-mariadb-corrections 2026-06-23 10:02:19 +05:30
Mihir Kandoi
75030bab0f Merge pull request #56341 from mihir-kandoi/pg-audit6-hard-errors 2026-06-23 10:02:06 +05:30