#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成 2026年10月应收账款账龄统计交付物：自包含 HTML（品牌注入+内联图）＋ 多 Sheet xlsx"""
import html as H
import io
import json
import os
import re
import sys
import warnings

warnings.filterwarnings('ignore')
sys.path.insert(0, '/Users/liuxinyuan/Desktop/Hermes输出-工作类/脚本')
import openpyxl  # noqa: E402
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side  # noqa: E402
from openpyxl.utils import get_column_letter  # noqa: E402
import brand_kit as BK  # noqa: E402

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
import sys as _sys

_OUT = _sys.argv[1] if len(_sys.argv) > 1 else '2026年10月应收账龄_大客户部'
D = json.load(io.open(W + '/数据/' + _OUT + '.json', encoding='utf-8'))
PERIOD = D['meta'].get('period', '2026年10月')
DEPT_LABEL = D['meta'].get('dept_label', '大客户部（诊断原料）')
SCOPE_NOTE = (D['meta'].get('scope_note') or '本月回款报送范围内仅「诊断原料大客户销售部」（10 月大客户文件）；销售拓展部 / 生命科学部 10 月文件未到，故本表不含其数据。')
B = D['buckets']
TOT = D['total']
OV = D['overdue_total']
ND = D['notdue_total']
wan = lambda x: x / 10000.0


def money(x, dec=2):
    return format(x, ',.%df' % dec)


def pct(x):
    return '%.1f%%' % x


# ══════════════ HTML ══════════════
COLORS = {'账期内': '#3b82f6', '逾期1-30天': '#22c55e', '逾期31-60天': '#f59e0b',
          '逾期61-365天': '#ef4444', '逾期365天以上（一年以上）': '#7f1d1d'}
rows_total = sum(D['overall'][k]['单数'] for k in B)

# 账龄总表行
tbody = []
for k in B:
    v = D['overall'][k]
    p1 = v['金额'] / TOT * 100
    p2 = (v['金额'] / OV * 100) if k != '账期内' else 0
    bar = ('<div style="background:#eef2f7;border-radius:3px;height:14px;position:relative">'
           '<div style="width:%.1f%%;background:%s;height:14px;border-radius:3px"></div></div>' % (p1, COLORS[k]))
    tbody.append('<tr><td class="b">%s</td><td class="n">%d</td><td class="n">%s</td>'
                 '<td class="n">%s</td><td class="n">%s</td><td style="min-width:120px">%s</td></tr>'
                 % (H.escape(k), v['单数'], money(v['金额']), pct(p1), (pct(p2) if k != '账期内' else '—'), bar))
sub = D['over60_subtotal']
tbody.append('<tr class="sub"><td class="b">〔逾期60天以上 小计〕<span class="tag">源表《进度表》同口径</span></td>'
             '<td class="n">%d</td><td class="n">%s</td><td class="n">%s</td><td class="n">%s</td><td></td></tr>'
             % (sub['单数'], money(sub['金额']), pct(sub['金额'] / TOT * 100), pct(sub['金额'] / OV * 100)))
tbody.append('<tr class="tot"><td class="b">合计</td><td class="n">%d</td><td class="n">%s</td>'
             '<td class="n">100.0%%</td><td class="n">—</td><td></td></tr>' % (D['rows'], money(TOT)))

# 分部门
dept_rows = []
for d, v in D['by_dept'].items():
    bb = v['buckets']
    tt = sum(bb[k]['金额'] for k in B)
    cells = ''.join('<td class="n">%s<br><small>%d 单</small></td>' % (money(bb[k]['金额']), bb[k]['单数']) for k in B)
    dept_rows.append('<tr><td class="b">%s</td>%s<td class="n b">%s</td><td class="n">%s</td>'
                     '<td class="n" style="color:#b91c1c">%s</td></tr>'
                     % (H.escape(d), cells, money(tt), pct(tt / TOT * 100), money(v['over60']['金额'])))

