14 KiB
商机模块(opportunity-module)接口验收报告(重写版)
本文替代旧版
verify-report.WRONG.bak.md。 旧版核心结论大面积错误,重写说明见文末「附录:为何重写」。
- 验收方式:原型驱动 + 黑盒实测。后端
localhost:8080(远程 DB8.129.84.155),调试用户userId=739564171091247104。- 实测脚本:
e2e_test.py(29 条主链)+supplement_test.py(49 条补测)+ 手工 curl(状态机 9 步完整流转、批量流转成功路径)。- 路由发现:一次性读取 4 个商机 Controller + 5 个规则/字典/偏好 Controller 的
@*Mapping(仅用于发现待测路由,验收结论一律以 HTTP 实测为准)。- 判定图例:✅ 存在且业务可用|⚠ 存在但有契约差异/边界问题|❌ 真实缺失或真实 bug。
一、总体结论
后端接口整体已实现,可用率极高。全量实测未发现任何真正的 404(路由缺失)。 报告范围内 85 个后端路由全部存在并正确响应。
唯一的致命缺陷是新建商机接口必然 500。其余问题均为「前端对接需注意的契约差异」,非缺失。
| 维度 | 结论 |
|---|---|
| 路由缺失(真 404) | 0 个 |
| 致命 bug | 1 个:POST /api/opportunity 新建必 500 |
| 契约差异(需前端适配) | 6 类(分页字段、参数名、路径风格、字典编码、视图路径、region 参数) |
| 状态机 9 条流转 | 全部实测通过(单条 + 批量成功路径均验证) |
二、致命缺陷(❌ 必须修)
D-01 ❌ 新建商机 POST /api/opportunity 必然 500
- 实测:
POST /api/opportunity,bodyopportunityName=xxx&oppSource=opp_source_02→HTTP 500 code:50001 系统内部错误。多次、多参数组合稳定复现。 - 后端日志根因(
logs/crm-app.log):DataIntegrityViolationException: Field 'current_stage_id' doesn't have a default value INSERT INTO opportunity (... 无 current_stage_id ...) VALUES (...) at OpportunityIntakeImpl.open(OpportunityIntakeImpl.java:73) at OpportunityCreateServiceImpl.createOpportunity(OpportunityCreateServiceImpl.java:79) - 定性:
OpportunityIntakeImpl.open()组装 INSERT 时未给current_stage_id赋值,而 DB 该列NOT NULL且无默认值。 - 影响:新建商机 100% 失败,是商机业务链的入口——最高优先级。
- 修复方向:入库前按「适用范围命中的阶段模板」解析出初始阶段并写入
current_stage_id;若允许无阶段初始态,则给该列加默认值 / 允许 NULL。 - 注:旧报告 W-02 把字段名写成
bid_form(错误),但「新建必 500」的现象判断是对的。实际肇因列是current_stage_id。
三、状态机族(旧报告全判 ❌,实测全部 ✅)
用已存在的推进中商机(748098957468499968 等)跑通完整流转,避开 D-01 的新建 500。
| 流转 | 接口 | 单条实测 | 批量接口 | 批量实测 |
|---|---|---|---|---|
| 领取 | POST /api/opportunity/claim |
✅ 200 code:0 | POST /claim-batch |
✅ 成功路径 code:0 |
| 分配 | POST /api/opportunity/assign |
✅ 200 code:0 | POST /assign-batch |
✅ 成功路径 code:0 |
| 抛公海 | POST /api/opportunity/release-pool |
✅ 200 code:0 | — | — |
| 暂缓 | POST /api/opportunity/pause |
✅ 200 code:0 | — | — |
| 恢复 | POST /api/opportunity/resume |
✅ 200 code:0 | — | — |
| 关闭 | POST /api/opportunity/close |
✅ 200 code:0 | POST /close-batch |
✅ code:0 |
| 重开 | POST /api/opportunity/reopen |
✅ 200 code:0 | — | — |
| 移交 | POST /api/opportunity/handover |
✅ 200 code:0 | — | — |
- 完整链路实测:
pause → resume → handover → release-pool → claim → release-pool → assign → close → reopen,9 步全 200 code:0。 - 状态守卫正确:对源状态不匹配的流转(如「推进中」再 claim)返
code:66003 当前状态不允许此操作——是正确的状态机守卫,非缺失。 - 路径风格:后端为 flat 风格(
/api/opportunity/claim,id 走 body),不是 RESTful/api/opportunity/{id}/claim。旧报告只测了 RESTful 路径得到 404 → 误判「状态机全缺」。
⚠ 契约差异:状态机全部走
POST + form body(id、pauseReason、poolReason、closeReason、reopenReason、newOwnerUserId/expectedOwnerUserId、userId、ids),前端不能用 RESTful 路径拼 id。
四、列表族(旧报告部分判 ❌,实测全部 ✅)
| 页面 | 接口 | 实测 | 说明 |
|---|---|---|---|
| 销售机会列表(3 视图) | POST /api/opportunity/page(viewType 区分) |
✅ 200 code:0 | 分页字段 content/total(见 C-01) |
| 商机公海列表 | POST /api/opportunity/page?viewType=pool |
✅ 200 code:0 | 同上 |
| 看板-阶段汇总 | POST /api/opportunity/board/stage-summary |
✅ 200 code:0 | 旧报告判 M-02 ❌,误判 |
| 看板-单阶段卡片 | POST /api/opportunity/board/cards |
✅ 200 code:0 | 旧报告判 M-03 ❌,误判 |
| 最近访问-上报 | POST /api/opportunity/view-touch |
✅ 200 code:0 | 后端以 view-touch 承担「访问上报」,非 spec 的 recent-visits/touch(旧报告 M-01 误判为缺失) |
| 可转商机线索 | GET /api/opportunity/convertible-leads |
✅ 200 code:0 | 新建页关联线索来源 |
⚠ C-01 分页字段:后端为 Spring Data / MyBatis-Plus 风格
data.content+data.total(另有size/current/pages/empty),不是data.records。旧报告 W-01 判断正确。
五、详情族(旧报告全判 ❌,实测全部 ✅)
后端为 flat 风格,id/oppId 走 query 或 body,非 RESTful /{id}/xxx。
| 功能 | 接口 | 实测 |
|---|---|---|
| 商机详情 | GET /api/opportunity/detail?id= |
✅ 200 code:0,返回完整字段 |
| 编辑商机 | POST /api/opportunity/edit |
✅ 200 code:0 |
| 关联客户列表 | GET /api/opportunity/customer/list?oppId= |
✅ 200 code:0 |
| 方案卡列表 | GET /api/opportunity/scheme-card/list?oppId= |
✅ 200 code:0 |
| 阶段进度 | GET /api/opportunity/stage/progress?oppId= |
✅ 200 code:0 |
| 现场勘察分页 | POST /api/opportunity/site-survey/page |
✅ 200 code:0(content/total) |
| 相关附件列表 | GET /api/opportunity/attachment/list?oppId= |
✅ 200 code:0 |
| 团队成员列表 | GET /api/opportunity/team/list?oppId= |
✅ 200 code:0 |
| 操作日志分页 | POST /api/opportunity/oplog/page |
✅ 200 code:0(content/total) |
| 关注 / 取消关注 | POST /api/opportunity/focus、/unfocus |
✅ 200 code:0 |
⚠ C-02 参数名:详情/子表全部用
oppId(非 spec 的opportunityId)、详情主体用id。旧报告 W-03/04/05 判断正确。
5.1 详情子操作(补测)
| 功能 | 接口 | 实测 | 说明 |
|---|---|---|---|
| 写跟进 | POST /api/opportunity/follow/add |
✅ 200 code:0 | 写入后 last_valid_follow_time 正确刷新(有效跟进定义生效) |
| 跟进分页 | POST /api/opportunity/follow/page |
✅ 200 code:0 | content/total |
| 删除跟进 | POST /api/opportunity/follow/delete |
✅ 200 code:0 | 删除写 ROW_DELETE 操作日志(联动正确) |
| 新增勘察 | POST /api/opportunity/site-survey/add |
⚠ 需 engineerUserId 等必填 |
路由存在,缺参返 40001 缺少必填参数:engineerUserId(正确校验) |
| 删除勘察 | POST /api/opportunity/site-survey/delete |
✅ 路由存在 | |
| 附件新增 | POST /api/opportunity/attachment/add |
✅ 路由存在 | 10 个上限校验正确:超限返 60001 跟进附件最多 10 个 |
| 附件删除 | POST /api/opportunity/attachment/delete |
✅ 200 code:0 | |
| 方案卡保存 | POST /api/opportunity/scheme-card/save |
✅ 路由存在 | 业务守卫正确:无客户归属返 66007 关联客户不属于本商机 |
| 方案卡提交 | POST /api/opportunity/scheme-card/submit |
✅ 路由存在 | |
| 方案卡详情 | GET /api/opportunity/scheme-card/detail?id= |
✅ 路由存在 | 不存在 id 返 66008 方案卡不存在(正确) |
| 可选模板 | GET /api/opportunity/scheme-card/selectable-templates?oppId= |
✅ 200 code:0 |
六、规则族(A7-3-2,旧报告"路径变更✅",实测全部 ✅)
后端统一 /api/rule/opp-*,方法统一 POST(分页/操作)+ GET(详情/版本),非 spec RESTful /{id}/versions 等。旧报告 M-04/05/06 把 /versions 判为缺失,实测均存在。
| 子域 | 分页 | 详情 | 版本 | 复制 | 发布 | 停用 | 删除 |
|---|---|---|---|---|---|---|---|
阶段模板 opp-stage-template |
✅ | ✅ | ✅ | ✅ | ⚠ | ✅ | ⚠ |
方案卡模板 opp-scheme-template |
✅ | ✅ | ✅ | ✅ | ⚠ | ✅ | ⚠ |
公海规则 opp-pool-rule |
✅ | ✅⚠ | ✅⚠ | ⚠ | ⚠ | ⚠ | ⚠ |
- 分页:
POST /api/rule/opp-{stage-template|scheme-template|pool-rule}/page— ✅ 200 code:0,content/total。 - 详情/版本:
GET .../detail?id=、GET .../versions?id=— ✅(阶段、方案卡实测 code:0)。 - 复制:
POST .../copy?id=— ✅(阶段、方案卡 code:0)。 - 停用:
POST .../disable?id=— ✅(阶段、方案卡 code:0)。 - ⚠ 发布
publish/ 删除delete/ 存草稿save-draft:路由均存在,实测返业务校验错(非缺失):publish→模板名称/规则名称不能为空(需带完整模板体)delete→仅草稿版本可删除(对已发布版本的正确守卫)save-draft→适用范围参数非法(需合法applyScope)
- ⚠ 公海规则
detail/versions/copy/disable/delete补测未带id返40001 缺少必填参数:id——路由存在,测试用例未准备 pool-rule 种子 id,非缺失。 - 方案卡模板另有
GET .../field-defs?templateId=— ✅ 200 code:0。
旧报告在规则族的「✅路径变更」结论是对的(因为它这一段确实用了 flat 路径判定)。
七、字典 / 视图 / 地区族(补测,均 ✅)
| 功能 | 接口 | 实测 | 说明 |
|---|---|---|---|
| 字典分组分页 | POST /api/dict/group/page |
✅ 200 code:0 | |
| 字典分组启用列表 | GET /api/dict/group/enabled-list |
✅ 200 code:0 | |
| 字典项分页 | POST /api/dict/item/page |
✅ 200 code:0 | |
| 字典项启用列表 | GET /api/dict/item/enabled-list?groupCode= |
✅ 200 code:0 | 商机各枚举取值来源 |
| 列偏好读取/保存 | GET /api/preference/get、POST /api/preference/save |
✅ 200 code:0 | 自定义列 |
| 保存视图列表 | GET /api/preference/view/list?scopeKey= |
✅ 200 code:0 | |
| 保存视图-保存 | POST /api/preference/view/save |
⚠ 需 JSON body | form 提交返 40001 请求格式不支持,请使用 JSON |
| 保存视图-删除/置默认 | POST /api/preference/view/delete、/set-default |
✅ 路由存在 | 需 scopeKey 参数 |
| 地区列表 | GET /api/rule/region/list |
✅ 200 code:0 | |
| 地区分级 | GET /api/rule/region/level?level= |
⚠ 需 level 参数 |
缺参返 40001 缺少必填参数:level(正确校验) |
⚠ C-03 字典编码:商机字典 groupCode 为
opp_stage/opp_status/opp_pause_reason/opp_close_reason(opp_前缀),非 spec 里写的opportunity_stage等。前端取字典需用实际编码。 ⚠ C-04 视图保存POST /api/preference/view/save必须Content-Type: application/json,不接受 form。
八、契约差异清单(前端对接必读,非 bug)
| 编号 | 差异 | spec 预期 | 实际 |
|---|---|---|---|
| C-01 | 分页字段 | data.records |
data.content + data.total(+size/current/pages/empty) |
| C-02 | 详情/子表参数名 | opportunityId |
oppId(详情主体 id) |
| C-03 | 商机字典 groupCode | opportunity_stage … |
opp_stage / opp_status / opp_pause_reason / opp_close_reason |
| C-04 | 视图保存请求体 | form | 必须 JSON |
| C-05 | 状态机/规则族路径风格 | RESTful /{id}/xxx |
flat:id 走 body/query,/api/opportunity/claim、/api/rule/opp-* |
| C-06 | 地区分级 | 无参 | 必传 level |
九、范围外(未测/未报,遵循锁定范围)
一键拉群 / 推送方案(A3-1-1-2-5 + A7-3-2-3)/ 督办 / 转项目(A3-1-1-2-10 + ConvertToProjectCmd)/ 工作计划联动(A3-1-1-1-7)/ 项目查重规则(A7-3-2-4)。
附录:为何重写(旧报告 verify-report.WRONG.bak.md 的问题)
- 根本错误——测错了路径:旧报告按
spec-endpoints.md推断的 RESTful 路径(GET /{id}、POST /{id}/claim、GET /{id}/versions…)去打,后端实际是 flat 风格,于是几乎每条都得到 404,被误判为「接口未实现」。共约 39 条 ❌ 属误判(状态机 13、详情 5、跟进 3、勘察 2、附件 4、团队 2、日志 1、协作 3、看板 3、规则 versions 3 等)。 - 未参考同目录实测产物:
e2e_test.py/03-list.html就在同一目录、用的是正确的 flat 路径,但旧报告完全没引用。 - W-02 字段名错误:新建 500 的肇因列是
current_stage_id而非bid_form(现象判断对,归因错)。 - e2e_test.py 自身的"37/37 OK"也不可信:其状态机段依赖新建返回的 id,而新建必 500 → id 为 None → 8 条状态机被 SKIP(重跑实为 OK=28 / FAIL=1 / SKIP=8)。本报告改用已存在商机实测状态机,才拿到真实的全通过证据。
本报告的证据基础:e2e_test.py(重跑)+ supplement_test.py(49 条补测)+ 手工 curl 完整状态机链 + 后端日志根因定位 + Controller 路由清单交叉核对。