"""对账单客户侧 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),
)