add feature/customer-statements to erpnext #22

Merged
norman merged 11 commits from feature/customer-statements into production 2026-07-09 12:53:51 +00:00
Owner

This addsa generate statements button to the customer list and customer screen. This prints a customer statement and generates late fees if necessary.

This addsa generate statements button to the customer list and customer screen. This prints a customer statement and generates late fees if necessary.
norman added 11 commits 2026-07-09 12:52:41 +00:00
Add ns_app/api/statements.py with get_customers_with_overdue_invoices
(one aggregated row per customer with overdue Sales Invoices) plus the
_get_outstanding_invoices / _aging_bucket helpers used to build the
per-customer statement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add get_statement_data (open invoices, aging buckets, totals, formatted
customer + company addresses) and generate_statements, which renders one
page per customer via a Jinja template and returns a printable HTML
document. Recipient window geometry (top:1.9in/left:1.125in) mirrors the
existing double-window print formats for #10 envelope compatibility; each
page uses page-break-after:always. Late fee is a zero placeholder here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Register doctype_list_js for Customer and add customer_list.js, which
lists customers with overdue invoices in a selection dialog (checkbox
table + select-all), then calls generate_statements and opens the
printable, one-page-per-customer document in a new window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Charge a late-payment fee when statements are generated so the customer's
receivable reflects it (the gap ERPNext Dunning leaves — it never
increases AR). The fee is billed as a submitted Sales Invoice (item ->
Dunning Type income account, rate = computed fee) rather than a Journal
Entry, so the app's existing payment flow (Run Payment / AutoPay /
multi-invoice) charges and settles it automatically via its Sales Invoice
references — a bare JE would sit uncollected.

Fee schedule/amounts come from the existing Dunning Type settings (yearly
rate_of_interest + flat dunning_fee), interest computed with ERPNext's own
Dunning formula. The fee Item is configured via a new Late Fee Item custom
field on Dunning Type (created in an after_migrate hook; ns_app/setup.py).
Nothing is auto-seeded: generation stops with a clear error if no Dunning
Type is configured or its income account / fee item is unset.

Billing is idempotent per customer/company/month, and prior fee invoices
are excluded from the interest base (no fee-on-fee). The fee invoice shows
on the statement flagged 'late fee', folded into Total Due (which equals
the customer's balance and is fully collectible).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bill late-fee invoices under a dedicated naming series (LPF-.YYYY.-),
  registered on Sales Invoice via after_migrate, so they are easy to spot
  and filter.
- Late fees are never taxed: a zero 'Actual' tax line keeps the taxes
  table non-empty so ERPNext skips auto-applying company/item tax
  templates (posts nothing to the ledger). Fee total == computed fee.
- generate_statements(skip_late_fee=...) generates a statement without
  billing a fee.
- Record every generation on the customer's timeline (add_comment) as an
  audit trail, noting Total Due and the fee invoice raised / skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace customer_list.js with customer_statements.js (loaded globally),
which adds the statement UI to both entry points:
- Customer list: 'Generate Statements' multi-select action.
- Customer form: 'Generate Statement' button for a single customer.

Both open a popup with a 'Generate late payment fee' checkbox (default on)
that maps to generate_statements(skip_late_fee), so fee billing can be
turned off per run. Shared generate/print helpers live on ns_statements.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The list button was registered by reassigning
frappe.listview_settings['Customer'] in a globally-loaded script, but
ERPNext's own Customer list_js (loaded when the list opens) overwrote it,
so the button never appeared. Register it via doctype_list_js instead —
which Frappe appends after the doctype's own list_js — and merge into the
existing settings (wrapping onload, preserving ERPNext's add_fields)
rather than reassigning. The form button and shared ns_statements helpers
stay in customer_statements.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Company (return) address down 0.25in (to 0.8in) and customer address
down 1.5in (to 3.0in) to line up with the #9 double-window envelope,
per a printed proof. Push the body padding-top to clear the lower
customer window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the customer address window up to 2.5in per printed proof, and
pull the body padding-top back up to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
norman merged commit 1b41fb0845 into production 2026-07-09 12:53:51 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: nsinnovations/ns_erpnext_app#22