Repository navigation
feat(overdue-limit): add the Time Based Overdue Limit for Sales Invoice and Sales Order - #3
Merged
Merged
Conversation
A rule names one Customer or Customer Group, an optional company, a day threshold and an action: Warn on Save, Block on Save or Block on Submit. Only Accounts Manager and System Manager can edit rules, so sales users cannot add their own role to the exempt list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s warn or block The customer's oldest overdue Sales Invoice in the document's company sets the day count. Only the most specific tier of rules applies: the customer's own, else its group's, else the nearest ancestor group's. A customer whose GL balance is zero or less is never checked, because unallocated payments leave invoices outstanding. Not wired into hooks.py yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frappe fills an empty Link field of a new document from the default keyed by its fieldname, and every ERPNext site has a global company default, so an every-company rule was silently saved as a rule for the default company. The form still pre-fills the company; clearing it now sticks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sites with the KSA customizations make tax_category mandatory on Customer, so get_or_create_customer failed there before any assertion ran. On ims.local this takes the Letter of Credit suite from 17 errors to 1 and the party statement integration suite from 7 to 5; what remains in both is the site's cost-center rule on balance sheet accounts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… saves The overdue invoice is posted today and its due date moved 25 days back, rather than backdated, so the suite still runs in January on CI, whose fiscal year is the calendar year. The test Sales Invoice carries no header cost center, as real invoices don't, because a payment copies it onto balance sheet lines that some sites refuse one on. make_reference_sales_order now wraps the new make_sales_order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e bug The matrix goes from 24 cases to 12. Checked against 23 bugs injected into limit.py, both versions catch 21. The old one missed an exemption from one block rule lifting another, and a misspelt skip flag, which its loop over SKIP_FLAGS could never catch. The new one misses only two wording changes, which it no longer pins. Dropped: cases restating one-liners (the day count, the role intersection), empty-in-empty-out, and the message sentences. The action table is now the reference doc's three-by-four table, tested against rule_outcome. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… customer balance A payment or credit note that isn't allocated to the overdue invoice no longer lifts the block, even when the customer owes nothing overall: it has to be matched to the invoice with Payment Reconciliation. This also drops the GL Entry query the check ran before every warning or block.
The oldest overdue invoice is now found by status, Overdue or Overdue and Discounted, instead of outstanding amount and due date, so what counts as overdue is ERPNext's decision. ERPNext's daily update_invoice_status job sets that status, so the rules depend on the scheduler: on a site where it is disabled, invoices stay Unpaid and no rule fires. The integration fixture now turns its invoice Overdue by running that same job instead of only backdating the due date.
Warn on Save runs at validate, Block on Save at before_save and Block on Submit at before_submit, and each hook looks only at its own action's rules, so a rule does what its name says and nothing more: - Warn on Save also warns at submit, because Frappe runs validate there too. - Block on Save no longer refuses the submit of a draft saved before the customer went overdue; a Block on Submit rule is what stops a submit. - Block on Submit no longer warns at save. The tier is still chosen before the action filter, so a customer's warning rule keeps shadowing its group's block rule. Each message line now names the rule's action.
…apply When a save is refused, the desk drops the warnings sent with it, so only the refusal shows; the docs had said both appear. An exempt user sees each hook's message stacked in one dialog. Also spell out that any rule makes a level win whatever its action, that exemption is per rule, and that a block rule with no exempt roles blocks Administrator too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the Time Based Overdue Limit: ERPNext's Credit Limit keyed on how long a customer's debt has been overdue instead of how large it is. A rule warns about or blocks new Sales Invoices and Sales Orders for a customer whose oldest overdue invoice has passed a number of days.
New doctypes: Time Based Overdue Limit, with the child table Time Based Overdue Limit Exempt Role. A rule names a Customer or Customer Group, an optional company (blank means every company), a threshold in days, one action and optional exempt roles. Only Accounts Manager and System Manager can edit rules.
One hook per action on Sales Invoice and Sales Order:
validatebefore_savebefore_submitWhich rules apply: the most specific level wins: the customer's own rules, else its group's, else the nearest ancestor group's. A level wins as soon as it has a rule of any action, and only then is the hook's action picked out.
The day count comes from the customer's oldest Sales Invoice in the document's company whose status is Overdue or Overdue and Discounted, so ERPNext decides what is overdue.
Exempt roles turn a block into a warning, rule by rule.
Never checked: credit notes, POS consolidated invoices, and anything written by migrate, a patch, an install or a data import.
Code layout: the decisions are plain functions in
overdue_limit/limit.py, kept apart from the queries and from the thincheckentry point.Behaviour to know before merging
update_invoice_statusjob is what turns an untouched invoice Overdue. On a site with the scheduler off, no rule fires.Deploying
Run
bench --site <site> migrateto create the two doctypes, then restart the bench so the new hooks load.Tests
All three suites ran on a local site:
overdue_limit/tests/test_limit.py: the decisions on plain rows. 12 tests, OK.overdue_limit/tests/test_limit_integration.py: real saves and submits of both doctypes. 12 tests, OK, 1 skipped because the site has a single company.doctype/time_based_overdue_limit/test_time_based_overdue_limit.py: the rule's own validation. 5 tests, OK.Docs
docs/overdue-limit/: the README index, a user guide and a developer reference.docs/development/testing.md: updated.🤖 Generated with Claude Code