You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

86 lines
13 KiB

7 days ago
# 票 01 — 缺陷修复白盒验收核对清单(23 条)
`effort: opportunity-acceptance` · `ticket: 01` · `date: 20260831` · `mode: 静态白盒(不起服)`
## 结论
- **23/23 条修复锚点全部在位,0 条 ❌**(opportunity-fix D-01 ×1 + bugfix D-01~D-22 ×22,其中 D-08/18/19 为翻案关闭项,核对「非缺陷形态保持」)。
- 次要瑕疵 3 处(均不阻塞验收,见 §C):D-01 方法 javadoc 残留旧方案描述 ×2、`OpportunitySubController` 重复 import ×1、错误码 64018 两族同值冲突(既有债务)。
- 全量单测结果见 §D(mvn-test.log)。
## A. opportunity-fix D-01(重点:新建商机 500 根因修复)
| # | 核对点 | 锚点(文件#行为) | 代码现状 | 结论 |
|---|---|---|---|---|
| A1 | fail-fast 无零值兜底 | `OpportunityIntakeImpl#applyInitialStage` L135-152 | `resolveBindingVersion` 返 null → 抛 `OpportunityIntakeException`(「系统未配置发布中的商机阶段模板…」);`listNodesOfVersion` 空 → 同抛;stage 三列(stage_template_id/stage_template_version/current_stage_id)**仅在命中后赋值**,无任何 `=0` 占位残留 | ✅ |
| A2 | 60001 友好错翻译链 | `OpportunityCreateServiceImpl` L94-98(DIRECT→`BusinessErrorException(msg)`→`ResultCodeEnum.BUSINESS_ERROR`=**60001**);`OpportunityCreationPortImpl` L59-63(LEAD_CONVERT→`OpportunityCreationException`) | 两条 seam 均在位,HTTP 层不再 500 | ✅ |
| A3 | 预设模板不可删/不可停(64018) | `OpportunityRuleConstants` L59/L99(`SEED_STAGE_TPL_CODE="OPP_STAGE_TPL_01"` / `CODE_STAGE_TPL_SEED_PROTECTED=64018`);`OpportunityStageTemplateServiceImpl#disableTemplate` L338-341 / `#deleteDraft` L354-357 | 两入口均按 `templateCode` 拦截(预设模板**任何版本**不可删/停,非仅发布中版),错误码/文案在位 | ✅ |
| A4 | publish 不破坏不变式 | `OpportunityStageTemplateServiceImpl#publish` L302-329 | 旧版顶替/默认顶替均直调私有 `updateStatus`,**不走** `disableTemplate` → 编辑发布中预设模板生成新草稿再发布顶替不受保护拦截;「始终有一个发布中默认模板」不变式保持 | ✅ |
| A5 | current_stage_id 赋值链 | `buildOpportunity` L120 → `applyInitialStage` L149-151 | `stageTemplateId`/`stageTemplateVersion`/`currentStageId`(首节点) 三列完整落位;`buildOpportunity` L96-103 的空串/0 兜底仅覆盖 oppType/bidForm/localityType/partyAClear/provinceCode/cityCode/industryCode(线索转入路径防御),**与 D-01 stage 三列无关**,非兜底残留 | ✅ |
| A6 | ⚠ 文档残留 ×2 | `applyInitialStage` javadoc L128-133 仍描述已撤销的「显式回填零值占位」旧方案;`buildOpportunity` L119 行注释「无模板容忍空,走法 A」同样过时 | 方法体已是 fail-fast(策略 B),**注释与方法体矛盾**,会误导后续维护;建议票 03(Demo 修正 session)顺手修正 | ⚠ 记录 |
## B. opportunity-bugfix D-01~D-22 静态复核
裁决基线 = 票 02 `retest-verdicts.md`(19 确认 + 3 翻案);运行时终版 = 票 09(✅146/⚠2/❌0)。本表核对**代码锚点在位**。
| # | 缺陷 | 修复锚点(文件#行为) | 代码现状 | 结论 |
|---|---|---|---|---|
| D-01 | 关联客户添加 404 | `OpportunitySubController` L71-75 `POST /customer/add`;`OpportunitySubServiceImpl#addCustomer` L87-125 | 端点+必填校验(66001)+重复预检(**66011** L109-110)+isPrimary 降旧主+快照回写+ROW_ADD 日志全在位 | ✅ |
| D-02 | 候选客户搜索 404 | SubController L78-81 `POST /customer/search`;SubServiceImpl#searchCustomers L128-137 | `searchCandidatePool` 分页 + keyword + NOT IN 排除已关联(快照池降级口径) | ✅ |
| D-03 | 设主要意向 404 | SubController L84-91;SubServiceImpl#setPrimaryCustomer L141-166 | 三步事务(降旧主→置新主→`refreshPrimarySnapshot` 主表回写)+ 已是主要幂等 L155-157 + FIELD_CHANGE 日志 | ✅ |
| D-04 | 团队成员添加 404 | SubController L226-230 `POST /team/add`;SubServiceImpl L353(守卫)/addTeamMembers | 批量去重保序、全重拒 66012、未知用户 66001、快照服务端取、ROW_ADD | ✅ |
| D-05 | 团队成员移除 404 | SubController L241-246 `POST /team/delete`;SubServiceImpl#deleteTeamMember L447-465 | 负责人行拒 **66013**(L455-457)+ 软删 `deleteKey=id, deleted=1` + ROW_DELETE | ✅ |
| D-06 | 团队成员编辑 404 | SubController L233-238 `POST /team/update`;SubServiceImpl#updateTeamMember L396-443 | 直接换人(目标已在队 66012)+ 负责人行**三重锁 66013**(换人 L406 / 角色 L425 / 权限 L429,仅 duty 可改 L436) | ✅ |
| D-07 | 暂缓态可编辑 | `OpportunityActionGuard#ensureNotPaused` L44-50(**66003**);接线 grep=17 调用点 | SubServiceImpl ×14(含票 12 workplan 三写入口 L492/520/546)+ edit(DetailServiceImpl L84)+ 方案卡 save/submit(SchemeCardServiceImpl L122/228);focus/touch 不拦、close 不拦(D-08 翻案)符合裁决 | ✅ |
| D-08 | 暂缓态可关闭(翻案) | 裁决:PRD §3.2 E6「2,3→4」合法边 | **无关闭拦截代码 = 正确形态**;e2e 断言已按裁决反转(票 05/09) | ✅(非缺陷) |
| D-09 | 禁领规则未生效 | `OpportunityActionGuard#ensureClaimAllowed` L59-72(**66014**,match(ownerDeptId),allowFreeClaim null/≠1 保守禁);`OpportunityTransitionController` claim L44 / claim-batch L57-59(**全量预检**) | claim/claim-batch 接线在位;release/assign 不拦(裁决:分配是禁领替代路径);`CODE_OPP_CLAIM_FORBIDDEN=66014` 在 OpportunityConstants L25 | ✅ |
| D-10 | 阶段模板编辑发布中 50001 | `OpportunityStageTemplateServiceImpl#resolveDraft` 第三分支 L283-295 | insert 前**清三毒**(`setId(null)`+`setCreateTime(null)`+`setUpdateTime(null)` L286-288,注释标 D-10) | ✅ |
| D-11 | 方案卡模板同构 50001 | `OpportunitySchemeCardTemplateServiceImpl` L226-238 | 清三毒 L229-231(注释标 D-11) | ✅ |
| D-12 | 公海规则同构 50001 | `OpportunityPoolRuleServiceImpl` L135-148 | 清三毒 L138-140(注释标 D-12) | ✅ |
| D-13 | owner_dept_id 恒空 | `crm-opportunity/owner/` 三件套(`OwnerSnapshot` record / `OwnerSnapshotResolver` 接口 / `OpportunityOwnerSnapshotResolverImpl`,bean 名冲突防避注释在位);消费方:`OpportunityCreateServiceImpl` L87(创建落快照)/ `OpportunityCreationPortImpl` L50-52(线索转入缺失兜底)/ `OpportunityTransitionController` 五端点(claim L46 / claim-batch L61 / assign L75 / assign-batch L87 / handover L168,快照参数已删、服务端 `of()` 解析) | 快照跟人走(claim/assign=新负责人,handover=完整过户);`TransitionImpl` claim/assign/handover 均 `.set(OwnerDeptId, cmd.ownerDeptId())` | ✅ |
| D-14 | 必填校验 500 类 | create:`OpportunityCreateServiceImpl` L56-64 六必填逐字段 66001(requireText L120-125);edit:`OpportunityDetailServiceImpl#edit` L75-82 `merged(dto,existing)` 合并校验 | 6 字段(名称/来源/招标形式/省/市/属地)×正负路径;edit null=保留原值、空白拒 | ✅ |
| D-15 | 公海看板 summary 未过滤 | `query/OpportunityViewFilter`(六视图 switch + `providesStatusCondition`,PUBLIC_POOL 由分支供状态条件避恒假 AND);`OpportunityCollabServiceImpl#applyBoardViewFilter` L142-148 | board 两端点共用单一事实源;viewType 可选参数默认 MINE | ✅ |
| D-16 | detail 六联动字段 null | `OpportunityDetailServiceImpl` fillEchoFields:`followed` L114 / `lastFollowTime` L125 / `schemeCardStatus`+`schemeBudget` L140-144(快照优先+存量兜底实时查)/ `stageStayDays` L159 / `nextStageName` L165 | 6 字段全落位(票 02 收窄 4 + 票 09 补 2,与 page 同源口径) | ✅ |
| D-17 | 方案卡提交后主表未回显 | `OpportunitySchemeCardServiceImpl` L265-271 | 事务内 LambdaUpdateWrapper 显式 set 两列(`SchemeCardStatus=SUBMITTED` + `SchemeBudget`),规避乐观锁实体 updateById 坑 | ✅ |
| D-18 | surveySeq 调用方传(翻案) | 裁决:原型「*勘察次数」必填下拉 | `addSiteSurvey` L154 `surveySeq` 保持 @RequestParam 必填 = 正确形态 | ✅(非缺陷) |
| D-19 | 勘察说明非必填(翻案) | 裁决:原型无星号 | L156 `surveyDesc required=false` 保持 = 正确形态 | ✅(非缺陷) |
| D-20 | oplog 流转不留痕 | `OpportunityTransitionImpl#recordStatusFlowOplog` L332-349(FIELD_CHANGE + `OplogLogType.STATUS_FLOW` + fieldName=opp_status + 旧/新值 + label 文案 + MANUAL/SYSTEM 归因) | grep 调用点 ×10 = E1 领取 L134 / E2 分配 L154 / E3 抛池 L103 / E4 暂缓 L197 / E5 恢复 L221 / E6 关闭 L238 / E7 重开 L252 / E8 转项目 L267 / E9 自环(旧=新) L292 / E10 回收 L177,流转边全覆盖 | ✅ |
| D-21 | bizType 契约矛盾 | `OpportunitySubController` L53-58 `ATTACHMENT_BIZ_TYPES`(Set.of 四值封闭)+ L194-199 判空校验 66001 | 四值 OPP/FOLLOW_UP/SITE_SURVEY/SCHEME_CARD 与 `OpportunityConstants` 同源;`Set.of contains(null) NPE 先判空`教训注释在位 | ✅ |
| D-22 | versions 实体直出泄露 | 三族 Controller `versions`:Stage L86-95 / SchemeCard L92-102 / PoolRule L85-94 | 出参全部 DTO 化(`fromEntity` 白名单,剥 creatorId/updaterId/deleted),三个 `OpportunityXxxVersionDTO` 新文件在位,注释标 D-22/ADR-0017 | ✅ |
| D-13+票12 | 工作计划四端点 | `OpportunitySubController` L261-288(list/add/update/delete);`OpportunitySubServiceImpl` L470-553 | list(deleteKey=0, createTime 升序)/ add(oppId+planContent 必填≤1000)/ update(planStatus 值域 0/1 封闭 66001、部分更新显式 set、finishTime 0→1 回填 1→0 清空)/ delete(软删+ROW_DELETE);三写入口全接 `requireOpp`(66002)+`ensureNotPaused`(66003);DTO `deadline``@DateTimeFormat` | ✅ |
## C. 次要瑕疵与工作区观察(不阻塞验收)
1. **⚠ D-01 方法 javadoc 残留旧方案**(§A6)——行为正确、注释过时,建议下次触碰该文件时顺手修正(也可留待打字窗口,本图不为此开票)。
2. **`OpportunitySubController` L36-37 `import java.util.Set;` 重复两次**——javac 合法(无错),纯整洁性问题。
3. **⚠ 错误码 64018 两族同值**:`CODE_STAGE_TPL_SEED_PROTECTED`(阶段模板,L99)与 `CODE_SCHEME_TPL_INVALID`(方案卡模板,L139)均为 64018——D-01 插入 64018 时方案卡族区间注释未顺延。既有债务,排查日志时两族错误码需按接口路径区分,不在本图修。
4. **工作区状态盘点**(git status,20260831):
- **票 12 增量在位且完整**:untracked `OpportunityWorkPlanAddDTO/UpdateDTO` 2 文件 + `OpportunitySubController`/`OpportunitySubServiceImpl`/`IOpportunitySubService` 的 `MM` 二次未暂存修改(workplan 节)+ `OpportunitySubServiceImplTest`(30 用例,AM)。**待打包增量** = 工作区 vs HEAD(938aed9…32ac292 已含 opportunity-fix + bugfix 部分 commit;bugfix 主体为已暂存未 commit)。
- **并行 effort 混入提示**:`crm-auth` 的 `ResourceServiceImpl(+Test)` 存在 auth-perms effort 的未暂存改动——**本图票 02 起服打包(clean package)会把这批改动一并带进 fat jar**;crm-auth 域不受商机验收影响但需知悉。
- `logs/crm-app.log` 存在 `DU`(unmerged)状态残留——log 文件本身已被 32ac292 停止跟踪,不影响构建,但首次 commit 时需留意。
5. **既有红灯豁免项**:crm-auth `ResourceServiceImplTest.save_editCatalog_noButtonFields`(map Notes 豁免口径)——auth-perms 票 02 正在处理中,本轮 mvn test 实测结果见 §D。
## D. 全量单测(mvn test,10 模块 reactor)
**20260831 实测:BUILD SUCCESS,`MVN-TEST-EXIT=0`,合计 873/0/0(Failures/Errors/Skipped),Total 01:26 min。** 日志:`mvn-test.log`。
| 模块 | Tests | F/E/S |
|---|---|---|
| crm-base | 38 | 0/0/0 |
| crm-file | 84 | 0/0/0 |
| crm-auth | 210 | 0/0/0 |
| crm-dict | 51 | 0/0/0 |
| crm-rule | 134 | 0/0/0 |
| crm-preference | 16 | 0/0/0 |
| crm-lead | 131 | 0/0/0 |
| **crm-opportunity** | **209** | **0/0/0**(与票 12 Answer 209/0/0 一致) |
| crm-app | — | (无测试段) |
- **豁免项失效(好消息)**:map Notes 的既有红灯 `ResourceServiceImplTest.save_editCatalog_noButtonFields` 本轮**未复现**——crm-auth 210/0/0。原因:该用例已被 auth-perms effort 票 02(perms-hardening,工作区未暂存改动)改写为「编辑 catalog 时 perms 只读保留 DB 原值」语义(Test L434-452),红灯随语义改写消失。商机域验收不受影响,豁免口径自动解除。
- 工具链:mvn=`D:\apache-maven-3.9.9\bin\mvn.cmd`(非 bugfix 票面写的 IDEA 捆绑路径,已失效),JDK17=`C:\Users\luowj\.jdks\jdk-17.0.15`。
## 变更追溯
- 复核输入:`.scratch/opportunity-fix/map.md`(最终定案节)、`.scratch/opportunity-bugfix/issues/02/03/04/05/06/09/12`、`retest-verdicts.md`
- 复核方式:纯静态(Read/Grep 源码锚点比对),不起服;运行时行为验证归票 02(全量 E2E 重跑)