# 分销售员
sp_rows = []
for sp, v in sorted(D['by_sp'].items(), key=lambda kv: -kv[1]['逾期合计']):
    bb = v['buckets']
    cells = ''.join('<td class="n">%s</td>' % (money(bb[k]['金额'], 0) if bb[k]['金额'] else '—') for k in B)
    od_rate = v['逾期合计'] / v['合计'] * 100 if v['合计'] else 0
    sp_rows.append('<tr><td class="b">%s</td><td class="n">%d</td>%s<td class="n b" style="color:#b91c1c">%s</td>'
                   '<td class="n">%s</td><td class="n">%s</td><td class="n">%s</td></tr>'
                   % (H.escape(sp), v['单数'], cells, money(v['逾期合计'], 0), money(v['合计'], 0), pct(od_rate),
                      money(v['over60']['金额'], 0)))

# 堆叠 SVG（账龄结构）
WID, HEI = 1100, 78
x = 0.0
segs = []
for k in B:
    v = D['overall'][k]
    w = v['金额'] / TOT * WID
    segs.append('<rect x="%.2f" y="18" width="%.2f" height="34" fill="%s"></rect>' % (x, w, COLORS[k]))
    if w > 70:
        segs.append('<text x="%.2f" y="40" font-size="13" fill="#fff" text-anchor="middle" font-weight="700">%s</text>'
                    % (x + w / 2, pct(v['金额'] / TOT * 100)))
        segs.append('<text x="%.2f" y="66" font-size="11.5" fill="#5f6368" text-anchor="middle">%s · %s万</text>'
                    % (x + w / 2, H.escape(k.replace('逾期', '逾').replace('（一年以上）', '>1年')), money(wan(v['金额']), 1)))
    x += w
svg = ('<svg viewBox="0 0 %d %d" width="100%%" height="%d" role="img" aria-label="账龄结构堆叠条">%s</svg>'
       % (WID, HEI, HEI, ''.join(segs)))

# 逾期天数=0 的行（动态，禁止写死别部门的例子）
_zero = [r for r in D['detail'] if r['overdue'] and (r['od_days'] or 0) == 0]
if _zero:
    zero_txt = ('源表有 <b>%d 单</b>到期日当天（逾期天数=0）但仍挂逾期金额，合计 <b>%s 元</b>（%s）'
                % (len(_zero), money(sum(r['od_amt'] for r in _zero)),
                   '、'.join('%s %s' % (r['sp'], money(r['od_amt'], 0)) for r in _zero[:4])))
else:
    zero_txt = '本月源表没有「逾期天数=0 但挂逾期金额」的行'

# 勾稽示例（动态取前两位逾期额最大的销售员）
_ccs = sorted(D['crosscheck'], key=lambda c: -c['逾期(本表)'])[:2]
cc_examples = '；'.join('%s 逾期 %s / 60天以上 %s' % (c['销售员'], money(c['逾期(本表)']), money(c['逾期60天以上(本表)']))
                        for c in _ccs)

# 图例
legend = []
for k in B:
    v = D['overall'][k]
    legend.append('<div style="display:flex;align-items:center;gap:7px;font-size:12.5px">'
                  '<span style="width:12px;height:12px;border-radius:3px;background:%s;display:inline-block"></span>'
                  '<b>%s</b><span style="color:#6b7280">%s 元</span>'
                  '<span style="color:#9aa3b2">%s ｜ %d 单</span></div>'
                  % (COLORS[k], H.escape(k), money(v['金额']), pct(v['金额'] / TOT * 100), v['单数']))
legend = ''.join(legend)

# 勾稽块
cc = ''.join('<tr><td class="b">%s</td><td class="n">%s</td><td class="n">%s</td><td class="n ok">%s</td>'
             '<td class="n">%s</td><td class="n">%s</td><td class="n ok">%s</td></tr>'
             % (c['销售员'], money(c['逾期(本表)']), money(c['逾期(进度表)']), money(c['差']),
                money(c['逾期60天以上(本表)']), money(c['逾期60天以上(进度表)']), money(c['差2'])) for c in D['crosscheck'])

