Reset the statement window positions to the app's proven #9 (9x4) double-window geometry, mirroring sales_invoice_ns.html — recipient window at top:1.5in/left:1.125in. It previously copied the dunning format's 1.9in, which sits too low for a #9. Tighten the body padding-top to keep clearance below the higher window, and correct the stale #10 references in the template comment and docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.8 KiB
Customer Statements & Late Payment Fees
Branch:
feature/customer-statements
Generates printable, one-page-per-customer account statements — formatted to fit a standard #9 (9x4) double-window envelope — for customers with overdue invoices, and (optionally) bills a late-payment fee that posts to the ledger and is collectible through the app's existing payment flow.
1. What it does
From either the Customer list or a Customer form, a user can generate account statements:
- Pick customers. On the list, Generate Statements opens a dialog listing every customer with overdue invoices (overdue count, max days overdue, total outstanding) with select-all. On a Customer form, Generate Statement targets that one customer.
- Choose whether to bill a late fee via a checkbox in the popup (Generate late payment fee, on by default).
- Get a printable report. A new browser tab opens with one statement per page — each showing the customer's open invoices, aging buckets (Current / 1–30 / 31–60 / 61–90 / 90+), and a Total Due. The customer and company (return) addresses sit in the two envelope-window positions.
Each generation is recorded on the customer's timeline as an audit-trail entry.
2. Late payment fees
How the fee is calculated
Interest uses ERPNext's own Dunning formula:
fee = Σ(invoice.outstanding × rate_of_interest/100/365 × days_overdue) + dunning_fee
Where the settings live — ERPNext Dunning Type
All fee configuration comes from the existing Dunning Type doctype
(Accounting ▸ Dunning Type). Nothing is auto-created; generation stops with a
clear error until it is configured. The default (is_default) Dunning Type for
the company is used. Fields consumed:
| Dunning Type field | Purpose |
|---|---|
rate_of_interest |
Annual interest rate (%) |
dunning_fee |
Flat fee added per statement |
income_account |
Credited when the fee is billed |
cost_center |
Cost center for the fee line (falls back to company default) |
custom_late_fee_item |
Late Fee Item — the Item used to bill the fee (custom field added by this app) |
How the fee posts, and how it gets paid
The fee is billed as a submitted Sales Invoice (item → the Dunning Type income account). This is deliberate: because it is a real Sales Invoice it
- increases the customer's receivable balance immediately, and
- appears in
get_unpaid_invoicesand is charged/settled automatically by the app's existing payment paths (Run Payment / AutoPay / multi-invoice →create_payment_entry), which allocate against Sales Invoices.
A bare Journal Entry (or an ERPNext Dunning document) would raise the balance but sit uncollectible by those flows — hence the Sales Invoice.
Fee invoice specifics
- Dedicated naming series
LPF-.YYYY.-(e.g.LPF-2026-00001) so late-fee invoices are easy to spot and filter. Registered on Sales Invoice'snaming_seriesviaafter_migrate. - Never taxed. A single zero-amount "Actual" tax line keeps ERPNext from auto-applying company/item tax templates, so the invoice total equals the computed fee exactly and nothing extra hits the ledger.
- Idempotent — at most one fee invoice per customer / company / calendar month. Prior fee invoices are excluded from the interest base (no fee-on-fee).
On the statement the fee shows as a normal invoice line tagged “late fee”, folded into a single Total Due that equals the customer's balance.
3. Configuration / prerequisites
- Migrate the app (
bench --site <site> migrate) — creates theLate Fee Itemcustom field on Dunning Type and registers theLPF-series. - Create an Item to represent the fee (a non-stock sales item, e.g. "Late Payment Fee").
- Create/complete a Dunning Type for the company with: rate of interest, dunning fee, income account, and the Late Fee Item. Mark it default.
If any of these is missing, statement generation throws a clear, actionable error and posts nothing.
4. Audit trail
Each generated statement adds an Info comment to the customer's timeline, e.g.
Statement generated — Total Due $557.17 (late fee invoice LPF-2026-00001).
The note reflects the outcome: the fee invoice raised, no late fee, or late fee skipped (when the fee checkbox was cleared). It is attributed to the generating user.
5. Files
| File | Role |
|---|---|
ns_app/api/statements.py |
Overdue-customer query, statement builder, printable HTML, late-fee billing (Sales Invoice), audit-trail entry |
ns_app/templates/statements/customer_statement.html |
Jinja template for one customer page (envelope windows + aging table) |
ns_app/public/js/customer_statements.js |
List action + Customer-form button + selection/fee-toggle popups (loaded globally) |
ns_app/setup.py |
after_migrate: creates the Late Fee Item custom field, registers the LPF- naming series |
ns_app/hooks.py |
Wires the JS (app_include_js) and after_migrate |
Server API (ns_app.api.statements)
get_customers_with_overdue_invoices()— customers with overdue invoices.generate_statements(customers, skip_late_fee=0)— bills fees (unless skipped), renders the printable HTML, records the audit entry. Returns{html, rendered, skipped}.get_statement_data(customer)— the per-customer statement data (internal).
Access is restricted to System Manager, Sales User/Manager, Accounts User/Manager.
6. Notes / non-goals
- No persisted "Statement" doctype — statements are generated on demand.
- No email/fax delivery — print only.
- Whether the fee should be taxed and the interest rate/fee amounts are business settings, controlled entirely through Dunning Type.