diff --git a/docs/CRM线索中心模块开发完成总结.md b/docs/CRM线索中心模块开发完成总结.md new file mode 100644 index 0000000..c164688 --- /dev/null +++ b/docs/CRM线索中心模块开发完成总结.md @@ -0,0 +1,262 @@ +# CRM线索中心模块开发完成总结 + +## 开发日期 +2026-05-19 + +## 项目信息 +- **服务归属**: hzhub-system (端口 8083) +- **包路径**: org.hzhub.crm +- **技术栈**: Spring Boot 3.5.8 + MyBatis-Plus + Sa-Token + +--- + +## 一、创建的文件列表 + +### 1. 数据库设计 +- `/data/hzhub/hzhub-system/src/main/resources/db/crm_lead_init.sql` + - 数据字典定义(5个字典类型) + - crm_lead 表(线索表) + - crm_lead_follow 表(跟进记录表) + +### 2. Entity 实体类 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLead.java` + - 继承 TenantEntity + - 包含 customerCode 字段(ERP客户编码) + - 使用 @TableLogic 逻辑删除 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLeadFollow.java` + - 继承 TenantEntity + +### 3. Bo 业务对象 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadBo.java` + - 继承 BaseEntity + - 使用 @AutoMapper 注解 + - 包含 customerCode 字段 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadFollowBo.java` + +### 4. Vo 视图对象 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadVo.java` + - 使用 @Translation 字段翻译 + - mobile 字段使用 @Sensitive 脱敏 + - 包含 customerCode 字段 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadFollowVo.java` + +### 5. Mapper 接口 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadMapper.java` + - 继承 BaseMapperPlus +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadFollowMapper.java` + +### 6. Mapper XML 映射文件 +- `/data/hzhub/hzhub-system/src/main/resources/mapper/crm/CrmLeadMapper.xml` +- `/data/hzhub/hzhub-system/src/main/resources/mapper/crm/CrmLeadFollowMapper.xml` + +### 7. Service 接口与实现 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadService.java` +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadFollowService.java` +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadServiceImpl.java` + - 实现列表查询、详情、新增、编辑、删除、分配、跟进记录查询、添加跟进 + - **关键逻辑**: 如果提供 customerCode,调用 ERP 服务拉取客户信息 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadFollowServiceImpl.java` + +### 8. ERP 集成类 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/service/ErpIntegrationService.java` + - 封装对 hzhub-erp 的调用 + - 使用 RestTemplate 调用 http://localhost:8082/erp/dynamic/v1/customer/detail + +### 9. Controller 控制器 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadController.java` + - 遵循 API 契约接口定义 + - 无 @SaCheckPermission 注解(员工门户权限由 Gateway 控制) + - 使用 @Log、@RepeatSubmit 注解 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadFollowController.java` + +### 10. 配置类 +- `/data/hzhub/hzhub-system/src/main/java/org/hzhub/system/config/RestTemplateConfig.java` + - 配置 RestTemplate Bean +- `/data/hzhub/hzhub-system/src/main/resources/application.yml` + - 添加 ERP 配置: erp.base-url + +--- + +## 二、数据库表结构摘要 + +### crm_lead(线索表) +主要字段: +- **lead_id**: 主键(ASSIGN_ID) +- **customer_code**: ERP客户编码(varchar(100),可选) +- **company_name**: 公司名称(必填) +- **contact_name**: 联系人(必填) +- **mobile**: 手机号(必填) +- **region_id**: 区域ID(关联 sys_dept) +- **source_type**: 来源类型(字典:crm_lead_source) +- **intent_level**: AI意向等级(字典:crm_intent_level) +- **ai_score**: AI评分(decimal(5,2)) +- **risk_level**: 风险等级(字典:crm_risk_level) +- **owner_user_id**: 负责人(关联 sys_user) +- **lead_status**: 状态(字典:crm_lead_status) +- **del_flag**: 删除标志(逻辑删除) + +继承 TenantEntity 字段: +- tenant_id, create_dept, create_by, create_time, update_by, update_time + +索引: +- idx_tenant_id, idx_customer_code, idx_mobile, idx_owner_user_id, idx_lead_status, idx_create_time + +### crm_lead_follow(跟进记录表) +主要字段: +- **follow_id**: 主键 +- **lead_id**: 线索ID(必填) +- **follow_type**: 跟进方式(字典:crm_follow_type) +- **content**: 跟进内容(必填) +- **ai_summary**: AI摘要 +- **next_follow_time**: 下次跟进时间 +- **follow_user_id**: 跟进人(关联 sys_user) +- **del_flag**: 删除标志 + +--- + +## 三、主要实现的接口功能 + +### 1. 线索列表查询 (GET /crm/lead/list) +- 支持多条件筛选:公司名称模糊、手机号、意向等级、负责人、customerCode等 +- 使用 TableDataInfo 分页返回 +- mobile 字段自动脱敏 +- 字典字段自动翻译 + +### 2. 线索详情查询 (GET /crm/lead/{leadId}) +- 返回线索详细信息 +- 字段自动翻译(负责人姓名、区域名称、字典值等) + +### 3. 新增线索 (POST /crm/lead) +- **关键逻辑**: 如果提供 customerCode,自动从 ERP 拉取客户信息填充 +- 校验手机号唯一性 +- 使用 @RepeatSubmit 防重复提交 +- 记录操作日志 + +### 4. 编辑线索 (PUT /crm/lead) +- 校验线索存在性 +- 校验手机号唯一性 +- 记录操作日志 + +### 5. 删除线索 (DELETE /crm/lead/{leadIds}) +- 支持批量删除(逻辑删除) +- 记录操作日志 + +### 6. 分配线索 (PUT /crm/lead/assign) +- 更新 owner_user_id +- 自动更新状态为 "following" + +### 7. 获取跟进记录列表 (GET /crm/lead/follow/{leadId}) +- 根据线索ID查询跟进记录 +- 按创建时间倒序 + +### 8. 添加跟进记录 (POST /crm/lead/follow) +- 自动设置跟进人为当前用户 +- 如果提供下次跟进时间,更新线索的 nextFollowTime +- 记录操作日志 + +--- + +## 四、ERP集成的实现方式 + +### 实现方式 +使用 `RestTemplate` 调用 hzhub-erp 服务 + +### 配置 +- **配置项**: `erp.base-url` (默认: http://localhost:8082) +- **配置类**: `RestTemplateConfig` (提供 RestTemplate Bean) + +### 调用逻辑 +```java +// CrmLeadServiceImpl.insertLead() 方法 +if (StringUtils.isNotBlank(lead.getCustomerCode())) { + // 调用 ERP 服务拉取客户详情 + Map customerDetail = erpIntegrationService + .getCustomerDetail(lead.getCustomerCode()); + + // 自动填充线索基础信息 + if (customerDetail != null) { + lead.setCompanyName((String) customerDetail.get("customerName")); + lead.setContactName((String) customerDetail.get("contactName")); + lead.setMobile((String) customerDetail.get("phone")); + // ... 其他字段 + } +} +``` + +### ERP接口 +- `GET /erp/dynamic/v1/customer/detail?customerCode={code}` +- 返回客户信息(Map形式) + +--- + +## 五、需要注意的配置项 + +### 1. 数据字典类型(需在数据库初始化) +- **crm_lead_source**: 线索来源(activity/referral/website/exhibition/wecom/erp/other) +- **crm_lead_status**: 线索状态(new/following/converted/invalid) +- **crm_intent_level**: AI意向等级(high/medium/low) +- **crm_risk_level**: 风险等级(high/medium/low) +- **crm_follow_type**: 跟进方式(phone/wecom/visit/email/other) + +### 2. ERP配置(application.yml) +```yaml +erp: + base-url: ${ERP_BASE_URL:http://localhost:8082} +``` + +可通过环境变量 `ERP_BASE_URL` 配置 ERP 服务地址。 + +### 3. SQL初始化顺序 +执行 `/data/hzhub/hzhub-system/src/main/resources/db/crm_lead_init.sql`: +1. 先创建数据字典(sys_dict_type, sys_dict_data) +2. 再创建业务表(crm_lead, crm_lead_follow) + +### 4. Gateway路由配置 +需在 `hzhub-gateway/src/main/resources/application.yml` 中添加: +```yaml +spring: + cloud: + gateway: + routes: + - id: hzhub-crm + uri: lb://hzhub-system + predicates: + - Path=/crm/** + filters: + - StripPrefix=1 +``` + +--- + +## 六、后续待实现功能(第二阶段) + +### 1. AI意向分析 +- 调用 hzhub-ai 服务(`/ai/analyze/intent`) +- 自动生成 aiScore、intentLevel + +### 2. AI跟进摘要生成 +- 调用 hzhub-ai 服务(`/ai/summarize`) +- 自动生成 aiSummary + +### 3. 线索转经销商 (POST /crm/lead/convert) +- 创建 crm_dealer 数据 +- 迁移历史跟进记录 +- 更新线索状态为 "converted" + +--- + +## 七、关键特性总结 + +1. **多租户支持**: 所有实体类继承 TenantEntity,自动租户隔离 +2. **ERP关联**: customerCode 字段关联 ERP 客户,自动拉取信息 +3. **字段翻译**: 使用 @Translation 注解自动翻译字典值、用户名、部门名 +4. **敏感字段脱敏**: mobile 字段列表查询时自动脱敏 +5. **逻辑删除**: 使用 @TableLogic,删除时不物理删除 +6. **员工门户适配**: 无 Sa-Token 权限注解,权限由 Gateway 控制 +7. **防重复提交**: 新增操作使用 @RepeatSubmit +8. **操作日志**: 新增、编辑、删除使用 @Log 记录 + +--- + +## 开发完成 +所有代码已创建完毕,可进行编译测试。 \ No newline at end of file diff --git a/docs/CRM销售模块详细设计说明书.md b/docs/CRM销售模块详细设计说明书.md new file mode 100644 index 0000000..ae1cf19 --- /dev/null +++ b/docs/CRM销售模块详细设计说明书.md @@ -0,0 +1,939 @@ +# CRM销售自动化(渠道版)执行级详细设计说明书 + +# 1. 文档说明 + +## 1.1 文档目标 + +本文档用于指导CRM销售自动化(渠道版)系统的: + +- 数据库设计 +- 后端接口开发 +- 前端页面开发 +- AI能力集成 +- 企业微信集成 +- 自动化流程配置 +- 权限与组织体系建设 + +本文档属于“开发执行版设计文档”。 + +--- + +# 2. 系统总体架构 + +## 2.1 技术架构建议 + +| 层级 | 技术建议 | +|---|---| +| 前端Web | Vue3 + Element Plus | +| 移动端 | 企业微信H5 + UniApp | +| 后端 | Java Spring Boot | +| 数据库 | MySQL 8 | +| 缓存 | Redis | +| 消息队列 | RabbitMQ | +| 搜索 | Elasticsearch | +| AI服务 | Python FastAPI | +| 文件存储 | MinIO | +| 工作流 | Flowable | +| BI | FineBI / Superset | + +--- + +# 3. 数据库设计规范 + +## 3.1 通用字段规范 + +所有业务表统一包含: + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| created_by | bigint | 创建人 | +| created_at | datetime | 创建时间 | +| updated_by | bigint | 更新人 | +| updated_at | datetime | 更新时间 | +| deleted | tinyint | 逻辑删除 | +| tenant_id | bigint | 租户ID | + +--- + +# 4. 线索中心模块设计 + +# 4.1 模块目标 + +用于管理潜在经销商。 + +支持: + +- 多渠道线索接入 +- AI意向识别 +- AI风险分析 +- 自动分配销售 +- 商机转化 + +--- + +# 4.2 数据表设计 + +## 4.2.1 leads(线索表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| company_name | varchar(200) | 公司名称 | +| contact_name | varchar(100) | 联系人 | +| mobile | varchar(50) | 手机号 | +| wechat | varchar(100) | 微信号 | +| province | varchar(50) | 省 | +| city | varchar(50) | 市 | +| region_id | bigint | 区域ID | +| source_type | varchar(50) | 来源类型 | +| activity_name | varchar(100) | 活动名称 | +| referrer_name | varchar(100) | 推荐人 | +| industry | varchar(100) | 行业 | +| company_scale | varchar(100) | 公司规模 | +| store_count | int | 门店数 | +| intent_level | varchar(20) | AI意向等级 | +| ai_score | decimal(5,2) | AI评分 | +| risk_level | varchar(20) | 风险等级 | +| owner_user_id | bigint | 负责人 | +| lead_status | varchar(50) | 状态 | +| converted_dealer_id | bigint | 转化经销商ID | + +--- + +## 4.2.2 lead_follow_records(线索跟进记录) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| lead_id | bigint | 线索ID | +| follow_type | varchar(50) | 跟进方式 | +| content | text | 跟进内容 | +| ai_summary | text | AI摘要 | +| next_follow_time | datetime | 下次跟进时间 | +| follow_user_id | bigint | 跟进人 | + +--- + +# 4.3 接口设计 + +## 4.3.1 创建线索 + +### API + +POST /api/leads + +### 请求参数 + +```json +{ + "companyName":"XX贸易有限公司", + "contactName":"张三", + "mobile":"13800000000", + "wechat":"zhangsan", + "province":"广东省", + "city":"深圳市", + "industry":"食品", + "storeCount":20 +} +``` + +### 业务逻辑 + +1. 校验手机号是否重复 +2. 调用AI服务分析意向等级 +3. 自动生成AI评分 +4. 根据区域规则分配销售 +5. 创建自动跟进任务 + +--- + +## 4.3.2 线索转经销商 + +POST /api/leads/convert + +### 逻辑 + +1. 创建dealer数据 +2. 迁移历史跟进记录 +3. 创建初始商机 +4. 更新线索状态 + +--- + +# 4.4 页面原型说明 + +## 4.4.1 线索列表页 + +### 页面布局 + +```text +------------------------------------------------- +顶部:搜索栏 + 高级筛选 +------------------------------------------------- +左侧:区域树 +右侧:线索列表 +------------------------------------------------- +底部:分页 +``` + +### 筛选项 + +- 区域 +- 来源 +- AI意向等级 +- 风险等级 +- 销售负责人 +- 创建时间 + +### 表格字段 + +| 字段 | +|---| +| 公司名称 | +| 联系人 | +| 手机 | +| 区域 | +| AI评分 | +| 意向等级 | +| 风险等级 | +| 当前负责人 | +| 跟进状态 | +| 下次跟进时间 | + +### 操作按钮 + +- 查看 +- 分配 +- 跟进 +- 转经销商 +- 作废 + +--- + +## 4.4.2 AI分析侧边栏 + +右侧固定展示: + +- AI意向评分 +- AI风险提示 +- 推荐动作 +- 推荐销售话术 +- 推荐拜访时间 + +--- + +# 5. 经销商中心模块设计 + +# 5.1 数据表设计 + +## 5.1.1 dealers(经销商表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| dealer_name | varchar(200) | 经销商名称 | +| dealer_code | varchar(100) | 编码 | +| contact_name | varchar(100) | 联系人 | +| mobile | varchar(50) | 手机 | +| province | varchar(50) | 省 | +| city | varchar(50) | 市 | +| level | varchar(50) | 等级 | +| lifecycle | varchar(50) | 生命周期 | +| signed_at | datetime | 签约时间 | +| store_count | int | 门店数 | +| team_size | int | 团队规模 | +| total_order_amount | decimal(18,2) | 累计订单金额 | +| total_payment_amount | decimal(18,2) | 累计回款金额 | +| activity_score | decimal(5,2) | 活跃评分 | +| risk_score | decimal(5,2) | 风险评分 | +| owner_user_id | bigint | 负责人 | + +--- + +## 5.1.2 dealer_tags(经销商标签表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| tag_name | varchar(100) | 标签名称 | +| tag_type | varchar(50) | 标签类型 | +| score | decimal(5,2) | 标签评分 | + +--- + +# 5.2 接口设计 + +## 5.2.1 获取经销商画像 + +GET /api/dealers/{id}/profile + +### 返回内容 + +```json +{ + "dealerName":"XX贸易", + "activityLevel":"高活跃", + "riskLevel":"低风险", + "growthTrend":"高增长", + "lastVisitTime":"2026-05-10", + "nextVisitTime":"2026-05-20" +} +``` + +--- + +# 5.3 页面设计 + +## 5.3.1 经销商详情页 + +### 页面布局 + +```text +顶部:基础信息卡片 +------------------------------------------------- +Tab页: +1. 基础档案 +2. 商机 +3. 拜访记录 +4. 订单 +5. 回款 +6. AI画像 +7. 会话记录 +------------------------------------------------- +右侧:AI经营分析面板 +``` + +### AI经营分析面板 + +显示: + +- 活跃度趋势 +- 近90天订单趋势 +- 流失风险 +- 推荐动作 +- 竞品风险 + +--- + +# 6. 拜访管理模块设计 + +# 6.1 数据表设计 + +## 6.1.1 visit_plans(拜访计划表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| visit_type | varchar(50) | 拜访类型 | +| planned_time | datetime | 计划时间 | +| visit_user_id | bigint | 销售人员 | +| status | varchar(50) | 状态 | + +--- + +## 6.1.2 visit_records(拜访记录表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| visit_plan_id | bigint | 拜访计划ID | +| visit_time | datetime | 拜访时间 | +| participants | varchar(500) | 参与人员 | +| voice_file_url | varchar(500) | 录音文件 | +| ai_summary | text | AI摘要 | +| ai_requirements | text | AI提取需求 | +| ai_risk | text | AI风险 | +| next_action | text | 下一步动作 | +| latitude | decimal(10,6) | 纬度 | +| longitude | decimal(10,6) | 经度 | +| sign_photo_url | varchar(500) | 签到照片 | + +--- + +# 6.2 AI语音处理流程 + +```text +语音上传 +-> ASR语音识别 +-> NLP结构化提取 +-> AI摘要生成 +-> 风险分析 +-> 推荐下一步动作 +-> 写入CRM +``` + +--- + +# 6.3 页面设计 + +## 6.3.1 移动端拜访页面 + +### 页面模块 + +- 地图签到 +- 语音录入按钮 +- 拍照上传 +- AI实时摘要 +- 下一步动作建议 + +### AI辅助区域 + +自动生成: + +- 客户关注点 +- 异议问题 +- 竞品信息 +- 推荐招商话术 + +--- + +# 7. 商机管理模块设计 + +# 7.1 数据表设计 + +## 7.1.1 opportunities(商机表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| opportunity_name | varchar(200) | 商机名称 | +| stage | varchar(50) | 商机阶段 | +| estimated_amount | decimal(18,2) | 预计金额 | +| success_rate | decimal(5,2) | 成交概率 | +| expected_sign_date | date | 预计签约时间 | +| owner_user_id | bigint | 销售负责人 | +| ai_next_stage | varchar(50) | AI建议阶段 | +| ai_probability | decimal(5,2) | AI成交预测 | + +--- + +## 7.1.2 opportunity_stage_logs(阶段日志表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| opportunity_id | bigint | 商机ID | +| old_stage | varchar(50) | 原阶段 | +| new_stage | varchar(50) | 新阶段 | +| change_reason | text | 变更原因 | +| changed_by | bigint | 变更人 | + +--- + +# 7.2 商机阶段规则 + +| 阶段 | 条件 | +|---|---| +| 初步接触 | 创建商机 | +| 需求沟通 | 已记录需求 | +| 招商政策沟通 | 已发送政策 | +| 样品测试 | 已寄样 | +| 商务谈判 | 已讨论返点 | +| 签约中 | 已提交合同 | +| 已签约 | 合同生效 | + +--- + +# 7.3 AI自动推进逻辑 + +## 规则示例 + +```text +如果AI识别: +- 已询价 +- 已谈返点 +- 已谈库存 + +则自动建议推进至“商务谈判”阶段。 +``` + +--- + +# 7.4 页面设计 + +## 7.4.1 商机看板页 + +### 视图模式 + +- Kanban阶段看板 +- 列表模式 +- 销售漏斗模式 + +### 看板列 + +```text +初步接触 +需求沟通 +招商政策沟通 +样品测试 +商务谈判 +签约中 +已签约 +``` + +### 卡片展示 + +- 经销商名称 +- 金额 +- AI成交概率 +- 最近跟进时间 +- 风险提示 + +--- + +# 8. 合同管理模块设计 + +# 8.1 数据表设计 + +## 8.1.1 contracts(合同表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| contract_no | varchar(100) | 合同编号 | +| dealer_id | bigint | 经销商ID | +| opportunity_id | bigint | 商机ID | +| contract_amount | decimal(18,2) | 合同金额 | +| sign_date | date | 签约日期 | +| expire_date | date | 到期日期 | +| contract_status | varchar(50) | 状态 | +| approval_status | varchar(50) | 审批状态 | +| sign_file_url | varchar(500) | 合同文件 | +| ai_risk_summary | text | AI风险摘要 | + +--- + +# 8.2 AI合同分析 + +AI识别: + +- 高风险条款 +- 超标准返点 +- 区域冲突 +- 超长账期 + +--- + +# 8.3 审批流设计 + +```text +销售提交 +-> 区域经理审批 +-> 财务审批 +-> 法务审批 +-> 总部审批 +-> 电子签章 +``` + +--- + +# 9. 订单管理模块设计 + +# 9.1 数据表设计 + +## 9.1.1 orders(订单表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| order_no | varchar(100) | 订单编号 | +| dealer_id | bigint | 经销商ID | +| contract_id | bigint | 合同ID | +| order_amount | decimal(18,2) | 订单金额 | +| order_status | varchar(50) | 订单状态 | +| shipment_status | varchar(50) | 发货状态 | +| payment_status | varchar(50) | 回款状态 | +| logistics_status | varchar(50) | 物流状态 | +| erp_sync_status | varchar(50) | ERP同步状态 | + +--- + +# 9.2 ERP同步逻辑 + +## 同步内容 + +- 订单 +- 发货 +- 库存 +- 回款 +- 物流 + +## 同步方式 + +- 定时拉取 +- Webhook回调 +- MQ异步同步 + +--- + +# 9.3 页面设计 + +## 9.3.1 订单中心 + +### 页面布局 + +```text +顶部:订单筛选 +------------------------------------------------- +中部:订单表格 +------------------------------------------------- +右侧:订单风险分析 +``` + +### 风险分析 + +- 延迟发货风险 +- 超账期风险 +- 异常退货风险 + +--- + +# 10. 回款管理模块设计 + +# 10.1 数据表设计 + +## 10.1.1 payments(回款表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| order_id | bigint | 订单ID | +| payment_amount | decimal(18,2) | 回款金额 | +| payment_date | date | 回款日期 | +| receivable_due_date | date | 应收截止日 | +| overdue_days | int | 超期天数 | +| payment_status | varchar(50) | 状态 | + +--- + +# 10.2 AI预警规则 + +| 条件 | 预警 | +|---|---| +| 超过30天未回款 | 高风险 | +| 连续2次延迟 | 中风险 | +| 回款金额下降 | 流失风险 | + +--- + +# 11. 企业微信协同模块设计 + +# 11.1 集成能力 + +## 企业微信侧边栏 + +展示: + +- 经销商画像 +- 最近订单 +- 最近拜访 +- 商机阶段 +- AI风险 + +--- + +## 会话存档同步 + +同步内容: + +- 文本 +- 图片 +- 文件 +- 语音 + +--- + +# 11.2 数据表设计 + +## 11.2.1 wecom_chat_records(企业微信会话表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | bigint | 主键 | +| external_user_id | varchar(100) | 外部联系人 | +| dealer_id | bigint | 经销商ID | +| sender_id | bigint | 发送人 | +| message_type | varchar(50) | 消息类型 | +| message_content | text | 内容 | +| send_time | datetime | 发送时间 | +| ai_analysis_result | text | AI分析结果 | + +--- + +# 11.3 AI会话分析 + +识别: + +- 采购意向 +- 价格异议 +- 投诉风险 +- 竞品信息 + +--- + +# 12. AI分析中心设计 + +# 12.1 AI标签体系 + +| 标签类型 | 示例 | +|---|---| +| 活跃标签 | 高频沟通 | +| 风险标签 | 流失风险 | +| 经营标签 | 高增长 | +| 敏感标签 | 价格敏感 | + +--- + +# 12.2 AI预测模型 + +| 模型 | 输入 | +|---|---| +| 成交预测 | 商机数据 | +| 回款预测 | 订单与历史回款 | +| 流失预测 | 活跃度与订单 | +| 补货预测 | 销量趋势 | + +--- + +# 13. 自动化工作流设计 + +# 13.1 自动提醒规则 + +| 规则 | 动作 | +|---|---| +| 7天未跟进 | 创建跟进提醒 | +| 30天未下单 | 创建经营风险提醒 | +| 合同即将到期 | 通知销售与经理 | +| 回款超期 | 通知财务与销售 | + +--- + +# 13.2 自动任务规则 + +| 条件 | 自动任务 | +|---|---| +| 高意向线索 | 自动创建拜访 | +| 商机推进 | 自动创建回访 | +| 合同签约 | 自动创建首单跟进 | + +--- + +# 14. BI分析设计 + +# 14.1 BI指标体系 + +## 销售指标 + +- 销售额 +- 回款率 +- 转化率 +- 客单价 +- 区域增长率 + +--- + +## AI经营指标 + +- 高风险经销商数 +- 高潜经销商数 +- AI成交预测准确率 +- AI流失预测准确率 + +--- + +# 14.2 仪表盘设计 + +## 总部仪表盘 + +展示: + +- 全国销售地图 +- 区域排名 +- 经销商增长趋势 +- AI风险预警 +- 商机漏斗 + +--- + +## 销售个人仪表盘 + +展示: + +- 今日待跟进 +- 今日拜访 +- 本月成交 +- AI推荐客户 +- AI销售建议 + +--- + +# 15. 权限与组织设计 + +# 15.1 RBAC模型 + +## 数据权限层级 + +```text +总部 +-> 大区 +-> 区域 +-> 城市 +-> 销售 +``` + +--- + +# 15.2 权限控制点 + +| 模块 | 控制点 | +|---|---| +| 线索 | 查看/分配/转移 | +| 经销商 | 编辑/归属 | +| 合同 | 审批/金额修改 | +| 回款 | 查看/核销 | +| BI | 区域数据隔离 | + +--- + +# 16. 移动端设计 + +# 16.1 企业微信H5页面 + +必须支持: + +- 快速拜访 +- AI语音录入 +- 地图签到 +- 拍照上传 +- 快速订单查询 +- AI风险提醒 + +--- + +# 16.2 移动端交互要求 + +| 要求 | 说明 | +|---|---| +| 单手操作 | 核心按钮底部固定 | +| 弱网容错 | 本地缓存 | +| 快速录入 | 支持语音 | +| 离线能力 | 支持草稿 | + +--- + +# 17. 性能与安全设计 + +# 17.1 性能目标 + +| 指标 | 目标 | +|---|---| +| 页面响应 | <2秒 | +| AI分析响应 | <5秒 | +| ERP同步延迟 | <1分钟 | +| 并发支持 | 5000用户 | + +--- + +# 17.2 安全设计 + +## 安全要求 + +- HTTPS加密 +- JWT鉴权 +- 数据权限隔离 +- 敏感字段脱敏 +- 操作日志审计 +- 企业微信身份校验 + +--- + +# 18. AI能力优先级实施建议 + +# P0(第一阶段必须上线) + +- 企业微信集成 +- 客户画像 +- AI语音拜访 +- 会话分析 +- 订单查询 + +--- + +# P1(第二阶段) + +- AI标签 +- AI摘要 +- AI意向识别 +- 自动提醒 + +--- + +# P2(第三阶段) + +- AI Copilot +- AI销售建议 +- AI预测 +- AI自动推进商机 + +--- + +# 19. 项目实施建议 + +# 第一阶段 + +建设: + +- 基础CRM +- 企业微信 +- 拜访管理 +- 商机管理 +- 订单查询 + +--- + +# 第二阶段 + +建设: + +- AI标签 +- AI会话分析 +- AI语音拜访 +- 自动化工作流 + +--- + +# 第三阶段 + +建设: + +- AI预测 +- AI Copilot +- AI经营分析 +- 智能决策 + +--- + +# 20. 最终产品定位 + +系统最终定位: + +# “AI驱动的渠道销售经营平台” + +区别于传统CRM: + +传统CRM:记录客户 + +本系统: + +- AI驱动增长 +- AI驱动招商 +- AI驱动销售动作 +- AI驱动经营分析 +- AI驱动渠道运营 + diff --git a/docs/CRM销售模块详细设计说明书V2.md b/docs/CRM销售模块详细设计说明书V2.md new file mode 100644 index 0000000..c513c3d --- /dev/null +++ b/docs/CRM销售模块详细设计说明书V2.md @@ -0,0 +1,1525 @@ +# CRM销售自动化(渠道版)执行级详细设计说明书 V2 + +# 1. 文档说明 + +## 1.1 文档目标 + +本文档用于指导HZHub项目中CRM销售自动化(渠道版)系统的: + +- 数据库设计(适配 MySQL 8 + MyBatis-Plus) +- 后端接口开发(适配 hzhub-system 服务) +- 前端页面开发(适配 hzhub-admin Vben Admin) +- AI能力集成(适配 LangChain4j + Weaviate) +- 企业微信集成 +- 自动化流程配置(适配 warm-flow) +- 权限与组织体系建设(适配 Sa-Token) + +本文档属于"开发执行版设计文档 V2",针对HZHub项目架构进行定制化适配。 + +--- + +## 1.2 与 V1 版本的主要差异 + +| 方面 | V1 版本 | V2 版本(HZHub 适配) | +|---|---|---| +| 前端技术栈 | Vue3 + Element Plus | Vben Admin + Ant Design Vue | +| 后端框架 | Java Spring Boot(通用) | Spring Boot 3.5.8 + MyBatis-Plus | +| API路径 | `/api/**` | `/crm/**`(通过 Gateway 路由) | +| 权限框架 | JWT(自定义) | Sa-Token(已集成) | +| 工作流引擎 | Flowable | warm-flow 1.8.2(已集成) | +| AI服务 | Python FastAPI | LangChain4j(Java) | +| 向量数据库 | 未指定 | Weaviate 1.25.0(已部署) | +| 搜索引擎 | Elasticsearch | 暂不集成(可选) | +| 消息队列 | RabbitMQ | 暂不集成(可选) | +| 文件存储 | MinIO | MinIO(已部署) | +| 实体设计 | 通用字段规范 | TenantEntity(多租户) | + +--- + +# 2. 系统总体架构 + +## 2.1 HZHub 集成架构 + +### 服务归属 + +CRM模块归属于 **hzhub-system** 服务(端口 8083),通过 Gateway 路由对外提供服务。 + +``` +┌─────────────────────────────────────────┐ +│ hzhub-admin (管理后台) │ +│ Vue3 + Vben Admin + Ant Design Vue │ +│ Port: 5666 │ +└────────────┬────────────────────────────┘ + │ + ┌────────┴────────┐ + │ hzhub-gateway │ (API Gateway, port 8080) + │ Spring Cloud │ JWT auth, routing + └───┬──────┬──────┘ + │ │ + ┌───▼──────────┐ + │ hzhub-system │ CRM 模块归属服务 + │ 8083 │ (Users, Roles, CRM) + └─────────────┘ +``` + +### Gateway 路由配置 + +在 `hzhub-gateway/src/main/resources/application.yml` 中添加 CRM 路由: + +```yaml +spring: + cloud: + gateway: + routes: + # CRM 路由(新增) + - id: hzhub-crm + uri: lb://hzhub-system + predicates: + - Path=/crm/** + filters: + - StripPrefix=1 # 去除 /crm 前缀 +``` + +**注意:** CRM 路由需要添加到现有路由列表中,位置在 `hzhub-system` 路由之后。 + +--- + +## 2.2 技术栈适配 + +| 层级 | HZHub 技术栈 | 说明 | +|---|---|---| +| 前端Web | Vue3 + Vben Admin + Ant Design Vue | 已在 hzhub-admin 中部署 | +| 移动端 | 企业微信H5 + UniApp | 集成企业微信侧边栏 | +| 后端 | Spring Boot 3.5.8 + MyBatis-Plus | hzhub-system 服务 | +| 数据库 | MySQL 8.0 | 已部署 | +| 缓存 | Redis 7 | 已部署,用于缓存与限流 | +| 向量数据库 | Weaviate 1.25.0 | 已部署,用于知识检索 | +| AI服务 | LangChain4j + LangGraph4j | hzhub-ai 服务集成 | +| 文件存储 | MinIO | 已部署,用于合同/录音文件存储 | +| 工作流 | warm-flow 1.8.2 | 已集成在 hzhub-system | +| 权限 | Sa-Token | 已集成,JWT-based | +| BI | FineBI / Superset | 可选(暂不集成) | + +--- + +# 3. 数据库设计规范(HZHub 适配) + +## 3.1 通用字段规范 + +所有CRM业务表继承 `TenantEntity`,包含以下字段: + +| 字段 | 类型 | 注解 | 说明 | +|---|---|---|---| +| tenant_id | varchar(20) | 无注解(继承) | 租户ID(多租户) | +| create_dept | bigint | @TableField(fill = INSERT) | 创建部门 | +| create_by | bigint | @TableField(fill = INSERT) | 创建人 | +| create_time | datetime | @TableField(fill = INSERT) | 创建时间 | +| update_by | bigint | @TableField(fill = INSERT_UPDATE) | 更新人 | +| update_time | datetime | @TableField(fill = INSERT_UPDATE) | 更新时间 | + +**注意:** 不需要手动添加 `id` 字段,使用 MyBatis-Plus 的 `@TableId` 注解。 + +--- + +## 3.2 Entity 实体类规范 + +### 基类继承 + +```java +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("crm_lead") +public class CrmLead extends TenantEntity { + + @TableId(value = "lead_id", type = IdType.ASSIGN_ID) + private Long leadId; + + // 业务字段... + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer delFlag; +} +``` + +### Bo 业务对象 + +```java +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CrmLead.class) +public class CrmLeadBo extends BaseEntity { + + private Long leadId; + + // 业务字段... +} +``` + +### Vo 视图对象 + +```java +@Data +@AutoMapper(target = CrmLead.class) +public class CrmLeadVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long leadId; + + /** + * 租户ID(翻译) + */ + @Translation(type = TransConstant.TENANT_ID_TO_NAME, mapper = "tenantId") + private String tenantId; + + /** + * 创建人(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") + private String createByName; + + // 业务字段... +} +``` + +--- + +## 3.3 Controller 接口规范 + +```java +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/crm/lead") +public class CrmLeadController extends BaseController { + + private final ICrmLeadService leadService; + + /** + * 获取线索列表 + */ + @SaCheckPermission("crm:lead:list") + @GetMapping("/list") + public TableDataInfo list(CrmLeadBo lead, PageQuery pageQuery) { + return leadService.selectPageLeadList(lead, pageQuery); + } + + /** + * 新增线索 + */ + @Log(title = "线索管理", businessType = BusinessType.INSERT) + @SaCheckPermission("crm:lead:add") + @RepeatSubmit() + @PostMapping() + public R add(@Validated @RequestBody CrmLeadBo lead) { + return R.ok(leadService.insertLead(lead)); + } +} +``` + +--- + +# 4. 线索中心模块设计 + +## 4.1 模块目标 + +用于管理潜在经销商,支持: + +- 多渠道线索接入 +- AI意向识别(LangChain4j) +- AI风险分析 +- 自动分配销售 +- 商机转化 + +--- + +## 4.2 数据表设计 + +### 4.2.1 crm_lead(线索表) + +| 字段 | 类型 | 注解 | 说明 | +|---|---|---|---| +| lead_id | bigint | @TableId(ASSIGN_ID) | 主键 | +| company_name | varchar(200) | - | 公司名称 | +| contact_name | varchar(100) | - | 联系人 | +| mobile | varchar(50) | - | 手机号 | +| wechat | varchar(100) | - | 微信号 | +| province | varchar(50) | - | 省 | +| city | varchar(50) | - | 市 | +| region_id | bigint | - | 区域ID(关联 sys_dept) | +| source_type | varchar(50) | - | 来源类型(字典:crm_lead_source) | +| activity_name | varchar(100) | - | 活动名称 | +| referrer_name | varchar(100) | - | 推荐人 | +| industry | varchar(100) | - | 行业(字典:crm_industry) | +| company_scale | varchar(100) | - | 公司规模(字典:crm_scale) | +| store_count | int | - | 门店数 | +| intent_level | varchar(20) | - | AI意向等级(字典:crm_intent_level) | +| ai_score | decimal(5,2) | - | AI评分 | +| risk_level | varchar(20) | - | 风险等级(字典:crm_risk_level) | +| owner_user_id | bigint | - | 负责人(关联 sys_user) | +| lead_status | varchar(50) | - | 状态(字典:crm_lead_status) | +| converted_dealer_id | bigint | - | 转化经销商ID | +| del_flag | int | @TableLogic | 删除标志 | + +**继承字段:** tenant_id, create_dept, create_by, create_time, update_by, update_time + +--- + +### 4.2.2 crm_lead_follow(线索跟进记录) + +| 字段 | 类型 | 说明 | +|---|---|---| +| follow_id | bigint | 主键 | +| lead_id | bigint | 线索ID | +| follow_type | varchar(50) | 跟进方式(字典:crm_follow_type) | +| content | text | 跟进内容 | +| ai_summary | text | AI摘要 | +| next_follow_time | datetime | 下次跟进时间 | +| follow_user_id | bigint | 跟进人(关联 sys_user) | +| del_flag | int | 删除标志 | + +--- + +## 4.3 接口设计 + +### 4.3.1 创建线索 + +**API:** `POST /crm/lead` + +**请求参数:** + +```json +{ + "companyName": "XX贸易有限公司", + "contactName": "张三", + "mobile": "13800000000", + "wechat": "zhangsan", + "province": "广东省", + "city": "深圳市", + "industry": "食品", + "storeCount": 20 +} +``` + +**业务逻辑:** + +1. 校验手机号是否重复(同租户内) +2. 调用 LangChain4j AI服务分析意向等级(`hzhub-ai:6039/ai/analyze/intent`) +3. 自动生成AI评分 +4. 根据区域规则(sys_dept)分配销售(owner_user_id) +5. 创建自动跟进任务(warm-flow) + +--- + +### 4.3.2 线索转经销商 + +**API:** `POST /crm/lead/convert` + +**请求参数:** + +```json +{ + "leadId": 12345, + "dealerName": "XX贸易", + "dealerCode": "DL20260001" +} +``` + +**逻辑:** + +1. 创建 `crm_dealer` 数据 +2. 迁移历史跟进记录到 `crm_dealer_follow` +3. 创建初始商机(`crm_opportunity`) +4. 更新线索状态为"已转化" +5. 触发 warm-flow 工作流(经销商审批) + +--- + +## 4.4 前端页面设计(Vben Admin) + +### 4.4.1 线索列表页 + +**路径:** `apps/web-antd/src/views/crm/lead/index.vue` + +**页面布局:** + +```text +------------------------------------------------- +顶部:搜索栏 + 高级筛选(Ant Design Form) +------------------------------------------------- +左侧:区域树(Ant Design Tree) +右侧:线索列表(Ant Design Table) +------------------------------------------------- +底部:分页(Ant Design Pagination) +``` + +**筛选项:** + +- 区域(区域树选择) +- 来源(字典下拉:crm_lead_source) +- AI意向等级(字典下拉:crm_intent_level) +- 风险等级(字典下拉:crm_risk_level) +- 销售负责人(用户选择器) +- 创建时间(日期范围) + +**表格字段:** + +| 字段 | Ant Design 组件 | +|---|---| +| 公司名称 | Tag(高意向标红) | +| 联系人 | - | +| 手机 | 脱敏展示 | +| 区域 | 翻译展示 | +| AI评分 | Progress(进度条) | +| 意向等级 | Badge(高/中/低) | +| 风险等级 | Tag(高风险标红) | +| 当前负责人 | Avatar(头像) | +| 跟进状态 | Badge | +| 下次跟进时间 | DatePicker | + +**操作按钮:** + +- 查看(Drawer 侧边栏) +- 分配(Modal 弹窗) +- 跟进(Drawer) +- 转经销商(Modal) +- 作废(Popconfirm 确认) + +--- + +### 4.4.2 AI分析侧边栏 + +右侧固定展示(Ant Design Drawer): + +```text +┌────────────────────────┐ +│ AI意向评分(Progress) │ +│ AI风险提示(Alert) │ +│ 推荐动作(Steps) │ +│ 推荐销售话术(Collapse)│ +│ 推荐拜访时间(DatePicker)│ +└────────────────────────┘ +``` + +**AI服务调用:** 通过 Gateway 调用 `hzhub-ai:6039/ai/chat/analyze` + +--- + +# 5. 经销商中心模块设计 + +## 5.1 数据表设计 + +### 5.1.1 crm_dealer(经销商表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| dealer_id | bigint | 主键 | +| dealer_name | varchar(200) | 经销商名称 | +| dealer_code | varchar(100) | 编码 | +| contact_name | varchar(100) | 联系人 | +| mobile | varchar(50) | 手机 | +| province | varchar(50) | 省 | +| city | varchar(50) | 市 | +| level | varchar(50) | 等级(字典:crm_dealer_level) | +| lifecycle | varchar(50) | 生命周期(字典:crm_lifecycle) | +| signed_at | datetime | 签约时间 | +| store_count | int | 门店数 | +| team_size | int | 团队规模 | +| total_order_amount | decimal(18,2) | 累计订单金额 | +| total_payment_amount | decimal(18,2) | 累计回款金额 | +| activity_score | decimal(5,2) | 活跃评分 | +| risk_score | decimal(5,2) | 飆险评分 | +| owner_user_id | bigint | 负责人(关联 sys_user) | +| del_flag | int | 删除标志 | + +--- + +### 5.1.2 crm_dealer_tag(经销商标签表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| tag_id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| tag_name | varchar(100) | 标签名称 | +| tag_type | varchar(50) | 标签类型(字典:crm_tag_type) | +| score | decimal(5,2) | 标签评分 | +| del_flag | int | 删除标志 | + +--- + +## 5.2 接口设计 + +### 5.2.1 获取经销商画像 + +**API:** `GET /crm/dealer/{dealerId}/profile` + +**返回内容:** + +```json +{ + "code": 200, + "data": { + "dealerName": "XX贸易", + "activityLevel": "高活跃", + "riskLevel": "低风险", + "growthTrend": "高增长", + "lastVisitTime": "2026-05-10", + "nextVisitTime": "2026-05-20", + "aiRecommendActions": [ + "建议本周拜访", + "关注库存周转" + ] + } +} +``` + +--- + +## 5.3 前端页面设计 + +### 5.3.1 经销商详情页 + +**路径:** `apps/web-antd/src/views/crm/dealer/detail.vue` + +**页面布局:** + +```text +顶部:基础信息卡片(Ant Design Card) +------------------------------------------------- +Tab页(Ant Design Tabs): +1. 基础档案(Descriptions) +2. 商机(Table + Kanban) +3. 拜访记录(Timeline) +4. 订单(Table) +5. 回款(Table) +6. AI画像(Progress + Chart) +7. 会话记录(Timeline) +------------------------------------------------- +右侧:AI经营分析面板(Affix 固定) +``` + +**AI经营分析面板:** + +使用 Ant Design Charts 展示: + +- 活跃度趋势(LineChart) +- 近90天订单趋势(AreaChart) +- 流失风险(Gauge) +- 推荐动作(Steps) +- 竞品风险(Alert) + +--- + +# 6. 拜访管理模块设计 + +## 6.1 数据表设计 + +### 6.1.1 crm_visit_plan(拜访计划表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| plan_id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| visit_type | varchar(50) | 拜访类型(字典:crm_visit_type) | +| planned_time | datetime | 计划时间 | +| visit_user_id | bigint | 销售人员(关联 sys_user) | +| status | varchar(50) | 状态(字典:crm_visit_plan_status) | +| del_flag | int | 删除标志 | + +--- + +### 6.1.2 crm_visit_record(拜访记录表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| record_id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| plan_id | bigint | 拜访计划ID | +| visit_time | datetime | 拜访时间 | +| participants | varchar(500) | 参与人员 | +| voice_file_url | varchar(500) | 录音文件(MinIO) | +| ai_summary | text | AI摘要 | +| ai_requirements | text | AI提取需求 | +| ai_risk | text | AI风险 | +| next_action | text | 下一步动作 | +| latitude | decimal(10,6) | 纬度 | +| longitude | decimal(10,6) | 经度 | +| sign_photo_url | varchar(500) | 签到照片(MinIO) | +| del_flag | int | 删除标志 | + +--- + +## 6.2 AI语音处理流程 + +```text +语音上传(MinIO) +-> 调用 hzhub-ai:6039/ai/voice/transcribe +-> LangChain4j ASR识别 +-> NLP结构化提取 +-> AI摘要生成 +-> 风险分析 +-> 推荐下一步动作 +-> 写入 CRM +``` + +**技术栈:** LangChain4j + OpenAI Whisper / 本地 ASR 模型 + +--- + +## 6.3 移动端页面设计 + +### 6.3.1 企业微信H5拜访页面 + +**路径:** `hzhub-portal-employee/src/pages/crm/visit.vue`(员工门户) + +**页面模块(Element Plus):** + +- 地图签到(腾讯地图) +- 语音录入按钮(Recorder) +- 拍照上传(Upload) +- AI实时摘要(Card) +- 下一步动作建议(Steps) + +**AI辅助区域:** + +自动生成(调用 hzhub-ai): + +- 客户关注点(Tag) +- 异议问题(Alert) +- 竞品信息(Collapse) +- 推荐招商话术(Collapse) + +--- + +# 7. 商机管理模块设计 + +## 7.1 数据表设计 + +### 7.1.1 crm_opportunity(商机表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| opportunity_id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| opportunity_name | varchar(200) | 商机名称 | +| stage | varchar(50) | 商机阶段(字典:crm_opportunity_stage) | +| estimated_amount | decimal(18,2) | 预计金额 | +| success_rate | decimal(5,2) | 成交概率 | +| expected_sign_date | date | 预计签约时间 | +| owner_user_id | bigint | 销售负责人(关联 sys_user) | +| ai_next_stage | varchar(50) | AI建议阶段 | +| ai_probability | decimal(5,2) | AI成交预测 | +| del_flag | int | 删除标志 | + +--- + +### 7.1.2 crm_opportunity_stage_log(阶段日志表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| log_id | bigint | 主键 | +| opportunity_id | bigint | 商机ID | +| old_stage | varchar(50) | 原阶段 | +| new_stage | varchar(50) | 新阶段 | +| change_reason | text | 变更原因 | +| changed_by | bigint | 变更人(关联 sys_user) | +| change_time | datetime | 变更时间 | + +--- + +## 7.2 商机阶段规则 + +| 阶段(字典:crm_opportunity_stage) | 条件 | +|---|---| +| 初步接触 | 创建商机 | +| 需求沟通 | 已记录需求 | +| 招商政策沟通 | 已发送政策 | +| 样品测试 | 已寄样 | +| 商务谈判 | 已讨论返点 | +| 签约中 | 已提交合同 | +| 已签约 | 合同生效 | + +--- + +## 7.3 AI自动推进逻辑 + +**规则示例:** + +```text +如果AI识别(LangChain4j分析会话记录): +- 已询价 +- 已谈返点 +- 已谈库存 + +则自动建议推进至"商务谈判"阶段。 +``` + +**实现方式:** 定时任务调用 `hzhub-ai:6039/ai/opportunity/predict` + +--- + +## 7.4 前端页面设计 + +### 7.4.1 商机看板页 + +**路径:** `apps/web-antd/src/views/crm/opportunity/kanban.vue` + +**视图模式(Ant Design Segmented):** + +- Kanban阶段看板 +- 列表模式 +- 销售漏斗模式 + +**看板列(Ant Design Card):** + +```text +初步接触 +需求沟通 +招商政策沟通 +样品测试 +商务谈判 +签约中 +已签约 +``` + +**卡片展示:** + +- 经销商名称 +- 金额(Statistic) +- AI成交概率(Progress) +- 最近跟进时间 +- 风险提示(Tag) + +--- + +# 8. 合同管理模块设计 + +## 8.1 数据表设计 + +### 8.1.1 crm_contract(合同表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| contract_id | bigint | 主键 | +| contract_no | varchar(100) | 合同编号 | +| dealer_id | bigint | 经销商ID | +| opportunity_id | bigint | 商机ID | +| contract_amount | decimal(18,2) | 合同金额 | +| sign_date | date | 签约日期 | +| expire_date | date | 到期日期 | +| contract_status | varchar(50) | 状态(字典:crm_contract_status) | +| approval_status | varchar(50) | 审批状态(字典:crm_approval_status) | +| sign_file_url | varchar(500) | 合同文件(MinIO) | +| ai_risk_summary | text | AI风险摘要 | +| del_flag | int | 删除标志 | + +--- + +## 8.2 AI合同分析 + +AI识别(LangChain4j): + +- 高风险条款 +- 超标准返点 +- 区域冲突 +- 超长账期 + +**实现:** 调用 `hzhub-ai:6039/ai/contract/analyze` + +--- + +## 8.3 审批流设计(warm-flow) + +```text +销售提交 +-> 区域经理审批(warm-flow节点) +-> 财务审批 +-> 法务审批 +-> 总部审批 +-> 电子签章(可选) +``` + +**实现:** + +1. 在 `hzhub-system` 中定义 warm-flow 流程定义(`workflow_definition`) +2. 合同提交触发流程实例(`workflow_instance`) +3. 审批节点与 `sys_role` 关联 + +--- + +# 9. 订单管理模块设计 + +## 9.1 数据表设计 + +### 9.1.1 crm_order(订单表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| order_id | bigint | 主键 | +| order_no | varchar(100) | 订单编号 | +| dealer_id | bigint | 经销商ID | +| contract_id | bigint | 合同ID | +| order_amount | decimal(18,2) | 订单金额 | +| order_status | varchar(50) | 订单状态(字典:crm_order_status) | +| shipment_status | varchar(50) | 发货状态(字典:crm_shipment_status) | +| payment_status | varchar(50) | 回款状态(字典:crm_payment_status) | +| logistics_status | varchar(50) | 物流状态(字典:crm_logistics_status) | +| erp_sync_status | varchar(50) | ERP同步状态(字典:crm_erp_sync_status) | +| del_flag | int | 删除标志 | + +--- + +## 9.2 ERP同步逻辑 + +### 同步内容 + +- 订单数据(从 `hzhub-erp:8082` 拉取) +- 发货数据 +- 库存数据 +- 回款数据 +- 物流数据 + +### 同步方式 + +1. **定时拉取:** 使用 XXL-Job(hzhub-extend 中已集成)定时任务 +2. **Webhook回调:** ERP 主动推送(可选) +3. **MQ异步同步:** 暂不集成(可选) + +**实现:** + +```java +@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点 +public void syncErpOrders() { + // 调用 hzhub-erp:8082/erp/order/sync + // 使用 dynamic-datasource 切换到 erp 数据源 +} +``` + +--- + +## 9.3 前端页面设计 + +### 9.3.1 订单中心 + +**路径:** `apps/web-antd/src/views/crm/order/index.vue` + +**页面布局:** + +```text +顶部:订单筛选(Form) +------------------------------------------------- +中部:订单表格(Table) +------------------------------------------------- +右侧:订单风险分析(Affix) +``` + +**风险分析:** + +- 延迟发货风险(Alert) +- 超账期风险(Alert) +- 异常退货风险(Alert) + +--- + +# 10. 回款管理模块设计 + +## 10.1 数据表设计 + +### 10.1.1 crm_payment(回款表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| payment_id | bigint | 主键 | +| dealer_id | bigint | 经销商ID | +| order_id | bigint | 订单ID | +| payment_amount | decimal(18,2) | 回款金额 | +| payment_date | date | 回款日期 | +| receivable_due_date | date | 应收截止日 | +| overdue_days | int | 超期天数 | +| payment_status | varchar(50) | 状态(字典:crm_payment_status) | +| del_flag | int | 删除标志 | + +--- + +## 10.2 AI预警规则 + +| 条件 | 预警 | Redis 缓存键 | +|---|---|---| +| 超过30天未回款 | 高风险 | `crm:payment:risk:{dealer_id}` | +| 连续2次延迟 | 中风险 | - | +| 回款金额下降 | 流失风险 | - | + +**实现:** 定时任务扫描 `crm_payment` 表,更新 Redis 缓存,前端实时查询。 + +--- + +# 11. 企业微信协同模块设计 + +## 11.1 集成能力 + +### 企业微信侧边栏 + +**展示内容:** + +- 经销商画像(从 `crm_dealer` 查询) +- 最近订单(从 `crm_order` 查询) +- 最近拜访(从 `crm_visit_record` 查询) +- 商机阶段(从 `crm_opportunity` 查询) +- AI风险(Redis 缓存) + +**实现:** + +1. 企业微信应用配置(应用ID、Secret) +2. 后端接口:`GET /crm/wecom/sidebar/{externalUserId}` +3. 前端嵌入企业微信侧边栏(H5页面) + +--- + +### 会话存档同步 + +**同步内容:** + +- 文本 +- 图片 +- 文件 +- 语音 + +**数据表:** `crm_wecom_chat_record` + +| 字段 | 类型 | 说明 | +|---|---|---| +| record_id | bigint | 主键 | +| external_user_id | varchar(100) | 外部联系人 | +| dealer_id | bigint | 经销商ID | +| sender_id | bigint | 发送人(关联 sys_user) | +| message_type | varchar(50) | 消息类型 | +| message_content | text | 内容 | +| send_time | datetime | 发送时间 | +| ai_analysis_result | text | AI分析结果 | +| del_flag | int | 删除标志 | + +--- + +## 11.2 AI会话分析 + +**识别内容:** + +- 采购意向 +- 价格异议 +- 投诉风险 +- 竞品信息 + +**实现:** 调用 `hzhub-ai:6039/ai/chat/analyze` + +--- + +# 12. AI分析中心设计 + +## 12.1 AI标签体系 + +| 标签类型(字典:crm_tag_type) | 示例 | +|---|---| +| 活跃标签 | 高频沟通 | +| 风险标签 | 流失风险 | +| 经营标签 | 高增长 | +| 敏感标签 | 价格敏感 | + +--- + +## 12.2 AI预测模型 + +| 模型 | 输入 | 调用服务 | +|---|---|---| +| 成交预测 | 商机数据 | `hzhub-ai:6039/ai/opportunity/predict` | +| 回款预测 | 订单与历史回款 | `hzhub-ai:6039/ai/payment/predict` | +| 流失预测 | 活跃度与订单 | `hzhub-ai:6039/ai/churn/predict` | +| 补货预测 | 销量趋势 | `hzhub-ai:6039/ai/replenish/predict` | + +**向量检索:** 使用 Weaviate 存储历史案例,LangChain4j 进行相似度检索。 + +--- + +# 13. 自动化工作流设计 + +## 13.1 自动提醒规则 + +| 规则 | 动作 | 实现方式 | +|---|---|---| +| 7天未跟进 | 创建跟进提醒 | XXL-Job 定时任务 + Redis 缓存 | +| 30天未下单 | 创建经营风险提醒 | - | +| 合同即将到期 | 通知销售与经理 | - | +| 回款超期 | 通知财务与销售 | - | + +--- + +## 13.2 自动任务规则 + +| 条件 | 自动任务 | warm-flow 流程 | +|---|---|---| +| 高意向线索 | 自动创建拜访 | `crm_auto_visit_flow` | +| 商机推进 | 自动创建回访 | `crm_auto_follow_flow` | +| 合同签约 | 自动创建首单跟进 | `crm_auto_order_flow` | + +--- + +# 14. BI分析设计(可选) + +## 14.1 BI指标体系 + +### 销售指标 + +- 销售额 +- 回款率 +- 转化率 +- 客单价 +- 区域增长率 + +### AI经营指标 + +- 高风险经销商数 +- 高潜经销商数 +- AI成交预测准确率 +- AI流失预测准确率 + +--- + +## 14.2 仪表盘设计(Ant Design Charts) + +### 总部仪表盘 + +**路径:** `apps/web-antd/src/views/crm/dashboard/index.vue` + +**展示内容:** + +- 全国销售地图(ChinaMap) +- 区域排名(BarChart) +- 经销商增长趋势(LineChart) +- AI风险预警(Alert) +- 商机漏斗(FunnelChart) + +--- + +### 销售个人仪表盘 + +**展示内容:** + +- 今日待跟进(Statistic) +- 今日拜访(Statistic) +- 本月成交(Statistic) +- AI推荐客户(List) +- AI销售建议(Collapse) + +--- + +# 15. 权限与组织设计(Sa-Token) + +## 15.1 RBAC模型 + +### 数据权限层级 + +```text +总部(sys_dept: root) +-> 大区(sys_dept: level 1) +-> 区域(sys_dept: level 2) +-> 城市(sys_dept: level 3) +-> 销售(sys_user) +``` + +**实现:** 使用 `sys_dept` 表的树形结构,通过 `DataPermissionHelper` 实现数据隔离。 + +--- + +## 15.2 权限控制点 + +| 模块 | 权限标识 | Sa-Token 注解 | +|---|---|---| +| 线索 | crm:lead:list, crm:lead:add, crm:lead:edit, crm:lead:remove | @SaCheckPermission | +| 经销商 | crm:dealer:list, crm:dealer:edit | - | +| 合同 | crm:contract:approve, crm:contract:amount-edit | - | +| 回款 | crm:payment:list, crm:payment:verify | - | +| BI | crm:bi:view(区域数据隔离) | - | + +**菜单配置:** 在 `sys_menu` 表中添加 CRM 模块菜单树。 + +--- + +# 16. 移动端设计 + +## 16.1 企业微信H5页面 + +**归属项目:** `hzhub-portal-employee`(员工门户,Element Plus) + +**必须支持:** + +- 快速拜访 +- AI语音录入 +- 地图签到(腾讯地图) +- 拍照上传(MinIO) +- 快速订单查询 +- AI风险提醒 + +--- + +## 16.2 移动端交互要求 + +| 要求 | 说明 | 技术实现 | +|---|---|---| +| 单手操作 | 核心按钮底部固定 | Element Plus Affix | +| 弱网容错 | 本地缓存 | IndexedDB / LocalStorage | +| 快速录入 | 支持语音 | Recorder + LangChain4j ASR | +| 离线能力 | 支持草稿 | IndexedDB | + +--- + +# 17. 性能与安全设计 + +## 17.1 性能目标 + +| 指标 | 目标 | 实现方式 | +|---|---|---| +| 页面响应 | <2秒 | Redis 缓存 + 分页查询 | +| AI分析响应 | <5秒 | LangChain4j 异步调用 | +| ERP同步延迟 | <1分钟 | XXL-Job 定时任务 | +| 并发支持 | 5000用户 | Gateway 限流 + Redis 缓存 | + +--- + +## 17.2 安全设计(HZHub 已有) + +### 安全要求 + +- HTTPS加密(Gateway 配置) +- Sa-Token JWT鉴权(已集成) +- 数据权限隔离(TenantEntity + DataPermissionHelper) +- 敏感字段脱敏(@Sensitive 注解) +- 操作日志审计(@Log 注解) +- 企业微信身份校验(wecom_userid) + +--- + +# 18. AI能力优先级实施建议 + +## P0(第一阶段必须上线) + +- 企业微信集成(侧边栏) +- 客户画像(基础字段) +- AI语音拜访(LangChain4j ASR) +- 会话分析(LangChain4j NLP) +- 订单查询(ERP同步) + +--- + +## P1(第二阶段) + +- AI标签(LangChain4j + Weaviate) +- AI摘要(LangChain4j) +- AI意向识别 +- 自动提醒(XXL-Job) + +--- + +## P2(第三阶段) + +- AI Copilot(LangGraph4j) +- AI销售建议 +- AI预测模型 +- AI自动推进商机 + +--- + +# 19. 项目实施计划 + +## 第一阶段(1-2个月) + +**建设内容:** + +- 基础CRM(线索、经销商、商机) +- 企业微信侧边栏集成 +- 拜访管理(移动端) +- warm-flow 审批流程 +- 订单查询(ERP同步) + +**技术栈:** + +- 后端:Spring Boot + MyBatis-Plus + Sa-Token +- 前端:Vben Admin + Ant Design Vue +- 移动端:Element Plus + 企业微信H5 +- 工作流:warm-flow + +--- + +## 第二阶段(2-3个月) + +**建设内容:** + +- AI标签体系(Weaviate) +- AI会话分析(LangChain4j) +- AI语音拜访(ASR + NLP) +- 自动化工作流(XXL-Job) +- 合同管理 + +**技术栈:** + +- AI:LangChain4j + hzhub-ai 服务 +- 向量:Weaviate +- 定时任务:XXL-Job + +--- + +## 第三阶段(3-4个月) + +**建设内容:** + +- AI预测模型(成交预测、流失预测) +- AI Copilot(LangGraph4j) +- AI经营分析面板 +- 智能决策支持 +- BI仪表盘 + +**技术栈:** + +- AI:LangGraph4j + hzhub-ai 服务 +- 图表:Ant Design Charts + +--- + +# 20. 数据字典定义 + +## 20.1 线索相关字典 + +### crm_lead_source(线索来源) + +| 字典值 | 字典标签 | +|---|---| +| activity | 活动 | +| referral | 推荐 | +| website | 网站 | +| exhibition | 展会 | + +### crm_lead_status(线索状态) + +| 字典值 | 字典标签 | +|---|---| +| new | 新线索 | +| following | 跟进中 | +| converted | 已转化 | +| invalid | 已作废 | + +### crm_intent_level(AI意向等级) + +| 字典值 | 字典标签 | +|---|---| +| high | 高意向 | +| medium | 中意向 | +| low | 低意向 | + +--- + +## 20.2 经销商相关字典 + +### crm_dealer_level(经销商等级) + +| 字典值 | 字典标签 | +|---|---| +| A | A级经销商 | +| B | B级经销商 | +| C | C级经销商 | + +### crm_lifecycle(生命周期) + +| 字典值 | 字典标签 | +|---|---| +| active | 活跃期 | +| stable | 稳定期 | +| decline | 衰退期 | +| churn | 流失期 | + +--- + +## 20.3 商机相关字典 + +### crm_opportunity_stage(商机阶段) + +| 字典值 | 字典标签 | +|---|---| +| initial_contact | 初步接触 | +| requirement | 需求沟通 | +| policy | 招商政策沟通 | +| sample | 样品测试 | +| negotiation | 商务谈判 | +| signing | 签约中 | +| signed | 已签约 | + +--- + +## 20.4 审批状态字典 + +### crm_approval_status(审批状态) + +| 字典值 | 字典标签 | +|---|---| +| pending | 待审批 | +| approved | 已审批 | +| rejected | 已拒绝 | + +--- + +# 21. 最终产品定位 + +系统最终定位: + +# "AI驱动的渠道销售经营平台" + +区别于传统CRM: + +传统CRM:记录客户 + +本系统: + +- AI驱动增长(LangChain4j + Weaviate) +- AI驱动招商(意向识别 + 风险分析) +- AI驱动销售动作(语音拜访 + 会话分析) +- AI驱动经营分析(预测模型 + Copilot) +- AI驱动渠道运营(自动化工作流) + +--- + +# 22. 与 HZHub 项目的集成要点 + +## 22.1 Gateway 路由配置 + +**位置:** `hzhub-gateway/src/main/resources/application.yml` + +```yaml +spring: + cloud: + gateway: + routes: + # CRM 路由(添加到现有路由列表) + - id: hzhub-crm + uri: lb://hzhub-system + predicates: + - Path=/crm/** + filters: + - StripPrefix=1 +``` + +--- + +## 22.2 hzhub-system 依赖 + +**位置:** `hzhub-system/pom.xml` + +```xml + + + + org.hzhub + hzhub-common-core + + + + + org.hzhub + hzhub-common-chat + ${project.version} + + +``` + +--- + +## 22.3 前端路由配置 + +**位置:** `hzhub-admin/apps/web-antd/src/router/routes/modules/crm.ts` + +```typescript +export default { + path: '/crm', + name: 'CRM', + component: Layout, + redirect: '/crm/dashboard', + meta: { + title: 'CRM管理', + icon: 'ant-design:team-outlined', + }, + children: [ + { + path: 'dashboard', + name: 'CrmDashboard', + component: () => import('@/views/crm/dashboard/index.vue'), + meta: { title: 'CRM仪表盘' }, + }, + { + path: 'lead', + name: 'CrmLead', + component: () => import('@/views/crm/lead/index.vue'), + meta: { title: '线索管理' }, + }, + // ... + ], +}; +``` + +--- + +## 22.4 菜单配置(sys_menu) + +在 `sys_menu` 表中添加 CRM 模块菜单树: + +```sql +-- CRM 模块根菜单 +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time) +VALUES (5000, 'CRM管理', 0, 5, '/crm', NULL, 'M', '0', '0', '', 'ant-design:team-outlined', 1, NOW()); + +-- 线索管理菜单 +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time) +VALUES (5001, '线索管理', 5000, 1, 'lead', 'crm/lead/index', 'C', '0', '0', 'crm:lead:list', 'ant-design:user-outlined', 1, NOW()); + +-- ... 其他菜单 +``` + +--- + +# 23. 开发规范建议 + +## 23.1 包结构 + +``` +hzhub-system/src/main/java/org/hzhub/ +├── crm/ +│ ├── controller/ # CRM 控制器 +│ │ ├── CrmLeadController.java +│ │ ├── CrmDealerController.java +│ │ └── ... +│ ├── domain/ # 实体类 +│ │ ├── CrmLead.java +│ │ ├── bo/ +│ │ │ ├── CrmLeadBo.java +│ │ └── vo/ +│ │ │ ├── CrmLeadVo.java +│ ├── service/ # 服务接口 +│ │ ├── ICrmLeadService.java +│ │ └── impl/ +│ │ │ ├── CrmLeadServiceImpl.java +│ └── mapper/ # MyBatis Mapper +│ │ ├── CrmLeadMapper.java +``` + +--- + +## 23.2 前端目录结构 + +``` +hzhub-admin/apps/web-antd/src/ +├── api/ +│ └── crm/ +│ │ ├── lead.ts # 线索 API +│ │ ├── dealer.ts # 经销商 API +│ │ └── types.ts # 类型定义 +├── views/ +│ └── crm/ +│ │ ├── dashboard/ # CRM 仪表盘 +│ │ ├── lead/ # 线索管理 +│ │ ├── dealer/ # 经销商管理 +│ │ ├── opportunity/ # 商机管理 +│ │ └── ... +``` + +--- + +# 24. 部署建议 + +## 24.1 Docker Compose 配置 + +CRM 模块不需要新增容器,使用现有 `hzhub-system` 容器。 + +**位置:** `hzhub-deploy/docker-compose.yml` + +```yaml +services: + # 现有服务(无需修改) + hzhub-system: + build: ../hzhub-system + container_name: hzhub-system + ports: + - "8083:8083" + environment: + - SPRING_PROFILES_ACTIVE=prod + depends_on: + - mysql + - redis + networks: + - hzhub-network +``` + +--- + +## 24.2 数据库初始化 + +**SQL 文件:** `hzhub-system/src/main/resources/db/crm_init.sql` + +包含: + +- CRM 表结构(crm_lead, crm_dealer, ...) +- 数据字典(crm_lead_source, crm_opportunity_stage, ...) +- 菜单配置(sys_menu) + +--- + +# 25. 后续优化方向 + +## 25.1 性能优化 + +- Redis 缓存经销商画像、AI评分 +- Weaviate 向量检索优化(相似案例推荐) +- MyBatis-Plus 分页优化(避免全表扫描) + +## 25.2 AI能力扩展 + +- LangGraph4j 集成(复杂 AI 工作流) +- 多模型支持(OpenAI、Claude、本地模型) +- AI Copilot 智能助手 + +## 25.3 移动端增强 + +- UniApp 跨平台支持(iOS/Android) +- 离线数据同步(IndexedDB) +- 企业微信深度集成 + +--- + +# 附录:技术栈对比表 + +| 功能模块 | V1 版本技术栈 | V2 版本(HZHub)技术栈 | 优势 | +|---|---|---|---| +| 前端框架 | Vue3 + Element Plus | Vben Admin + Ant Design Vue | 企业级UI,更丰富的组件 | +| 后端框架 | Spring Boot(通用) | Spring Boot 3.5.8 + MyBatis-Plus | 更强的ORM能力,多租户支持 | +| 权限框架 | JWT(自定义) | Sa-Token | 已集成,成熟稳定 | +| 工作流引擎 | Flowable | warm-flow 1.8.2 | 已集成,轻量级 | +| AI服务 | Python FastAPI | LangChain4j(Java) | 与后端同语言,更易集成 | +| 向量数据库 | 未指定 | Weaviate 1.25.0 | 已部署,用于知识检索 | +| 文件存储 | MinIO | MinIO(已部署) | 无需额外部署 | +| 数据权限 | 自定义实现 | TenantEntity + DataPermissionHelper | 已有实现,支持多租户 | + +--- + +# 总结 + +本文档将 CRM 销售自动化系统完全适配到 HZHub 项目架构中,充分利用项目已有的基础设施(Gateway、Sa-Token、warm-flow、MinIO、Weaviate、LangChain4j),避免重复开发,提高实施效率。 + +核心改动: +1. 服务归属:CRM 模块归属于 `hzhub-system` 服务 +2. API路径:统一使用 `/crm/**`,通过 Gateway 路由 +3. 数据模型:继承 `TenantEntity`,支持多租户与自动填充 +4. 前端UI:使用 Vben Admin + Ant Design Vue +5. AI集成:调用 `hzhub-ai` 服务的 LangChain4j API +6. 权限控制:使用 Sa-Token + 数据权限隔离 + +后续实施时,严格按照本文档执行,确保与 HZHub 项目架构的一致性。 \ No newline at end of file diff --git a/docs/CRM销售模块详细设计说明书V3.md b/docs/CRM销售模块详细设计说明书V3.md new file mode 100644 index 0000000..02f2a9c --- /dev/null +++ b/docs/CRM销售模块详细设计说明书V3.md @@ -0,0 +1,1201 @@ +# CRM销售自动化(渠道版)执行级详细设计说明书 V3 + +# 1. 文档说明 + +## 1.1 文档目标 + +本文档用于指导HZHub项目中CRM销售自动化(渠道版)系统的: + +- 数据库设计(适配 MySQL 8 + MyBatis-Plus) +- 后端接口开发(适配 hzhub-system 服务) +- 前端页面开发(适配 **hzhub-portal-employee 员工门户**) +- AI能力集成(适配 LangChain4j + Weaviate) +- 企业微信集成 +- 自动化流程配置(适配 warm-flow) +- 权限与组织体系建设(适配 Sa-Token) + +本文档属于"开发执行版设计文档 V3",针对**员工门户(hzhub-portal-employee)**进行定制化适配。 + +--- + +## 1.2 与 V1/V2 版本的主要差异 + +| 方面 | V1 版本 | V2 版本 | V3 版本(员工门户适配) | +|---|---|---|---| +| 前端项目 | Vue3 + Element Plus(通用) | Vben Admin(管理后台) | **Element Plus(员工门户)** | +| 前端路径 | `/api/**` | `/crm/**`(管理后台) | **`/crm/**`(员工门户)** | +| 现有基础 | 无 | 无 | **已有CRM和经销商页面** | +| 数据来源 | 独立CRM表 | 独立CRM表 | **ERP客户数据 + CRM表** | +| 技术栈 | Python FastAPI | LangChain4j | **LangChain4j + Vue3 Composition API** | +| UI风格 | 未指定 | Vben Admin 企业级 | **Element Plus 卡片式** | +| 用户角色 | 通用销售人员 | 管理员 + 销售 | **企业员工 + 销售人员** | + +--- + +## 1.3 员工门户现有功能 + +### 已实现页面 + +| 页面 | 路径 | 功能 | 数据来源 | +|---|---|---|---| +| 销售CRM | `/crm` | 商机管道视图 + 客户列表 | ERP Customer(动态API) | +| 经销商管理 | `/dealer` | 经销商卡片视图 + 筛选 | ERP Customer(动态API) | + +### 现有API + +```typescript +// ERP 客户数据(动态API) +GET /erp/dynamic/v1/customer/list // 客户列表 +GET /erp/dynamic/v1/customer/detail // 客户详情 +GET /erp/dynamic/v1/customer/sales-areas // 销区列表 +GET /erp/dynamic/v1/customer/brands // 品牌列表 +``` + +### 现有数据模型(CustomerVO) + +```typescript +interface CustomerVO { + customerCode: string; // 客户编码 + customerName: string; // 客户名称 + companyCode: string; // 公司编码 + companyName: string; // 公司名称 + brand: string; // 品牌 + brandName: string; // 品牌名称 + contactName: string; // 联系人 + salesAreaCode: string; // 销区编码 + salesAreaName: string; // 销区名称 + salesPersonCode: string; // 销售人员编码 + salesPersonName: string; // 销售人员姓名 + province: string; // 省 + city: string; // 市 + isStop: number; // 状态(0合作中,1停用) + // ... 其他字段 +} +``` + +--- + +# 2. 系统总体架构 + +## 2.1 HZHub 集成架构(员工门户) + +### 服务归属 + +CRM模块归属于 **hzhub-system** 服务(端口 8083),前端在 **hzhub-portal-employee** 实现。 + +``` +┌─────────────────────────────────────────┐ +│ hzhub-portal-employee (员工门户) │ +│ Vue3 + Element Plus + TypeScript │ +│ Port: 5137 │ +│ 已有: /crm (销售CRM) + /dealer (经销商) │ +└────────────┬────────────────────────────┘ + │ + ┌────────┴────────┐ + │ hzhub-gateway │ (API Gateway, port 8080) + │ Spring Cloud │ JWT auth, routing + └───┬──────┬──────┘ + │ │ + ┌───▼──────────┐ + │ hzhub-system │ CRM 模块归属服务 + │ 8083 │ (Users, Roles, CRM) + └─────────────┘ +``` + +### Gateway 路由配置 + +在 `hzhub-gateway/src/main/resources/application.yml` 中添加 CRM 路由: + +```yaml +spring: + cloud: + gateway: + routes: + # CRM 路由(新增) + - id: hzhub-crm + uri: lb://hzhub-system + predicates: + - Path=/crm/** + filters: + - StripPrefix=1 # 去除 /crm 前缀 +``` + +--- + +## 2.2 技术栈适配(员工门户) + +| 层级 | HZHub 技术栈 | 说明 | +|---|---|---| +| 前端Web | Vue3 + Element Plus + TypeScript | **hzhub-portal-employee 已部署** | +| 前端路由 | Vue Router | 静态路由配置(`staticRouter.ts`) | +| 状态管理 | Pinia + persistedstate | 已集成 | +| HTTP请求 | hook-fetch + SSE | 已集成 | +| 后端 | Spring Boot 3.5.8 + MyBatis-Plus | hzhub-system 服务 | +| 数据库 | MySQL 8.0 | 已部署 | +| ERP数据 | SQL Server 2008 R2 | **hzhub-erp 服务** | +| 缓存 | Redis 7 | 已部署 | +| 向量数据库 | Weaviate 1.25.0 | 已部署 | +| AI服务 | LangChain4j + LangGraph4j | hzhub-ai 服务集成 | +| 文件存储 | MinIO | 已部署 | +| 工作流 | warm-flow 1.8.2 | 已集成在 hzhub-system | +| 权限 | Sa-Token | 已集成,JWT-based | + +--- + +# 3. 数据库设计规范(HZHub 适配) + +## 3.1 通用字段规范 + +所有CRM业务表继承 `TenantEntity`,包含以下字段: + +| 字段 | 类型 | 注解 | 说明 | +|---|---|---|---| +| tenant_id | varchar(20) | 无注解(继承) | 租户ID(多租户) | +| create_dept | bigint | @TableField(fill = INSERT) | 创建部门 | +| create_by | bigint | @TableField(fill = INSERT) | 创建人 | +| create_time | datetime | @TableField(fill = INSERT) | 创建时间 | +| update_by | bigint | @TableField(fill = INSERT_UPDATE) | 更新人 | +| update_time | datetime | @TableField(fill = INSERT_UPDATE) | 更新时间 | + +**注意:** 不需要手动添加 `id` 字段,使用 MyBatis-Plus 的 `@TableId` 注解。 + +--- + +## 3.2 Entity 实体类规范 + +### 基类继承 + +```java +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("crm_lead") +public class CrmLead extends TenantEntity { + + @TableId(value = "lead_id", type = IdType.ASSIGN_ID) + private Long leadId; + + // 业务字段... + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer delFlag; +} +``` + +--- + +# 4. 线索中心模块设计(适配员工门户) + +## 4.1 模块目标 + +用于管理潜在经销商,支持: + +- 多渠道线索接入 +- **关联ERP客户数据**(customer_code) +- AI意向识别(LangChain4j) +- AI风险分析 +- 自动分配销售 +- 商机转化 + +--- + +## 4.2 数据表设计 + +### 4.2.1 crm_lead(线索表) + +| 字段 | 类型 | 注解 | 说明 | +|---|---|---|---| +| lead_id | bigint | @TableId(ASSIGN_ID) | 主键 | +| customer_code | varchar(100) | - | **ERP客户编码(关联)** | +| company_name | varchar(200) | - | 公司名称 | +| contact_name | varchar(100) | - | 联系人 | +| mobile | varchar(50) | - | 手机号 | +| wechat | varchar(100) | - | 微信号 | +| province | varchar(50) | - | 省 | +| city | varchar(50) | - | 市 | +| region_id | bigint | - | 区域ID(关联 sys_dept) | +| source_type | varchar(50) | - | 来源类型(字典:crm_lead_source) | +| activity_name | varchar(100) | - | 活动名称 | +| referrer_name | varchar(100) | - | 推荐人 | +| industry | varchar(100) | - | 行业(字典:crm_industry) | +| company_scale | varchar(100) | - | 公司规模(字典:crm_scale) | +| store_count | int | - | 门店数 | +| intent_level | varchar(20) | - | AI意向等级(字典:crm_intent_level) | +| ai_score | decimal(5,2) | - | AI评分 | +| risk_level | varchar(20) | - | 风险等级(字典:crm_risk_level) | +| owner_user_id | bigint | - | 负责人(关联 sys_user) | +| lead_status | varchar(50) | - | 状态(字典:crm_lead_status) | +| converted_dealer_id | bigint | - | 转化经销商ID | +| del_flag | int | @TableLogic | 删除标志 | + +**继承字段:** tenant_id, create_dept, create_by, create_time, update_by, update_time + +**关键设计:** +- `customer_code` 关联 ERP 客户数据(可选,支持同步) +- 如果 `customer_code` 存在,自动从 ERP 拉取基础信息 +- 线索转化时,创建 `crm_dealer` 并关联 `customer_code` + +--- + +### 4.2.2 crm_lead_follow(线索跟进记录) + +| 字段 | 类型 | 说明 | +|---|---|---| +| follow_id | bigint | 主键 | +| lead_id | bigint | 线索ID | +| follow_type | varchar(50) | 跟进方式(字典:crm_follow_type) | +| content | text | 跟进内容 | +| ai_summary | text | AI摘要 | +| next_follow_time | datetime | 下次跟进时间 | +| follow_user_id | bigint | 跟进人(关联 sys_user) | +| del_flag | int | 删除标志 | + +--- + +## 4.3 接口设计 + +### 4.3.1 创建线索 + +**API:** `POST /crm/lead` + +**请求参数:** + +```json +{ + "customerCode": "C001", // ERP客户编码(可选) + "companyName": "XX贸易有限公司", + "contactName": "张三", + "mobile": "13800000000", + "wechat": "zhangsan", + "province": "广东省", + "city": "深圳市", + "industry": "食品", + "storeCount": 20 +} +``` + +**业务逻辑:** + +1. 如果提供 `customer_code`,调用 `hzhub-erp:8082/erp/dynamic/v1/customer/detail` 拉取客户信息 +2. 自动填充客户基础信息(companyName, contactName, mobile 等) +3. 校验手机号是否重复(同租户内) +4. 调用 LangChain4j AI服务分析意向等级(`hzhub-ai:6039/ai/analyze/intent`) +5. 自动生成AI评分 +6. 根据区域规则(sys_dept)分配销售(owner_user_id) +7. 创建自动跟进任务(warm-flow) + +--- + +### 4.3.2 线索转经销商 + +**API:** `POST /crm/lead/convert` + +**请求参数:** + +```json +{ + "leadId": 12345, + "dealerName": "XX贸易", + "dealerCode": "DL20260001", + "customerCode": "C001" // ERP客户编码(可选) +} +``` + +**逻辑:** + +1. 创建 `crm_dealer` 数据 +2. 如果 `customer_code` 存在,同步 ERP 客户数据 +3. 迁移历史跟进记录到 `crm_dealer_follow` +4. 创建初始商机(`crm_opportunity`) +5. 更新线索状态为"已转化" +6. 触发 warm-flow 工作流(经销商审批) + +--- + +## 4.4 前端页面设计(Element Plus - 员工门户) + +### 4.4.1 扩展现有CRM页面 + +**路径:** `hzhub-portal-employee/src/pages/crm/index.vue`(**已存在,需扩展**) + +**现有功能:** +- 商机管道视图(Pipeline) +- 客户列表表格(使用 ERP CustomerVO) +- 新建客户对话框(基础功能) + +**新增功能:** + +#### 1. 线索列表Tab页 + +在现有CRM页面添加Tab切换: + +```vue + + + + + + + + + + + +``` + +#### 2. 线索管理模块 + +**筛选栏:** + +```vue +
+ + + + + + + + + + + 搜索 +
+``` + +**线索列表(Element Plus Table):** + +```vue + + + + + + + + + + + + + + + + + + +``` + +#### 3. 线索详情 Drawer + +```vue + + + {{ currentLead.companyName }} + {{ currentLead.contactName }} + {{ currentLead.mobile }} + + + {{ currentLead.customerCode }} + + + + + + + + + {{ getRiskLabel(currentLead.riskLevel) }} + + + + + + 跟进记录 + + + + + + + + + + +``` + +#### 4. 新建线索 Dialog + +```vue + + + + + + + + + + + + + + + + + + + + +``` + +--- + +### 4.4.2 AI分析侧边栏(Element Plus Drawer) + +右侧固定展示: + +```vue + +
+ +
+ + AI意向评分 +
+
+ + {{ riskReason }} + + + + + + + + + + +
+
+
+
+
+``` + +**AI服务调用:** 通过 Gateway 调用 `hzhub-ai:6039/ai/chat/analyze` + +--- + +# 5. 经销商中心模块设计 + +## 5.1 现有页面扩展 + +**路径:** `hzhub-portal-employee/src/pages/dealer/index.vue`(**已存在,需扩展**) + +**现有功能:** +- 经销商卡片视图(使用 ERP CustomerVO) +- 筛选(销区、品牌、状态、搜索) +- 统计数据展示 + +**新增功能:** + +### 5.1.1 关联CRM经销商数据 + +```typescript +interface DealerVO extends CustomerVO { + // CRM扩展字段 + dealerId?: number; // CRM经销商ID + level?: string; // 经销商等级 + lifecycle?: string; // 生命周期 + totalOrderAmount?: number; // 累计订单金额 + totalPaymentAmount?: number; // 累计回款金额 + activityScore?: number; // 活跃评分 + riskScore?: number; // 风险评分 + tags?: DealerTagVO[]; // AI标签 +} +``` + +### 5.1.2 经销商详情 Dialog + +```vue + + + + + {{ dealer.customerCode }} + + {{ dealer.level }} + + + + + + + + + + + + + + + + +
+ 活跃度评分 + +
+
+
+ + +
+ 风险评分 + +
+
+
+
+
+
+
+``` + +--- + +## 5.2 数据表设计 + +### 5.2.1 crm_dealer(经销商表) + +| 字段 | 类型 | 说明 | +|---|---|---| +| dealer_id | bigint | 主键 | +| customer_code | varchar(100) | **ERP客户编码(关联)** | +| dealer_name | varchar(200) | 经销商名称 | +| dealer_code | varchar(100) | 编码 | +| contact_name | varchar(100) | 联系人 | +| mobile | varchar(50) | 手机 | +| province | varchar(50) | 省 | +| city | varchar(50) | 市 | +| level | varchar(50) | 等级(字典:crm_dealer_level) | +| lifecycle | varchar(50) | 生命周期(字典:crm_lifecycle) | +| signed_at | datetime | 签约时间 | +| store_count | int | 门店数 | +| team_size | int | 团队规模 | +| total_order_amount | decimal(18,2) | 累计订单金额 | +| total_payment_amount | decimal(18,2) | 累计回款金额 | +| activity_score | decimal(5,2) | 活跃评分 | +| risk_score | decimal(5,2) | 飆险评分 | +| owner_user_id | bigint | 负责人(关联 sys_user) | +| del_flag | int | 删除标志 | + +--- + +# 6. API 接口设计(员工门户适配) + +## 6.1 前端 API 定义 + +**路径:** `hzhub-portal-employee/src/api/crm/`(**新建目录**) + +创建: +- `index.ts`(API调用) +- `types.ts`(类型定义) + +### API示例 + +```typescript +import request from '@/utils/request'; + +// 线索列表 +export function getLeadList(params: LeadQueryParams) { + return request.get>('/crm/lead/list', params).json(); +} + +// 创建线索 +export function createLead(data: LeadForm) { + return request.post>('/crm/lead', data).json(); +} + +// 线索详情 +export function getLeadDetail(leadId: number) { + return request.get>(`/crm/lead/${leadId}`).json(); +} + +// 线索跟进记录 +export function getLeadFollowRecords(leadId: number) { + return request.get>(`/crm/lead/follow/${leadId}`).json(); +} + +// 添加跟进记录 +export function addLeadFollow(data: LeadFollowForm) { + return request.post>('/crm/lead/follow', data).json(); +} + +// 线索转经销商 +export function convertLeadToDealer(data: ConvertForm) { + return request.post>('/crm/lead/convert', data).json(); +} +``` + +--- + +## 6.2 后端接口设计(hzhub-system) + +### Controller规范 + +```java +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/crm/lead") +public class CrmLeadController extends BaseController { + + private final ICrmLeadService leadService; + + /** + * 获取线索列表 + */ + @GetMapping("/list") + public TableDataInfo list(CrmLeadBo lead, PageQuery pageQuery) { + return leadService.selectPageLeadList(lead, pageQuery); + } + + /** + * 新增线索 + */ + @Log(title = "线索管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated @RequestBody CrmLeadBo lead) { + return R.ok(leadService.insertLead(lead)); + } + + /** + * 线索详情 + */ + @GetMapping("/{leadId}") + public R detail(@PathVariable Long leadId) { + return R.ok(leadService.selectLeadById(leadId)); + } + + /** + * 线索跟进记录 + */ + @GetMapping("/follow/{leadId}") + public R> followRecords(@PathVariable Long leadId) { + return R.ok(leadService.selectFollowRecordsByLeadId(leadId)); + } + + /** + * 添加跟进记录 + */ + @Log(title = "线索跟进", businessType = BusinessType.INSERT) + @PostMapping("/follow") + public R addFollow(@Validated @RequestBody CrmLeadFollowBo follow) { + return R.ok(leadService.insertFollowRecord(follow)); + } + + /** + * 线索转经销商 + */ + @Log(title = "线索转化", businessType = BusinessType.INSERT) + @PostMapping("/convert") + public R convert(@Validated @RequestBody CrmLeadConvertBo convert) { + return R.ok(leadService.convertToDealer(convert)); + } +} +``` + +--- + +# 7. ERP数据集成设计 + +## 7.1 数据同步策略 + +### 同步方式 + +**方式1:关联查询(实时)** + +线索/经销商表存储 `customer_code`,实时调用 ERP 动态API 拉取客户信息。 + +```java +public CrmLeadVo selectLeadById(Long leadId) { + CrmLead lead = leadMapper.selectById(leadId); + CrmLeadVo vo = BeanUtil.toBean(lead, CrmLeadVo.class); + + // 如果有ERP客户编码,拉取ERP数据补充 + if (StringUtils.isNotBlank(lead.getCustomerCode())) { + CustomerVO erpCustomer = erpService.getCustomerDetail(lead.getCustomerCode()); + vo.setErpCustomerName(erpCustomer.getCustomerName()); + vo.setErpSalesAreaName(erpCustomer.getSalesAreaName()); + // ... + } + return vo; +} +``` + +**方式2:定时同步(批量)** + +使用 XXL-Job 定时任务同步 ERP 客户数据到 CRM 表。 + +```java +@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点 +public void syncErpCustomers() { + // 调用 hzhub-erp:8082/erp/dynamic/v1/customer/list + // 批量更新 crm_dealer 表 +} +``` + +--- + +## 7.2 ERP动态API调用 + +**hzhub-system 服务调用 ERP:** + +```java +@Service +public class ErpIntegrationService { + + @Value("${erp.base-url}") + private String erpBaseUrl; // http://localhost:8082 + + /** + * 获取ERP客户详情 + */ + public CustomerVO getCustomerDetail(String customerCode) { + String url = erpBaseUrl + "/erp/dynamic/v1/customer/detail?customerCode=" + customerCode; + ResponseEntity> response = restTemplate.getForEntity(url, R.class); + return response.getBody().getData(); + } + + /** + * 获取ERP客户列表 + */ + public List getCustomerList(CustomerQueryParam param) { + String url = erpBaseUrl + "/erp/dynamic/v1/customer/list"; + // ... + } +} +``` + +--- + +# 8. 企业微信集成(员工门户) + +## 8.1 企业微信侧边栏 + +**展示内容:** + +- 经销商画像(从 CRM + ERP 拉取) +- 最近订单 +- 最近拜访 +- 商机阶段 +- AI风险 + +**实现:** + +1. 企业微信应用配置(应用ID、Secret) +2. 后端接口:`GET /crm/wecom/sidebar/{externalUserId}` +3. 前端嵌入企业微信侧边栏(H5页面) + +--- + +## 8.2 移动端H5页面 + +**员工门户支持企业微信H5:** + +```typescript +// 企业微信环境判断 +if (window.location.href.includes('wework')) { + // 使用企业微信JS-SDK + wx.config({ + beta: true, + appId: 'YOUR_APPID', + // ... + }); +} +``` + +**移动端页面:** + +- `/crm/mobile/visit` - 拜访记录(语音录入) +- `/crm/mobile/follow` - 快速跟进 + +--- + +# 9. AI能力集成设计 + +## 9.1 AI服务调用(LangChain4j) + +**调用 hzhub-ai 服务:** + +```java +@Service +public class CrmAiService { + + @Autowired + private ChatClient chatClient; // LangChain4j + + /** + * AI意向分析 + */ + public LeadIntentAnalysis analyzeIntent(CrmLead lead) { + String prompt = String.format( + "分析以下线索的意向等级和风险:%s,联系人:%s,行业:%s", + lead.getCompanyName(), lead.getContactName(), lead.getIndustry() + ); + + AiMessage response = chatClient.generate(prompt); + // 解析AI响应,提取意向等级、评分、风险等级 + return parseIntentAnalysis(response.text()); + } + + /** + * AI跟进摘要生成 + */ + public String generateFollowSummary(String followContent) { + String prompt = "请为以下跟进记录生成摘要:" + followContent; + AiMessage response = chatClient.generate(prompt); + return response.text(); + } +} +``` + +--- + +## 9.2 向量检索(Weaviate) + +**使用 Weaviate 存储历史线索案例:** + +```java +@Service +public class LeadSimilarityService { + + /** + * 查找相似线索案例 + */ + public List findSimilarLeads(CrmLead newLead) { + // 将新线索信息转为向量 + // 在 Weaviate 中检索相似案例 + // 返回历史成功案例供参考 + } +} +``` + +--- + +# 10. 自动化工作流设计(warm-flow) + +## 10.1 线索分配流程 + +```text +线索创建 +-> AI意向分析 +-> 自动分配规则(根据区域) +-> warm-flow审批流程 +-> 销售确认接手 +``` + +**实现:** + +```java +// 触发warm-flow流程 +workflowService.startProcessInstanceByKey("crm_lead_assign_flow", leadId); +``` + +--- + +## 10.2 线索转化流程 + +```text +线索转化申请 +-> warm-flow审批流程(区域经理审批) +-> 创建经销商数据 +-> 创建商机 +-> 更新线索状态 +``` + +--- + +# 11. 权限与组织设计(Sa-Token) + +## 11.1 数据权限层级 + +```text +总部(sys_dept: root) +-> 大区(sys_dept: level 1) +-> 区域(sys_dept: level 2) +-> 城市(sys_dept: level 3) +-> 销售(sys_user) +``` + +**实现:** 使用 `sys_dept` 表的树形结构,通过 `DataPermissionHelper` 实现数据隔离。 + +--- + +## 11.2 权限控制点 + +| 模块 | 权限标识 | Sa-Token 注解 | +|---|---|---| +| 线索 | crm:lead:list, crm:lead:add, crm:lead:edit, crm:lead:remove | @SaCheckPermission | +| 经销商 | crm:dealer:list, crm:dealer:edit | - | +| 合同 | crm:contract:approve, crm:contract:amount-edit | - | +| 回款 | crm:payment:list, crm:payment:verify | - | +| BI | crm:bi:view(区域数据隔离) | - | + +**注意:** 员工门户不需要 Sa-Token注解,权限由 Gateway 统一控制。 + +--- + +# 12. 前端开发规范(Element Plus) + +## 12.1 页面结构规范 + +```vue + + + + + +``` + +--- + +## 12.2 目录结构 + +``` +hzhub-portal-employee/src/ +├── api/ +│ └── crm/ +│ │ ├── index.ts # CRM API调用 +│ │ └── types.ts # 类型定义 +├── pages/ +│ └── crm/ +│ │ ├── index.vue # CRM主页(扩展) +│ │ ├── LeadDetailDrawer.vue # 线索详情组件 +│ │ ├── FollowDrawer.vue # 跟进记录组件 +│ │ └── ConvertDialog.vue # 转化对话框 +│ └── dealer/ +│ │ ├── index.vue # 经销商主页(扩展) +│ │ ├── DealerDetailDialog.vue # 经销商详情 +``` + +--- + +# 13. 实施计划(员工门户) + +## 第一阶段(1-2个月) + +**建设内容:** + +- 扩展员工门户现有CRM页面(线索管理Tab) +- 后端CRM基础接口(hzhub-system) +- ERP数据关联(customer_code) +- 线索跟进记录(Timeline展示) +- warm-flow审批流程(线索分配) + +**技术栈:** + +- 后端:Spring Boot + MyBatis-Plus + Sa-Token + warm-flow +- 前端:Vue3 + Element Plus + TypeScript +- ERP集成:hzhub-erp 动态API + +--- + +## 第二阶段(2-3个月) + +**建设内容:** + +- AI意向分析(LangChain4j) +- AI跟进摘要生成 +- AI风险分析 +- 企业微信侧边栏集成 +- 移动端H5拜访页面 + +**技术栈:** + +- AI:LangChain4j + hzhub-ai 服务 +- 向量:Weaviate(案例检索) +- 移动端:企业微信JS-SDK + +--- + +## 第三阶段(3-4个月) + +**建设内容:** + +- AI预测模型(成交预测、流失预测) +- AI Copilot(LangGraph4j) +- BI仪表盘(扩展现有 `/bi` 页面) +- 自动化工作流(XXL-Job) + +**技术栈:** + +- AI:LangGraph4j + hzhub-ai 服务 +- 定时任务:XXL-Job + +--- + +# 14. 与 HZHub 项目的集成要点 + +## 14.1 Gateway 路由配置 + +**位置:** `hzhub-gateway/src/main/resources/application.yml` + +```yaml +spring: + cloud: + gateway: + routes: + # CRM 路由(添加到现有路由列表) + - id: hzhub-crm + uri: lb://hzhub-system + predicates: + - Path=/crm/** + filters: + - StripPrefix=1 +``` + +--- + +## 14.2 前端路由扩展 + +**位置:** `hzhub-portal-employee/src/routers/modules/staticRouter.ts` + +**现有路由(已存在):** + +```typescript +{ + path: '/crm', + name: 'crm', + component: () => import('@/pages/crm/index.vue'), + meta: { + title: '销售CRM', + subtitle: '客户关系管理', + icon: 'TrendCharts', + }, +}, +{ + path: '/dealer', + name: 'dealer', + component: () => import('@/pages/dealer/index.vue'), + meta: { + title: '经销商管理', + subtitle: '经销商信息管理', + icon: 'Shop', + }, +}, +``` + +**无需修改,直接扩展现有页面即可。** + +--- + +## 14.3 数据库初始化 + +**SQL 文件:** `hzhub-system/src/main/resources/db/crm_init.sql` + +包含: + +- CRM 表结构(crm_lead, crm_dealer, crm_opportunity 等) +- 数据字典(crm_lead_source, crm_opportunity_stage 等) +- 菜单配置(sys_menu - 告知管理员在后台配置) + +--- + +# 15. 核心差异总结(员工门户 vs 管理后台) + +| 方面 | 管理后台(V2) | 员工门户(V3) | +|---|---|---| +| 前端项目 | hzhub-admin | **hzhub-portal-employee** | +| UI框架 | Vben Admin + Ant Design Vue | **Element Plus** | +| 现有基础 | 无,需要新建 | **已有CRM和经销商页面** | +| 开发方式 | 新建独立页面 | **扩展现有页面** | +| 数据来源 | 独立CRM表 | **ERP客户数据 + CRM表** | +| 用户角色 | 管理员 + 销售 | **企业员工 + 销售人员** | +| 权限控制 | Sa-Token注解 | **Gateway统一控制** | +| 移动端 | 企业微信H5(新建) | **企业微信H5(员工门户支持)** | + +--- + +# 总结 + +本文档将 CRM 销售自动化系统完全适配到 **员工门户(hzhub-portal-employee)**,充分利用: + +1. **现有页面**:`/crm`(销售CRM)和 `/dealer`(经销商)已存在,直接扩展 +2. **ERP数据集成**:关联 `customer_code`,实时拉取 ERP 客户信息 +3. **Element Plus UI**:使用卡片式、Timeline、Drawer等组件 +4. **Vue3 Composition API**:` + + + + \ No newline at end of file diff --git a/hzhub-portal-employee/src/pages/opportunity/index.vue b/hzhub-portal-employee/src/pages/opportunity/index.vue new file mode 100644 index 0000000..c4703ba --- /dev/null +++ b/hzhub-portal-employee/src/pages/opportunity/index.vue @@ -0,0 +1,494 @@ + + + + + \ No newline at end of file diff --git a/hzhub-portal-employee/src/routers/modules/staticRouter.ts b/hzhub-portal-employee/src/routers/modules/staticRouter.ts index ba9afd4..2538443 100644 --- a/hzhub-portal-employee/src/routers/modules/staticRouter.ts +++ b/hzhub-portal-employee/src/routers/modules/staticRouter.ts @@ -33,21 +33,31 @@ export const layoutRouter: RouteRecordRaw[] = [ name: 'dealer', component: () => import('@/pages/dealer/index.vue'), meta: { - title: '经销商管理', - subtitle: '经销商信息管理', + title: '客户管理', + subtitle: '客户信息管理', icon: 'Shop', }, }, { - path: '/crm', - name: 'crm', - component: () => import('@/pages/crm/index.vue'), + path: '/opportunity', + name: 'opportunity', + component: () => import('@/pages/opportunity/index.vue'), meta: { - title: '销售CRM', - subtitle: '客户关系管理', + title: '商机中心', + subtitle: '商机管道管理', icon: 'TrendCharts', }, }, + { + path: '/lead', + name: 'lead', + component: () => import('@/pages/lead/index.vue'), + meta: { + title: '线索中心', + subtitle: '线索跟进转化', + icon: 'UserFilled', + }, + }, { path: '/supply', name: 'supply', diff --git a/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadController.java b/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadController.java new file mode 100644 index 0000000..d4e8a30 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadController.java @@ -0,0 +1,115 @@ +package org.hzhub.crm.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import lombok.RequiredArgsConstructor; +import org.hzhub.common.core.domain.R; +import org.hzhub.common.idempotent.annotation.RepeatSubmit; +import org.hzhub.common.log.annotation.Log; +import org.hzhub.common.log.enums.BusinessType; +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.common.web.core.BaseController; +import org.hzhub.crm.domain.bo.CrmLeadBo; +import org.hzhub.crm.domain.bo.CrmLeadConvertBo; +import org.hzhub.crm.domain.vo.CrmLeadVo; +import org.hzhub.crm.service.ICrmLeadService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * CRM线索管理 Controller + * 员工门户版本(无需Sa-Token权限注解,权限由Gateway控制) + * + * 注意:Gateway已配置 /crm/** 路由并StripPrefix=1(去除/crm前缀) + * 所以Controller使用 /lead 路径,实际对外接口为 /crm/lead/** + * + * @author hzhub + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/lead") +public class CrmLeadController extends BaseController { + + private final ICrmLeadService leadService; + + /** + * 获取线索列表 + */ + @GetMapping("/list") + public TableDataInfo list(CrmLeadBo lead, PageQuery pageQuery) { + return leadService.selectPageLeadList(lead, pageQuery); + } + + /** + * 获取线索详情 + * + * @param leadId 线索ID + */ + @GetMapping("/{leadId}") + public R getInfo(@PathVariable Long leadId) { + return R.ok(leadService.selectLeadById(leadId)); + } + + /** + * 新增线索 + */ + @Log(title = "线索管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping + public R add(@Validated @RequestBody CrmLeadBo lead) { + return toAjax(leadService.insertLead(lead)); + } + + /** + * 修改线索 + */ + @Log(title = "线索管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping + public R edit(@Validated @RequestBody CrmLeadBo lead) { + return toAjax(leadService.updateLead(lead)); + } + + /** + * 删除线索 + * + * @param leadIds 线索ID串 + */ + @Log(title = "线索管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{leadIds}") + public R remove(@PathVariable Long[] leadIds) { + return toAjax(leadService.deleteLeadByIds(leadIds)); + } + + /** + * 分配线索 + */ + @Log(title = "线索分配", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/assign") + public R assign(@RequestBody AssignRequest request) { + return toAjax(leadService.assignLead(request.getLeadId(), request.getOwnerUserId())); + } + + /** + * 分配请求DTO + */ + @lombok.Data + public static class AssignRequest { + private Long leadId; + private Long ownerUserId; + } + + /** + * 线索转经销商 + */ + @Log(title = "线索转化", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/convert") + public R convert(@Validated @RequestBody CrmLeadConvertBo convert) { + return toAjax(leadService.convertToDealer(convert)); + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadFollowController.java b/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadFollowController.java new file mode 100644 index 0000000..d037f8e --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmLeadFollowController.java @@ -0,0 +1,51 @@ +package org.hzhub.crm.controller; + +import lombok.RequiredArgsConstructor; +import org.hzhub.common.core.domain.R; +import org.hzhub.common.log.annotation.Log; +import org.hzhub.common.log.enums.BusinessType; +import org.hzhub.crm.domain.bo.CrmLeadFollowBo; +import org.hzhub.crm.domain.vo.CrmLeadFollowVo; +import org.hzhub.crm.service.ICrmLeadFollowService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * CRM线索跟进记录 Controller + * 员工门户版本(无需Sa-Token权限注解,权限由Gateway控制) + * + * 注意:Gateway已配置 /crm/** 路由并StripPrefix=1(去除/crm前缀) + * 所以Controller使用 /lead/follow 路径,实际对外接口为 /crm/lead/follow/** + * + * @author hzhub + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/lead/follow") +public class CrmLeadFollowController { + + private final ICrmLeadFollowService followService; + + /** + * 获取跟进记录列表 + * + * @param leadId 线索ID + */ + @GetMapping("/{leadId}") + public R> getFollowRecords(@PathVariable Long leadId) { + return R.ok(followService.selectFollowRecordsByLeadId(leadId)); + } + + /** + * 添加跟进记录 + */ + @Log(title = "线索跟进", businessType = BusinessType.INSERT) + @PostMapping + public R addFollow(@Validated @RequestBody CrmLeadFollowBo follow) { + followService.insertFollowRecord(follow); + return R.ok(); + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmOpportunityController.java b/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmOpportunityController.java new file mode 100644 index 0000000..93f764f --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/controller/CrmOpportunityController.java @@ -0,0 +1,82 @@ +package org.hzhub.crm.controller; + +import lombok.RequiredArgsConstructor; +import org.hzhub.common.core.domain.R; +import org.hzhub.common.idempotent.annotation.RepeatSubmit; +import org.hzhub.common.log.annotation.Log; +import org.hzhub.common.log.enums.BusinessType; +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.common.web.core.BaseController; +import org.hzhub.crm.domain.bo.CrmOpportunityBo; +import org.hzhub.crm.domain.vo.CrmOpportunityVo; +import org.hzhub.crm.service.ICrmOpportunityService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * CRM商机管理 Controller + * 员工门户版本(无需Sa-Token权限注解,权限由Gateway控制) + * + * 注意:Gateway已配置 /crm/** 路由并StripPrefix=1(去除/crm前缀) + * 所以Controller使用 /opportunity 路径,实际对外接口为 /crm/opportunity/** + * + * @author hzhub + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/opportunity") +public class CrmOpportunityController extends BaseController { + + private final ICrmOpportunityService opportunityService; + + /** + * 获取商机列表 + */ + @GetMapping("/list") + public TableDataInfo list(CrmOpportunityBo opportunity, PageQuery pageQuery) { + return opportunityService.selectPageOpportunityList(opportunity, pageQuery); + } + + /** + * 获取商机详情 + * + * @param opportunityId 商机ID + */ + @GetMapping("/{opportunityId}") + public R getInfo(@PathVariable Long opportunityId) { + return R.ok(opportunityService.selectOpportunityById(opportunityId)); + } + + /** + * 新增商机 + */ + @Log(title = "商机管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping + public R add(@Validated @RequestBody CrmOpportunityBo opportunity) { + return toAjax(opportunityService.insertOpportunity(opportunity)); + } + + /** + * 修改商机 + */ + @Log(title = "商机管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping + public R edit(@Validated @RequestBody CrmOpportunityBo opportunity) { + return toAjax(opportunityService.updateOpportunity(opportunity)); + } + + /** + * 删除商机 + * + * @param opportunityIds 商机ID串 + */ + @Log(title = "商机管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{opportunityIds}") + public R remove(@PathVariable Long[] opportunityIds) { + return toAjax(opportunityService.deleteOpportunityByIds(opportunityIds)); + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmDealer.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmDealer.java new file mode 100644 index 0000000..7cb05a2 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmDealer.java @@ -0,0 +1,128 @@ +package org.hzhub.crm.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.tenant.core.TenantEntity; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * CRM经销商对象 crm_dealer + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("crm_dealer") +public class CrmDealer extends TenantEntity { + + /** + * 经销商ID + */ + @TableId(value = "dealer_id", type = IdType.ASSIGN_ID) + private Long dealerId; + + /** + * ERP客户编码(可选) + */ + private String customerCode; + + /** + * 经销商名称 + */ + private String dealerName; + + /** + * 经销商编码 + */ + private String dealerCode; + + /** + * 联系人 + */ + private String contactName; + + /** + * 手机号 + */ + private String mobile; + + /** + * 省 + */ + private String province; + + /** + * 市 + */ + private String city; + + /** + * 经销商等级(字典:crm_dealer_level) + */ + private String level; + + /** + * 生命周期(字典:crm_lifecycle) + */ + private String lifecycle; + + /** + * 签约时间 + */ + private Date signedAt; + + /** + * 门店数 + */ + private Integer storeCount; + + /** + * 团队规模 + */ + private Integer teamSize; + + /** + * 累计订单金额 + */ + private BigDecimal totalOrderAmount; + + /** + * 累计回款金额 + */ + private BigDecimal totalPaymentAmount; + + /** + * 活跃评分 + */ + private BigDecimal activityScore; + + /** + * 风险评分 + */ + private BigDecimal riskScore; + + /** + * 负责人(关联 sys_user) + */ + private Long ownerUserId; + + /** + * 来源线索ID + */ + private Long sourceLeadId; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer delFlag; + + public CrmDealer(Long dealerId) { + this.dealerId = dealerId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLead.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLead.java new file mode 100644 index 0000000..7a1d4b1 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLead.java @@ -0,0 +1,148 @@ +package org.hzhub.crm.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.tenant.core.TenantEntity; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * CRM线索对象 crm_lead + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("crm_lead") +public class CrmLead extends TenantEntity { + + /** + * 线索ID + */ + @TableId(value = "lead_id", type = IdType.ASSIGN_ID) + private Long leadId; + + /** + * ERP客户编码(可选) + */ + private String customerCode; + + /** + * 公司名称 + */ + private String companyName; + + /** + * 联系人 + */ + private String contactName; + + /** + * 手机号 + */ + private String mobile; + + /** + * 微信号 + */ + private String wechat; + + /** + * 省 + */ + private String province; + + /** + * 市 + */ + private String city; + + /** + * 区域ID(关联 sys_dept) + */ + private Long regionId; + + /** + * 来源类型(字典:crm_lead_source) + */ + private String sourceType; + + /** + * 活动名称 + */ + private String activityName; + + /** + * 推荐人 + */ + private String referrerName; + + /** + * 行业(字典:crm_industry) + */ + private String industry; + + /** + * 公司规模(字典:crm_scale) + */ + private String companyScale; + + /** + * 门店数 + */ + private Integer storeCount; + + /** + * AI意向等级(字典:crm_intent_level) + */ + private String intentLevel; + + /** + * AI评分 + */ + private BigDecimal aiScore; + + /** + * 风险等级(字典:crm_risk_level) + */ + private String riskLevel; + + /** + * 负责人(关联 sys_user) + */ + private Long ownerUserId; + + /** + * 状态(字典:crm_lead_status) + */ + private String leadStatus; + + /** + * 转化经销商ID + */ + private Long convertedDealerId; + + /** + * 下次跟进时间 + */ + private Date nextFollowTime; + + /** + * 备注 + */ + private String remark; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer delFlag; + + public CrmLead(Long leadId) { + this.leadId = leadId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLeadFollow.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLeadFollow.java new file mode 100644 index 0000000..445a9e4 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmLeadFollow.java @@ -0,0 +1,67 @@ +package org.hzhub.crm.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.tenant.core.TenantEntity; + +import java.util.Date; + +/** + * CRM线索跟进记录对象 crm_lead_follow + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("crm_lead_follow") +public class CrmLeadFollow extends TenantEntity { + + /** + * 跟进ID + */ + @TableId(value = "follow_id", type = IdType.ASSIGN_ID) + private Long followId; + + /** + * 线索ID + */ + private Long leadId; + + /** + * 跟进方式(字典:crm_follow_type) + */ + private String followType; + + /** + * 跟进内容 + */ + private String content; + + /** + * AI摘要 + */ + private String aiSummary; + + /** + * 下次跟进时间 + */ + private Date nextFollowTime; + + /** + * 跟进人(关联 sys_user) + */ + private Long followUserId; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer delFlag; + + public CrmLeadFollow(Long followId) { + this.followId = followId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmOpportunity.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmOpportunity.java new file mode 100644 index 0000000..ec473cb --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/CrmOpportunity.java @@ -0,0 +1,98 @@ +package org.hzhub.crm.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.tenant.core.TenantEntity; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * CRM商机对象 crm_opportunity + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("crm_opportunity") +public class CrmOpportunity extends TenantEntity { + + /** + * 商机ID + */ + @TableId(value = "opportunity_id", type = IdType.ASSIGN_ID) + private Long opportunityId; + + /** + * 经销商ID + */ + private Long dealerId; + + /** + * 商机名称 + */ + private String opportunityName; + + /** + * 商机阶段(字典:crm_opportunity_stage) + */ + private String stage; + + /** + * 商机金额 + */ + private BigDecimal amount; + + /** + * 成功概率(百分比) + */ + private Integer probability; + + /** + * 预计成交日期 + */ + private Date expectedCloseDate; + + /** + * 实际成交日期 + */ + private Date actualCloseDate; + + /** + * 负责人(关联 sys_user) + */ + private Long ownerUserId; + + /** + * 产品名称 + */ + private String productName; + + /** + * 商机描述 + */ + private String description; + + /** + * 来源线索ID + */ + private Long sourceLeadId; + + /** + * 状态(字典:crm_opportunity_status) + */ + private String status; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer delFlag; + + public CrmOpportunity(Long opportunityId) { + this.opportunityId = opportunityId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmDealerBo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmDealerBo.java new file mode 100644 index 0000000..5f4ae0f --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmDealerBo.java @@ -0,0 +1,134 @@ +package org.hzhub.crm.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.core.xss.Xss; +import org.hzhub.common.mybatis.core.domain.BaseEntity; +import org.hzhub.crm.domain.CrmDealer; + +import java.math.BigDecimal; + +/** + * CRM经销商业务对象 crm_dealer + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CrmDealer.class, reverseConvertGenerate = false) +public class CrmDealerBo extends BaseEntity { + + /** + * 经销商ID + */ + private Long dealerId; + + /** + * ERP客户编码(可选) + */ + private String customerCode; + + /** + * 经销商名称 + */ + @Xss(message = "经销商名称不能包含脚本字符") + @NotBlank(message = "经销商名称不能为空") + @Size(min = 0, max = 200, message = "经销商名称长度不能超过{max}个字符") + private String dealerName; + + /** + * 经销商编码 + */ + @NotBlank(message = "经销商编码不能为空") + @Size(min = 0, max = 100, message = "经销商编码长度不能超过{max}个字符") + private String dealerCode; + + /** + * 联系人 + */ + @Xss(message = "联系人不能包含脚本字符") + @Size(min = 0, max = 100, message = "联系人长度不能超过{max}个字符") + private String contactName; + + /** + * 手机号 + */ + @Size(min = 0, max = 50, message = "手机号长度不能超过{max}个字符") + private String mobile; + + /** + * 省 + */ + @Size(min = 0, max = 50, message = "省份长度不能超过{max}个字符") + private String province; + + /** + * 市 + */ + @Size(min = 0, max = 50, message = "城市长度不能超过{max}个字符") + private String city; + + /** + * 经销商等级(字典:crm_dealer_level) + */ + private String level; + + /** + * 生命周期(字典:crm_lifecycle) + */ + private String lifecycle; + + /** + * 签约时间 + */ + private String signedAt; + + /** + * 门店数 + */ + private Integer storeCount; + + /** + * 团队规模 + */ + private Integer teamSize; + + /** + * 累计订单金额 + */ + private BigDecimal totalOrderAmount; + + /** + * 累计回款金额 + */ + private BigDecimal totalPaymentAmount; + + /** + * 活跃评分 + */ + private BigDecimal activityScore; + + /** + * 风险评分 + */ + private BigDecimal riskScore; + + /** + * 负责人(关联 sys_user) + */ + private Long ownerUserId; + + /** + * 来源线索ID + */ + private Long sourceLeadId; + + public CrmDealerBo(Long dealerId) { + this.dealerId = dealerId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadBo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadBo.java new file mode 100644 index 0000000..8b51e1a --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadBo.java @@ -0,0 +1,160 @@ +package org.hzhub.crm.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.core.xss.Xss; +import org.hzhub.common.mybatis.core.domain.BaseEntity; +import org.hzhub.crm.domain.CrmLead; + +import java.math.BigDecimal; + +/** + * CRM线索业务对象 crm_lead + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CrmLead.class, reverseConvertGenerate = false) +public class CrmLeadBo extends BaseEntity { + + /** + * 线索ID + */ + private Long leadId; + + /** + * ERP客户编码(可选) + */ + private String customerCode; + + /** + * 公司名称 + */ + @Xss(message = "公司名称不能包含脚本字符") + @NotBlank(message = "公司名称不能为空") + @Size(min = 0, max = 200, message = "公司名称长度不能超过{max}个字符") + private String companyName; + + /** + * 联系人 + */ + @Xss(message = "联系人不能包含脚本字符") + @NotBlank(message = "联系人不能为空") + @Size(min = 0, max = 100, message = "联系人长度不能超过{max}个字符") + private String contactName; + + /** + * 手机号 + */ + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + @Size(min = 0, max = 50, message = "手机号长度不能超过{max}个字符") + private String mobile; + + /** + * 微信号 + */ + @Size(min = 0, max = 100, message = "微信号长度不能超过{max}个字符") + private String wechat; + + /** + * 省 + */ + @Size(min = 0, max = 50, message = "省份长度不能超过{max}个字符") + private String province; + + /** + * 市 + */ + @Size(min = 0, max = 50, message = "城市长度不能超过{max}个字符") + private String city; + + /** + * 区域ID(关联 sys_dept) + */ + private Long regionId; + + /** + * 来源类型(字典:crm_lead_source) + */ + private String sourceType; + + /** + * 活动名称 + */ + @Size(min = 0, max = 100, message = "活动名称长度不能超过{max}个字符") + private String activityName; + + /** + * 推荐人 + */ + @Size(min = 0, max = 100, message = "推荐人长度不能超过{max}个字符") + private String referrerName; + + /** + * 行业(字典:crm_industry) + */ + private String industry; + + /** + * 公司规模(字典:crm_scale) + */ + private String companyScale; + + /** + * 门店数 + */ + private Integer storeCount; + + /** + * AI意向等级(字典:crm_intent_level) + */ + private String intentLevel; + + /** + * AI评分 + */ + private BigDecimal aiScore; + + /** + * 风险等级(字典:crm_risk_level) + */ + private String riskLevel; + + /** + * 负责人(关联 sys_user) + */ + private Long ownerUserId; + + /** + * 状态(字典:crm_lead_status) + */ + private String leadStatus; + + /** + * 转化经销商ID + */ + private Long convertedDealerId; + + /** + * 下次跟进时间 + */ + private String nextFollowTime; + + /** + * 备注 + */ + @Size(min = 0, max = 500, message = "备注长度不能超过{max}个字符") + private String remark; + + public CrmLeadBo(Long leadId) { + this.leadId = leadId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadConvertBo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadConvertBo.java new file mode 100644 index 0000000..8407f4b --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadConvertBo.java @@ -0,0 +1,53 @@ +package org.hzhub.crm.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * CRM线索转化经销商请求对象 + * + * @author hzhub + */ +@Data +public class CrmLeadConvertBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 线索ID(必填) + */ + @NotNull(message = "线索ID不能为空") + private Long leadId; + + /** + * 经销商名称(必填) + */ + @NotBlank(message = "经销商名称不能为空") + private String dealerName; + + /** + * 经销商编码(必填) + */ + @NotBlank(message = "经销商编码不能为空") + private String dealerCode; + + /** + * ERP客户编码(可选) + */ + private String customerCode; + + /** + * 签约时间(可选,格式:YYYY-MM-DD) + */ + private String signedAt; + + /** + * 经销商等级(可选,默认C) + */ + private String level; +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadFollowBo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadFollowBo.java new file mode 100644 index 0000000..03085a9 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmLeadFollowBo.java @@ -0,0 +1,70 @@ +package org.hzhub.crm.domain.bo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.mybatis.core.domain.BaseEntity; +import org.hzhub.crm.domain.CrmLeadFollow; + +import java.util.Date; + +/** + * CRM线索跟进记录业务对象 crm_lead_follow + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CrmLeadFollow.class, reverseConvertGenerate = false) +public class CrmLeadFollowBo extends BaseEntity { + + /** + * 跟进ID + */ + private Long followId; + + /** + * 线索ID + */ + @NotNull(message = "线索ID不能为空") + private Long leadId; + + /** + * 跟进方式(字典:crm_follow_type) + */ + @NotBlank(message = "跟进方式不能为空") + private String followType; + + /** + * 跟进内容 + */ + @NotBlank(message = "跟进内容不能为空") + @Size(min = 0, max = 2000, message = "跟进内容长度不能超过{max}个字符") + private String content; + + /** + * AI摘要 + */ + private String aiSummary; + + /** + * 下次跟进时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date nextFollowTime; + + /** + * 跟进人(关联 sys_user) + */ + private Long followUserId; + + public CrmLeadFollowBo(Long followId) { + this.followId = followId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmOpportunityBo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmOpportunityBo.java new file mode 100644 index 0000000..6aaf80a --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/bo/CrmOpportunityBo.java @@ -0,0 +1,110 @@ +package org.hzhub.crm.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hzhub.common.core.xss.Xss; +import org.hzhub.common.mybatis.core.domain.BaseEntity; +import org.hzhub.crm.domain.CrmOpportunity; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * CRM商机业务对象 crm_opportunity + * + * @author hzhub + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CrmOpportunity.class, reverseConvertGenerate = false) +public class CrmOpportunityBo extends BaseEntity { + + /** + * 商机ID + */ + private Long opportunityId; + + /** + * 经销商ID + */ + @NotNull(message = "经销商ID不能为空") + private Long dealerId; + + /** + * 商机名称 + */ + @Xss(message = "商机名称不能包含脚本字符") + @NotBlank(message = "商机名称不能为空") + @Size(min = 0, max = 200, message = "商机名称长度不能超过{max}个字符") + private String opportunityName; + + /** + * 商机阶段(字典:crm_opportunity_stage) + */ + private String stage; + + /** + * 商机金额 + */ + @DecimalMax(value = "99999999999999.99", message = "商机金额不能超过{value}") + @DecimalMin(value = "0", message = "商机金额不能小于{value}") + private BigDecimal amount; + + /** + * 成功概率(百分比) + */ + @DecimalMin(value = "0", message = "成功概率不能小于{value}") + @DecimalMax(value = "100", message = "成功概率不能超过{value}") + private Integer probability; + + /** + * 预计成交日期 + */ + private Date expectedCloseDate; + + /** + * 实际成交日期 + */ + private Date actualCloseDate; + + /** + * 负责人(关联 sys_user) + */ + private Long ownerUserId; + + /** + * 产品名称 + */ + @Xss(message = "产品名称不能包含脚本字符") + @Size(min = 0, max = 200, message = "产品名称长度不能超过{max}个字符") + private String productName; + + /** + * 商机描述 + */ + @Xss(message = "商机描述不能包含脚本字符") + @Size(min = 0, max = 500, message = "商机描述长度不能超过{max}个字符") + private String description; + + /** + * 来源线索ID + */ + private Long sourceLeadId; + + /** + * 状态(字典:crm_opportunity_status) + */ + private String status; + + public CrmOpportunityBo(Long opportunityId) { + this.opportunityId = opportunityId; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmDealerVo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmDealerVo.java new file mode 100644 index 0000000..3f810ad --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmDealerVo.java @@ -0,0 +1,178 @@ +package org.hzhub.crm.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.hzhub.common.sensitive.annotation.Sensitive; +import org.hzhub.common.sensitive.core.SensitiveStrategy; +import org.hzhub.common.translation.annotation.Translation; +import org.hzhub.common.translation.constant.TransConstant; +import org.hzhub.crm.domain.CrmDealer; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * CRM经销商视图对象 crm_dealer + * + * @author hzhub + */ +@Data +@AutoMapper(target = CrmDealer.class) +public class CrmDealerVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 经销商ID + */ + private Long dealerId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * ERP客户编码(可选) + */ + private String customerCode; + + /** + * 经销商名称 + */ + private String dealerName; + + /** + * 经销商编码 + */ + private String dealerCode; + + /** + * 联系人 + */ + private String contactName; + + /** + * 手机号(列表查询时脱敏) + */ + @Sensitive(strategy = SensitiveStrategy.PHONE) + private String mobile; + + /** + * 省 + */ + private String province; + + /** + * 市 + */ + private String city; + + /** + * 经销商等级 + */ + private String level; + + /** + * 经销商等级名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "level", other = "crm_dealer_level") + private String levelName; + + /** + * 生命周期 + */ + private String lifecycle; + + /** + * 生命周期名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "lifecycle", other = "crm_lifecycle") + private String lifecycleName; + + /** + * 签约时间 + */ + private Date signedAt; + + /** + * 门店数 + */ + private Integer storeCount; + + /** + * 团队规模 + */ + private Integer teamSize; + + /** + * 累计订单金额 + */ + private BigDecimal totalOrderAmount; + + /** + * 累计回款金额 + */ + private BigDecimal totalPaymentAmount; + + /** + * 活跃评分 + */ + private BigDecimal activityScore; + + /** + * 风险评分 + */ + private BigDecimal riskScore; + + /** + * 负责人ID + */ + private Long ownerUserId; + + /** + * 负责人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "ownerUserId") + private String ownerUserName; + + /** + * 来源线索ID + */ + private Long sourceLeadId; + + /** + * 创建人 + */ + private Long createBy; + + /** + * 创建人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") + private String createByName; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新人 + */ + private Long updateBy; + + /** + * 更新人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy") + private String updateByName; + + /** + * 更新时间 + */ + private Date updateTime; +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadFollowVo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadFollowVo.java new file mode 100644 index 0000000..cd610e4 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadFollowVo.java @@ -0,0 +1,81 @@ +package org.hzhub.crm.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.hzhub.common.translation.annotation.Translation; +import org.hzhub.common.translation.constant.TransConstant; +import org.hzhub.crm.domain.CrmLeadFollow; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * CRM线索跟进记录视图对象 crm_lead_follow + * + * @author hzhub + */ +@Data +@AutoMapper(target = CrmLeadFollow.class) +public class CrmLeadFollowVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 跟进ID + */ + private Long followId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 线索ID + */ + private Long leadId; + + /** + * 跟进方式 + */ + private String followType; + + /** + * 跟进方式名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "followType", other = "crm_follow_type") + private String followTypeName; + + /** + * 跟进内容 + */ + private String content; + + /** + * AI摘要 + */ + private String aiSummary; + + /** + * 下次跟进时间 + */ + private Date nextFollowTime; + + /** + * 跟进人ID + */ + private Long followUserId; + + /** + * 跟进人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "followUserId") + private String followUserName; + + /** + * 创建时间 + */ + private Date createTime; +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadVo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadVo.java new file mode 100644 index 0000000..2e40a64 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmLeadVo.java @@ -0,0 +1,222 @@ +package org.hzhub.crm.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.hzhub.common.sensitive.annotation.Sensitive; +import org.hzhub.common.sensitive.core.SensitiveStrategy; +import org.hzhub.common.translation.annotation.Translation; +import org.hzhub.common.translation.constant.TransConstant; +import org.hzhub.crm.domain.CrmLead; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * CRM线索视图对象 crm_lead + * + * @author hzhub + */ +@Data +@AutoMapper(target = CrmLead.class) +public class CrmLeadVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 线索ID + */ + private Long leadId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * ERP客户编码(可选) + */ + private String customerCode; + + /** + * 公司名称 + */ + private String companyName; + + /** + * 联系人 + */ + private String contactName; + + /** + * 手机号(列表查询时脱敏) + */ + @Sensitive(strategy = SensitiveStrategy.PHONE) + private String mobile; + + /** + * 微信号 + */ + private String wechat; + + /** + * 省 + */ + private String province; + + /** + * 市 + */ + private String city; + + /** + * 区域ID + */ + private Long regionId; + + /** + * 区域名称(翻译) + */ + @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "regionId") + private String regionName; + + /** + * 来源类型 + */ + private String sourceType; + + /** + * 来源类型名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "sourceType", other = "crm_lead_source") + private String sourceTypeName; + + /** + * 活动名称 + */ + private String activityName; + + /** + * 推荐人 + */ + private String referrerName; + + /** + * 行业 + */ + private String industry; + + /** + * 行业名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "industry", other = "crm_industry") + private String industryName; + + /** + * 公司规模 + */ + private String companyScale; + + /** + * 门店数 + */ + private Integer storeCount; + + /** + * AI意向等级 + */ + private String intentLevel; + + /** + * AI意向等级名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "intentLevel", other = "crm_intent_level") + private String intentLevelName; + + /** + * AI评分 + */ + private BigDecimal aiScore; + + /** + * 风险等级 + */ + private String riskLevel; + + /** + * 风险等级名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "riskLevel", other = "crm_risk_level") + private String riskLevelName; + + /** + * 负责人ID + */ + private Long ownerUserId; + + /** + * 负责人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "ownerUserId") + private String ownerUserName; + + /** + * 状态 + */ + private String leadStatus; + + /** + * 状态名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "leadStatus", other = "crm_lead_status") + private String leadStatusName; + + /** + * 转化经销商ID + */ + private Long convertedDealerId; + + /** + * 下次跟进时间 + */ + private Date nextFollowTime; + + /** + * 备注 + */ + private String remark; + + /** + * 创建人 + */ + private Long createBy; + + /** + * 创建人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") + private String createByName; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新人 + */ + private Long updateBy; + + /** + * 更新人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy") + private String updateByName; + + /** + * 更新时间 + */ + private Date updateTime; +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmOpportunityVo.java b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmOpportunityVo.java new file mode 100644 index 0000000..e24cffc --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/domain/vo/CrmOpportunityVo.java @@ -0,0 +1,151 @@ +package org.hzhub.crm.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.hzhub.common.translation.annotation.Translation; +import org.hzhub.common.translation.constant.TransConstant; +import org.hzhub.crm.domain.CrmOpportunity; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * CRM商机视图对象 crm_opportunity + * + * @author hzhub + */ +@Data +@AutoMapper(target = CrmOpportunity.class) +public class CrmOpportunityVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 商机ID + */ + private Long opportunityId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 经销商ID + */ + private Long dealerId; + + /** + * 经销商名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "dealerId", other = "crm_dealer") + private String dealerName; + + /** + * 商机名称 + */ + private String opportunityName; + + /** + * 商机阶段(字典:crm_opportunity_stage) + */ + private String stage; + + /** + * 商机阶段名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "stage", other = "crm_opportunity_stage") + private String stageName; + + /** + * 商机金额 + */ + private BigDecimal amount; + + /** + * 成功概率(百分比) + */ + private Integer probability; + + /** + * 预计成交日期 + */ + private Date expectedCloseDate; + + /** + * 实际成交日期 + */ + private Date actualCloseDate; + + /** + * 负责人ID + */ + private Long ownerUserId; + + /** + * 负责人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "ownerUserId") + private String ownerUserName; + + /** + * 产品名称 + */ + private String productName; + + /** + * 商机描述 + */ + private String description; + + /** + * 来源线索ID + */ + private Long sourceLeadId; + + /** + * 状态(字典:crm_opportunity_status) + */ + private String status; + + /** + * 状态名称(翻译) + */ + @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "status", other = "crm_opportunity_status") + private String statusName; + + /** + * 创建人 + */ + private Long createBy; + + /** + * 创建人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") + private String createByName; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新人 + */ + private Long updateBy; + + /** + * 更新人姓名(翻译) + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy") + private String updateByName; + + /** + * 更新时间 + */ + private Date updateTime; +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmDealerMapper.java b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmDealerMapper.java new file mode 100644 index 0000000..e4f4825 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmDealerMapper.java @@ -0,0 +1,14 @@ +package org.hzhub.crm.mapper; + +import org.hzhub.common.mybatis.core.mapper.BaseMapperPlus; +import org.hzhub.crm.domain.CrmDealer; +import org.hzhub.crm.domain.vo.CrmDealerVo; + +/** + * CRM经销商 Mapper 接口 + * + * @author hzhub + */ +public interface CrmDealerMapper extends BaseMapperPlus { + +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadFollowMapper.java b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadFollowMapper.java new file mode 100644 index 0000000..8d80e20 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadFollowMapper.java @@ -0,0 +1,14 @@ +package org.hzhub.crm.mapper; + +import org.hzhub.common.mybatis.core.mapper.BaseMapperPlus; +import org.hzhub.crm.domain.CrmLeadFollow; +import org.hzhub.crm.domain.vo.CrmLeadFollowVo; + +/** + * CRM线索跟进记录 Mapper 接口 + * + * @author hzhub + */ +public interface CrmLeadFollowMapper extends BaseMapperPlus { + +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadMapper.java b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadMapper.java new file mode 100644 index 0000000..b714e34 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmLeadMapper.java @@ -0,0 +1,14 @@ +package org.hzhub.crm.mapper; + +import org.hzhub.common.mybatis.core.mapper.BaseMapperPlus; +import org.hzhub.crm.domain.CrmLead; +import org.hzhub.crm.domain.vo.CrmLeadVo; + +/** + * CRM线索 Mapper 接口 + * + * @author hzhub + */ +public interface CrmLeadMapper extends BaseMapperPlus { + +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmOpportunityMapper.java b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmOpportunityMapper.java new file mode 100644 index 0000000..a25d266 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/mapper/CrmOpportunityMapper.java @@ -0,0 +1,14 @@ +package org.hzhub.crm.mapper; + +import org.hzhub.common.mybatis.core.mapper.BaseMapperPlus; +import org.hzhub.crm.domain.CrmOpportunity; +import org.hzhub.crm.domain.vo.CrmOpportunityVo; + +/** + * CRM商机 Mapper 接口 + * + * @author hzhub + */ +public interface CrmOpportunityMapper extends BaseMapperPlus { + +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/ErpIntegrationService.java b/hzhub-system/src/main/java/org/hzhub/crm/service/ErpIntegrationService.java new file mode 100644 index 0000000..9210a1e --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/ErpIntegrationService.java @@ -0,0 +1,81 @@ +package org.hzhub.crm.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hzhub.common.core.domain.R; +import org.hzhub.common.core.utils.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; + +/** + * ERP集成服务 + * 封装对 hzhub-erp 的调用 + * + * @author hzhub + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class ErpIntegrationService { + + @Value("${erp.base-url:http://localhost:8082}") + private String erpBaseUrl; + + private final RestTemplate restTemplate; + + /** + * 获取ERP客户详情 + * + * @param customerCode ERP客户编码 + * @return 客户信息(Map形式,包含customerName, contactName, phone等字段) + */ + public Map getCustomerDetail(String customerCode) { + if (StringUtils.isBlank(customerCode)) { + return null; + } + + try { + String url = erpBaseUrl + "/erp/dynamic/v1/customer/detail?customerCode=" + customerCode; + log.info("调用ERP服务获取客户详情: {}", url); + + R> response = restTemplate.getForObject(url, R.class); + if (response != null && response.getCode() == 200) { + return response.getData(); + } + + log.warn("ERP客户详情获取失败: customerCode={}, response={}", customerCode, response); + return null; + } catch (Exception e) { + log.error("调用ERP服务异常: customerCode={}", customerCode, e); + return null; + } + } + + /** + * 获取ERP客户列表(可选) + * + * @param params 查询参数 + * @return 客户列表 + */ + public Object getCustomerList(Map params) { + try { + String url = erpBaseUrl + "/erp/dynamic/v1/customer/list"; + log.info("调用ERP服务获取客户列表: {}", url); + + // 这里简化处理,实际应根据params构建查询参数 + R response = restTemplate.getForObject(url, R.class); + if (response != null && response.getCode() == 200) { + return response.getData(); + } + + log.warn("ERP客户列表获取失败: response={}", response); + return null; + } catch (Exception e) { + log.error("调用ERP服务异常", e); + return null; + } + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmDealerService.java b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmDealerService.java new file mode 100644 index 0000000..55a52ee --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmDealerService.java @@ -0,0 +1,63 @@ +package org.hzhub.crm.service; + +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.crm.domain.bo.CrmDealerBo; +import org.hzhub.crm.domain.vo.CrmDealerVo; + +/** + * CRM经销商 Service 接口 + * + * @author hzhub + */ +public interface ICrmDealerService { + + /** + * 分页查询经销商列表 + * + * @param dealer 经销商查询条件 + * @param pageQuery 分页参数 + * @return 经销商列表 + */ + TableDataInfo selectPageDealerList(CrmDealerBo dealer, PageQuery pageQuery); + + /** + * 查询经销商详情 + * + * @param dealerId 经销商ID + * @return 经销商详情 + */ + CrmDealerVo selectDealerById(Long dealerId); + + /** + * 新增经销商 + * + * @param dealer 经销商信息 + * @return 结果 + */ + int insertDealer(CrmDealerBo dealer); + + /** + * 修改经销商 + * + * @param dealer 经销商信息 + * @return 结果 + */ + int updateDealer(CrmDealerBo dealer); + + /** + * 批量删除经销商 + * + * @param dealerIds 经销商ID数组 + * @return 结果 + */ + int deleteDealerByIds(Long[] dealerIds); + + /** + * 校验经销商编码是否唯一 + * + * @param dealer 经销商信息 + * @return 结果 + */ + boolean checkDealerCodeUnique(CrmDealerBo dealer); +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadFollowService.java b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadFollowService.java new file mode 100644 index 0000000..7daf8d7 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadFollowService.java @@ -0,0 +1,30 @@ +package org.hzhub.crm.service; + +import org.hzhub.crm.domain.bo.CrmLeadFollowBo; +import org.hzhub.crm.domain.vo.CrmLeadFollowVo; + +import java.util.List; + +/** + * CRM线索跟进记录 Service 接口 + * + * @author hzhub + */ +public interface ICrmLeadFollowService { + + /** + * 查询线索跟进记录列表 + * + * @param leadId 线索ID + * @return 跟进记录列表 + */ + List selectFollowRecordsByLeadId(Long leadId); + + /** + * 新增跟进记录 + * + * @param follow 跟进记录信息 + * @return 结果 + */ + int insertFollowRecord(CrmLeadFollowBo follow); +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadService.java b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadService.java new file mode 100644 index 0000000..509110a --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmLeadService.java @@ -0,0 +1,101 @@ +package org.hzhub.crm.service; + +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.crm.domain.bo.CrmLeadBo; +import org.hzhub.crm.domain.bo.CrmLeadConvertBo; +import org.hzhub.crm.domain.bo.CrmLeadFollowBo; +import org.hzhub.crm.domain.vo.CrmLeadFollowVo; +import org.hzhub.crm.domain.vo.CrmLeadVo; + +import java.util.List; + +/** + * CRM线索 Service 接口 + * + * @author hzhub + */ +public interface ICrmLeadService { + + /** + * 分页查询线索列表 + * + * @param lead 线索查询条件 + * @param pageQuery 分页参数 + * @return 线索列表 + */ + TableDataInfo selectPageLeadList(CrmLeadBo lead, PageQuery pageQuery); + + /** + * 查询线索详情 + * + * @param leadId 线索ID + * @return 线索详情 + */ + CrmLeadVo selectLeadById(Long leadId); + + /** + * 新增线索 + * + * @param lead 线索信息 + * @return 结果 + */ + int insertLead(CrmLeadBo lead); + + /** + * 修改线索 + * + * @param lead 线索信息 + * @return 结果 + */ + int updateLead(CrmLeadBo lead); + + /** + * 批量删除线索 + * + * @param leadIds 线索ID数组 + * @return 结果 + */ + int deleteLeadByIds(Long[] leadIds); + + /** + * 分配线索 + * + * @param leadId 线索ID + * @param ownerUserId 负责人ID + * @return 结果 + */ + int assignLead(Long leadId, Long ownerUserId); + + /** + * 查询线索跟进记录列表 + * + * @param leadId 线索ID + * @return 跟进记录列表 + */ + List selectFollowRecordsByLeadId(Long leadId); + + /** + * 添加跟进记录 + * + * @param follow 跟进记录信息 + * @return 结果 + */ + int insertFollowRecord(CrmLeadFollowBo follow); + + /** + * 校验手机号是否唯一 + * + * @param lead 线索信息 + * @return 结果 + */ + boolean checkMobileUnique(CrmLeadBo lead); + + /** + * 线索转经销商 + * + * @param convert 转化参数 + * @return 结果 + */ + int convertToDealer(CrmLeadConvertBo convert); +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmOpportunityService.java b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmOpportunityService.java new file mode 100644 index 0000000..fd3dbbe --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/ICrmOpportunityService.java @@ -0,0 +1,65 @@ +package org.hzhub.crm.service; + +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.crm.domain.bo.CrmOpportunityBo; +import org.hzhub.crm.domain.vo.CrmOpportunityVo; + +/** + * CRM商机 Service 接口 + * + * @author hzhub + */ +public interface ICrmOpportunityService { + + /** + * 分页查询商机列表 + * + * @param opportunity 商机查询条件 + * @param pageQuery 分页参数 + * @return 商机列表 + */ + TableDataInfo selectPageOpportunityList(CrmOpportunityBo opportunity, PageQuery pageQuery); + + /** + * 查询商机详情 + * + * @param opportunityId 商机ID + * @return 商机详情 + */ + CrmOpportunityVo selectOpportunityById(Long opportunityId); + + /** + * 新增商机 + * + * @param opportunity 商机信息 + * @return 结果 + */ + int insertOpportunity(CrmOpportunityBo opportunity); + + /** + * 修改商机 + * + * @param opportunity 商机信息 + * @return 结果 + */ + int updateOpportunity(CrmOpportunityBo opportunity); + + /** + * 批量删除商机 + * + * @param opportunityIds 商机ID数组 + * @return 结果 + */ + int deleteOpportunityByIds(Long[] opportunityIds); + + /** + * 创建初始商机(线索转化时调用) + * + * @param dealerId 经销商ID + * @param sourceLeadId 来源线索ID + * @param ownerUserId 负责人ID + * @return 结果 + */ + int createInitialOpportunity(Long dealerId, Long sourceLeadId, Long ownerUserId); +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmDealerServiceImpl.java b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmDealerServiceImpl.java new file mode 100644 index 0000000..8178acd --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmDealerServiceImpl.java @@ -0,0 +1,109 @@ +package org.hzhub.crm.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hzhub.common.core.constant.SystemConstants; +import org.hzhub.common.core.exception.ServiceException; +import org.hzhub.common.core.utils.MapstructUtils; +import org.hzhub.common.core.utils.StringUtils; +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.crm.domain.CrmDealer; +import org.hzhub.crm.domain.bo.CrmDealerBo; +import org.hzhub.crm.domain.vo.CrmDealerVo; +import org.hzhub.crm.mapper.CrmDealerMapper; +import org.hzhub.crm.service.ICrmDealerService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * CRM经销商 Service 实现 + * + * @author hzhub + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class CrmDealerServiceImpl implements ICrmDealerService { + + private final CrmDealerMapper dealerMapper; + + @Override + public TableDataInfo selectPageDealerList(CrmDealerBo dealer, PageQuery pageQuery) { + LambdaQueryWrapper wrapper = buildQueryWrapper(dealer); + Page page = dealerMapper.selectVoPage(pageQuery.build(), wrapper); + return TableDataInfo.build(page); + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(CrmDealerBo dealer) { + Map params = dealer.getParams(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CrmDealer::getDelFlag, SystemConstants.NORMAL) + .like(StringUtils.isNotBlank(dealer.getDealerName()), CrmDealer::getDealerName, dealer.getDealerName()) + .eq(StringUtils.isNotBlank(dealer.getDealerCode()), CrmDealer::getDealerCode, dealer.getDealerCode()) + .eq(StringUtils.isNotBlank(dealer.getMobile()), CrmDealer::getMobile, dealer.getMobile()) + .eq(StringUtils.isNotBlank(dealer.getLevel()), CrmDealer::getLevel, dealer.getLevel()) + .eq(StringUtils.isNotBlank(dealer.getLifecycle()), CrmDealer::getLifecycle, dealer.getLifecycle()) + .eq(ObjectUtil.isNotNull(dealer.getOwnerUserId()), CrmDealer::getOwnerUserId, dealer.getOwnerUserId()) + .eq(StringUtils.isNotBlank(dealer.getCustomerCode()), CrmDealer::getCustomerCode, dealer.getCustomerCode()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + CrmDealer::getCreateTime, params.get("beginTime"), params.get("endTime")) + .orderByDesc(CrmDealer::getCreateTime); + return wrapper; + } + + @Override + public CrmDealerVo selectDealerById(Long dealerId) { + return dealerMapper.selectVoById(dealerId); + } + + @Override + public int insertDealer(CrmDealerBo dealer) { + // 校验经销商编码唯一性 + if (!checkDealerCodeUnique(dealer)) { + throw new ServiceException("经销商编码已存在"); + } + + CrmDealer crmDealer = MapstructUtils.convert(dealer, CrmDealer.class); + return dealerMapper.insert(crmDealer); + } + + @Override + public int updateDealer(CrmDealerBo dealer) { + // 校验经销商是否存在 + CrmDealer existingDealer = dealerMapper.selectById(dealer.getDealerId()); + if (ObjectUtil.isNull(existingDealer)) { + throw new ServiceException("经销商不存在"); + } + + // 校验经销商编码唯一性 + if (!checkDealerCodeUnique(dealer)) { + throw new ServiceException("经销商编码已存在"); + } + + CrmDealer crmDealer = MapstructUtils.convert(dealer, CrmDealer.class); + return dealerMapper.updateById(crmDealer); + } + + @Override + public int deleteDealerByIds(Long[] dealerIds) { + return dealerMapper.deleteBatchIds(List.of(dealerIds)); + } + + @Override + public boolean checkDealerCodeUnique(CrmDealerBo dealer) { + boolean exist = dealerMapper.exists(new LambdaQueryWrapper() + .eq(CrmDealer::getDealerCode, dealer.getDealerCode()) + .ne(ObjectUtil.isNotNull(dealer.getDealerId()), CrmDealer::getDealerId, dealer.getDealerId())); + return !exist; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadFollowServiceImpl.java b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadFollowServiceImpl.java new file mode 100644 index 0000000..803e2f2 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadFollowServiceImpl.java @@ -0,0 +1,51 @@ +package org.hzhub.crm.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hzhub.common.core.constant.SystemConstants; +import org.hzhub.common.core.utils.MapstructUtils; +import org.hzhub.common.satoken.utils.LoginHelper; +import org.hzhub.crm.domain.CrmLeadFollow; +import org.hzhub.crm.domain.bo.CrmLeadFollowBo; +import org.hzhub.crm.domain.vo.CrmLeadFollowVo; +import org.hzhub.crm.mapper.CrmLeadFollowMapper; +import org.hzhub.crm.service.ICrmLeadFollowService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * CRM线索跟进记录 Service 实现 + * + * @author hzhub + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class CrmLeadFollowServiceImpl implements ICrmLeadFollowService { + + private final CrmLeadFollowMapper followMapper; + + @Override + public List selectFollowRecordsByLeadId(Long leadId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CrmLeadFollow::getLeadId, leadId) + .eq(CrmLeadFollow::getDelFlag, SystemConstants.NORMAL) + .orderByDesc(CrmLeadFollow::getCreateTime); + return followMapper.selectVoList(wrapper); + } + + @Override + public int insertFollowRecord(CrmLeadFollowBo follow) { + // 设置跟进人为当前用户 + if (ObjectUtil.isNull(follow.getFollowUserId())) { + follow.setFollowUserId(LoginHelper.getUserId()); + } + + CrmLeadFollow crmFollow = MapstructUtils.convert(follow, CrmLeadFollow.class); + return followMapper.insert(crmFollow); + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadServiceImpl.java b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadServiceImpl.java new file mode 100644 index 0000000..917b2a6 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmLeadServiceImpl.java @@ -0,0 +1,313 @@ +package org.hzhub.crm.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hzhub.common.core.constant.SystemConstants; +import org.hzhub.common.core.exception.ServiceException; +import org.hzhub.common.core.utils.MapstructUtils; +import org.hzhub.common.core.utils.StringUtils; +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.common.satoken.utils.LoginHelper; +import org.hzhub.crm.domain.CrmDealer; +import org.hzhub.crm.domain.CrmLead; +import org.hzhub.crm.domain.CrmLeadFollow; +import org.hzhub.crm.domain.bo.CrmDealerBo; +import org.hzhub.crm.domain.bo.CrmLeadBo; +import org.hzhub.crm.domain.bo.CrmLeadConvertBo; +import org.hzhub.crm.domain.bo.CrmLeadFollowBo; +import org.hzhub.crm.domain.vo.CrmDealerVo; +import org.hzhub.crm.domain.vo.CrmLeadFollowVo; +import org.hzhub.crm.domain.vo.CrmLeadVo; +import org.hzhub.crm.mapper.CrmDealerMapper; +import org.hzhub.crm.mapper.CrmLeadFollowMapper; +import org.hzhub.crm.mapper.CrmLeadMapper; +import org.hzhub.crm.service.ErpIntegrationService; +import org.hzhub.crm.service.ICrmLeadService; +import org.hzhub.crm.service.ICrmOpportunityService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * CRM线索 Service 实现 + * + * @author hzhub + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class CrmLeadServiceImpl implements ICrmLeadService { + + private final CrmLeadMapper leadMapper; + private final CrmLeadFollowMapper followMapper; + private final CrmDealerMapper dealerMapper; + private final ErpIntegrationService erpIntegrationService; + private final ICrmOpportunityService opportunityService; + + @Override + public TableDataInfo selectPageLeadList(CrmLeadBo lead, PageQuery pageQuery) { + LambdaQueryWrapper wrapper = buildQueryWrapper(lead); + Page page = leadMapper.selectVoPage(pageQuery.build(), wrapper); + return TableDataInfo.build(page); + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(CrmLeadBo lead) { + Map params = lead.getParams(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CrmLead::getDelFlag, SystemConstants.NORMAL) + .like(StringUtils.isNotBlank(lead.getCompanyName()), CrmLead::getCompanyName, lead.getCompanyName()) + .eq(StringUtils.isNotBlank(lead.getMobile()), CrmLead::getMobile, lead.getMobile()) + .eq(StringUtils.isNotBlank(lead.getIntentLevel()), CrmLead::getIntentLevel, lead.getIntentLevel()) + .eq(StringUtils.isNotBlank(lead.getRiskLevel()), CrmLead::getRiskLevel, lead.getRiskLevel()) + .eq(ObjectUtil.isNotNull(lead.getOwnerUserId()), CrmLead::getOwnerUserId, lead.getOwnerUserId()) + .eq(StringUtils.isNotBlank(lead.getLeadStatus()), CrmLead::getLeadStatus, lead.getLeadStatus()) + .eq(StringUtils.isNotBlank(lead.getSourceType()), CrmLead::getSourceType, lead.getSourceType()) + .eq(StringUtils.isNotBlank(lead.getCustomerCode()), CrmLead::getCustomerCode, lead.getCustomerCode()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + CrmLead::getCreateTime, params.get("beginTime"), params.get("endTime")) + .orderByDesc(CrmLead::getCreateTime); + return wrapper; + } + + @Override + public CrmLeadVo selectLeadById(Long leadId) { + CrmLeadVo lead = leadMapper.selectVoById(leadId); + if (ObjectUtil.isNull(lead)) { + return lead; + } + return lead; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int insertLead(CrmLeadBo lead) { + // 如果提供了customerCode,从ERP拉取客户信息 + if (StringUtils.isNotBlank(lead.getCustomerCode())) { + enrichLeadFromErp(lead); + } + + // 校验手机号唯一性 + if (!checkMobileUnique(lead)) { + throw new ServiceException("手机号已存在"); + } + + // 设置默认状态 + if (StringUtils.isBlank(lead.getLeadStatus())) { + lead.setLeadStatus("new"); + } + + // 设置创建人为当前用户 + if (ObjectUtil.isNull(lead.getCreateBy())) { + lead.setCreateBy(LoginHelper.getUserId()); + } + + CrmLead crmLead = MapstructUtils.convert(lead, CrmLead.class); + return leadMapper.insert(crmLead); + } + + /** + * 从ERP拉取客户信息并填充线索 + */ + private void enrichLeadFromErp(CrmLeadBo lead) { + try { + Map customerDetail = erpIntegrationService.getCustomerDetail(lead.getCustomerCode()); + if (customerDetail != null) { + // 从ERP客户信息填充线索基础信息(如果未提供) + if (StringUtils.isBlank(lead.getCompanyName())) { + lead.setCompanyName((String) customerDetail.get("customerName")); + } + if (StringUtils.isBlank(lead.getContactName())) { + lead.setContactName((String) customerDetail.get("contactName")); + } + if (StringUtils.isBlank(lead.getMobile())) { + lead.setMobile((String) customerDetail.get("phone")); + } + if (StringUtils.isBlank(lead.getProvince())) { + lead.setProvince((String) customerDetail.get("province")); + } + if (StringUtils.isBlank(lead.getCity())) { + lead.setCity((String) customerDetail.get("city")); + } + + log.info("成功从ERP拉取客户信息: customerCode={}", lead.getCustomerCode()); + } else { + log.warn("ERP客户信息获取失败: customerCode={}", lead.getCustomerCode()); + } + } catch (Exception e) { + log.error("从ERP拉取客户信息异常: customerCode={}", lead.getCustomerCode(), e); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int updateLead(CrmLeadBo lead) { + // 校验线索是否存在 + CrmLead existingLead = leadMapper.selectById(lead.getLeadId()); + if (ObjectUtil.isNull(existingLead)) { + throw new ServiceException("线索不存在"); + } + + // 校验手机号唯一性 + if (!checkMobileUnique(lead)) { + throw new ServiceException("手机号已存在"); + } + + CrmLead crmLead = MapstructUtils.convert(lead, CrmLead.class); + return leadMapper.updateById(crmLead); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteLeadByIds(Long[] leadIds) { + List ids = List.of(leadIds); + return leadMapper.deleteByIds(ids); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int assignLead(Long leadId, Long ownerUserId) { + // 校验线索是否存在 + CrmLead existingLead = leadMapper.selectById(leadId); + if (ObjectUtil.isNull(existingLead)) { + throw new ServiceException("线索不存在"); + } + + // 更新负责人和状态 + return leadMapper.update(null, + new LambdaUpdateWrapper() + .set(CrmLead::getOwnerUserId, ownerUserId) + .set(CrmLead::getLeadStatus, "following") + .set(CrmLead::getUpdateTime, new Date()) + .set(CrmLead::getUpdateBy, LoginHelper.getUserId()) + .eq(CrmLead::getLeadId, leadId)); + } + + @Override + public List selectFollowRecordsByLeadId(Long leadId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CrmLeadFollow::getLeadId, leadId) + .eq(CrmLeadFollow::getDelFlag, SystemConstants.NORMAL) + .orderByDesc(CrmLeadFollow::getCreateTime); + return followMapper.selectVoList(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int insertFollowRecord(CrmLeadFollowBo follow) { + // 设置跟进人为当前用户 + if (ObjectUtil.isNull(follow.getFollowUserId())) { + follow.setFollowUserId(LoginHelper.getUserId()); + } + + CrmLeadFollow crmFollow = MapstructUtils.convert(follow, CrmLeadFollow.class); + int result = followMapper.insert(crmFollow); + + // 如果提供了下次跟进时间,更新线索的nextFollowTime + if (ObjectUtil.isNotNull(follow.getNextFollowTime()) && result > 0) { + leadMapper.update(null, + new LambdaUpdateWrapper() + .set(CrmLead::getNextFollowTime, crmFollow.getNextFollowTime()) + .set(CrmLead::getUpdateTime, new Date()) + .set(CrmLead::getUpdateBy, LoginHelper.getUserId()) + .eq(CrmLead::getLeadId, follow.getLeadId())); + } + + return result; + } + + @Override + public boolean checkMobileUnique(CrmLeadBo lead) { + boolean exist = leadMapper.exists(new LambdaQueryWrapper() + .eq(CrmLead::getMobile, lead.getMobile()) + .ne(ObjectUtil.isNotNull(lead.getLeadId()), CrmLead::getLeadId, lead.getLeadId())); + return !exist; + } + + /** + * 线索转经销商 + * + * @param convert 转化参数 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int convertToDealer(CrmLeadConvertBo convert) { + // 1. 查询线索 + CrmLead lead = leadMapper.selectById(convert.getLeadId()); + if (lead == null) { + throw new ServiceException("线索不存在"); + } + if ("converted".equals(lead.getLeadStatus())) { + throw new ServiceException("线索已转化,不能重复转化"); + } + + // 2. 校验经销商编码唯一性 + boolean dealerCodeExists = dealerMapper.exists(new LambdaQueryWrapper() + .eq(CrmDealer::getDealerCode, convert.getDealerCode())); + if (dealerCodeExists) { + throw new ServiceException("经销商编码已存在"); + } + + // 3. 创建经销商 + CrmDealer dealer = new CrmDealer(); + dealer.setCustomerCode(convert.getCustomerCode()); + dealer.setDealerName(convert.getDealerName()); + dealer.setDealerCode(convert.getDealerCode()); + dealer.setContactName(lead.getContactName()); + dealer.setMobile(lead.getMobile()); + dealer.setProvince(lead.getProvince()); + dealer.setCity(lead.getCity()); + dealer.setLevel(convert.getLevel() != null ? convert.getLevel() : "C"); + dealer.setLifecycle("active"); + + // 处理签约时间 + if (convert.getSignedAt() != null) { + dealer.setSignedAt(cn.hutool.core.date.DateUtil.parse(convert.getSignedAt())); + } else { + dealer.setSignedAt(new Date()); + } + + dealer.setStoreCount(lead.getStoreCount() != null ? lead.getStoreCount() : 0); + dealer.setTeamSize(0); + dealer.setOwnerUserId(lead.getOwnerUserId()); + dealer.setSourceLeadId(lead.getLeadId()); + + int dealerResult = dealerMapper.insert(dealer); + if (dealerResult <= 0) { + throw new ServiceException("创建经销商失败"); + } + + // 4. 创建初始商机(线索转化时自动创建) + int opportunityResult = opportunityService.createInitialOpportunity( + dealer.getDealerId(), + lead.getLeadId(), + lead.getOwnerUserId() + ); + if (opportunityResult <= 0) { + log.warn("创建初始商机失败,但经销商创建成功: dealerId={}", dealer.getDealerId()); + } + + // 5. 更新线索状态 + lead.setLeadStatus("converted"); + lead.setConvertedDealerId(dealer.getDealerId()); + int leadResult = leadMapper.updateById(lead); + + log.info("线索转化成功: leadId={}, dealerId={}, dealerCode={}, opportunityId已创建", + lead.getLeadId(), dealer.getDealerId(), dealer.getDealerCode()); + + return leadResult; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmOpportunityServiceImpl.java b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmOpportunityServiceImpl.java new file mode 100644 index 0000000..f0f0615 --- /dev/null +++ b/hzhub-system/src/main/java/org/hzhub/crm/service/impl/CrmOpportunityServiceImpl.java @@ -0,0 +1,108 @@ +package org.hzhub.crm.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hzhub.common.core.constant.SystemConstants; +import org.hzhub.common.core.exception.ServiceException; +import org.hzhub.common.core.utils.MapstructUtils; +import org.hzhub.common.core.utils.StringUtils; +import org.hzhub.common.mybatis.core.page.PageQuery; +import org.hzhub.common.mybatis.core.page.TableDataInfo; +import org.hzhub.crm.domain.CrmOpportunity; +import org.hzhub.crm.domain.bo.CrmOpportunityBo; +import org.hzhub.crm.domain.vo.CrmOpportunityVo; +import org.hzhub.crm.mapper.CrmOpportunityMapper; +import org.hzhub.crm.service.ICrmOpportunityService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * CRM商机 Service 实现 + * + * @author hzhub + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class CrmOpportunityServiceImpl implements ICrmOpportunityService { + + private final CrmOpportunityMapper opportunityMapper; + + @Override + public TableDataInfo selectPageOpportunityList(CrmOpportunityBo opportunity, PageQuery pageQuery) { + LambdaQueryWrapper wrapper = buildQueryWrapper(opportunity); + Page page = opportunityMapper.selectVoPage(pageQuery.build(), wrapper); + return TableDataInfo.build(page); + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(CrmOpportunityBo opportunity) { + Map params = opportunity.getParams(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CrmOpportunity::getDelFlag, SystemConstants.NORMAL) + .eq(ObjectUtil.isNotNull(opportunity.getDealerId()), CrmOpportunity::getDealerId, opportunity.getDealerId()) + .like(StringUtils.isNotBlank(opportunity.getOpportunityName()), CrmOpportunity::getOpportunityName, opportunity.getOpportunityName()) + .eq(StringUtils.isNotBlank(opportunity.getStage()), CrmOpportunity::getStage, opportunity.getStage()) + .eq(ObjectUtil.isNotNull(opportunity.getOwnerUserId()), CrmOpportunity::getOwnerUserId, opportunity.getOwnerUserId()) + .eq(StringUtils.isNotBlank(opportunity.getStatus()), CrmOpportunity::getStatus, opportunity.getStatus()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + CrmOpportunity::getCreateTime, params.get("beginTime"), params.get("endTime")) + .orderByDesc(CrmOpportunity::getCreateTime); + return wrapper; + } + + @Override + public CrmOpportunityVo selectOpportunityById(Long opportunityId) { + return opportunityMapper.selectVoById(opportunityId); + } + + @Override + public int insertOpportunity(CrmOpportunityBo opportunity) { + CrmOpportunity crmOpportunity = MapstructUtils.convert(opportunity, CrmOpportunity.class); + return opportunityMapper.insert(crmOpportunity); + } + + @Override + public int updateOpportunity(CrmOpportunityBo opportunity) { + // 校验商机是否存在 + CrmOpportunity existingOpportunity = opportunityMapper.selectById(opportunity.getOpportunityId()); + if (ObjectUtil.isNull(existingOpportunity)) { + throw new ServiceException("商机不存在"); + } + + CrmOpportunity crmOpportunity = MapstructUtils.convert(opportunity, CrmOpportunity.class); + return opportunityMapper.updateById(crmOpportunity); + } + + @Override + public int deleteOpportunityByIds(Long[] opportunityIds) { + return opportunityMapper.deleteBatchIds(List.of(opportunityIds)); + } + + @Override + public int createInitialOpportunity(Long dealerId, Long sourceLeadId, Long ownerUserId) { + // 创建初始商机 + CrmOpportunity opportunity = new CrmOpportunity(); + opportunity.setDealerId(dealerId); + opportunity.setOpportunityName("初始商机"); + opportunity.setStage("lead"); + opportunity.setProbability(10); // 初始概率10% + opportunity.setOwnerUserId(ownerUserId); + opportunity.setSourceLeadId(sourceLeadId); + opportunity.setStatus("active"); + + int result = opportunityMapper.insert(opportunity); + if (result > 0) { + log.info("创建初始商机成功: dealerId={}, opportunityId={}", dealerId, opportunity.getOpportunityId()); + } + return result; + } +} \ No newline at end of file diff --git a/hzhub-system/src/main/java/org/hzhub/system/controller/system/SysUserController.java b/hzhub-system/src/main/java/org/hzhub/system/controller/system/SysUserController.java index de2322c..dc77fd6 100644 --- a/hzhub-system/src/main/java/org/hzhub/system/controller/system/SysUserController.java +++ b/hzhub-system/src/main/java/org/hzhub/system/controller/system/SysUserController.java @@ -316,4 +316,23 @@ public class SysUserController extends BaseController { return R.ok(wecomUserSyncService.syncFromWecom()); } + /** + * 员工门户用户选择器列表 + * 不需要权限注解(员工门户权限由Gateway控制) + * 返回简化的用户信息供选择器使用 + */ + @GetMapping("/portal/select") + public R> portalSelect(@RequestParam(required = false) String keyword) { + SysUserBo user = new SysUserBo(); + if (StringUtils.isNotBlank(keyword)) { + user.setUserName(keyword); + } + // 只返回状态正常的用户 + user.setStatus("0"); + // 使用分页查询,pageSize设为1000以获取所有用户 + PageQuery pageQuery = new PageQuery(1000, 1); + TableDataInfo pageData = userService.selectPageUserList(user, pageQuery); + return R.ok(pageData.getRows()); + } + } diff --git a/hzhub-system/src/main/resources/db/crm_dealer_init.sql b/hzhub-system/src/main/resources/db/crm_dealer_init.sql new file mode 100644 index 0000000..8e78358 --- /dev/null +++ b/hzhub-system/src/main/resources/db/crm_dealer_init.sql @@ -0,0 +1,58 @@ +-- ===================================================== +-- CRM经销商表初始化SQL +-- ===================================================== + +-- 经销商表 +CREATE TABLE IF NOT EXISTS crm_dealer ( + dealer_id BIGINT NOT NULL COMMENT '经销商ID(主键)', + tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户ID', + customer_code VARCHAR(100) DEFAULT NULL COMMENT 'ERP客户编码(关联)', + dealer_name VARCHAR(200) NOT NULL COMMENT '经销商名称', + dealer_code VARCHAR(100) NOT NULL COMMENT '经销商编码', + contact_name VARCHAR(100) DEFAULT NULL COMMENT '联系人', + mobile VARCHAR(50) DEFAULT NULL COMMENT '手机', + province VARCHAR(50) DEFAULT NULL COMMENT '省', + city VARCHAR(50) DEFAULT NULL COMMENT '市', + level VARCHAR(50) DEFAULT 'C' COMMENT '经销商等级(字典:crm_dealer_level)', + lifecycle VARCHAR(50) DEFAULT 'active' COMMENT '生命周期(字典:crm_lifecycle)', + signed_at DATETIME DEFAULT NULL COMMENT '签约时间', + store_count INT DEFAULT 0 COMMENT '门店数', + team_size INT DEFAULT 0 COMMENT '团队规模', + total_order_amount DECIMAL(18,2) DEFAULT 0 COMMENT '累计订单金额', + total_payment_amount DECIMAL(18,2) DEFAULT 0 COMMENT '累计回款金额', + activity_score DECIMAL(5,2) DEFAULT 0 COMMENT '活跃评分', + risk_score DECIMAL(5,2) DEFAULT 0 COMMENT '风险评分', + owner_user_id BIGINT DEFAULT NULL COMMENT '负责人(关联 sys_user)', + source_lead_id BIGINT DEFAULT NULL COMMENT '来源线索ID', + create_dept BIGINT DEFAULT NULL COMMENT '创建部门', + create_by BIGINT DEFAULT NULL COMMENT '创建人', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by BIGINT DEFAULT NULL COMMENT '更新人', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + del_flag INT DEFAULT 0 COMMENT '删除标志', + PRIMARY KEY (dealer_id), + KEY idx_tenant_id (tenant_id), + KEY idx_customer_code (customer_code), + KEY idx_dealer_code (dealer_code), + KEY idx_owner_user_id (owner_user_id), + KEY idx_source_lead_id (source_lead_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='CRM经销商表'; + +-- 数据字典:经销商等级 +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(18, '000000', '经销商等级', 'crm_dealer_level', 103, 1, sysdate(), NULL, NULL, '经销商等级列表'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(61, '000000', 1, 'A级经销商', 'A', 'crm_dealer_level', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, 'A级经销商'), +(62, '000000', 2, 'B级经销商', 'B', 'crm_dealer_level', '', 'success', 'N', 103, 1, sysdate(), NULL, NULL, 'B级经销商'), +(63, '000000', 3, 'C级经销商', 'C', 'crm_dealer_level', '', 'info', 'Y', 103, 1, sysdate(), NULL, NULL, 'C级经销商'); + +-- 数据字典:生命周期 +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(19, '000000', '经销商生命周期', 'crm_lifecycle', 103, 1, sysdate(), NULL, NULL, '经销商生命周期'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(64, '000000', 1, '活跃期', 'active', 'crm_lifecycle', '', 'success', 'Y', 103, 1, sysdate(), NULL, NULL, '活跃期'), +(65, '000000', 2, '稳定期', 'stable', 'crm_lifecycle', '', 'primary', 'N', 103, 1, sysdate(), NULL, NULL, '稳定期'), +(66, '000000', 3, '衰退期', 'decline', 'crm_lifecycle', '', 'warning', 'N', 103, 1, sysdate(), NULL, NULL, '衰退期'), +(67, '000000', 4, '流失期', 'churn', 'crm_lifecycle', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '流失期'); \ No newline at end of file diff --git a/hzhub-system/src/main/resources/db/crm_lead_init.sql b/hzhub-system/src/main/resources/db/crm_lead_init.sql new file mode 100644 index 0000000..56b0ecb --- /dev/null +++ b/hzhub-system/src/main/resources/db/crm_lead_init.sql @@ -0,0 +1,147 @@ +-- CRM线索中心模块数据库初始化脚本 +-- 适用于 hzhub-system 服务 (MySQL 8.0) +-- 创建时间: 2026-05-20 +-- 修复时间: 2026-05-20 (添加主键字段) + +-- ======================================== +-- 1. 数据字典定义 +-- ======================================== + +-- 线索来源类型 (crm_lead_source) +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark) +VALUES (13, '000000', '线索来源', 'crm_lead_source', 1, NOW(), 'CRM线索来源类型'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark) +VALUES +(39, '000000', 1, '活动', 'activity', 'crm_lead_source', '', 'default', 'N', 1, NOW(), '线下招商活动'), +(40, '000000', 2, '推荐', 'referral', 'crm_lead_source', '', 'default', 'N', 1, NOW(), '客户推荐'), +(41, '000000', 3, '网站', 'website', 'crm_lead_source', '', 'default', 'N', 1, NOW(), '官网咨询'), +(42, '000000', 4, '展会', 'exhibition', 'crm_lead_source', '', 'default', 'N', 1, NOW(), '行业展会'), +(43, '000000', 5, '企业微信', 'wecom', 'crm_lead_source', '', 'default', 'N', 1, NOW(), '企业微信咨询'), +(44, '000000', 6, 'ERP客户', 'erp', 'crm_lead_source', '', 'default', 'N', 1, NOW(), '从ERP客户转化'), +(45, '000000', 7, '其他', 'other', 'crm_lead_source', '', 'default', 'N', 1, NOW(), '其他来源'); + +-- 线索状态 (crm_lead_status) +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark) +VALUES (14, '000000', '线索状态', 'crm_lead_status', 1, NOW(), 'CRM线索状态'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark) +VALUES +(46, '000000', 1, '新线索', 'new', 'crm_lead_status', '', 'info', 'Y', 1, NOW(), '刚录入,未分配'), +(47, '000000', 2, '跟进中', 'following', 'crm_lead_status', '', 'primary', 'N', 1, NOW(), '已分配,正在跟进'), +(48, '000000', 3, '已转化', 'converted', 'crm_lead_status', '', 'success', 'N', 1, NOW(), '已转为经销商'), +(49, '000000', 4, '已作废', 'invalid', 'crm_lead_status', '', 'danger', 'N', 1, NOW(), '线索无效'); + +-- AI意向等级 (crm_intent_level) +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark) +VALUES (15, '000000', 'AI意向等级', 'crm_intent_level', 1, NOW(), 'AI分析意向等级'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark) +VALUES +(50, '000000', 1, '高意向', 'high', 'crm_intent_level', '', 'danger', 'N', 1, NOW(), 'AI评分 >= 80'), +(51, '000000', 2, '中意向', 'medium', 'crm_intent_level', '', 'warning', 'N', 1, NOW(), 'AI评分 60-80'), +(52, '000000', 3, '低意向', 'low', 'crm_intent_level', '', 'info', 'N', 1, NOW(), 'AI评分 < 60'); + +-- 风险等级 (crm_risk_level) +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark) +VALUES (16, '000000', '风险等级', 'crm_risk_level', 1, NOW(), 'CRM风险等级'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark) +VALUES +(53, '000000', 1, '高风险', 'high', 'crm_risk_level', '', 'danger', 'N', 1, NOW(), '需重点关注'), +(54, '000000', 2, '中风险', 'medium', 'crm_risk_level', '', 'warning', 'N', 1, NOW(), '需持续跟踪'), +(55, '000000', 3, '低风险', 'low', 'crm_risk_level', '', 'success', 'Y', 1, NOW(), '正常跟进'); + +-- 跟进方式 (crm_follow_type) +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark) +VALUES (17, '000000', '跟进方式', 'crm_follow_type', 1, NOW(), 'CRM跟进方式'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark) +VALUES +(56, '000000', 1, '电话', 'phone', 'crm_follow_type', '', 'default', 'Y', 1, NOW(), '电话跟进'), +(57, '000000', 2, '企业微信', 'wecom', 'crm_follow_type', '', 'default', 'N', 1, NOW(), '企业微信跟进'), +(58, '000000', 3, '拜访', 'visit', 'crm_follow_type', '', 'default', 'N', 1, NOW(), '上门拜访'), +(59, '000000', 4, '邮件', 'email', 'crm_follow_type', '', 'default', 'N', 1, NOW(), '邮件跟进'), +(60, '000000', 5, '其他', 'other', 'crm_follow_type', '', 'default', 'N', 1, NOW(), '其他方式'); + +-- ======================================== +-- 2. 线索表 (crm_lead) +-- ======================================== + +CREATE TABLE IF NOT EXISTS crm_lead ( + lead_id BIGINT NOT NULL COMMENT '线索ID(主键)', + tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户ID', + customer_code VARCHAR(100) DEFAULT NULL COMMENT 'ERP客户编码(可选)', + company_name VARCHAR(200) NOT NULL COMMENT '公司名称', + contact_name VARCHAR(100) NOT NULL COMMENT '联系人', + mobile VARCHAR(50) NOT NULL COMMENT '手机号', + wechat VARCHAR(100) DEFAULT NULL COMMENT '微信号', + province VARCHAR(50) DEFAULT NULL COMMENT '省', + city VARCHAR(50) DEFAULT NULL COMMENT '市', + region_id BIGINT DEFAULT NULL COMMENT '区域ID(关联 sys_dept)', + source_type VARCHAR(50) DEFAULT NULL COMMENT '来源类型(字典:crm_lead_source)', + activity_name VARCHAR(100) DEFAULT NULL COMMENT '活动名称', + referrer_name VARCHAR(100) DEFAULT NULL COMMENT '推荐人', + industry VARCHAR(100) DEFAULT NULL COMMENT '行业(字典:crm_industry)', + company_scale VARCHAR(100) DEFAULT NULL COMMENT '公司规模(字典:crm_scale)', + store_count INT DEFAULT 0 COMMENT '门店数', + intent_level VARCHAR(20) DEFAULT NULL COMMENT 'AI意向等级(字典:crm_intent_level)', + ai_score DECIMAL(5,2) DEFAULT NULL COMMENT 'AI评分', + risk_level VARCHAR(20) DEFAULT NULL COMMENT '风险等级(字典:crm_risk_level)', + owner_user_id BIGINT DEFAULT NULL COMMENT '负责人(关联 sys_user)', + lead_status VARCHAR(50) DEFAULT 'new' COMMENT '状态(字典:crm_lead_status)', + converted_dealer_id BIGINT DEFAULT NULL COMMENT '转化经销商ID', + next_follow_time DATETIME DEFAULT NULL COMMENT '下次跟进时间', + remark VARCHAR(500) DEFAULT NULL COMMENT '备注', + create_dept BIGINT DEFAULT NULL COMMENT '创建部门', + create_by BIGINT DEFAULT NULL COMMENT '创建人', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by BIGINT DEFAULT NULL COMMENT '更新人', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + del_flag INT DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (lead_id), + KEY idx_tenant_id (tenant_id), + KEY idx_customer_code (customer_code), + KEY idx_mobile (mobile), + KEY idx_owner_user_id (owner_user_id), + KEY idx_lead_status (lead_status), + KEY idx_create_time (create_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='CRM线索表'; + +-- ======================================== +-- 3. 线索跟进记录表 (crm_lead_follow) +-- ======================================== + +CREATE TABLE IF NOT EXISTS crm_lead_follow ( + follow_id BIGINT NOT NULL COMMENT '跟进ID(主键)', + tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户ID', + lead_id BIGINT NOT NULL COMMENT '线索ID', + follow_type VARCHAR(50) NOT NULL COMMENT '跟进方式(字典:crm_follow_type)', + content TEXT NOT NULL COMMENT '跟进内容', + ai_summary TEXT DEFAULT NULL COMMENT 'AI摘要', + next_follow_time DATETIME DEFAULT NULL COMMENT '下次跟进时间', + follow_user_id BIGINT NOT NULL COMMENT '跟进人(关联 sys_user)', + create_dept BIGINT DEFAULT NULL COMMENT '创建部门', + create_by BIGINT DEFAULT NULL COMMENT '创建人', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by BIGINT DEFAULT NULL COMMENT '更新人', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + del_flag INT DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (follow_id), + KEY idx_tenant_id (tenant_id), + KEY idx_lead_id (lead_id), + KEY idx_follow_user_id (follow_user_id), + KEY idx_create_time (create_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='CRM线索跟进记录表'; + +-- ======================================== +-- 4. 初始化完成提示 +-- ======================================== + +-- 查询验证 +SELECT 'CRM数据字典初始化完成' AS message; +SELECT dict_id, dict_name, dict_type FROM sys_dict_type WHERE dict_type LIKE 'crm_%'; +SELECT COUNT(*) AS dict_count FROM sys_dict_data WHERE dict_type LIKE 'crm_%'; + +SELECT 'CRM表创建完成' AS message; +SHOW TABLES LIKE 'crm_%'; \ No newline at end of file diff --git a/hzhub-system/src/main/resources/db/crm_opportunity_init.sql b/hzhub-system/src/main/resources/db/crm_opportunity_init.sql new file mode 100644 index 0000000..69e1b5b --- /dev/null +++ b/hzhub-system/src/main/resources/db/crm_opportunity_init.sql @@ -0,0 +1,76 @@ +-- ===================================================== +-- CRM商机表初始化SQL +-- ===================================================== + +-- 商机表 +CREATE TABLE IF NOT EXISTS crm_opportunity ( + opportunity_id BIGINT NOT NULL COMMENT '商机ID(主键)', + tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户ID', + dealer_id BIGINT NOT NULL COMMENT '经销商ID(关联 crm_dealer)', + opportunity_name VARCHAR(200) NOT NULL COMMENT '商机名称', + stage VARCHAR(50) DEFAULT 'lead' COMMENT '商机阶段(字典:crm_opportunity_stage)', + amount DECIMAL(18,2) DEFAULT 0 COMMENT '商机金额', + probability INT DEFAULT 0 COMMENT '成功概率(百分比)', + expected_close_date DATE DEFAULT NULL COMMENT '预计成交日期', + actual_close_date DATE DEFAULT NULL COMMENT '实际成交日期', + owner_user_id BIGINT DEFAULT NULL COMMENT '负责人(关联 sys_user)', + product_name VARCHAR(200) DEFAULT NULL COMMENT '产品名称', + description TEXT DEFAULT NULL COMMENT '商机描述', + source_lead_id BIGINT DEFAULT NULL COMMENT '来源线索ID', + status VARCHAR(50) DEFAULT 'active' COMMENT '状态(字典:crm_opportunity_status)', + create_dept BIGINT DEFAULT NULL COMMENT '创建部门', + create_by BIGINT DEFAULT NULL COMMENT '创建人', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by BIGINT DEFAULT NULL COMMENT '更新人', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + del_flag INT DEFAULT 0 COMMENT '删除标志', + PRIMARY KEY (opportunity_id), + KEY idx_tenant_id (tenant_id), + KEY idx_dealer_id (dealer_id), + KEY idx_stage (stage), + KEY idx_owner_user_id (owner_user_id), + KEY idx_source_lead_id (source_lead_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='CRM商机表'; + +-- 商机跟进记录表 +CREATE TABLE IF NOT EXISTS crm_opportunity_follow ( + follow_id BIGINT NOT NULL COMMENT '跟进记录ID(主键)', + tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户ID', + opportunity_id BIGINT NOT NULL COMMENT '商机ID', + follow_type VARCHAR(50) DEFAULT NULL COMMENT '跟进方式(字典:crm_follow_type)', + content TEXT DEFAULT NULL COMMENT '跟进内容', + ai_summary TEXT DEFAULT NULL COMMENT 'AI摘要', + next_follow_time DATETIME DEFAULT NULL COMMENT '下次跟进时间', + follow_user_id BIGINT DEFAULT NULL COMMENT '跟进人(关联 sys_user)', + create_dept BIGINT DEFAULT NULL COMMENT '创建部门', + create_by BIGINT DEFAULT NULL COMMENT '创建人', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by BIGINT DEFAULT NULL COMMENT '更新人', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + del_flag INT DEFAULT 0 COMMENT '删除标志', + PRIMARY KEY (follow_id), + KEY idx_tenant_id (tenant_id), + KEY idx_opportunity_id (opportunity_id), + KEY idx_follow_user_id (follow_user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='CRM商机跟进记录表'; + +-- 数据字典:商机阶段 +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, status, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(102, '000000', '商机阶段', 'crm_opportunity_stage', '0', 103, 1, sysdate(), NULL, NULL, '商机管道阶段列表'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(107, '000000', 1, '线索', 'lead', 'crm_opportunity_stage', '', 'info', 'Y', '0', 103, 1, sysdate(), NULL, NULL, '线索阶段'), +(108, '000000', 2, '谈判中', 'negotiation', 'crm_opportunity_stage', '', 'warning', 'N', '0', 103, 1, sysdate(), NULL, NULL, '谈判阶段'), +(109, '000000', 3, '方案', 'proposal', 'crm_opportunity_stage', '', 'primary', 'N', '0', 103, 1, sysdate(), NULL, NULL, '方案阶段'), +(110, '000000', 4, '赢单', 'closing', 'crm_opportunity_stage', '', 'success', 'N', '0', 103, 1, sysdate(), NULL, NULL, '赢单阶段'), +(111, '000000', 5, '输单', 'lost', 'crm_opportunity_stage', '', 'danger', 'N', '0', 103, 1, sysdate(), NULL, NULL, '输单阶段'); + +-- 数据字典:商机状态 +INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, status, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(103, '000000', '商机状态', 'crm_opportunity_status', '0', 103, 1, sysdate(), NULL, NULL, '商机状态列表'); + +INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_dept, create_by, create_time, update_by, update_time, remark) VALUES +(112, '000000', 1, '进行中', 'active', 'crm_opportunity_status', '', 'primary', 'Y', '0', 103, 1, sysdate(), NULL, NULL, '进行中'), +(113, '000000', 2, '已成交', 'won', 'crm_opportunity_status', '', 'success', 'N', '0', 103, 1, sysdate(), NULL, NULL, '已成交'), +(114, '000000', 3, '已失败', 'lost', 'crm_opportunity_status', '', 'danger', 'N', '0', 103, 1, sysdate(), NULL, NULL, '已失败'), +(115, '000000', 4, '已暂停', 'paused', 'crm_opportunity_status', '', 'info', 'N', '0', 103, 1, sysdate(), NULL, NULL, '已暂停'); \ No newline at end of file diff --git a/hzhub-system/src/main/resources/mapper/crm/CrmDealerMapper.xml b/hzhub-system/src/main/resources/mapper/crm/CrmDealerMapper.xml new file mode 100644 index 0000000..c14ee1d --- /dev/null +++ b/hzhub-system/src/main/resources/mapper/crm/CrmDealerMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hzhub-system/src/main/resources/mapper/crm/CrmLeadFollowMapper.xml b/hzhub-system/src/main/resources/mapper/crm/CrmLeadFollowMapper.xml new file mode 100644 index 0000000..e9951d4 --- /dev/null +++ b/hzhub-system/src/main/resources/mapper/crm/CrmLeadFollowMapper.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/hzhub-system/src/main/resources/mapper/crm/CrmLeadMapper.xml b/hzhub-system/src/main/resources/mapper/crm/CrmLeadMapper.xml new file mode 100644 index 0000000..2247910 --- /dev/null +++ b/hzhub-system/src/main/resources/mapper/crm/CrmLeadMapper.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/hzhub-system/src/main/resources/mapper/crm/CrmOpportunityMapper.xml b/hzhub-system/src/main/resources/mapper/crm/CrmOpportunityMapper.xml new file mode 100644 index 0000000..0128fd8 --- /dev/null +++ b/hzhub-system/src/main/resources/mapper/crm/CrmOpportunityMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file