#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
经理口径二次收口：KPI/趋势/部门拆分/订单，PC + 移动端
=========================================================
病根（刘欣 = 销售拓展部经理，仍能看到全公司销售额与订单）：
 1) getKPI() 只有「admin/gm → 全公司」和「个人」两个分支，manager 掉进个人分支；
    年度趋势的目标/实际来自全公司统一口径（d.yearTargets2026 + 全公司月度实际）→ 全公司数据外泄。
 2) 看板「三部门拆分」无条件遍历全部部门 → 大客户部/生命科学部的当月/Q3 数字外泄。
 3) 无筛选时 fullYearTax 回退到全公司合计（dash.total_h1_h2 / dash.total_h1 + prog.total）→ 外泄。
 4) app.html（移动端）完全没有 manager 适配：客户按 c.dept 硬匹配（客户表无该字段→看 0 个）、
    订单/商机只按本人、fullYearTax 用全公司数。

修法：manager 一律「本部门成员集合」作用域；全公司口径只给 admin/gm。
"""
import io, re, sys, shutil, os

PC = '工具/index.html'
MB = '工具/app.html'
APPLY = '--apply' in sys.argv
CHECK = '--check' in sys.argv

def esc(s):
    return s

# ══════════════════ app.html：先补 helper（放 renderCustomers 前面，函数声明会提升）══════════════════
HELPERS = r'''
/* ── 数据作用域（2026-09-20）：经理看本部门，销售看本人，admin/gm 看全部 ── */
function normDeptName(d) {
  d = String(d || '');
  if (d.indexOf('大客户') >= 0) return '大客户部';
  if (d.indexOf('生命科学') >= 0 || d === '浙江' || d === '广东') return '生命科学销售部';
  if (d.indexOf('拓展部') >= 0) return '销售拓展部';
  return d;
}
// 返回 {names:[本部门在职成员], region:'浙江'|'广东'|''}；admin/gm 返回 null（看全部）
function deptScope() {
  var u = state.user || {};
  var role = u.role || 'sales';
  if (role === 'admin' || role === 'gm') return null;
  if (role !== 'manager') return { names: [u.name || ''], region: '' };
  var p = ((state.data.personnel || {}).personnel || []).filter(function(x) { return x && x.status === '在职'; });
  var nd = normDeptName(u.dept);
  var names = p.filter(function(x) { return normDeptName(x.dept || x.department) === nd; }).map(function(x) { return x.name; });
  var region = '';
  if (nd === '生命科学销售部') {
    if (String(u.dept || '').indexOf('浙江') >= 0) region = '浙江';
    else if (String(u.dept || '').indexOf('广东') >= 0) region = '广东';
  }
  return { names: names, region: region };
}
function inScope(sc, name) { return !!sc && sc.names.indexOf(name) >= 0; }

'''

# manager 的 KPI 分支（PC/移动端共用同一段代码，靠缩进适配）
MANAGER_KPI = r'''  // ── 部门经理：本部门 KPI（2026-09-20 刘欣案；经理≠管理员，不得看全公司）──
  if (role === 'manager') {
    var _nd = normDeptName(u.dept);
    var _uo = String(u.dept || '');
    var _region = _uo.indexOf('浙江') >= 0 ? '浙江' : (_uo.indexOf('广东') >= 0 ? '广东' : '');
    var _fit = function(x) {
      if (normDeptName(x.dept || x.department) !== _nd) return false;
      if (_region && x.region && String(x.region).indexOf(_region) < 0) return false;
      return true;
    };
    var _people = (dash.people || []).filter(_fit);
    var _dRows = (dash.depts || []).filter(function(dp) { return normDeptName(dp.dept) === _nd; });
    var _dr = _dRows[0] || (_dRows.length ? _dRows[0] : {});
    var _sum = function(a) { var t = 0; (a || []).forEach(function(v) { t += (v || 0); }); return t; };
    var _annual = _people.reduce(function(s, p) { return s + (p.target || 0); }, 0) || (_dr.target || 0);
    var _ytd = _people.reduce(function(s, p) { return s + (p.h1_total || 0) + (p.h2_actual || 0); }, 0);
    if (!_ytd) _ytd = (_dr.h1_total || 0) + (_dr.h2_actual || 0);
    var _h1m = (_dr.h1_months || []).slice(0, 6), _h2m = (_dr.h2_months || []).slice(0, 6);
    var _h2a = (_dr.h2_actual_months || []).slice(0, 6);
    var _mi = Math.max(0, new Date().getMonth() - 6);
    var _isH2 = new Date().getMonth() >= 6;
    var _mT = _isH2 ? (_h2m[_mi] || 0) : (_h1m[new Date().getMonth()] || 0);
    var _mA = _isH2 ? (_h2a[_mi] || 0) : 0;
    var _lm = new Date().getMonth(); // 上月（0-based）
    var _last = (_lm >= 7) ? (_h2a[_lm - 7] || 0) : 0;
    var _q3T = _sum(_h2m.slice(0, 3)), _q3A = _sum(_h2a.slice(0, 3));
    return { label: _nd, dept: _nd, role: 'manager', annual: _annual, ytd: _ytd,
             pct: _annual ? Math.round(_ytd / _annual * 1000) / 10 : 0,
             monthly: { targets: _h1m, actuals: [] }, h2Targets: _h2m, h2Actuals: _h2a,
             yearTargets: _h1m.concat(_h2m), yearActuals: [0, 0, 0, 0, 0, 0].concat(_h2a),
             current: _mA, last: _last, monthTarget: _mT, monthActual: _mA,
             q3Target: _q3T, q3Actual: _q3A, deptMembers: _people.map(function(p) { return p.name; }) };
  }

'''

def patch_common(path, s):
    """PC 与移动端共有的四处"""
    n = 0
    # 1) getKPI：manager 分支（插在个人分支之前）
    for anchor in ["  // 非 admin: 显示个人 KPI\n", "  // 个人 KPI：非 admin，或 admin 下钻到具体销售员\n"]:
        if anchor in s:
            s = s.replace(anchor, MANAGER_KPI + anchor, 1); n += 1; break
    # 2) 三部门拆分：经理只看自己部门
    old = "  (dash.depts || []).forEach(function(dp) {\n    var di = deptNames.indexOf(dp.dept);\n    if (di < 0) return;"
    new = ("  var _scDept = (state.user || {}).role === 'manager' ? normDeptName((state.user || {}).dept) : '';\n"
           "  (dash.depts || []).forEach(function(dp) {\n    var di = deptNames.indexOf(dp.dept);\n    if (di < 0) return;\n"
           "    if (_scDept && normDeptName(dp.dept) !== _scDept) return; // 经理：不展示其它部门")
    if old in s: s = s.replace(old, new, 1); n += 1
    # 3) fullYearTax 全公司回退：只给 admin/gm
    old = "  if (!filteredSalesSet) fullYearTax = dash.total_h1_h2 || fullYearTax;"
    new = ("  var _isAllScope = (userRole === 'admin' || userRole === 'gm');\n"
           "  if (!filteredSalesSet && _isAllScope) fullYearTax = dash.total_h1_h2 || fullYearTax;\n"
           "  if (!filteredSalesSet && !_isAllScope) fullYearTax = 0; // 非全量角色：不展示全公司合计")
    if old in s: s = s.replace(old, new, 1); n += 1
    # 4) 无筛选时的全公司年度目标/累计：只给 admin/gm
    old = "    filteredAnnual = dash.total_target || 0;\n    filteredYtd = dash.total_h1 || 0;"
    new = ("    var _isAll2 = (userRole === 'admin' || userRole === 'gm');\n"
           "    filteredAnnual = _isAll2 ? (dash.total_target || 0) : 0;\n"
           "    filteredYtd = _isAll2 ? (dash.total_h1 || 0) : 0;")
    if old in s: s = s.replace(old, new, 1); n += 1
    return s, n

def main():
    shutil.copy(PC, PC + '.bak_before_mgr2')
    shutil.copy(MB, MB + '.bak_before_mgr2')
    tot = 0

    # ═══ PC ═══
    s = io.open(PC, encoding='utf-8').read()
    s, n = patch_common(PC, s)
    tot += n
    print('  ✅ PC  通用作用域 %d 处' % n)

    # ═══ 移动端 ═══
    s2 = io.open(MB, encoding='utf-8').read()
    before = s2
    # helper 注入
    anchor = 'function renderCustomers(main) {'
    if 'function deptScope()' not in s2 and anchor in s2:
        s2 = s2.replace(anchor, HELPERS + anchor, 1)
        print('  ✅ MB  注入 deptScope/normDeptName/inScope')
    s2, n = patch_common(MB, s2)
    print('  ✅ MB  通用作用域 %d 处' % n)
    tot += n
    # 客户表过滤：manager 用部门成员（客户表无 dept 字段）
    old = "  if (role === 'manager') return custs.filter(function(c) { return c.dept === dept; });"
    new = "  if (role === 'manager') return custs.filter(function(c) { return inScope(deptScope(), c.salesperson || c.sales); });"
    if old in s2: s2 = s2.replace(old, new, 1); print('  ✅ MB  客户表过滤→部门成员'); tot += 1
    # 客户池
    old = ("  var userName = (state.user||{}).name;\n"
           "  if (userName && ((state.user||{}).role!=='admin')) {\n"
           "    all = all.filter(function(c){return c.owner===userName||(c.operators||[]).indexOf(userName)>=0;});\n"
           "  }")
    new = ("  var userName = (state.user||{}).name;\n"
           "  var _scC = deptScope();\n"
           "  if (_scC) {\n"
           "    all = all.filter(function(c){return inScope(_scC, c.owner)||(_scC.names.some(function(nm){return (c.operators||[]).indexOf(nm)>=0;}));});\n"
           "  }")
    if old in s2: s2 = s2.replace(old, new, 1); print('  ✅ MB  客户池→部门成员'); tot += 1
    # 商机
    old = ("  var oppUserName = (state.user || {}).name;\n"
           "  if (oppUserName && ((state.user || {}).role !== 'admin')) {\n"
           "    opps = opps.filter(function(o) { return o.sales === oppUserName; });\n"
           "  }")
    new = ("  var oppUserName = (state.user || {}).name;\n"
           "  var _scO = deptScope();\n"
           "  if (_scO) {\n"
           "    opps = opps.filter(function(o) { return inScope(_scO, o.sales); });\n"
           "  }")
    if old in s2: s2 = s2.replace(old, new, 1); print('  ✅ MB  商机→部门成员'); tot += 1
    # 订单列表
    old = ("  var uname = (state.user||{}).name;\n"
           "  if (uname && ((state.user||{}).role!=='admin')) {\n"
           "    orders = orders.filter(function(o) { return o.creator===uname; });\n"
           "  }")
    new = ("  var uname = (state.user||{}).name;\n"
           "  var _scS = deptScope();\n"
           "  if (_scS) {\n"
           "    orders = orders.filter(function(o) { return inScope(_scS, o.creator); });\n"
           "  }")
    if old in s2: s2 = s2.replace(old, new, 1); print('  ✅ MB  订单列表→部门成员'); tot += 1
    # 订单 fullYearTax：非 admin/gm 用自己作用域的订单合计
    old = "  var fullYearTax = (dash.total_h1 || 0) + ((_shipProgM === new Date().getMonth() + 1) ? (prog.total || 0) : 0);"
    new = ("  var _roleS = (state.user || {}).role;\n"
           "  var fullYearTax;\n"
           "  if (_roleS === 'admin' || _roleS === 'gm') {\n"
           "    fullYearTax = (dash.total_h1 || 0) + ((_shipProgM === new Date().getMonth() + 1) ? (prog.total || 0) : 0);\n"
           "  } else {\n"
           "    fullYearTax = orders.reduce(function(s2, o) { return s2 + (parseFloat(o.taxTotal || o.amount || 0) || 0); }, 0);\n"
           "  }")
    if old in s2: s2 = s2.replace(old, new, 1); print('  ✅ MB  订单全公司合计→本部门'); tot += 1

    if s2 == before and tot == 0:
        print('  ⚠️ 移动端无改动（锚点未命中，需人工核对）')

    if APPLY:
        io.open(PC, 'w', encoding='utf-8').write(s)
        io.open(MB, 'w', encoding='utf-8').write(s2)
        print('\n  📦 已写入 2 个文件，共 %d 处' % tot)
    else:
        print('\n  （预演，未写入；加 --apply 生效）')

main()
