Commit Graph

58778 Commits

Author SHA1 Message Date
Nabin Hait
e2fbc48b9a Merge pull request #55898 from nabinhait/fix/stock-closing-entry-wrong-field
fix(stock): use correct field when reading previous stock closing balance
2026-06-19 14:36:21 +05:30
Mihir Kandoi
4180e29af4 Merge pull request #56130 from mihir-kandoi/pg-support
refactor(postgres): port Support module queries to the query builder
2026-06-19 13:52:10 +05:30
Mihir Kandoi
39eb34f333 refactor(postgres): address Greptile review on warranty_claim on_cancel
- Filter the parent Maintenance Visit's docstatus (mv.docstatus != 2) via a qb join, as
  the original SQL did, instead of the child Maintenance Visit Purpose row's docstatus.
  Synced in normal flows, but exactly faithful to the original intent.
- Add a limit(500) to bound the read on a cancellation path.

Adds two both-engine tests calling on_cancel directly: an active (non-cancelled) visit
blocks the claim cancel; with no referencing visit the claim is marked Cancelled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:13:14 +05:30
Mihir Kandoi
c6f9415e9d Merge pull request #56129 from mihir-kandoi/fix-test-workstation-warehouse
fix(tests): point _Test Workstation 1 fixture at an existing warehouse
2026-06-19 13:06:48 +05:30
Mihir Kandoi
f768778d81 refactor(postgres): port Support module queries to the query builder
Convert the remaining raw frappe.db.sql in the Support module to frappe.qb / ORM so the
queries run on PostgreSQL as well as MariaDB. Faithful conversions -- no MariaDB
behaviour change:

- issue.py, warranty_claim.py (Maintenance Visit lookup / make_maintenance_visit)
- reports: first_response_time_for_issues and issue_summary (GROUP BY on the grouped
  Date(creation)/based-on field + Avg/Count -- Postgres-valid), support_hour_distribution

Tests: existing issue suite (35) passes on both engines; adds both-engine tests for the
previously-untested warranty_claim mapper (3) and the three reports. All green on
MariaDB and PostgreSQL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:58:52 +05:30
Mihir Kandoi
c541bc9239 fix(tests): point _Test Workstation 1 fixture at an existing warehouse
BootStrapTestData.make_workstation() created _Test Workstation 1 referencing the
warehouse "_Test warehouse - _TC" (lowercase 'w'), but make_warehouse() never
creates that name -- it makes "_Test Warehouse - _TC" (capital W) and others.

On MariaDB the lowercase reference resolves to the capital warehouse via the default
case-insensitive collation, so it silently works. On PostgreSQL (case-sensitive) the
link cannot be found, so on a freshly-bootstrapped site make_workstation() raises
LinkValidationError: Could not find Warehouse: _Test warehouse - _TC, which aborts the
module-level BootStrapTestData() import and blocks every test extending ERPNextTestSuite.
It was masked only on sites where the workstation was already bootstrapped (make_records
skips existing) or where the lowercase name happened to exist from legacy data.

Point the fixture at the existing "_Test Warehouse - _TC". Verified on a freshly-reset
site: the buggy fixture raises the LinkValidationError on Postgres and passes after the
fix; MariaDB passes either way.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:47:19 +05:30
Nabin Hait
817c5007d9 Merge pull request #55899 from nabinhait/fix/margin-currency-rate-refresh
fix: don't convert item margin on exchange rate refresh
2026-06-19 12:42:21 +05:30
Nabin Hait
dfd0c85ba4 Merge pull request #56083 from nabinhait/refactor-si-pos-service
refactor(sales_invoice): tidy POSService (set_pos_fields + mode-of-payment queries)
2026-06-19 12:26:19 +05:30
Nabin Hait
8caaac96b6 Merge pull request #56086 from nabinhait/refactor-si-gl-composer
refactor(sales_invoice): simplify SalesInvoiceGLComposer GL builders
2026-06-19 12:03:21 +05:30
Nabin Hait
9f02c47592 refactor(sales_invoice): decompose POSService.set_pos_fields and dedupe MOP queries
set_pos_fields was a 97-line method (cyclomatic complexity E/36). Split
it into a small orchestrator plus focused helpers, each preserving the
exact for_validate semantics (A/5 after).

Collapse the three near-identical mode-of-payment query builders onto a
shared _enabled_mode_of_payment_query, and add type hints and docstrings.
Public signatures and return shapes are unchanged.
2026-06-19 11:58:43 +05:30
Nabin Hait
7f47c218ce test(sales_invoice): characterize POSService default and mode-of-payment logic
Pin the behaviour of POSService.set_pos_fields (POS-profile default
resolution and the for_validate guard) and the mode-of-payment query
helpers before refactoring them.
2026-06-19 11:57:40 +05:30
Nabin Hait
ae11b3b848 Merge pull request #56085 from nabinhait/fix-pos-get-warehouse
fix(sales_invoice): remove dead, non-functional POSService.get_warehouse
2026-06-19 11:52:41 +05:30
Mihir Kandoi
64e177df8b Merge pull request #56125 from mihir-kandoi/pg-crm 2026-06-19 10:44:48 +05:30
Mihir Kandoi
413ec60a3e refactor(postgres): address Greptile review on prospects report
- Fix N+1: the per-lead conversion issued 4 queries per lead (Opportunity, Quotation,
  Issue, Communication). Collect the reference documents for all leads in 3 bulk
  queries, then one Communication query per lead -> ~N+3 round-trips instead of 4N,
  matching the original single-query-per-lead cost.