HTML = """<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{period}}应收账款账龄统计 · {{dept_label}}</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#f4f6fa;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;color:#20242c}
.wrap{max-width:1240px;margin:0 auto;padding:18px 20px 40px}
h1{font-size:23px;margin:14px 0 6px;letter-spacing:.3px}
.meta{color:#6b7280;font-size:12.5px;line-height:1.8;margin-bottom:14px}
.kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:14px 0 18px}
.kpi{background:#fff;border:1px solid #e6eaf2;border-radius:10px;padding:13px 14px}
.kpi .l{font-size:12px;color:#6b7280}
.kpi .v{font-size:22px;font-weight:800;margin-top:5px;letter-spacing:.2px}
.kpi .s{font-size:11.5px;color:#9aa3b2;margin-top:3px}
.card{background:#fff;border:1px solid #e6eaf2;border-radius:10px;padding:16px 18px;margin-bottom:16px}
.card h2{font-size:15.5px;margin:0 0 12px;padding-left:9px;border-left:3px solid #1a5276}
table{width:100%;border-collapse:collapse;font-size:12.5px}
th{background:#1a5276;color:#fff;font-weight:600;padding:8px 9px;text-align:left;white-space:nowrap}
td{padding:8px 9px;border-bottom:1px solid #eef1f6}
td.n{text-align:right;font-variant-numeric:tabular-nums}
td.b{font-weight:700}
tr.sub td{background:#fdf6e3}
tr.tot td{background:#eef4fa;font-weight:800}
small{color:#9aa3b2;font-size:10.5px}
.tag{background:#e8f0fe;color:#1a5276;border-radius:4px;padding:1px 5px;font-size:10.5px;margin-left:6px;font-weight:600}
.ok{color:#15803d;font-weight:700}
.note{background:#fbfcfe;border:1px dashed #cbd5e1;border-radius:8px;padding:11px 13px;font-size:12px;color:#4b5563;line-height:1.85}
.note b{color:#1a5276}
@media(max-width:1100px){.kpis{grid-template-columns:repeat(2,1fr)}}
@media(max-width:760px){.kpis{grid-template-columns:1fr}table{font-size:11.5px}th,td{padding:6px}}
</style></head><body><div class="wrap">
<h1>{{period}}应收账款账龄统计 · {{dept_label}}</h1>
<div class="meta">
截止 <b>{{cutoff}}</b> ｜ 数据源：<b>{{source}}</b> → Sheet「附表_逾期&amp;未逾期（汇总）」<br>
账龄口径：逾期判据 = <b>逾期金额 &gt; 0</b>；分档按逾期天数 ｜ 「逾期1-30天」含到期日当天（0 天）3 单 53,675 元 ｜ 生成 {{gen}}
</div>
<div class="kpis">
  <div class="kpi"><div class="l">应收总额</div><div class="v">{{tot_wan}}<span style="font-size:13px"> 万</span></div><div class="s">{{rows}} 单</div></div>
  <div class="kpi"><div class="l">逾期合计</div><div class="v" style="color:#b91c1c">{{ov_wan}}<span style="font-size:13px"> 万</span></div><div class="s">占应收 {{ov_pct}}</div></div>
  <div class="kpi"><div class="l">账期内</div><div class="v" style="color:#1d4ed8">{{nd_wan}}<span style="font-size:13px"> 万</span></div><div class="s">{{nd_pct}}</div></div>
  <div class="kpi"><div class="l">逾期60天以上</div><div class="v" style="color:#b91c1c">{{o60_wan}}<span style="font-size:13px"> 万</span></div><div class="s">{{o60_pct}} ｜ {{o60_n}} 单</div></div>
  <div class="kpi"><div class="l">逾期一年以上</div><div class="v" style="color:#7f1d1d">{{y1_wan}}<span style="font-size:13px"> 万</span></div><div class="s">{{y1_n}} 单 ｜ 最长期 {{max_days}} 天</div></div>
</div>
<div class="card"><h2>一、账龄结构</h2>{{svg}}
<div style="display:flex;flex-wrap:wrap;gap:14px;margin-top:12px;padding-top:11px;border-top:1px solid #eef1f6">
{{legend}}
</div></div>
<div class="card"><h2>二、分账龄统计（{{dept_label}}）</h2>
<table><thead><tr><th>账龄</th><th style="text-align:right">单数</th><th style="text-align:right">金额(元)</th>
<th style="text-align:right">占应收</th><th style="text-align:right">占逾期</th><th>占比</th></tr></thead>
<tbody>{{tbody}}</tbody></table></div>
<div class="card"><h2>三、分部门 × 账龄（元）</h2>
<table><thead><tr><th>销售部门</th>{{hdr_b}}<th style="text-align:right">应收合计</th><th style="text-align:right">占全司</th>
<th style="text-align:right">逾期60天以上</th></tr></thead><tbody>{{dept_rows}}</tbody></table>
<div class="note" style="margin-top:10px">{{scope_note}}</div></div>
<div class="card"><h2>四、分销售员 × 账龄（元）</h2>
<table><thead><tr><th>销售员</th><th style="text-align:right">单数</th>{{hdr_b}}
<th style="text-align:right">逾期合计</th><th style="text-align:right">应收合计</th><th style="text-align:right">逾期占比</th>
<th style="text-align:right">逾期60天以上</th></tr></thead><tbody>{{sp_rows}}</tbody></table></div>
<div class="card"><h2>五、勾稽校验（本表 vs 源文件《逾期应收回款_进度》）</h2>
<table><thead><tr><th>销售员</th><th style="text-align:right">逾期(本表)</th><th style="text-align:right">逾期(进度表)</th>
<th style="text-align:right">差</th><th style="text-align:right">60天以上(本表)</th><th style="text-align:right">60天以上(进度表)</th>
<th style="text-align:right">差</th></tr></thead><tbody>{{cc}}</tbody></table>
<div class="note" style="margin-top:10px">
<b>口径与坑位说明</b><br>
① <b>逾期判据用「逾期金额 &gt; 0」，不是「逾期天数 &gt; 0」</b> —— {{zero_txt}}。若按天数判据会把这类行误归「账期内」并丢掉金额。<br>
② 账龄分档互斥、合计 = 应收总额；「逾期60天以上」为 <b>61-365 天 + 365 天以上</b> 小计，与源表《进度表》「60天以上」列口径一致。<br>
③ 逐人两列勾稽差均为 <b>0.00</b>（{{cc_examples}}）。<br>
④ 逾期合计 <b>{{ov_exact}} 元</b>（占应收 {{ov_pct2}}）｜ 账期内 <b>{{nd_exact}} 元</b>｜ 最长逾期天数 <b>{{max_days}} 天</b>（{{max_cust}}）。<br>
⑤ <b>口径对照</b>：你要的「账期内 / 逾期30天 / 逾期60天 / 逾期60天以上 / 逾期一年以上」对应本表 ——
账期内 → <b>账期内</b>；逾期30天 → <b>逾期1-30天</b>；逾期60天 → <b>逾期31-60天</b>；
逾期60天以上 → <b>逾期61-365天 ＋ 逾期一年以上</b>（见〔逾期60天以上 小计〕行，{{o60_exact}} 元）；
逾期一年以上 → <b>逾期365天以上（一年以上）</b>。
</div></div>
</div><script>/* 静态页，无外部依赖 */</script></body></html>"""

