#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""给 三部门逐月_2026.json 的客户行补「归属销售员」字段，并重建集团口径数组。

背景（2026-10-05）：
  看板页要上工作台，必须做行级数据授权（非管理员只能看自己的客户）。
  但 customers 数组原本只有 {month, dept, cust, amt, amt_2025, grp} —— **没有归属字段**，
  想按人过滤都无从下手。故从 数据/customer_sp_map.json 引入归属。

授权规则（对齐既定红线）：
  · 非 admin/gm：只看 sales 含自己姓名的客户
  · 无归属客户（sales=null）：仅 admin/gm 可见
  · **集团口径：仅 admin/gm 可见** —— 因为 49 个集团里有 7 个跨多个销售员
    （如 天隆/佰奥莱博/丽珠 = 刘子研+刘新元，圣湘 = 李江辉+赵云浩），
    按单人过滤会得出「只属于我的那个集团」这种会误导的假集团。
    → customers_grp 每行带 sales_list（成员归属集合）与 mixed 标记，供前端判定。

铁律：
  · 只加字段，不改任何金额 → Σ amt 与 Σ amt_2025 必须不变（自检）
  · 幂等：重复运行结果一致

用法：env -u PYTHONPATH -u VIRTUAL_ENV /usr/bin/python3 脚本/apply_sales_owner.py
"""
import io
import json
import os
import shutil
import sys
from collections import defaultdict

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import sp_merge as _spm   # 销售员归并映射（唯一权威源，见 脚本/sp_merge.py）

HOME = os.path.expanduser("~")
W = f"{HOME}/Desktop/Hermes输出-工作类"
DATA = f"{W}/数据/三部门逐月_2026.json"
SPMAP = f"{W}/数据/customer_sp_map.json"


def r2(x):
    return round(float(x), 2)


def load_gmap():
    """读集团映射：客户名 → 集团名（与 脚本/apply_group_rollup.py 同源，保证口径一致）。

    权威源：数据/diag_mappings.json 的 cust_to_group（官方《诊断原料&生命科学客户清单》
    「集团简称 → 成员客户」，真归属 k!=v）。兜底：数据/pricing/_key_customers.json 的 group。
    """
    dm = f"{W}/数据/diag_mappings.json"
    if os.path.exists(dm):
        try:
            c2g = (json.load(io.open(dm, encoding="utf-8")) or {}).get("cust_to_group") or {}
            gm = {str(k).strip(): str(v).strip() for k, v in c2g.items()
                  if k and v and str(k).strip() != str(v).strip()}   # 只取真归属
            if gm:
                return gm
        except Exception as e:
            print(f"  ⚠️ diag_mappings.json 读取失败({e})，回退 _key_customers.json")
    try:
        kdata = json.load(io.open(f"{W}/数据/pricing/_key_customers.json", encoding="utf-8"))
        rows = kdata if isinstance(kdata, list) else kdata.get("customers", [])
        gm = {}
        for r in rows:
            if isinstance(r, dict) and r.get("group"):
                nm = (r.get("name") or r.get("cust") or "").strip()
                if nm:
                    gm[nm] = str(r["group"]).strip()
        return gm
    except Exception:
        return {}


def main():
    spm = (json.load(io.open(SPMAP, encoding="utf-8")) or {}).get("map") or {}
    spm = {str(k).strip(): str(v).strip() for k, v in spm.items() if k and v}
    # 集团名的权威集合（来自集团映射表，非销售员名）：用于判定 customers_grp 行的 grp
    gmap = load_gmap()
    grpset = {v for k, v in gmap.items() if k != v}
    J = json.load(io.open(DATA, encoding="utf-8"))
    custs = J.get("customers") or []

    # ── ① 实体口径：补 sales（含销售员归并：黄明月/张立娅/刘新元 → 刘子研）──
    for c in custs:
        c["sales"] = _spm.merge_sp(spm.get((c.get("cust") or "").strip()))   # 无归属 = None

    # ── ② 集团口径：重建，带上归属集合 ──
    bucket = defaultdict(lambda: {"amt": 0.0, "amt_2025": 0.0, "members": set(), "owners": set()})
    for c in custs:
        key = c.get("grp") or c.get("cust")
        b = bucket[(c["month"], c["dept"], key)]
        b["amt"] += c.get("amt") or 0.0
        b["amt_2025"] += c.get("amt_2025") or 0.0
        b["members"].add(c.get("cust"))
        b["owners"].add(c.get("sales") or "(无归属)")

    cgrp = []
    for (m, d, key), b in bucket.items():
        owners = sorted(b["owners"])
        cgrp.append({
            "month": m, "dept": d, "cust": key,
            "amt": r2(b["amt"]), "amt_2025": r2(b["amt_2025"]),
            "grp": key if key in grpset else None,
            "merged": len(b["members"]) if len(b["members"]) > 1 else 1,
            "sales_list": owners,
            "mixed": len(owners) > 1,           # 跨多个销售员 → 非 admin/gm 不可见
        })
    cgrp.sort(key=lambda x: (x["month"], x["dept"], -x["amt"]))

    # ── ③ 自检：金额必须守恒 ──
    e26 = r2(sum(c.get("amt") or 0 for c in custs))
    g26 = r2(sum(c["amt"] for c in cgrp))
    e25 = r2(sum(c.get("amt_2025") or 0 for c in custs))
    g25 = r2(sum(c["amt_2025"] for c in cgrp))
    owned = [c for c in custs if c.get("sales")]
    own_amt = r2(sum(c["amt"] or 0 for c in owned))
    tot_amt = e26
    print("================ 自检门禁 ================")
    print(f"  归属映射源: customer_sp_map.json（{len(spm)} 条）")
    print(f"  实体口径 {len(custs)} 行 ｜ Σ今年 {e26:,.2f} ｜ Σ去年 {e25:,.2f}")
    print(f"  集团口径 {len(cgrp)} 行 ｜ Σ今年 {g26:,.2f} ｜ Σ去年 {g25:,.2f}")
    print(f"  有归属客户: {len(owned)}/{len(custs)} 行 ｜ 金额 {own_amt:,.1f} 万 ({own_amt/tot_amt*100:.1f}%)")
    print(f"  无归属金额: {tot_amt-own_amt:,.1f} 万 ({(tot_amt-own_amt)/tot_amt*100:.1f}%) → 仅 admin/gm 可见")
    mixed = [c for c in cgrp if c.get("mixed")]
    gk = {c["cust"] for c in cgrp if c.get("grp")}
    mixedk = {c["cust"] for c in mixed}
    print(f"  跨销售员的集团: {len(mixedk)}/{len(gk)} → 仅 admin/gm 可见")
    g1, g2 = abs(e26 - g26) < 0.05, abs(e25 - g25) < 0.05
    print(f"  ① Σ今年守恒 -> {'PASS' if g1 else 'FAIL'}")
    print(f"  ② Σ去年守恒 -> {'PASS' if g2 else 'FAIL'}")
    if not (g1 and g2):
        print("  ❌ 金额不守恒，拒绝写盘")
        return 1

    J["customers"] = custs
    J["customers_grp"] = cgrp
    J.setdefault("meta", {})["sales_scope"] = {
        "rule": "非 admin/gm 仅见 sales 含本人姓名的客户；无归属客户仅 admin/gm；集团口径仅 admin/gm",
        "source": "数据/customer_sp_map.json 的 map（method: 最近月份最常见销售员）",
        "owned_rows": len(owned), "total_rows": len(custs),
        "unowned_amount": r2(tot_amt - own_amt), "mixed_groups": len(mixedk),
    }
    shutil.copy2(DATA, DATA + ".bak-pre-salesowner")
    io.open(DATA, "w", encoding="utf-8").write(json.dumps(J, ensure_ascii=False, separators=(",", ":")))
    print(f"\n✅ 已写回 {DATA}（备份 .bak-pre-salesowner）")
    return 0


if __name__ == "__main__":
    sys.exit(main())
