* 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.
Accounts module contains masters and transactions to manage a traditional double entry accounting system.
Accounting heads are called "Accounts" and they can be groups in a tree like "Chart of Accounts"
Entries are:
- Journal Entries
- Sales Invoice (Itemised)
- Purchase Invoice (Itemised)
All accounting entries are stored in the General Ledger
Payment Ledger
Transactions on Receivable and Payable Account types will also be stored in Payment Ledger. This is so that payment reconciliation process only requires update on this ledger.
Key Fields
| Field | Description |
|---|---|
account_type |
Receivable/Payable |
account |
Accounting head |
party |
Party Name |
voucher_no |
Voucher No |
against_voucher_no |
Linked voucher(secondary effect) |
amount |
can be +ve/-ve |
Design
debit and credit have been replaced with account_type and amount. against_voucher_no is populated for all entries. So, outstanding amount can be calculated by summing up amount only using against_voucher_no.
Ex:
- Consider an invoice for ₹100 and a partial payment of ₹80 against that invoice. Payment Ledger will have following entries.
| voucher_no | against_voucher_no | amount |
|---|---|---|
| SINV-01 | SINV-01 | 100 |
| PAY-01 | SINV-01 | -80 |
- Reconcile a Credit Note against an invoice using a Journal Entry
An invoice for ₹100 partially reconciled against a credit of ₹70 using a Journal Entry. Payment Ledger will have the following entries.
| voucher_no | against_voucher_no | amount |
|---|---|---|
| SINV-01 | SINV-01 | 100 |
| CR-NOTE-01 | CR-NOTE-01 | -70 |
| JE-01 | CR-NOTE-01 | +70 |
| JE-01 | SINV-01 | -70 |