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.

190 lines
40 KiB

3 weeks ago
# 商机业务(crm-opportunity)
销售机会的全生命周期业务域:商机实体、两条正交轴(归属流转**状态** + 业务**阶段**)、四视图(商机公海 / 销售机会(我负责的)/ 我的关注 / 商机管理)、领取 / 分配 / 暂缓 / 抛公海 / 关闭 / 移交 / 转项目等流转。与线索同构,依赖方向单向 `crm-opportunity → crm-rule → (crm-auth / crm-dict / crm-file)`;商机规则(阶段模板 / 方案卡模板 / 方案推送 / 项目查重 / 商机公海与提醒 / 商机字典,原型 A7-3-2)归 `crm-rule`,本模块只消费。商机由**线索转入**或**销售直接创建**两条入口进入,进系统后完全平权(不按来源切片状态机/公海/视图);两条入口的建档机制收敛于深模块**商机建档**(OpportunityIntake,ADR-0030)。
3 weeks ago
> 本文档只钉词汇与边界(票 01);状态机迁移图、表结构、公海规则、四视图查询各留给后续票(04/03/05/10)。事实出处见 `.scratch/opportunity-module/research-close-pause-actions.md`(原型活数据 V1.0-20260815)。
## Language
**商机**:
一条正在推进、准备转成项目的销售机会记录,`crm-opportunity` 的核心聚合根。两条正交轴:归属流转 `oppStatus`(**状态**)+ 业务打法 `oppStage`(**阶段**)。归属由 `owner_user_id`(领取人)表达。全生命周期最终走向「已转项目」或被「关闭」。字段/类名统一 `opp` 前缀,模块名与表名全拼 `crm-opportunity` / `opportunity_*`
_Avoid_: opportunity(英文单说)、线索(线索是上游实体)、项目(项目是转化后的下游实体)、客户
**状态(oppStatus)**:
3 weeks ago
商机的**归属流转轴**,`OpportunityStatus`(`opp_status` tinyint)。5 态:`1 待领取(在公海)` / `2 推进中`(默认活跃态) / `3 暂缓中` / `4 已关闭`(半终态,可重启) / `5 已转项目`(绝对终态,无出边)。与业务阶段是两条独立的线——状态管「归属处境」,不管「业务打法走到第几步」。迁移全图见票 04:领取/分配→2、抛公海 2→1、暂缓 2⇄3、关闭 2/3→4、重启 4→2、转项目 2→5、移交(换 owner 不改 status)。CAS 乐观锁 + 无 owner guard(可见即可点,票 01)。
_Avoid_: 阶段(阶段是另一条轴)、进度、stage、中止(不存在)
3 weeks ago
**阶段(oppStage)**:
2 weeks ago
商机的**业务阶段轴**,`OpportunityStage`。表达按公司标准打法走到第几步:客户圈定 → 关系摸排 → 资料采集 → 方案卡 → 已转项目(原型详情页进度条 A3-1-1-1-1)。阶段序列由 `crm-rule` 的**商机阶段模板**配置(原型 A7-3-2-1,票 06)。商机绑定模板时锁死具体版本(`stage_template_id`=版本行 id),发新版不自动迁移。除「已转项目」外其余阶段**可自由切换(连跳/回退)**,每次写 `opportunity_stage_history` 快照。「已转项目」为固定末节点(`is_fixed`,不可删、不可手动点),仅由**推送方案卡成功(系统自动创建项目)**触发,同时驱动轴1 已转项目终态(联动;grill 补拍 20260821,替代票 06「任一方案卡转项目」口径)。与状态正交。
3 weeks ago
_Avoid_: 状态(状态是另一条轴)、节点(口语可叫「节点」,字段用 stage)、status
**领取人**:
当前持有该商机的销售,存于 `owner_user_id`,= 归属 + 可见性两层含义(既是「谁负责」,也是默认「谁能看到」)。抛公海时清空。是「销售机会(我负责的)」视图的第一层业务过滤依据。直接创建商机时默认领取人 = 当前用户(原型 A3-1-1-2-1)。
_Avoid_: 负责人(负责人是公海池概念)、持有者、owner(英文单说)
**创建人**:
2 weeks ago
录入该商机的用户,全生命周期不变。直接创建 / 线索转入均落创建人。**商机无删除动作**(grill 补拍 20260821)——终法只有关闭/转项目,历史数据永不删,故无删除权限判定问题。
3 weeks ago
_Avoid_: 录入人、owner(owner 是领取人)
**团队成员**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
领取人拉入协作的人员,存于独立关联(原型「添加团队成员&一键拉群」A3-1-1-2-8、团队成员 Tab A3-1-1-1-8)。每人配**项目角色**(商机负责人 / 方案工程师 / 招标专员 / 现场工程师 等,**纯回显标签、不带任何读写权限**,grill 拍板 20260821)与**权限**(读写 / 只读 / 可写跟进 / 可推进节点 等,**与角色无关、单独配**,原型「改角色」「改权限」两个独立入口)。团队成员是**行级授权白名单**——绕开 DataScope 部门天花板给出可见性,但能否写取决于其自身权限点。**生效边界(grill 补拍 20260821)**:详情/子表读 + 个人工作区视图(我参与的/recent);管理/公海工作区严格按 DataScope 不混白名单;详情可见 = DataScope OR 白名单命中(仅存活行 `deleted=0`;移除成员=软删 team 行,所有成员身份判定带 `deleted=0`,grill 拍板 20260821)。**抛公海只清空领取人,不动团队成员**(产品明确)。**转商机时自动插一条团队成员:领取人 = 商机负责人(票 02 拍板)**。
3 weeks ago
_Avoid_: 协作人、参与者、观察者(团队成员带角色与权限,不只是「能看」)
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
**子表软删复用键**:
商机带唯一约束子表的删除通则(grill 拍板 20260821,对齐 `crm-rule` `LeadPool` 既有实践):带业务字段/需审计的子表(`opportunity_customer`/`opportunity_scheme_card`/`opportunity_team`)走**软删除 + 唯一索引复用键 `delete_key`**——存活 `delete_key=0`,逻辑删除时 `delete_key=id`,唯一索引挂 `(业务键列, delete_key)` 而非裸列(避免逻辑删除行占唯一位、同键重新添加报冲突)。纯行为/收藏表(`opportunity_focus`/`opportunity_view_log`)无业务字段走**物理硬删**,唯一索引裸列即可。
_Avoid_: 裸列唯一索引+逻辑删除(会因旧删除行占位报重)、关联表一律硬删(带审计的子表软删保留历史);focus/view_log 不软删(纯行为流水,硬删 + logback INFO 排查,不入 oplog;grill 20260821 选项 C)
3 weeks ago
**商机来源**:
渠道归因字段 `oppSource`(字典码,必填,原型 A3-1-1-2-1)。表达商机从哪个渠道来(展会 / 转介绍 / 官网 等)。**与「来源线索」是两回事**——来源是渠道分类,每条商机都有;来源线索是血缘。**从线索转来的商机,`oppSource` 默认置为「线索转入」**(产品明确);直接创建的由用户选。
_Avoid_: 来源线索、渠道(口语,字段名 oppSource)、来路
**来源线索**:
3 weeks ago
线索转入的血缘键 `source_lead_id`(可空 FK)。**两条入口都可写此字段**:线索侧 port 自动带(票 02),商机侧新增入口当用户选商机来源=「线索转入」时手选一条【关联线索】(票 12,候选范围 = 线索状态已领取/跟进中,与线索侧转商机准入对称)。直接创建且商机来源非「线索转入」的为 NULL(「直接创建」= `source_lead_id IS NULL`,不单列术语)。是幂等键——`UNIQUE(source_lead_id)`(票 02)保证**一条线索只能关联一条商机**,两条入口共享同一 DB 约束。**不参与商机身份**:线索转来的与直接创建的商机在模型里完全平权。
3 weeks ago
_Avoid_: 商机来源(那是 oppSource 渠道)、线索 id、parent
**商机建档**:
两条创建入口收敛出的深模块 `com.crm.opportunity.intake.OpportunityIntake`(单方法 `open(spec)`,ADR-0030):主表落库 + 阶段落位 + 主要意向客户子表 + 初始操作日志 + 领取人团队成员五步机制 + `source_lead_id` 唯一性防护(预检 + 并发唯一键转译),一个事务内原子完成(REQUIRED 传播——线索侧并入 crm-lead 外层事务,ADR-0020 语义不变)。入参是内部统一契约 `OpportunityIntakeSpec`(不导出);失败抛自有 `OpportunityIntakeException`,**不跨 seam**:线索侧 port 译为 `OpportunityCreationException`,商机侧直接新建译为 `BusinessErrorException`。两条入口(转商机 port / 直接新建 service)自此只做「入参→spec 翻译 + 异常翻译」,机制改动只动一处。
_Avoid_: 建商机(口语)、创建商机 service(那是 adapter,不是机制)、IntakeService(类型名已定死 OpportunityIntake)
**进入方式**:
建档来源枚举 `IntakeSource`(`LEAD_CONVERT` 线索转入 / `DIRECT` 直接新建),承载两条入口按来源分叉的全部细节:初始操作日志的 `op_source`(SYSTEM/USER)与 `op_desc`(「由线索转入创建」/「直接创建商机」),以及 LEAD_CONVERT 恒定的商机来源字典码 `defaultOppSource()`=opp_source_01(修正转商机路径漏填 oppSource 的缺陷;DIRECT 为 null,由用户所选经 spec 显式携带)。
_Avoid_: 商机来源(那是 oppSource 渠道分类)、入口(口语可叫入口,类型/字段名用 IntakeSource)、渠道
3 weeks ago
**关联客户**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
商机关联的客户(原型「添加关联客户」A3-1-1-2-4、客户信息 Tab A3-1-1-1-2)。子表 = **`opportunity_customer`(票 13)**:`customer_id` 引用 A4 客户 + 名快照、`customer_role` 角色(→crm-dict)、`is_primary_intended` **是否主要意向客户**。规则(产品拍板):一商机可多个关联客户,**有且仅一个主要意向客户**;`UNIQUE(opportunity_id, customer_id)`。**两条入口都必填主要意向客户**(直接新建表单含意向客户必选,与线索转商机 `customerId` 必填对齐;grill 补拍 20260821),保证主客户约束自创建起恒成立。线索转商机时填的意向客户 = 落为主要意向客户;商机侧可加普通关联客户、可改主要意向客户(切换标记)。主表冗余 `primary_customer_id`/名快照(免 join)。**主要意向客户是列表派生列(方案预算/方案卡状态)的取值锚点**(票 07/10)。**无删除动作(grill 纠偏 20260821,原型/票 13 均无删除)**:关联客户不可单独删(仅随商机级联清理);更换主要意向客户走「改主要意向客户」切标记(事务内旧主→0/新主→1)而非删行,保证「有且仅一个主要意向客户」恒成立。**切主一致性(grill 拍板 20260821)**:一事务三步(旧主→0 / 新主→1 / 刷主表冗余)原子完成;并发复用商机主表乐观锁 `version`(切主=商机级写),子表部分唯一索引 `UNIQUE(opportunity_id) WHERE is_primary_intended=1` 作 DB 最后防线。⚠ `customer_id` 引用 + 下拉选客户能力依赖 A4 客户模块(未建),见票 11。
3 weeks ago
_Avoid_: 客户(客户是 A4 域的实体)、甲方(甲方是快照里的一个字段)、customer
**暂缓**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
商机流转动作(原型 A3-1-1-2-6):短期推不动、但仍有价值时挂起。选**暂缓原因**(字典)+ **预计重启日期**(必填,不早于今天)+ 补充说明 → 状态「暂缓中」。附加字段落主表(`pause_time`=now/`pause_reason`/`pause_expected_restart_date`/`pause_remark`,E4 写、E5 清空;grill 拍板 20260821)。**保留领取人+团队、不进公海、冻结回收计时**(暂缓期间不被超时回收;取消暂缓时把 `last_valid_follow_time` 顺推暂缓时长实现冻结,见【取消暂缓】,不存剩余天数伪状态)。保留暂缓前阶段/节点。到期不自动恢复——系统提醒领取人,人工选 取消暂缓(恢复推进)/ 继续暂缓 / 关闭。按钮可见性由权限点配置,可见即可点,与是否领取人无关。**暂缓期间一切编辑操作全禁,必须先取消暂缓回到推进中才能写**(写跟进/子表写同禁;P1-7 产品拍板 20260821)。暂缓中(3) 允许的只有 E5 取消暂缓/E6 关闭/E9 移交。
3 weeks ago
_Avoid_: 中止(不存在)、挂起、冻结、关闭(关闭是明确无价值)
**取消暂缓**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
暂缓的逆动作(产品补充):暂缓中 → 恢复推进,回到暂缓前阶段/节点。原型 A3 无独立页(推测详情页按钮)。按钮可见即可点,与领取人无关。**回收锚点顺推(grill 拍板 20260821)**:取消暂缓时 `last_valid_follow_time += 暂缓时长`(把暂缓那段从未跟进时长扣除),实现「暂缓期间冻结回收」;回收判定本身是无状态实时比较(`now−last_valid_follow_time≥recycle_days`),**不存「剩余天数」**。
3 weeks ago
_Avoid_: 恢复(口语)、重启(重启是关闭的逆动作)、激活
**抛公海**:
2 weeks ago
商机流转动作(原型 A3-1-1-2-7):不想继续做,退回商机公海让他人领取。**只清空领取人**,回公海,**不动其他团队成员**(产品明确)。**无池实体**——公海 = `opp_status=1 且 owner 为空` 的逻辑视图,`owner_dept_id` 保留作 **DataScope 部门锚点**(公海可见性按查看者档位自然判定,同管理视图口径;grill 补拍 20260821 暂定,待产品终审)。人工抛公海入口受公海规则 `allow_manual_pool` 开关控制;此外连续 `recycle_days` 天无**有效跟进**会被**超时回收**自动回公海(票 05 `executeRecycle`,锚点 `last_valid_follow_time`)。
3 weeks ago
_Avoid_: 释放(线索用「释放」)、退回、放弃
**关闭**:
商机流转动作(原型 A3-1-1-2-9):明确无法继续推进时终止。选**关闭原因**(字典:项目取消 / 客户放弃 / 竞争失败 / 重复商机 / 无效商机 / 其他)+ 补充说明(选「其他」必填)+ 勾选确认框 → 状态「已关闭」。保留关闭前状态/阶段(**只读锁定**),历史数据不删。已关闭不可重复关闭。与暂缓互斥:短期有价值→暂缓,明确无价值→关闭。
_Avoid_: 中止(不存在)、作废(线索用「作废」)、删除、输单(口语)
**重启商机**:
关闭的逆动作(原型称「重新开启」,产品称「重启商机」):已关闭 → 恢复。原型要求填重开原因。原型原文写「须管理权限用户」,但**产品后来统一为「重启按钮由权限点控制、可见即可点、与领取人无关」,以产品口径为准**。
_Avoid_: 恢复、取消暂缓(取消暂缓是暂缓的逆动作)、激活
**移交**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
负责人变更动作,**入口在团队成员页(原型 A3-1-1-1-8)**,非详情页顶部操作区。**移交只换领取人 `owner_user_id` + 姓名/部门快照同步刷新**(`owner_dept_id` 换为移交对象的部门,公海规则匹配锚点跟人走;grill 补拍 20260821),不动团队成员等其他。**移交=部门完整过户**(grill 拍板 20260821):移交后若被超时回收/抛公海,**回落新负责人部门的公海**,原部门不再可见/领回(`owner_dept_id`=当前归属部门快照,非原始血缘;原始血缘部门由不变字段 `origin_dept_id` 记录,仅审计追溯、不参与匹配/可见性)。**移交同步维护 team 表(grill 拍板 20260821)**:新负责人 upsert 一条 team 行角色标签=「商机负责人」;原负责人角色标签改为普通协作(权限列不动——角色不带权限)、**留在团队继续协作并落入「我参与的」**,保证 `owner_user_id` 与 team 中「商机负责人」角色行始终指同一人。**无独立「转让」动作**——「转让只有负责人能转」不是额外 guard,而是移交入口本身在负责人管理的团队成员页里(原型:「当前用户只能维护其权限范围内的团队成员及权限设置」),团队管理权实际落在领取人手上。
3 weeks ago
_Avoid_: 转让(无此独立动作)、转移、指派(分配才是指派)
**转项目**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
商机的下游终态动作:商机推到最后转成项目,商机进入「已转项目」终态、锁死不可回退(对称线索的「已转商机」)。**无独立「转项目」按钮动作**(grill 补拍 20260821)——标准路径 = 创建商机 → 填写方案卡 → **推送方案卡 → 系统自动创建项目**(**本期 spin-out、不可达**,grill 拍板 20260821),推送成功即触发轴1 终态(E8)+ 轴2 固定节点;列表「转项目」按钮只是**推送流程快捷入口**。**本期 E8 仅特殊路径可达** = 跳过商机录入、项目侧直接创建项目,可关联方案卡(选填,归 A5)。**关联即联动**(grill 补拍 20260821):项目创建成功且关联了商机(或商机下的方案卡)时,A5 回调商机模块触发 E8(2→5)+ 落固定节点;本模块暴露回调端口(入参 = 商机 id 或方案卡 id),校验关联合法性后执行 E8 侧效(幂等:已终态则拒绝/跳过)。**转项目端口**名字已定死(grill 拍板 20260821,对称 lead→opp port)——商机侧 inbound `OpportunityConvertPort`(`ConvertToProjectCmd(opportunityId 必填幂等键, schemeCardId 选填, projectId)` + `OpportunityConvertException`),A5 调用、商机侧实现;幂等命中已终态=安静跳过。项目实体、项目查重规则(原型 A7-3-2-4)归 A5 项目模块,本域只承认终态、定契约。
3 weeks ago
_Avoid_: 成交、转化、结项、CreateProject(具体接口名留给 A5)
**已转项目**:
商机**终态**:已转成项目。锁死——不可暂缓 / 抛公海 / 关闭 / 编辑。对称线索的「已转商机」终态。
_Avoid_: 已成交、已完成、结束
3 weeks ago
**项目金额**:
商机主表字段 `project_amount`(decimal(14,2),选填)。销售挖掘得来的**项目整体总包金额**。一个商机一个值,默认人民币元不建币种字段。早期未必知可后编缓补。票 03 Amendment 1。
_Avoid_: 方案预算(方案卡字段,不同层次)、预计金额(旧名,已弃)、成交金额(项目阶段才有)、合同金额
**方案预算**:
3 weeks ago
方案卡子表字段 `scheme_budget`(票 07)。销售对该商机下某个**方案卡**填的金额。**一客户一卡**(`UNIQUE(opportunity_id, customer_id)`),一商机多客户 → 多卡多预算。商机列表「方案预算」列 = 查**主要意向客户**(票 13)那张方案卡的预算,主表不冗余。**不做主卡(`is_primary`)**——取值锚点是主要意向客户(客户级),非卡级。
3 weeks ago
_Avoid_: 项目金额(商机主表字段,不同层次)、预计金额(旧名,已弃)
3 weeks ago
**方案卡**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
商机推进过程中沉淀「客户需求/预算/招标形式/介入阶段/方案状态」的业务单据,转项目的**基础档案**。住 `crm-opportunity``opportunity_scheme_card`(票 07,原型 A3-1-1-1-3)。**一商机可多张、一客户一卡**(`UNIQUE(opportunity_id, customer_id)`),每张必关联商机下一个关联客户(无孤立方案卡);字段集由绑定的**方案卡模板**版本驱动(动态字段);单卡状态 `草稿1/已提交2/已推送3`(「未创建」=0卡展示态,P0-4)。列表「方案卡状态/方案预算」列取**主要意向客户那张卡**(票 13,非主卡)。**任一方案卡推送成功 → 系统自动创建项目 = 「已转项目」联动触发源**(grill 补拍 20260821;**推送本期 spin-out、不可达**):一商机多客户多卡,其中任一张推送成功即触发整商机转项目终态(E8,2→5),**终态压制方案卡自身状态机**——其余草稿/已提交卡随即冻结为只读(`card_status<3` 可编辑的前提是商机未关闭/未终态)。**模板可用性grill 拍板 20260821)**:无发布中模板不能建卡入口置灰);候选匹配专用优先通用兵底;`crm-rule` 初始化 seed 植入一条全公司通用发布中模板作兵底字段集见 PRD 附录 A原型 A7-3-2-2 OCR 校准 11 字段),`crm-opportunity` 只消费
_Avoid_: 推送规则(A7-3-2-3)、查重规则(A7-3-2-4)——均随推送票 spin-out(grill 拍板 20260821,翻转“本期做”),与方案卡本体/字段模板分层,不混入本票范畴
3 weeks ago
**现场勘察**:
2 weeks ago
记录商机某次到客户现场勘察的**结果单据**(现场条件/客户需求/安装环境/工程师建议 + 附件)。住 `crm-opportunity``opportunity_site_survey`(票 08,原型 A3-1-1-1-4)。**一商机可多条**(多次勘察保留历史不覆盖);**无状态机**(区别于方案卡);**不绑阶段节点**(自由记录);主要工程师从 **`crm-auth` 组织架构**选(`engineer_user_id`,非自由文本);**`apply_no` 申请单号人工录入选填**(外部勘察申请流程未对接,grill 拍板 20260821);附件**复用 `crm-file`**商机附件子表(`biz_type=SITE_SURVEY`+`biz_id`,票 09),不独立建表。
3 weeks ago
_Avoid_: 工程师=操作人(工程师是被选执行人,可跨部门;操作人是当前登录销售)
**跟进记录**:
2 weeks ago
商机的客户沟通记录,`opportunity_follow`(票 09,原型 A3-1-1-1-6)。**无草稿态**(确认即提交,异于线索反馈记录);字段:跟进内容/时间/方式/关联客户/联系人/结果标签/下次跟进时间/附件。**保存跟进→刷新 `last_valid_follow_time`**(票 05 回收锚点刷新点之一);与阶段推进**不联动**。**支持删除、删除不回退回收锚点(grill 拍板 20260821)**:`last_valid_follow_time` 保持不变(防「写了又删」刷锚点),「最近跟进」按删除后最新留存行自然变化,删除入 oplog;**编辑本期不做**(写错以删除+补写修正)。
3 weeks ago
_Avoid_: 线索反馈记录(有草稿态,机制不同)
**商机附件**:
统一附件子表 `opportunity_attachment`(票 09,原型 A3-1-1-1-5,文件本体走 `crm-file`)。**一张表 + `biz_type`+`biz_id` 定位**,服务商机通用/跟进/勘察/方案卡各场景(`biz_type` 枚举 OPP/FOLLOW_UP/SITE_SURVEY/SCHEME_CARD…),避免每个子表各建一套附件表。资料类型走 `crm-dict`「商机资料类型」分组(原型明要求做字典);附件归属商机(无孤立附件)。
_Avoid_: 方案卡附件/勘察附件各自建表(已统一为一表)
**工作计划**:
商机下的待办/后续动作子表 `opportunity_work_plan`(票 09,原型 A3-1-1-1-7)。字段:计划内容/截止时间/执行状态(未完成/已完成/逐期实时算)/创建人。本域只定**商机侧子表结构与归属**,不定 Tab 怎么读中央工作计划模块 A1X(spin-out)。
_Avoid_: 中央工作计划模块(A1X,跨域,本票不定读写)
**操作日志**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
商机的**字段级变更审计** `opportunity_oplog`(票 09,原型 A3-1-1-1-9):实体对象/字段名称/旧值/新值/操作说明/来源(系统自动 vs 人工)/时间/操作人。普通用户只读。**双维度分类(grill 拍板 20260821)**:`op_kind` 记录形态(`FIELD_CHANGE` 有旧新值 / `ROW_ADD` 整行新增 / `ROW_DELETE` 整行删除带 `biz_ref`+快照)+ `log_type` 业务类别(原型 line 1745:字段变更/状态流转/阶段流转/附件上传/方案推送/子表增删 前端可见,`SYSTEM_INTERNAL` 记库不显)。子表整行增删走 `ROW_ADD`/`ROW_DELETE`(日志可读「删除关联客户:XX公司」非 `deleted 0→1` 废记录),删除留痕另靠 BaseEntity `updater_id`/`update_time`(不加 `deleted_by`)。默认全记 + 短黑名单直接不记(技术字段/`last_valid_follow_time`),黑名单 ∩ SYSTEM_INTERNAL = ∅。**与线索 `lead_history` 口径不同**:线索是动作类型枚举(CLAIM/ASSIGN…)+JSON,商机是字段级旧新值审计。状态/阶段迁移另有专用快照表(`opportunity_status_history`/`opportunity_stage_history`),与本通用审计并存不重。
3 weeks ago
_Avoid_: 线索 history 模型(动作枚举,口径不同)、状态/阶段快照表(专用表,非通用审计)
2 weeks ago
**催办/督办**:
管理侧督促功能(原型 A3-2-1-1-1 督办功能;管理列表/详情的「催办/批量催办」入口)。**本期不实现(spin-out 留白,grill 补拍 20260821)**——催办本质是向负责人发通知,平台通知基础设施未建(暂缓到期提醒发送渠道同因挂起);留白待通知能力就绪后与督办一并规划落地,本期管理视图隐藏相关按钮。
_Avoid_: 写跟进(催办是督促不是跟进记录)、站内信自建(通知属平台能力,商机域不自建)
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
**待发通知**:
回收前提醒的**落地载体** `opportunity_pending_notice`(grill 拍板 20260821,§7.10):平台通知能力未建,回收 Job 不直接发提醒,而是 upsert 一条待发记录(`notice_type`+`target_user_id`+`plan_notify_time`+`payload_json`+`notice_status` 待发0/已发1/已失效2),等通知能力就绪后另起消费者补发。**通用表**:本期只 `RECYCLE_REMIND` 真实写入,预留 `PAUSE_EXPIRE`(暂缓到期)/`URGE`(催办)——三处「向负责人发通知」需求共用一张待发表,不各建。提醒发出前商机被回收/领取/暂缓/关闭 → 待发记录置已失效。
_Avoid_: 直接发通知(本期只落待发记录)、三种提醒各建表(统一一张通用待发表)
2 weeks ago
**视图(内置 + 自定义)**:
2 weeks ago
销售机会工作区下的视图组织(票 14 推翻票 10「单一我负责的」,以 demo 0803 为准)。**内置视图四个**(「常用检索」Tab,固定不可删/改):**最近访问**(打开过详情的商机,时间倒序、按 id 去重留最新,上限可配 默认 50,落 `opportunity_view_log`)/ **我负责的** `owner=当前用户` / **我参与的**(团队成员且非负责人,见下)/ **我关注的**(关注表 `opportunity_focus` 命中,见下)。**自定义视图**(「自定义检索」Tab)= 用户保存的检索:检索名称 + 条件列表(字段∈列表可筛选列 + 操作符 等于/不等于/包含/不包含/为空/不为空 + 值)+ 排序(字段+asc/desc),可编辑/重命名/删除/图钉设默认——**落 `crm-preference` 平台能力**(用户私有、按 `scope_key` 分组,本期 scope=`opportunity.sales`,后续菜单加 scope 即复用)。**商机管理**(按 `@DataScope`)/ **商机公海**(`opp_status=1 且 owner 空` 逻辑视图)仍是独立 workspace、不进销售机会视图下拉、本期不开自定义视图。计算列不冗余存:节点停留=今−进入当前节点日(stage_history)、最近跟进=最新 follow、预计金额=`project_amount`。详情三皮肤(个人/管理/公海)=同一聚合按视角展不同操作区,非三套数据。
2 weeks ago
_Avoid_: 视图=状态切片(同一商机可跨视图)、预计成交时间(产品二次拍板:商机列表删除该列、不上主表;仅方案卡内部字段票07)、自定义视图绑死商机(落 crm-preference 按 scope 通用,票 14)、内置视图可编辑(内置固定,只自定义视图可增删改)
2 weeks ago
**视图形态**:
与「哪个视图/检索」**正交**的展示形态维度(原型 line 1281,票 10/14 未定义;grill 补拍 20260821):**列表 list(默认)/ 分屏 split / 看板 board**,标题栏左侧切换。同一份列表数据三种渲染,**后端列表接口不变**(同查询、同 DataScope、同分页),形态 = 纯前端渲染维度;三个工作区 + 最近访问均支持。看板 = 按「当前工作节点」分列的卡片流(列头带「共 N 条」计数),分屏 = 左列表 + 右详情面板(复用详情聚合接口)。**列配置(自定义字段/列偏好)仅列表视图生效**(grill 拍板 20260821),看板卡片与分屏面板字段固定不跟列偏好。形态偏好落 `crm-preference`(scope=`opportunity.sales`,未设 → list)。
_Avoid_: 形态=视图内容(两轴正交)、后端为形态单开接口(同一接口前端渲染)、列配置对看板/分屏生效
**最近访问**:
商机级个人浏览行为数据,落 `opportunity_view_log`(`user_id`+`opportunity_id`+`last_view_time`,`UNIQUE(user_id, opportunity_id)`;grill 补拍 20260821,票 10 未定落库方式)。**调用详情聚合接口即 upsert**,个人/管理/公海任意皮肤都算。**不写 `opportunity_oplog`**——访问是高频行为数据,oplog 是字段级审计轨迹(票 09 行为类别不含浏览),两者分离。表内历史行后台 Job 按容量/时间裁剪。「最近访问」视图 = 命中当前用户按 `last_view_time` 倒序取上限(默认 50)。
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
_Avoid_: 操作日志(访问不入审计)、浏览记录(统一叫最近访问)、视图=权限旁路(最近访问只是可见入口,领取/写跟进等动作仍走完整权限点+状态 CAS+公海规则校验)
2 weeks ago
2 weeks ago
**关注**:
商机级个人收藏动作,落 `opportunity_focus`(`opportunity_id`+`user_id`,`UNIQUE(opportunity_id, user_id)`;grill 补拍 20260821,票 14 视图引用前此表未定义)。**纯收藏夹语义,不带通知**(通知属平台通知基础设施,未建)。关注/取关无需原因、物理删行、不入操作日志。「我关注的」视图 = 该表命中当前用户;管理/公海/最近访问视图的「关注」按钮读写此表。
_Avoid_: 团队成员(成员是行级授权白名单,关注是个人收藏)、收藏(统一叫关注)
2 weeks ago
**批量操作**:
视图列表的批量领取/关注/分配/领导释放 = **逐条处理、部分成功**(grill 拍板 20260821):成功的成功,失败的逐条给失败原因,前端展「成功 x 条,失败 y 条 + 失败明细」;无全批回滚、无长事务。
_Avoid_: 全有或全无(批量不允许整批回滚)
**权限点**:
商机模块权限模型 = **按钮可见即可点,权限全由权限点配置决定,与「是否负责人」无关**(票 01 产品明确)。权限点**动作级**颗粒度(一个动作一个点,不按视图 × 动作拆),全集见 PRD §1.5;编码实现阶段按平台规范登记。团队成员行上的读写/只读/可写跟进/可推进节点 = **行级角色配置,不走全局权限点**
_Avoid_: owner guard(无「操作人==负责人」拦截,可见即可点)、视图×动作拆分(颗粒度过细)
2 weeks ago
**我参与的**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
销售机会内置视图之一(票 14):**我是该商机团队成员但不是负责人** —— `EXISTS(团队成员 WHERE opportunity_id=? AND user_id=当前用户 AND deleted=0) AND owner_user_id <> 当前用户`。团队成员表 = 票 02/04 建商机落的领取人=负责人 + 后续「添加团队成员」加入者。**成员身份判定一律带存活过滤 `deleted=0`**(grill 拍板 20260821:移除成员=软删 team 行,移除后 `EXISTS` 自动失效,从本视图与详情白名单消失)。与「我负责的」互斥不重叠(负责人走 `mine`,非负责成员走 `participated`)。
2 weeks ago
_Avoid_: 参与=含负责人(负责人归「我负责的」,参与专指非负责成员)、参与=仅能看(团队成员带角色权限,非观察者)
3 weeks ago
**进入公海原因**:
商机抢公海/回收时的分类 `pool_reason`(票 10 原型 A3-3-1 筛选项):销售主动释放 / 特殊原因暂停 / 领导释放 / 超期回收。公海列表按此筛选;「领导释放」=管理侧强制抛公海(操作人≠owner);「超期回收」=票 05 executeRecycle。抢公海时亦快照入池前工作节点。
_Avoid_: 抢公海无原因(已分四类,回填票 04/05)
**方案卡模板**:
方案卡的**版本化字段集配置**(选哪些字段/顺序/显示/必填/适用范围),住 `crm-rule``opportunity_scheme_card_template`(票 07 原型 A7-3-2-2)。与票 05 公海规则、票 06 阶段模板**同族**(公共约定 V-CONFIG:`template_code`+`version_no`+草稿/发布中/已停用,绑定锁版发新版不迁移)。字段来自**系统字段库**非自由造(删字段不删系统字段定义);字段库 = **自建 `opportunity_scheme_field_def`**(字段元数据:控件类型/关联对象目标/自动回填来源,crm-dict 码表模型不匹配故不复用;下拉类字段选项集可 `option_source` 引用 crm-dict 分组);适用范围**两档**(所有部门通用/对应部门专用,与票 05/06 同为两档,票 06 早期「三档」说法已废)。**创建方案卡时手动选模板版本**(不自动匹配):候选 = 适用范围命中商机 `owner_dept_id` 的「发布中」版本,选定后 `card_template_id` 锁版本、发新版在途卡不迁移(grill 补拍 20260821)。
3 weeks ago
_Avoid_: 方案卡(运行时单据,模板是配置)
商机业务阶段轴(轴2)的**版本化配置**,住 `crm-rule``opportunity_stage_template`(票 06 原型 A7-3-2-1)。范式 = `template_code`+`version_no`+草稿/发布中/已停用 + 适用范围(所有商机/指定部门 两档单选,本期不做指定业务类型) + 是否默认,与票 05 公海规则、票 07 方案卡模板**同族**(公共约定 V-CONFIG)。子表:`_dept`(适用对象,apply_scope=2)、`opportunity_stage_node`(有序阶段节点,引用「商机阶段」字典)。商机绑定后**锁死版本、发新版不迁移**(在途商机续用原版本,新建用最新发布版本)。
3 weeks ago
_Avoid_: 阶段字典(模板引用字典,不同层次)、阶段(阶段是运行时轴,模板是配置)
**阶段模板节点**:
`opportunity_stage_node`:一个阶段模板版本下的有序节点(`seq_no` + `stage_dict_code` 引用「商机阶段」字典 + `custom_node_name` + `work_goal` 工作目标 + `is_fixed`)。「已转项目」= `is_fixed=1` 固定末节点。模板节点覆盖显示名不反写字典。
_Avoid_: 阶段字典项(被引用方)、current_stage(运行时指针)
3 weeks ago
3 weeks ago
**商机公海规则**:
2 weeks ago
商机公海/回收/领取/提醒的**版本化规则配置**,住 `crm-rule``opportunity_pool_rule`(票 05 原型 A7-3-2-5 定稿)。范式 = 草稿/发布中/已停用 + `rule_code` + `version_no`(与票 06 阶段模板、票 07 方案卡模板同族),**不是** `lead_pool` 那种「一部门一池」实体(推翻初稿)。**匹配锚点 = 商机自身的 `owner_dept_id`**(规则跟着商机走,不跟查看人部门走;grill 补拍 20260821):抛公海/回收后 owner 清空但部门快照保留,系统读它匹配规则——专属规则(apply_scope=2 命中)决定两件事:该商机在公海**谁能看到、谁能领**(适用部门外的销售不可见不可领);无专属规则 → 用通用默认规则(apply_scope=1)兜底,全部门可见,能否领看 `allow_free_claim`。适用范围 = 所有部门通用 / 对应部门专用 + 默认规则兜底(无省/市/区地理维度)。**匹配以最低层级规则为准**(部门专用 > 通用默认,多部门专用取最具体);**一个部门同时只能被一条发布中规则覆盖**(发布时校验部门不重叠),故命中唯一(票 05 产品拍板)。五配置项:`allow_manual_pool`(允许手动抛公海)/ `auto_recycle_enabled`+`recycle_days` / `allow_free_claim`(允许自由领取)/ `recycle_remind_enabled`+`remind_days`。
3 weeks ago
_Avoid_: 商机公海池(无池实体)、商机池、线索池
**回收天数**:
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架) TDD 三切片(红→绿各一轮,19 测试全绿): - OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目), implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04 - Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法 * 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1) * 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点 (opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列 + 单计时器 claim_time/last_valid_follow_time * C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空) * 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票) * @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在 crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝 * 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status) * 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除) - OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper) - 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖 - CONTEXT.md 待细化小节勾销主表项 边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。 验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
商机公海规则字段 `recycle_days`(`auto_recycle_enabled` 时必填):连续 `recycle_days` 天无**有效跟进**则超时回收→待领取,清 owner(票 05 `executeRecycle`)。回收前 `remind_days` 天提醒当前负责人(`remind_days` < `recycle_days` 硬校验)。**回收/提醒由 crm-opportunity 自建 `@Scheduled` Job 串行**(先扫提醒再扫回收对齐线索域 ADR-0019 竞态规避cron 配置占位符默认每日凌晨grill 补拍 20260821)。**提醒只落待发记录不直接发送**(grill 拍板 20260821):Job upsert 一条 `opportunity_pending_notice`实际发送等平台通知能力spin-out 留白)。**无失效天数 M**(商机无失效态 04/05)。
3 weeks ago
_Avoid_: 失效天数(商机无)、超时天数、N
2 weeks ago
**有效跟进**:
商机公海回收计时的**刷新动作**(刷新锚点 `last_valid_follow_time`)。P1-8 产品拍板(20260821):**商机状态为推进中(2) 且动作 = 写跟进**(本期唯一动作;上附件/编辑字段/方案卡/勘察等不计,产品二次修订)。动作类型集合是**配置项**——落 `crm-rule` 第四族「有效跟进规则」`opportunity_follow_rule`(V-CONFIG 同族:`rule_code`+`version_no`+草稿/发布中/已停用+适用范围两档+动作类型多选子表),回收 Job 按商机 `owner_dept_id` 实时匹配当前发布中规则(与公海规则同匹配引擎)。领取/分配重置锚点=now;两入口创建锚点=进入名下时间(P1-6)。
_Avoid_: 浏览(不算有效跟进)、编辑主表字段(不计)、上传附件(不计)
3 weeks ago
## 待细化(后续票)
- 「移交」按钮原型位置、「取消暂缓/重启」独立页存否 —— 见 `issues/01` 的 To-verify,以产品口径为准。
- ~~商机主表字段~~ **票 03 已落地**(`Opportunity` 实体 + `OpportunityMapper`,三簇 + Amendment 1 + 乐观锁 version);子表(方案卡票 07 已落 / 跟进·附件·工作计划·操作日志票 09 已落 / 现场勘察票 08 已落 / 团队成员待后续)字段归各票。
- ~~状态机迁移图~~ **票 04 已落地**(`OpportunityTransition` + sealed `OpportunityTransitionCmd` 九边(E1-E9,每带 allowedFromStatuses,无 owner guard)+ `executeRecycle` 批量回收(E10);CAS 两级口径:守卫前置 STATUS_NOT_ALLOWED / 0 行 CAS_FAIL,多起始态边(E6/E9)锚合法起始态集合,E9 另锚 owner;E5 锚点顺推纯函数;`OpportunityStatusHistory` 快照表 + Recorder;`OpportunityConvertPort` 三件套 inbound 端口(幂等含并发复查终态补丁));E9 team 表同步维护待 opportunity_team 结构定稿后接线(javadoc 注明);E7 重启不刷回收锚点为规格空白(PRD §3.2 注记 20260824,待产品拍板)。
- ~~商机公海规则~~ **票 05 已落地**(`OpportunityMaintenanceJob` + 待发通知表):crm-rule 侧实体/版本状态机服务/控制器/菜单种子 + 快照式匹配引擎 `OpportunityPoolRuleMatcher`(见 crm-rule CONTEXT 商机规则子域);本模块侧 `executeRecycle` 演进为 **ids 集合语义**(选择策略归 Job——各部门 recycle_days 不同无法单 cutoff 表达,迁移机制归状态机,按 id+推进中 CAS 复查静默跳过并发变更行)、`OpportunityMaintenanceJob` 单 `@Scheduled`(cron 占位符 `crm.opportunity.maintenance-cron` 默认每日 3 点)**先提醒后回收**串行(ADR-0019 对齐)+ 回收后待发提醒置已失效;`OpportunityPendingNotice` 实体 + 常量(UNIQUE 批次键 = plan_notify_time,幂等口径见 PRD §7.10 回填)。**已知缺口(code-review 20260824 登记)**:① §7.10 失效语义仅落地「回收」来源——领取(E1)/暂缓(E4/E5)/关闭(E6/E9)路径的置失效埋点待通知消费者票统一补(消费者本期 spin-out,误置「待发」无人消费、实际影响为零);② `allow_free_claim`/`allow_manual_pool` 本期仅可配置未生效,消费点随领取/抛公海入口票(票 10/11)接线;③ `invalidatePendingReminds` 按本轮候选全集近似置失效(含 CAS 静默跳过行),已声明由消费者补发时校验商机状态兜底。四视图查询与「我的关注」表(票 10)仍待后续。
- ~~商机子表族(跟进 / 附件 / 工作计划 / 操作日志)~~ **票 09 已落地**(§7.4–7.7 四张表结构 + Mapper 骨架):`OpportunityFollow`(跟进,软删+delete_key,无草稿态,支持删除不回退锚点,本期不做编辑)/ `OpportunityAttachment`(统一附件表,`biz_type`+`biz_id` 定位,`AttachmentBizType` 枚举 OPP/FOLLOW_UP/SITE_SURVEY/SCHEME_CARD,文件本体走 crm-file,软删+delete_key)/ `OpportunityWorkPlan`(工作计划**结构 only**,plan_status 0/1,精确态枚举与联动归 A1X 模块 spin-out,软删+delete_key)/ `OpportunityOplog`(字段级审计,**双维度** `OplogKind`(FIELD_CHANGE/ROW_ADD/ROW_DELETE)+ `OplogLogType`(7 类,SYSTEM_INTERNAL 记库不显),只增不删无 delete_key,带 biz_ref + snapshotJson)。**本票只落表结构 + Mapper + 枚举 + 冒烟单测**(对齐票 03 粒度);**接线待后续**:跟进读写 Service/Controller + 刷 `last_valid_follow_time`(票 05 联动)、附件读写、统一审计切面自动写 oplog(写入点驱动)均未接。crm-dict 新增分组「跟进方式/跟进结果标签/商机资料类型」待字典种子票。
- ~~`crm-rule` 从「线索规则」升级为「通用业务规则域」的 CONTEXT.md 改写~~ **票 05 已完成**(标题/首段升级 + 商机规则子域三词汇:商机公海规则/规则版本状态机/公海规则匹配引擎;线索子域内容不动)。