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.
 
 
 
 
 

7.1 KiB

04: P1 修复 B — 补齐 6 个 404 写端点(D-01~06:关联客户 ×3 + 团队成员 ×3)

Type: task Status: resolved Blocked by: 02

任务

原型有按钮、实体已存在、只缺 Controller/Service 写端点的 6 个接口:

# 缺陷 端点(建议,flat 风格) 原型依据
D-01 关联客户-添加 POST /api/opportunity/customer/add a3-1-1-1-2-4 添加关联客户弹窗 + 详情「添加关联客户」按钮
D-02 关联客户-搜索 POST /api/opportunity/customer/search(分页搜客户池候选) 同弹窗内搜索框
D-03 关联客户-设主要意向 POST /api/opportunity/customer/set-primary 同弹窗/列表切主交互
D-04 团队成员-添加 POST /api/opportunity/team/add a3-1-1-1-8「+ 添加成员」
D-05 团队成员-移除 POST /api/opportunity/team/delete 同页「移除」
D-06 团队成员-编辑 POST /api/opportunity/team/update 同页「编辑」

实现要点(不是裸 CRUD,域规则在 CONTEXT.md)

关联客户crm-opportunity/CONTEXT.md 规则):

  • 一商机多客户;有且仅一个主要意向客户(add 时若该商机已无主要意向且新客户设为主要则直接生效;已有主要时切主走 set-primary)。
  • set-primary = 三步事务:旧主要置 0 → 新主要置 1 → 商机主表 primaryCustomerNameSnapshot 回写。
  • 无删除动作(原型无删除按钮,别加 delete 端点)。
  • search 是「候选客户池」搜索(从 crm 客户域搜,过滤已关联的),返回分页。

团队成员(规则):

  • 行级授权白名单:add 把用户加白名单(projectRole/permission 按 DTO);delete 走软删(deleteKey 约定——看 OpportunityTeam 实体既有字段怎么设计的,与既有 delete_key 复用键约定一致)。
  • 商机负责人必须在团队里:delete 时若目标是当前负责人要拦截(错误码风格对齐既有 64xxx/66xxx 家族)。
  • 移交商机(handover)时 team 表同步维护的逻辑已存在,别动。

通用

  • Controller IO 遵 ADR-0017(Param DTO 进、Result 出);权限注解对齐既有写端点(看 page/claim 等怎么标的)。
  • 输入校验失败返回 400 类业务码(不是 500)。
  • Bruno 集合 E:\code\crm-api-docs 里这 6 个端点此前不存在,票 10 会补文档;本票只管实现 + 单测。

验收

  • 6 端点实测全通:add → list 回显 → update 改角色 → delete 软删 → set-primary 切主(旧主让位 + 快照回写)→ search 分页过滤已关联。
  • 负路径:重复添加同一客户拦截;删除负责人拦截;无主要意向时 add 直设主要。
  • mvn compile + 新增单测绿;e2e-core.py 相关段回归无回归。

Answer

(20260829 完成)6 端点全部落地,验收 5/5 达成。

