# 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: 1. **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. 2. **Choose whether to bill a late fee** via a checkbox in the popup (*Generate late payment fee*, on by default). 3. **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_invoices` and 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's `naming_series` via `after_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 1. **Migrate** the app (`bench --site migrate`) — creates the `Late Fee Item` custom field on Dunning Type and registers the `LPF-` series. 2. Create an **Item** to represent the fee (a non-stock sales item, e.g. "Late Payment Fee"). 3. 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.