diff --git a/.scratch/a7-acceptance/README.md b/.scratch/a7-acceptance/README.md new file mode 100644 index 0000000..50477e7 --- /dev/null +++ b/.scratch/a7-acceptance/README.md @@ -0,0 +1,78 @@ +# A7 后台管理验收包(a7-acceptance) + +- **生成**:2026-09-09(一期 [a7-parity](../a7-parity/demo/index.html) 12 tab + 二期 [a7-heavy](../a7-heavy/demo/index.html) 3 tab 合并;合并脚本 [_merge_demo.py](_merge_demo.py) 可重放) +- **目标环境**:测试服务 `https://ai.itc.vip/crm-api`(**demo 默认基址已指向,双击即用**;如需换环境,访问一次 `index.html?api=<新基址>` 即被记住) + +## 交付物清单 + +| 文件 | 用途 | +|---|---| +| [demo/index.html](demo/index.html) | **15 tab 统一验收 demo**(双击即用,默认管理员登录入口) | +| [验收步骤清单.md](验收步骤清单.md) | 逐 tab 逐步操作与预期说明,**结果列留白**待验收人填写 | +| [验收结果汇总.md](验收结果汇总.md) | 验收结果登记表(结构化缺陷块,**可整文件回喂 AI 定位修复**) | +| [issues/_TEMPLATE.md](issues/_TEMPLATE.md) | 单缺陷工单模板(本地 issue tracker 惯例,一缺陷一文件) | +| parity-smoke-result.json / heavy-smoke-result.json | 冒烟自测证据(58 步 + 22 步) | +| shots-parity/ · shots-heavy-run/ | 冒烟截图(各 10 张 / 9 张) | +| _merge_demo.py · _check_demo.py · _gen_checklist.py | 再生成 / 静态校验 / 清单再生成工具 | + +## 新人上手(4 步) + +1. **打开**:双击 `demo/index.html`,页头「API:」应显示 `https://ai.itc.vip/crm-api`。 +2. **登录**:点「uid 登录」→ 应显示「已登录:罗伟健 (739564171091247104)」。 +3. **验收**:按《[验收步骤清单.md](验收步骤清单.md)》逐 tab 从上到下点每个步骤按钮——输出框**绿色 = PASS**,**红色(失败 + code=)= 候选缺陷**;每步结果填进清单结果列。 +4. **记缺陷**:FAIL 的按《[验收结果汇总.md](验收结果汇总.md)》格式登记(界面红框原文、执行日志面板里的请求 URL/参数直接复制);每 tab 末尾的「清理」步骤**必须执行**,不留演示数据。 + +## 覆盖范围(15 tab = A7 全分区) + +| tab | 原型分区 | 覆盖 | +|---|---|---| +| 用户管理 user | A7-1 | 用户列表 / 统计三时间戳 / 移交预览·自移交 61019·执行清零·返还 | +| 同步任务 sync | A7-1-2(二期票01) | 任务化契约 SYNC-MMdd / 异步终态 / 61023 并发占位 / 分页倒序 / 详情五块 / 设置读写+61022 / lastSyncTime 切表 | +| 角色管理 role | A7-2 | stats 四计数 / 批量增量分配 / copy 停用草稿 / enable 61021 / 内置角色保护 | +| 权限点管理 res | A7-2 | references 引用清单 / 停启 roundtrip / 禁删两段 61009+61020 | +| 方案推送规则 push | A7-3 | D-01 无步骤④发布 / 4 步骤回显 / 64029 / 64030 / copy / 64027 | +| 项目查重规则 dedup | A7-3 | 发布+referenceCount / 64031 两路 / 引用计数入账 / 64030 / 64033 | +| 项目规则 projrule | A7-3 | current 5 区块聚合 / 分区保存回显 / 64036 比例校验 | +| 线索池 pool | A7-3 线索规则 | 三类异人成员建池回填 / 导出导入 BatchResult / 列表 / 清理 | +| 公海绑定数 poolrule | A7-3-4(二期票03) | 部门专用规则发布 / boundOppCount 增量 base+n / 默认规则兜底 / 清理 | +| 全局日志 glog | A7-4(二期票02 · crm-log) | AOP 采集 / 操作·登录·批量三日志 / 结果文件下载 ZIP / 打包下载 / 40401 形态 / BUDDY 权限拒绝 | +| 字典种子验证 seed | A7-5 | opp_stage 06/07 + opportunity_status 4 值 | +| 分组管理 dgroup | A7-5(v3 契约) | 新建/编辑/启停/删除/设默认值(仅二级项) | +| 字典项管理 ditem | A7-5(v3 契约) | 无键值自动编码 / 组内名称唯一 63007 / 复合子项 / 63013 默认项禁停 | +| 两级树 dtree | A7-5 | 一级列表 + 行内二级展开 | +| 引用计数联测 dref | A7-5 | 客户引用 +1 / 换引用迁移 / 引用项禁删 63009 / 归档保留计数 | + +## 登录与账号 + +- **管理员**:罗伟健 `739564171091247104`(页头默认选中) +- **非管理员**:曾偲青(BUDDY) `744842318024015872`——全局日志 tab 步骤 9 用它验证权限拒绝口径 +- 登录通道 = `GET /api/auth/debug/token?userId=`(verify 环境调试通道);输入框可粘贴**任意测试环境 userId** 直登 +- 演示数据一律 `e2d-` 前缀可辨;操作/登录/批量日志行与同步任务行为审计数据,留存不删 + +## 已知边界与争议留白(🟡 不报缺陷) + +- 数据共享 3 页(A7-2-3 backup):等原型正式化,未纳入盘点。 +- 同步任务两态容忍:测试环境钉钉凭据不可达 → 任务落 all_failed(或长时间 running)为正常形态;61023 并发占位同理。 +- lastSyncTime 口径 = 同步任务表最新成功行 endTime,无成功行为空。 +- 职务下拉值域来源、「参与商机数」列、池规则冗余列、幻影挂账 3 端点、项目规则「阶段提醒」区块:**争议留白**,见清单尾部待确认清单。 +- 全局日志采集 = 写侧(POST)先行不采查询 GET;登录日志仅「退出」钩子采集(debug/token 不走登录采集点)。 + +## 冒烟自测(可选,验收人自证环境可用) + +```bash +# python 用仓库自带 venv;两个脚本均已支持 E2D_* 环境变量 +E2D_API=https://ai.itc.vip/crm-api \ +E2D_DEMO='E:\code\crm-backend-matt\.scratch\a7-acceptance\demo\index.html' \ +E2D_SHOTS='E:\code\crm-backend-matt\.scratch\a7-acceptance\shots-parity' \ +E2D_RESULT='E:\code\crm-backend-matt\.scratch\a7-acceptance\parity-smoke-result.json' \ +.scratch/customer-e2e/.venv10/Scripts/python.exe .scratch/a7-parity/demo_smoke.py # 一期 58 步 + +E2D_API=... E2D_DEMO=... E2D_SHOTS='...shots-heavy-run' E2D_RESULT='...heavy-smoke-result.json' \ +.scratch/customer-e2e/.venv10/Scripts/python.exe .scratch/a7-heavy/demo_smoke.py # 二期 22 步 +``` + +**最近结论(2026-09-09)**:一期 58/58、二期 22/22 **ALL_PASS**(对测试服务),console/page error 双零(BUDDY 403 拒绝豁免除外),网络层零 PUT/DELETE、零路径变量形态。 + +## 给负责人的回喂流程 + +新人填完《验收结果汇总.md》→ **整个文件直接交给 AI**(附本目录与后端仓 `E:\code\crm-backend-matt` 路径)→ AI 按缺陷块逐条定位修复;争议留白项等负责人确认后再改版。 diff --git a/.scratch/a7-acceptance/_check_demo.py b/.scratch/a7-acceptance/_check_demo.py new file mode 100644 index 0000000..b784ac0 --- /dev/null +++ b/.scratch/a7-acceptance/_check_demo.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +"""统一 demo 静态校验:重复 const / onclick 函数覆盖 / 重复 function 定义。""" +import io, re, subprocess, sys, collections +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') + +P = r'E:\code\crm-backend-matt\.scratch\a7-acceptance\demo\index.html' +t = open(P, encoding='utf-8').read() +js = re.search(r'', t, re.S).group(1) + +# 1. node --check 语法校验 +r = subprocess.run(['node', '--check', '--input-type=commonjs', '-'], input=js.encode('utf-8'), + capture_output=True) +print('node --check:', 'OK' if r.returncode == 0 else 'FAIL') +if r.returncode != 0: + print(r.stderr.decode('utf-8', 'replace')[:1500]) + +# 2. 重复 const 声明 +consts = re.findall(r'^const\s+(\w+)', js, re.M) +dup = [k for k, c in collections.Counter(consts).items() if c > 1] +print('重复 const:', dup if dup else '无') + +# 3. onclick 引用函数是否都定义 +called = set(re.findall(r'onclick="(\w+)\(', t)) +defined = set(re.findall(r'(?:async\s+)?function\s+(\w+)\(', js)) | set(consts) +missing = sorted(c for c in called if c not in defined) +print('onclick 引用数:', len(called), '| 未定义:', missing if missing else '无') + +# 4. 重复 function 声明(同名) +fns = re.findall(r'^(?:async\s+)?function\s+(\w+)\(', js, re.M) +dupfn = [k for k, c in collections.Counter(fns).items() if c > 1] +print('重复 function:', dupfn if dupfn else '无') + +# 5. 关键锚点 +for a in ['SY_STATUS_TXT', 'STATUS_TXT', 'syncPage()', 'infoBox', 'waitFor', 'remintToken']: + print(f' {a}:', js.count(a)) diff --git a/.scratch/a7-acceptance/_gen_checklist.py b/.scratch/a7-acceptance/_gen_checklist.py new file mode 100644 index 0000000..1f15c23 --- /dev/null +++ b/.scratch/a7-acceptance/_gen_checklist.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +"""从统一 demo 生成《验收步骤清单.md》:逐 tab 提取联测步骤(按钮 + 预期说明),结果列留白。""" +import io, re, sys, html, pathlib +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') + +ROOT = pathlib.Path(__file__).resolve().parent +t = (ROOT / 'demo' / 'index.html').read_text(encoding='utf-8') + +TAB_META = { + 'user': ('A7-1 用户管理', '用户列表 / 统计出参 / 移交全链路 / 时间戳'), + 'sync': ('A7-1-2 同步任务(二期票01)', '任务化契约 / 分页倒序 / 详情五块 / 设置读写与边界 / lastSyncTime 切表'), + 'role': ('A7-2 角色管理', '统计四计数 / 批量增量分配 / copy / 启停校验 / 内置保护'), + 'res': ('A7-2 权限点管理', '引用清单 / 停启 roundtrip / 禁删两段'), + 'push': ('A7-3 方案推送规则', '状态机全链(草稿/发布/64029/64030/copy/64027)'), + 'dedup': ('A7-3 项目查重规则', '发布与引用计数 / 64031 两路 / 64030 / 64033'), + 'projrule': ('A7-3 项目规则', 'current 5 区块聚合 / 分区保存 / 64036 比例校验'), + 'pool': ('A7-3 线索规则·线索池', '三类异人成员 / 导出导入 / 列表 / 清理'), + 'poolrule': ('A7-3-4 公海规则「绑定商机(数量)」(二期票03)', '部门专用规则发布 / boundOppCount 增量 / 默认兜底 / 清理'), + 'glog': ('A7-4 日志管理(二期票02 · crm-log)', 'AOP 采集 / 操作·登录·批量日志 / 结果文件 / 权限口径'), + 'seed': ('A7-5 字典种子验证', 'opp_stage 06/07 + opportunity_status 4 值'), + 'dgroup': ('A7-5 数据字典·分组管理(v3 契约)', '新建/编辑/启停/删除/设默认值(仅二级项)'), + 'ditem': ('A7-5 数据字典·字典项管理(v3 契约)', '无键值自动编码 / 组内名称唯一 63007 / 复合子项 / 63013 禁停'), + 'dtree': ('A7-5 数据字典·两级树', '一级列表 + 行内二级展开'), + 'dref': ('A7-5 数据字典·引用计数联测', '客户引用 +1 / 换引用迁移 / 引用项禁删 63009 / 归档保留计数'), +} + +def strip(h): + h = re.sub(r'<[^>]+>', ' ', h) + return re.sub(r'\s+', ' ', html.unescape(h)).strip() + +out = [] +out.append('# A7 后台管理验收步骤清单(a7-acceptance · 15 页全分区)\n') +out.append('- **被测对象**:`demo/index.html`(15 tab 统一版)× 测试环境 `https://ai.itc.vip/crm-api`') +out.append('- **使用方法**:双击打开 demo → 页头「uid 登录」(默认管理员罗伟健)→ 按下表逐 tab 从上到下点每个步骤按钮 →') +out.append(' 输出框**绿色(✅/提示)= PASS**,**红色(失败 + code=)= 候选缺陷** → 结果列填 PASS / FAIL,FAIL 的按《验收结果汇总.md》格式记缺陷。') +out.append('- **纪律**:数字类断言(条数/计数)随库内数据浮动,以**相对变化**为准;每 tab 最后的「清理」步骤务必执行,不留演示数据。\n') + +for tab, label in re.findall(r'', t): + sec_m = re.search(r'
]*>(.*?)
', t, re.S) + if not sec_m: + continue + sec = sec_m.group(1) + meta = TAB_META.get(tab, ('', '')) + out.append(f'\n---\n\n## {label}({tab})· {meta[0]}\n') + if meta[1]: + out.append(f'> 覆盖:{meta[1]}\n') + steps = re.findall(r'
([^<]*)(.*?)
', sec, re.S) + if steps: + out.append('| 步骤 | 操作 | 说明 / 预期 | 结果(PASS/FAIL) | 备注 |') + out.append('|---|---|---|---|---|') + for num, body in steps: + btns = re.findall(r']*>([^<]+)', body) + hints = re.findall(r'(.*?)', body, re.S) + desc = ';'.join(strip(h) for h in hints) or '—' + out.append(f'| {strip(num)} | {" / ".join(strip(b) for b in btns) or "—"} | {desc} | | |') + else: + btns = [] + for b in re.findall(r']*>([^<]+)', sec): + x = strip(b) + if x and x not in btns: + btns.append(x) + out.append('本页为主界面操作(无联测步骤面板),对照原型逐控件核对:\n') + out.append('可操作入口:' + '、'.join(btns)) + out.append('\n| 操作 | 预期 | 结果(PASS/FAIL) | 备注 |') + out.append('|---|---|---|---|') + out.append('| 逐控件操作并观察列表/弹窗反馈 | 与原型 A7 对应页一致(弹窗字段、错误码文案、列表列头) | | |') + +out.append(''' +--- + +## 争议留白(待产品/负责人确认,本轮不作为缺陷) + +- [ ] 数据共享 3 页(A7-2-3 backup 草稿箱):等原型正式化,未纳入盘点与验收。 +- [ ] 职务下拉值域来源(OA title 去重 vs 字典分组):挂账待拍板。 +- [ ] 角色管理「处理使用人员」弹窗「当前参与商机数」列:计数口径未拍板。 +- [ ] OpportunityPoolRule 列表「所属战队」等冗余列:随 UI 确认后补。 +- [ ] 幻影挂账 3 端点(POST /depts/save、/depts/delete、/users/admin-role):原型无入口,保留运维用途。 +- [ ] 项目规则「阶段提醒」区块:原型自注「待补充」。 +- [ ] 全局日志采集覆盖面 = 写侧(POST)先行,不采「查询」GET;登录日志仅采「退出」钩子(debug/token 不走登录采集点)。 + +## 已知形态说明(不算缺陷) + +- 同步任务两态容忍:测试环境钉钉凭据不可达时,任务以 all_failed 终态落行(或长时间 running)均为正常形态。 +- lastSyncTime 口径 = 同步任务表最新成功行 endTime;无成功行时为空。 +- 操作/登录/批量日志行与同步任务行 = 审计数据,留存不删;其余演示数据一律 e2d- 前缀 + 步骤面板末步清理。 +''') + +p = ROOT / '验收步骤清单.md' +p.write_text('\n'.join(out), encoding='utf-8') +print('OK ->', p) +print('tabs:', len(re.findall(r'