# Map: 客户联系人图谱与联系人页签整节落地(A4-4-2 §6 全量) `wayfinder:map` created: 2026-09-09 ## Destination **A4-4-2 联系人页签第 6 节需求全量落地并验收**:6.4 联系人图谱(职务层级自动布局 + 公司节点 + 编辑器全套 + 持久化)+ 6.1 查询展示(脱敏点击明文 + 查看埋点)+ 6.2 快速添加 + 6.3 页内编辑 + 联系人导入三段式 + 职务/来源字典前置。终点 = 测试环境(ai.itc.vip/crm-api)亲手验收 + 两件套交付(Bruno 增量同步 + e2e-report)。 ## Notes ### 范围拍板(chart grill 2026-09-09,四问) 1. **目的地 = 全链落地可验收**(非 spec 冻结 / 非 POC):图里装执行(task 票串行链,对齐 customer-module / customer-e2e 先例),终点落在测试环境人工验收。 2. **随行项全部进图**:图谱本体 + 职务二级联动选择器 + 字典前置(job_title 标准职务预置 + contact_source 分组,顺手销 W-01)+ 联系人导入三段式(当初票 06 约定「随图谱 effort」)。 3. **列表侧增强也进图**(A4-4-2 §6 整节):6.1 查询展示 / 6.2 快速添加 / 6.3 页内编辑。**脱敏口径拍板(用户)**:不搞三档权限——所有人默认脱敏显示,页面点击临时明文,明文查看行为埋点记录(谁/何时/哪个联系人号码)。此拍板**替代**当初客户 E2E 划出的「三档脱敏」,仅适用于联系人电话,不自动扩展到其他域。 4. **验收底盘 = 对齐 customer-e2e 先例全套**:API E2E 增量套件 + demo Playwright 冒烟(含图谱编辑器交互)+ Bruno 文档同步 + e2e-report 聚合。 ### 需求权威与底稿 - **原型需求文本全量在案**:`.scratch/opportunity-bugfix/lanhu-pages/A4-4-2_客户详情(联系人).md` —— u533 = 6.1/6.2/6.3 列表侧需求、**u536 = 6.4 联系人图谱 15 条完整需求**(生成规则/公司节点/未识别层级/卡片悬停气泡/编辑模式/多父连线/系统边删除持久否决/循环检测/并发冲突/保存重置/验收标准)。判定纪律沿用:**原型第一 > spec > 问用户**。 - **存量占位**:`CustomerContact` 实体已有 `job_title_name / job_title_id / job_title_category / job_title_level` 四列(customer-module D5 留位,注释「图谱用」);`customer_module/map.md` D5 = 图谱划出决策原文。 - **导入先例**:客户导入三段式(`CustomerImportExecutor`:template/preview+confirm/result + Analyzer 预检 + fail 明细 + clip500);联系人导入对齐此形态,复用深度由票 01 定。 - **契约权威**:`.scratch/customer-module/API-SUMMARY.md`(73 端点);新端点须增量登记并过 ADR-0017 纪律(flat 动词 + 查询参数传 id,禁 @PathVariable/PUT/DELETE/@RequestBody)。 - **前端形态**:本仓交付 = 后端 + 单文件 demo(`.scratch/customer-e2e/demo/index.html` 同款纪律,当前 961 行);**真实 Vue 前端另仓另人,demo 即参考实现**。 ### 环境事实(2026-09-09 实探) - 测试环境 `https://ai.itc.vip/crm-api`:jar 已含 rework+defectfix 最新构建;verify profile debug token 可用(`GET /api/auth/debug/token?userId=739564171091247104`);数据 mine 15 / pool 3;**contact_source 字典 0 项 = W-01(本图销号)**。 - 本地起服先例:verify profile + `CRM_MINIO_AK/SK`;Maven 增量陷阱(先删 fat jar);E2E 解释器 `py -X utf8`;Playwright `.scratch/customer-e2e/.venv10`(chromium 已缓存)。 - 新表(边/查看日志/导入表)走 `ddl-auto: update` 自动建(共享库加表不加列,无 DDL 票);注意孤立单大写字母 `@Column` 陷阱(AGENTS.md 规则)与 UTF-8 BOM 纪律。 - 跨模块缝合(crm-customer ↔ crm-dict 字典、联系人增强触碰脱敏口径):按惯例 **ADR + CONTEXT.md 双文档化**(票 03/04 落点)。 ### 默认技能 - `/prototype` — 票 00 已 resolved(原型 = 正式 demo 底座;后续需求微调仍直改 prototype/ 并续记修订) - `/webapp-testing` — 票 00 自检截图 / 票 08 demo Playwright - `/grilling` + `/domain-modeling` — 票 01 已 resolved(spec.md 冻结);若 spec 需返工重开 grilling 时再用 - `/bruno-sync` — 票 09 文档同步(动手前先读 skill 机制 + bruno-sync.config.json) ## Decisions so far - **2026-09-09 改序(用户)**:先原型后 spec——新票 00 原型 demo(纯前端单文件 HTML + mock 数据,零后端依赖)插到最前,验证对 u533/u536 的需求理解;图谱全量 + 列表侧关键交互都进。验收通过 → 该原型升格为正式 demo 底座(票 06 改为「接真实后端 API」不重写);理解偏差 → 改原型不进代码。票 01 spec 冻结改为「demo 验收后继续」,届时以 demo 为活参照(原型里 mock 实现的规则 = grilling 素材)。 - **2026-09-10 原型第⑥轮验收反馈(用户,[票 00](issues/00-prototype-demo.md))**:等级换 **1–10(10 = 顶层)** + 画 10 等级格子区分;**等级 10 行在公司上方、1–9 在下方**;未设置等级的**固定右侧栏**;**拖动到具体等级格子 = 设置等级**(连线到未设等级者改引导拖格子,弹窗定级删除);**两点之间只有一条边**(无向查重,显式入规则面板);**等级锁定:节点只有所有边都取消了才能重新设置等级**(含 pending 与公司系统边)。原型已实现并自检 44/44 PASS(0 报错,shots/10-leveldrop.png);衍生待钉点(等级↔字典映射细则、右侧栏持久化语义、定级保存确认)已记入票 01 素材。 - **2026-09-10 原型第⑦轮验收反馈(用户,同日第二轮,[票 00](issues/00-prototype-demo.md))**:**等级锁定规则细化(放宽⑥的全禁口径)**——有**上级**连线时不能调整等级;**只有下级连线时可以调整**,但不能调整到**低于或等于自己最高的下级**(公司系统边不计入比较);**取消等级(拖回右侧栏)仍须先取消所有连线**。原型已实现(tryLevelDrop 重写,edgesOfContact 删除)并自检 49/49 PASS(0 报错);顺带全局纠正⑥落盘笔误「辿」(U+8FBF)→「边」(U+8FB9) ×10。 - **2026-09-10 票 00 resolved(原型验收通过,用户指示「根据原型推进 Map」)**:7 轮验收反馈全部落地(①定级弹窗 ②连线交互修复 ③值域数值化 ④单父+公司系统边+auto 单父化 ⑤无向连线+一致性清理 ⑥10 等级格子+右侧栏+拖格子定级 ⑦等级调整约束细化),自检 49/49 PASS、零报错;原型升格正式 demo 底座(票 06 接真实 API 不重写)。**票 01 解锁为 frontier**:已定规则全景 12 条 + 待钉点 5 条灌入票 00 Answer / 票 01 票面;票 01 原「多父 DAG」假设已按修订④ 修正为单父树。见 [票 00](issues/00-prototype-demo.md)。 - **2026-09-10 票 02 resolved(字典前置落地)**:crm-dict 缝合完成——`dict_item.level` 通用档位列(ADR-0036,档位=图谱等级**初始值**,业务侧定级永不回写字典)+ job_title 两级树种子 5+9(档位 10/6/3)+ contact_source 4 项(**W-01 销号**)+ CONTEXT.md「档位」词条。执行拍板:二级兜底 code=`other_job`(组内 code/名称双重唯一性校验,不能与一级 other 同名同码,spec 已同步);零新端点(复用 tree/enabled-list);起服 HTTP 断言归票 07 E2E(单测已等价覆盖数据源)。crm-dict 64/64 全绿。见 [票 02](issues/02-dict-prereq.md)。 - **2026-09-10 票 03 resolved(图谱域后端落地)**:spec 契约全量实现(ADR-0037)——edge/graph 两张新表(边=无软删快照表,物理删+全量重插)+ `level_source` 列 + `graph/detail|save` 两端点 + 七步校验链 + CAS 版本锁(显式 SQL 非 @Version 插件)。执行拍板四条:错误码顺延 67018/67019/67020(67017 已被战略协议占用);版本行 id PK+唯一索引(BaseEntity 雪花零例外);等级调整约束先于边校验(撤级/调级提示优先于「端点未定级」);首定级豁免 + 撤级 null 走 LambdaUpdateWrapper(MP NOT_NULL 陷阱,同 CustomerOwnershipWriter)。联系人增删联动 bump + 级联清边。crm-customer 198 例全绿(图谱 12 例新集)。见 [票 03](issues/03-graph-backend.md)。 - **2026-09-10 票 04 resolved(联系人列表侧增强落地)**:reveal/quickAdd/batchEdit/export 四端点 + `customer_contact_reveal_log` 埋点(只写不管理,D10)+ **全局脱敏口径**(ADR-0038:mask 前3+****+后4 出参五处 + isMaskedForm 回显保护 + 导出/port 同口径;checkPhone 维持 D26 不动)。执行拍板:导出全量(spec 冻结优先于票面「当前页勾选」);quickAdd 部分成功 / batchEdit 原子批(补交失败行不死锁);错误码 67021/67022 顺延。基座 update 回显保护(脱敏串提交=不改号)。crm-customer 209 例全绿(增强 11 例新集)。见 [票 04](issues/04-contact-enhance-backend.md)。 - **2026-09-10 票 01 resolved(spec 冻结,grilling 12 问全部拍板)**:产出 [spec.md](spec.md) 冻结版——D1–D12 拍板 + T1/T2 技术自定 + 新表 5 张 DDL 草案 + 端点契约 15 个 + 字典种子定稿(job_title 两级树 5+9 / contact_source 4 项,W-01 销号)。**最大反转:只有手动边,没有自动边**(推翻原型「紧邻更高档」自动生成与删除否决机制;公司边 = 等级 10 派生视图不可删,摘线=降级);等级双值模型(字典=初始值,level_source 区分字典跟随/手工锁定);保存才入库 + 图谱版本乐观锁;明文埋点独立表;联系人导入独立表镜像。demo 修订⑧已预告票 06(接线前先回写原型行为)。四条 fog 全部消解。见 [票 01](issues/01-spec-freeze.md)。 - **2026-09-10 票 05 resolved(联系人导入三段式落地)**:六端点 + Analyzer/Executor(ADR-0039)——`contact_import_task`/`contact_import_fail` 独立表镜像客户导入(D11);单 sheet 五列模板(职务/来源=字典 code,值域预检 + 四列联动);SKIP(默认)命中=疑似重复不写入、OVERWRITE 才覆盖;行级部分成功(全失败也 DONE);落库 D1 档位 levelSource=1(UPDATE 职务变化重置手工锁;值全同计 unchanged 不触碰);错误码 67023/67024 顺延(67021/67022 被票 04 占用,第三次连锁)。票面「图谱自动更新引擎」随 spec D6 作废——成功收尾一次 bump 版本即信号。crm-customer 218 例全绿(导入 9 例新集)。见 [票 05](issues/05-contact-import.md)。 - **2026-09-10 票 06 resolved(demo 接真实 API,原型升格正式 demo)**:修订⑧先行回写(ADR-0040)——auto 边引擎/否决交互全删(rebuildAutoEdges/autoParentsOf/vetoed tombstone)、公司边派生渲染(edge-company 虚线浅蓝无 × 不可删、摘线=降级、撤级豁免)、层级变化只做 sanitizeManualEdges 清理不生成边(非法边 save 67018 兜底);数据层收敛单一 fetch 层(API_BASE ?api= + apiGet/apiPost/apiDownload + apiEnvelope Result 信封,mock 死代码清零);导入三段式 modal 接五端点(page 为列表分页不在 demo 范围);saveGraph 全量快照 + 67017 重拉重置基准。自检 selfcheck.py 改造:Playwright route 内存后端桩接管 14 端点(含 save CAS/quickAdd 追加/batchEdit 四列联动 bump/导入固定预检,template 钉 GET 405 语义),59 步 0 失败(原 51 步按修订⑧改造 + 新增导入冒烟)。冒烟/对账暴露并修复 3 个原型缺陷:jobFull option value 错绑类别 code(职务变更静默失效)/脱敏回显形态误拦保存(含 * 放行=不改口径)/模板下载 POST→GET 405。CONTEXT.md 无需更新(后端零代码变更)。见 [票 06](issues/06-demo-extension.md)。 - **2026-09-10 票 07 resolved(E2E API 套件全绿)**:e2e-graph.py(对齐 e2e-incr 模式,G-00~G-06 七流程 + teardown + scan_calls)起服实测 **58 checks = 57✅ + 1⚠(D-G1 缺陷记档)+ 0❌,幂等重跑稳定,SCAN 17/17 零缺零余**;产物 e2e-graph-checks.json / e2e-graph-specimens.json(17 specimens)。票面随修订⑧适配三条:①图谱读无自动边——断言 D1 字典联动 level {10,6,3,None}+脱敏+bump 信号;②「否决持久化」作废(无自动边即无否决),边 CRUD 按全量终态语义设计(重复边=快照内两条同边、单父违反=快照内一个子两个父,错误码用真实 67018/67019/67020 而非 demo 桩自洽的 67017);③「关系变更日志」适配:graph/save 无专属 oplog(grep 零命中),断言 contact 动作链路 CONTACT_ADD/CONTACT_EDIT「批量编辑联系人【N 条】」+ action 过滤纯度。**发现 D-G1 (P2)**:batchEdit「空串=清空」语义在 Spring 表单绑定下不可达(StringEditor allowEmpty 把空串转 null,Service isBlank→null 分支永不可达,Param 注释与运行时不符)——缺陷验证模式记档不排查,前端清空手机号需 reveal+明文替代。语义分叉记档:导入无手机号=FAIL「手机号必填(导入查重键)」(批量无人在场不可 SUSPECT)vs quickAdd 同条件=疑似重复提示,两口径并存合理。并发冲突用同用户双 session(CAS 按版本不按用户;跨用户实测被 visibleToCurrent 数据可见域拦,团队成员不在口径内)。无后端变更 → 无 ADR、CONTEXT.md 不动。见 [票 07](issues/07-e2e-api-suite.md)。 - **2026-09-10 票 08 resolved(UI Playwright 冒烟全绿)**:demo_smoke_graph.py(对齐 demo_smoke_r2 模式)——真服务 8080 + 真实夹具(e2c-cgd* 客户 + quickAdd 4 联系人,归属 ADMIN 解耦 BUDDY),票面 6 条按修订⑧适配(公司边无×/否决作废/右侧栏=未设置等级),**40 checks 全 PASS 两轮幂等** + console/page error=0 + 零 PUT/DELETE + 13 张截图 + DB 正门清扫。冒烟路径三件基建经验:①demo 无登录 UI → page.route 反代(urllib 原样转发 + 去 origin/Authorization/host/content-length 头 + 注入 Bearer + 补 CORS:credentials:'include' 下 ACAO 不能是 *,须回显 origin + Allow-Credentials:true);②**Chromium/Playwright 的 post_data_buffer 不含上传文件内容**(572B 头部 vs 4.6KB xlsx,文件只回引用),route.fetch/urllib 转发均丢 file part → 67023「请选择导入文件」——upload 请求分流为 Python 侧重读同一 xlsx 直调真实 API,真实预检结果 fulfill 给页面渲染(confirm/result/failures 无文件请求继续走转发);③demo「保存全部」后保持页内编辑态(listEditing=true,单元格仍 input),reveal/导出断言前须先退出编辑;视口 1720×1080 保证图谱卡片在视口内(否则 bounding_box 坐标落视口外鼠标拖拽无效)。交互覆盖:真实鼠标拖卡位移/.dot-bottom 拖线(tryConnect 自动定向)+× 删边、重置确认/未保存三选(继续编辑+放弃修改)/完成绘制退出(保存修改)三弹窗、悬停气泡、页内编辑撤销+保存(DB 取证)、quickAdd 前端查重→ack→服务端部分成功 2 成 1 败、脱敏明文+reveal_log 埋点 DB 取证、模板/导出下载、导入三段式(预检 FAIL 行→确认轮询→失败明细→好行入库坏行未入)。无后端变更 → 无 ADR、CONTEXT.md 不动。见 [票 08](issues/08-ui-playwright-smoke.md)。 - **2026-09-10 票 09 resolved(Bruno 同步 + 报告聚合 + 发版移交,**destination 达成,Map 收官**)**:①Bruno 增量同步——12 个新 .bru 全部 generated 通道落 `D:\code\crm-api-docs\A4 客户管理\客户详情\` 三新子目录(联系人图谱 2/联系人 4/联系人导入 6;**票面预估 11,源码导入组含 /page 实为 12,结构听源码**);目录归属按源码类级 @Tag(A4-4-2 客户详情页联系人区)而非票面原述「联系人页签」,与独立菜单「A4 客户管理/联系人」旧端点分属不同目录,页面归属语义正确;示例值=票 07 specimens 实测真值(9 端点 JSON 逐字段真值 + export/template 二进制流文字说明);verify 对账 12/12 PASS;git 未动留用户审查。②报告聚合——[e2e-report.md](e2e-report.md) 八章终稿(spec 逐条三档 13✅+1⚠记录性 T2/缺陷 D-G1 P2 台账/双量尺覆盖对账/W-01 销号四节点链/API-SUMMARY 增量登记零漂移:§1 补 67018–67024 + 新 §2.7b 12 端点)。③发版移交——jar 重建 BUILD SUCCESS(首跑 clean 失败 = 本地 8080 JVM 锁 jar,停服释放;`-rf :crm-app` 中段 resume 从本地仓库拉陈旧依赖报错——同 `-pl` 单模块坑族,全反应堆重跑 3:05);探针发版前基线已取(contact_source 0 项 = 测试环境旧 jar 铁证;新 jar 启动 ddl-auto 建表 + DictDataInitializer 自动补种,无需手工迁移);服务器三步(scp jar → restart.sh → 探针复验)需 SSH 凭据 + 亲手验收清单随 e2e-report.md §八移交用户。工具坑:Write 不能出 workspace → bruno-out 副本 + python 复制;verify 脚本首版 regex `^` 无 MULTILINE 错报 0/12,逐行 match 后 12/12。见 [票 09](issues/09-bruno-report-handoff.md)。 ## Not yet specified - (2026-09-10 票 01 spec 冻结后四条全部消解:分档映射→D3 三档制 10/6/3;自动边增量语义→随 D6「只有手动边」作废;导入表复用→D11 独立表镜像;规模上限→T2 记录性预案。后续新 fog 逐票追加于此。) ## Out of scope - **节点展开/折叠、联系人搜索与节点定位** — 原型 6.4 第 6 条明说「本期暂不支持」。 - **跨客户人脉关系** — 原型 6.4 第 1 条明说「不展示跨客户关系」。 - **OCR 快捷新增** — customer-module D5 划出,未随行进图(按钮占位照旧)。 - **商机域 A5-1-2-2「生态关系图谱」** — 商机域独立特性,与本图无关。 - **真实 Vue 前端仓实现** — demo 即前端参考实现;Vue 仓由前端团队按 Bruno 文档另做。 - **其他域的脱敏策略** — Q3 拍板仅限联系人电话;三档脱敏原决策不复活。 ## 票与依赖图 ``` 00 (task: 原型 demo 验证理解) ─→ 01 (grilling: spec 冻结,以 demo 为活参照) ─┬→ 02 (task: 字典前置) ─┬→ 03 (task: 图谱后端) ─┐ │ ├→ 04 (task: 联系人增强后端) ─┼→ 06 (task: demo 接真实 API) ─┐ │ └→ 05 (task: 联系人导入) ────┤ ├→ 08 (task: UI Playwright) ─┐ │ └→ 07 (task: E2E API 套件) ┘ ├→ 09 (task: Bruno+报告) │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ (07 也是 08/09 前置) ``` Frontier:**无——destination 已达成(2026-09-10 票 09 resolved,Map 收官)**。全链 00→09 十票全 resolved。发版移交余项(服务器三步需 SSH 凭据 + 用户亲手验收清单)见 [e2e-report.md §八](e2e-report.md)。