实现总览

  • 端点OpportunitySubController 追加,flat 路径、隐式表单绑定,operatorId 取 SecurityUtils):
    • POST /customer/add(D-01):必填校验(66001)→ 重复预检(66011)→ isPrimary=1 时降旧主 → insert(deleteKey=0)→ 主表 primaryCustomerId/primaryCustomerNameSnapshot 回写。无 delete 端点(原型无删除按钮)。
    • POST /customer/search(D-02):候选池按拍板「快照池降级」(20260829)——客户模块 A4 未建,OpportunityCustomerMapper.searchCandidatePool 取 opportunity_customer 存活行按 customer_id 去重(MAX(customer_name_snapshot),兼容 ONLY_FULL_GROUP_BY),keyword 模糊 + NOT IN 排除当前商机已关联 + 分页;A4 建成切真源,签名不变。
    • POST /customer/set-primary(D-03):2 参 @RequestParam;目标须已关联(66001)、已是主要幂等返回;三步事务:旧主降 0 → 新主置 1 → 主表快照回写,FIELD_CHANGE 日志。
    • POST /team/add(D-04):批量 userIds 去重保序;全重整体拒 66012「该成员已存在」、部分重复跳过仅插新;username 快照服务端取 crm_auth_user(未知用户 66001);permission 不传默认 READ_ONLY(v29 页面无权限编辑入口,后端语义按 grill 拍板保留)。
    • POST /team/update(D-06):支持直接换人(v29 A3-1-1-1-8;目标已在队 66012,快照服务端刷新,无需先加后删);负责人行三重锁 66013(换人/改项目角色/permission 禁改,仅 duty 可改——「请走移交 E9」)。
    • POST /team/delete(D-05):软删 set(DeleteKey, id).set(Deleted, 1)(复用键约定,同成员可重新加入);负责人行拒 66013。
  • 新增错误码:66011 客户重复关联 / 66012 团队成员已存在 / 66013 负责人行锁定(OpportunityConstants,66xxx 家族)。
  • 新增 duty 列:OpportunityTeam+duty varchar(200)(v29 团队页列 5「职责」,非必填文本),ddl-auto: update 自动建列。
  • 操作日志:ROW_ADD/ROW_DELETE/FIELD_CHANGE + OplogLogType.ROW_CHANGE,opSource=MANUAL,bizRef 截 50 字。
  • ADR-0017:4 DTO + 1 Param(extends BaseDTO/BaseParam,toEntity 写在 DTO);无主键语义出参 OpportunityCustomerCandidateDTO 不继承 BaseDTO;无权限注解(sys_menu 无 opportunity 权限点,动态拦截器口径对齐既有子域端点)。

验收对照

验收项 结果
6 端点实测全通(add→list 回显→update→delete→set-primary→search) t04-verify.py 27/27 全绿(客户域 10 + 团队域 16 + CLEANUP)
负路径:重复添加拦截 66011(客户)/ 66012(成员:全重拒 + 换人目标已在队)
负路径:负责人行拦截 66013 三重(删/换人/改角色),仅 duty 可改
负路径:无主要时 add 直设主要 C4:旧主 5002→0、新主 5003→1、主表快照=(5003,客户丙)
mvn compile EXIT=0
单测 新增 OpportunitySubServiceImplTest 18/18;crm-opportunity 全模块 183/183(顺带清掉票 03 遗留 4 处失败——938aed9 把「无模板」改 fail-fast 未同步测试,测试侧对齐)
e2e-core 相关段回归 F11/F12 11/11 缺陷 0——6 端点探测全从「404 无端点」翻绿;报告另存 *-t04-regression.*,原始报告已恢复

工程记录

  • BeanUtils 按名复制坑:DTO oppId vs 实体 opportunityId 名字不同,copyProperties 静默不复制 → INSERT 缺 opportunity_id 报 50001。修法:toEntity() 内显式 entity.setOpportunityId(getOppId())(团队域没踩到因手动 set)。BeanUtils 型 toEntity 均需逐一核对字段名。
  • Maven 增量构建静默跳过:package 日志 BUILD SUCCESS 但 jar:jar 因「输入未变」跳过重建,fat jar 落后源码(api-docs 无新端点报 40401)。判别法:解剖 fat jar 的 BOOT-INF/lib 内嵌模块 jar 查字节码字符串常量(setOpportunityId 在 = 修复在内)。
  • 沙箱拦截杀进程:taskkill/Stop-Process 在沙箱内静默失效(输出通道死、进程存活);Bash 带 required_permissions: all 出沙箱后 taskkill /F /T 直接成功。
  • 脚本沉淀.scratch/opportunity-bugfix/t04-verify.py(C/T/CLEANUP 三段,Sess 模式复用 t03)、t04-jarcheck2.py(jar 内容判别)。