docs: describe the customer statements feature
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
135
docs/CUSTOMER_STATEMENTS.md
Normal file
135
docs/CUSTOMER_STATEMENTS.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Customer Statements & Late Payment Fees
|
||||
|
||||
> Branch: `feature/customer-statements`
|
||||
|
||||
Generates printable, **one-page-per-customer** account statements — formatted to
|
||||
fit a standard #10 **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 <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.
|
||||
Reference in New Issue
Block a user