| | |
| | | # CRM 与 ERP 销售一体化改造 - 前端文档 |
| | | # CRM 与 ERP 销售一体化改造 - 前端文档(完整版) |
| | | |
| | | ## 概述 |
| | | |
| | | 本次改造将 CRM 客户作为统一客户主数据,ERP 销售模块(销售订单、销售出库、销售退货、收款单)统一使用 CRM 客户。 |
| | | 本次改造实现 CRM 与 ERP 销售模块的深度集成,包括: |
| | | - **Phase 1**:客户数据统一 - ERP 使用 CRM 客户 |
| | | - **Phase 2**:产品数据统一 - CRM/ERP 使用 MDM 物料 |
| | | - **Phase 3**:合同生成销售订单 |
| | | - **Phase 4**:销售全流程追溯 |
| | | - **Phase 5**:营销数据报表 |
| | | - **Phase 6**:数据迁移与清理(SQL 部分) |
| | | |
| | | --- |
| | | |
| | | ## 一、改造清单 |
| | | ## 一、改造清单汇总 |
| | | |
| | | ### 后端改造(已完成) |
| | | ### 1.1 新增模块 |
| | | |
| | | | 模块 | 说明 | |
| | | |------|------| |
| | | | `yudao-module-crm-api` | CRM API 模块(客户 API、报表 API、追溯 API) | |
| | | | `yudao-module-erp-api` | ERP API 模块(销售订单 API) | |
| | | | `yudao-module-mdm-api` | MDM API 模块(物料 API) | |
| | | |
| | | ### 1.2 后端改造文件 |
| | | |
| | | | 文件 | 改动说明 | |
| | | |------|----------| |
| | | | `yudao-module-crm-api/` | 新增 CRM 客户 API 模块 | |
| | | | `CrmCustomerApi.java` | 新增客户 API 接口 | |
| | | | `CrmCustomerRespDTO.java` | 新增客户 DTO | |
| | | | `CrmCustomerApiImpl.java` | 实现 CRM 客户 API | |
| | | | `CrmCustomerApi.java` | CRM 客户 API 接口 | |
| | | | `CrmCustomerRespDTO.java` | 客户响应 DTO | |
| | | | `CrmReportApi.java` | 营销报表 API 接口 | |
| | | | `CrmSaleTraceApi.java` | 销售追溯 API 接口 | |
| | | | `ErpSaleOrderApi.java` | 销售订单 API 接口 | |
| | | | `MdmItemApi.java` | MDM 物料 API 接口 | |
| | | | `ErpSaleOrderServiceImpl.java` | 使用 CRM 客户 API | |
| | | | `ErpFinanceReceiptServiceImpl.java` | 使用 CRM 客户 API | |
| | | | `ErpSaleOrderController.java` | 使用 CRM 客户 API | |
| | | | `ErpSaleOrderDO.java` | 更新注释关联 CRM 客户 | |
| | | | `ErpSaleOutDO.java` | 更新注释关联 CRM 客户 | |
| | | | `ErpSaleReturnDO.java` | 更新注释关联 CRM 客户 | |
| | | | `ErpFinanceReceiptDO.java` | 更新注释关联 CRM 客户 | |
| | | |
| | | ### 后端改造(待完成) |
| | | |
| | | | 文件 | 改动说明 | |
| | | |------|----------| |
| | | | `ErpSaleOutController.java` | 使用 CRM 客户 API | |
| | | | `ErpSaleReturnController.java` | 使用 CRM 客户 API | |
| | | | `ErpFinanceReceiptController.java` | 使用 CRM 客户 API | |
| | | | `CrmContractServiceImpl.java` | 新增生成销售订单方法 | |
| | | | `CrmProductServiceImpl.java` | 使用 MDM 物料 API | |
| | | | `ErpProductServiceImpl.java` | 使用 MDM 物料 API | |
| | | |
| | | --- |
| | | |
| | | ## 二、涉及页面 |
| | | |
| | | ### ERP 销售模块 |
| | | ### 2.1 ERP 销售模块 |
| | | |
| | | | 页面 | 路径 | 改动说明 | |
| | | |------|------|----------| |
| | | | 销售订单列表 | `/erp/sale/order/index` | 客户选择改为 CRM 客户选择组件 | |
| | | | 销售订单列表 | `/erp/sale/order/index` | 客户选择改为 CRM 客户,新增关联合同列 | |
| | | | 销售订单表单 | `/erp/sale/order/form` | 客户选择改为 CRM 客户选择组件 | |
| | | | 销售出库列表 | `/erp/sale/out/index` | 客户选择改为 CRM 客户选择组件 | |
| | | | 销售出库表单 | `/erp/sale/out/form` | 客户选择改为 CRM 客户选择组件 | |
| | |
| | | | 收款单列表 | `/erp/finance/receipt/index` | 客户选择改为 CRM 客户选择组件 | |
| | | | 收款单表单 | `/erp/finance/receipt/form` | 客户选择改为 CRM 客户选择组件 | |
| | | |
| | | ### 2.2 CRM 合同模块 |
| | | |
| | | | 页面 | 路径 | 改动说明 | |
| | | |------|------|----------| |
| | | | 合同列表 | `/crm/contract/index` | 添加"生成销售订单"按钮 | |
| | | | 合同详情 | `/crm/contract/detail` | 显示销售订单信息、销售流程追溯 | |
| | | |
| | | ### 2.3 CRM 商机模块 |
| | | |
| | | | 页面 | 路径 | 改动说明 | |
| | | |------|------|----------| |
| | | | 商机详情 | `/crm/business/detail` | 添加销售流程追溯入口 | |
| | | |
| | | ### 2.4 CRM 客户模块 |
| | | |
| | | | 页面 | 路径 | 改动说明 | |
| | | |------|------|----------| |
| | | | 客户详情 | `/crm/customer/detail` | 添加销售流程追溯列表 | |
| | | |
| | | ### 2.5 CRM 报表模块(新增) |
| | | |
| | | | 页面 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | 报表概览 | `/crm/report/dashboard` | 数据概览页面 | |
| | | |
| | | --- |
| | | |
| | | ## 三、客户选择组件 |
| | | ## 三、API 接口 |
| | | |
| | | ### 3.1 使用 CRM 客户选择组件 |
| | | |
| | | ERP 销售模块的客户选择统一使用 CRM 客户选择组件。 |
| | | |
| | | ```html |
| | | <!-- 原来:ERP 客户选择 --> |
| | | <el-select v-model="form.customerId" placeholder="请选择客户"> |
| | | <el-option |
| | | v-for="item in customerList" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | |
| | | <!-- 改为:CRM 客户选择组件 --> |
| | | <CrmCustomerSelect v-model="form.customerId" /> |
| | | ``` |
| | | |
| | | ### 3.2 CRM 客户 API |
| | | ### 3.1 CRM 客户 API |
| | | |
| | | | 方法 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | GET | /crm/customer/simple-list | 获取客户精简列表(用于下拉选择) | |
| | | | GET | /crm/customer/list-by-ids | 批量获取客户信息 | |
| | | | GET | /rpc-api/crm/customer/get | 获取单个客户 | |
| | | | POST | /rpc-api/crm/customer/validate | 校验客户存在 | |
| | | |
| | | **客户精简列表响应:** |
| | | |
| | |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | ### 3.2 CRM 合同 API(新增) |
| | | |
| | | ## 四、废弃的 ERP 客户管理 |
| | | | 方法 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | POST | /crm/contract/generate-sale-order | 根据合同生成销售订单 | |
| | | |
| | | ### 4.1 ERP 客户管理页面废弃 |
| | | **请求参数:** |
| | | |
| | | 以下页面标记为废弃,建议在菜单中隐藏或删除: |
| | | | 参数 | 类型 | 必填 | 说明 | |
| | | |------|------|------|------| |
| | | | id | Long | 是 | 合同编号 | |
| | | |
| | | - `/erp/customer/index` - ERP 客户列表 |
| | | - `/erp/customer/form` - ERP 客户表单 |
| | | **响应:** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": 12345, |
| | | "msg": "操作成功" |
| | | } |
| | | ``` |
| | | |
| | | ### 3.3 CRM 销售追溯 API(新增) |
| | | |
| | | | 方法 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | GET | /rpc-api/crm/sale-trace/get-by-business-id | 根据商机编号获取追溯信息 | |
| | | | GET | /rpc-api/crm/sale-trace/get-by-contract-id | 根据合同编号获取追溯信息 | |
| | | | GET | /rpc-api/crm/sale-trace/get-list-by-customer-id | 根据客户编号获取追溯列表 | |
| | | |
| | | **响应示例:** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "businessId": 1, |
| | | "businessName": "商机A", |
| | | "businessPrice": 100000, |
| | | "customerId": 1, |
| | | "customerName": "客户A", |
| | | "contractId": 1, |
| | | "contractName": "合同A", |
| | | "contractPrice": 95000, |
| | | "contractStatus": 20, |
| | | "orderId": 1, |
| | | "orderNo": "SO001", |
| | | "orderPrice": 95000, |
| | | "receivablePrice": 50000, |
| | | "receivableItems": [ |
| | | { "receivableId": 1, "receivableNo": "RK001", "price": 30000 } |
| | | ] |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 3.4 CRM 营销报表 API(新增) |
| | | |
| | | | 方法 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | GET | /rpc-api/crm/report/get-customer-analyze | 获取客户分析统计 | |
| | | | GET | /rpc-api/crm/report/get-business-analyze | 获取商机分析统计 | |
| | | | GET | /rpc-api/crm/report/get-contract-analyze | 获取合同分析统计 | |
| | | | GET | /rpc-api/crm/report/get-sales-funnel | 获取销售漏斗统计 | |
| | | | GET | /rpc-api/crm/report/get-sales-performance | 获取销售业绩统计 | |
| | | |
| | | **客户分析响应:** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "totalCount": 1000, |
| | | "todayCount": 15, |
| | | "dealCount": 300, |
| | | "dealRate": 30.00, |
| | | "lockCount": 50, |
| | | "poolCount": 200 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **商机分析响应:** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "totalCount": 500, |
| | | "todayCount": 8, |
| | | "totalPrice": 5000000.00, |
| | | "successCount": 150, |
| | | "successPrice": 2000000.00, |
| | | "successRate": 30.00, |
| | | "processingCount": 250, |
| | | "failCount": 100 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **合同分析响应:** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "totalCount": 400, |
| | | "todayCount": 5, |
| | | "totalPrice": 3000000.00, |
| | | "approveCount": 300, |
| | | "approvePrice": 2500000.00, |
| | | "processCount": 20, |
| | | "receivablePrice": 1500000.00, |
| | | "receivableRate": 60.00 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **销售漏斗响应:** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "stages": [ |
| | | { "name": "客户", "count": 1000, "price": 0, "conversionRate": 100.00 }, |
| | | { "name": "商机", "count": 500, "price": 5000000.00, "conversionRate": 50.00 }, |
| | | { "name": "合同", "count": 300, "price": 2500000.00, "conversionRate": 30.00 }, |
| | | { "name": "回款", "count": null, "price": 1500000.00, "conversionRate": 15.00 } |
| | | ] |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **销售业绩响应:** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "items": [ |
| | | { |
| | | "userId": 1, |
| | | "userName": "张三", |
| | | "deptName": "销售一部", |
| | | "contractCount": 50, |
| | | "contractPrice": 500000.00, |
| | | "receivablePrice": 300000.00, |
| | | "businessCount": 80, |
| | | "businessPrice": 800000.00 |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 3.5 ERP 销售订单 API(新增 RPC) |
| | | |
| | | | 方法 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | POST | /rpc-api/erp/sale-order/create | 创建销售订单 | |
| | | | GET | /rpc-api/erp/sale-order/get | 获取销售订单 | |
| | | | GET | /rpc-api/erp/sale-order/get-by-contract-id | 根据合同编号获取订单 | |
| | | |
| | | ### 3.6 MDM 物料 API |
| | | |
| | | | 方法 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | POST | /rpc-api/mdm/item/create | 创建物料 | |
| | | | PUT | /rpc-api/mdm/item/update | 更新物料 | |
| | | | GET | /rpc-api/mdm/item/get | 获取物料 | |
| | | | POST | /rpc-api/mdm/item/list | 获取物料列表 | |
| | | | GET | /rpc-api/mdm/item/validate-exists | 校验物料存在 | |
| | | |
| | | --- |
| | | |
| | | ## 五、SQL 执行 |
| | | ## 四、前端修改点 |
| | | |
| | | 见 `docs/crm_erp_integration_sql.sql` |
| | | ### 4.1 客户选择组件 |
| | | |
| | | ERP 销售模块的客户选择统一使用 CRM 客户选择组件。 |
| | | |
| | | ```html |
| | | <!-- 原来:ERP 客户选择 --> |
| | | <el-select v-model="form.customerId" placeholder="请选择客户"> |
| | | <el-option |
| | | v-for="item in customerList" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | |
| | | <!-- 改为:CRM 客户选择组件 --> |
| | | <CrmCustomerSelect v-model="form.customerId" /> |
| | | ``` |
| | | |
| | | **组件实现:** |
| | | |
| | | ```html |
| | | <!-- CrmCustomerSelect.vue --> |
| | | <template> |
| | | <el-select |
| | | v-model="selectedValue" |
| | | placeholder="请选择客户" |
| | | filterable |
| | | remote |
| | | :remote-method="searchCustomer" |
| | | @focus="loadCustomers" |
| | | > |
| | | <el-option |
| | | v-for="item in customerList" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | > |
| | | <span>{{ item.name }}</span> |
| | | <span style="color: #999; margin-left: 8px;">{{ item.mobile }}</span> |
| | | </el-option> |
| | | </el-select> |
| | | </template> |
| | | |
| | | <script> |
| | | import { getCustomerSimpleList } from '@/api/crm/customer' |
| | | |
| | | export default { |
| | | props: { |
| | | value: [Number, String] |
| | | }, |
| | | data() { |
| | | return { |
| | | customerList: [], |
| | | selectedValue: this.value |
| | | } |
| | | }, |
| | | watch: { |
| | | value(val) { |
| | | this.selectedValue = val |
| | | }, |
| | | selectedValue(val) { |
| | | this.$emit('input', val) |
| | | } |
| | | }, |
| | | methods: { |
| | | async loadCustomers() { |
| | | if (this.customerList.length === 0) { |
| | | const { data } = await getCustomerSimpleList() |
| | | this.customerList = data |
| | | } |
| | | }, |
| | | async searchCustomer(query) { |
| | | if (query) { |
| | | // 可选:实现远程搜索 |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | ``` |
| | | |
| | | ### 4.2 合同列表页面 |
| | | |
| | | **文件路径:** `/src/views/crm/contract/index.vue` |
| | | |
| | | **新增按钮:** |
| | | |
| | | ```html |
| | | <!-- 在合同操作列添加 --> |
| | | <el-table-column label="操作" width="200"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | | v-if="row.auditStatus === 20 && !row.orderId" |
| | | type="success" |
| | | size="small" |
| | | @click="handleGenerateOrder(row)" |
| | | > |
| | | 生成销售订单 |
| | | </el-button> |
| | | <el-button |
| | | v-if="row.orderId" |
| | | type="primary" |
| | | size="small" |
| | | @click="handleViewOrder(row)" |
| | | > |
| | | 查看订单 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | ``` |
| | | |
| | | **新增方法:** |
| | | |
| | | ```js |
| | | import { generateSaleOrder } from '@/api/crm/contract' |
| | | |
| | | methods: { |
| | | async handleGenerateOrder(row) { |
| | | try { |
| | | await this.$confirm('确认根据该合同生成销售订单?', '提示') |
| | | const { data } = await generateSaleOrder(row.id) |
| | | this.$message.success('生成销售订单成功') |
| | | this.getList() |
| | | } catch (e) { |
| | | if (e !== 'cancel') { |
| | | this.$message.error(e.message || '生成失败') |
| | | } |
| | | } |
| | | }, |
| | | handleViewOrder(row) { |
| | | this.$router.push({ |
| | | path: '/erp/sale/order/detail', |
| | | query: { id: row.orderId } |
| | | }) |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 4.3 销售订单列表页面 |
| | | |
| | | **文件路径:** `/src/views/erp/sale/order/index.vue` |
| | | |
| | | **新增列:** |
| | | |
| | | ```html |
| | | <el-table-column label="关联合同" prop="contractNo" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-link |
| | | v-if="row.contractId" |
| | | type="primary" |
| | | @click="handleViewContract(row)" |
| | | > |
| | | {{ row.contractNo }} |
| | | </el-link> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | ``` |
| | | |
| | | ### 4.4 商机详情页面 - 销售追溯 |
| | | |
| | | **文件路径:** `/src/views/crm/business/detail.vue` |
| | | |
| | | ```html |
| | | <el-card header="销售流程追溯"> |
| | | <el-timeline v-if="traceInfo"> |
| | | <el-timeline-item |
| | | :timestamp="traceInfo.businessCreateTime" |
| | | color="#409EFF" |
| | | > |
| | | <el-card> |
| | | <h4>商机创建</h4> |
| | | <p>商机名称:{{ traceInfo.businessName }}</p> |
| | | <p>商机金额:¥{{ traceInfo.businessPrice }}</p> |
| | | </el-card> |
| | | </el-timeline-item> |
| | | |
| | | <el-timeline-item |
| | | v-if="traceInfo.contractId" |
| | | :timestamp="traceInfo.contractCreateTime" |
| | | color="#67C23A" |
| | | > |
| | | <el-card> |
| | | <h4>合同签订</h4> |
| | | <p>合同名称:{{ traceInfo.contractName }}</p> |
| | | <p>合同金额:¥{{ traceInfo.contractPrice }}</p> |
| | | </el-card> |
| | | </el-timeline-item> |
| | | |
| | | <el-timeline-item |
| | | v-if="traceInfo.orderId" |
| | | :timestamp="traceInfo.orderCreateTime" |
| | | color="#E6A23C" |
| | | > |
| | | <el-card> |
| | | <h4>销售订单</h4> |
| | | <p>订单号: |
| | | <el-link type="primary" @click="viewOrder(traceInfo.orderId)"> |
| | | {{ traceInfo.orderNo }} |
| | | </el-link> |
| | | </p> |
| | | <p>订单金额:¥{{ traceInfo.orderPrice }}</p> |
| | | </el-card> |
| | | </el-timeline-item> |
| | | |
| | | <el-timeline-item v-if="traceInfo.receivablePrice > 0" color="#F56C6C"> |
| | | <el-card> |
| | | <h4>回款记录</h4> |
| | | <p>已回款金额:¥{{ traceInfo.receivablePrice }}</p> |
| | | </el-card> |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | <el-empty v-else description="暂无销售流程数据" /> |
| | | </el-card> |
| | | ``` |
| | | |
| | | ### 4.5 报表概览页面 |
| | | |
| | | **文件路径:** `/src/views/crm/report/dashboard.vue` |
| | | |
| | | ```html |
| | | <template> |
| | | <div class="report-dashboard"> |
| | | <!-- 统计卡片 --> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-title">客户总数</div> |
| | | <div class="stat-value">{{ customerAnalyze.totalCount }}</div> |
| | | <div class="stat-footer">今日新增:{{ customerAnalyze.todayCount }}</div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-title">商机总数</div> |
| | | <div class="stat-value">{{ businessAnalyze.totalCount }}</div> |
| | | <div class="stat-footer">转化率:{{ businessAnalyze.successRate }}%</div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-title">合同金额</div> |
| | | <div class="stat-value">¥{{ contractAnalyze.totalPrice }}</div> |
| | | <div class="stat-footer">已回款:¥{{ contractAnalyze.receivablePrice }}</div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card"> |
| | | <div class="stat-title">回款率</div> |
| | | <div class="stat-value">{{ contractAnalyze.receivableRate }}%</div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 销售业绩排行 --> |
| | | <el-card header="销售业绩排行"> |
| | | <el-table :data="performanceData.items" stripe> |
| | | <el-table-column prop="userName" label="销售人员" /> |
| | | <el-table-column prop="deptName" label="部门" /> |
| | | <el-table-column prop="contractCount" label="合同数量" width="100" /> |
| | | <el-table-column prop="contractPrice" label="合同金额" width="150" /> |
| | | <el-table-column prop="receivablePrice" label="回款金额" width="150" /> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | getCustomerAnalyze, |
| | | getBusinessAnalyze, |
| | | getContractAnalyze, |
| | | getSalesPerformance |
| | | } from '@/api/crm/report' |
| | | |
| | | export default { |
| | | data() { |
| | | return { |
| | | customerAnalyze: {}, |
| | | businessAnalyze: {}, |
| | | contractAnalyze: {}, |
| | | performanceData: { items: [] } |
| | | } |
| | | }, |
| | | created() { |
| | | this.loadData() |
| | | }, |
| | | methods: { |
| | | async loadData() { |
| | | const [c, b, ct, p] = await Promise.all([ |
| | | getCustomerAnalyze(), |
| | | getBusinessAnalyze(), |
| | | getContractAnalyze(), |
| | | getSalesPerformance() |
| | | ]) |
| | | this.customerAnalyze = c.data |
| | | this.businessAnalyze = b.data |
| | | this.contractAnalyze = ct.data |
| | | this.performanceData = p.data |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | ``` |
| | | |
| | | ### 4.6 API 定义文件 |
| | | |
| | | **文件路径:** `/src/api/crm/contract.js` |
| | | |
| | | ```js |
| | | // 生成销售订单 |
| | | export function generateSaleOrder(id) { |
| | | return request({ |
| | | url: '/crm/contract/generate-sale-order', |
| | | method: 'post', |
| | | params: { id } |
| | | }) |
| | | } |
| | | ``` |
| | | |
| | | **文件路径:** `/src/api/crm/saleTrace.js` |
| | | |
| | | ```js |
| | | // 根据商机编号获取销售流程追溯信息 |
| | | export function getTraceByBusinessId(businessId) { |
| | | return request({ |
| | | url: '/crm/sale-trace/get-by-business-id', |
| | | method: 'get', |
| | | params: { businessId } |
| | | }) |
| | | } |
| | | |
| | | // 根据合同编号获取销售流程追溯信息 |
| | | export function getTraceByContractId(contractId) { |
| | | return request({ |
| | | url: '/crm/sale-trace/get-by-contract-id', |
| | | method: 'get', |
| | | params: { contractId } |
| | | }) |
| | | } |
| | | |
| | | // 根据客户编号获取销售流程追溯列表 |
| | | export function getTraceListByCustomerId(customerId) { |
| | | return request({ |
| | | url: '/crm/sale-trace/get-list-by-customer-id', |
| | | method: 'get', |
| | | params: { customerId } |
| | | }) |
| | | } |
| | | ``` |
| | | |
| | | **文件路径:** `/src/api/crm/report.js` |
| | | |
| | | ```js |
| | | // 获取客户分析统计 |
| | | export function getCustomerAnalyze(startTime, endTime) { |
| | | return request({ |
| | | url: '/crm/report/get-customer-analyze', |
| | | method: 'get', |
| | | params: { startTime, endTime } |
| | | }) |
| | | } |
| | | |
| | | // 获取商机分析统计 |
| | | export function getBusinessAnalyze(startTime, endTime) { |
| | | return request({ |
| | | url: '/crm/report/get-business-analyze', |
| | | method: 'get', |
| | | params: { startTime, endTime } |
| | | }) |
| | | } |
| | | |
| | | // 获取合同分析统计 |
| | | export function getContractAnalyze(startTime, endTime) { |
| | | return request({ |
| | | url: '/crm/report/get-contract-analyze', |
| | | method: 'get', |
| | | params: { startTime, endTime } |
| | | }) |
| | | } |
| | | |
| | | // 获取销售漏斗统计 |
| | | export function getSalesFunnel() { |
| | | return request({ |
| | | url: '/crm/report/get-sales-funnel', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // 获取销售业绩统计 |
| | | export function getSalesPerformance(startTime, endTime) { |
| | | return request({ |
| | | url: '/crm/report/get-sales-performance', |
| | | method: 'get', |
| | | params: { startTime, endTime } |
| | | }) |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 六、改造完成状态 |
| | | ## 五、数据模型变更 |
| | | |
| | | ### 5.1 CRM 合同表 |
| | | |
| | | ```sql |
| | | ALTER TABLE `crm_contract` |
| | | ADD COLUMN `order_id` bigint DEFAULT NULL COMMENT '关联 ERP 销售订单编号' AFTER `total_price`, |
| | | ADD COLUMN `order_no` varchar(32) DEFAULT NULL COMMENT 'ERP 销售订单单号' AFTER `order_id`; |
| | | ``` |
| | | |
| | | ### 5.2 CRM 商机表 |
| | | |
| | | ```sql |
| | | ALTER TABLE `crm_business` |
| | | ADD COLUMN `order_id` bigint DEFAULT NULL COMMENT '关联 ERP 销售订单编号' AFTER `total_price`, |
| | | ADD COLUMN `order_no` varchar(32) DEFAULT NULL COMMENT 'ERP 销售订单单号' AFTER `order_id`; |
| | | ``` |
| | | |
| | | ### 5.3 ERP 销售订单表 |
| | | |
| | | ```sql |
| | | ALTER TABLE `erp_sale_order` |
| | | ADD COLUMN `contract_id` bigint DEFAULT NULL COMMENT '关联 CRM 合同编号' AFTER `customer_id`, |
| | | ADD COLUMN `contract_no` varchar(32) DEFAULT NULL COMMENT 'CRM 合同单号' AFTER `contract_id`; |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 六、业务规则 |
| | | |
| | | ### 6.1 生成销售订单条件 |
| | | |
| | | - 合同必须审批通过(auditStatus = 20) |
| | | - 合同未关联销售订单(orderId 为空) |
| | | - 合同必须包含产品信息 |
| | | |
| | | ### 6.2 客户数据规则 |
| | | |
| | | - ERP 销售模块统一使用 CRM 客户 |
| | | - 原 ERP 客户表(erp_customer)废弃 |
| | | - 客户选择通过 CRM 客户 API 获取 |
| | | |
| | | ### 6.3 产品数据规则 |
| | | |
| | | - CRM 和 ERP 产品统一使用 MDM 物料(itemType=2) |
| | | - MDM 物料类型:1-物料,2-产品,3-半成品 |
| | | |
| | | --- |
| | | |
| | | ## 七、业务流程图 |
| | | |
| | | ``` |
| | | 客户管理(CRM) → 商机跟进(CRM) → 合同签订(CRM) → 合同审批 |
| | | ↓ |
| | | 生成销售订单(ERP) |
| | | ↓ |
| | | 订单审批(ERP) |
| | | ↓ |
| | | 销售出库(ERP) |
| | | ↓ |
| | | 客户回款(ERP) |
| | | ``` |
| | | |
| | | **销售漏斗转化:** |
| | | |
| | | ``` |
| | | 客户(1000) → 商机(500) → 合同(300) → 回款(150) |
| | | ↓ ↓ ↓ ↓ |
| | | 100% 50% 30% 15% |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 八、注意事项 |
| | | |
| | | 1. **数据权限**:报表数据需要根据用户权限过滤 |
| | | 2. **状态控制**:只有审批通过的合同才能生成订单 |
| | | 3. **唯一性**:一个合同只能生成一个销售订单 |
| | | 4. **数据同步**:生成订单后,合同与订单双向关联 |
| | | 5. **废弃页面**:ERP 客户管理页面(/erp/customer/index)建议在菜单中隐藏 |
| | | |
| | | --- |
| | | |
| | | ## 九、完成状态 |
| | | |
| | | | Phase | 内容 | 状态 | |
| | | |-------|------|------| |
| | |
| | | |
| | | --- |
| | | |
| | | ## 七、产出文档清单 |
| | | ## 十一、销售报价模块(新增) |
| | | |
| | | ### 11.1 涉及页面 |
| | | |
| | | | 页面 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | 报价单列表 | `/crm/sale-quotation/index` | 报价单列表、操作按钮 | |
| | | | 报价单表单 | `/crm/sale-quotation/form` | 新建/编辑报价单 | |
| | | | 报价单详情 | `/crm/sale-quotation/detail` | 查看详情、审批进度 | |
| | | |
| | | ### 11.2 API 接口 |
| | | |
| | | | 方法 | 路径 | 说明 | |
| | | |------|------|------| |
| | | | POST | /crm/sale-quotation/create | 创建报价单 | |
| | | | PUT | /crm/sale-quotation/update | 更新报价单 | |
| | | | DELETE | /crm/sale-quotation/delete | 删除报价单 | |
| | | | GET | /crm/sale-quotation/get | 获取报价单详情 | |
| | | | GET | /crm/sale-quotation/page | 分页查询 | |
| | | | GET | /crm/sale-quotation/page-by-customer | 按客户分页 | |
| | | | GET | /crm/sale-quotation/page-by-business | 按商机分页 | |
| | | | PUT | /crm/sale-quotation/submit | 提交审批(需传 processDefinitionKey) | |
| | | | GET | /crm/sale-quotation/approve-process-list | 获取可选审批流程列表 | |
| | | | POST | /crm/sale-quotation/convert-contract | 转为合同 | |
| | | |
| | | **请求参数示例**(创建报价单): |
| | | |
| | | | 参数 | 类型 | 必填 | 说明 | |
| | | |------|------|------|------| |
| | | | name | String | 是 | 报价单名称 | |
| | | | customerId | Long | 是 | 客户编号 | |
| | | | businessId | Long | 否 | 商机编号 | |
| | | | contactId | Long | 否 | 联系人编号 | |
| | | | quotationTime | LocalDateTime | 否 | 报价日期 | |
| | | | validUntil | LocalDateTime | 否 | 有效期至 | |
| | | | discountPercent | BigDecimal | 否 | 整单折扣 | |
| | | | taxRate | BigDecimal | 否 | 税率 | |
| | | | fileUrl | String | 否 | 附件地址 | |
| | | | remark | String | 否 | 备注 | |
| | | | items | List | 是 | 物料明细列表 | |
| | | |
| | | **物料明细 Item**: |
| | | |
| | | | 参数 | 类型 | 必填 | 说明 | |
| | | |------|------|------|------| |
| | | | itemId | Long | 是 | MDM 物料编号 | |
| | | | quotationPrice | BigDecimal | 是 | 报价单价 | |
| | | | count | BigDecimal | 是 | 数量 | |
| | | | taxPercent | BigDecimal | 否 | 税率 | |
| | | | remark | String | 否 | 备注 | |
| | | |
| | | ### 11.3 前端修改点 |
| | | |
| | | #### 报价单列表页面 |
| | | |
| | | **文件路径:** `/src/views/crm/sale-quotation/index.vue` |
| | | |
| | | ```html |
| | | <!-- 操作列 --> |
| | | <el-table-column label="操作" width="280"> |
| | | <template #default="{ row }"> |
| | | <el-button v-if="row.status === 0" type="primary" size="small" @click="handleUpdate(row)">编辑</el-button> |
| | | <el-button v-if="row.status === 0" type="success" size="small" @click="handleSubmit(row)">提交审批</el-button> |
| | | <el-button v-if="row.status === 20 && !row.contractId" type="warning" size="small" @click="handleConvertContract(row)">转合同</el-button> |
| | | <el-button v-if="row.contractId" type="primary" size="small" @click="handleViewContract(row)">查看合同</el-button> |
| | | <el-button v-if="row.status === 0" type="danger" size="small" @click="handleDelete(row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | ``` |
| | | |
| | | **提交审批弹窗**: |
| | | |
| | | ```html |
| | | <el-dialog title="选择审批流程" v-model="submitDialogVisible" width="400px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="审批流程" required> |
| | | <el-select v-model="selectedProcessKey" placeholder="请选择" style="width: 100%"> |
| | | <el-option v-for="item in processList" :key="item.key" :label="item.name" :value="item.key" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="submitDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="confirmSubmit" :loading="submitLoading">确定</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | ``` |
| | | |
| | | **JS 方法**: |
| | | |
| | | ```js |
| | | import { getSaleQuotationApproveProcessList, submitSaleQuotation, convertSaleQuotationToContract } from '@/api/crm/saleQuotation' |
| | | |
| | | methods: { |
| | | async handleSubmit(row) { |
| | | const { data } = await getSaleQuotationApproveProcessList() |
| | | this.processList = data |
| | | this.currentRow = row |
| | | this.submitDialogVisible = true |
| | | }, |
| | | async confirmSubmit() { |
| | | if (!this.selectedProcessKey) { |
| | | this.$message.warning('请选择审批流程') |
| | | return |
| | | } |
| | | this.submitLoading = true |
| | | try { |
| | | await submitSaleQuotation(this.currentRow.id, this.selectedProcessKey) |
| | | this.$message.success('提交成功') |
| | | this.submitDialogVisible = false |
| | | this.getList() |
| | | } finally { |
| | | this.submitLoading = false |
| | | } |
| | | }, |
| | | async handleConvertContract(row) { |
| | | try { |
| | | await this.$confirm('确认将该报价单转为合同?', '提示') |
| | | const { data } = await convertSaleQuotationToContract(row.id) |
| | | this.$message.success('转合同成功') |
| | | this.$router.push({ path: '/crm/contract/detail', query: { id: data } }) |
| | | } catch (e) { |
| | | if (e !== 'cancel') this.$message.error(e.message || '转合同失败') |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | #### 报价单表单页面 - MDM 物料选择 |
| | | |
| | | **文件路径:** `/src/views/crm/sale-quotation/form.vue` |
| | | |
| | | 由于报价单使用 MDM 物料,需要使用 MDM 物料选择组件: |
| | | |
| | | ```html |
| | | <MdmItemSelect v-model="item.itemId" @change="handleItemChange" /> |
| | | ``` |
| | | |
| | | **API 定义文件**:`/src/api/crm/saleQuotation.js` |
| | | |
| | | ```js |
| | | import request from '@/utils/request' |
| | | |
| | | // 创建报价单 |
| | | export function createSaleQuotation(data) { |
| | | return request({ url: '/crm/sale-quotation/create', method: 'post', data }) |
| | | } |
| | | |
| | | // 更新报价单 |
| | | export function updateSaleQuotation(data) { |
| | | return request({ url: '/crm/sale-quotation/update', method: 'put', data }) |
| | | } |
| | | |
| | | // 删除报价单 |
| | | export function deleteSaleQuotation(ids) { |
| | | return request({ url: '/crm/sale-quotation/delete', method: 'delete', params: { ids } }) |
| | | } |
| | | |
| | | // 获取报价单详情 |
| | | export function getSaleQuotation(id) { |
| | | return request({ url: '/crm/sale-quotation/get', method: 'get', params: { id } }) |
| | | } |
| | | |
| | | // 分页查询 |
| | | export function getSaleQuotationPage(params) { |
| | | return request({ url: '/crm/sale-quotation/page', method: 'get', params }) |
| | | } |
| | | |
| | | // 提交审批 |
| | | export function submitSaleQuotation(id, processDefinitionKey) { |
| | | return request({ url: '/crm/sale-quotation/submit', method: 'put', params: { id, processDefinitionKey } }) |
| | | } |
| | | |
| | | // 获取审批流程列表 |
| | | export function getSaleQuotationApproveProcessList() { |
| | | return request({ url: '/crm/sale-quotation/approve-process-list', method: 'get' }) |
| | | } |
| | | |
| | | // 转合同 |
| | | export function convertSaleQuotationToContract(id) { |
| | | return request({ url: '/crm/sale-quotation/convert-contract', method: 'post', params: { id } }) |
| | | } |
| | | ``` |
| | | |
| | | ### 11.4 业务流程 |
| | | |
| | | ``` |
| | | 商机(可选) → 销售报价(草稿) → 提交审批 → 审批中 → 审批通过 → 转合同 |
| | | ↓ |
| | | 审批拒绝 → 重新提交或修改 |
| | | ``` |
| | | |
| | | ### 11.5 注意事项 |
| | | |
| | | 1. 报价单和合同统一使用 **MDM 物料**(`MdmItemApi`),与 ERP 销售订单一致 |
| | | 2. BPM 分类编码 `sale_quotation_approve` 需要在 BPM 管理后台创建 |
| | | 3. 报价单转合同时,物料明细直接复制到合同产品表(字段一致) |
| | | 4. 报价单号前缀为 `QT`,格式:QT + yyyyMMdd + 6位自增序号 |
| | | 5. 商机关联为可选,报价单可以独立创建不依赖商机 |
| | | |
| | | ### 11.6 合同产品字段变更说明 |
| | | |
| | | 合同产品表 `crm_contract_product` 已改为使用 MDM 物料: |
| | | |
| | | | 原字段 | 新字段 | 说明 | |
| | | |--------|--------|------| |
| | | | `product_id` | `item_id` | MDM 物料编号 | |
| | | | `product_price` | `item_price` | 物料原价 | |
| | | | - | `item_unit_id` | 计量单位编号 | |
| | | |
| | | 前端表单需要使用 MDM 物料选择组件替代 CRM 产品选择组件。 |
| | | |
| | | --- |
| | | |
| | | ## 十、产出文档清单 |
| | | |
| | | | 文档 | 说明 | |
| | | |------|------| |
| | | | `docs/crm_erp_integration_frontend.md` | CRM 客户一体化前端文档 | |
| | | | `docs/crm_erp_integration_frontend.md` | 本文档 | |
| | | | `docs/crm_erp_integration_sql.sql` | SQL 变更脚本 | |
| | | | `docs/mdm_product_integration_frontend.md` | MDM 产品一体化前端文档 | |
| | | | `docs/contract_generate_order_frontend.md` | 合同生成订单前端文档 | |
| | | | `docs/sales_traceability_frontend.md` | 销售追溯前端文档 | |
| | | | `docs/marketing_report_frontend.md` | 营销报表前端文档 | |