#!/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"

# ── 客户级归属覆盖（持久化，唯一覆盖点；优先于 customer_sp_map.json）────────────
#   用途：当 customer_sp_map.json 的「最近月份最常见销售员」口径与实际下单人
#   不一致、且用户已拍板时，在这里钉死归属。**每次重跑管道都会重新生效**，
#   因此重建 customer_sp_map.json 或重跑 refresh_sandept.sh 都不会把它推翻。
#   规则（用户 2026-10-06 拍板）：谁下单归谁 —— 客户级归属为单值，整户归「金额主下单人」。
#   键=客户名（与 customers[].cust 完全一致），值=归属销售员（写出仍会过 sp_merge 归并）。
CUSTOMER_OWNER_OVERRIDE = {
    # 2026-10-06：该户 2026 年订单实为 陈溪子 8.80 万 + 刘新元 0.99 万，
    #   map 里因「最近月份最常见」错记为 刘新元(→归并刘子研)，改归主下单人 陈溪子。
    "深圳市雅为世纪科技有限公司": "陈溪子",
}

# 覆盖理由（写入 meta，供审计）
OVERRIDE_REASON = {
    "深圳市雅为世纪科技有限公司": "用户 2026-10-06 拍板：谁下单归谁。2026 年该客户订单=陈溪子 8.80 万"
                                 "（2026-09 销售拓展部）+ 刘新元 0.99 万（2026-06 大客户部）；"
                                 "客户级归属为单值，整户（9.79 万）归金额主下单人 陈溪子。",
}


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（含销售员归并：黄明月/张立娅/刘新元 → 刘子研）──
    #   归属优先级：CUSTOMER_OWNER_OVERRIDE（用户拍板覆盖）> customer_sp_map.json
    _ov_hit = []
    for c in custs:
        nm = (c.get("cust") or "").strip()
        raw = CUSTOMER_OWNER_OVERRIDE.get(nm) or spm.get(nm)
        if nm in CUSTOMER_OWNER_OVERRIDE:
            _ov_hit.append(nm)
        c["sales"] = _spm.merge_sp(raw)   # 无归属 = 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 可见")
    # 客户级覆盖自检（真跑）
    for _cn, _to in CUSTOMER_OWNER_OVERRIDE.items():
        _rows = [c for c in custs if (c.get("cust") or "").strip() == _cn]
        _got = sorted({c["sales"] for c in _rows})
        _amt = r2(sum(c.get("amt") or 0 for c in _rows))
        print(f"  覆盖[{_cn}] → {_got}（{len(_rows)} 行，Σ {_amt} 万）"
              f" {'OK' if _got == [_spm.merge_sp(_to)] else 'MISMATCH'}")
    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),
    }
    # 客户级归属覆盖审计（持久，用户拍板）
    if CUSTOMER_OWNER_OVERRIDE:
        J["meta"]["customer_owner_override"] = {
            "rule": "客户级归属覆盖：键=客户名，值=归属销售员；优先于 customer_sp_map.json",
            "source": "脚本/apply_sales_owner.py 的 CUSTOMER_OWNER_OVERRIDE（唯一持久覆盖点，"
                       "每次重跑管道都会重新生效 → 重建 customer_sp_map 也不会被推翻）",
            "decided_by": "用户 2026-10-06 拍板（谁下单归谁）",
            "applied": {
                k: {
                    "to": _spm.merge_sp(v),
                    "from_raw": (spm.get(k) or None),
                    "from_effective": _spm.merge_sp(spm.get(k)),
                    "reason": OVERRIDE_REASON.get(k, ""),
                } for k, v in CUSTOMER_OWNER_OVERRIDE.items()
            },
            "rows_touched": {k: sum(1 for c in custs if (c.get("cust") or "").strip() == k)
                             for k in CUSTOMER_OWNER_OVERRIDE},
        }
    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())
