# 10 · 拍板修复 N1+N2+N3(当轮) **Type:** task **Status:** resolved **Blocked by:** 08 ## Question 票 08 用户拍板「全部当轮修」的三项缺口,本轮落地并回归: - **N1(P2·字段级)**:`GraphNodeDTO` 补 `isInternal` / `giftRemark` 两字段 + `toNode()` 填充(真源 `CustomerContact`);`图谱全量读.bru` nodes[]. 结构表同步补 2 行;demo `cardBubbleOver` 三行切真值(人脉行维持 "--" 注记) - **N2(P3·审计级)**:`CustomerConstants` 加 `ACTION_GRAPH_EDIT` + `CustomerContactGraphServiceImpl.save` 落一条叙事 oplog(操作人/动作/客户) - **N3(P2·跨模块)**:crm-project `/api/project/list` 加 `customerId` 过滤(`ProjectListParam` + 查询侧);customer 侧照商机先例建 `CustomerProjectQueryPort` + 实现 + `detailHead.projectCount` 切真值;`详情公共头部.bru` 的 projectCount 行说明同步(恒 null 注记改真值口径) **纪律**:动核心逻辑过 /code-review;修完编译 + 模块测试 + 图谱相关 E2E(e2e-graph.py)重跑全绿;双仓不提交。 **产物**:源码改动 + Bruno 文档同步 + 测试/E2E 结果 + Answer。 ## Answer **N1 落地**:`GraphNodeDTO` 补 `isInternal` / `giftRemark` 两字段 + `toNode()` 自真源 `CustomerContact` 填充;`图谱全量读.bru` nodes[]. 结构表补 2 行;demo `applyGraph` 节点映射与 `cardBubbleOver` 三行切真值(人脉行恒 "--" 注记)。图谱真值采集用轻夹具(建客户 + quickAdd 联系人 + DB 临时改值 → 采集 → 还原 → 正门清扫),graph/detail 实测回显 `isInternal:1` + `giftRemark:"e2c-r3d-n3-礼品-龙井茶两盒"`(specimens-proj-truth.json)。 **N2 落地**:`CustomerConstants` 加 `ACTION_GRAPH_EDIT`;`CustomerContactGraphServiceImpl.save` 落一条叙事 oplog(操作人/动作/客户);`OplogPageParam.action` 值域与 `操作日志分页.bru` 同步为 18 种白名单(与 ACTION_* 全集一致)。 **N3 落地(票面两案取 port 路)**:弃「/api/project/list 加 customerId 过滤」(跨模块 Controller 耦合),选对称商机 ADR-0029 范式—— - `crm-customer/port/CustomerProjectQueryPort`(接口 + `ProjectItem` record 全 @Schema)+ `port/impl/NoopCustomerProjectQueryPortImpl`(兜底空页 total=0 显 "0";port 异常才由 countProjectsQuietly 降级显 "--",CodeReview P3-2 厘清的口径) - `crm-project/port/inbound/impl/CustomerProjectQueryPortImpl`(@Primary 真实现:customerId 等值分页、`orderByDesc(updateTime).orderByDesc(id)` 稳定兜底(P3-1)、阶段名回退链「别名→字典名→枚举名」批量回显、status/filingStatus 枚举 label) - `CustomerDetailServiceImpl.detailHead.projectCount` 切真值(size=1 probe 取 total,异常降级 null,对称 countOpportunitiesQuietly)+ `pageProjects`;`CustomerDetailController` 新端点 `GET /api/customer/project/page`(tag A4 客户管理/客户详情/关联项目) - 文档仓:`关联项目页签.bru` 新建(seq:7、9 字段结构表、67002、真值示例);`详情公共头部.bru` projectCount 行改真值口径 + 响应示例三处漂移刷新 **回归证据**: - 编译 EXIT=0;package 经测试适配后 EXIT=0——4 个集成测试构造器插参(Members/Follow 加 NoopProjectPort 静态内部类、Follow `projectCount` 断言 isNull→isEqualTo(0L) 契约同步、Graph/Enhance 加 `Mockito.mock(CustomerOplogWriter)`) - e2e-graph.py 重跑 **58 checks 全绿**(0 fail 0 warn,端点 17/17,teardown 清扫) - 模块测试复跑 **265 tests / 0 fail / 0 error / 0 skip**(crm-customer + crm-project 31 类)。首跑 TEST_EXIT=1 为预存环境坑非本票回归:本地仓库 crm-dict / crm-opportunity / crm-rule snapshot 过期(`DictItemDTO.setLevel` / `SchemeCardOwnerType` / `ProjectRuleQueryPort` NoClassDefFoundError),`mvn -pl crm-dict install` + `mvn -pl crm-opportunity,crm-rule -am install -DskipTests` 刷新后全绿(本地仓 snapshot 过期先例再次应验) - CodeReview 子代理:**无 P1/P2**,2×P3 全采纳(分页 id 兜底排序、Noop javadoc 降级契约措辞);「兑底→兜底」Unicode 错字修复 - 双仓均未提交(纪律):后端本票 7 改 + 3 新 + 4 测试 + demo index.html;文档仓 3 改 + 1 新目录,另累积 a4-doc-fields 票 03 的 7 个 @Schema java 改动与前几轮 .bru 写回——全部待用户审查 产物:`specimens-proj-truth.json`(4 条真值)、日志 `compile-n3.log` / `package-n3.log` / `e2e-graph-n3.log` / `test-n3-r3.log`(均在 `.scratch/customer-integration-ready/`)。