| | |
| | | "Bash(dir /s /b \"C:\\\\Program Files \\(x86\\)\\\\MySQL\\\\*\\\\bin\\\\mysql.exe\")", |
| | | "Bash(cmd.exe /c \"where mysql\")", |
| | | "Bash(cmd.exe /c \"dir /s /b C:\\\\ProgramData\\\\MySQL\\\\*\\\\mysql.exe\")", |
| | | "Bash(cmd.exe /c \"dir /s /b C:\\\\Program Files\\\\MySQL\\\\*\\\\mysql.exe\")", |
| | | "Bash(mysql -u root -p123456 ruoyi-vue-pro -e \"SELECT id, name, code, deleted FROM bpm_category WHERE code = 'purchase_approve' OR name LIKE '%éè´å®¡æ ¸%';\")", |
| | | "Bash(dir /s /b \"D:\\\\ç马\\\\åé\\\\å端\\\\mom-pro2-after\\\\yudao-module-mdm-api\\\\*.java\")" |
| | | "Bash(cmd.exe /c \"dir /s /b C:\\\\Program Files\\\\MySQL\\\\*\\\\mysql.exe\")" |
| | | ], |
| | | "deny": [ |
| | | "Bash(rm -rf *)", |
| | |
| | | # 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` | è¥éæ¥è¡¨åç«¯ææ¡£ | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- é宿¥ä»·æ¨¡åæ°æ®åºè¡¨ |
| | | -- ============================================= |
| | | |
| | | -- 1. æ¥ä»·å主表 |
| | | CREATE TABLE IF NOT EXISTS `crm_sale_quotation` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ç¼å·', |
| | | `no` varchar(32) NOT NULL COMMENT 'æ¥ä»·åå·', |
| | | `name` varchar(128) NOT NULL COMMENT 'æ¥ä»·ååç§°', |
| | | `customer_id` bigint NOT NULL COMMENT '客æ·ç¼å·', |
| | | `business_id` bigint DEFAULT NULL COMMENT 'åæºç¼å·', |
| | | `contact_id` bigint DEFAULT NULL COMMENT 'è系人ç¼å·', |
| | | `owner_user_id` bigint NOT NULL COMMENT 'è´è´£äººç¼å·', |
| | | `status` int NOT NULL DEFAULT 0 COMMENT 'ç¶æï¼0è稿 10审æ¹ä¸ 20å·²éè¿ 30å·²æç» 40已忶 50已转ååï¼', |
| | | `process_instance_id` varchar(64) DEFAULT NULL COMMENT '工使µç¼å·', |
| | | `quotation_time` datetime DEFAULT NULL COMMENT 'æ¥ä»·æ¥æ', |
| | | `valid_until` datetime DEFAULT NULL COMMENT 'æææè³', |
| | | `total_product_price` decimal(24,6) DEFAULT NULL COMMENT 'äº§åæ»éé¢', |
| | | `discount_percent` decimal(10,2) DEFAULT 0 COMMENT 'æ´åææ£(%)', |
| | | `discount_price` decimal(24,6) DEFAULT 0 COMMENT '伿 éé¢', |
| | | `total_price` decimal(24,6) DEFAULT NULL COMMENT 'æ¥ä»·æ»éé¢', |
| | | `tax_rate` decimal(10,2) DEFAULT NULL COMMENT 'ç¨ç(%)', |
| | | `tax_price` decimal(24,6) DEFAULT NULL COMMENT 'ç¨é¢', |
| | | `contract_id` bigint DEFAULT NULL COMMENT 'å
³èååç¼å·', |
| | | `contract_no` varchar(32) DEFAULT NULL COMMENT 'å
³èåååå·', |
| | | `file_url` varchar(512) DEFAULT NULL COMMENT 'éä»¶å°å', |
| | | `remark` varchar(1024) DEFAULT NULL COMMENT '夿³¨', |
| | | `creator` varchar(64) DEFAULT NULL COMMENT 'å建è
', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `updater` varchar(64) DEFAULT NULL COMMENT 'æ´æ°è
', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', |
| | | `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT 'ç§æ·ç¼å·', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_no` (`no`), |
| | | KEY `idx_customer_id` (`customer_id`), |
| | | KEY `idx_business_id` (`business_id`), |
| | | KEY `idx_owner_user_id` (`owner_user_id`) |
| | | ) ENGINE=InnoDB COMMENT='CRM é宿¥ä»·å'; |
| | | |
| | | -- 2. æ¥ä»·å产åæç»è¡¨ |
| | | CREATE TABLE IF NOT EXISTS `crm_sale_quotation_product` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ç¼å·', |
| | | `quotation_id` bigint NOT NULL COMMENT 'æ¥ä»·åç¼å·', |
| | | `item_id` bigint NOT NULL COMMENT 'MDMç©æç¼å·', |
| | | `item_unit_id` bigint DEFAULT NULL COMMENT '计éåä½ç¼å·', |
| | | `item_price` decimal(24,6) NOT NULL COMMENT 'ç©æåä»·ï¼åä½ï¼', |
| | | `quotation_price` decimal(24,6) NOT NULL COMMENT 'æ¥ä»·åä»·', |
| | | `count` decimal(24,6) NOT NULL COMMENT 'æ°é', |
| | | `total_price` decimal(24,6) NOT NULL COMMENT 'å°è®¡éé¢', |
| | | `tax_percent` decimal(10,2) DEFAULT NULL COMMENT 'ç¨ç(%)', |
| | | `tax_price` decimal(24,6) DEFAULT NULL COMMENT 'ç¨é¢', |
| | | `remark` varchar(256) DEFAULT NULL COMMENT '夿³¨', |
| | | `creator` varchar(64) DEFAULT NULL COMMENT 'å建è
', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `updater` varchar(64) DEFAULT NULL COMMENT 'æ´æ°è
', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', |
| | | `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT 'ç§æ·ç¼å·', |
| | | PRIMARY KEY (`id`), |
| | | KEY `idx_quotation_id` (`quotation_id`) |
| | | ) ENGINE=InnoDB COMMENT='CRM é宿¥ä»·å产åæç»'; |
| | | |
| | | -- ============================================= |
| | | -- åå产å表修æ¹ï¼ä½¿ç¨ MDM ç©æï¼ |
| | | -- ============================================= |
| | | |
| | | -- 3. ä¿®æ¹åå产åè¡¨ç»æ |
| | | -- 注æï¼å¦æå·²ææ°æ®ï¼éè¦å
å¤çæ°æ®è¿ç§» |
| | | -- ALTER TABLE `crm_contract_product` DROP COLUMN `product_id`; |
| | | -- ALTER TABLE `crm_contract_product` DROP COLUMN `product_price`; |
| | | |
| | | ALTER TABLE `crm_contract_product` |
| | | ADD COLUMN `item_id` bigint DEFAULT NULL COMMENT 'MDMç©æç¼å·' AFTER `contract_id`, |
| | | ADD COLUMN `item_unit_id` bigint DEFAULT NULL COMMENT '计éåä½ç¼å·' AFTER `item_id`, |
| | | ADD COLUMN `item_price` decimal(24,6) DEFAULT NULL COMMENT 'ç©æåä»·ï¼åä½ï¼' AFTER `item_unit_id`; |
| | | |
| | | -- æ°æ®è¿ç§»ï¼å° product_id éå½å为 item_idï¼å¦æéè¦ä¿çåææ°æ®ï¼ |
| | | -- UPDATE `crm_contract_product` SET `item_id` = `product_id`, `item_price` = `product_price`; |
| | | |
| | | -- ============================================= |
| | | -- BPM åç±»ï¼éè¦å¨ BPM 管çåå°å建ï¼è¿éæä¾ SQL åèï¼ |
| | | -- ============================================= |
| | | |
| | | -- INSERT INTO `bpm_category` (`name`, `code`, `description`, `status`, `sort`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | -- VALUES ('é宿¥ä»·å®¡æ¹', 'sale_quotation_approve', 'é宿¥ä»·åå®¡æ¹æµç¨åç±»', 0, 0, '1', NOW(), '1', NOW(), b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # CRM ä¸ ERP é宿µç¨å
¨æ¯å¾ |
| | | |
| | | ## ä¸ãæ´ä½ä¸å¡æµç¨ |
| | | |
| | | ``` |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â CRM éå®çº¿ç´¢ç®¡çï¼åç«¯ï¼ â |
| | | ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ⤠|
| | | â â |
| | | â 线索(clue) ââ转åââ⺠客æ·(customer) ââè·è¿âââº åæº(business) â |
| | | â â â â â |
| | | â â â â â |
| | | â â¼ â¼ â¼ â |
| | | â 线索å
¥åº 客æ·å
¬æµ·æ± åæºç¶ææ¨è¿ â |
| | | â 线索åé
客æ·éå® èµ¢å/è¾å â |
| | | â â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â |
| | | â¼ |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â CRM é宿¥ä»·ä¸ååï¼æ°å¢ï¼ â |
| | | ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ⤠|
| | | â â |
| | | â é宿¥ä»·(quotation) ââBPM审æ¹ââ⺠åå(contract) â |
| | | â â â â â |
| | | â â â â â |
| | | â â¼ â¼ â¼ â |
| | | â å建æ¥ä»·å 审æ¹éè¿ ååç¾è®¢ â |
| | | â éæ©MDMç©æ å®¡æ¹æç» ååå®¡æ¹ â |
| | | â æäº¤å®¡æ¹ å®¡æ¹åæ¶ çæè®¢å â |
| | | â 转为åå å
³èåæº â |
| | | â â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â |
| | | â¼ |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â ERP éå®è®¢å䏿§è¡ â |
| | | ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ⤠|
| | | â â |
| | | â éå®è®¢å(order) ââ⺠éå®åºåº(out) ââ⺠éå®éè´§(return) â |
| | | â â â â â |
| | | â â â â â |
| | | â â¼ â¼ â¼ â |
| | | â 订åå®¡æ¹ åºåºå®¡æ ¸ éè´§å®¡æ ¸ â |
| | | â å
³èåå åºåºåå· éè´§åå· â |
| | | â éæ©MDMç©æ åºåºæ°é éè´§æ°é â |
| | | â â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â |
| | | â¼ |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | â ERP è´¢å¡æ¶æ¬¾ â |
| | | ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ⤠|
| | | â â |
| | | â æ¶æ¬¾å(receipt) âââº æ¶æ¬¾æ ¸é â |
| | | â â â |
| | | â â¼ â |
| | | â å
³èéå®è®¢å â |
| | | â å
³èCRMå®¢æ· â |
| | | â æ¶æ¬¾éé¢ â |
| | | â â |
| | | âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## äºãæ°æ®æµè½¬è¯¦è§£ |
| | | |
| | | ### 2.1 å®¢æ·æ°æ®ï¼ç»ä¸æ¥æºï¼ |
| | | |
| | | | 模å | æ°æ®æ¥æº | 说æ | |
| | | |------|----------|------| |
| | | | CRM å®¢æ· | `crm_customer` | **å¯ä¸å®¢æ·æ°æ®æº** | |
| | | | ERP éå®è®¢å | `crm_customer` (éè¿ API) | ä½¿ç¨ CRM å®¢æ· | |
| | | | ERP éå®åºåº | `crm_customer` (éè¿ API) | ä½¿ç¨ CRM å®¢æ· | |
| | | | ERP éå®éè´§ | `crm_customer` (éè¿ API) | ä½¿ç¨ CRM å®¢æ· | |
| | | | ERP æ¶æ¬¾å | `crm_customer` (éè¿ API) | ä½¿ç¨ CRM å®¢æ· | |
| | | |
| | | **å
³é® API**ï¼`CrmCustomerApi` |
| | | - `/rpc-api/crm/customer/get` - è·ååä¸ªå®¢æ· |
| | | - `/rpc-api/crm/customer/list-by-ids` - æ¹éè·åå®¢æ· |
| | | - `/rpc-api/crm/customer/validate` - æ ¡éªå®¢æ·åå¨ |
| | | |
| | | ### 2.2 ç©ææ°æ®ï¼ç»ä¸æ¥æºï¼ |
| | | |
| | | | 模å | æ°æ®æ¥æº | 说æ | |
| | | |------|----------|------| |
| | | | MDM ç©æ | `mdm_item` | **å¯ä¸ç©ææ°æ®æº** | |
| | | | é宿¥ä»· | `mdm_item` (éè¿ API) | ä½¿ç¨ MDM ç©æ | |
| | | | CRM åå | `mdm_item` (éè¿ API) | ä½¿ç¨ MDM ç©æ | |
| | | | ERP éå®è®¢å | `mdm_item` (éè¿ API) | ä½¿ç¨ MDM ç©æ | |
| | | |
| | | **å
³é® API**ï¼`MdmItemApi` |
| | | - `/rpc-api/mdm/item/get` - è·åç©æ |
| | | - `/rpc-api/mdm/item/list` - æ¹éè·åç©æ |
| | | - `/rpc-api/mdm/item/validate-item-exists` - æ ¡éªç©æåå¨ |
| | | |
| | | --- |
| | | |
| | | ## ä¸ãè¯¦ç»æµç¨å¾ |
| | | |
| | | ### 3.1 CRM é宿¥ä»·æµç¨ï¼æ°å¢ï¼ |
| | | |
| | | ``` |
| | | ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ |
| | | â å建æ¥ä»·å ââââââºâ æäº¤å®¡æ¹ ââââââºâ BPMå®¡æ¹ ââââââºâ 审æ¹éè¿ â |
| | | â â â â â â â â |
| | | â éæ©å®¢æ· â â éæ©æµç¨ â â 审æ¹ä¸ç¶æ â â å·²éè¿ç¶æ â |
| | | â éæ©MDMç©æ â â 卿鿩 â â status=10 â â status=20 â |
| | | â 填忥价éé¢ â â â â â â â |
| | | â å¯å
³èåæº â â â â â â â |
| | | ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ |
| | | â |
| | | â¼ |
| | | ââââââââââââââââ ââââââââââââââââ |
| | | â 转为åå ââââââºâ ååçæ â |
| | | â â â â |
| | | â ç©æç´æ¥å¤å¶ â â ååå®¡æ¹æµç¨ â |
| | | â åæ®µä¸è´ â â çæéå®è®¢å â |
| | | â ç¶æ=50 â â â |
| | | ââââââââââââââââ ââââââââââââââââ |
| | | ``` |
| | | |
| | | **ç¶ææµè½¬**ï¼ |
| | | |
| | | | ç¶æå¼ | ç¶æå | å¯æä½ | |
| | | |--------|--------|--------| |
| | | | 0 | è稿 | ç¼è¾ãå é¤ãæäº¤å®¡æ¹ | |
| | | | 10 | 审æ¹ä¸ | æ | |
| | | | 20 | å·²éè¿ | 转åå | |
| | | | 30 | å·²æç» | éæ°æäº¤ | |
| | | | 40 | 已忶 | æ | |
| | | | 50 | 已转åå | æ¥çåå | |
| | | |
| | | ### 3.2 CRM ååæµç¨ |
| | | |
| | | ``` |
| | | ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ |
| | | â å建åå ââââââºâ æäº¤å®¡æ¹ ââââââºâ BPMå®¡æ¹ ââââââºâ 审æ¹éè¿ â |
| | | â â â â â â â â |
| | | â éæ©å®¢æ· â â åºå®æµç¨Key â â 审æ¹ä¸ç¶æ â â å·²éè¿ç¶æ â |
| | | â éæ©MDMç©æ â â crm-contract â â auditStatus â â auditStatus â |
| | | â ååéé¢ â â -audit â â =10 â â =20 â |
| | | â å¯å
³èåæº â â â â â â â |
| | | ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ |
| | | â |
| | | â¼ |
| | | ââââââââââââââââ |
| | | â çæéå®è®¢å â |
| | | â â |
| | | â ERP订åå建 â |
| | | â å
³èååç¼å· â |
| | | â ç©ææç»å¤å¶ â |
| | | ââââââââââââââââ |
| | | ``` |
| | | |
| | | ### 3.3 ERP éå®è®¢åæµç¨ |
| | | |
| | | ``` |
| | | ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ |
| | | â å建订å ââââââºâ 订åå®¡æ¹ ââââââºâ 审æ¹éè¿ ââââââºâ éå®åºåº â |
| | | â â â â â â â â |
| | | â CRMå®¢æ· â â ERPå®¡æ¹æµç¨ â â 坿§è¡åºåº â â åºåºåçæ â |
| | | â MDMç©æ â â â â â â åºåºæ°é累计 â |
| | | â å
³èåå(å¯) â â â â â â â |
| | | â 订åéé¢ â â â â â â â |
| | | ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ |
| | | â |
| | | â¼ |
| | | ââââââââââââââââ |
| | | â æ¶æ¬¾æ ¸é â |
| | | â â |
| | | â æ¶æ¬¾åå建 â |
| | | â å
³è订å â |
| | | â æ¶æ¬¾éé¢ç´¯è®¡ â |
| | | ââââââââââââââââ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## åãæ¨¡åå
³ç³»å¾ |
| | | |
| | | ``` |
| | | âââââââââââââââââââââââââââââââââââââââ |
| | | â MDM ä¸»æ°æ®æ¨¡å â |
| | | â â |
| | | â âââââââââââââââ âââââââââââââââ â |
| | | â â mdm_item â â mdm_unit â â |
| | | â â (ç©æ) â â (åä½) â â |
| | | â âââââââââââââââ âââââââââââââââ â |
| | | â â |
| | | ââââââââââââââââ¬âââââââââââââââââââââââ |
| | | â |
| | | â MdmItemApi |
| | | â |
| | | ââââââââââââââââââââââââââ¼âââââââââââââââââââââââââ |
| | | â â â |
| | | â¼ â¼ â¼ |
| | | âââââââââââââââââââ âââââââââââââââââââ âââââââââââââââââââ |
| | | â CRM 模å â â ERP 模å â â BPM 模å â |
| | | â â â â â â |
| | | â âââââââââââââââ â â âââââââââââââââ â â âââââââââââââââ â |
| | | â âcrm_customer â â â âerp_sale_ â â â â bpm_categoryâ â |
| | | â â(客æ·) âââââââ⤠â order â â â â (审æ¹åç±») â â |
| | | â âââââââââââââââ â â â(éå®è®¢å) â â â âââââââââââââââ â |
| | | â â â âââââââââââââââ â â â |
| | | â âââââââââââââââ â â âââââââââââââââ â â âââââââââââââââ â |
| | | â âcrm_sale_ â â â âerp_sale_ â â â â bpm_process â â |
| | | â â quotation â â â â out â â â â _definition â â |
| | | â â(é宿¥ä»·) â â â â(éå®åºåº) â â â â (æµç¨å®ä¹) â â |
| | | â âââââââââââââââ â â âââââââââââââââ â â âââââââââââââââ â |
| | | â â â â â â |
| | | â âââââââââââââââ â â âââââââââââââââ â â â |
| | | â âcrm_contract â â â âerp_finance_ â â â â |
| | | â â(åå) âââââââ⤠â receipt â â â â |
| | | â âââââââââââââââ â â â(æ¶æ¬¾å) â â â â |
| | | â â â âââââââââââââââ â â â |
| | | â â â â â â |
| | | âââââââââââââââââââ âââââââââââââââââââ âââââââââââââââââââ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## äºãå
³é®è¡¨ç»æ |
| | | |
| | | ### 5.1 CRM 表 |
| | | |
| | | | 表å | 说æ | å
³é®å段 | |
| | | |------|------|----------| |
| | | | `crm_customer` | 客æ·ä¸»è¡¨ | id, name, owner_user_id | |
| | | | `crm_business` | åæºä¸»è¡¨ | id, customer_id, total_price, status_id | |
| | | | `crm_sale_quotation` | é宿¥ä»· | id, no, customer_id, business_id, status | |
| | | | `crm_sale_quotation_product` | æ¥ä»·æç» | quotation_id, **item_id**(MDMç©æ) | |
| | | | `crm_contract` | åå主表 | id, no, customer_id, business_id, audit_status | |
| | | | `crm_contract_product` | ååæç» | contract_id, **item_id**(MDMç©æ) | |
| | | |
| | | ### 5.2 ERP 表 |
| | | |
| | | | 表å | 说æ | å
³é®å段 | |
| | | |------|------|----------| |
| | | | `erp_sale_order` | éå®è®¢å | id, no, **customer_id**(CRM), contract_id | |
| | | | `erp_sale_order_items` | 订åæç» | order_id, **item_id**(MDMç©æ) | |
| | | | `erp_sale_out` | éå®åºåº | id, order_id, customer_id | |
| | | | `erp_finance_receipt` | æ¶æ¬¾å | id, **customer_id**(CRM) | |
| | | |
| | | --- |
| | | |
| | | ## å
ãAPI è°ç¨å
³ç³» |
| | | |
| | | ### 6.1 CRM è°ç¨ MDM |
| | | |
| | | ``` |
| | | CrmSaleQuotationServiceImpl |
| | | ââ⺠MdmItemApi.validateItemExists(itemId) // æ ¡éªç©æåå¨ |
| | | ââ⺠MdmItemApi.getItemMap(itemIds) // æ¹éè·åç©æä¿¡æ¯ |
| | | |
| | | CrmContractServiceImpl |
| | | ââ⺠MdmItemApi.validateItemExists(itemId) // æ ¡éªç©æåå¨ |
| | | ââ⺠MdmItemApi.getItemMap(itemIds) // æ¹éè·åç©æä¿¡æ¯ |
| | | ``` |
| | | |
| | | ### 6.2 ERP è°ç¨ CRM |
| | | |
| | | ``` |
| | | ErpSaleOrderServiceImpl |
| | | ââ⺠CrmCustomerApi.getCustomer(customerId) // è·åCRMå®¢æ· |
| | | ââ⺠CrmCustomerApi.validateCustomer(customerId) // æ ¡éªå®¢æ·åå¨ |
| | | |
| | | ErpFinanceReceiptServiceImpl |
| | | ââ⺠CrmCustomerApi.getCustomer(customerId) // è·åCRMå®¢æ· |
| | | ``` |
| | | |
| | | ### 6.3 CRM è°ç¨ BPM |
| | | |
| | | ``` |
| | | CrmSaleQuotationServiceImpl |
| | | ââ⺠BpmProcessInstanceApi.createProcessInstance() // åå»ºå®¡æ¹æµç¨å®ä¾ |
| | | ââ⺠BpmCategoryService.getCategoryMap() // è·å审æ¹åç±» |
| | | ââ⺠BpmProcessDefinitionService.getProcessDefinitionInfoListByCategory() // è·åæµç¨å®ä¹ |
| | | |
| | | CrmContractServiceImpl |
| | | ââ⺠BpmProcessInstanceApi.createProcessInstance() // åå»ºå®¡æ¹æµç¨å®ä¾ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ä¸ãç¶ææä¸¾å¯¹ç
§ |
| | | |
| | | ### 7.1 é宿¥ä»·ç¶æ (CrmSaleQuotationStatusEnum) |
| | | |
| | | | å¼ | æä¸¾ | 说æ | |
| | | |---|------|------| |
| | | | 0 | DRAFT | è稿 | |
| | | | 10 | PROCESS | 审æ¹ä¸ | |
| | | | 20 | APPROVE | å·²éè¿ | |
| | | | 30 | REJECT | å·²æç» | |
| | | | 40 | CANCEL | 已忶 | |
| | | | 50 | CONVERTED | 已转åå | |
| | | |
| | | ### 7.2 åå审æ¹ç¶æ (CrmAuditStatusEnum) |
| | | |
| | | | å¼ | æä¸¾ | 说æ | |
| | | |---|------|------| |
| | | | 0 | DRAFT | æªæäº¤ | |
| | | | 10 | PROCESS | 审æ¹ä¸ | |
| | | | 20 | APPROVE | å®¡æ ¸éè¿ | |
| | | | 30 | REJECT | å®¡æ ¸ä¸éè¿ | |
| | | | 40 | CANCEL | 已忶 | |
| | | |
| | | ### 7.3 BPM ä»»å¡ç¶æ (BpmTaskStatusEnum) |
| | | |
| | | | å¼ | æä¸¾ | 说æ | |
| | | |---|------|------| |
| | | | 2 | APPROVE | 审æ¹éè¿ | |
| | | | 3 | REJECT | 审æ¹ä¸éè¿ | |
| | | | 4 | CANCEL | 已忶 | |
| | | |
| | | **ç¶ææ å°**ï¼BPMç¶æ â ä¸å¡ç¶æ |
| | | - `2(APPROVE)` â `20(å·²éè¿)` |
| | | - `3(REJECT)` â `30(å·²æç»)` |
| | | - `4(CANCEL)` â `40(已忶)` |
| | | |
| | | --- |
| | | |
| | | ## å
«ãæä½æé说æ |
| | | |
| | | ### 8.1 é宿¥ä»·æä½æé |
| | | |
| | | | ç¶æ | ç¼è¾ | å é¤ | æäº¤å®¡æ¹ | 转åå | |
| | | |------|------|------|----------|--------| |
| | | | è稿(0) | â
| â
| â
| â | |
| | | | 审æ¹ä¸(10) | â | â | â | â | |
| | | | å·²éè¿(20) | â | â | â | â
| |
| | | | å·²æç»(30) | â
| â | â
| â | |
| | | | 已转åå(50) | â | â | â | â | |
| | | |
| | | ### 8.2 ååæä½æé |
| | | |
| | | | ç¶æ | ç¼è¾ | å é¤ | æäº¤å®¡æ¹ | çæè®¢å | |
| | | |------|------|------|----------|----------| |
| | | | æªæäº¤(0) | â
| â
| â
| â | |
| | | | 审æ¹ä¸(10) | â
| â | â | â | |
| | | | å·²éè¿(20) | â | â | â | â
| |
| | | | å·²æç»(30) | â
| â | â
| â | |
| | | |
| | | --- |
| | | |
| | | ## ä¹ã宿´ä¸å¡é¾æ¡ç¤ºä¾ |
| | | |
| | | ``` |
| | | 示ä¾ï¼ä¸ä¸ªå®¢æ·ä»çº¿ç´¢å°å款ç宿´æµç¨ |
| | | |
| | | 1. ãçº¿ç´¢é¶æ®µã |
| | | - éå®äººåå½å
¥çº¿ç´¢ â çº¿ç´¢ç¶æï¼å¾
è·è¿ |
| | | - 线索转åä¸ºå®¢æ· â 客æ·å建æå |
| | | |
| | | 2. ã客æ·è·è¿ã |
| | | - å建è·è¿è®°å½ â è®°å½æ²éå
容 |
| | | - åå»ºåæº â åæºéé¢ï¼100,000å
|
| | | - åæºç¶ææ¨è¿ â èµ¢å |
| | | |
| | | 3. ãæ¥ä»·é¶æ®µã(æ°å¢) |
| | | - å建é宿¥ä»·å â æ¥ä»·éé¢ï¼95,000å
|
| | | - éæ©MDMç©æ â ç©æAãç©æB |
| | | - æäº¤å®¡æ¹ â éæ©å®¡æ¹æµç¨ |
| | | - 审æ¹éè¿ â ç¶æï¼å·²éè¿ |
| | | |
| | | 4. ãååé¶æ®µã |
| | | - æ¥ä»·å转åå â ååèªå¨å建 |
| | | - ååå®¡æ¹ â BPMå®¡æ¹æµç¨ |
| | | - 审æ¹éè¿ â ç¶æï¼å®¡æ ¸éè¿ |
| | | |
| | | 5. ã订åé¶æ®µã |
| | | - ååçæéå®è®¢å â ERP订åå建 |
| | | - 订åå
³èåå â ååç¼å·è®°å½ |
| | | - 订åå®¡æ¹ â ERPå®¡æ¹æµç¨ |
| | | - 审æ¹éè¿ â 坿§è¡åºåº |
| | | |
| | | 6. ãåºåºé¶æ®µã |
| | | - å建éå®åºåºå â éæ©ä»åº |
| | | - åºåºå®¡æ ¸ â åºåºå®æ |
| | | - åºåºæ°éåå订å |
| | | |
| | | 7. ãæ¶æ¬¾é¶æ®µã |
| | | - åå»ºæ¶æ¬¾å â å
³è订å |
| | | - æ¶æ¬¾éé¢ï¼50,000å
|
| | | - æ¶æ¬¾æ ¸é â è®¢åæ¶æ¬¾ç´¯è®¡ |
| | | |
| | | 8. ã追溯æ¥è¯¢ã |
| | | - æ¥è¯¢é宿µç¨è¿½æº¯ â æ¾ç¤ºå®æ´é¾æ¡ |
| | | - åæº â æ¥ä»· â åå â 订å â åºåº â æ¶æ¬¾ |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## åãæ°æ®è¿ç§»æ³¨æäºé¡¹ |
| | | |
| | | ### 10.1 åå产åè¡¨åæ´ |
| | | |
| | | åè¡¨ä½¿ç¨ CRM 产åï¼ç°æ¹ä¸º MDM ç©æï¼ |
| | | |
| | | ```sql |
| | | -- æ°å¢å段 |
| | | ALTER TABLE crm_contract_product |
| | | ADD COLUMN item_id bigint COMMENT 'MDMç©æç¼å·', |
| | | ADD COLUMN item_unit_id bigint COMMENT '计éåä½ç¼å·', |
| | | ADD COLUMN item_price decimal(24,6) COMMENT 'ç©æåä»·'; |
| | | |
| | | -- æ°æ®è¿ç§»ï¼å¦æææ å°å
³ç³»ï¼ |
| | | -- UPDATE crm_contract_product SET item_id = product_id; |
| | | ``` |
| | | |
| | | ### 10.2 BPM åç±»å建 |
| | | |
| | | éè¦å¨ BPM 管çåå°å建审æ¹åç±»ï¼ |
| | | |
| | | ```sql |
| | | INSERT INTO bpm_category (name, code, description, status, sort) |
| | | VALUES ('é宿¥ä»·å®¡æ¹', 'sale_quotation_approve', 'é宿¥ä»·åå®¡æ¹æµç¨åç±»', 0, 0); |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## åä¸ãæ»ç» |
| | | |
| | | ### ç»ä¸æ°æ®æº |
| | | |
| | | | æ°æ®ç±»å | ç»ä¸æ¥æº | ä½¿ç¨æ¨¡å | |
| | | |----------|----------|----------| |
| | | | å®¢æ· | CRM (`crm_customer`) | CRMãERPéå®å
¨æµç¨ | |
| | | | ç©æ | MDM (`mdm_item`) | é宿¥ä»·ãååãERP订å | |
| | | |
| | | ### æ ¸å¿æµç¨ |
| | | |
| | | ``` |
| | | 客æ·(CRM) â åæº(CRM) â æ¥ä»·(CRM+BPM) â åå(CRM+BPM) â 订å(ERP) â åºåº(ERP) â æ¶æ¬¾(ERP) |
| | | ``` |
| | | |
| | | ### å
³é®æ¹è¿ |
| | | |
| | | 1. **æ°æ®ç»ä¸**ï¼å®¢æ·åç©ææ°æ®ä¸å忣ï¼ç»ä¸ç®¡ç |
| | | 2. **æµç¨è¡æ¥**ï¼æ¥ä»· â åå â 订åï¼æ°æ®æ ç¼æµè½¬ |
| | | 3. **BPMéæ**ï¼æ¥ä»·ååå齿¯æå®¡æ¹æµç¨ |
| | | 4. **è¿½æº¯å®æ´**ï¼ä»åæºå°å款å
¨é¾æ¡å¯è¿½æº¯ |
| | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") |
| | | private Long id; |
| | | |
| | | @Schema(description = "产åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") |
| | | private Long productId; |
| | | @Schema(description = "产ååç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "æå") |
| | | private String productName; |
| | | @Schema(description = "äº§åæ¡ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") |
| | | private String productNo; |
| | | @Schema(description = "产ååä½", requiredMode = Schema.RequiredMode.REQUIRED, example = "æå") |
| | | private Integer productUnit; |
| | | @Schema(description = "MDM ç©æç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") |
| | | private Long itemId; |
| | | @Schema(description = "ç©æç¼ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001") |
| | | private String itemCode; |
| | | @Schema(description = "ç©æåç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "ç©æA") |
| | | private String itemName; |
| | | @Schema(description = "ç©ææ¡ç ", example = "BAR001") |
| | | private String itemBarCode; |
| | | @Schema(description = "计éåä½ç¼å·", example = "1") |
| | | private Long itemUnitId; |
| | | @Schema(description = "计éåä½åç§°", example = "个") |
| | | private String itemUnitName; |
| | | |
| | | @Schema(description = "产ååä»·", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") |
| | | private BigDecimal productPrice; |
| | | @Schema(description = "ç©æåä»·", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") |
| | | private BigDecimal itemPrice; |
| | | |
| | | @Schema(description = "ååä»·æ ¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") |
| | | private BigDecimal contractPrice; |
| | |
| | | @AllArgsConstructor |
| | | public static class Product { |
| | | |
| | | @Schema(description = "产åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") |
| | | @NotNull(message = "产åç¼å·ä¸è½ä¸ºç©º") |
| | | private Long productId; |
| | | @Schema(description = "MDM ç©æç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") |
| | | @NotNull(message = "ç©æç¼å·ä¸è½ä¸ºç©º") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "产ååä»·", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") |
| | | @NotNull(message = "产ååä»·ä¸è½ä¸ºç©º") |
| | | private BigDecimal productPrice; |
| | | @Schema(description = "ç©æåä»·", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") |
| | | @NotNull(message = "ç©æåä»·ä¸è½ä¸ºç©º") |
| | | private BigDecimal itemPrice; |
| | | |
| | | @Schema(description = "ååä»·æ ¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") |
| | | @NotNull(message = "ååä»·æ ¼ä¸è½ä¸ºç©º") |
| | |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Stream; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | |
| | | PageResult<CrmFollowUpRecordDO> pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO); |
| | | // 1.1 æ¥è¯¢è系人ååæº |
| | | Map<Long, CrmContactDO> contactMap = contactService.getContactMap( |
| | | convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())); |
| | | convertSetByFlatMap(pageResult.getList(), item -> { |
| | | List<Long> contactIds = item.getContactIds(); |
| | | return contactIds != null ? contactIds.stream() : Stream.empty(); |
| | | })); |
| | | Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap( |
| | | convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())); |
| | | convertSetByFlatMap(pageResult.getList(), item -> { |
| | | List<Long> businessIds = item.getBusinessIds(); |
| | | return businessIds != null ? businessIds.stream() : Stream.empty(); |
| | | })); |
| | | // 1.2 æ¥è¯¢ç¨æ· |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(pageResult.getList(), item -> Long.valueOf(item.getCreator()))); |
| | | convertSet(pageResult.getList(), item -> { |
| | | String creator = item.getCreator(); |
| | | return creator != null && !creator.isEmpty() ? Long.valueOf(creator) : null; |
| | | })); |
| | | // 2. æ¼æ¥æ°æ® |
| | | PageResult<CrmFollowUpRecordRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> { |
| | | // 2.1 设置è系人ååæºä¿¡æ¯ |
| | | record.setBusinesses(new ArrayList<>()).setContacts(new ArrayList<>()); |
| | | record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, contact -> |
| | | record.getContacts().add(new CrmBusinessRespVO().setId(contact.getId()).setName(contact.getName())))); |
| | | record.getBusinessIds().forEach(id -> MapUtils.findAndThen(businessMap, id, business -> |
| | | record.getBusinesses().add(new CrmBusinessRespVO().setId(business.getId()).setName(business.getName())))); |
| | | List<Long> contactIds = record.getContactIds(); |
| | | if (contactIds != null) { |
| | | contactIds.forEach(id -> MapUtils.findAndThen(contactMap, id, contact -> |
| | | record.getContacts().add(new CrmBusinessRespVO().setId(contact.getId()).setName(contact.getName())))); |
| | | } |
| | | List<Long> businessIds = record.getBusinessIds(); |
| | | if (businessIds != null) { |
| | | businessIds.forEach(id -> MapUtils.findAndThen(businessMap, id, business -> |
| | | record.getBusinesses().add(new CrmBusinessRespVO().setId(business.getId()).setName(business.getName())))); |
| | | } |
| | | // 2.2 è®¾ç½®ç¨æ·ä¿¡æ¯ |
| | | MapUtils.findAndThen(userMap, Long.valueOf(record.getCreator()), user -> record.setCreatorName(user.getNickname())); |
| | | String creator = record.getCreator(); |
| | | if (creator != null && !creator.isEmpty()) { |
| | | MapUtils.findAndThen(userMap, Long.valueOf(creator), user -> record.setCreatorName(user.getNickname())); |
| | | } |
| | | }); |
| | | return success(voPageResult); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.controller.admin.quotation; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationPageReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationRespVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationSaveReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationProductDO; |
| | | import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管çåå° - CRM é宿¥ä»·å") |
| | | @RestController |
| | | @RequestMapping("/crm/sale-quotation") |
| | | @Validated |
| | | public class CrmSaleQuotationController { |
| | | |
| | | @Resource |
| | | private CrmSaleQuotationService saleQuotationService; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建é宿¥ä»·å") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:create')") |
| | | public CommonResult<Long> createSaleQuotation(@Valid @RequestBody CrmSaleQuotationSaveReqVO createReqVO) { |
| | | return success(saleQuotationService.createSaleQuotation(createReqVO, getLoginUserId())); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°é宿¥ä»·å") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:update')") |
| | | public CommonResult<Boolean> updateSaleQuotation(@Valid @RequestBody CrmSaleQuotationSaveReqVO updateReqVO) { |
| | | saleQuotationService.updateSaleQuotation(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤é宿¥ä»·å") |
| | | @Parameter(name = "ids", description = "ç¼å·æ°ç»", required = true) |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:delete')") |
| | | public CommonResult<Boolean> deleteSaleQuotation(@RequestParam("ids") List<Long> ids) { |
| | | saleQuotationService.deleteSaleQuotation(ids); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾é宿¥ä»·å") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:query')") |
| | | public CommonResult<CrmSaleQuotationRespVO> getSaleQuotation(@RequestParam("id") Long id) { |
| | | CrmSaleQuotationDO saleQuotation = saleQuotationService.getSaleQuotation(id); |
| | | if (saleQuotation == null) { |
| | | return success(null); |
| | | } |
| | | List<CrmSaleQuotationProductDO> productList = saleQuotationService.getSaleQuotationProductListByQuotationId(id); |
| | | return success(buildSaleQuotationRespVO(saleQuotation, productList)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾é宿¥ä»·åå页") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:query')") |
| | | public CommonResult<PageResult<CrmSaleQuotationRespVO>> getSaleQuotationPage(@Valid CrmSaleQuotationPageReqVO pageReqVO) { |
| | | PageResult<CrmSaleQuotationDO> pageResult = saleQuotationService.getSaleQuotationPage(pageReqVO, getLoginUserId()); |
| | | return success(buildSaleQuotationRespVOPageResult(pageResult)); |
| | | } |
| | | |
| | | @GetMapping("/page-by-customer") |
| | | @Operation(summary = "è·å¾é宿¥ä»·åå页ï¼åºäºå®¢æ·ï¼") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:query')") |
| | | public CommonResult<PageResult<CrmSaleQuotationRespVO>> getSaleQuotationPageByCustomerId(@Valid CrmSaleQuotationPageReqVO pageReqVO) { |
| | | PageResult<CrmSaleQuotationDO> pageResult = saleQuotationService.getSaleQuotationPageByCustomerId(pageReqVO); |
| | | return success(buildSaleQuotationRespVOPageResult(pageResult)); |
| | | } |
| | | |
| | | @GetMapping("/page-by-business") |
| | | @Operation(summary = "è·å¾é宿¥ä»·åå页ï¼åºäºåæºï¼") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:query')") |
| | | public CommonResult<PageResult<CrmSaleQuotationRespVO>> getSaleQuotationPageByBusinessId(@Valid CrmSaleQuotationPageReqVO pageReqVO) { |
| | | PageResult<CrmSaleQuotationDO> pageResult = saleQuotationService.getSaleQuotationPageByBusinessId(pageReqVO); |
| | | return success(buildSaleQuotationRespVOPageResult(pageResult)); |
| | | } |
| | | |
| | | @GetMapping("/export-excel") |
| | | @Operation(summary = "导åºé宿¥ä»·å Excel") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:export')") |
| | | @ApiAccessLog(operateType = EXPORT) |
| | | public void exportSaleQuotationExcel(@Valid CrmSaleQuotationPageReqVO pageReqVO, |
| | | HttpServletResponse response) throws IOException { |
| | | pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); |
| | | List<CrmSaleQuotationRespVO> list = buildSaleQuotationRespVOPageResult( |
| | | saleQuotationService.getSaleQuotationPage(pageReqVO, getLoginUserId())).getList(); |
| | | ExcelUtils.write(response, "é宿¥ä»·å.xls", "æ°æ®", CrmSaleQuotationRespVO.class, list); |
| | | } |
| | | |
| | | @PutMapping("/submit") |
| | | @Operation(summary = "æäº¤é宿¥ä»·å®¡æ¹") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:submit')") |
| | | public CommonResult<Boolean> submitSaleQuotation(@RequestParam("id") Long id, |
| | | @RequestParam("processDefinitionKey") String processDefinitionKey) { |
| | | saleQuotationService.submitSaleQuotation(id, processDefinitionKey, getLoginUserId()); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/approve-process-list") |
| | | @Operation(summary = "è·åé宿¥ä»·å®¡æ¹æµç¨å表") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:query')") |
| | | public CommonResult<List<Map<String, Object>>> getSaleQuotationApproveProcessList() { |
| | | return success(saleQuotationService.getSaleQuotationApproveProcessDefinitionList()); |
| | | } |
| | | |
| | | @PostMapping("/convert-contract") |
| | | @Operation(summary = "转为åå") |
| | | @Parameter(name = "id", description = "æ¥ä»·åç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:convert-contract')") |
| | | public CommonResult<Long> convertToContract(@RequestParam("id") Long id) { |
| | | return success(saleQuotationService.convertToContract(id, getLoginUserId())); |
| | | } |
| | | |
| | | // ==================== æå»ºååº VO ==================== |
| | | |
| | | private CrmSaleQuotationRespVO buildSaleQuotationRespVO(CrmSaleQuotationDO saleQuotation, List<CrmSaleQuotationProductDO> productList) { |
| | | CrmSaleQuotationRespVO respVO = BeanUtils.toBean(saleQuotation, CrmSaleQuotationRespVO.class); |
| | | // 1. æå»ºç©ææç» |
| | | if (CollUtil.isNotEmpty(productList)) { |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(convertSet(productList, CrmSaleQuotationProductDO::getItemId)); |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(productList, CrmSaleQuotationProductDO::getItemUnitId)); |
| | | respVO.setItems(BeanUtils.toBean(productList, CrmSaleQuotationRespVO.Item.class, item -> { |
| | | MdmItemRespDTO mdmItem = itemMap.get(item.getItemId()); |
| | | if (mdmItem != null) { |
| | | item.setItemCode(mdmItem.getCode()); |
| | | item.setItemName(mdmItem.getName()); |
| | | item.setItemBarCode(mdmItem.getBarCode()); |
| | | } |
| | | MapUtils.findAndThen(unitMap, item.getItemUnitId(), unit -> item.setItemUnitName(unit.getName())); |
| | | })); |
| | | } |
| | | // 2. å¡«å
å
³èä¿¡æ¯ |
| | | fillSaleQuotationRelatedFields(respVO); |
| | | return respVO; |
| | | } |
| | | |
| | | private PageResult<CrmSaleQuotationRespVO> buildSaleQuotationRespVOPageResult(PageResult<CrmSaleQuotationDO> pageResult) { |
| | | if (CollUtil.isEmpty(pageResult.getList())) { |
| | | return PageResult.empty(pageResult.getTotal()); |
| | | } |
| | | // 1. æ¥ä»·æç» |
| | | List<CrmSaleQuotationProductDO> productList = saleQuotationService.getSaleQuotationProductListByQuotationIds( |
| | | convertSet(pageResult.getList(), CrmSaleQuotationDO::getId)); |
| | | Map<Long, List<CrmSaleQuotationProductDO>> productMap = convertMultiMap(productList, CrmSaleQuotationProductDO::getQuotationId); |
| | | // 1.2 ç©æä¿¡æ¯ |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(convertSet(productList, CrmSaleQuotationProductDO::getItemId)); |
| | | // 1.3 ç¨æ·ä¿¡æ¯ |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(pageResult.getList(), CrmSaleQuotationDO::getOwnerUserId)); |
| | | // 1.4 é¨é¨ä¿¡æ¯ |
| | | Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( |
| | | convertSet(pageResult.getList(), CrmSaleQuotationDO::getOwnerUserId)); |
| | | |
| | | // 2. å¼å§æ¼æ¥ |
| | | return BeanUtils.toBean(pageResult, CrmSaleQuotationRespVO.class, vo -> { |
| | | vo.setItems(BeanUtils.toBean(productMap.get(vo.getId()), CrmSaleQuotationRespVO.Item.class, |
| | | item -> MapUtils.findAndThen(itemMap, item.getItemId(), mdmItem -> item.setItemCode(mdmItem.getCode()) |
| | | .setItemName(mdmItem.getName()).setItemBarCode(mdmItem.getBarCode())))); |
| | | MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname())); |
| | | MapUtils.findAndThen(deptMap, vo.getOwnerUserId(), dept -> vo.setOwnerUserDeptName(dept.getName())); |
| | | }); |
| | | } |
| | | |
| | | private void fillSaleQuotationRelatedFields(CrmSaleQuotationRespVO vo) { |
| | | // å¡«å
è´è´£äººåç§° |
| | | if (vo.getOwnerUserId() != null) { |
| | | AdminUserRespDTO user = adminUserApi.getUser(vo.getOwnerUserId()); |
| | | if (user != null) { |
| | | vo.setOwnerUserName(user.getNickname()); |
| | | // å¡«å
é¨é¨åç§° |
| | | if (user.getDeptId() != null) { |
| | | DeptRespDTO dept = deptApi.getDept(user.getDeptId()); |
| | | if (dept != null) { |
| | | vo.setOwnerUserDeptName(dept.getName()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | // å¡«å
å建人åç§° |
| | | if (vo.getCreator() != null) { |
| | | try { |
| | | AdminUserRespDTO user = adminUserApi.getUser(Long.valueOf(vo.getCreator())); |
| | | if (user != null) { |
| | | vo.setCreatorName(user.getNickname()); |
| | | } |
| | | } catch (NumberFormatException ignored) { |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.controller.admin.quotation.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | |
| | | @Schema(description = "管çåå° - CRM é宿¥ä»·åå页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class CrmSaleQuotationPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "æ¥ä»·åå·", example = "QT001") |
| | | private String no; |
| | | |
| | | @Schema(description = "æ¥ä»·ååç§°", example = "æ¥ä»·åA") |
| | | private String name; |
| | | |
| | | @Schema(description = "客æ·ç¼å·", example = "18336") |
| | | private Long customerId; |
| | | |
| | | @Schema(description = "åæºç¼å·", example = "10864") |
| | | private Long businessId; |
| | | |
| | | @Schema(description = "åºæ¯ç±»å", example = "1") |
| | | @InEnum(CrmSceneTypeEnum.class) |
| | | private Integer sceneType; |
| | | |
| | | @Schema(description = "审æ¹ç¶æ", example = "20") |
| | | @InEnum(CrmSaleQuotationStatusEnum.class) |
| | | private Integer status; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.controller.admin.quotation.vo; |
| | | |
| | | import cn.idev.excel.annotation.ExcelIgnoreUnannotated; |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - CRM é宿¥ä»·å Response VO") |
| | | @Data |
| | | @ExcelIgnoreUnannotated |
| | | public class CrmSaleQuotationRespVO { |
| | | |
| | | @Schema(description = "æ¥ä»·åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") |
| | | @ExcelProperty("æ¥ä»·åç¼å·") |
| | | private Long id; |
| | | |
| | | @Schema(description = "æ¥ä»·åå·", requiredMode = Schema.RequiredMode.REQUIRED, example = "QT001") |
| | | @ExcelProperty("æ¥ä»·åå·") |
| | | private String no; |
| | | |
| | | @Schema(description = "æ¥ä»·ååç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "æ¥ä»·åA") |
| | | @ExcelProperty("æ¥ä»·ååç§°") |
| | | private String name; |
| | | |
| | | @Schema(description = "客æ·ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") |
| | | @ExcelProperty("客æ·ç¼å·") |
| | | private Long customerId; |
| | | @Schema(description = "客æ·åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "客æ·A") |
| | | @ExcelProperty("客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "åæºç¼å·", example = "10864") |
| | | @ExcelProperty("åæºç¼å·") |
| | | private Long businessId; |
| | | @Schema(description = "åæºåç§°", example = "åæºA") |
| | | @ExcelProperty("åæºåç§°") |
| | | private String businessName; |
| | | |
| | | @Schema(description = "è系人ç¼å·", example = "18546") |
| | | private Long contactId; |
| | | @Schema(description = "è系人åç§°", example = "è系人A") |
| | | @ExcelProperty("è系人åç§°") |
| | | private String contactName; |
| | | |
| | | @Schema(description = "è´è´£äººçç¨æ·ç¼å·", example = "25682") |
| | | @ExcelProperty("è´è´£äººç¼å·") |
| | | private Long ownerUserId; |
| | | @Schema(description = "è´è´£äººåå", example = "å¼ ä¸") |
| | | @ExcelProperty("è´è´£äººåå") |
| | | private String ownerUserName; |
| | | @Schema(description = "è´è´£äººé¨é¨") |
| | | @ExcelProperty("è´è´£äººé¨é¨") |
| | | private String ownerUserDeptName; |
| | | |
| | | @Schema(description = "审æ¹ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") |
| | | @ExcelProperty("审æ¹ç¶æ") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "工使µç¼å·", example = "1043") |
| | | private String processInstanceId; |
| | | |
| | | @Schema(description = "æ¥ä»·æ¥æ") |
| | | @ExcelProperty("æ¥ä»·æ¥æ") |
| | | private LocalDateTime quotationTime; |
| | | |
| | | @Schema(description = "æææè³") |
| | | @ExcelProperty("æææè³") |
| | | private LocalDateTime validUntil; |
| | | |
| | | @Schema(description = "äº§åæ»éé¢", example = "19510") |
| | | @ExcelProperty("äº§åæ»éé¢") |
| | | private BigDecimal totalProductPrice; |
| | | |
| | | @Schema(description = "æ´åææ£", example = "10.00") |
| | | @ExcelProperty("æ´åææ£") |
| | | private BigDecimal discountPercent; |
| | | |
| | | @Schema(description = "伿 éé¢", example = "1951") |
| | | @ExcelProperty("伿 éé¢") |
| | | private BigDecimal discountPrice; |
| | | |
| | | @Schema(description = "æ¥ä»·æ»éé¢", example = "17559") |
| | | @ExcelProperty("æ¥ä»·æ»éé¢") |
| | | private BigDecimal totalPrice; |
| | | |
| | | @Schema(description = "ç¨ç", example = "13.00") |
| | | @ExcelProperty("ç¨ç") |
| | | private BigDecimal taxRate; |
| | | |
| | | @Schema(description = "ç¨é¢", example = "2282") |
| | | @ExcelProperty("ç¨é¢") |
| | | private BigDecimal taxPrice; |
| | | |
| | | @Schema(description = "å
³èååç¼å·", example = "12345") |
| | | private Long contractId; |
| | | @Schema(description = "å
³èåååå·", example = "HT001") |
| | | @ExcelProperty("å
³èåååå·") |
| | | private String contractNo; |
| | | |
| | | @Schema(description = "éä»¶å°å", example = "https://www.baidu.com") |
| | | private String fileUrl; |
| | | |
| | | @Schema(description = "夿³¨", example = "夿³¨ä¿¡æ¯") |
| | | @ExcelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å建æ¶é´", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @ExcelProperty("å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | @Schema(description = "å建人", example = "25682") |
| | | private String creator; |
| | | @Schema(description = "å建人åå", example = "å¼ ä¸") |
| | | @ExcelProperty("å建人åå") |
| | | private String creatorName; |
| | | |
| | | @Schema(description = "ç©ææç»å表") |
| | | private List<Item> items; |
| | | |
| | | @Schema(description = "ç©ææç»") |
| | | @Data |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public static class Item { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") |
| | | private Long id; |
| | | |
| | | @Schema(description = "MDM ç©æç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") |
| | | private Long itemId; |
| | | @Schema(description = "ç©æç¼ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001") |
| | | private String itemCode; |
| | | @Schema(description = "ç©æåç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "ç©æA") |
| | | private String itemName; |
| | | @Schema(description = "ç©ææ¡ç ", example = "BAR001") |
| | | private String itemBarCode; |
| | | @Schema(description = "计éåä½ç¼å·", example = "1") |
| | | private Long itemUnitId; |
| | | @Schema(description = "计éåä½åç§°", example = "个") |
| | | private String itemUnitName; |
| | | |
| | | @Schema(description = "ç©æåä»·", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") |
| | | private BigDecimal itemPrice; |
| | | |
| | | @Schema(description = "æ¥ä»·åä»·", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") |
| | | private BigDecimal quotationPrice; |
| | | |
| | | @Schema(description = "æ°é", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | private BigDecimal count; |
| | | |
| | | @Schema(description = "å°è®¡éé¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "1230.00") |
| | | private BigDecimal totalPrice; |
| | | |
| | | @Schema(description = "ç¨ç", example = "13.00") |
| | | private BigDecimal taxPercent; |
| | | |
| | | @Schema(description = "ç¨é¢", example = "159.90") |
| | | private BigDecimal taxPrice; |
| | | |
| | | @Schema(description = "夿³¨", example = "夿³¨") |
| | | private String remark; |
| | | |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.controller.admin.quotation.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Schema(description = "管çåå° - CRM é宿¥ä»·åå建/æ´æ° Request VO") |
| | | @Data |
| | | public class CrmSaleQuotationSaveReqVO { |
| | | |
| | | @Schema(description = "æ¥ä»·åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") |
| | | private Long id; |
| | | |
| | | @Schema(description = "æ¥ä»·ååç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "æ¥ä»·åA") |
| | | @NotNull(message = "æ¥ä»·ååç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "客æ·ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") |
| | | @NotNull(message = "客æ·ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long customerId; |
| | | |
| | | @Schema(description = "åæºç¼å·", example = "10864") |
| | | private Long businessId; |
| | | |
| | | @Schema(description = "è系人ç¼å·", example = "18546") |
| | | private Long contactId; |
| | | |
| | | @Schema(description = "è´è´£äººçç¨æ·ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") |
| | | @NotNull(message = "è´è´£äººä¸è½ä¸ºç©º") |
| | | private Long ownerUserId; |
| | | |
| | | @Schema(description = "æ¥ä»·æ¥æ") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime quotationTime; |
| | | |
| | | @Schema(description = "æææè³") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime validUntil; |
| | | |
| | | @Schema(description = "æ´åææ£", example = "10.00") |
| | | private BigDecimal discountPercent; |
| | | |
| | | @Schema(description = "ç¨ç", example = "13.00") |
| | | private BigDecimal taxRate; |
| | | |
| | | @Schema(description = "éä»¶å°å", example = "https://www.baidu.com") |
| | | private String fileUrl; |
| | | |
| | | @Schema(description = "夿³¨", example = "夿³¨ä¿¡æ¯") |
| | | private String remark; |
| | | |
| | | @Schema(description = "ç©ææç»å表", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotEmpty(message = "ç©ææç»ä¸è½ä¸ºç©º") |
| | | private List<Item> items; |
| | | |
| | | @Schema(description = "ç©ææç»") |
| | | @Data |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public static class Item { |
| | | |
| | | @Schema(description = "ç¼å·", example = "888") |
| | | private Long id; |
| | | |
| | | @Schema(description = "MDM ç©æç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") |
| | | @NotNull(message = "ç©æç¼å·ä¸è½ä¸ºç©º") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "æ¥ä»·åä»·", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") |
| | | @NotNull(message = "æ¥ä»·åä»·ä¸è½ä¸ºç©º") |
| | | private BigDecimal quotationPrice; |
| | | |
| | | @Schema(description = "æ°é", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @NotNull(message = "æ°éä¸è½ä¸ºç©º") |
| | | private BigDecimal count; |
| | | |
| | | @Schema(description = "ç¨ç", example = "13.00") |
| | | private BigDecimal taxPercent; |
| | | |
| | | @Schema(description = "夿³¨", example = "夿³¨") |
| | | private String remark; |
| | | |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.crm.dal.dataobject.contract; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | |
| | | */ |
| | | private Long contractId; |
| | | /** |
| | | * 产åç¼å· |
| | | * MDM ç©æç¼å· |
| | | * |
| | | * å
³è {@link CrmProductDO#getId()} |
| | | * å
³è MdmItemRespDTO.id |
| | | */ |
| | | private Long productId; |
| | | private Long itemId; |
| | | /** |
| | | * 产ååä»·ï¼åä½ï¼å
|
| | | * 计éåä½ç¼å· |
| | | * |
| | | * å
³è MdmUnitMeasureRespDTO.id |
| | | */ |
| | | private BigDecimal productPrice; |
| | | private Long itemUnitId; |
| | | /** |
| | | * ç©æåä»·ï¼åä½ï¼å
ï¼åä½ï¼ |
| | | */ |
| | | private BigDecimal itemPrice; |
| | | /** |
| | | * ååä»·æ ¼, åä½ï¼å
|
| | | */ |
| | |
| | | /** |
| | | * æ»è®¡ä»·æ ¼ï¼åä½ï¼å
|
| | | * |
| | | * totalPrice = businessPrice * count |
| | | * totalPrice = contractPrice * count |
| | | */ |
| | | private BigDecimal totalPrice; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.dal.dataobject.quotation; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| | | import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * CRM é宿¥ä»·å DO |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @TableName("crm_sale_quotation") |
| | | @KeySequence("crm_sale_quotation_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class CrmSaleQuotationDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * æ¥ä»·åå· |
| | | */ |
| | | private String no; |
| | | /** |
| | | * æ¥ä»·ååç§° |
| | | */ |
| | | private String name; |
| | | /** |
| | | * 客æ·ç¼å· |
| | | * |
| | | * å
³è {@link CrmCustomerDO#getId()} |
| | | */ |
| | | private Long customerId; |
| | | /** |
| | | * åæºç¼å·ï¼éå¿
é¡» |
| | | * |
| | | * å
³è {@link CrmBusinessDO#getId()} |
| | | */ |
| | | private Long businessId; |
| | | /** |
| | | * è系人ç¼å·ï¼éå¿
é¡» |
| | | * |
| | | * å
³è {@link CrmContactDO#getId()} |
| | | */ |
| | | private Long contactId; |
| | | /** |
| | | * è´è´£äººç¨æ·ç¼å· |
| | | * |
| | | * å
³è AdminUserDO ç id åæ®µ |
| | | */ |
| | | private Long ownerUserId; |
| | | /** |
| | | * 审æ¹ç¶æ |
| | | * |
| | | * æä¸¾ {@link CrmSaleQuotationStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | /** |
| | | * 工使µç¼å· |
| | | * |
| | | * å
³è ProcessInstance ç id 屿§ |
| | | */ |
| | | private String processInstanceId; |
| | | |
| | | /** |
| | | * æ¥ä»·æ¥æ |
| | | */ |
| | | private LocalDateTime quotationTime; |
| | | /** |
| | | * æææè³ |
| | | */ |
| | | private LocalDateTime validUntil; |
| | | /** |
| | | * äº§åæ»éé¢ï¼åä½ï¼å
|
| | | */ |
| | | private BigDecimal totalProductPrice; |
| | | /** |
| | | * æ´åææ£ï¼ç¾åæ¯ |
| | | */ |
| | | private BigDecimal discountPercent; |
| | | /** |
| | | * 伿 éé¢ï¼åä½ï¼å
|
| | | */ |
| | | private BigDecimal discountPrice; |
| | | /** |
| | | * æ¥ä»·æ»éé¢ï¼åä½ï¼å
|
| | | * |
| | | * totalPrice = totalProductPrice - discountPrice |
| | | */ |
| | | private BigDecimal totalPrice; |
| | | /** |
| | | * ç¨çï¼ç¾åæ¯ |
| | | */ |
| | | private BigDecimal taxRate; |
| | | /** |
| | | * ç¨é¢ï¼åä½ï¼å
|
| | | */ |
| | | private BigDecimal taxPrice; |
| | | |
| | | /** |
| | | * å
³èååç¼å· |
| | | * |
| | | * å
³è {@link CrmContractDO#getId()} |
| | | */ |
| | | private Long contractId; |
| | | /** |
| | | * å
³èåååå· |
| | | */ |
| | | private String contractNo; |
| | | /** |
| | | * éä»¶å°å |
| | | */ |
| | | private String fileUrl; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.dal.dataobject.quotation; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** |
| | | * CRM é宿¥ä»·å产åæç» DO |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @TableName("crm_sale_quotation_product") |
| | | @KeySequence("crm_sale_quotation_product_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class CrmSaleQuotationProductDO extends BaseDO { |
| | | |
| | | /** |
| | | * ä¸»é® |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * æ¥ä»·åç¼å· |
| | | * |
| | | * å
³è {@link CrmSaleQuotationDO#getId()} |
| | | */ |
| | | private Long quotationId; |
| | | /** |
| | | * MDM ç©æç¼å· |
| | | * |
| | | * å
³è MdmItemRespDTO.id |
| | | */ |
| | | private Long itemId; |
| | | /** |
| | | * 计éåä½ç¼å· |
| | | * |
| | | * å
³è MdmUnitMeasureRespDTO.id |
| | | */ |
| | | private Long itemUnitId; |
| | | /** |
| | | * ç©æåä»·ï¼åä½ï¼å
ï¼åä½ï¼ |
| | | */ |
| | | private BigDecimal itemPrice; |
| | | /** |
| | | * æ¥ä»·åä»·ï¼åä½ï¼å
|
| | | */ |
| | | private BigDecimal quotationPrice; |
| | | /** |
| | | * æ°é |
| | | */ |
| | | private BigDecimal count; |
| | | /** |
| | | * å°è®¡éé¢ï¼åä½ï¼å
|
| | | * |
| | | * totalPrice = quotationPrice * count |
| | | */ |
| | | private BigDecimal totalPrice; |
| | | /** |
| | | * ç¨çï¼ç¾åæ¯ |
| | | */ |
| | | private BigDecimal taxPercent; |
| | | /** |
| | | * ç¨é¢ï¼åä½ï¼å
|
| | | */ |
| | | private BigDecimal taxPrice; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.dal.mysql.quotation; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationPageReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * CRM é宿¥ä»·å Mapper |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Mapper |
| | | public interface CrmSaleQuotationMapper extends BaseMapperX<CrmSaleQuotationDO> { |
| | | |
| | | default CrmSaleQuotationDO selectByNo(String no) { |
| | | return selectOne(CrmSaleQuotationDO::getNo, no); |
| | | } |
| | | |
| | | default PageResult<CrmSaleQuotationDO> selectPage(CrmSaleQuotationPageReqVO pageReqVO, Long userId) { |
| | | MPJLambdaWrapperX<CrmSaleQuotationDO> query = new MPJLambdaWrapperX<>(); |
| | | // æ¼æ¥æ°æ®æéçæ¥è¯¢æ¡ä»¶ |
| | | CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_SALE_QUOTATION.getType(), |
| | | CrmSaleQuotationDO::getId, userId, pageReqVO.getSceneType()); |
| | | // æ¼æ¥èªèº«çæ¥è¯¢æ¡ä»¶ |
| | | query.selectAll(CrmSaleQuotationDO.class) |
| | | .likeIfPresent(CrmSaleQuotationDO::getNo, pageReqVO.getNo()) |
| | | .likeIfPresent(CrmSaleQuotationDO::getName, pageReqVO.getName()) |
| | | .eqIfPresent(CrmSaleQuotationDO::getCustomerId, pageReqVO.getCustomerId()) |
| | | .eqIfPresent(CrmSaleQuotationDO::getBusinessId, pageReqVO.getBusinessId()) |
| | | .eqIfPresent(CrmSaleQuotationDO::getStatus, pageReqVO.getStatus()) |
| | | .orderByDesc(CrmSaleQuotationDO::getId); |
| | | return selectJoinPage(pageReqVO, CrmSaleQuotationDO.class, query); |
| | | } |
| | | |
| | | default PageResult<CrmSaleQuotationDO> selectPageByCustomerId(CrmSaleQuotationPageReqVO pageReqVO) { |
| | | return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmSaleQuotationDO>() |
| | | .eq(CrmSaleQuotationDO::getCustomerId, pageReqVO.getCustomerId()) |
| | | .likeIfPresent(CrmSaleQuotationDO::getNo, pageReqVO.getNo()) |
| | | .likeIfPresent(CrmSaleQuotationDO::getName, pageReqVO.getName()) |
| | | .orderByDesc(CrmSaleQuotationDO::getId)); |
| | | } |
| | | |
| | | default PageResult<CrmSaleQuotationDO> selectPageByBusinessId(CrmSaleQuotationPageReqVO pageReqVO) { |
| | | return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmSaleQuotationDO>() |
| | | .eq(CrmSaleQuotationDO::getBusinessId, pageReqVO.getBusinessId()) |
| | | .likeIfPresent(CrmSaleQuotationDO::getNo, pageReqVO.getNo()) |
| | | .likeIfPresent(CrmSaleQuotationDO::getName, pageReqVO.getName()) |
| | | .orderByDesc(CrmSaleQuotationDO::getId)); |
| | | } |
| | | |
| | | default List<CrmSaleQuotationDO> selectListByCustomerId(Long customerId) { |
| | | return selectList(CrmSaleQuotationDO::getCustomerId, customerId); |
| | | } |
| | | |
| | | default List<CrmSaleQuotationDO> selectListByBusinessId(Long businessId) { |
| | | return selectList(CrmSaleQuotationDO::getBusinessId, businessId); |
| | | } |
| | | |
| | | default Long selectCountByBusinessId(Long businessId) { |
| | | return selectCount(CrmSaleQuotationDO::getBusinessId, businessId); |
| | | } |
| | | |
| | | default Long selectCountByCustomerId(Long customerId) { |
| | | return selectCount(CrmSaleQuotationDO::getCustomerId, customerId); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.dal.mysql.quotation; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationProductDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * CRM é宿¥ä»·å产åæç» Mapper |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Mapper |
| | | public interface CrmSaleQuotationProductMapper extends BaseMapperX<CrmSaleQuotationProductDO> { |
| | | |
| | | default List<CrmSaleQuotationProductDO> selectListByQuotationId(Long quotationId) { |
| | | return selectList(new LambdaQueryWrapperX<CrmSaleQuotationProductDO>() |
| | | .eq(CrmSaleQuotationProductDO::getQuotationId, quotationId)); |
| | | } |
| | | |
| | | default List<CrmSaleQuotationProductDO> selectListByQuotationIds(Collection<Long> quotationIds) { |
| | | return selectList(new LambdaQueryWrapperX<CrmSaleQuotationProductDO>() |
| | | .in(CrmSaleQuotationProductDO::getQuotationId, quotationIds)); |
| | | } |
| | | |
| | | default void deleteByQuotationId(Long quotationId) { |
| | | delete(new LambdaQueryWrapperX<CrmSaleQuotationProductDO>() |
| | | .eq(CrmSaleQuotationProductDO::getQuotationId, quotationId)); |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | public static final String RECEIVABLE_PREFIX = "HK"; |
| | | |
| | | /** |
| | | * é宿¥ä»· {@link cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO} |
| | | */ |
| | | public static final String SALE_QUOTATION_NO_PREFIX = "QT"; |
| | | |
| | | @Resource |
| | | private StringRedisTemplate stringRedisTemplate; |
| | | |
| | |
| | | |
| | | // ========== æ°æ®ç»è®¡ 1_020_014_000 ========== |
| | | |
| | | // ========== é宿¥ä»· 1_020_015_000 ========== |
| | | ErrorCode SALE_QUOTATION_NOT_EXISTS = new ErrorCode(1_020_015_000, "é宿¥ä»·åä¸åå¨"); |
| | | ErrorCode SALE_QUOTATION_NO_EXISTS = new ErrorCode(1_020_015_001, "é宿¥ä»·åå·å·²åå¨"); |
| | | ErrorCode SALE_QUOTATION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_015_002, "é宿¥ä»·å({})䏿¯èç¨¿ç¶æï¼æ æ³ä¿®æ¹"); |
| | | ErrorCode SALE_QUOTATION_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_020_015_003, "é宿¥ä»·å({})䏿¯èç¨¿ç¶æï¼æ æ³å é¤"); |
| | | ErrorCode SALE_QUOTATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_015_004, "é宿¥ä»·å䏿¯èç¨¿ç¶æï¼æ æ³æäº¤å®¡æ¹"); |
| | | ErrorCode SALE_QUOTATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_015_005, "é宿¥ä»·å䏿¯å®¡æ¹ä¸ç¶æï¼æ æ³æ´æ°å®¡æ¹ç¶æ"); |
| | | ErrorCode SALE_QUOTATION_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_015_006, "é宿¥ä»·å®¡æ¹åç±»ä¸åå¨ï¼è¯·æ£æ¥ BPM é
ç½®"); |
| | | ErrorCode SALE_QUOTATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_020_015_007, "é宿¥ä»·å®¡æ¹æµç¨ä¸åå¨ææªæ¿æ´»ï¼è¯·æ£æ¥ BPM é
ç½®"); |
| | | ErrorCode SALE_QUOTATION_NOT_APPROVE = new ErrorCode(1_020_015_008, "é宿¥ä»·åæªå®¡æ¹éè¿ï¼æ æ³è½¬åå"); |
| | | ErrorCode SALE_QUOTATION_CONTRACT_EXISTS = new ErrorCode(1_020_015_009, "é宿¥ä»·å已转ååï¼è¯·å¿é夿ä½"); |
| | | ErrorCode SALE_QUOTATION_ITEM_NOT_EXISTS = new ErrorCode(1_020_015_010, "ç©æä¸å卿æªå¯ç¨"); |
| | | |
| | | } |
| | |
| | | CRM_CONTRACT(5, "åå"), |
| | | CRM_PRODUCT(6, "产å"), |
| | | CRM_RECEIVABLE(7, "忬¾"), |
| | | CRM_RECEIVABLE_PLAN(8, "忬¾è®¡å") |
| | | CRM_RECEIVABLE_PLAN(8, "忬¾è®¡å"), |
| | | CRM_SALE_QUOTATION(9, "é宿¥ä»·") |
| | | ; |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()).map(CrmBizTypeEnum::getType).toArray(Integer[]::new); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.enums.quotation; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.Getter; |
| | | import lombok.RequiredArgsConstructor; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * CRM é宿¥ä»·å®¡æ¹ç¶ææä¸¾ |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @RequiredArgsConstructor |
| | | @Getter |
| | | public enum CrmSaleQuotationStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | DRAFT(0, "è稿"), |
| | | PROCESS(10, "审æ¹ä¸"), |
| | | APPROVE(20, "å·²éè¿"), |
| | | REJECT(30, "å·²æç»"), |
| | | CANCEL(40, "已忶"), |
| | | CONVERTED(50, "已转åå"); |
| | | |
| | | private final Integer status; |
| | | private final String name; |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()) |
| | | .map(CrmSaleQuotationStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; |
| | | import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderCreateReqDTO; |
| | | import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import com.mzt.logapi.context.LogRecordContext; |
| | | import com.mzt.logapi.service.impl.DiffParseFunction; |
| | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| | |
| | | private BpmProcessInstanceApi bpmProcessInstanceApi; |
| | | @Resource |
| | | private ErpSaleOrderApi saleOrderApi; |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | } |
| | | |
| | | private List<CrmContractProductDO> validateContractProducts(List<CrmContractSaveReqVO.Product> list) { |
| | | // 1. æ ¡éªäº§ååå¨ |
| | | productService.validProductList(convertSet(list, CrmContractSaveReqVO.Product::getProductId)); |
| | | // 1. æ ¡éªç©æåå¨ä¸å¯ç¨ |
| | | List<Long> itemIds = convertSet(list, CrmContractSaveReqVO.Product::getItemId); |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(itemIds); |
| | | // 2. 转å为 CrmContractProductDO å表 |
| | | return convertList(list, o -> BeanUtils.toBean(o, CrmContractProductDO.class, |
| | | item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getContractPrice(), item.getCount())))); |
| | | return convertList(list, o -> BeanUtils.toBean(o, CrmContractProductDO.class, item -> { |
| | | MdmItemRespDTO mdmItem = itemMap.get(item.getItemId()); |
| | | if (mdmItem == null) { |
| | | throw exception(PRODUCT_NOT_EXISTS); |
| | | } |
| | | item.setItemUnitId(mdmItem.getUnitMeasureId()); |
| | | item.setItemPrice(mdmItem.getSalesPrice()); |
| | | item.setTotalPrice(MoneyUtils.priceMultiply(item.getContractPrice(), item.getCount())); |
| | | })); |
| | | } |
| | | |
| | | private void calculateTotalPrice(CrmContractDO contract, List<CrmContractProductDO> contractProducts) { |
| | |
| | | List<ErpSaleOrderCreateReqDTO.Item> items = contractProducts.stream() |
| | | .map(cp -> { |
| | | ErpSaleOrderCreateReqDTO.Item item = new ErpSaleOrderCreateReqDTO.Item(); |
| | | item.setProductId(cp.getProductId()); |
| | | item.setItemId(cp.getItemId()); |
| | | item.setProductPrice(cp.getContractPrice()); |
| | | item.setCount(cp.getCount()); |
| | | return item; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.quotation; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationPageReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationSaveReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationProductDO; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * CRM é宿¥ä»·å Service æ¥å£ |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | public interface CrmSaleQuotationService { |
| | | |
| | | /** |
| | | * å建é宿¥ä»·å |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @param userId ç¨æ·ç¼å· |
| | | * @return ç¼å· |
| | | */ |
| | | Long createSaleQuotation(CrmSaleQuotationSaveReqVO createReqVO, Long userId); |
| | | |
| | | /** |
| | | * æ´æ°é宿¥ä»·å |
| | | * |
| | | * @param updateReqVO æ´æ°ä¿¡æ¯ |
| | | */ |
| | | void updateSaleQuotation(CrmSaleQuotationSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å é¤é宿¥ä»·å |
| | | * |
| | | * @param ids ç¼å·å表 |
| | | */ |
| | | void deleteSaleQuotation(List<Long> ids); |
| | | |
| | | /** |
| | | * è·å¾é宿¥ä»·å |
| | | * |
| | | * @param id ç¼å· |
| | | * @return é宿¥ä»·å |
| | | */ |
| | | CrmSaleQuotationDO getSaleQuotation(Long id); |
| | | |
| | | /** |
| | | * è·å¾é宿¥ä»·åå页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @param userId ç¨æ·ç¼å· |
| | | * @return é宿¥ä»·åå页 |
| | | */ |
| | | PageResult<CrmSaleQuotationDO> getSaleQuotationPage(CrmSaleQuotationPageReqVO pageReqVO, Long userId); |
| | | |
| | | /** |
| | | * è·å¾é宿¥ä»·åå页ï¼åºäºå®¢æ·ï¼ |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return é宿¥ä»·åå页 |
| | | */ |
| | | PageResult<CrmSaleQuotationDO> getSaleQuotationPageByCustomerId(CrmSaleQuotationPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å¾é宿¥ä»·åå页ï¼åºäºåæºï¼ |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return é宿¥ä»·åå页 |
| | | */ |
| | | PageResult<CrmSaleQuotationDO> getSaleQuotationPageByBusinessId(CrmSaleQuotationPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å¾é宿¥ä»·å产åæç»å表 |
| | | * |
| | | * @param quotationId æ¥ä»·åç¼å· |
| | | * @return 产åæç»å表 |
| | | */ |
| | | List<CrmSaleQuotationProductDO> getSaleQuotationProductListByQuotationId(Long quotationId); |
| | | |
| | | /** |
| | | * è·å¾é宿¥ä»·å产åæç»å表 |
| | | * |
| | | * @param quotationIds æ¥ä»·åç¼å·å表 |
| | | * @return 产åæç»å表 |
| | | */ |
| | | List<CrmSaleQuotationProductDO> getSaleQuotationProductListByQuotationIds(Collection<Long> quotationIds); |
| | | |
| | | /** |
| | | * æäº¤é宿¥ä»·åå®¡æ¹ |
| | | * |
| | | * @param id æ¥ä»·åç¼å· |
| | | * @param processDefinitionKey æµç¨å®ä¹ Key |
| | | * @param userId ç¨æ·ç¼å· |
| | | */ |
| | | void submitSaleQuotation(Long id, String processDefinitionKey, Long userId); |
| | | |
| | | /** |
| | | * æ´æ°é宿¥ä»·å审æ¹ç¶æ |
| | | * |
| | | * @param id æ¥ä»·åç¼å· |
| | | * @param bpmResult BPM 审æ¹ç»æ |
| | | */ |
| | | void updateSaleQuotationAuditStatus(Long id, Integer bpmResult); |
| | | |
| | | /** |
| | | * è·åé宿¥ä»·å®¡æ¹æµç¨å表 |
| | | * |
| | | * @return æµç¨å®ä¹å表 |
| | | */ |
| | | List<Map<String, Object>> getSaleQuotationApproveProcessDefinitionList(); |
| | | |
| | | /** |
| | | * 转为åå |
| | | * |
| | | * @param id æ¥ä»·åç¼å· |
| | | * @param userId ç¨æ·ç¼å· |
| | | * @return ååç¼å· |
| | | */ |
| | | Long convertToContract(Long id, Long userId); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.quotation; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.lang.Assert; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| | | import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; |
| | | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationPageReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationSaveReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationProductDO; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.quotation.CrmSaleQuotationMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.quotation.CrmSaleQuotationProductMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO; |
| | | import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; |
| | | import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; |
| | | import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; |
| | | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; |
| | | import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.flowable.engine.repository.ProcessDefinition; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| | | import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * CRM é宿¥ä»·å Service å®ç°ç±» |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class CrmSaleQuotationServiceImpl implements CrmSaleQuotationService { |
| | | |
| | | /** |
| | | * é宿¥ä»· BPM åç±»ç¼ç |
| | | */ |
| | | private static final String SALE_QUOTATION_APPROVE_CATEGORY_CODE = "sale_quotation_approve"; |
| | | |
| | | @Resource |
| | | private CrmSaleQuotationMapper saleQuotationMapper; |
| | | @Resource |
| | | private CrmSaleQuotationProductMapper saleQuotationProductMapper; |
| | | |
| | | @Resource |
| | | private CrmNoRedisDAO noRedisDAO; |
| | | |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | |
| | | @Resource |
| | | private CrmCustomerService customerService; |
| | | @Resource |
| | | private CrmBusinessService businessService; |
| | | @Resource |
| | | private CrmContactService contactService; |
| | | @Resource |
| | | private CrmPermissionService permissionService; |
| | | @Resource |
| | | private CrmContractService contractService; |
| | | |
| | | @Resource |
| | | private BpmCategoryService bpmCategoryService; |
| | | @Resource |
| | | private BpmProcessDefinitionService bpmProcessDefinitionService; |
| | | @Resource |
| | | private BpmProcessInstanceApi bpmProcessInstanceApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createSaleQuotation(CrmSaleQuotationSaveReqVO createReqVO, Long userId) { |
| | | // 1.1 æ ¡éªç©æé¡¹çæææ§ |
| | | List<CrmSaleQuotationProductDO> quotationProducts = validateQuotationItems(createReqVO.getItems()); |
| | | // 1.2 çææ¥ä»·åå·ï¼å¹¶æ ¡éªå¯ä¸æ§ |
| | | String no = noRedisDAO.generate(CrmNoRedisDAO.SALE_QUOTATION_NO_PREFIX); |
| | | if (saleQuotationMapper.selectByNo(no) != null) { |
| | | throw exception(SALE_QUOTATION_NO_EXISTS); |
| | | } |
| | | // 1.3 æ ¡éªå
³èåæ®µ |
| | | validateRelationDataExists(createReqVO); |
| | | |
| | | // 2.1 æå
¥æ¥ä»·å |
| | | CrmSaleQuotationDO saleQuotation = BeanUtils.toBean(createReqVO, CrmSaleQuotationDO.class, q -> q |
| | | .setNo(no) |
| | | .setStatus(CrmSaleQuotationStatusEnum.DRAFT.getStatus())); |
| | | calculateTotalPrice(saleQuotation, quotationProducts); |
| | | saleQuotationMapper.insert(saleQuotation); |
| | | // 2.2 æå
¥æ¥ä»·æç» |
| | | quotationProducts.forEach(p -> p.setQuotationId(saleQuotation.getId())); |
| | | saleQuotationProductMapper.insertBatch(quotationProducts); |
| | | // 2.3 åå»ºæ°æ®æé |
| | | permissionService.createPermission(saleQuotation.getId(), userId); |
| | | return saleQuotation.getId(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateSaleQuotation(CrmSaleQuotationSaveReqVO updateReqVO) { |
| | | // 1.1 æ ¡éªåå¨ |
| | | CrmSaleQuotationDO saleQuotation = validateSaleQuotationExists(updateReqVO.getId()); |
| | | // 1.2 æ ¡éªç¶æï¼åªæèç¨¿ç¶ææè½ä¿®æ¹ |
| | | if (!CrmSaleQuotationStatusEnum.DRAFT.getStatus().equals(saleQuotation.getStatus())) { |
| | | throw exception(SALE_QUOTATION_UPDATE_FAIL_NOT_DRAFT, saleQuotation.getNo()); |
| | | } |
| | | // 1.3 æ ¡éªç©æé¡¹çæææ§ |
| | | List<CrmSaleQuotationProductDO> quotationProducts = validateQuotationItems(updateReqVO.getItems()); |
| | | // 1.4 æ ¡éªå
³èåæ®µ |
| | | validateRelationDataExists(updateReqVO); |
| | | |
| | | // 2.1 æ´æ°æ¥ä»·å |
| | | CrmSaleQuotationDO updateObj = BeanUtils.toBean(updateReqVO, CrmSaleQuotationDO.class); |
| | | calculateTotalPrice(updateObj, quotationProducts); |
| | | saleQuotationMapper.updateById(updateObj); |
| | | // 2.2 æ´æ°æ¥ä»·æç» |
| | | updateQuotationProductList(updateReqVO.getId(), quotationProducts); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteSaleQuotation(List<Long> ids) { |
| | | // 1. æ ¡éªä¸å¤äºéèç¨¿ç¶æ |
| | | List<CrmSaleQuotationDO> saleQuotations = saleQuotationMapper.selectByIds(ids); |
| | | if (CollUtil.isEmpty(saleQuotations)) { |
| | | return; |
| | | } |
| | | saleQuotations.forEach(saleQuotation -> { |
| | | if (!CrmSaleQuotationStatusEnum.DRAFT.getStatus().equals(saleQuotation.getStatus())) { |
| | | throw exception(SALE_QUOTATION_DELETE_FAIL_NOT_DRAFT, saleQuotation.getNo()); |
| | | } |
| | | }); |
| | | |
| | | // 2. éåå é¤ |
| | | saleQuotations.forEach(saleQuotation -> { |
| | | // 2.1 å 餿¥ä»·å |
| | | saleQuotationMapper.deleteById(saleQuotation.getId()); |
| | | // 2.2 å 餿¥ä»·æç» |
| | | saleQuotationProductMapper.deleteByQuotationId(saleQuotation.getId()); |
| | | // 2.3 å 餿°æ®æé |
| | | permissionService.deletePermission(saleQuotation.getId()); |
| | | }); |
| | | } |
| | | |
| | | private CrmSaleQuotationDO validateSaleQuotationExists(Long id) { |
| | | CrmSaleQuotationDO saleQuotation = saleQuotationMapper.selectById(id); |
| | | if (saleQuotation == null) { |
| | | throw exception(SALE_QUOTATION_NOT_EXISTS); |
| | | } |
| | | return saleQuotation; |
| | | } |
| | | |
| | | private List<CrmSaleQuotationProductDO> validateQuotationItems(List<CrmSaleQuotationSaveReqVO.Item> list) { |
| | | // 1. æ ¡éªç©æåå¨ä¸å¯ç¨ |
| | | List<Long> itemIds = convertSet(list, CrmSaleQuotationSaveReqVO.Item::getItemId); |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(itemIds); |
| | | // 2. 转æ¢ä¸º CrmSaleQuotationProductDO å表 |
| | | return convertList(list, o -> BeanUtils.toBean(o, CrmSaleQuotationProductDO.class, item -> { |
| | | MdmItemRespDTO mdmItem = itemMap.get(item.getItemId()); |
| | | if (mdmItem == null) { |
| | | throw exception(SALE_QUOTATION_ITEM_NOT_EXISTS); |
| | | } |
| | | item.setItemUnitId(mdmItem.getUnitMeasureId()); |
| | | item.setItemPrice(mdmItem.getSalesPrice()); |
| | | if (item.getQuotationPrice() != null && item.getCount() != null) { |
| | | item.setTotalPrice(MoneyUtils.priceMultiply(item.getQuotationPrice(), item.getCount())); |
| | | if (item.getTaxPercent() != null) { |
| | | item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); |
| | | } |
| | | } |
| | | })); |
| | | } |
| | | |
| | | private void validateRelationDataExists(CrmSaleQuotationSaveReqVO reqVO) { |
| | | // 1. æ ¡éªå®¢æ· |
| | | customerService.validateCustomer(reqVO.getCustomerId()); |
| | | // 2. æ ¡éªåæºï¼å¯éï¼ |
| | | if (reqVO.getBusinessId() != null) { |
| | | businessService.validateBusiness(reqVO.getBusinessId()); |
| | | } |
| | | // 3. æ ¡éªè系人ï¼å¯éï¼ |
| | | if (reqVO.getContactId() != null) { |
| | | contactService.validateContact(reqVO.getContactId()); |
| | | } |
| | | } |
| | | |
| | | private void updateQuotationProductList(Long id, List<CrmSaleQuotationProductDO> newList) { |
| | | // ç¬¬ä¸æ¥ï¼å¯¹æ¯æ°èæ°æ®ï¼è·å¾æ·»å ãä¿®æ¹ãå é¤çå表 |
| | | List<CrmSaleQuotationProductDO> oldList = saleQuotationProductMapper.selectListByQuotationId(id); |
| | | List<List<CrmSaleQuotationProductDO>> diffList = diffList(oldList, newList, |
| | | (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); |
| | | |
| | | // ç¬¬äºæ¥ï¼æ¹éæ·»å ãä¿®æ¹ãå é¤ |
| | | if (CollUtil.isNotEmpty(diffList.get(0))) { |
| | | diffList.get(0).forEach(o -> o.setQuotationId(id)); |
| | | saleQuotationProductMapper.insertBatch(diffList.get(0)); |
| | | } |
| | | if (CollUtil.isNotEmpty(diffList.get(1))) { |
| | | saleQuotationProductMapper.updateBatch(diffList.get(1)); |
| | | } |
| | | if (CollUtil.isNotEmpty(diffList.get(2))) { |
| | | saleQuotationProductMapper.deleteByIds(convertList(diffList.get(2), CrmSaleQuotationProductDO::getId)); |
| | | } |
| | | } |
| | | |
| | | private void calculateTotalPrice(CrmSaleQuotationDO saleQuotation, List<CrmSaleQuotationProductDO> items) { |
| | | saleQuotation.setTotalProductPrice(getSumValue(items, CrmSaleQuotationProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); |
| | | saleQuotation.setTotalTaxPrice(getSumValue(items, CrmSaleQuotationProductDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO)); |
| | | // 计ç®ä¼æ ä»·æ ¼ |
| | | if (saleQuotation.getDiscountPercent() == null) { |
| | | saleQuotation.setDiscountPercent(BigDecimal.ZERO); |
| | | } |
| | | BigDecimal basePrice = saleQuotation.getTotalProductPrice().add(saleQuotation.getTotalTaxPrice()); |
| | | saleQuotation.setDiscountPrice(MoneyUtils.priceMultiplyPercent(basePrice, saleQuotation.getDiscountPercent())); |
| | | saleQuotation.setTotalPrice(basePrice.subtract(saleQuotation.getDiscountPrice())); |
| | | } |
| | | |
| | | @Override |
| | | public CrmSaleQuotationDO getSaleQuotation(Long id) { |
| | | return saleQuotationMapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<CrmSaleQuotationDO> getSaleQuotationPage(CrmSaleQuotationPageReqVO pageReqVO, Long userId) { |
| | | return saleQuotationMapper.selectPage(pageReqVO, userId); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<CrmSaleQuotationDO> getSaleQuotationPageByCustomerId(CrmSaleQuotationPageReqVO pageReqVO) { |
| | | return saleQuotationMapper.selectPageByCustomerId(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<CrmSaleQuotationDO> getSaleQuotationPageByBusinessId(CrmSaleQuotationPageReqVO pageReqVO) { |
| | | return saleQuotationMapper.selectPageByBusinessId(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public List<CrmSaleQuotationProductDO> getSaleQuotationProductListByQuotationId(Long quotationId) { |
| | | return saleQuotationProductMapper.selectListByQuotationId(quotationId); |
| | | } |
| | | |
| | | @Override |
| | | public List<CrmSaleQuotationProductDO> getSaleQuotationProductListByQuotationIds(Collection<Long> quotationIds) { |
| | | if (CollUtil.isEmpty(quotationIds)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return saleQuotationProductMapper.selectListByQuotationIds(quotationIds); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void submitSaleQuotation(Long id, String processDefinitionKey, Long userId) { |
| | | // 1. æ ¡éªæ¥ä»·ååå¨ä¸ä¸ºèç¨¿ç¶æ |
| | | CrmSaleQuotationDO saleQuotation = validateSaleQuotationExists(id); |
| | | if (!CrmSaleQuotationStatusEnum.DRAFT.getStatus().equals(saleQuotation.getStatus())) { |
| | | throw exception(SALE_QUOTATION_SUBMIT_FAIL_NOT_DRAFT); |
| | | } |
| | | |
| | | // 2. æ ¡éªåç±»åæµç¨å®ä¹æ¯å¦åå¨ |
| | | validateSaleQuotationApproveCategoryAndProcessDefinition(); |
| | | |
| | | // 3. æ ¡éªä¼ å
¥çæµç¨å®ä¹ Key æ¯å¦å±äºè¯¥åç±»ä¸å¤äºæ¿æ´»ç¶æ |
| | | ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey); |
| | | if (processDefinition == null) { |
| | | throw exception(SALE_QUOTATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | |
| | | // 4. å建æµç¨å®ä¾ |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, |
| | | new BpmProcessInstanceCreateReqDTO() |
| | | .setProcessDefinitionKey(processDefinitionKey) |
| | | .setBusinessKey(String.valueOf(id))); |
| | | |
| | | // 5. æ´æ°æ¥ä»·åç¶æ |
| | | saleQuotationMapper.updateById(new CrmSaleQuotationDO() |
| | | .setId(id) |
| | | .setProcessInstanceId(processInstanceId) |
| | | .setStatus(CrmSaleQuotationStatusEnum.PROCESS.getStatus())); |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éª BPM é宿¥ä»·å®¡æ¹åç±»åæµç¨å®ä¹æ¯å¦åå¨ |
| | | */ |
| | | private void validateSaleQuotationApproveCategoryAndProcessDefinition() { |
| | | // 1. æ ¡éªåç±»æ¯å¦åå¨ |
| | | List<String> codes = Collections.singletonList(SALE_QUOTATION_APPROVE_CATEGORY_CODE); |
| | | Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(codes); |
| | | if (!categoryMap.containsKey(SALE_QUOTATION_APPROVE_CATEGORY_CODE)) { |
| | | throw exception(SALE_QUOTATION_BPM_CATEGORY_NOT_EXISTS); |
| | | } |
| | | |
| | | // 2. æ ¡éªåç±»ä¸æ¯å¦æå¯ç¨çæµç¨å®ä¹ |
| | | List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService |
| | | .getProcessDefinitionInfoListByCategory(SALE_QUOTATION_APPROVE_CATEGORY_CODE); |
| | | if (CollUtil.isEmpty(definitionInfoList)) { |
| | | throw exception(SALE_QUOTATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | |
| | | // 3. æ ¡éªæ¯å¦ææ¿æ´»ç¶æçæµç¨å®ä¹ |
| | | Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId); |
| | | List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds); |
| | | boolean hasActiveDefinition = processDefinitions.stream() |
| | | .anyMatch(pd -> !pd.isSuspended()); |
| | | if (!hasActiveDefinition) { |
| | | throw exception(SALE_QUOTATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void updateSaleQuotationAuditStatus(Long id, Integer bpmResult) { |
| | | // 1. æ ¡éªæ¥ä»·ååå¨ä¸ä¸ºå®¡æ¹ä¸ç¶æ |
| | | CrmSaleQuotationDO saleQuotation = validateSaleQuotationExists(id); |
| | | if (!CrmSaleQuotationStatusEnum.PROCESS.getStatus().equals(saleQuotation.getStatus())) { |
| | | log.error("[updateSaleQuotationAuditStatus] æ¥ä»·å({}) ä¸å¤äºå®¡æ¹ä¸", id); |
| | | throw exception(SALE_QUOTATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); |
| | | } |
| | | |
| | | // 2. 转æ¢å®¡æ¹ç»æ |
| | | Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); |
| | | saleQuotationMapper.updateById(new CrmSaleQuotationDO() |
| | | .setId(id) |
| | | .setStatus(auditStatus)); |
| | | } |
| | | |
| | | /** |
| | | * BPM 审æ¹ç»æè½¬æ¢ |
| | | */ |
| | | private Integer convertBpmResultToAuditStatus(Integer bpmResult) { |
| | | Integer auditStatus = BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult) |
| | | ? CrmSaleQuotationStatusEnum.APPROVE.getStatus() |
| | | : BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult) |
| | | ? CrmSaleQuotationStatusEnum.REJECT.getStatus() |
| | | : BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult) |
| | | ? CrmSaleQuotationStatusEnum.CANCEL.getStatus() : null; |
| | | Assert.notNull(auditStatus, "BPM 审æ¹ç»æ({}) 转æ¢å¤±è´¥", bpmResult); |
| | | return auditStatus; |
| | | } |
| | | |
| | | @Override |
| | | public List<Map<String, Object>> getSaleQuotationApproveProcessDefinitionList() { |
| | | // 1. æ ¡éªåç±»åæµç¨å®ä¹æ¯å¦åå¨ |
| | | validateSaleQuotationApproveCategoryAndProcessDefinition(); |
| | | |
| | | // 2. è·ååç±»ä¸çæµç¨å®ä¹ä¿¡æ¯ |
| | | List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService |
| | | .getProcessDefinitionInfoListByCategory(SALE_QUOTATION_APPROVE_CATEGORY_CODE); |
| | | |
| | | // 3. è·åæµç¨å®ä¹è¯¦æ
|
| | | Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId); |
| | | List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds); |
| | | |
| | | // 4. è¿æ»¤ï¼åªä¿çæ¿æ´»ç¶æï¼ä¸ç¸å key åªä¿çææ°çæ¬ |
| | | Map<String, ProcessDefinition> latestVersionMap = new HashMap<>(); |
| | | for (ProcessDefinition pd : processDefinitions) { |
| | | if (pd.isSuspended()) { |
| | | continue; |
| | | } |
| | | ProcessDefinition existing = latestVersionMap.get(pd.getKey()); |
| | | if (existing == null || pd.getVersion() > existing.getVersion()) { |
| | | latestVersionMap.put(pd.getKey(), pd); |
| | | } |
| | | } |
| | | |
| | | // 5. è¿åæµç¨å®ä¹å表 |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (ProcessDefinition pd : latestVersionMap.values()) { |
| | | Map<String, Object> item = new HashMap<>(); |
| | | item.put("id", pd.getId()); |
| | | item.put("key", pd.getKey()); |
| | | item.put("name", pd.getName()); |
| | | item.put("version", pd.getVersion()); |
| | | result.add(item); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long convertToContract(Long id, Long userId) { |
| | | // 1. æ ¡éªæ¥ä»·ååå¨ä¸å·²å®¡æ¹éè¿ |
| | | CrmSaleQuotationDO saleQuotation = validateSaleQuotationExists(id); |
| | | if (!CrmSaleQuotationStatusEnum.APPROVE.getStatus().equals(saleQuotation.getStatus())) { |
| | | throw exception(SALE_QUOTATION_NOT_APPROVE); |
| | | } |
| | | // æ ¡éªæ¯å¦å·²è½¬åå |
| | | if (saleQuotation.getContractId() != null) { |
| | | throw exception(SALE_QUOTATION_CONTRACT_EXISTS); |
| | | } |
| | | |
| | | // 2. è·åæ¥ä»·æç» |
| | | List<CrmSaleQuotationProductDO> quotationProducts = saleQuotationProductMapper.selectListByQuotationId(id); |
| | | |
| | | // 3. æå»ºååå建 VO |
| | | CrmContractSaveReqVO contractVO = new CrmContractSaveReqVO(); |
| | | contractVO.setName(saleQuotation.getName() + "-åå"); |
| | | contractVO.setCustomerId(saleQuotation.getCustomerId()); |
| | | contractVO.setBusinessId(saleQuotation.getBusinessId()); |
| | | contractVO.setOwnerUserId(saleQuotation.getOwnerUserId()); |
| | | contractVO.setOrderDate(saleQuotation.getQuotationTime()); |
| | | contractVO.setDiscountPercent(saleQuotation.getDiscountPercent()); |
| | | contractVO.setRemark("ç±æ¥ä»·å[" + saleQuotation.getNo() + "]çæ"); |
| | | // è½¬æ¢æç»é¡¹ï¼æ¥ä»·åç©æ -> åå产å |
| | | List<CrmContractSaveReqVO.Product> contractProducts = convertList(quotationProducts, qp -> { |
| | | CrmContractSaveReqVO.Product product = new CrmContractSaveReqVO.Product(); |
| | | product.setItemId(qp.getItemId()); |
| | | product.setItemPrice(qp.getItemPrice()); |
| | | product.setContractPrice(qp.getQuotationPrice()); |
| | | product.setCount(qp.getCount().intValue()); |
| | | return product; |
| | | }); |
| | | contractVO.setProducts(contractProducts); |
| | | |
| | | // 4. å建åå |
| | | Long contractId = contractService.createContract(contractVO, userId); |
| | | |
| | | // 5. æ´æ°æ¥ä»·åå
³èä¿¡æ¯ |
| | | CrmContractDO contract = contractService.getContract(contractId); |
| | | saleQuotationMapper.updateById(new CrmSaleQuotationDO() |
| | | .setId(id) |
| | | .setContractId(contractId) |
| | | .setContractNo(contract.getNo()) |
| | | .setStatus(CrmSaleQuotationStatusEnum.CONVERTED.getStatus())); |
| | | |
| | | return contractId; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.quotation.listener; |
| | | |
| | | import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.quotation.CrmSaleQuotationMapper; |
| | | import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.context.ApplicationListener; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * é宿¥ä»·å®¡æ¹çç»æçå¬å¨å®ç°ç±» |
| | | * |
| | | * éè¿ businessKeyï¼æ¥ä»·åIDï¼å¹é
ï¼è䏿¯åºå®çæµç¨å®ä¹ Keyï¼ |
| | | * è¿æ ·å¯ä»¥æ¯æåç±»ä¸ä»»æ Key çæµç¨æ¨¡åã |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | public class CrmSaleQuotationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> { |
| | | |
| | | @Resource |
| | | private CrmSaleQuotationService saleQuotationService; |
| | | @Resource |
| | | private CrmSaleQuotationMapper saleQuotationMapper; |
| | | |
| | | @Override |
| | | public void onApplicationEvent(BpmProcessInstanceStatusEvent event) { |
| | | // éè¿ businessKey æ¥è¯¢æ¥ä»·å |
| | | Long quotationId; |
| | | try { |
| | | quotationId = Long.parseLong(event.getBusinessKey()); |
| | | } catch (NumberFormatException e) { |
| | | return; // businessKey 䏿¯æ°åï¼å¿½ç¥ |
| | | } |
| | | |
| | | CrmSaleQuotationDO saleQuotation = saleQuotationMapper.selectById(quotationId); |
| | | if (saleQuotation == null) { |
| | | return; // 䏿¯é宿¥ä»·ï¼å¿½ç¥ |
| | | } |
| | | |
| | | // åªæå®¡æ¹ä¸ç¶æçæ¥ä»·åæå¤ç |
| | | if (!CrmSaleQuotationStatusEnum.PROCESS.getStatus().equals(saleQuotation.getStatus())) { |
| | | log.warn("[onApplicationEvent] é宿¥ä»·å({}) ä¸å¤äºå®¡æ¹ä¸ç¶æï¼å½åç¶æ: {}", quotationId, saleQuotation.getStatus()); |
| | | return; |
| | | } |
| | | |
| | | log.info("[onApplicationEvent] é宿¥ä»·å({}) 审æ¹ç¶ææ´æ°: {}", quotationId, event.getStatus()); |
| | | // æ´æ°å®¡æ¹ç¶æ |
| | | saleQuotationService.updateSaleQuotationAuditStatus(quotationId, event.getStatus()); |
| | | } |
| | | |
| | | } |