diff --git a/.scratch/opportunity-module/issues/11-customerid-in-create-cmd.md b/.scratch/opportunity-module/issues/11-customerid-in-create-cmd.md index 231c599..7e3d28f 100644 --- a/.scratch/opportunity-module/issues/11-customerid-in-create-cmd.md +++ b/.scratch/opportunity-module/issues/11-customerid-in-create-cmd.md @@ -1,7 +1,7 @@ # 11 CreateOpportunityCmd 新增 customerId + 线索侧意向客户下拉 Type: task -Status: open +Status: resolved Blocked by: 02 ## Question diff --git a/.scratch/opportunity-module/issues/12-related-lead-validation.md b/.scratch/opportunity-module/issues/12-related-lead-validation.md index f007eb8..6472e99 100644 --- a/.scratch/opportunity-module/issues/12-related-lead-validation.md +++ b/.scratch/opportunity-module/issues/12-related-lead-validation.md @@ -1,7 +1,7 @@ # 12 商机侧新增入口:关联线索校验 Type: task -Status: open +Status: resolved Blocked by: 03 ## Question @@ -27,6 +27,41 @@ Blocked by: 03 2. 命中已占用时:`{code: "opp.related_lead.taken", message: "该线索已关联其他商机"}` 之类的明确错误码。 3. 两条入口共用建商机核心逻辑(票 02 定)不变:核心逻辑不关心 `source_lead_id` 从哪来,只负责 insert + 幂等;候选/校验/联动在入口层做。 +## ❌ 后端待做(本轮补,拆于 20260826) + +**前误纠正**:map 曾将本票标“已落”——实为误判:当时只验了转商机 port(`OpportunityCreationPortImpl`) +路径上的 `source_lead_id` 预检 + 主表 uk,但本票要求的**商机侧直接新建商机入口(A3-1-1-2-1) +后端一行未写** —— 商机侧无任何新建 Controller/Service。产品拍板 A:要现在补。 + +### 已备资产(可复用) + +- 主表 `source_lead_id` 注释已明写“**两条入口都可写**、NULL 不参与唯一=直接创建可多条” + `@Index(unique=true)`(已核实)。 +- `opp_source_01`(线索转入)已在 crm-dict `opp_source` 分组。 +- Lead 状态常量 `STATUS_CLAIMED=3` / `STATUS_FOLLOWING=4`(`crm-lead/constant/LeadConstants`)。 +- 依赖方向 `crm-opportunity → crm-lead` 已存在(pom 已核)。 + +### 架构拍板(甲,20260826):候选线索查询放 crm-lead + +- 在 `ILeadService` 新增入站查询方法 `listConvertibleLeads(userId)`:返回该用户 + `status IN (3,4)` 的线索轻量 DTO(id/leadName/phone)——“哪些线索可转商机”的准入规则留在线索域(对齐 `LeadServiceImpl` 转商机准入)。 +- “**未被商机占用**”过滤由**商机侧**补(它本就管 opp 表):拿到候选后剔除 `source_lead_id` 已在 opportunity 表占用的。 + +## 落地清单(后端实现) + +1. **crm-lead**:`ILeadService.listConvertibleLeads(Long userId)` + Impl(`status IN (3,4)` + owner 过滤),返 `ConvertibleLeadView`(id/leadName/phone)。 +2. **crm-opportunity 新建 API**: + - `CreateOpportunityRequest` DTO:`oppSource`(必填)/`sourceLeadId`(可空)/ 商机名/行业/省市/客户 id 等表单字段。 + - `POST /api/opportunity` Controller + `IOpportunityCreateService` + Impl。 + - **校验**:`oppSource==opp_source_01`(线索转入)→ `sourceLeadId` 必填;否则可空。 + - **唯一性预检**:sourceLeadId 非空时查 opp 表已占用 → 报“该线索已关联其他商机”;DB uk 作并发最终防线。 + - **候选线索下拉接口** `GET /api/opportunity/convertible-leads`:调 `listConvertibleLeads` 后剔除已被 opp 占用。 +3. **复用建商机核心逻辑**(票 02/16):初始状态/阶段落位/初始日志/团队成员 与转商机 port 一致; + 直接创建时 `oppSource!=线索转入` 则 sourceLeadId=null(不写来源快照)。 + +## ❌ 前端(本仓库外) + +- 表单【关联线索】下拉交互、客户下拉。后端接口(convertible-leads)就绪后前端接入。 + ## ⚠ 依赖 - 依赖票 03(主表结构,已 resolved)。 diff --git a/.scratch/opportunity-module/map.md b/.scratch/opportunity-module/map.md index f146467..1488bba 100644 --- a/.scratch/opportunity-module/map.md +++ b/.scratch/opportunity-module/map.md @@ -27,14 +27,18 @@ | 08 | 现场勘察 | ✅ 已落(`OpportunitySiteSurvey` + Mapper,结构) | | 09 | 子表族(跟进/附件/工作计划/操作日志) | ✅ 已落(四实体 + Mapper + 3 枚举,结构) | | 10 | 四视图查询口径 | ✅ 已落(底座三表 + ViewType/PageParam/DTO + ViewQuery 六视图 filter + 分页 + 计算列节点停留/最近跟进/方案卡状态/预算 + RECENT view_log 排序 + followed + Controller `/api/opportunity/page`;**读侧回显已补全**:字典名 opp_source/industry(industry 两级树一级 code,票 09)+ 部门名 owner_dept_id + 区划名 province/city + 当前/下一节点名(依 crm-rule IOpportunityStageTemplateService,customNodeName 优先/opp_stage 字典名兜底);132 全绿) | -| 11 | createCmd customerId | ✅ 已落(走法甲:customerId 可空占位,待 A4 下拉就绪收紧必填) | -| 12 | 商机侧新增入口关联线索校验 | ✅ 已落(source_lead_id 唯一性预检 + DB uk 并发防线,零 A4 依赖;原标「卡 A4」为误判已纠) | +| 11 | createCmd customerId | ✅ 后端已全落(ConvertOpportunityParam.customerId → CreateOpportunityCmd.customerId → port 消费建子表;走法甲可空占位,待 A4 下拉就绪收紧必填;仅剩前端下拉交互) | +| 12 | 商机侧新增入口关联线索校验 | ✅ 后端已全落(票 70e2e35:商机侧独立新建入口 A3-1-1-2-1 = CreateController+Service,oppSource=线索转入则 sourceLeadId 必填 + 唯一性预检 + 候选线索下拉;仅剩前端下拉交互。前版误标“已落”实为只验转商机 port 路径,已纠) | | 13 | 商机-客户关联子表 | ✅ 已落(实体 `OpportunityCustomer`+约束 + 转商机时建 `is_primary_intended=1` 主要意向客户子表 + 刷主表冗余;仅「下拉选客户」数据源卡 A4) | | 14 | 自定义视图 + 内置视图族 | ✅ 已落(底座三表 team/focus/view_log + `TeamMemberPermission` 枚举;**内置六视图**在 crm-opportunity 枚举/ViewQuery 硬编码;**自定义视图 = crm-preference 平台能力**:`UserSavedView` 实体 + Mapper + `SavedView`/`SavedViewCondition` DTO + `SavedViewService` list/save/delete/setDefault + Impl(默认单值互斥) + Controller `/api/preference/view` + 16 单测;**商机侧接线已完成**:`SavedViewOperator` 六操作符枚举 + `OpportunitySavedViewFilter` 把 filter_json 条件翻译成 wrapper(字段池白名单=票10可筛选列,未知字段/操作符跳过)+ PageParam.savedViewId + ViewQueryImpl 叠加;读默认视图=前端调 /view/list 取 isDefault。144 全绿) | | 15 | 全局默认阶段模板兜底 seed | ✅ 已落(crm-rule `StageTemplateInitializer`:植入 1 头 5 节点发布中默认模板,客户圈定→关系摸排→资料采集→方案卡→已转项目;对称方案卡兜底范式;零 A4/零字典改动)——见 `issues/15` | | 16 | 票 02 转商机同步事务补齐(阶段落位+初始日志+团队成员) | ✅ 已落(票 02 簇 D 同步事务 5 步已齐:阶段落位(resolveBindingVersion 首节点,无模板容忍空走法 A)+ 初始操作日志(ROW_ADD「由线索转入创建」)+ 团队成员(领取人=商机负责人 project_role_01/READ_WRITE);零 A4 依赖)——见 `issues/16`,blocked by 15 | -**前沿小结**:商机模块**结构层已全部落地**(主表 + 状态机 + 三族规则 + 六子表 + 视图数据源三表 team/focus/view_log)。剩余:① **票 10 查询层 + 票 14 视图组织/自定义视图 = 可独立开工的读侧大票(不卡 A4)** → 已全部落地 ✅;② **票 11/12/13 转商机写入口(走法甲)已落地** ✅:实现 `OpportunityCreationPortImpl`(唯一性预检 + 建商机 + customerId 非空建主要意向客户子表刷冗余);crm-opportunity 新依赖 crm-lead(**ADR-0029 技术债**:下游反向依赖上游,埋循环雷,同库单体阶段可接受);**仅剩卡 A4 的**:转商机弹窗「下拉选客户」的**数据源**(customerId 现可空占位,A4 就绪后收紧必填)。**票 02 port 消费侧已实现**(`OpportunityCreationPortImpl`),不再卡 A4;**商机全面开发不阻塞** — 详见下「customer_id 契约占位」决策(写入口按 id 入参 + 名快照 + 不做跨模块存在性校验)。**省市地区:已修**(原“省市拆分 TODO”实为命令契约丢字段 bug,CreateOpportunityCmd 已补 provinceCode+cityCode 两级,删同填占位)。 +**前沿小结**:商机模块 **16 票后端全部落地 ✅**(结构层 + 状态机 + 三族规则 + 六子表 + 视图数据源三表 + 两条建商机入口)。 +**20260826 纠偏 + 收官**:票 12 曾被误标“已落”(实为只验了转商机 port 路径的预检/uk),经核实发现**商机侧独立新建入口(A3-1-1-2-1)后端一行未写**;已补(票 70e2e35):crm-lead `listConvertibleLeads`(候选线索准入留线索域,架构甲)+ crm-opportunity `IOpportunityCreateService`/Controller(POST /api/opportunity、GET /convertible-leads,oppSource=线索转入则 sourceLeadId 必填 + 唯一性预检 + 候选剔除已占用)。至此票 11/12/13 + 15/16 均 resolved。 +**PRD 后端收官结论**:本 Java 仓库能交付的后端部分**已全部完成**;整体 PRD 上线仅差两块均在**本仓库外**:① **前端**(转商机弹窗/商机侧新建表单的【客户/关联线索】下拉交互);② **A4 客户模块**(客户下拉数据源)。后端为两块均预留好缝(customerId 可空占位、convertible-leads 接口就绪),A4/前端到位后不需改后端(顶多将 customerId 收紧必填)。 + +**早期前沿小结**(历史):商机模块**结构层已全部落地**(主表 + 状态机 + 三族规则 + 六子表 + 视图数据源三表 team/focus/view_log)。剩余:① **票 10 查询层 + 票 14 视图组织/自定义视图 = 可独立开工的读侧大票(不卡 A4)** → 已全部落地 ✅;② **票 11/12/13 转商机写入口(走法甲)已落地** ✅:实现 `OpportunityCreationPortImpl`(唯一性预检 + 建商机 + customerId 非空建主要意向客户子表刷冗余);crm-opportunity 新依赖 crm-lead(**ADR-0029 技术债**:下游反向依赖上游,埋循环雷,同库单体阶段可接受);**仅剩卡 A4 的**:转商机弹窗「下拉选客户」的**数据源**(customerId 现可空占位,A4 就绪后收紧必填)。**票 02 port 消费侧已实现**(`OpportunityCreationPortImpl`),不再卡 A4;**商机全面开发不阻塞** — 详见下「customer_id 契约占位」决策(写入口按 id 入参 + 名快照 + 不做跨模块存在性校验)。**省市地区:已修**(原“省市拆分 TODO”实为命令契约丢字段 bug,CreateOpportunityCmd 已补 provinceCode+cityCode 两级,删同填占位)。 ### 交付物清单(对齐线索地图的阅读顺序)