## Answer **Status: resolved** ### A. 客户主表 `customer`(`crm-customer`) #### A1. 字段清单 **系统字段(BaseEntity 继承)** | Java 字段 | 列名 | 类型 | 说明 | |---|---|---|---| | id | id | bigint PK | 自增主键 | | createTime | create_time | datetime | 创建时间 | | updateTime | update_time | datetime | 最后修改时间 | | createBy | create_by | bigint | 创建人 user_id | | updateBy | update_by | bigint | 最后修改人 user_id | | deleted | deleted | tinyint(1) default 0 | 逻辑删除(**一期:仅系统在合并时置 1,不开放手动删除/无前端删除入口**——§5.3 拍板 A) | **业务字段 — 系统生成** | Java 字段 | 列名 | 类型 | 必填 | 说明 | |---|---|---|---|---| | customerNo | customer_no | varchar(32) | 系统生成 | 唯一编号,格式 KH+yyyyMMdd+4位序;只读不可改 | **业务字段 — 基本信息** | Java 字段 | 列名 | 类型 | 必填 | 说明 | |---|---|---|---|---| | customerName | customer_name | varchar(100) | ✅ | 客户名称;停输防抖触发相似查重 | | customerType | customer_type | varchar(50) | ✅ | 客户类型字典 `customer_type`;见 A3 字典节 | | provinceCode | province_code | varchar(20) | ✅ | 省(三级地区,与商机对称)| | cityCode | city_code | varchar(20) | ✅ | 市 | | districtCode | district_code | varchar(20) | | 区/县(客户有三级,商机只二级) | | industryCode | industry_code | varchar(50) | ✅ | 行业一级 code(复用 crm-dict `industry` 两级树;客户选到二级,见商机图 D1「行业字典口径」决策:商机选一级、客户选二级)| | industryChildCode | industry_child_code | varchar(50) | | 行业二级 code(客户额外存二级;商机无此列) | | customerStarLevel | customer_star_level | tinyint | ✅ | 客户星级 1~5(枚举,非字典;字典值「一星/二星/…/五星」=前端展示用,存数字) | | relationStarLevel | relation_star_level | tinyint | ✅ | 关系星级 1~5(同上) | | networkUnits | network_units | text | | 人脉关系单位,JSON 数组存标签名(单个≤50字;非关联 CRM 客户;选填) | > ⚠ **JPA/Hibernate 孤立单大写字母规则**(AGENTS.md):`districtCode` / `industryCode` / `industryChildCode` / `provinceCode` / `cityCode` / `customerNo` / `customerStarLevel` / `relationStarLevel` — 均为「完整词+Code/Level/No」,非孤立单大写,**不需加 `@Column(name=...)`**。 **业务字段 — 归属(轴1)** | Java 字段 | 列名 | 类型 | 必填 | 说明 | |---|---|---|---|---| | ownerUserId | owner_user_id | bigint | ✅(新增时) | 销售负责人 user_id;公海时为 NULL | | ownerUserNameSnapshot | owner_user_name_snapshot | varchar(64) | | 负责人姓名快照(列表免 join) | | ownerDeptId | owner_dept_id | bigint | ✅(新增时) | 销售部门 dept_id 快照;公海时保留最后部门(供公海可见性锚点,对称商机) | | ownerDeptNameSnapshot | owner_dept_name_snapshot | varchar(64) | | 部门名快照 | > 协同人(团队成员)= 独立子表 `customer_team_member`(不在主表)。 **业务字段 — 轴2 阶段 + 档案状态** | Java 字段 | 列名 | 类型 | 必填 | 说明 | |---|---|---|---|---| | customerStage | customer_stage | tinyint default 1 | | 客户阶段:潜在1/重潜2/已成交3;只增不减;不可手动改 | | archiveStatus | archive_status | tinyint default 1 | | 档案状态:**有效1/已归档2**(D19 拍板去掉「已被合并」,合并 Out-of-scope);正交于归属;已归档即删除语义(软删),列表加 `archive_status` 筛选默认排除 | **业务字段 — 快照/冗余(列表免 join)** | Java 字段 | 列名 | 类型 | 说明 | |---|---|---|---| | lastValidFollowTime | last_valid_follow_time | datetime | 最近有效跟进时间(新建时=create_time;每写一条跟进记录刷新;超期提醒计时锚点) | | strategicAgreementLevel | strategic_agreement_level | tinyint | 战略协议等级快照(1~3级;战略协议子域划出但主表留此冗余供列表展示;NULL=未签);由战略协议子系统写入,本图只读 | | vipCustomerLevel | vip_customer_level | tinyint | 已成交VIP客户等级快照(1~5级;NULL=无;由 A5 项目模块写入,本图只读/留 seam)| > ⚠ `strategicAgreementLevel` → Hibernate 命名策略**孤立单大写字母**:`Level` 前面的 `l` 是小写 → `strategicAgreement` + `Level` 整段。实测 `SpringPhysicalNamingStrategy` 对 `strategicAgreementLevel` 生成 `strategic_agreement_level` ✅(`Level` 是完整词,无问题)。但 `vipCustomerLevel` → `vip` 首字母 `v` 是小写,`C` 是孤立大写?→ 不是,`vip` 是小写词,`Customer` 是完整词,`Level` 是完整词,**不踩坑**。 **业务字段 — 关联信息** | Java 字段 | 列名 | 类型 | 必填 | 说明 | |---|---|---|---|---| | companyDecisionMaker | company_decision_maker | varchar(100) | | 公司决策人(文本) | | isBizNegotiated | is_biz_negotiated | tinyint | ✅ | 是否商机勾兑(是1/否0) | | directorVisitTime | director_visit_time | datetime | | 总监近期拜访时间(人工维护,日期时间)| | isChild | is_child | tinyint default 0 | ✅ | 是否子客户(是1/否0);=1时 parent_customer_id 必填 | | parentCustomerId | parent_customer_id | bigint | 条件必填 | 父客户 id;服务端防环校验;isChild=0时为 NULL | | parentCustomerNameSnapshot | parent_customer_name_snapshot | varchar(100) | | 父客户名称快照 | | cooperationSystem | cooperation_system | varchar(200) | | 合作系统(文本,选填) | | nonCoopReason | non_coop_reason | varchar(200) | | 不合作原因(文本,选填) | | joinedPresidentClass | joined_president_class | tinyint | | 是否参加总裁班(是1/否0/NULL=未选) | | presidentClassPerson | president_class_person | varchar(100) | 条件必填 | 总裁班参加人(joinedPresidentClass=1时必填) | | presidentClassPhone | president_class_phone | varchar(20) | 条件必填 | 总裁班参加人电话 | | joinedProductClass | joined_product_class | tinyint | | 是否参加产品经理班 | | productClassPerson | product_class_person | varchar(100) | 条件必填 | 产品班参加人 | | productClassPhone | product_class_phone | varchar(20) | 条件必填 | 产品班参加人电话 | **业务字段 — 工商信息(企查查回填)** | Java 字段 | 列名 | 类型 | 说明 | |---|---|---|---| | unifiedCreditCode | unified_credit_code | varchar(18) | 统一社会信用代码;UNIQUE(NULL 除外,未填时不唯一约束);18 位格式校验 | | legalRepresentative | legal_representative | varchar(100) | 法定代表人 | | establishedDate | established_date | date | 成立时间(不得晚于当前日期) | | registeredCapital | registered_capital | varchar(100) | 注册资本(文本,含单位,如「1000万元」) | | businessScope | business_scope | text | 经营范围(不套 50 字限制) | | staffSize | staff_size | varchar(50) | 人员规模 | | annualRevenue | annual_revenue | varchar(50) | 年产值 | | businessAddress | business_address | varchar(500) | 工商注册地址(不套 50 字限制) | | bizNameFromLookup | biz_name_from_lookup | varchar(200) | 企查查确认的工商名称(可与 customer_name 不同;只存不覆盖 customer_name,除非用户确认)| **业务字段 — 补充资料** | Java 字段 | 列名 | 类型 | 说明 | |---|---|---|---| | remark | remark | varchar(500) | 备注(≤500字) | > 附件 = 独立子表 `customer_attachment`(复用 crm-file,`biz_type=CUSTOMER`;不在主表)。 --- #### A2. 需要显式 `@Column(name=...)` 的孤立单大写字母字段 检查全部字段——**结论:本表无需加 `@Column(name=...)`**。所有字段均为「完整词 + 完整词」组合,无孤立单大写字母触发 Hibernate 命名策略陷阱。 --- #### A3. 字典清单(新增/复用) | 字典分组 code | 含义 | 状态 | 参考值 | |---|---|---|---| | `customer_type` | 客户类型 | 🆕 需新增 | **(grill Q8/D18 定稿,18 值)** 运营商 / 土建总包 / 机电总包 / 装修总包 / 教育局 / 投资(代建)公司 / 设计院 / 集成商 / 集成商(子客户) / 咨询造价公司 / 甲方(企业用户) / 甲方(普教) / 甲方(中高职) / 甲方(政府机关) / 竞争同行 / 个人用户 / 操手 / 招标代理 | | `industry` | 行业(两级树) | ✅ 已有(商机图票09已建;9一级+51二级) | 复用 | | `customer_star_level` | 客户星级 | **硬编码枚举 1~5(D4-rev1 定稿,不走字典)** | 1~5 星 | | `relation_star_level` | 关系星级 | **硬编码枚举 1~5(D4-rev1 定稿,不走字典)** | 一星~五星 | > ✅ 已定(grill Q8/D18):`customer_type` = crm-dict 字典分组,18 值(见上表)。含 4 甲方值 + 14 非甲方角色值,是通用角色字典。 --- #### A4. DataScope 注解 ```java @DataScope(module = "customer", ownerColumn = "owner_user_id", deptColumn = "owner_dept_id") ``` 复用 crm-auth `DataScopeEnum(SELF/DEPT/DEPT_AND_CHILD/ALL)`,不新建可见性机制。 --- #### A5. 索引 | 索引名 | 列 | 说明 | |---|---|---| | `uk_unified_credit_code` | `unified_credit_code` | 信用代码全局唯一(NULL 不参与唯一约束,MySQL 默认行为)| | `idx_owner_stage` | `owner_user_id, customer_stage, deleted` | 我的客户视图常用过滤 | | `idx_dept_stage` | `owner_dept_id, archive_status, deleted` | 公海/总览部门维度 | | `idx_name` | `customer_name` | 相似查重防抖查询 | | `idx_parent` | `parent_customer_id` | 父子客户查询/防环 | | `idx_last_follow` | `last_valid_follow_time, owner_user_id` | 超期未跟进提醒扫表 | --- #### A6. 产品确认项(⚠ P0)— 已随 grill 全部销号 | # | 问题 | 影响 | |---|---|---| | P0-1 | ~~`customer_type` 完整枚举值~~ **已拍板(grill Q8/D18)**:crm-dict 字典,18 值 | 字典种子数据 | | P0-2 | ~~客户选行业到几级~~ **定稿:二级**(`industry_code` + `industry_child_code` 两列;与商机图 D1 一致) | 两列 | | P0-3 | ~~星级枚举 vs 字典~~ **已拍板(D4-rev1)**:硬编码枚举 1~5,不走字典 | 存储类型 | | P1-1 | `vip_customer_level` 等级范围(原型列表列有「已成交VIP客户等级」但详情字段写「1~5级」,待确认范围及触发条件,本图只留 seam) | — | | P1-2 | `strategic_agreement_level` 战略协议等级范围(原型见「一级/二级/三级」=1~3;战略协议子域划出,主表只存冗余快照,待战略协议 effort 回填写逻辑) | — | --- #### A7. 回填 ticket 02 的下游影响 - **票 03**(三视图):`archiveStatus` 过滤(**有效1/已归档2**,无已被合并——D19),`ownerUserId IS NULL` = 公海视图;`last_valid_follow_time` 排序。 - **票 04**(查重):`unified_credit_code` UNIQUE 索引 + `customer_name` idx 是查重的存储底座。 - **票 05**(阶段):`customer_stage` 默认1,只能 UPDATE SET customer_stage=? WHERE id=? AND customer_stage < ?(只前进)。 - **票 07**(oplog):`last_valid_follow_time` 新建时初始化为 `create_time`。 - **票 08**(交割):`ownerUserId` + `ownerDeptId` 是交割换主的操作列。 - **票 10**(超期提醒):`last_valid_follow_time` 是计时锚点;新建时初始化为 `create_time`(grill 挂号确认,见 map.md)。