max_days = max(r['od_days'] or 0 for r in D['detail'] if r['overdue'])
max_cust = next(r['cust'] for r in D['detail'] if (r['od_days'] or 0) == max_days)
y1 = D['overall']['逾期365天以上（一年以上）']
hdr_b = ''.join('<th style="text-align:right">%s</th>' % H.escape(k) for k in B)
_VALS = {
    'cutoff': D['meta']['cutoff'], 'source': D['meta']['source'], 'gen': D['meta']['generated'],
    'tot_wan': '%.2f' % wan(TOT), 'rows': str(D['rows']),
    'ov_wan': '%.2f' % wan(OV), 'ov_pct': pct(OV / TOT * 100),
    'nd_wan': '%.2f' % wan(ND), 'nd_pct': pct(ND / TOT * 100),
    'o60_wan': '%.2f' % wan(sub['金额']), 'o60_pct': pct(sub['金额'] / TOT * 100), 'o60_n': str(sub['单数']),
    'y1_wan': '%.2f' % wan(y1['金额']), 'y1_n': str(y1['单数']),
    'max_days': str(max_days), 'max_cust': H.escape(max_cust),
    'cc_examples': cc_examples, 'zero_txt': zero_txt, 'o60_exact': money(sub['金额']), 'ov_exact': money(OV), 'nd_exact': money(ND), 'ov_pct2': pct(OV / TOT * 100),
    'svg': svg, 'tbody': ''.join(tbody), 'dept_rows': ''.join(dept_rows), 'sp_rows': ''.join(sp_rows),
    'hdr_b': hdr_b, 'cc': cc, 'legend': legend,
    'period': PERIOD, 'dept_label': DEPT_LABEL, 'scope_note': SCOPE_NOTE,
}
page = HTML
for _k, _v in _VALS.items():
    page = page.replace('{{%s}}' % _k, str(_v))
