unis_crm/%E6%B8%A0%E9%81%93%E6%8B%93...

164 lines
11 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 渠道拓展 ⇄ CRM拓展 数据互移功能方案
> 版本V1.0  日期2026-08-28  适用范围CRM 系统渠道拓展 / CRM拓展
## 一、功能概述
1. 在「渠道拓展详情」底部操作区新增「移至CRM拓展」按钮将整条渠道拓展数据主表 + 联系人子表 + 跟进记录)迁移为一条 CRM 拓展记录,删除源渠道记录,并同步更新所有引用该渠道的地方。
2. 对称地在「CRM拓展详情」底部操作区新增「移至渠道拓展」按钮实现 CRM 拓展数据反向迁移为渠道拓展,逻辑对称。
3. 点击按钮后先弹出「迁移前补填」表单,用户补填源数据中缺失的目标必填字段,确认后执行迁移。
4. 按钮权限与现有「编辑资料」按钮保持一致:仅记录本人(负责人)可见可点。
## 二、权限控制
- 复用前端 `canEditSelectedItem = ownerUserId === currentUserId`,与编辑按钮同一套权限逻辑,不新增权限点。
- 非本人记录:按钮禁用并显示「仅本人可操作」,行为与编辑按钮一致。
- 后端同样校验记录存在且 `owner_user_id = 当前用户`,防止越权调用。
## 三、后端接口设计
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/expansion/channel/{id}/move-to-crm` | 渠道 → CRM请求体携带弹窗补填字段 |
| POST | `/api/expansion/crm/{id}/move-to-channel` | CRM → 渠道,请求体携带弹窗补填字段 |
服务层新增方法(均使用 `@Transactional`,任一步失败整体回滚):
```java
Long moveChannelToCrm(Long userId, Long channelId, MoveChannelToCrmRequest payload);
Long moveCrmToChannel(Long userId, Long crmId, MoveCrmToChannelRequest payload);
```
迁移流程(以渠道 → CRM 为例):
1. 校验记录存在且归属当前用户(复用 `countOwnedChannelExpansion`)。
2. 查询源主表数据(复用现有单条查询)。
3. 自动映射字段 + 合并弹窗补填字段,构造 `CreateCrmExpansionRequest`(不走表单校验)。
4. `insertCrmExpansion` 创建目标主表,获取新 id。
5. 联系人子表逐条复制(`name / mobile / title / sort_order`)。
6. 引用同步详见第四节跟进记录、外勤打卡、日报消息迁移至新记录商机、其它CRM进货商引用置空。
7. 删除源:先删联系人子表,再删渠道主表。
## 四、引用同步清单(决策①:删除源 + 同步引用)
### 4.1 渠道 → CRM删除渠道源记录后
| 引用表.字段 | 原值 | 处理后 | 说明 |
|---|---|---|---|
| `crm_opportunity.channel_expansion_id` | 源渠道id | 置空 NULL | 存在外键约束,删除前必须置空,否则删除失败 |
| `crm_crm_expansion.supplier_id` | 源渠道id | 置空 NULL | 存在外键约束,删除前必须置空 |
| `crm_expansion_followup.biz_type / biz_id` | `'channel'` / 源id | `'crm'` / 新id | 拜访/跟进记录随迁 |
| `work_checkin.biz_type / biz_id / biz_name` | `'channel'` / 源id / 旧名 | `'crm'` / 新id / 新名 | 外勤打卡关联同步 |
| `work_report_message.biz_type / biz_id / biz_name` | `'channel'` / 源id / 旧名 | `'crm'` / 新id / 新名 | 日报消息关联同步 |
### 4.2 CRM → 渠道删除CRM源记录后
| 引用表.字段 | 原值 | 处理后 | 说明 |
|---|---|---|---|
| `crm_expansion_followup.biz_type / biz_id` | `'crm'` / 源id | `'channel'` / 新id | 拜访/跟进记录随迁 |
| `work_checkin.biz_type / biz_id / biz_name` | `'crm'` / 源id / 旧名 | `'channel'` / 新id / 新名 | 外勤打卡关联同步 |
| `work_report_message.biz_type / biz_id / biz_name` | `'crm'` / 源id / 旧名 | `'channel'` / 新id / 新名 | 日报消息关联同步 |
### 补充说明
- `crm_channel_expansion_contact` 子表外键为 `ON DELETE CASCADE`,删除渠道主表时联系人自动级联删除,无需单独处理。
- `work_todo`(待办)仅用于日报「明日计划」待办(`biz_type='report'`),不涉及渠道/CRM拓展无需同步。
- `sys_activity_log`(首页动态日志)为历史遗留表,当前代码无写入路径;如存在历史引用建议一并清理(可选)。
- `crm_crm_expansion.h3c_contact_id` 指向销售拓展(`crm_sales_expansion`),与本次迁移无关,保持不变。
## 五、字段对应关系 + 弹窗补填范围(决策②:迁移前弹窗填写)
### 5.1 渠道拓展 → CRM拓展自动映射有对应关系
| 渠道拓展字段(列) | 渠道拓展含义 | CRM拓展字段 | CRM拓展含义 / 说明 |
|---|---|---|---|
| `channel_name` | 渠道名称 | `end_user` | 最终用户 |
| `province` | 省份 | `office_name` | 代表处(按 tz_bsc 字典转换,未匹配保留原值) |
| `channel_industry` | 聚焦行业 | `industry_attr` | 行业属性(按 tz_sshy 字典转换) |
| `contact_established_date` | 建立联系时间 | `purchase_date` | 采购时间 |
| `contact_name / mobile / title` | 主联系人 | `contact_name / phone / title` | 冗余字段 |
| `crm_channel_expansion_contact`(子表) | 多联系人 | `crm_crm_expansion_contact`(子表) | 逐条平移 |
| `remark` | 备注 | `remark` | 写入DB列CRM表单无备注字段 |
| `owner_user_id` | 负责人 | `owner_user_id` | 保持不变 |
**渠道特有、CRM 无对应 → 不迁移:** `channel_code`、`city`、`office_address`、`certification_level`、`annual_revenue`、`staff_size`、`intent_level`、`has_desktop_exp`、`channel_attribute`、`internal_attribute`、`stage`、`landed_flag`、`expected_sign_date`
**弹窗补填CRM无来源的必填字段**
| 弹窗字段 | 默认值 | 说明 |
|---|---|---|
| `extension_type`(类型) | 空 | 字典 `crm_extension_type` 单选,必填 |
| `online_status`(在线情况) | 空 | 字典 `crm_online_status` 单选,必填 |
| `has_expansion_opportunity`(是否有扩容机会) | 空 | 字典 `sys_is` 单选,必填 |
| `supplier_id`(进货商) | 空 | AdaptiveSelect 选渠道,必填(不能选自身) |
| `h3c_contact_id`(新华三对接人) | 空 | AdaptiveSelect 选销售拓展,必填 |
| `warranty_expiry`(过保时间) | 采购时间+3年 | 预填可改 |
### 5.2 CRM拓展 → 渠道拓展:自动映射(有对应关系)
| CRM拓展字段 | CRM拓展含义 | 渠道拓展字段(列) | 渠道拓展含义 / 说明 |
|---|---|---|---|
| `end_user` | 最终用户 | `channel_name` | 渠道名称 |
| `office_name` | 代表处 | `province` | 省份(反查 tz_bsc未匹配保留原值 |
| `industry_attr` | 行业属性 | `channel_industry` | 聚焦行业 |
| `purchase_date` | 采购时间 | `contact_established_date` | 建立联系时间 |
| `contact_name / phone / title` | 冗余联系人 | `contact_name / mobile / title` | 主联系人 |
| `crm_crm_expansion_contact`(子表) | 多联系人 | `crm_channel_expansion_contact`(子表) | 逐条平移 |
| `remark` | 备注 | `remark` | 直接映射 |
| `owner_user_id` | 负责人 | `owner_user_id` | 保持不变 |
**CRM特有、渠道无对应 → 不迁移:** `extension_type`、`warranty_expiry`、`online_status`、`supplier_id`、`h3c_contact_id`、`has_expansion_opportunity`
**弹窗补填(渠道无来源的必填字段):**
| 弹窗字段 | 默认值 | 说明 |
|---|---|---|
| `city`(市) | 空 | 必填 |
| `office_address`(办公地址) | 空 | 必填 |
| `certification_level`(认证级别) | 空 | 字典单选,必填 |
| `annual_revenue`(年度营业额·万元) | 空 | 数字>0必填 |
| `staff_size`(人员规模) | 空 | 正整数,必填 |
| `channel_attribute`(渠道属性) | 空 | 字典单选,必填 |
| `internal_attribute`(内部属性) | 空 | 字典单选,必填 |
| `intent_level`(合作意向) | `medium` | 可改 |
| `has_desktop_exp`(桌面扩展能力) | `false` | 可改 |
| `stage`(阶段) | `initial_contact` | 预填 |
| `landed_flag`(是否落地) | `false` | 预填 |
| `expected_sign_date`(预计签约时间) | 空 | 可空 |
> 说明:渠道表单编辑时校验 `province / city / certificationLevel / officeAddress / channelIndustry / annualRevenue / staffSize / channelAttribute / internalAttribute` 等字段非空,因此迁移弹窗将这些字段设为必填,确保迁移后的记录完整、可继续编辑保存。
## 六、并发与重复防护
迁移为「删除源 + 创建目标」的复合写操作,必须保证并发安全与幂等,防止重复迁移、竞态丢失或脏数据。
### 6.1 后端并发控制
- 迁移接口在事务内对源记录执行 `SELECT ... FOR UPDATE`(行锁),锁定源记录,避免与并发的编辑/其它迁移操作产生竞态。
- 若源记录已被其它事务迁移或删除,`SELECT FOR UPDATE` 返回空 → 抛出「记录不存在或已被迁移」提示。
- 引用同步、目标创建、源删除均在同一个事务内完成,任一步失败整体回滚,不产生中间态。
### 6.2 幂等与重复提交防护
- 迁移成功后源记录即被删除;重复调用接口时源记录不存在,后端返回明确错误(「记录不存在或已被迁移」),不会重复创建目标。
- 前端提交后立即进入 loading 态并禁用按钮,防止用户重复点击。
### 6.3 迁移与编辑的互斥
- 迁移期间源记录被行锁锁定,其它用户的编辑操作(`updateChannelExpansion` / `updateCrmExpansion`)在锁释放前等待,避免「迁移了旧数据」的脏读。
- 弹窗补填的字段在后端二次校验(必填、数值范围、进货商/对接人不能为空),校验失败整体回滚。
## 七、前端实现
- 按钮位置:详情页底部操作区(现有「编辑资料」按钮旁),按 `selectedItem.type` 显示:`type='channel'` 显示「移至CRM拓展」`type='crm'` 显示「移至渠道拓展」,`type='sales'` 不显示。
- 样式与交互:与编辑按钮同款按钮样式;点击后弹「迁移前补填」表单(复用现有表单字段组件与字典选项),校验通过后二次确认,再调用接口。
- 状态处理:提交时 loading 态(按钮禁用防重复提交);成功后刷新列表、关闭详情并切换到新记录;失败时 toast 展示错误信息(如「记录不存在或已被迁移」)。
## 八、实现清单(后续开发步骤)
1. 后端:新增 `MoveChannelToCrmRequest` / `MoveCrmToChannelRequest` DTO`ExpansionController` 新增两个接口;`ExpansionService` 新增两个 `@Transactional` 方法。
2. Mapper新增按源 id 更新 `work_checkin` / `work_report_message``biz_type` / `biz_id` / `biz_name`、商机 `channel_expansion_id` 置空、其它CRM `supplier_id` 置空、删除渠道/CRM主表等 SQL。
3. 并发防护:迁移查询源记录时使用 `SELECT ... FOR UPDATE` 行锁;重复迁移返回「记录不存在或已被迁移」;后端对弹窗补填字段二次校验。
4. 前端:新增两个补填表单弹窗组件 + 两个迁移按钮 + 二次确认与成功刷新逻辑,提交时 loading 禁用防重复点击。
5. 验证渠道→CRM 与 CRM→渠道 各执行一遍,核对主表、联系人、跟进记录、打卡/日报关联、商机引用均正确迁移或置空。