# 03 商机主表字段与归属模型 Type: grilling Status: resolved Blocked by: 01 ## Question 定 `opportunity` 主表的字段清单与归属模型(对称线索主表,是后面状态机/公海/子表/视图票的地基)。 - **归属字段**:`owner_user_id`(领取人 = 决策[B],归属归商机主表)、`owner_dept_id`(部门快照,兼公海可见范围锚点)、`creator_user_id`(创建人,全生命周期不变)。~~`pool_id`~~ 票 05 定稿删(商机域无池实体)。 - **两条轴的落点**:轴1 归属流转状态字段(单字段,取值见 04);轴2 当前阶段字段(`current_stage_id` + 引用哪个阶段模板 `stage_template_id`/版本)。 - **来源字段**:`source_lead_id`(可空,见 02)+ 带出快照(商机名称/联系电话/需求产品 code 等)。 - **业务字段**(从原型 A3-1-1-2-1 新增/编辑商机抽取):商机名称、行业(字典)、招标形式(字典)、甲方/意向客户、地区(国标 code)、预计金额、预计成交时间、备注等——逐字段定必填/类型/长度/校验。 - **两把计时器?**(票 05 已答):线索有回收N/失效M两把;商机**只有自动回收**(无失效态)。回收锚点 = `last_valid_follow_time`(最近有效跟进时间,非 claim_time),见簇 B4。 产出:主表 DDL 级字段规格表(字段名/必填/类型/长度/校验/错误提示)。 ## Answer 三簇拍板(grilling,产品逐条确认)。主表 `opportunity` 字段规格表如下。 ### 簇 A — 业务字段(原型 A3-1-1-2-1 抽取,纠正若干脑补) | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `opp_name` 商机名称 | varchar(200) | ✅ | | | `opp_source` 商机来源 | varchar(64) 字典码 | ✅ | 渠道归因;线索转入时商机侧默认置「线索转入」(票 02) | | `opp_type` 商机类型 | varchar(64) 字典码 | ✅ | 原型必填 | | `industry_code` 行业 | varchar(64) 字典码 | ✅ | | | `bid_form` 招标形式 | varchar(64) 字典码 | ✅ | 原型必填 | | `locality_type` 项目属地 | varchar(64) 字典码 | ✅ | **本地/异地分类**(产品澄清:不是地理坐标,是分类标签) | | `party_a_clear` 甲方是否明确 | tinyint 0/1 | ✅ | | | `party_a` 甲方名称 | varchar(200) | 条件必填 | `party_a_clear=1` 时必填 | | `province_code` 省份 | varchar(12) | ✅ | 对称 lead | | `city_code` 市/区 | varchar(12) | ✅ | 对称 lead 约定:既可填市码也可填区/县码(不单建 `district_code`) | | `remark` 备注 | text | ✕ | | **不建**:预计金额、预计成交时间(原型 A3-1-1-2-1 必填清单无,尊重原型不脑补,产品确认不建)。✅ **票 10 口径冲突已解**(产品二次拍板):**「预计成交时间」列从商机列表删除,不上商机主表**。它本是方案卡内部字段(票 07),仅存在于方案卡,不进商机四视图列表。故主表无需建 `expected_deal_date`。 ### 簇 B — 归属 + 两轴 + 来源 + 计时器 **B1 归属**(票 02 已大半定): | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `owner_user_id` 领取人 | bigint | ✕ | 抛公海后清空;归属+可见性;票 01 | | `owner_name_snapshot` 领取人姓名快照 | varchar(64) | ✕ | 对称 lead | | `owner_dept_id` 领取人部门快照 | bigint | ✕ | 票 02:调岗不变 | | `pool_reason` 进入公海原因 | varchar(32) | ✕ | **票 10 新增**:抛公海/回收时写(销售主动释放/特殊原因暂停/领导释放/超期回收,→字典);公海列表筛选。入池前工作节点复用 `current_stage_id`(不单建快照字段) | | `creator_user_id` 创建人 | bigint | ✅ | 全生命周期不变;票 01 | | ~~`pool_id` 来源公海池~~ **删** | ― | ― | **票 05 定稿删除**(推翻初稿 Amendment):商机域**无独立池实体**(原型 A7-3-2-5 确认公海=版本化规则,非 lead_pool 实体);公海 = `opp_status=1 且 owner 为空` 的逻辑视图,可见范围由 `owner_dept_id` + 匹配规则适用范围判定,不需外键指向池 | **不建**(票 05 定稿):`dept_id`(归属部门)、`team_dept_id`、`pool_name_snapshot`——商机主表只用 `owner_dept_id`(快照)做 DataScope 部门天花板;部门/团队/市场模型全落在 `opportunity_pool` 族(票 05),不上主表。 **B2 两条轴**(票 01 定两轴命名): | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `opp_status` 状态 | tinyint | ✅ | 轴 1 归属流转;取值票 04 定 | | `current_stage_id` 当前阶段 | bigint | ✅ | 轴 2;指向 `opportunity_stage_node.id`(绑定模板版本下的某节点,票 06 定稿);新建默认落首节点(客户圈定) | | `stage_template_id` 阶段模板 | bigint | ✅ | **票 06 定稿**:存**模板版本行 id**(`opportunity_stage_template.id`,版本级锁死,非 code);绑定时按适用范围匹配当时最新发布版本,发新版不自动迁移(在途商机续用原版本) | | `stage_template_version` 阶段模板版本号 | varchar(20) | ✅ | **票 06 定稿**(原占位 int→改 varchar):冗余 `version_no`(V1.2 等)展示用免 join;版本锁由 `stage_template_id` 保证 | **B3 来源快照**(票 02 拍死,此处对齐): | 字段 | 类型 | 说明 | |---|---|---| | `source_lead_id` 来源线索 | bigint UNIQUE | 幂等键;`NULL` 不参与唯一(直接创建可多条 NULL);票 02 | | `source_lead_name` 线索名快照 | varchar(200) | | | `source_phone` 联系电话快照 | varchar(32) | | | `source_product_code` 需求产品码快照 | varchar(64) | | **B4 计时器**: | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `claim_time` 领取时刻 | datetime | ✕ | 归属起点;抛公海时不清(对称 lead) | | `last_valid_follow_time` 最近有效跟进时间 | datetime | ✕ | **票 05 定稿回填**(推翻初稿 `recycle_deadline`):自动回收计时锚点。回收判定 = `now − last_valid_follow_time ≥ rule.recycle_days`(Job 实时算,不预存 deadline)。有效跟进(跟进记录/附件/字段编辑,非浏览)时刷新(票 09 联动) | **不建**(票 05 定稿):`expire_deadline`——商机无「过期失效」态(票 04),线索的失效计时器 M 不对称。 ### 簇 C — 系统字段 + 索引 + DataScope + 高频展示冗余 - **C1 继承 `BaseEntity`**:`Opportunity extends BaseEntity`,复用 id / 审计(create_time/update_time/creator/updater)/ 逻辑删除,不自建。 - **C2 DataScope 注解**:`@DataScope(module="opportunity", ownerColumn="owner_user_id", deptColumn="owner_dept_id")`。deptColumn 用 `owner_dept_id`(因 B1a 不建 `dept_id`)。module 名对齐票 01 + ADR-0018(按模块可配)。 - **C3 主要意向客户快照冗余进主表**(高频展示,对称 lead 反馈快照冗余): | 字段 | 类型 | 说明 | |---|---|---| | `primary_customer_id` 主要意向客户 id | bigint | = 关联客户表中 `is_primary_intended=1` 那条的 `customer_id`(票 13);引用 A4(未建) | | `primary_customer_name_snapshot` 主客户名快照 | varchar(200) | 高频展示,列表/详情顶部免 join;切换主客户时同步刷新 | ✅ **完整关联客户子表 = 票 13**(`opportunity_customer`,含 `is_primary_intended` 主客户标记 + `customer_role` 角色),已从“另议”钉死。一商机有且仅一个主要意向客户;`UNIQUE(opportunity_id, customer_id)`。主表此两字段仅为主客户冗余快照。 - **C4 起步索引**(票 05/10 视图定稿后再补): - `UNIQUE idx_source_lead` (`source_lead_id`) — 幂等键(票 02) - `idx_owner_status` (`owner_user_id`, `opp_status`) — 「我负责的」视图 - `idx_owner_dept_status` (`owner_dept_id`, `opp_status`) — 公海视图(`opp_status=1`)+ 部门天花板(票 05:公海=逻辑视图,无 pool_id) - `idx_owner_dept` (`owner_dept_id`, `opp_status`) — 部门天花板 ### 未定/传出决策(不阻塞本票) - `opp_status` 取值域 → 票 04 - ~~`stage_template_*` 版本模型~~ **票 06 已定**:`stage_template_id`=版本行 id 锁版本,`stage_template_version`=冗余版本号 varchar,`current_stage_id`→节点 id;绑定不自动迁移。 - ~~`pool_id` 是否引用商机公海池~~ **票 05 已答:删 pool_id(无池实体),新增 `last_valid_follow_time` 回收锚点**。 - 关联客户子表完整字段 → **票 13**(`opportunity_customer`;`customer_id` 引用挖 A4、下拉选客户能力挂 A4/票 11) - `dept_id`/`team_dept_id` 是否补建 → 票 05 公海规则出结论后回看 ### 已核实事实 - 原型 A3-1-1-2-1 必填清单:商机名称 / 商机来源 / 商机类型 / 商机行业 / 项目阶段 / 招标形式 / 项目区域(省市区) / 项目属地 / 甲方是否明确 / 关联客户 / 客户角色 / 是否主要客户 + 甲方名称(条件)。原型无预计金额/成交时间。 - lead 主表 `city_code` 约定:既可填市码也可填区/县码;商机对称沿用。 - 项目属地 = 本地/异地字典分类(产品澄清,非地理坐标,与项目区域正交)。 ## Amendment 1 — 新增 `project_amount`(项目金额) 产品口径变更:商机需记录**项目整体总包金额**(销售挖掘得来的),本票原「不建预计金额/成交时间」的前一半推翻——**项目金额建**,预计成交时间仍不建。 业务字段组新增一行: | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `project_amount` 项目金额 | decimal(14,2) | ✕ | 项目整体总包金额;销售新增商机时选填(早期未必知,可后编缓补);默认人民币元,不建币种字段 | **与「方案预算」区分(反正两个金额容易混)**: - **项目金额 `project_amount`** = 商机主表字段,项目整体总包,一个商机一个值。 - **方案预算 `scheme_budget`** = 方案卡子表字段(票 07),销售对该商机下某个方案卡填的金额,一个商机可多个方案卡→多个预算。商机列表的「方案预算」列 = **关联查主要意向客户(票 13)那张方案卡的预算**,主表不冗余(一客户一卡;票 07/13 处理)。