_left = re.findall(r'\{\{\w+\}\}', page)
assert not _left, '未替换占位符: %s' % sorted(set(_left))
page = BK.inject(page, theme='light', tag='营销中心 · 销售管理', footer_extra='数据源：' + D['meta']['source'])
# 串味守卫：不得混入其它部门的标志性数值
# 只查「跨期/跨部门的总量值」串味；逐人金额（勾稽表）属本页数据，放行
_FOREIGN = {'1,757,504.70': ('OV',), '2,854,628.50': ('OV',), '844,980.60': ('SUB',), '1,114,564.00': ('SUB',)}
_own = {'OV': OV, 'SUB': sub['金额']}
_leak = [k for k, tags in _FOREIGN.items()
         if k in page and not any(abs(_own[t] - float(k.replace(',', ''))) < 0.01 for t in tags)]
assert not _leak, '❌ 跨期/跨部门数值串味: %s' % _leak
v = BK.verify(page)
bad = [k for k, ok in v.items() if not ok]
assert not bad, '品牌体检未过: %s' % bad
out_html = W + '/销售分析/' + _OUT.replace('应收账龄_', '应收账款账龄统计_') + '.html'
io.open(out_html, 'w', encoding='utf-8').write(page)
print('✅ HTML:', out_html, '%.0f KB' % (os.path.getsize(out_html) / 1024), '｜ 品牌体检', v)

# ══════════════ xlsx ══════════════
TH = PatternFill('solid', fgColor='1A5276')
HF = Font(color='FFFFFF', bold=True, size=10.5)
RED = PatternFill('solid', fgColor='FDEDEC')
YEL = PatternFill('solid', fgColor='FEF9E7')
GRN = PatternFill('solid', fgColor='EAFAF1')
BLU = PatternFill('solid', fgColor='EEF4FA')
THIN = Side(style='thin', color='D5DBE3')
BD = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)

wb = openpyxl.Workbook()


def sheet(title, header, rows, widths=None, fills=None):
    ws = wb.create_sheet(title)
    ws.append(header)
    for c in range(1, len(header) + 1):
        cell = ws.cell(1, c)
        cell.fill, cell.font, cell.border = TH, HF, BD
        cell.alignment = Alignment(horizontal='center', vertical='center')
    for i, r in enumerate(rows, start=2):
        for j, v in enumerate(r, start=1):
            cell = ws.cell(i, j, v)
            cell.border = BD
            if isinstance(v, (int, float)):
                cell.number_format = '#,##0.00' if isinstance(v, float) else '#,##0'
                cell.alignment = Alignment(horizontal='right')
            if fills and fills(i - 2):
                cell.fill = fills(i - 2)
    for j, w in enumerate(widths or [], start=1):
        ws.column_dimensions[get_column_letter(j)].width = w
    ws.freeze_panes = 'A2'
    return ws


# Sheet1 账龄汇总
rows = [[k, D['overall'][k]['单数'], D['overall'][k]['金额'], D['overall'][k]['金额'] / TOT,
         (D['overall'][k]['金额'] / OV if k != '账期内' else None), D['overall'][k]['客户数']] for k in B]
rows.append(['〔逾期60天以上 小计〕', sub['单数'], sub['金额'], sub['金额'] / TOT, sub['金额'] / OV, sub['客户数']])
rows.append(['合计', D['rows'], TOT, 1.0, None, len({r['cust'] for r in D['detail']})])
ws = sheet('账龄汇总', ['账龄', '单数', '金额(元)', '占应收', '占逾期', '客户数'], rows,
           [30, 10, 16, 11, 11, 10],
           lambda i: RED if i == 5 else (YEL if i == 6 else (BLU if i == 7 else None)))
for r in range(2, 9):
    for c in (4, 5):
        ws.cell(r, c).number_format = '0.0%'

# Sheet2 分部门
rows = []
for d, v in D['by_dept'].items():
    bb = v['buckets']
    tt = sum(bb[k]['金额'] for k in B)
    rows.append([d] + [bb[k]['金额'] for k in B] + [tt, v['over60']['金额'], v['over60']['金额'] / tt])
sheet('分部门×账龄', ['销售部门'] + B + ['应收合计', '逾期60天以上', '逾期60天以上占比'], rows,
      [28] + [15] * 5 + [16, 16, 18])

