Files
ns_erpnext_app/docs/CUSTOMER_STATEMENTS.md
Norman King 0aefdeb5ff fix(statements): set envelope geometry for #9 double-window
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>
2026-07-09 07:52:07 -04:00

5.8 KiB
Raw Blame History

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 / 130 / 3160 / 6190 / 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-invoicecreate_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.