#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
刷新 pricing/_key_customers.json 的 sales_2026（ERP 权威口径）
================================================================
背景：该文件 14 个脚本引用，但**没有任何脚本负责重算 sales_2026** ——
      它的 2026 金额停留在一份 H1 快照（latest_date=2026-06-30，合计 2181.7万），
      而 ERP 全年真值是 3401.5万，导致前端「客户」Tab 的 2026 列系统性偏低。

做法：
  ① 从官方《诊断原料&生命科学客户清单》读「集团简称 → 成员客户」映射
  ② 从 ERP 最新全年件按客户聚合 2026 价税合计（标准订单+已审核+非赠品+剔宝锐系）
  ③ 逐条刷新：
       实体客户  → sales_2026 = ERP 该客户 2026 金额（ERP 无记录 → 0）
       集团汇总  → sales_2026 = 其成员实体客户之和
  ④ 只改 sales_2026，其余字段一律不动；写入前自动备份

用法：
  /usr/bin/python3 脚本/refresh_key_customers.py            # dry-run（只报告）
  /usr/bin/python3 脚本/refresh_key_customers.py --apply    # 实际写入（先备份）
"""
import os, sys, json, shutil, collections, importlib.util
from datetime import datetime

import pandas as pd

B = os.path.expanduser('~/Desktop/Hermes输出-工作类')
os.chdir(B)

KC_PATH   = '数据/pricing/_key_customers.json'
AUTH_XLS  = '数据/源文件/诊断原料&生命科学客户清单20260831.xls'
APPLY     = '--apply' in sys.argv

# ── ① 官方清单：集团 → 成员 ──
def load_group_map():
    if not os.path.exists(AUTH_XLS):
        print(f"  ⚠️ 无官方清单 {AUTH_XLS}，跳过集团映射")
        return {}, {}
    xls = pd.ExcelFile(AUTH_XLS)
    members = collections.defaultdict(set)      # 集团简称 → {客户名}
    cust2grp = {}                               # 客户名 → 集团简称
    n = 0
    for sn in xls.sheet_names:
        df = xls.parse(sn, header=None, dtype=str)
        if df.empty:
            continue
        # 找表头行（含「集团简称」）
        hdr = None
        for i in range(min(5, len(df))):
            row = [str(x) for x in df.iloc[i].tolist()]
            if any('集团简称' in x for x in row):
                hdr = i
                cols = row
                break
        if hdr is None:
            continue
        try:
            ci_grp = next(i for i, c in enumerate(cols) if '集团简称' in c)
            ci_nm  = next(i for i, c in enumerate(cols) if '客户名称' in c)
        except StopIteration:
            continue
        for _, r in df.iloc[hdr+1:].iterrows():
            g = str(r.iloc[ci_grp]).strip() if ci_grp < len(r) else ''
            c = str(r.iloc[ci_nm]).strip() if ci_nm < len(r) else ''
            if not g or not c or g == 'nan' or c == 'nan':
                continue
            members[g].add(c)
            cust2grp.setdefault(c, g)
            n += 1
    print(f"  官方清单：{len(members)} 个集团 ｜ {n} 条成员关系 ｜ {len(cust2grp)} 个客户有集团归属")
    return members, cust2grp

# ── ② ERP：客户 → 2026 金额 ──
def load_erp_amounts():
    spec = importlib.util.spec_from_file_location('core', '脚本/erp_sales_core.py')
    core = importlib.util.module_from_spec(spec); spec.loader.exec_module(core)
    fp = core.find_latest_erp()
    df = pd.read_excel(fp, dtype=str)
    df = df[(df['单据类型'] == '标准销售订单') & (df['单据状态'] == '已审核') & (df['是否赠品'] != '是')]
    df = df[~df['客户'].fillna('').str.contains('宝锐|宝泰|横琴', regex=True)]
    df['_a'] = pd.to_numeric(df['价税合计'], errors='coerce').fillna(0)
    d = df.groupby('客户')['_a'].sum().to_dict()
    print(f"  ERP：{os.path.basename(fp)} ｜ {len(d)} 个客户 ｜ 合计 {sum(d.values())/1e4:,.1f}万")
    return {k: round(v, 2) for k, v in d.items()}, fp

def main():
    print("=" * 74)
    print(f"刷新 _key_customers.sales_2026   {'【写入模式】' if APPLY else '【DRY-RUN 只报告】'}")
    print("=" * 74)

    members, cust2grp = load_group_map()
    erp, erp_fp = load_erp_amounts()
    kc = json.load(open(KC_PATH, encoding='utf-8'))
    print(f"  _key_customers：{len(kc)} 条 ｜ 现 sales_2026 合计 {sum((c.get('sales_2026') or 0) for c in kc)/1e4:,.1f}万")
    print()

    # 先算实体客户新值
    ent_new = {}
    for c in kc:
        nm = c['name']
        if nm in members:          # 是集团简称 → 汇总条目
            continue
        ent_new[nm] = erp.get(nm, 0.0)

    changed = same = 0
    detail = []
    for c in kc:
        nm = c['name']
        old = c.get('sales_2026') or 0
        if nm in members:
            new = round(sum(ent_new.get(m, 0.0) for m in members[nm]), 2)
            kind = f"集团汇总({len(members[nm])}成员)"
        else:
            new = ent_new.get(nm, 0.0)
            kind = "实体"
        if abs(new - old) > 0.5:
            changed += 1
            detail.append((nm, old, new, kind))
        else:
            same += 1
        if APPLY:
            c['sales_2026'] = new
            # ⚠️ total 必须与年度值自洽 —— build_customer_360.py:268 已定义项目口径
            #    total = sales_2024 + sales_2025 + sales_2026（三年累计）。
            #    原先 total 是旧快照，刷新 sales_2026 后不同步 → 272 条 total < 三年和。
            #    代价：9 条含 2024 前历史的客户损失合计 12.7万（占全年 0.03%），
            #    换来全表口径统一 + 客户 Top10 排序与年度值一致。
            c['total'] = round((c.get('sales_2024') or 0) + (c.get('sales_2025') or 0)
                               + (c.get('sales_2026') or 0), 2)

    print(f"  将被修改: {changed} 条 ｜ 保持不变: {same} 条")
    print()
    print("  === 变动 Top 25（按新值降序）===")
    print(f"  {'客户':<38}{'旧(H1)':>10}{'新(ERP)':>11}{'差':>11}  类型")
    print("  " + "-" * 84)
    for nm, old, new, kind in sorted(detail, key=lambda x: -x[2])[:25]:
        print(f"  {nm[:36]:<38}{old/1e4:>9.1f}万{new/1e4:>10.1f}万{(new-old)/1e4:>+10.1f}万  {kind}")

    # ⚠️ 勾稽必须用「实体客户」口径：集团汇总条目是其成员的再聚合，
    #    两者相加会双重计数（虚增约 780万），不能作为覆盖率分子。
    ent_only = sum(v for k, v in ent_new.items())
    grp_only = sum((c.get('sales_2026') or 0) for c in kc if c['name'] in members)
    print()
    print("  === 勾稽（实体口径）===")
    print(f"    ERP 全年客户合计            : {sum(erp.values())/1e4:,.1f}万")
    print(f"    实体客户覆盖（{len(ent_new)} 家）   : {ent_only/1e4:,.1f}万")
    print(f"    未覆盖（未建重点客户档案）   : {(sum(erp.values())-ent_only)/1e4:,.1f}万")
    print(f"    理论全年（three_year_monthly）: 3401.5万")
    print(f"    （另有 {len(members)} 个集团汇总条目 合计 {grp_only/1e4:,.1f}万，是成员的再聚合，不参与勾稽）")

    if APPLY:
        ts = datetime.now().strftime('%Y%m%d_%H%M%S')
        bak = f"{KC_PATH}.bak-before-sales2026refresh_{ts}"
        shutil.copy2(KC_PATH, bak)
        # ⚠️ 必须 indent=1：该文件既有格式为 indent=1（reassign_resigned/fix_owner 同），
        #    用 indent=2 会把整个文件重排，污染 diff 并可能影响下游按行读取的脚本
        json.dump(kc, open(KC_PATH, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
        print(f"\n  ✅ 已写入 {KC_PATH}")
        print(f"    备份: {bak}")
    else:
        print(f"\n  （DRY-RUN，未写入。加 --apply 执行）")

if __name__ == '__main__':
    main()
