Commit Graph

4232 Commits

Author SHA1 Message Date
Jatin3128
a33da337ec feat: block sales invoice submit when customer overdue exceeds threshold (#57230)
* feat: block sales invoice submit when customer overdue exceeds threshold

Adds an opt-in, per-customer Overdue Billing Threshold. When enabled in
Accounts Settings, submitting a Sales Invoice is blocked if the customer's
overdue amount exceeds their threshold, unless the current user holds a
configured bypass role. Modeled on the existing credit limit feature.

- Accounts Settings (Credit Limits tab): enable toggle + bypass role.
- Per-customer threshold on the Customer Credit Limit table, shown only
  when the feature is enabled via a property setter (same mechanism as
  subscription / accounting dimension sections). Table relabeled to
  "Credit & Overdue Limits".
- Overdue is read live from the ledger via get_outstanding_invoices
  (payments already netted), summing Sales Invoices past their due date.
- Enforced in Sales Invoice on_submit, after the credit-limit check;
  returns are exempt.
- validate_credit_limit_on_change no longer trips when a row sets only
  the overdue threshold (credit_limit = 0).

Fixes #52960

* fix: compute overdue amount in company currency and format with fmt_money

get_customer_overdue_amount now sums GL Entry debit - credit grouped per
invoice, which is always booked in company currency, instead of using
get_outstanding_invoices which returns the receivable-account currency.
The threshold is in company currency, so the previous comparison could mix
currencies for customers with a foreign-currency receivable account. This
mirrors how get_customer_outstanding computes the figure for the existing
credit-limit check.

The blocking message now formats both amounts with fmt_money using the
company currency.

Adds a test asserting a 100 USD invoice at a conversion rate of 50 is
counted as 5000 in company currency.

* refactor: drop redundant threshold coercion and dead test cleanup

- Coerce the overdue threshold with flt() once when reading it, instead of
  calling flt() on it at each of the three use sites.
- Remove a no-op set_overdue_billing_threshold() call in the feature-disabled
  block (the threshold was already set to that value) and the trailing reset,
  which is dead since each test is rolled back.

No behaviour change.

* fix: compute overdue amount from payment terms, matching the Overdue status

The overdue amount keyed on Sales Invoice.due_date, which set_due_date() sets
to the LAST payment term. An invoice whose first term was past due and unpaid
was therefore counted as zero, even though ERPNext already shows it as Overdue
in the invoice list. The gate and the UI could disagree.

get_customer_overdue_amount now follows the same rule as is_overdue(): per
invoice, the amount that has fallen due (sum of payment schedule terms past
their due date) minus what has been paid, clamped to the outstanding balance.
Invoices without a schedule (POS, opening) still fall back to the invoice due
date, mirroring is_overdue()'s own guard.

The ledger stays the source of truth for what is unpaid: the outstanding per
invoice is still SUM(debit) - SUM(credit) from GL Entry. base_payment_amount is
always stored in company currency, so no currency conversion is needed and the
comparison against the threshold stays consistent.

Adds a test covering a two-term invoice: only the past-due term counts, and
paying it off clears the overdue amount.

* feat: honour the overdue billing threshold set on the customer group

The threshold lives on Customer Credit Limit, which is also rendered on
Customer Group. A threshold set there was stored but never evaluated, so the
configuration was a silent no-op.

get_overdue_billing_threshold now reads the customer's row and falls back to
its customer group, mirroring get_credit_limit. The group's
bypass_credit_limit_check is deliberately not consulted: it is labelled for the
credit limit check at sales order and is unrelated to overdue billing.

get_customer_group_details also dropped the threshold when copying group rows
onto a customer, because it copied a single hardcoded field per table. It now
copies a list of fields per table, so credit_limit and overdue_billing_threshold
both carry over.
2026-07-17 18:58:10 +05:30
Nishka Gosalia
5fc03a116a feat: map settings for DocTypes to show on settings dialog (#57025)
fix: mapping settings for DocType settings
2026-07-16 19:30:27 +05:30
Afsal Syed
9ae2069bd9 test(stock): add portal user contact link verification for customer and supplier 2026-07-16 15:11:28 +05:30
Afsal Syed
337a06dfb6 feat(stock): automatically link portal users to their associated contact profiles for customers and suppliers 2026-07-16 15:11:28 +05:30
Nabin Hait
04f75cc64f Merge pull request #56983 from nabinhait/fix-flaky-usd-exchange-rate-tests
test: seed current-dated USD↔INR exchange rate to fix flaky currency tests
2026-07-14 18:13:28 +05:30
Mihir Kandoi
b6cce627a8 feat: company-wise restriction for Item, Customer and Supplier masters (#57124) 2026-07-14 10:30:47 +00:00
Nabin Hait
e1e56b6920 test: adjust currency tests for deterministic seeded exchange rate
Seeding a current-dated USD->INR rate makes get_exchange_rate resolve
62.9 on today() instead of hitting the live API, which exposed three
tests that implicitly relied on a different/undefined current rate:

- customer: dropped its own colliding current-dated seed (ignored via
  ignore_if_duplicate, and its cleanup deleted the shared seed) and now
  asserts the quotation resolves the seeded rate via get_exchange_rate.
- exchange_rate_revaluation: the revalued rate (62.9) is now below the
  booked 80, so the revaluation is a loss (debited) rather than a gain;
  derive the gain/loss column from the sign instead of assuming a gain.
- purchase_invoice: the receipt rate was an accidental tuple (70,) that
  got discarded and recomputed to the seed; set explicit rates with the
  receipt above the invoice so the stock exchange difference is a credit,
  matching the asserted column.
2026-07-12 21:12:29 +05:30
mergify[bot]
45102e12cc feat: explain FIFO allocation of fixed Discount Amount on Sales Order (backport #56436) (#57062)
* feat: explain FIFO allocation of fixed Discount Amount on Sales Order (#56436)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
(cherry picked from commit 62fed1d562)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.json

* chore: resolved conflicts

---------

Co-authored-by: Mohammad Umair Sayed <umair_sayyed@yahoo.com>
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2026-07-11 17:57:49 +00:00
Shllokkk
394c9d80f9 fix: use correct mapper path for make_work_orders call 2026-07-10 20:59:08 +05:30
S Sakthivel Murugan
b72ecdda0d test: add regression test for trends chart total row 2026-07-06 17:54:25 +05:30
S Sakthivel Murugan
e6f9149ad7 fix: use company currency instead of global default in report 2026-07-06 17:54:25 +05:30
Nikhil Kothari
dc09362454 fix: replace all old icons (#56864) 2026-07-03 23:03:28 +05:30
rohitwaghchaure
341a07dffa fix: restrict state-changing whitelisted endpoints to POST (#56858)
Add methods=["POST"] to 50 whitelisted functions that create or modify
documents (get_doc followed by insert/save/submit), so they can no
longer be invoked via GET requests.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 18:47:13 +05:30
Khushi Rawat
c9145c5ece Merge branch 'develop' into fix/letterhead-footer-print-formats 2026-07-03 02:27:58 +05:30
khushi8112
2d0c0a8c09 fix: add page numbers to print format footer 2026-07-03 02:26:52 +05:30
khushi8112
e60a467972 fix: render letter head footer in print formats 2026-07-03 02:16:59 +05:30
Nabin Hait
b12725c4b2 Merge pull request #56778 from frappe/chore/test-quotation-trends
test: Quotation Trends report coverage
2026-07-02 23:07:02 +05:30
Nabin Hait
15862566a8 Merge pull request #56784 from frappe/chore/test-territory-wise-sales
test: Territory-wise Sales report coverage
2026-07-02 23:04:22 +05:30
Nabin Hait
6cffa0faeb Merge pull request #56783 from frappe/chore/test-sales-person-wise-transaction-summary
fix: correct filter handling in Sales Person-wise Transaction Summary + tests
2026-07-02 22:58:48 +05:30
Nabin Hait
a075437db7 Merge pull request #56780 from frappe/chore/test-customer-wise-item-price
test: Customer-wise Item Price report coverage
2026-07-02 22:57:19 +05:30
Nabin Hait
9f4914e08f Merge pull request #56781 from frappe/chore/test-sales-person-commission-summary
test: Sales Person Commission Summary report coverage
2026-07-02 22:56:20 +05:30
Nabin Hait
0888405640 test: reuse shared distribution helper and assert a single territory row 2026-07-02 17:15:05 +05:30
Nabin Hait
087fb29d51 test: narrow Territory-wise Sales docstring to covered stages 2026-07-02 17:13:42 +05:30
Nabin Hait
2bab709ac4 test: scope date range, reload invoice, strengthen total-row check 2026-07-02 17:12:58 +05:30
Nabin Hait
5298438905 test: also assert Jun amount bucket in Quotation Trends monthly test 2026-07-02 17:11:37 +05:30
Nabin Hait
e0b0926dff fix: only resolve items when item_group/brand filter is set 2026-07-02 17:10:19 +05:30
Nabin Hait
8d70385019 test: Territory Target Variance based on Item Group report coverage 2026-07-02 15:43:59 +05:30
Nabin Hait
f95baa54de test: Territory-wise Sales report coverage 2026-07-02 15:42:07 +05:30
Nabin Hait
3092c920ff fix: pass only valid document filters in Sales Person-wise Transaction Summary 2026-07-02 15:40:32 +05:30
Nabin Hait
55646667be test: Sales Person Commission Summary report coverage 2026-07-02 15:36:47 +05:30
Nabin Hait
9865f63613 test: Customer-wise Item Price report coverage 2026-07-02 15:33:53 +05:30
Nabin Hait
08876ae07a test: Quotation Trends report coverage 2026-07-02 15:32:13 +05:30
Raffael Meyer
ba1e8f0005 fix(Quotation): create Customer from Lead (#55923) 2026-07-02 03:27:14 +02:00
Soham Kulkarni
3eaea74a51 Merge pull request #56656 from sokumon/revamp-workspaces
chore: exporting workspaces with sidebars
2026-07-02 05:39:24 +05:30
Mihir Kandoi
65cb89cc40 Merge pull request #56552 from aerele/fix-quotation-conversion-rate-from-customer
fix: set conversion_rate on quotation created from customer
2026-07-01 21:46:28 +05:30
Nabin Hait
bb0a46db9e Merge pull request #56504 from frappe/chore/item-wise-sales-history-test-coverage
test: Item-wise Sales History report coverage
2026-07-01 20:28:29 +05:30
Nabin Hait
bd874d09ef test: drop unused variable and apply ruff formatting 2026-07-01 19:33:00 +05:30
ervishnucs
e4d6c0854b fix: remove redundant conversion_rate 2026-07-01 19:28:38 +05:30
Mihir Kandoi
e6f8f8f7e9 refactor: use frappe._dict in importers of ItemDetailsCtx
Extend the boundary rule to callers: non-decorated code that built or
annotated with ItemDetailsCtx now uses frappe._dict directly, and drops
the now-unused import. asset_capitalization keeps ItemDetailsCtx for its
own normalize_ctx_input-decorated functions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:30:16 +05:30
Mihir Kandoi
8f3eb6cb31 fix(selling): tie-break POS customer contact pick for cross-engine parity
The contact lookup orders only by is_primary_contact desc then takes contacts[0]; contacts commonly
tie (the no-primary case), so MariaDB and Postgres could pick a different contact. Add a parent
(contact name) tiebreaker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:10:19 +05:30
sokumon
5a32866b93 chore: export more workspaces 2026-06-30 17:00:54 +05:30
Mihir Kandoi
70142d147e fix(selling): break last-sales-amount ties deterministically in Inactive Customers
get_last_sales_amt ordered by the sales date DESC only; same-date documents made the reported Last Order Amount engine-dependent. Add name DESC tiebreaker.
2026-06-29 16:29:46 +05:30
ervishnucs
dead28e50e test: assert quotation from customer uses actual exchange rate 2026-06-28 20:49:56 +05:30
ervishnucs
8446be6518 fix: set currency and price list before computing quotation totals 2026-06-28 20:13:53 +05:30
ervishnucs
e61d299e63 fix: recalculate totals after setting quotation conversion rate 2026-06-28 09:53:38 +05:30
Mihir Kandoi
057af21cd8 fix: party aliases should be no copy 2026-06-27 16:13:43 +05:30
ervishnucs
31ee3f1923 fix: set conversion_rate on quotation created from customer 2026-06-26 13:33:30 +05:30
Mihir Kandoi
0583349ae4 test: convert aggregate/pluck/positional raw SQL to ORM
A deeper re-audit (with an adversarial skeptic) of the queries left raw
in the prior commit found more that have exact ORM equivalents:

- scalar SUM/MAX  -> frappe.qb + Sum/Max .run()[0][0]
- SUM ... GROUP BY -> frappe.qb .groupby().select(Sum().as_()) run(as_dict)
- name IN (values) -> get_all(filters={'f': ['in', ...]})
- sql_list(select col) -> get_all(pluck='col')
- bulk UPDATE ... = NULL/value -> frappe.db.set_value(filters, field, val)
- positional as_list reads -> get_all(..., as_list=True) (+ sorted())

Note: get_value(dt, filters, 'sum(x)') and get_all(fields=['sum(x)'])
are rejected by frappe ('SQL functions are not allowed as strings'), so
aggregates go through frappe.qb. get_all(as_list=True) returns a tuple
(not a list), so consumers that mutate use sorted().

All affected test modules pass on MariaDB.
2026-06-26 11:34:31 +05:30
Mihir Kandoi
0776f7f7fa test: convert trivially-equivalent raw SQL to ORM helpers
Convert test-only raw frappe.db.sql calls that have an exact ORM
equivalent: full-table/filtered deletes -> frappe.db.delete, count ->
frappe.db.count, row-existence assertions -> frappe.db.exists,
single-row scalar fetches -> frappe.db.get_value, and simple
equality/range-filter selects -> frappe.get_all. No behaviour change.

Raw SQL that genuinely needs it is left as-is (dynamic identifiers,
aggregates/group-by, positional as_list consumers, DB-catalog
introspection).
2026-06-26 10:44:17 +05:30
Nabin Hait
16c71fa102 test: add coverage for Item-wise Sales History report
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 09:16:41 +05:30