Commit Graph

59358 Commits

Author SHA1 Message Date
Mihir Kandoi
c4e1fe274b Merge pull request #56055 from aerele/fix/support-71415
fix: disable is_debit_note while creating credit note
2026-06-18 22:23:39 +05:30
Mihir Kandoi
1a56f3b032 Merge pull request #56105 from mihir-kandoi/pg-parity-other-class
fix(postgres): MariaDB/Postgres parity in pick-list, serial match, null ordering & link-query ranking
2026-06-18 22:22:16 +05:30
Mihir Kandoi
08375a9e2f Merge pull request #56110 from mihir-kandoi/pg-accounts-gl-core
refactor(postgres): port GL & ledger-core queries to the query builder
2026-06-18 22:02:05 +05:30
Mihir Kandoi
fa378e2d7a test(sales_payment_summary): exercise the POS customer filter
Address review (#56113): add a customer filter to the is_pos test so the
customer-subquery fix is covered (a.customer was unselected before and errored).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:34:37 +05:30
Mihir Kandoi
006a65e873 refactor(postgres): finish budget qb conversion (validate_expense_against_budget)
Address review / semgrep (#56112): convert the last raw f-string query
(budget_records, with a dynamic dimension column + tree EXISTS condition) to
frappe.qb, clearing the sql-format-injection finding. Also switch the two
remaining implicit comma-joins (get_requested_amount / get_ordered_amount) to
explicit .join().on() for consistency. test_budget 23/23 on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:32:46 +05:30
Mihir Kandoi
e7b135b51e fix(postgres): aggregate bare account in pos_closing payments; explicit limit on gl-entry fetch
Address review (#56111):
- pos_closing get_payments grouped by mode_of_payment but selected a bare account ->
  Postgres GroupingError. Wrap in Max() (deterministic, both engines agree; account is
  consumed downstream for the change-amount adjustment). test_pos_closing_entry 9/9 both engines.
- get_voucherwise_gl_entries: add limit=0 to make the unbounded fetch explicit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:29:00 +05:30
Mihir Kandoi
dabc94ed06 Merge pull request #56101 from mihir-kandoi/pg-arbitrary-representative
fix(selling): split multi-order invoice amount across its sales orders (payment terms status)
2026-06-18 21:06:33 +05:30
Mihir Kandoi
1f4702bde7 fix(crm): skip rows with no first-contact date in lead conversion time
Address review (#56105): when there's no matching communication, first_contact is
None and date_diff(invoice_date, None) treats None as today, giving a wrong
(negative) duration. Skip the entry instead, mirroring the communication_count guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:04:33 +05:30
Mihir Kandoi
4708ac4e3d fix(postgres): port sales_payment_summary to qb and make POS data Postgres-valid
Converts the report's raw f-string SQL (incl. 3-way UNIONs) to frappe.qb. Split out of #56082.

The non-POS path is MariaDB-identical. get_pos_invoice_data had a loose GROUP BY that
errors on Postgres; line-level warehouse/cost_center/mode_of_payment are now Max()
(deterministic, both engines agree), the unused item_code dropped, and customer added to
the invoice subquery so the customer filter works (it referenced a column the subquery
never selected). + a test covering the previously-untested is_pos path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:01:35 +05:30
Mihir Kandoi
996a02180b fix(postgres): port budget queries to qb and fix requested-amount aggregation
Converts budget.py raw SQL to frappe.qb for Postgres compatibility. Split out of #56082.

Mostly MariaDB-identical, with one behaviour change: get_requested_amount summed
Sum(qty) * <arbitrary rate> (a bare rate column, invalid on Postgres and wrong when an
item was requested at different rates). Now Sum(qty * rate) per line -- correct, and
identical on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:01:33 +05:30
Mihir Kandoi
d8a2f53a29 refactor(postgres): port accounts doctypes & match-condition helper to the query builder
Pure MariaDB-identical conversion for Postgres compatibility. Split out of #56082.

bank_clearance, pos_closing_entry, process_statement_of_accounts, party, utils, and
sales_invoice/services/pos converted to frappe.qb; bundles erpnext/utilities/query.py
(the get_match_conditions_qb helper, frappe#40075 follow-up) which
process_statement_of_accounts depends on. No behaviour change on MariaDB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:01:11 +05:30
Mihir Kandoi
13d06e77b4 refactor(postgres): port GL & ledger-core queries to the query builder
Pure MariaDB-identical conversion (raw frappe.db.sql -> frappe.qb / portable
functions) for Postgres compatibility. Split out of #56082.

general_ledger, gl_entry, gl_validator, period_closing_voucher, deferred_revenue,
process_payment_reconciliation + their tests. No behaviour change on MariaDB;
verified equivalent and the suites pass on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:00:55 +05:30
Mihir Kandoi
5787951ed1 fix(crm): guard first_contact result before indexing (lead conversion time)
Address review (#56105): the IS NOT NULL guard can return no rows (the count above
filters on sender, this query on recipients), so [0][0] would raise IndexError.
Fall back to None when empty, matching the prior behaviour for that case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 20:50:31 +05:30
Mihir Kandoi
3f6f3abf69 fix(selling): make multi-order invoice split sum exactly to the grand total
Address review (#56101): with 3+ orders the proportional shares could drift by a
sub-cent and not sum back to the grand total, leaving the last payment term
"Partly Paid". The last order (sorted, so MariaDB and Postgres agree) now absorbs
the residual: grand_total - sum(prior shares). Extended the test to three orders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 20:48:36 +05:30
Mihir Kandoi
055c58364a Merge pull request #56090 from mihir-kandoi/pg-bom-rowshape
fix(manufacturing): make arbitrary GROUP-BY picks deterministic in BOM & transfer queries
2026-06-18 20:46:30 +05:30
Mihir Kandoi
cfa6d286ad fix(postgres): other-class parity fixes (case-folding & null-ordering) for merged queries
Parity sweep findings in queries already merged in develop:

- pos_invoice.py: serial_no return-match matched case-sensitively on Postgres (case-insensitive
  on MariaDB). Replaced the get_all or_filters lookup with a qb query that case-folds both sides
  via Lower() (no-op on MariaDB).
- controllers/queries.py + pick_list.py: the Locate()-based relevance ranking in link-query
  ORDER BY is case-sensitive on Postgres (Strpos) vs case-insensitive on MariaDB (Locate), so
  autocomplete order differed. Lower() both arguments so the ranking matches on both engines.
- crm/lead_conversion_time.py: "first contact" used ORDER BY communication_date LIMIT 1 read by
  index; a NULL date sorts first on MariaDB but last on Postgres, changing the result. Added
  `communication_date IS NOT NULL` so both engines return the earliest real contact date.

Verified on MariaDB and Postgres: test_pick_list 40/40, test_pos_invoice 26/26 on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 20:36:30 +05:30
vorasmit
b9b402f2ec fix: tax.base_tax_amount as none when payment entry created using API 2026-06-18 20:15:03 +05:30
Mihir Kandoi
b04a9e25ff fix(stock): make pick_list link query valid on Postgres (GROUP BY joined column)
get_pick_list_query selects Sales Order.customer (a joined table's column) while
grouping only by Pick List.name. Postgres' functional-dependency relaxation applies
to a table's own primary key, not to a joined table's columns, so the query raises
GroupingError on Postgres. MariaDB arbitrary-picks and runs.

customer is already pinned to a single value by `WHERE Sales Order.customer = filter`,
so adding it to the GROUP BY is identical on MariaDB and valid on Postgres.

Test (errors with GroupingError on the old code on Postgres, passes on both engines):
- test_get_pick_list_query_postgres_valid: a submitted pick list for a customer is
  returned by the link query.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 20:14:31 +05:30
Mihir Kandoi
b1c6666d02 fix(selling): split multi-order invoice amount across its sales orders (payment terms status)
payment_terms_status_for_sales_order grouped invoice rows by `sii.parent` and took
`Max(sii.sales_order)`, so an invoice that bills several Sales Orders was credited
in full to one arbitrary order and the rest were starved of that payment.

get_so_with_invoices now returns one row per (invoice, sales_order) and splits the
invoice's grand total across the orders in proportion to each order's net line
amount on that invoice. A single-order invoice keeps the full grand total (ratio 1),
so the common case is unchanged; the split is pure Python over deterministic SQL,
so MariaDB and Postgres produce identical results (100% parity).

Test (fails on the old code, passes on both engines):
- test_invoice_billing_multiple_orders_splits_proportionally: one invoice billing two
  SOs 600/400 -> each order credited its share, summing to the grand total. Old
  Max(sales_order) collapsed the invoice onto one order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:44:31 +05:30
Mihir Kandoi
fe0465f16e fix(manufacturing): deterministic arbitrary-pick in BOM explosion & WO transfer tracking
Same class as the sub_assembly_queries / bom_stock_analysis fixes in this PR: two
more merged queries wrapped a non-functionally-dependent column in Max(), so the
engines can disagree and the value is wrong for the case the column drives.

bom_explosion._subitems_query — Max(is_phantom_item):
  Rows are grouped by item_code and get_subitems() drops any grouped row whose
  is_phantom_item is truthy. When one item_code is listed in a BOM both as a
  phantom sub-assembly and as a plain raw material, Max() returns 1 and the real
  raw material is silently dropped from the plan. Use Min(): an item is phantom
  only when EVERY line for it is phantom, so a real material is never lost.

required_items._material_transfer_qty_by_item — Max(original_item):
  original_item is the output dict key. The same item B can be transferred both
  for itself (original_item NULL) and as a substitute for required item A
  (original_item=A). Grouping by item_code alone with Max() merged the two and
  credited B's whole transfer to A, leaving B at 0. Group by
  (item_code, original_item) and accumulate into the keyed dict so each transfer
  is credited to the right required item (two rows can resolve to one key, e.g.
  A's own transfer and B-for-A, hence += not plain assignment).

Both were previously undefined SQL (loose GROUP BY); the fix makes MariaDB and
Postgres agree on the correct, deterministic value. Other Max()-wrapped columns
in these queries are functionally dependent on the grouped item and unchanged.

Tests (fail on the old code, pass on both engines):
- test_subitems_query_keeps_real_rm_listed_alongside_phantom
- test_transferred_qty_not_misattributed_between_item_and_its_substitute

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:03:26 +05:30
pandiyan
3ba8f690a4 fix: apply docstatus filter to exclude cancelled Work Orders in Serial No 2026-06-18 18:29:08 +05:30
rohitwaghchaure
47a9c54b70 Merge pull request #55931 from DipenFrappe/fix-rfq-accounting-dimensions
feat(rfq): add accounting dimensions support to Request for Quotation
2026-06-18 18:27:49 +05:30
Shllokkk
336307f287 Merge pull request #56087 from Shllokkk/pcv-je-validation
fix(journal entry): validate opening entry against pcv on save
2026-06-18 18:04:33 +05:30
Dipen Gala
79421bcfcc fix: set cost_center on RFQ item before submitting in test
The test was mutating an already-submitted RFQ, which raised
UpdateAfterSubmitError because cost_center lacks allow_on_submit.
Use do_not_submit=True, set cost_center on the draft, save, then submit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:21:26 +05:30
Shllokkk
e23a7883f3 fix(journal entry): validate opening entry against pcv on save 2026-06-18 16:57:00 +05:30
Shllokkk
deff5848ed Merge pull request #55504 from aerele/fix/soa-show-opening-entries
fix(accounts): allow process statement of account generation with opening entries
2026-06-18 16:44:25 +05:30
Mihir Kandoi
b579dbc1e6 fix(manufacturing): prefer the phantom line as bom_stock_analysis representative
Address review on #56090: get_bom_data groups components by item_code, so it
picks one representative BOM Item line for the (bom_no, is_phantom_item) pair.
Taking the first line by idx dropped the phantom flag when a non-phantom line
was listed before the phantom one, so explode_phantom_boms skipped the sub-BOM.

Keep one row per item_code (preserving the qty_per_unit total per component
rather than widening the GROUP BY), but make the representative phantom-
preferring: the first line, upgraded to the first phantom line if any exists.
A phantom sub-BOM is therefore never dropped due to line order, on either engine.

Adds test_phantom_explosion_when_phantom_line_is_not_first (phantom line at
idx 2) alongside the existing idx-1 case; both pass on MariaDB and Postgres and
the new one fails on the naive first-line representative.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:27:23 +05:30
Mihir Kandoi
41da9eb7fc fix(manufacturing): keep bom_no/is_phantom_item pair coherent in sub-BOM resolution
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>
2026-06-18 16:14:30 +05:30
Mihir Kandoi
1cc98a82ba fix(accounts): Journal Entry future payments mis-allocated to one invoice in AR
get_future_payments_from_journal_entry has no GROUP BY: the Sum() makes it an
implicit single-group aggregate, so every future-dated JE payment company-wide
collapses into ONE row keyed by an arbitrary (invoice, party). The Accounts
Receivable report then allocates the entire future sum against that one invoice and
shows zero future payment for every other invoice. This predates the postgres work
(MariaDB returned an arbitrary single row); the qb conversion only made the arbitrary
pick deterministic via Max().

Add an explicit GROUP BY (je.name, jea.reference_name, jea.party, jea.party_type,
je.posting_date, je.cheque_no) and drop the Max() wrappers, so each (JE, invoice,
party) is its own future-payment row -- matching the Payment Entry path and the
(invoice_no, party) keying the report's allocator already expects. Identical on
MariaDB and PostgreSQL.

Ships a JE-path future-payment test (one future JE paying two invoices -> each
invoice keeps its own future amount; fails on the old code with 0 != 50).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:53:57 +05:30
Mihir Kandoi
eb7f7f2124 Merge pull request #56081 from mihir-kandoi/pg-query-report-json
fix(postgres): make Query Report SQL portable across MariaDB and PostgreSQL
2026-06-18 15:18:49 +05:30
Nabin Hait
fcd312f205 refactor(sales_invoice): decompose SDBNB and change-amount GL methods
stock_delivered_but_not_billed_gl_entries (C/15) is split into a thin loop
plus _sdbnb_booking_for_item (eligibility + valuation), _is_sdbnb_reversal
(the account predicate) and _append_sdbnb_gl_entries (the two GL rows) —
guard clauses replace the nested continues. get_gle_for_change_amount now
uses the shared _amount_in_account_currency helper. No C-rank functions
remain in the file. Behaviour unchanged; characterization tests and the
full Sales Invoice suite (133) green.
2026-06-18 15:03:32 +05:30
Mihir Kandoi
3d4b50d37d fix(postgres): make Query Report SQL portable across MariaDB and PostgreSQL
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>
2026-06-18 14:59:01 +05:30
Nabin Hait
5de87f473e test(sales_invoice): characterize SDBNB and change-amount GL entries
Pin the two least-covered SalesInvoiceGLComposer methods before refactor:
- stock_delivered_but_not_billed_gl_entries: billing a perpetual Delivery
  Note via Sales Invoice reverses the SDBNB account into COGS for an equal
  amount.
- get_gle_for_change_amount: empty without change, mandatory-account error,
  and the debit-to / change-account entry pair.
2026-06-18 14:55:37 +05:30
rohitwaghchaure
31849f6029 Merge pull request #56079 from rohitwaghchaure/allow-negative-stock-for-batch-level
feat: allow negative stock at batch level
2026-06-18 14:31:09 +05:30
Nabin Hait
1f06f2e3a0 refactor(sales_invoice): simplify SalesInvoiceGLComposer GL builders
Extract two cross-cutting helpers used across the GL methods:
- _amount_in_account_currency: the repeated 'base if account is in company
  currency else transaction amount' ternary (customer, tax, item, POS and
  write-off entries).
- _return_aware_against_voucher: the return/self-outstanding against_voucher
  rule duplicated in the customer and POS entries.

Pull the per-item income and discount rows into their own builders and
flatten make_item_gl_entries with a guard clause. Complexity drops:
make_customer C11->B7, make_pos C12->B7, make_item C14->B10,
make_discount C13->B10, make_tax->A3. No behaviour change; entry dicts and
amounts are identical (full Sales Invoice suite green).

stock_delivered_but_not_billed_gl_entries is left for a coverage-first pass
(it is the least-tested GL branch).
2026-06-18 14:16:41 +05:30
Nabin Hait
3038ad8abe fix(sales_invoice): remove dead, non-functional POSService.get_warehouse
get_warehouse could never run: it filtered POS Profile on a non-existent
'user' column (users live in the applicable_for_users child table), and
embedded a Python bool (frappe.session["user"] == "") inside a query
builder predicate, which raises before reaching the database. It also
has no callers. Remove it and the now-unused msgprint import.
2026-06-18 14:00:07 +05:30
Khushi Rawat
dc202ac4a2 Merge pull request #56030 from Shllokkk/budget-distribution-grid-lock
fix: lock budget distribution table and guard against null distributi…
2026-06-18 13:58:07 +05:30
Rohit Waghchaure
ca07982ee0 feat: add batch-level option to allow negative stock for batch 2026-06-18 13:41:09 +05:30
Mihir Kandoi
f269f6a8d8 Merge pull request #56062 from mihir-kandoi/pg-accounts-pos
refactor(postgres): port Accounts POS, pricing & invoicing doctype queries to the query builder
2026-06-18 13:22:49 +05:30
Sudharsanan11
2ca1bdd8a7 test(stock): add test to validate the precision for additional cost amount 2026-06-18 12:12:05 +05:30
Sudharsanan11
cf338bb757 fix(stock): apply precision to the additional cost amount in stock entry 2026-06-18 12:12:05 +05:30
Khushi Rawat
bda7a8ced2 Merge pull request #54570 from khushi8112/item-opening-stock-dialog
feat: add opening stock dialog for stock items
2026-06-18 11:28:08 +05:30
Shllokkk
d37e5cd97d fix: lock budget distribution table and guard against null distribution rows 2026-06-18 11:23:39 +05:30
khushi8112
e57593fcf8 fix(item): add server-side guard for serial/batch items in make_opening_stock_entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 02:25:13 +05:30
khushi8112
c5b4a742b3 fix: so many conflicts 2026-06-18 02:05:24 +05:30
MochaMind
526f91f6b5 fix: Bosnian translations 2026-06-17 23:22:48 +05:30
MochaMind
4465ebaeb5 fix: Persian translations 2026-06-17 23:22:44 +05:30
Mihir Kandoi
88cb132fd1 refactor(postgres): port Purchase Invoice expense-account queries to the query builder
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:01:39 +05:30
Mihir Kandoi
d23677636d refactor(postgres): port Purchase Invoice GL composer queries to the query builder
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:01:38 +05:30
Mihir Kandoi
8e0ba50c4d refactor(postgres): port Purchase Invoice doctype queries to the query builder
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:01:37 +05:30