"""对账单客户侧 HTML 渲染(匿名分享页,手机优先,可直接打印)。 安全:所有字段经 django.utils.html.escape;不含任何 Django 模板执行。 """ from __future__ import annotations from decimal import Decimal from django.utils.html import escape def _money(value) -> str: try: d = Decimal(str(value or 0)) except Exception: return escape(str(value)) return f"{d:,.2f}" _PAGE = """ 对账单 {customer_name} {date_from}~{date_to}

对 账 单

本页由系统自动生成,仅供对账参考
客户:{customer_name}{customer_code}
区间:{date_from} ~ {date_to}
生成时间:{generated_at}
期初余额(元)
{opening}
本期应收(元)
{period_debit}
本期收款(元)
{period_credit}
期末余额(元)
{closing}
{rows}
日期类型单号摘要 应收收款余额
如对本对账单有异议,请在收到后 3 个工作日内与业务员联系核对。
""" def render_statement_html(data: dict, *, generated_at: str) -> str: """把 customer_statement 的返回值渲染成客户侧 HTML 页面。""" lines = data.get("lines") or [] debit_total = Decimal("0") credit_total = Decimal("0") rows = [] for ln in lines: d = Decimal(str(ln.get("debit") or 0)) c = Decimal(str(ln.get("credit") or 0)) debit_total += d credit_total += c kind = "应收" if ln.get("type") == "receivable" else "收款" rows.append( '{date}{kind}' '{ref}{desc}{debit}' '{credit}{balance}'.format( cls="debit" if d else "credit", date=escape(str(ln.get("date") or "")), kind=kind, ref=escape(str(ln.get("ref") or "")), desc=escape(str(ln.get("description") or "")), debit=_money(d) if d else "", credit=_money(c) if c else "", balance=_money(ln.get("balance")), ) ) if not rows: rows.append('本期无往来明细') customer = data.get("customer") or {} code = customer.get("code") return _PAGE.format( customer_name=escape(str(customer.get("name") or "")), customer_code=f"({escape(str(code))})" if code else "", date_from=escape(str(data.get("date_from") or "")), date_to=escape(str(data.get("date_to") or "")), generated_at=escape(generated_at), opening=_money(data.get("opening_balance")), period_debit=_money(debit_total), period_credit=_money(credit_total), closing=_money(data.get("closing_balance")), rows="\n ".join(rows), )