# Sheet3 分销售员
rows = []
for sp, v in sorted(D['by_sp'].items(), key=lambda kv: -kv[1]['逾期合计']):
    bb = v['buckets']
    rows.append([sp, v['单数']] + [bb[k]['金额'] for k in B] + [v['逾期合计'], v['合计'],
                v['逾期合计'] / v['合计'], v['over60']['金额']])
sheet('分销售员×账龄', ['销售员', '单数'] + B + ['逾期合计', '应收合计', '逾期占比', '逾期60天以上'], rows,
      [12, 8] + [15] * 5 + [15, 15, 11, 16], lambda i: RED if rows[i][7] > 500000 else None)

# Sheet4-6 明细
det = sorted(D['detail'], key=lambda r: -(r['od_days'] or 0))
ws = sheet('账龄明细(全部)', ['销售部门', '销售员', '往来单位', '单据编号', '到期日', '应收金额(元)',
                            '逾期天数', '账龄档', '本档金额(元)', '逾期金额(元)', '未逾期金额(元)', '10月回款进度'],
           [[r['dept'], r['sp'], r['cust'], r['bill'], r['due'], r['amt'], r['od_days'], r['bucket'],
             r['amt'], r['od_amt'], r['nd_amt'], r['oct_pay']] for r in det],
           [22, 9, 34, 17, 12, 14, 9, 22, 14, 14, 14, 14],
           lambda i: RED if det[i]['bucket'].startswith(('逾期61', '逾期365')) else (
               YEL if det[i]['bucket'] == '逾期31-60天' else None))
od = [r for r in det if r['overdue']]
sheet('逾期明细', ['销售部门', '销售员', '往来单位', '单据编号', '到期日', '逾期天数', '逾期金额(元)', '账龄档'],
      [[r['dept'], r['sp'], r['cust'], r['bill'], r['due'], r['od_days'], r['od_amt'], r['bucket']] for r in od],
      [22, 9, 34, 17, 12, 9, 15, 22],
      lambda i: RED if od[i]['bucket'].startswith(('逾期61', '逾期365')) else (YEL if od[i]['bucket'] == '逾期31-60天' else None))
nd = [r for r in det if not r['overdue']]
sheet('账期内明细', ['销售部门', '销售员', '往来单位', '单据编号', '到期日', '未逾期金额(元)', '账龄档'],
      [[r['dept'], r['sp'], r['cust'], r['bill'], r['due'], r['nd_amt'], r['bucket']] for r in nd],
      [22, 9, 34, 17, 12, 15, 12], lambda i: GRN)

# Sheet7 勾稽
sheet('勾稽校验', ['销售员', '逾期(本表)', '逾期(进度表)', '差', '60天以上(本表)', '60天以上(进度表)', '差'],
      [[c['销售员'], c['逾期(本表)'], c['逾期(进度表)'], c['差'], c['逾期60天以上(本表)'],
        c['逾期60天以上(进度表)'], c['差2']] for c in D['crosscheck']], [12, 16, 16, 10, 16, 18, 10])
ws = sheet('口径说明', ['项', '说明'], [
    ['截止期', D['meta']['cutoff']],
    ['数据源', D['meta']['source']],
    ['逾期判据', '逾期金额 > 0（非逾期天数 > 0）'],
    ['账龄分档', '账期内 / 逾期1-30天 / 逾期31-60天 / 逾期61-365天 / 逾期365天以上（一年以上），互斥且合计=应收总额'],
    ['逾期1-30天含0天', '3 单到期日当天（逾期天数=0）但挂逾期金额，合计 53,675 元，计入逾期1-30天档'],
    ['逾期60天以上口径', '= 61-365天 + 365天以上，与源表《进度表》「60天以上」列一致（844,980.60 元）'],
    ['勾稽结果', '逐人「逾期」「60天以上」与源表《进度表》差均为 0.00'],
    ['报送范围', SCOPE_NOTE],
], [22, 96])
del wb['Sheet']
out_xlsx = W + '/销售分析/' + _OUT.replace('应收账龄_', '应收账款账龄统计_') + '.xlsx'
wb.save(out_xlsx)
print('✅ xlsx:', out_xlsx, '%.0f KB' % (os.path.getsize(out_xlsx) / 1024), '｜ Sheets:', len(wb.sheetnames))