- Constrain reference_doctype in the Communication lookup (names are unique only within
  a doctype), closing a latent cross-doctype name-collision gap the original also had.

Both-engine test still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:26:45 +05:30
Mihir Kandoi
6733681e93 Merge pull request #56124 from mihir-kandoi/pg-assets 2026-06-19 10:22:21 +05:30
Mihir Kandoi
5104007d12 refactor(postgres): port CRM module queries to the query builder
Convert the remaining raw frappe.db.sql in the CRM module to frappe.qb / ORM so the
queries run on PostgreSQL as well as MariaDB. Faithful conversions -- no MariaDB
behaviour change:

- opportunity.py, doctype/utils.py (get_last_interaction)
- reports: campaign_efficiency, first_response_time_for_opportunity (GROUP BY on the
  grouped Date(creation) + Avg -- Postgres-valid), lead_conversion_time,
  prospects_engaged_but_not_converted

lead_conversion_time also keeps the IS NOT NULL communication-date guard (forward-port
of the fix already on develop). Also drops invalid backtick notation from two get_all
order_by clauses in doctype/utils.py (order_by="`creation` DESC"), which frappe's
query engine rejects -- a latent failure on both engines, surfaced by the new test.

Tests: existing opportunity suite plus new both-engine tests for the four previously
untested reports/utils. All green on MariaDB and PostgreSQL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:15:20 +05:30
Mihir Kandoi
4bc3420b21 refactor(postgres): address Greptile review on Assets conversions
- cancel_movement_entries: filter the parent Asset Movement's docstatus via a qb join
  (as the original SQL did), instead of the child Asset Movement Item.docstatus. Behaviour
  is identical in normal flows (child docstatus is synced) but this is exactly faithful.
- get_maintenance_log: add a both-engine test for this previously-untested whitelisted
  endpoint. Confirms the frappe v16 dict aggregate field spec ({"COUNT": ...}) runs and
  returns correct per-status counts (no runtime crash).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:02:59 +05:30
Mihir Kandoi
fc9608d14d refactor(postgres): port Assets module queries to the query builder
Convert the remaining raw frappe.db.sql in the Assets module to frappe.qb / ORM so
the queries run on PostgreSQL as well as MariaDB. Faithful 1:1 conversions -- no
MariaDB behaviour change:

- asset.py (gl-entry / bom-cost fetches), asset_maintenance.py (team members),
  asset_movement.py (latest location/custodian), location.py (get_children)
- fixed_asset_register.py: the depreciation-amount aggregate groups by asset.name
  (the primary key) selecting only asset.name + Sum(gle.debit), which is valid under
  Postgres strict GROUP BY (PK functional dependency)

Tests: existing asset (61), asset_maintenance, asset_movement and location suites
pass on both engines; adds a test for the previously-untested Fixed Asset Register
report (covers the GROUP BY aggregate on both engines).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:48:35 +05:30
Mihir Kandoi
facb27c3f4 Merge pull request #56089 from mihir-kandoi/pg-ar-future-payments
fix(accounts): Journal Entry future payments mis-allocated to one invoice in Accounts Receivable
2026-06-19 08:50:35 +05:30
Smit Vora
68a1fe1480 Merge pull request #56104 from frappe/fix-inclusive-payment-entry-none-base-tax-amount
fix: base_tax_amount as none when payment entry created using API
2026-06-19 08:47:18 +05:30
Mihir Kandoi
e34a64ecee Merge pull request #56118 from frappe/mergify/bp/develop/pr-56065
fix(stock): propagate renamed attribute values to variant items (backport #56065)
2026-06-19 07:42:50 +05:30
Mihir Kandoi
98e012095a Merge pull request #56113 from mihir-kandoi/pg-accounts-sales-payment-summary
fix(postgres): port sales_payment_summary to qb and make POS data Postgres-valid
2026-06-18 23:03:47 +05:30
Mihir Kandoi
e8bebba915 Merge pull request #56112 from mihir-kandoi/pg-accounts-budget
fix(postgres): port budget queries to qb and fix requested-amount aggregation
2026-06-18 23:01:01 +05:30
Mihir Kandoi
d3c0d9b283 fix: resolve item attribute backport conflict 2026-06-18 22:38:23 +05:30
Mihir Kandoi
1cfb41e1c4 Merge pull request #56111 from mihir-kandoi/pg-accounts-doctypes
refactor(postgres): port accounts doctypes & match-condition helper to qb
2026-06-18 22:35:44 +05:30
barredterra
0e244dd83a fix(stock): update variant attributes on value rename
(cherry picked from commit c7acd88742)
2026-06-18 17:05:40 +00:00
barredterra
4c29d5630d test(stock): add cleanup for item attribute value changes in tests
(cherry picked from commit 60f5de7ab8)
2026-06-18 17:05:40 +00:00
barredterra
4806b82add fix(stock): propagate renamed attribute values to variant items
(cherry picked from commit 27d574dad5)

# Conflicts:
#	erpnext/stock/doctype/item_attribute/item_attribute.py
2026-06-18 17:05:40 +00:00
Mihir Kandoi
5a80278d1e Merge pull request #56098 from aerele/fix/serial-no-work-order-docstatus-filter
fix: apply docstatus filter to exclude cancelled Work Orders in Seria…
2026-06-18 22:25:56 +05:30
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