Files
ns_erpnext_app/ns_app/templates/statements/customer_statement.html
Norman King 75a9c9d154 feat(statements): statement builder + printable envelope template
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>
2026-07-08 19:04:47 -04:00

101 lines
3.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{# One customer account statement = one printed page.
Envelope geometry (recipient window at top:1.9in / left:1.125in) mirrors the
existing double-window print formats so the same #10 double-window envelopes
work. Rendered via frappe.render_template with context key `s`
(see ns_app.api.statements.get_statement_data). #}
{% set fmt = frappe.utils.fmt_money %}
<div class="statement-page">
<!-- Return address (top-left envelope window) -->
<div class="return-window">
<strong>{{ s.company_name }}</strong><br>
{{ s.return_address | safe }}
</div>
<!-- Document header (top-right) -->
<div class="doc-header">
<div class="doc-title">STATEMENT</div>
<div><strong>Date:</strong> {{ frappe.utils.formatdate(s.statement_date, "MM-dd-yyyy") }}</div>
<div><strong>Account:</strong> {{ s.customer }}</div>
</div>
<!-- Recipient address (lower envelope window) -->
<div class="recipient-window">
{{ s.customer_name }}<br>
{{ s.customer_address | safe }}
</div>
<!-- Statement body (starts below the address windows) -->
<div class="statement-body">
<div class="intro">
The following is a summary of your account as of
{{ frappe.utils.formatdate(s.statement_date, "MM-dd-yyyy") }}.
Please remit payment for any past-due balance at your earliest convenience.
</div>
<table class="items">
<thead>
<tr>
<th>Invoice</th>
<th class="c">Date</th>
<th class="c">Due Date</th>
<th class="c">Days Overdue</th>
<th class="c">Aging</th>
<th class="r">Outstanding</th>
</tr>
</thead>
<tbody>
{% for inv in s.invoices %}
<tr class="{{ 'overdue' if inv.is_overdue else '' }}">
<td>{{ inv.name }}</td>
<td class="c">{{ frappe.utils.formatdate(inv.posting_date, "MM-dd-yyyy") }}</td>
<td class="c">{{ frappe.utils.formatdate(inv.due_date, "MM-dd-yyyy") }}</td>
<td class="c">{{ inv.days_overdue if inv.days_overdue else "—" }}</td>
<td class="c">{{ inv.aging_bucket }}</td>
<td class="r">{{ fmt(inv.outstanding_amount, currency=s.currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Totals -->
<div class="totals">
<p><span>Total Outstanding:</span><span>{{ fmt(s.total_outstanding, currency=s.currency) }}</span></p>
{% if s.late_fee and s.late_fee > 0 %}
<p><span>Late Payment Fee:</span><span>{{ fmt(s.late_fee, currency=s.currency) }}</span></p>
{% endif %}
<p class="grand"><span>Total Due:</span><span>{{ fmt(s.total_due, currency=s.currency) }}</span></p>
</div>
<!-- Aging summary -->
<table class="aging">
<thead>
<tr>
<th class="c">Current</th>
<th class="c">130</th>
<th class="c">3160</th>
<th class="c">6190</th>
<th class="c">90+</th>
</tr>
</thead>
<tbody>
<tr>
<td class="c">{{ fmt(s.aging["Current"], currency=s.currency) }}</td>
<td class="c">{{ fmt(s.aging["1-30"], currency=s.currency) }}</td>
<td class="c">{{ fmt(s.aging["31-60"], currency=s.currency) }}</td>
<td class="c">{{ fmt(s.aging["61-90"], currency=s.currency) }}</td>
<td class="c">{{ fmt(s.aging["90+"], currency=s.currency) }}</td>
</tr>
</tbody>
</table>
<div class="footer">
Prompt payment is always appreciated. We accept payments by check or over
the phone using a debit or credit card. Automatic payment setup is also
available upon request. Please contact us if payment has already been sent.
</div>
</div><!-- /statement-body -->
</div><!-- /statement-page -->