From a378f38df1785cbc8c9f1d55ace6a37952d2f65b Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 10 八月 2026 14:01:56 +0800
Subject: [PATCH] feat(crm): 新增开票功能并完善权限查询
---
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceRespVO.java | 102 ++
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java | 93 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java | 29
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java | 49 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportRespVO.java | 24
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java | 300 +++++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java | 139 +++
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceSaveReqVO.java | 55 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java | 25
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java | 5
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/invoice/CrmInvoiceDO.java | 97 ++
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/listener/CrmInvoiceStatusListener.java | 31
docs/crm_invoice_integration.md | 129 +++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java | 75 +
yudao-server/src/main/resources/application-jhhg.yaml | 324 +++++++
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoicePageReqVO.java | 35
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApiImpl.java | 6
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportReqVO.java | 19
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java | 13
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java | 7
docs/sql/crm_invoice_init.sql | 53 +
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java | 7
docs/sql/clean_business_data.sql | 248 ++++-
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java | 7
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java | 20
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java | 1
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java | 8
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceService.java | 139 +++
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java | 1
docs/sql/hrm_employee_import.sql | 8
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java | 11
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java | 5
docs/hrm_employee_import.md | 125 +++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java | 15
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApi.java | 7
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java | 6
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java | 3
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java | 12
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/CrmInvoiceController.java | 188 ++++
39 files changed, 2,372 insertions(+), 49 deletions(-)
diff --git a/docs/crm_invoice_integration.md b/docs/crm_invoice_integration.md
new file mode 100644
index 0000000..99b2106
--- /dev/null
+++ b/docs/crm_invoice_integration.md
@@ -0,0 +1,129 @@
+# CRM 寮�绁ㄥ姛鑳� - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 鏂板锛歚views/crm/invoice/index.vue`锛堝紑绁ㄥ垪琛級銆乣views/crm/invoice/modules/form.vue`锛堝紑绁ㄨ〃鍗曪級銆乣views/crm/invoice/detail/index.vue`锛堝紑绁ㄨ鎯咃級
+- 鏂板锛歚src/api/crm/invoice/index.ts`锛堝紑绁ㄦ帴鍙o級
+- 淇敼锛歚views/crm/receivable/modules/form.vue`锛堝洖娆捐〃鍗曞鍔犮�屽厛寮�绁ㄥ悗鍥炴銆嶆彁绀轰笌鏍¢獙锛�
+- 淇敼锛氬洖娆惧垪琛�/璇︽儏锛堝睍绀哄紑绁ㄦ儏鍐垫彁绀猴紝鎴栬烦杞紑绁級
+
+> 鍚庣鑿滃崟宸叉柊澧炪�岃惀閿�绠$悊 -> 寮�绁ㄧ鐞嗐�嶏紙component = `crm/invoice/index`锛夛紝鍓嶇鍙渶鍒涘缓瀵瑰簲鐩綍涓庨〉闈㈢粍浠躲��
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. 鍚堝悓瀹℃壒閫氳繃 -> 鍒涘缓寮�绁細寮�绁ㄦ椂閫夋嫨鍚堝悓锛屽鎴枫�佸悎鍚屽悕绉颁粠鍚堝悓甯﹀叆
+2. 寮�绁ㄦ彁浜ゅ鎵� -> 瀹℃壒閫氳繃锛氬鎵归�氳繃鍚庡紑绁ㄦ墠鐢熸晥锛屾墠鍏佽鍙備笌鍥炴鏍¢獙
+3. 瀹℃壒閫氳繃 -> 涓婁紶鍙戠エ闄勪欢锛氶�氳繃闄勪欢鏈哄埗涓婁紶鍙戠エ鎵弿浠�/鐢靛瓙绁�
+4. 鍙戠エ宸蹭笂浼犻檮浠� -> 鍒涘缓鍥炴锛氬洖娆惧垱寤烘椂鍚庣鏍¢獙銆屽悎鍚岀疮璁″凡寮�绁ㄤ笖宸蹭笂浼犻檮浠剁殑閲戦 >= 绱鍥炴閲戦锛堝惈鏈锛夈��
+
+## API
+
+### 寮�绁ㄦ帴鍙�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/crm/invoice/create` | 鍒涘缓寮�绁� |
+| PUT | `/crm/invoice/update` | 鏇存柊寮�绁紙浠呰崏绋�/瀹℃壒涓級 |
+| DELETE | `/crm/invoice/delete` | 鍒犻櫎寮�绁紙瀹℃壒閫氳繃涓嶅彲鍒狅級 |
+| GET | `/crm/invoice/get` | 鑾峰緱寮�绁ㄨ鎯咃紙鍚檮浠跺垪琛級 |
+| GET | `/crm/invoice/page` | 鑾峰緱寮�绁ㄥ垎椤� |
+| GET | `/crm/invoice/page-by-customer` | 鍩轰簬瀹㈡埛鐨勫紑绁ㄥ垎椤� |
+| GET | `/crm/invoice/export-excel` | 瀵煎嚭寮�绁� Excel |
+| PUT | `/crm/invoice/submit` | 鎻愪氦寮�绁ㄥ鎵� |
+| GET | `/crm/invoice/audit-count` | 寰呭鏍稿紑绁ㄦ暟閲� |
+
+**鍒涘缓/鏇存柊璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鍚� | 鏇存柊鏃跺繀濉� |
+| contractId | Long | 鏄� | 鍚堝悓缂栧彿锛岄�夋嫨鍚堝悓鍚庡鎴疯嚜鍔ㄥ甫鍏� |
+| ownerUserId | Long | 鍚� | 璐熻矗浜猴紝榛樿褰撳墠鐢ㄦ埛 |
+| invoiceType | Integer | 鏄� | 鍙戠エ绫诲瀷锛屽瓧鍏� `crm_invoice_type`锛�1涓撶敤 2鏅�� 3鐢靛瓙锛� |
+| invoiceNo | String | 鍚� | 鍙戠エ鍙风爜 |
+| invoiceTitle | String | 鍚� | 鍙戠エ鎶ご |
+| price | BigDecimal | 鏄� | 寮�绁ㄩ噾棰濓紙鍏冿級 |
+| invoiceTime | LocalDateTime | 鏄� | 寮�绁ㄦ棩鏈� |
+| remark | String | 鍚� | 澶囨敞 |
+| blobIds | Long[] | 鍚� | 鍙戠エ闄勪欢 BlobId 鍒楄〃 |
+
+**鍝嶅簲瀛楁鏂板锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| no | String | 寮�绁ㄧ紪鍙凤紙KP+鏃ユ湡+搴忓彿锛屼繚瀛樻椂鍚庣鐢熸垚锛� |
+| customerId / customerName | Long / String | 瀹㈡埛 |
+| contract | CrmContractRespVO | 鍚堝悓淇℃伅 |
+| ownerUserName / ownerUserDeptName | String | 璐熻矗浜�/閮ㄩ棬 |
+| auditStatus | Integer | 瀹℃壒鐘舵�侊紙瀛楀吀 `crm_audit_status`锛� |
+| attachmentList | StorageBlobRespDTO[] | 鍙戠エ闄勪欢鍒楄〃 |
+| processInstanceId | String | 宸ヤ綔娴佺紪鍙� |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "id": 1,
+ "no": "KP20260810000001",
+ "contractId": 100,
+ "contract": { "id": 100, "no": "HT2026...", "name": "..." },
+ "customerId": 200,
+ "customerName": "XX绉戞妧",
+ "invoiceType": 1,
+ "invoiceNo": "02564178",
+ "invoiceTitle": "XX绉戞妧鏈夐檺鍏徃",
+ "price": 9000.00,
+ "invoiceTime": "2026-08-10",
+ "auditStatus": 0,
+ "attachmentList": [{ "id": 1, "url": "...", "name": "鍙戠エ.pdf" }]
+ }
+}
+```
+
+### 闄勪欢鎺ュ彛锛堟部鐢� system 妯″潡锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/system/storage-blob/upload` | 涓婁紶鍙戠エ鏂囦欢锛岃幏寰� `blobId` |
+| POST | `/system/storage-attachment/bind` | 缁戝畾闄勪欢鍒板紑绁紙`recordType=crm_invoice`銆乣recordId=寮�绁╥d`锛� |
+| GET | `/system/storage-attachment/list` | 鏌ヨ寮�绁ㄩ檮浠跺垪琛� |
+| DELETE | `/system/storage-attachment/delete` | 鍒犻櫎闄勪欢 |
+
+> 涓婁紶涓庝繚瀛樺垎绂伙細琛ㄥ崟鍏堥�夋枃浠朵笂浼犳嬁鍒� `blobId`锛岄殢鍒涘缓/鏇存柊鎺ュ彛涓�璧锋彁浜� `blobIds`锛涘悗绔粦瀹氥�傝鎯呮帴鍙h繑鍥炵殑 `attachmentList` 鍙洿鎺ュ睍绀�/棰勮銆�
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| no 寮�绁ㄧ紪鍙� | 鍒楄〃銆佽鎯� | 璺宠浆璇︽儏 |
+| customerName 瀹㈡埛 | 鍒楄〃銆佽鎯� | 璺宠浆瀹㈡埛璇︽儏 |
+| contract 鍚堝悓 | 鍒楄〃銆佽鎯� | 璺宠浆鍚堝悓璇︽儏 |
+| invoiceType 鍙戠エ绫诲瀷 | 鍒楄〃銆佽鎯� | 瀛楀吀灞曠ず |
+| invoiceNo 鍙戠エ鍙风爜 | 鍒楄〃銆佽鎯� | - |
+| price 寮�绁ㄩ噾棰� | 鍒楄〃銆佽鎯� | 閲戦鏍煎紡鍖� |
+| invoiceTime 寮�绁ㄦ棩鏈� | 鍒楄〃銆佽鎯� | 鏃ユ湡鏍煎紡鍖� |
+| auditStatus 瀹℃壒鐘舵�� | 鍒楄〃銆佽鎯� | 瀛楀吀 `crm_audit_status` |
+| attachmentList 鍙戠エ闄勪欢 | 璇︽儏 | 闄勪欢鍒楄〃锛屽彲棰勮/涓嬭浇 |
+| ownerUserName 璐熻矗浜� | 鍒楄〃 | - |
+| remark 澶囨敞 | 鍒楄〃銆佽鎯� | - |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鍒涘缓鍥炴 | 鍚庣鏍¢獙锛氬悓鍚堝悓銆屽鎵归�氳繃涓斿凡涓婁紶鍙戠エ闄勪欢銆嶇殑绱寮�绁ㄩ噾棰� >= 绱鍥炴閲戦锛堝惈鏈锛夛紝鍚﹀垯鎶ラ敊銆屽悎鍚岀疮璁″紑绁ㄩ噾棰濅笉瓒筹紝鐩墠鍙洖娆� X 鍏冦�� |
+| 寮�绁ㄩ噾棰� | 鍚庣鏍¢獙锛氬紑绁ㄩ噾棰濅笉鑳借秴杩囧悎鍚岄噾棰濆噺鍘诲凡瀹℃壒閫氳繃鐨勫紑绁ㄩ噾棰� |
+| 寮�绁ㄧ紪杈� | 浠呰崏绋裤�佸鎵逛腑鍙紪杈戯紱瀹㈡埛銆佸悎鍚屻�佽礋璐d汉涓嶅彲淇敼 |
+| 寮�绁ㄥ垹闄� | 瀹℃壒閫氳繃鍚庝笉鍙垹闄� |
+| 寮�绁ㄥ鎵� | 鎻愪氦鍚庤蛋 BPM 娴佺▼锛坘ey=`crm-invoice-audit`锛夛紝瀹℃壒閫氳繃鎵嶇敓鏁� |
+| 寰呭姙 | 鍚庣 `audit-count` 鍙敤浜庡緟鍔炵孩鐐� |
+
+## 娉ㄦ剰浜嬮」
+
+- 鍙戠エ绫诲瀷瀛楀吀 `crm_invoice_type` 鍚庣宸查厤缃紝鍓嶇闇�鍦ㄥ父閲�/瀛楀吀涓敞鍐岋紙1澧炲�肩◣涓撶敤 2澧炲�肩◣鏅�� 3鐢靛瓙锛�
+- 鑿滃崟鏉冮檺锛氱鐞嗗憳闇�灏嗘柊鑿滃崟锛坄crm:invoice:*`锛夊垎閰嶇粰鐩稿簲瑙掕壊
+- 鍥炴琛ㄥ崟鍦ㄧ敤鎴烽�夋嫨鍚堝悓鍚庯紝鍙彁绀恒�岃鍏堝畬鎴愬紑绁ㄥ苟涓婁紶鍙戠エ闄勪欢銆嶏紝鑻ユ湭寮�绁ㄥ垯鍒涘缓鍥炴浼氭敹鍒板悗绔敊璇彁绀�
+- 闄勪欢蹇呴』璧� `/system/storage-*` 鎺ュ彛锛宍recordType` 鍥哄畾涓� `crm_invoice`锛岀姝㈠湪涓氬姟琛ㄦ柊澧炴枃浠跺湴鍧�瀛楁
+- 涓婁紶鐨勫彂绁ㄦ枃浠跺缓璁檺鍒跺浘鐗�/PDF 鏍煎紡
diff --git a/docs/hrm_employee_import.md b/docs/hrm_employee_import.md
new file mode 100644
index 0000000..25fbea1
--- /dev/null
+++ b/docs/hrm_employee_import.md
@@ -0,0 +1,125 @@
+# 鍛樺伐淇℃伅瀵煎叆 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 浜哄姏璧勬簮 > 鍛樺伐绠$悊 鍒楄〃椤碉紙`src/views/hrm/employee/index.vue`锛�
+- 鍛樺伐鏂板/缂栬緫寮圭獥锛坄src/views/hrm/employee/modules/form.vue`銆乣src/views/hrm/employee/data.ts`锛�
+- 鍛樺伐璇︽儏锛坄src/views/hrm/employee/modules/detail.vue`锛�
+- 鏂板锛氬憳宸ュ鍏ュ脊绐楋紙鍚笅杞芥ā鏉裤�佷笂浼犳枃浠躲�佸睍绀哄鍏ョ粨鏋滐級
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. **涓嬭浇妯℃澘** -> 鍓嶇璋冪敤 `GET /hrm/employee/import-template` 涓嬭浇 Excel 妯℃澘锛屾ā鏉垮寘鍚ず渚嬭渚涘弬鑰冦��
+2. **濉啓妯℃澘** -> 鐢ㄦ埛鎸夋ā鏉垮~鍐欏憳宸ヤ俊鎭紝鍏朵腑"閮ㄩ棬鍚嶇О/宀椾綅鍚嶇О/鐢ㄦ埛瑙掕壊"涓夊垪濉�**鍚嶇О**锛堢敤鎴疯鑹插涓敤涓枃閫楀彿鍒嗛殧锛夛紝鐢卞悗绔嚜鍔ㄨ浆鎹负 ID銆�
+3. **涓婁紶瀵煎叆** -> 鍓嶇璋冪敤 `POST /hrm/employee/import`锛坢ultipart 琛ㄥ崟锛宖ile + updateSupport锛変笂浼犳枃浠躲��
+4. **灞曠ず缁撴灉** -> 鍚庣杩斿洖瀵煎叆缁撴灉锛堝垱寤烘垚鍔熷悕鍗� / 鏇存柊鎴愬姛鍚嶅崟 / 澶辫触鍚嶅崟鍙婂師鍥狅級锛屽墠绔�愭潯灞曠ず銆�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/hrm/employee/import-template` | 涓嬭浇鍛樺伐瀵煎叆妯℃澘锛堝惈绀轰緥鏁版嵁锛� |
+| POST | `/hrm/employee/import` | 瀵煎叆鍛樺伐 Excel |
+
+### 涓嬭浇妯℃澘
+
+**鍝嶅簲锛�** Excel 鏂囦欢锛坄鍛樺伐瀵煎叆妯℃澘.xls`锛夛紝琛ㄥご濡備笅锛�
+
+| 鍛樺伐濮撳悕 | 鎬у埆 | 鎵嬫満鍙� | 閮ㄩ棬鍚嶇О | 宀椾綅鍚嶇О | 鐢ㄦ埛瑙掕壊 | 鍏ヨ亴鏃ユ湡 | 鍛樺伐鐘舵�� | 韬唤璇佸彿 | 鍑虹敓鏃ユ湡 | 姘戞棌 | 濠氬Щ鐘舵�� | 骞撮緞 | 瀛﹀巻 | 鏀挎不闈㈣矊 | 閭 | 澶囨敞 |
+|---------|------|--------|---------|---------|---------|---------|---------|---------|---------|------|---------|------|------|---------|------|------|
+
+- 鎬у埆锛氬~"鐢�"/"濂�"锛堝瓧鍏� `hrm_gender`锛屽�� 1/2锛�
+- 鍛樺伐鐘舵�侊細濉�"鍦ㄨ亴"/"璇曠敤"/"绂昏亴"锛堝瓧鍏� `hrm_employee_status`锛屽�� 1/2/3锛�
+- 鐢ㄦ埛瑙掕壊锛氬~瑙掕壊鍚嶇О锛屽涓敤**涓枃閫楀彿**鍒嗛殧锛堝"鏅�氱鐞嗗憳,鏅�氬憳宸�"锛�
+
+### 瀵煎叆鍛樺伐
+
+**璇锋眰鍙傛暟锛坢ultipart/form-data锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| file | MultipartFile | 鏄� | 涓婁紶鐨� Excel 鏂囦欢 |
+| updateSupport | Boolean | 鍚� | 鏄惁鏀寔鏇存柊锛岄粯璁� false銆備负 true 鏃舵墜鏈哄彿宸插瓨鍦ㄧ殑鍛樺伐浼氳鏇存柊锛屽惁鍒欒涓哄け璐� |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "createNames": ["寮犱笁", "鏉庡洓"],
+ "updateNames": ["鐜嬩簲"],
+ "failureNames": {
+ "璧靛叚": "鎵嬫満鍙峰凡瀛樺湪锛岃鏇存崲鎵嬫満鍙�",
+ "瀛欎竷": "閮ㄩ棬銆愪笉瀛樺湪鐨勯儴闂ㄣ�戜笉瀛樺湪"
+ }
+ }
+}
+```
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| createNames | List\<String\> | 鍒涘缓鎴愬姛鐨勫憳宸ュ鍚嶆暟缁� |
+| updateNames | List\<String\> | 鏇存柊鎴愬姛鐨勫憳宸ュ鍚嶆暟缁� |
+| failureNames | Map\<String, String\> | 瀵煎叆澶辫触闆嗗悎锛宬ey 涓哄憳宸ュ鍚嶏紝value 涓哄け璐ュ師鍥� |
+
+## 瀛楁灞曠ず瑙勫垯
+
+### 瀵煎叆妯℃澘锛堟柊澧炲瓧娈碉級
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 鍛樺伐濮撳悕 | 妯℃澘鍒� | 蹇呭~ |
+| 鎬у埆 | 妯℃澘鍒� | 蹇呭~锛岄粯璁ょ敺 |
+| 鎵嬫満鍙� | 妯℃澘鍒� | 蹇呭~锛屽皢浣滀负绯荤粺鐧诲綍璐﹀彿 |
+| 閮ㄩ棬鍚嶇О | 妯℃澘鍒� | 蹇呭~锛屽~鍚嶇О锛屽悗绔浆 ID |
+| 宀椾綅鍚嶇О | 妯℃澘鍒� | 蹇呭~锛屽~鍚嶇О锛屽悗绔浆 ID |
+| 鐢ㄦ埛瑙掕壊 | 妯℃澘鍒� | 閫夊~锛屽~鍚嶇О锛屽涓敤涓枃閫楀彿鍒嗛殧 |
+| 鍏ヨ亴鏃ユ湡 | 妯℃澘鍒� | 蹇呭~锛屾牸寮� YYYY-MM-DD |
+| 鍛樺伐鐘舵�� | 妯℃澘鍒� | 蹇呭~锛岄粯璁ゅ湪鑱� |
+| 韬唤璇佸彿 | 妯℃澘鍒� | 閫夊~ |
+| 鍑虹敓鏃ユ湡 | 妯℃澘鍒� | 閫夊~锛屾牸寮� YYYY-MM-DD |
+| 姘戞棌 | 妯℃澘鍒� | 閫夊~锛屾枃鏈紙濡�"姹夋棌"锛� |
+| 濠氬Щ鐘舵�� | 妯℃澘鍒� | 閫夊~锛屾枃鏈紙濡�"宸插"锛� |
+| 骞撮緞 | 妯℃澘鍒� | 閫夊~锛屾暟瀛� |
+| 瀛﹀巻 | 妯℃澘鍒� | 閫夊~锛屾枃鏈紙濡�"鏈"锛� |
+| 鏀挎不闈㈣矊 | 妯℃澘鍒� | 閫夊~锛屾枃鏈紙濡�"涓叡鍏氬憳"锛� |
+| 閭 | 妯℃澘鍒� | 閫夊~ |
+| 澶囨敞 | 妯℃澘鍒� | 閫夊~ |
+
+### 鏂板/缂栬緫琛ㄥ崟锛堟柊澧炲瓧娈碉級
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 姘戞棌 | 琛ㄥ崟 | 鏂囨湰杈撳叆妗� |
+| 濠氬Щ鐘舵�� | 琛ㄥ崟 | 鏂囨湰杈撳叆妗� |
+| 骞撮緞 | 琛ㄥ崟 | 鏁板瓧杈撳叆妗� |
+| 瀛﹀巻 | 琛ㄥ崟 | 鏂囨湰杈撳叆妗� |
+| 鏀挎不闈㈣矊 | 琛ㄥ崟 | 鏂囨湰杈撳叆妗� |
+
+### 鍒楄〃 / 璇︽儏 / 瀵煎嚭
+
+- 鏂板瀛楁涓虹函鏂囨湰锛屾棤闇�瀛楀吀杞爜锛屽彲鐩存帴灞曠ず
+- 瀵煎嚭 Excel锛坄GET /hrm/employee/export-excel`锛夊凡鍖呭惈涓婅堪瀛楁鍒�
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鎵嬫満鍙峰凡瀛樺湪涓� updateSupport=false | 璇ヨ瀵煎叆澶辫触锛屽師鍥�"鎵嬫満鍙峰凡瀛樺湪锛岃鏇存崲鎵嬫満鍙�" |
+| 鎵嬫満鍙峰凡瀛樺湪涓� updateSupport=true | 鏇存柊璇ュ憳宸ヤ俊鎭紙鍚柊澧炲瓧娈碉級 |
+| 閮ㄩ棬鍚嶇О / 宀椾綅鍚嶇О濉敊鎴栦笉瀛樺湪 | 璇ヨ瀵煎叆澶辫触锛屽師鍥�"閮ㄩ棬銆恱x銆戜笉瀛樺湪" / "宀椾綅銆恱x銆戜笉瀛樺湪" |
+| 鐢ㄦ埛瑙掕壊鍚嶇О濉敊鎴栦笉瀛樺湪 | 璇ヨ瀵煎叆澶辫触锛屽師鍥�"瑙掕壊銆恱x銆戜笉瀛樺湪" |
+| 鍛樺伐濮撳悕銆佹墜鏈哄彿銆佸叆鑱屾棩鏈熶负绌� | 璇ヨ瀵煎叆澶辫触锛屽師鍥�"瀵煎叆瀛楁銆恱x銆戜笉鑳戒负绌�" |
+| 鎬у埆銆佸憳宸ョ姸鎬佷负绌� | 榛樿鎸夌敺 / 鍦ㄨ亴澶勭悊 |
+| 瀵煎叆鏃跺憳宸ョ紪鍙� | 鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紝鏃犻渶鍦ㄦā鏉夸腑濉啓 |
+
+## 娉ㄦ剰浜嬮」
+
+- 瀵煎叆鎺ュ彛鏉冮檺鐮佷负 `hrm:employee:import`锛岄渶鍦ㄨ彍鍗曟潈闄愪腑閰嶇疆銆�
+- 鐢ㄦ埛瑙掕壊鍚嶇О闇�涓庣郴缁熻鑹插畬鍏ㄤ竴鑷达紙绮剧‘鍖归厤锛屼笉鍋氭ā绯婏級銆�
+- 鍚屼竴琛屽唴锛岄儴闂� / 宀椾綅 / 瑙掕壊瑙f瀽澶辫触鍒欒琛屾暣浣撳け璐ワ紝涓嶄細閮ㄥ垎瀵煎叆銆�
+- 瀵煎叆鍒涘缓鍛樺伐鏃朵細鍚屾鍒涘缓绯荤粺鐧诲綍璐﹀彿锛堢敤鎴峰悕涓烘墜鏈哄彿锛岄粯璁ゅ瘑鐮佷负鎵嬫満鍙凤級锛岃鎻愰啋浣跨敤鏂规敞鎰忚处鍙峰畨鍏ㄣ��
+- 妯℃澘涓ず渚嬭鐨勯儴闂� / 宀椾綅 / 瑙掕壊鍚嶇О涓虹ず渚嬫暟鎹紝姝e紡瀵煎叆鍓嶉渶鏇挎崲涓虹郴缁熶腑鐪熷疄瀛樺湪鐨勫悕绉般��
diff --git a/docs/sql/clean_business_data.sql b/docs/sql/clean_business_data.sql
index 61da7f2..e2611d5 100644
--- a/docs/sql/clean_business_data.sql
+++ b/docs/sql/clean_business_data.sql
@@ -1,7 +1,8 @@
-- ============================================================
-- 绯荤粺涓氬姟鏁版嵁娓呯悊瀛樺偍杩囩▼
--
--- 鐢ㄩ�旓細娓呯┖鎵�鏈変笟鍔″崟鎹暟鎹紝淇濈暀绯荤粺閰嶇疆鍜屽熀纭�鏁版嵁
+-- 鐢ㄩ�旓細娓呯┖鎵�鏈変笟鍔℃暟鎹� + 鍩虹鏁版嵁锛堢墿鏂�/瀹㈡埛/渚涘簲鍟�/浠撳簱绛夛級
+-- 淇濈暀锛氱郴缁熻〃銆佸伐浣滄祦寮曟搸銆佸畾鏃朵换鍔°�佺紪鐮佽鍒欍�佸熀纭�璁炬柦閰嶇疆
-- 娉ㄦ剰锛氭鎿嶄綔涓嶅彲閫嗭紝鎵ц鍓嶈纭宸插浠芥暟鎹簱
--
-- 浣跨敤鏂瑰紡锛欳ALL sp_clean_business_data();
@@ -13,11 +14,10 @@
CREATE PROCEDURE sp_clean_business_data()
BEGIN
- -- 鍏抽棴澶栭敭妫�鏌ワ紝閬垮厤鍥犺〃闂村紩鐢ㄥ鑷村垹闄ゅけ璐�
SET FOREIGN_KEY_CHECKS = 0;
-- ============================================================
- -- 1. 绯荤粺鏃ュ織涓嶵oken锛堝彲瀹夊叏娓呯悊锛�
+ -- 1. 绯荤粺鏃ュ織涓嶵oken
-- ============================================================
TRUNCATE TABLE system_login_log;
TRUNCATE TABLE system_operate_log;
@@ -31,7 +31,7 @@
TRUNCATE TABLE infra_job_log;
-- ============================================================
- -- 2. 鏂囦欢涓庨檮浠讹紙涓氬姟鏂囦欢锛屾竻鐞嗗悗涓嶅奖鍝嶇郴缁熻繍琛岋級
+ -- 2. 鏂囦欢涓庨檮浠�
-- ============================================================
TRUNCATE TABLE system_storage_attachment;
TRUNCATE TABLE system_storage_blob;
@@ -62,28 +62,19 @@
-- ============================================================
-- 4. MES 浠撳簱妯″潡 鈥� 涓氬姟鍗曟嵁
-- ============================================================
- -- 搴撳瓨浜ゆ槗娴佹按锛堝簳灞傦紝鍏堟竻锛�
TRUNCATE TABLE mes_wm_transaction;
TRUNCATE TABLE mes_wm_stock_reserve;
TRUNCATE TABLE mes_wm_material_stock;
-
- -- 搴忓垪鍙� / 鎵规 / 鏉$爜
TRUNCATE TABLE mes_wm_sn;
TRUNCATE TABLE mes_wm_batch;
TRUNCATE TABLE mes_wm_barcode;
-
- -- 鐩樼偣
TRUNCATE TABLE mes_wm_stock_taking_task_result;
TRUNCATE TABLE mes_wm_stock_taking_task_line;
TRUNCATE TABLE mes_wm_stock_taking_task;
TRUNCATE TABLE mes_wm_stock_taking_plan_param;
TRUNCATE TABLE mes_wm_stock_taking_plan;
-
- -- 鍖呰
TRUNCATE TABLE mes_wm_package_line;
TRUNCATE TABLE mes_wm_package;
-
- -- 鍑哄叆搴撴槑缁�/琛岋紙鍏堟竻 detail/line锛屽啀娓呬富琛級
TRUNCATE TABLE mes_wm_item_receipt_detail;
TRUNCATE TABLE mes_wm_item_receipt_line;
TRUNCATE TABLE mes_wm_item_receipt;
@@ -132,7 +123,7 @@
TRUNCATE TABLE mes_wm_arrival_notice;
-- ============================================================
- -- 5. MES 璐ㄦ妯″潡 鈥� 涓氬姟鏁版嵁
+ -- 5. MES 璐ㄦ妯″潡 鈥� 涓氬姟鍗曟嵁
-- ============================================================
TRUNCATE TABLE mes_qc_indicator_result_detail;
TRUNCATE TABLE mes_qc_indicator_result;
@@ -165,11 +156,100 @@
TRUNCATE TABLE mes_pd_document;
TRUNCATE TABLE mes_pd_project;
- -- 缂栫爜鐢熸垚璁板綍锛堣鍒欐湰韬繚鐣欙級
+ -- ============================================================
+ -- 8. MES 鍩虹鏁版嵁 鈥� 鐗╂枡/浜у搧/BOM/SOP/SIP
+ -- ============================================================
+ TRUNCATE TABLE mes_md_product_bom;
+ TRUNCATE TABLE mes_md_product_sip;
+ TRUNCATE TABLE mes_md_product_sop;
+ TRUNCATE TABLE mes_md_item;
+ TRUNCATE TABLE mes_md_item_batch_config;
+ TRUNCATE TABLE mes_md_item_type;
+ TRUNCATE TABLE mes_md_unit_measure;
+ TRUNCATE TABLE mes_md_vendor;
TRUNCATE TABLE mes_md_auto_code_record;
-- ============================================================
- -- 8. CRM 妯″潡 鈥� 涓氬姟鍗曟嵁
+ -- 9. MES 鍩虹鏁版嵁 鈥� 杞﹂棿/宸ヤ綔绔�/璧勬簮
+ -- ============================================================
+ TRUNCATE TABLE mes_md_workstation_machine;
+ TRUNCATE TABLE mes_md_workstation_tool;
+ TRUNCATE TABLE mes_md_workstation_worker;
+ TRUNCATE TABLE mes_md_workstation;
+ TRUNCATE TABLE mes_md_workshop;
+
+ -- ============================================================
+ -- 10. MES 鍩虹鏁版嵁 鈥� 鐝粍/鎺掔彮
+ -- ============================================================
+ TRUNCATE TABLE mes_cal_plan_team;
+ TRUNCATE TABLE mes_cal_plan_shift;
+ TRUNCATE TABLE mes_cal_plan;
+ TRUNCATE TABLE mes_cal_team_shift;
+ TRUNCATE TABLE mes_cal_team_member;
+ TRUNCATE TABLE mes_cal_team;
+ TRUNCATE TABLE mes_cal_holiday;
+
+ -- ============================================================
+ -- 11. MES 鍩虹鏁版嵁 鈥� 璁惧/宸ュ叿鍙拌处
+ -- ============================================================
+ TRUNCATE TABLE mes_dv_check_plan_subject;
+ TRUNCATE TABLE mes_dv_check_plan_machinery;
+ TRUNCATE TABLE mes_dv_check_plan;
+ TRUNCATE TABLE mes_dv_machinery;
+ TRUNCATE TABLE mes_dv_machinery_type;
+ TRUNCATE TABLE mes_dv_subject;
+ TRUNCATE TABLE mes_tm_tool;
+ TRUNCATE TABLE mes_tm_tool_type;
+
+ -- ============================================================
+ -- 12. MES 鍩虹鏁版嵁 鈥� 宸ヨ壓璺嚎/宸ュ簭/鍙傛暟
+ -- ============================================================
+ TRUNCATE TABLE mes_pro_route_product_bom;
+ TRUNCATE TABLE mes_pro_route_product;
+ TRUNCATE TABLE mes_pro_route_process;
+ TRUNCATE TABLE mes_pro_route;
+ TRUNCATE TABLE mes_pro_process_content;
+ TRUNCATE TABLE mes_pro_process;
+ TRUNCATE TABLE mes_pd_process_param_template_detail;
+ TRUNCATE TABLE mes_pd_process_param_template;
+ TRUNCATE TABLE mes_pd_process_param;
+
+ -- ============================================================
+ -- 13. MES 鍩虹鏁版嵁 鈥� 璐ㄦ鏂规/缂洪櫡/鎸囨爣
+ -- ============================================================
+ TRUNCATE TABLE mes_qc_template_item;
+ TRUNCATE TABLE mes_qc_template_indicator;
+ TRUNCATE TABLE mes_qc_template;
+ TRUNCATE TABLE mes_qc_defect;
+ TRUNCATE TABLE mes_qc_indicator;
+
+ -- ============================================================
+ -- 14. MES 鍩虹鏁版嵁 鈥� 浠撳簱/搴撳尯/搴撲綅
+ -- ============================================================
+ TRUNCATE TABLE mes_wm_warehouse_location;
+ TRUNCATE TABLE mes_wm_warehouse_area;
+ TRUNCATE TABLE mes_wm_warehouse;
+
+ -- ============================================================
+ -- 15. MES 鍩虹閰嶇疆 鈥� 缂栫爜璁板綍/鏉$爜/瀹夌伅閰嶇疆
+ -- ============================================================
+ TRUNCATE TABLE mes_wm_barcode_config;
+ TRUNCATE TABLE mes_pro_andon_config;
+
+ -- ============================================================
+ -- 16. MDM 涓绘暟鎹�
+ -- ============================================================
+ TRUNCATE TABLE mdm_item_sku;
+ TRUNCATE TABLE mdm_item_migration_map;
+ TRUNCATE TABLE mdm_item_batch_config;
+ TRUNCATE TABLE mdm_item;
+ TRUNCATE TABLE mdm_item_category;
+ TRUNCATE TABLE mdm_brand;
+ TRUNCATE TABLE mdm_unit_measure;
+ TRUNCATE TABLE mdm_warehouse;
+
+ -- ============================================================
+ -- 17. CRM 妯″潡 鈥� 涓氬姟鍗曟嵁 + 瀹㈡埛 + 閰嶇疆
-- ============================================================
TRUNCATE TABLE after_sale_return_item;
TRUNCATE TABLE after_sale_return;
@@ -191,9 +271,14 @@
TRUNCATE TABLE crm_follow_up_record;
TRUNCATE TABLE crm_customer;
TRUNCATE TABLE crm_permission;
+ TRUNCATE TABLE crm_business_status;
+ TRUNCATE TABLE crm_business_status_type;
+ TRUNCATE TABLE crm_contract_config;
+ TRUNCATE TABLE crm_customer_limit_config;
+ TRUNCATE TABLE crm_customer_pool_config;
-- ============================================================
- -- 9. ERP 妯″潡 鈥� 涓氬姟鍗曟嵁
+ -- 18. ERP 妯″潡 鈥� 涓氬姟鍗曟嵁 + 渚涘簲鍟� + 缁撶畻璐︽埛
-- ============================================================
TRUNCATE TABLE erp_finance_payment_item;
TRUNCATE TABLE erp_finance_payment;
@@ -205,9 +290,31 @@
TRUNCATE TABLE erp_purchase_request;
TRUNCATE TABLE erp_sale_order_items;
TRUNCATE TABLE erp_sale_order;
+ TRUNCATE TABLE erp_supplier;
+ TRUNCATE TABLE erp_account;
-- ============================================================
- -- 10. HRM 妯″潡 鈥� 涓氬姟鍗曟嵁
+ -- 19. SRM 妯″潡 鈥� 涓氬姟鍗曟嵁 + 渚涘簲鍟� + 閰嶇疆
+ -- ============================================================
+ TRUNCATE TABLE srm_bid_award;
+ TRUNCATE TABLE srm_bid_evaluation;
+ TRUNCATE TABLE srm_bid_open;
+ TRUNCATE TABLE srm_tender_bid;
+ TRUNCATE TABLE srm_tender_material;
+ TRUNCATE TABLE srm_tender_project;
+ TRUNCATE TABLE srm_delivery_plan;
+ TRUNCATE TABLE srm_purchase_collaboration;
+ TRUNCATE TABLE srm_supplier_quote;
+ TRUNCATE TABLE srm_supplier_evaluation;
+ TRUNCATE TABLE srm_supplier_certificate;
+ TRUNCATE TABLE srm_supplier_contact;
+ TRUNCATE TABLE srm_supplier_apply;
+ TRUNCATE TABLE srm_supplier_category;
+ TRUNCATE TABLE srm_supplier;
+ TRUNCATE TABLE srm_tender_config;
+
+ -- ============================================================
+ -- 20. HRM 妯″潡 鈥� 涓氬姟鍗曟嵁 + 閰嶇疆
-- ============================================================
TRUNCATE TABLE hrm_salary_payment_detail;
TRUNCATE TABLE hrm_salary_payment;
@@ -224,32 +331,27 @@
TRUNCATE TABLE hrm_employee_social_security;
TRUNCATE TABLE hrm_employee_salary;
TRUNCATE TABLE hrm_employee;
+ TRUNCATE TABLE hrm_attendance_holiday;
+ TRUNCATE TABLE hrm_attendance_rule;
+ TRUNCATE TABLE hrm_leave_type_config;
+ TRUNCATE TABLE hrm_salary_structure;
+ TRUNCATE TABLE hrm_social_security_scheme;
+ TRUNCATE TABLE hrm_tax_rate_config;
-- ============================================================
- -- 11. SRM 妯″潡 鈥� 涓氬姟鍗曟嵁
- -- ============================================================
- TRUNCATE TABLE srm_bid_award;
- TRUNCATE TABLE srm_bid_evaluation;
- TRUNCATE TABLE srm_bid_open;
- TRUNCATE TABLE srm_tender_bid;
- TRUNCATE TABLE srm_tender_material;
- TRUNCATE TABLE srm_tender_project;
- TRUNCATE TABLE srm_delivery_plan;
- TRUNCATE TABLE srm_purchase_collaboration;
- TRUNCATE TABLE srm_supplier_quote;
- TRUNCATE TABLE srm_supplier_evaluation;
- TRUNCATE TABLE srm_supplier_certificate;
- TRUNCATE TABLE srm_supplier_contact;
- TRUNCATE TABLE srm_supplier_apply;
-
- -- ============================================================
- -- 12. BPM 妯″潡 鈥� 涓氬姟鍗曟嵁
+ -- 21. BPM 妯″潡 鈥� 涓氬姟鏁版嵁 + 娴佺▼瀹氫箟
-- ============================================================
TRUNCATE TABLE bpm_oa_leave;
TRUNCATE TABLE bpm_process_instance_copy;
+ TRUNCATE TABLE bpm_process_definition_info;
+ TRUNCATE TABLE bpm_process_expression;
+ TRUNCATE TABLE bpm_process_listener;
+ TRUNCATE TABLE bpm_user_group;
+ TRUNCATE TABLE bpm_form;
+ TRUNCATE TABLE bpm_category;
-- ============================================================
- -- 13. AI 妯″潡 鈥� 浼氳瘽/鍐呭鏁版嵁
+ -- 22. AI 妯″潡 鈥� 鏁版嵁 + 閰嶇疆
-- ============================================================
TRUNCATE TABLE ai_chat_message;
TRUNCATE TABLE ai_chat_conversation;
@@ -261,9 +363,13 @@
TRUNCATE TABLE ai_music;
TRUNCATE TABLE ai_write;
TRUNCATE TABLE ai_workflow;
+ TRUNCATE TABLE ai_api_key;
+ TRUNCATE TABLE ai_model;
+ TRUNCATE TABLE ai_tool;
+ TRUNCATE TABLE ai_chat_role;
-- ============================================================
- -- 14. IM 妯″潡 鈥� 浼氳瘽/娑堟伅鏁版嵁
+ -- 23. IM 妯″潡 鈥� 鏁版嵁 + 閰嶇疆
-- ============================================================
TRUNCATE TABLE im_group_message_receipt;
TRUNCATE TABLE im_group_message;
@@ -282,27 +388,79 @@
TRUNCATE TABLE im_rtc_participant;
TRUNCATE TABLE im_rtc_call;
TRUNCATE TABLE im_user_online;
+ TRUNCATE TABLE im_sensitive_word;
+ TRUNCATE TABLE im_face_user_item;
+ TRUNCATE TABLE im_face_pack_item;
+ TRUNCATE TABLE im_face_pack;
-- ============================================================
- -- 15. OA 閫氱煡鍏憡 鈥� 涓氬姟鏁版嵁
+ -- 24. BI 鎶ヨ〃閰嶇疆
+ -- ============================================================
+ TRUNCATE TABLE bi_warehouse_coordinate;
+ TRUNCATE TABLE bi_chart_config;
+ TRUNCATE TABLE bi_dashboard;
+ TRUNCATE TABLE bi_data_source_config;
+
+ -- ============================================================
+ -- 25. 鍏朵粬妯″潡鏁版嵁
-- ============================================================
TRUNCATE TABLE oa_notice_read;
TRUNCATE TABLE oa_notice_user;
TRUNCATE TABLE oa_notice;
TRUNCATE TABLE system_notice;
+ TRUNCATE TABLE system_notify_template;
+ TRUNCATE TABLE system_mail_log;
+ TRUNCATE TABLE system_mail_template;
+ TRUNCATE TABLE system_mail_account;
+ TRUNCATE TABLE system_sms_code;
+ TRUNCATE TABLE system_sms_log;
+ TRUNCATE TABLE system_sms_template;
+ TRUNCATE TABLE system_sms_channel;
+ TRUNCATE TABLE system_social_user_bind;
+ TRUNCATE TABLE system_social_user;
+ TRUNCATE TABLE system_social_client;
+ TRUNCATE TABLE report_go_view_project;
- -- ============================================================
- -- 鎭㈠澶栭敭妫�鏌�
- -- ============================================================
SET FOREIGN_KEY_CHECKS = 1;
- -- 杩斿洖娓呯悊缁撴灉
SELECT '涓氬姟鏁版嵁娓呯悊瀹屾垚' AS result;
END$$
DELIMITER ;
-- ============================================================
--- 鎵ц绀轰緥锛�
--- CALL sp_clean_business_data();
+-- 淇濈暀鐨勮〃锛堜笉浼氳娓呯悊锛夛細
+--
+-- 銆愮郴缁熸牳蹇冦��
+-- system_users / system_dept / system_post
+-- system_role / system_role_menu
+-- system_menu
+-- system_dict_type / system_dict_data
+-- system_tenant / system_tenant_package
+-- system_user_post / system_user_role
+-- system_oauth2_client
+-- system_notify_template 鈫� 宸叉竻鐞�
+--
+-- 銆愬熀纭�璁炬柦銆�
+-- infra_config / infra_job
+-- infra_codegen_table / infra_codegen_column
+-- infra_data_source_config / infra_file_config
+--
+-- 銆愬伐浣滄祦寮曟搸銆�
+-- act_* (Activiti 寮曟搸琛�)
+-- flw_* (Flowable 寮曟搸琛�)
+--
+-- 銆愬畾鏃朵换鍔°��
+-- qrtz_* (Quartz 寮曟搸琛�)
+--
+-- 銆愮紪鐮佽鍒欙紙淇濈暀锛夈��
+-- mes_md_auto_code_rule
+-- mes_md_auto_code_part
+--
+-- 銆愮ず渚嬭〃銆�
+-- yudao_demo01_contact
+-- yudao_demo02_category
+-- yudao_demo03_course / yudao_demo03_grade / yudao_demo03_student
-- ============================================================
+
+-- 鎵ц锛欳ALL sp_clean_business_data();
diff --git a/docs/sql/crm_invoice_init.sql b/docs/sql/crm_invoice_init.sql
new file mode 100644
index 0000000..ae34570
--- /dev/null
+++ b/docs/sql/crm_invoice_init.sql
@@ -0,0 +1,53 @@
+-- =====================================================
+-- CRM 寮�绁ㄥ姛鑳藉垵濮嬪寲鑴氭湰
+-- 1. 寤鸿〃 crm_invoice
+-- 2. 鏁版嵁瀛楀吀 crm_invoice_type锛堝彂绁ㄧ被鍨嬶級
+-- 3. 鑿滃崟涓庢寜閽潈闄愶紙钀ラ攢绠$悊 -> 寮�绁ㄧ鐞嗭級
+-- =====================================================
+
+-- 1. 寤鸿〃
+CREATE TABLE IF NOT EXISTS `crm_invoice` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `no` varchar(64) NOT NULL COMMENT '寮�绁ㄧ紪鍙�',
+ `customer_id` bigint NOT NULL COMMENT '瀹㈡埛缂栧彿',
+ `contract_id` bigint NOT NULL COMMENT '鍚堝悓缂栧彿',
+ `owner_user_id` bigint NOT NULL COMMENT '璐熻矗浜虹紪鍙�',
+ `invoice_type` int NOT NULL COMMENT '鍙戠エ绫诲瀷锛堝瓧鍏� crm_invoice_type锛�',
+ `invoice_no` varchar(64) DEFAULT NULL COMMENT '鍙戠エ鍙风爜',
+ `invoice_title` varchar(255) DEFAULT NULL COMMENT '鍙戠エ鎶ご',
+ `price` decimal(18,2) NOT NULL COMMENT '寮�绁ㄩ噾棰濓紙鍏冿級',
+ `invoice_time` datetime DEFAULT NULL COMMENT '寮�绁ㄦ棩鏈�',
+ `remark` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+ `process_instance_id` varchar(64) DEFAULT NULL COMMENT '宸ヤ綔娴佺紪鍙�',
+ `audit_status` int NOT NULL COMMENT '瀹℃壒鐘舵�侊紙crm_audit_status锛�',
+ `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` tinyint NOT NULL DEFAULT 0 COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM 寮�绁�';
+
+-- 2. 鏁版嵁瀛楀吀绫诲瀷锛歝rm_invoice_type
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`)
+VALUES (1061500, 'CRM 鍙戠エ绫诲瀷', 'crm_invoice_type', 0, '寮�绁ㄨ褰曠殑鍙戠エ绫诲瀷', '1', NOW(), '1', NOW(), b'0', NULL);
+
+-- 2.1 鏁版嵁瀛楀吀鏁版嵁
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+ (1061600, 1, '澧炲�肩◣涓撶敤鍙戠エ', '1', 'crm_invoice_type', 0, 'primary', '', '', '1', NOW(), '1', NOW(), b'0'),
+ (1061601, 2, '澧炲�肩◣鏅�氬彂绁�', '2', 'crm_invoice_type', 0, 'success', '', '', '1', NOW(), '1', NOW(), b'0'),
+ (1061602, 3, '鐢靛瓙鍙戠エ', '3', 'crm_invoice_type', 0, 'warning', '', '', '1', NOW(), '1', NOW(), b'0');
+
+-- 3. 鑿滃崟锛氳惀閿�绠$悊(id=2617) -> 寮�绁ㄧ鐞�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1064000, '寮�绁ㄧ鐞�', '', 2, 59, 2617, 'invoice', '#', 'crm/invoice/index', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 3.1 寮�绁ㄧ鐞嗘寜閽潈闄�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+ (1064001, '寮�绁ㄧ鐞嗘煡璇�', 'crm:invoice:query', 3, 1, 1064000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+ (1064002, '寮�绁ㄧ鐞嗗垱寤�', 'crm:invoice:create', 3, 2, 1064000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+ (1064003, '寮�绁ㄧ鐞嗘洿鏂�', 'crm:invoice:update', 3, 3, 1064000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+ (1064004, '寮�绁ㄧ鐞嗗垹闄�', 'crm:invoice:delete', 3, 4, 1064000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+ (1064005, '寮�绁ㄧ鐞嗗鍑�', 'crm:invoice:export', 3, 5, 1064000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
diff --git a/docs/sql/hrm_employee_import.sql b/docs/sql/hrm_employee_import.sql
new file mode 100644
index 0000000..7156ae8
--- /dev/null
+++ b/docs/sql/hrm_employee_import.sql
@@ -0,0 +1,8 @@
+-- 鍛樺伐淇℃伅瀵煎叆鍔熻兘 - 鏂板瀛楁
+-- 鐢ㄤ簬 hrm_employee 琛細姘戞棌銆佸濮荤姸鎬併�佸勾榫勩�佸鍘嗐�佹斂娌婚潰璨�
+
+ALTER TABLE hrm_employee ADD COLUMN nation VARCHAR(32) COMMENT '姘戞棌' AFTER id_card;
+ALTER TABLE hrm_employee ADD COLUMN marriage_status VARCHAR(16) COMMENT '濠氬Щ鐘舵��' AFTER nation;
+ALTER TABLE hrm_employee ADD COLUMN age INT COMMENT '骞撮緞' AFTER marriage_status;
+ALTER TABLE hrm_employee ADD COLUMN education VARCHAR(32) COMMENT '瀛﹀巻' AFTER age;
+ALTER TABLE hrm_employee ADD COLUMN political_status VARCHAR(32) COMMENT '鏀挎不闈㈣矊' AFTER education;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/CrmInvoiceController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/CrmInvoiceController.java
new file mode 100644
index 0000000..0a960e5
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/CrmInvoiceController.java
@@ -0,0 +1,188 @@
+package cn.iocoder.yudao.module.crm.controller.admin.invoice;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+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.contract.vo.contract.CrmContractRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaveReqVO;
+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.dal.dataobject.invoice.CrmInvoiceDO;
+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.invoice.CrmInvoiceService;
+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.storage.StorageAttachmentApi;
+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.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+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.pojo.PageParam.PAGE_SIZE_NONE;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - CRM 寮�绁�")
+@RestController
+@RequestMapping("/crm/invoice")
+@Validated
+public class CrmInvoiceController {
+
+ @Resource
+ private CrmInvoiceService invoiceService;
+ @Resource
+ private CrmContractService contractService;
+ @Resource
+ private CrmCustomerService customerService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private StorageAttachmentApi storageAttachmentApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓寮�绁�")
+ @PreAuthorize("@ss.hasPermission('crm:invoice:create')")
+ public CommonResult<Long> createInvoice(@Valid @RequestBody CrmInvoiceSaveReqVO createReqVO) {
+ return success(invoiceService.createInvoice(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊寮�绁�")
+ @PreAuthorize("@ss.hasPermission('crm:invoice:update')")
+ public CommonResult<Boolean> updateInvoice(@Valid @RequestBody CrmInvoiceSaveReqVO updateReqVO) {
+ invoiceService.updateInvoice(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎寮�绁�")
+ @Parameter(name = "id", description = "缂栧彿", required = true)
+ @PreAuthorize("@ss.hasPermission('crm:invoice:delete')")
+ public CommonResult<Boolean> deleteInvoice(@RequestParam("id") Long id) {
+ invoiceService.deleteInvoice(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱寮�绁�")
+ @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('crm:invoice:query')")
+ public CommonResult<CrmInvoiceRespVO> getInvoice(@RequestParam("id") Long id) {
+ CrmInvoiceDO invoice = invoiceService.getInvoice(id);
+ return success(buildInvoiceDetail(invoice));
+ }
+
+ private CrmInvoiceRespVO buildInvoiceDetail(CrmInvoiceDO invoice) {
+ if (invoice == null) {
+ return null;
+ }
+ return buildInvoiceDetailList(Collections.singletonList(invoice)).get(0);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱寮�绁ㄥ垎椤�")
+ @PreAuthorize("@ss.hasPermission('crm:invoice:query')")
+ public CommonResult<PageResult<CrmInvoiceRespVO>> getInvoicePage(@Valid CrmInvoicePageReqVO pageReqVO) {
+ PageResult<CrmInvoiceDO> pageResult = invoiceService.getInvoicePage(pageReqVO, getLoginUserId());
+ return success(new PageResult<>(buildInvoiceDetailList(pageResult.getList()), pageResult.getTotal()));
+ }
+
+ @GetMapping("/page-by-customer")
+ @Operation(summary = "鑾峰緱寮�绁ㄥ垎椤碉紝鍩轰簬鎸囧畾瀹㈡埛")
+ public CommonResult<PageResult<CrmInvoiceRespVO>> getInvoicePageByCustomer(@Valid CrmInvoicePageReqVO pageReqVO) {
+ Assert.notNull(pageReqVO.getCustomerId(), "瀹㈡埛缂栧彿涓嶈兘涓虹┖");
+ PageResult<CrmInvoiceDO> pageResult = invoiceService.getInvoicePageByCustomerId(pageReqVO);
+ return success(new PageResult<>(buildInvoiceDetailList(pageResult.getList()), pageResult.getTotal()));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭寮�绁� Excel")
+ @PreAuthorize("@ss.hasPermission('crm:invoice:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportInvoiceExcel(@Valid CrmInvoicePageReqVO exportReqVO,
+ HttpServletResponse response) throws IOException {
+ exportReqVO.setPageSize(PAGE_SIZE_NONE);
+ List<CrmInvoiceDO> list = invoiceService.getInvoicePage(exportReqVO, getLoginUserId()).getList();
+ // 瀵煎嚭 Excel
+ ExcelUtils.write(response, "寮�绁�.xls", "鏁版嵁", CrmInvoiceRespVO.class,
+ buildInvoiceDetailList(list));
+ }
+
+ private List<CrmInvoiceRespVO> buildInvoiceDetailList(List<CrmInvoiceDO> invoiceList) {
+ if (CollUtil.isEmpty(invoiceList)) {
+ return Collections.emptyList();
+ }
+ // 1.1 鑾峰彇瀹㈡埛鍒楄〃
+ Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
+ convertSet(invoiceList, CrmInvoiceDO::getCustomerId));
+ // 1.2 鑾峰彇鍒涘缓浜恒�佽礋璐d汉鍒楄〃
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(invoiceList,
+ invoice -> Stream.of(NumberUtils.parseLong(invoice.getCreator()), invoice.getOwnerUserId())));
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+ // 1.3 鑾峰緱鍚堝悓鍒楄〃
+ Map<Long, CrmContractDO> contractMap = contractService.getContractMap(
+ convertSet(invoiceList, CrmInvoiceDO::getContractId));
+ // 2. 鎷兼帴缁撴灉
+ return BeanUtils.toBean(invoiceList, CrmInvoiceRespVO.class, (invoiceVO) -> {
+ // 2.1 鎷兼帴瀹㈡埛鍚嶇О
+ findAndThen(customerMap, invoiceVO.getCustomerId(), customer -> invoiceVO.setCustomerName(customer.getName()));
+ // 2.2 鎷兼帴璐熻矗浜恒�佸垱寤轰汉鍚嶇О
+ MapUtils.findAndThen(userMap, NumberUtils.parseLong(invoiceVO.getCreator()),
+ user -> invoiceVO.setCreatorName(user.getNickname()));
+ MapUtils.findAndThen(userMap, invoiceVO.getOwnerUserId(), user -> {
+ invoiceVO.setOwnerUserName(user.getNickname());
+ MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> invoiceVO.setOwnerUserDeptName(dept.getName()));
+ });
+ // 2.3 鎷兼帴鍚堝悓淇℃伅
+ findAndThen(contractMap, invoiceVO.getContractId(), contract ->
+ invoiceVO.setContract(BeanUtils.toBean(contract, CrmContractRespVO.class)));
+ // 2.4 鎷兼帴鍙戠エ闄勪欢
+ invoiceVO.setAttachmentList(storageAttachmentApi.listAttachments("crm_invoice", invoiceVO.getId()));
+ });
+ }
+
+ @PutMapping("/submit")
+ @Operation(summary = "鎻愪氦寮�绁ㄥ鎵�")
+ @PreAuthorize("@ss.hasPermission('crm:invoice:update')")
+ public CommonResult<Boolean> submitInvoice(@RequestParam("id") Long id) {
+ invoiceService.submitInvoice(id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/audit-count")
+ @Operation(summary = "鑾峰緱寰呭鏍稿紑绁ㄦ暟閲�")
+ @PreAuthorize("@ss.hasPermission('crm:invoice:query')")
+ public CommonResult<Long> getAuditInvoiceCount() {
+ return success(invoiceService.getAuditInvoiceCount(getLoginUserId()));
+ }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoicePageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoicePageReqVO.java
new file mode 100644
index 0000000..994a1bb
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoicePageReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
+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 CrmInvoicePageReqVO extends PageParam {
+
+ @Schema(description = "寮�绁ㄧ紪鍙�")
+ private String no;
+
+ @Schema(description = "瀹㈡埛缂栧彿", example = "4963")
+ private Long customerId;
+
+ @Schema(description = "鍚堝悓缂栧彿", example = "4963")
+ private Long contractId;
+
+ @Schema(description = "鍦烘櫙绫诲瀷", example = "1")
+ @InEnum(CrmSceneTypeEnum.class)
+ private Integer sceneType; // 鍦烘櫙绫诲瀷锛屼负 null 鏃跺垯琛ㄧず鍏ㄩ儴
+
+ @Schema(description = "瀹℃壒鐘舵��", example = "20")
+ @InEnum(CrmAuditStatusEnum.class)
+ private Integer auditStatus;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceRespVO.java
new file mode 100644
index 0000000..05b1e68
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceRespVO.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
+import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 寮�绁� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmInvoiceRespVO {
+
+ @Schema(description = "缂栧彿", example = "25787")
+ @ExcelProperty("缂栧彿")
+ private Long id;
+
+ @Schema(description = "寮�绁ㄧ紪鍙�", example = "31177")
+ @ExcelProperty("寮�绁ㄧ紪鍙�")
+ private String no;
+
+ @Schema(description = "鍙戠エ绫诲瀷", example = "1")
+ @ExcelProperty(value = "鍙戠エ绫诲瀷", converter = DictConvert.class)
+ @DictFormat(DictTypeConstants.CRM_INVOICE_TYPE)
+ private Integer invoiceType;
+
+ @Schema(description = "鍙戠エ鍙风爜", example = "02564178")
+ @ExcelProperty("鍙戠エ鍙风爜")
+ private String invoiceNo;
+
+ @Schema(description = "鍙戠エ鎶ご", example = "XX绉戞妧鏈夐檺鍏徃")
+ @ExcelProperty("鍙戠エ鎶ご")
+ private String invoiceTitle;
+
+ @Schema(description = "寮�绁ㄩ噾棰�", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
+ @ExcelProperty("寮�绁ㄩ噾棰�")
+ private BigDecimal price;
+
+ @Schema(description = "寮�绁ㄦ棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
+ @ExcelProperty("寮�绁ㄦ棩鏈�")
+ private LocalDateTime invoiceTime;
+
+ @Schema(description = "瀹㈡埛缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Long customerId;
+ @Schema(description = "瀹㈡埛鍚嶅瓧", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
+ @ExcelProperty("瀹㈡埛鍚嶅瓧")
+ private String customerName;
+
+ @Schema(description = "鍚堝悓缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("鍚堝悓缂栧彿")
+ private Long contractId;
+ @Schema(description = "鍚堝悓淇℃伅")
+ private CrmContractRespVO contract;
+
+ @Schema(description = "璐熻矗浜虹殑鐢ㄦ埛缂栧彿", example = "25682")
+ private Long ownerUserId;
+ @Schema(description = "璐熻矗浜哄悕瀛�", example = "25682")
+ @ExcelProperty("璐熻矗浜哄悕瀛�")
+ private String ownerUserName;
+ @Schema(description = "璐熻矗浜洪儴闂�")
+ @ExcelProperty("璐熻矗浜洪儴闂�")
+ private String ownerUserDeptName;
+
+ @Schema(description = "宸ヤ綔娴佺紪鍙�", example = "1043")
+ @ExcelProperty("宸ヤ綔娴佺紪鍙�")
+ private String processInstanceId;
+
+ @Schema(description = "瀹℃壒鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @ExcelProperty(value = "瀹℃壒鐘舵��", converter = DictConvert.class)
+ @DictFormat(DictTypeConstants.CRM_AUDIT_STATUS)
+ private Integer auditStatus;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍙戠エ闄勪欢鍒楄〃")
+ private List<StorageBlobRespDTO> attachmentList;
+
+ @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ private LocalDateTime createTime;
+
+ @Schema(description = "鏇存柊鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("鏇存柊鏃堕棿")
+ private LocalDateTime updateTime;
+
+ @Schema(description = "鍒涘缓浜�", example = "25682")
+ private String creator;
+ @Schema(description = "鍒涘缓浜哄悕瀛�", example = "test")
+ @ExcelProperty("鍒涘缓浜哄悕瀛�")
+ private String creatorName;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceSaveReqVO.java
new file mode 100644
index 0000000..21cf8be
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceSaveReqVO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 寮�绁ㄦ柊澧�/淇敼 Request VO")
+@Data
+public class CrmInvoiceSaveReqVO {
+
+ @Schema(description = "缂栧彿", example = "25787")
+ private Long id;
+
+ @Schema(description = "寮�绁ㄧ紪鍙�", example = "31177")
+ private String no;
+
+ @Schema(description = "瀹㈡埛缂栧彿", example = "2")
+ private Long customerId; // 璇ュ瓧娈典笉閫氳繃鍓嶇浼犻�掞紝鑰屾槸 contractId 鏌ヨ鍑烘潵璁剧疆杩涘幓
+
+ @Schema(description = "鍚堝悓缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "鍚堝悓缂栧彿涓嶈兘涓虹┖")
+ private Long contractId;
+
+ @Schema(description = "璐熻矗浜虹紪鍙�", example = "25682")
+ private Long ownerUserId;
+
+ @Schema(description = "鍙戠エ绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "鍙戠エ绫诲瀷涓嶈兘涓虹┖")
+ private Integer invoiceType;
+
+ @Schema(description = "鍙戠エ鍙风爜", example = "02564178")
+ private String invoiceNo;
+
+ @Schema(description = "鍙戠エ鎶ご", example = "XX绉戞妧鏈夐檺鍏徃")
+ private String invoiceTitle;
+
+ @Schema(description = "寮�绁ㄩ噾棰�", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
+ @NotNull(message = "寮�绁ㄩ噾棰濅笉鑳戒负绌�")
+ private BigDecimal price;
+
+ @Schema(description = "寮�绁ㄦ棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
+ @NotNull(message = "寮�绁ㄦ棩鏈熶笉鑳戒负绌�")
+ private LocalDateTime invoiceTime;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍙戠エ闄勪欢 BlobId 鍒楄〃")
+ private List<Long> blobIds;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/invoice/CrmInvoiceDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/invoice/CrmInvoiceDO.java
new file mode 100644
index 0000000..664300d
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/invoice/CrmInvoiceDO.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.invoice;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+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.common.CrmAuditStatusEnum;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+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;
+
+/**
+ * 寮�绁� DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("crm_invoice")
+@KeySequence("crm_invoice_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmInvoiceDO extends BaseDO {
+
+ /**
+ * ID
+ */
+ @TableId
+ private Long id;
+ /**
+ * 寮�绁ㄧ紪鍙�
+ */
+ private String no;
+ /**
+ * 瀹㈡埛缂栧彿
+ *
+ * 鍏宠仈 {@link CrmCustomerDO#getId()}
+ */
+ private Long customerId;
+ /**
+ * 鍚堝悓缂栧彿
+ *
+ * 鍏宠仈 {@link CrmContractDO#getId()}
+ */
+ private Long contractId;
+ /**
+ * 璐熻矗浜虹紪鍙凤紝鍏宠仈 {@link AdminUserRespDTO#getId()}
+ */
+ private Long ownerUserId;
+
+ /**
+ * 鍙戠エ绫诲瀷
+ *
+ * 瀛楀吀 {@link cn.iocoder.yudao.module.crm.enums.DictTypeConstants#CRM_INVOICE_TYPE}
+ */
+ private Integer invoiceType;
+ /**
+ * 鍙戠エ鍙风爜
+ */
+ private String invoiceNo;
+ /**
+ * 鍙戠エ鎶ご
+ */
+ private String invoiceTitle;
+ /**
+ * 寮�绁ㄩ噾棰濓紝鍗曚綅锛氬厓
+ */
+ private BigDecimal price;
+ /**
+ * 寮�绁ㄦ棩鏈�
+ */
+ private LocalDateTime invoiceTime;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 宸ヤ綔娴佺紪鍙�
+ *
+ * 鍏宠仈 ProcessInstance 鐨� id 灞炴��
+ */
+ private String processInstanceId;
+ /**
+ * 瀹℃壒鐘舵��
+ *
+ * 鏋氫妇 {@link CrmAuditStatusEnum}
+ */
+ private Integer auditStatus;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java
new file mode 100644
index 0000000..a5a814f
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.invoice;
+
+import cn.hutool.core.collection.CollUtil;
+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.invoice.vo.invoice.CrmInvoicePageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
+import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 寮�绁� Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface CrmInvoiceMapper extends BaseMapperX<CrmInvoiceDO> {
+
+ default CrmInvoiceDO selectByNo(String no) {
+ return selectOne(CrmInvoiceDO::getNo, no);
+ }
+
+ default PageResult<CrmInvoiceDO> selectPageByCustomerId(CrmInvoicePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<CrmInvoiceDO>()
+ .eq(CrmInvoiceDO::getCustomerId, reqVO.getCustomerId()) // 蹇呴』浼犻��
+ .eqIfPresent(CrmInvoiceDO::getNo, reqVO.getNo())
+ .eqIfPresent(CrmInvoiceDO::getContractId, reqVO.getContractId())
+ .orderByDesc(CrmInvoiceDO::getId));
+ }
+
+ default PageResult<CrmInvoiceDO> selectPage(CrmInvoicePageReqVO pageReqVO, Long userId) {
+ MPJLambdaWrapperX<CrmInvoiceDO> query = new MPJLambdaWrapperX<>();
+ // 鎷兼帴鏁版嵁鏉冮檺鐨勬煡璇㈡潯浠�
+ CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_INVOICE.getType(),
+ CrmInvoiceDO::getId, userId, pageReqVO.getSceneType());
+ // 鎷兼帴鑷韩鐨勬煡璇㈡潯浠�
+ query.selectAll(CrmInvoiceDO.class)
+ .eqIfPresent(CrmInvoiceDO::getNo, pageReqVO.getNo())
+ .eqIfPresent(CrmInvoiceDO::getContractId, pageReqVO.getContractId())
+ .eqIfPresent(CrmInvoiceDO::getAuditStatus, pageReqVO.getAuditStatus())
+ .orderByDesc(CrmInvoiceDO::getId);
+ return selectJoinPage(pageReqVO, CrmInvoiceDO.class, query);
+ }
+
+ default Long selectCountByAudit(Long userId) {
+ MPJLambdaWrapperX<CrmInvoiceDO> query = new MPJLambdaWrapperX<>();
+ // 鎴戣礋璐g殑 + 闈炲叕娴�
+ CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_INVOICE.getType(),
+ CrmInvoiceDO::getId, userId, CrmSceneTypeEnum.OWNER.getType());
+ // 鏈鏍�
+ query.eq(CrmInvoiceDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus());
+ return selectCount(query);
+ }
+
+ default List<CrmInvoiceDO> selectListByContractIdAndStatus(Long contractId, Collection<Integer> auditStatuses) {
+ return selectList(new LambdaQueryWrapperX<CrmInvoiceDO>()
+ .eq(CrmInvoiceDO::getContractId, contractId)
+ .in(CrmInvoiceDO::getAuditStatus, auditStatuses));
+ }
+
+ default Map<Long, BigDecimal> selectInvoicePriceMapByContractId(Collection<Long> contractIds) {
+ if (CollUtil.isEmpty(contractIds)) {
+ return Collections.emptyMap();
+ }
+ // SQL sum 鏌ヨ
+ List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmInvoiceDO>()
+ .select("contract_id, SUM(price) AS total_price")
+ .eq("audit_status", CrmAuditStatusEnum.APPROVE.getStatus())
+ .groupBy("contract_id")
+ .in("contract_id", contractIds));
+ // 鑾峰緱閲戦
+ return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price"));
+ }
+
+ default Long selectCountByContractId(Long contractId) {
+ return selectCount(CrmInvoiceDO::getContractId, contractId);
+ }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
index 07b7b6b..566c23a 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
@@ -1,11 +1,13 @@
package cn.iocoder.yudao.module.crm.dal.mysql.permission;
+import cn.hutool.core.collection.CollUtil;
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.permission.CrmPermissionDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
/**
@@ -30,6 +32,9 @@
}
default List<CrmPermissionDO> selectByBizTypeAndBizIds(Integer bizType, Collection<Long> bizIds) {
+ if (CollUtil.isEmpty(bizIds)) {
+ return Collections.emptyList();
+ }
return selectList(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.in(CrmPermissionDO::getBizId, bizIds));
@@ -67,6 +72,9 @@
}
default Long selectListByBiz(Collection<Integer> bizTypes, Collection<Long> bizIds, Collection<Long> userIds) {
+ if (CollUtil.isEmpty(bizTypes) || CollUtil.isEmpty(bizIds) || CollUtil.isEmpty(userIds)) {
+ return 0L;
+ }
return selectCount(new LambdaQueryWrapperX<CrmPermissionDO>()
.in(CrmPermissionDO::getBizType, bizTypes)
.in(CrmPermissionDO::getBizId, bizIds)
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java
index a5f1962..7303e0c 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java
@@ -34,6 +34,11 @@
*/
public static final String SALE_QUOTATION_NO_PREFIX = "QT";
+ /**
+ * 寮�绁� {@link cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO}
+ */
+ public static final String INVOICE_PREFIX = "KP";
+
@Resource
private StringRedisTemplate stringRedisTemplate;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
index b5ff83a..b38795f 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
@@ -15,5 +15,6 @@
String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 浜у搧鐘舵��
String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 璺熻繘鏂瑰紡
String CRM_RECEIVABLE_RETURN_TYPE = "crm_receivable_return_type"; // CRM 鍥炴鏂瑰紡
+ String CRM_INVOICE_TYPE = "crm_invoice_type"; // CRM 鍙戠エ绫诲瀷
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index 490a690..835b74a 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -119,4 +119,15 @@
ErrorCode SALE_QUOTATION_ITEM_NOT_EXISTS = new ErrorCode(1_020_015_005, "鐗╂枡涓嶅瓨鍦ㄦ垨鏈惎鐢�");
ErrorCode SALE_QUOTATION_CONTRACT_EXISTS = new ErrorCode(1_020_015_006, "閿�鍞姤浠峰崟宸茶浆鍚堝悓锛岃鍕块噸澶嶆搷浣�");
+ // ========== 寮�绁� 1_020_016_000 ==========
+ ErrorCode INVOICE_NOT_EXISTS = new ErrorCode(1_020_016_000, "寮�绁ㄤ笉瀛樺湪");
+ ErrorCode INVOICE_NO_EXISTS = new ErrorCode(1_020_016_001, "鐢熸垚寮�绁ㄥ簭鍒楀彿閲嶅锛岃閲嶈瘯");
+ ErrorCode INVOICE_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_016_002, "鏇存柊寮�绁ㄥけ璐ワ紝鍘熷洜锛氱姝㈢紪杈�");
+ ErrorCode INVOICE_DELETE_FAIL_IS_APPROVE = new ErrorCode(1_020_016_003, "鍒犻櫎寮�绁ㄥけ璐ワ紝鍘熷洜锛氬紑绁ㄥ鎵瑰凡閫氳繃");
+ ErrorCode INVOICE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_016_004, "寮�绁ㄦ彁浜ゅ鏍稿け璐ワ紝鍘熷洜锛氬紑绁ㄦ病澶勫湪鏈彁浜ょ姸鎬�");
+ ErrorCode INVOICE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_016_005, "鏇存柊寮�绁ㄥ鏍哥姸鎬佸け璐ワ紝鍘熷洜锛氬紑绁ㄤ笉鏄鏍镐腑鐘舵��");
+ ErrorCode INVOICE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_016_006, "鍒涘缓寮�绁ㄥけ璐ワ紝鍘熷洜锛氬悎鍚屼笉鏄鏍搁�氳繃鐘舵��");
+ ErrorCode INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_016_007, "鍒涘缓寮�绁ㄥけ璐ワ紝鍘熷洜锛氬紑绁ㄩ噾棰濊秴鍑哄悎鍚岄噾棰濓紝鐩墠鍙紑绁細{} 鍏�");
+ ErrorCode RECEIVABLE_CREATE_FAIL_INVOICE_NOT_ENOUGH = new ErrorCode(1_020_016_008, "鍒涘缓鍥炴澶辫触锛屽師鍥狅細鍚堝悓绱寮�绁ㄩ噾棰濅笉瓒筹紝鐩墠鍙洖娆撅細{} 鍏�");
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
index 30d51cf..4d1ba00 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
@@ -160,4 +160,16 @@
String CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE = "鍒犻櫎鍥炴璁″垝";
String CRM_RECEIVABLE_PLAN_DELETE_SUCCESS = "鍒犻櫎浜嗗悎鍚屻�恵getContractById{#receivablePlan.contractId}}銆戠殑绗�恵{#receivablePlan.period}}銆戞湡鍥炴璁″垝";
+ // ======================= CRM_INVOICE 寮�绁� =======================
+
+ String CRM_INVOICE_TYPE = "CRM 寮�绁�";
+ String CRM_INVOICE_CREATE_SUB_TYPE = "鍒涘缓寮�绁�";
+ String CRM_INVOICE_CREATE_SUCCESS = "鍒涘缓浜嗗悎鍚屻�恵getContractById{#invoice.contractId}}銆戠殑缂栧彿涓恒�恵{#invoice.no}}銆戠殑寮�绁�";
+ String CRM_INVOICE_UPDATE_SUB_TYPE = "鏇存柊寮�绁�";
+ String CRM_INVOICE_UPDATE_SUCCESS = "鏇存柊浜嗗悎鍚屻�恵getContractById{#invoice.contractId}}銆戠殑缂栧彿涓恒�恵{#invoice.no}}銆戠殑寮�绁�: {_DIFF{#updateReqVO}}";
+ String CRM_INVOICE_DELETE_SUB_TYPE = "鍒犻櫎寮�绁�";
+ String CRM_INVOICE_DELETE_SUCCESS = "鍒犻櫎浜嗗悎鍚屻�恵getContractById{#invoice.contractId}}銆戠殑缂栧彿涓恒�恵{#invoice.no}}銆戠殑寮�绁�";
+ String CRM_INVOICE_SUBMIT_SUB_TYPE = "鎻愪氦寮�绁ㄥ鎵�";
+ String CRM_INVOICE_SUBMIT_SUCCESS = "鎻愪氦缂栧彿涓恒�恵{#invoiceNo}}銆戠殑寮�绁ㄥ鎵规垚鍔�";
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java
index 6220ddc..212c487 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java
@@ -25,7 +25,8 @@
CRM_PRODUCT(6, "浜у搧"),
CRM_RECEIVABLE(7, "鍥炴"),
CRM_RECEIVABLE_PLAN(8, "鍥炴璁″垝"),
- CRM_SALE_QUOTATION(9, "閿�鍞姤浠�")
+ CRM_SALE_QUOTATION(9, "閿�鍞姤浠�"),
+ CRM_INVOICE(10, "寮�绁�")
;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(CrmBizTypeEnum::getType).toArray(Integer[]::new);
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceService.java
new file mode 100644
index 0000000..490be7c
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceService.java
@@ -0,0 +1,139 @@
+package cn.iocoder.yudao.module.crm.service.invoice;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO;
+import jakarta.validation.Valid;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * CRM 寮�绁� Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface CrmInvoiceService {
+
+ /**
+ * 鍒涘缓寮�绁�
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createInvoice(@Valid CrmInvoiceSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊寮�绁�
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateInvoice(@Valid CrmInvoiceSaveReqVO updateReqVO);
+
+ /**
+ * 鏇存柊寮�绁ㄦ祦绋嬪鎵圭粨鏋�
+ *
+ * @param id 寮�绁ㄧ紪鍙�
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ void updateInvoiceAuditStatus(Long id, Integer bpmResult);
+
+ /**
+ * 鍒犻櫎寮�绁�
+ *
+ * @param id 缂栧彿
+ */
+ void deleteInvoice(Long id);
+
+ /**
+ * 鍙戣捣寮�绁ㄥ鎵规祦绋�
+ *
+ * @param id 寮�绁ㄧ紪鍙�
+ * @param userId 鐢ㄦ埛缂栧彿
+ */
+ void submitInvoice(Long id, Long userId);
+
+ /**
+ * 鑾峰緱寮�绁�
+ *
+ * @param id 缂栧彿
+ * @return 寮�绁�
+ */
+ CrmInvoiceDO getInvoice(Long id);
+
+ /**
+ * 鑾峰緱寮�绁ㄥ垪琛�
+ *
+ * @param ids 缂栧彿
+ * @return 寮�绁ㄥ垪琛�
+ */
+ List<CrmInvoiceDO> getInvoiceList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱寮�绁� Map
+ *
+ * @param ids 缂栧彿
+ * @return 寮�绁� Map
+ */
+ default Map<Long, CrmInvoiceDO> getInvoiceMap(Collection<Long> ids) {
+ return convertMap(getInvoiceList(ids), CrmInvoiceDO::getId);
+ }
+
+ /**
+ * 鑾峰緱寮�绁ㄥ垎椤�
+ *
+ * 鏁版嵁鏉冮檺锛氬熀浜� {@link CrmInvoiceDO} 璇诲彇
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 寮�绁ㄥ垎椤�
+ */
+ PageResult<CrmInvoiceDO> getInvoicePage(CrmInvoicePageReqVO pageReqVO, Long userId);
+
+ /**
+ * 鑾峰緱寮�绁ㄥ垎椤碉紝鍩轰簬鎸囧畾瀹㈡埛
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 寮�绁ㄥ垎椤�
+ */
+ PageResult<CrmInvoiceDO> getInvoicePageByCustomerId(CrmInvoicePageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱寰呭鏍稿紑绁ㄦ暟閲�
+ *
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 寰呭鎵规暟閲�
+ */
+ Long getAuditInvoiceCount(Long userId);
+
+ /**
+ * 鏍规嵁鍚堝悓缂栧彿鏌ヨ寮�绁ㄥ垪琛�
+ *
+ * @param contractId 鍚堝悓缂栧彿
+ * @param auditStatuses 瀹℃壒鐘舵�侀泦鍚�
+ * @return 寮�绁ㄥ垪琛�
+ */
+ List<CrmInvoiceDO> getInvoiceListByContractIdAndStatus(Long contractId, Collection<Integer> auditStatuses);
+
+ /**
+ * 鑾峰緱鍚堝悓宸插紑绁ㄩ噾棰� Map锛堜粎瀹℃壒閫氳繃锛�
+ *
+ * @param contractIds 鍚堝悓缂栧彿
+ * @return 寮�绁ㄩ噾棰� Map
+ */
+ Map<Long, BigDecimal> getInvoicePriceMapByContractId(Collection<Long> contractIds);
+
+ /**
+ * 鏍规嵁鍚堝悓缂栧彿鏌ヨ寮�绁ㄦ暟閲�
+ *
+ * @param contractId 鍚堝悓缂栧彿
+ * @return 寮�绁ㄦ暟閲�
+ */
+ Long getInvoiceCountByContractId(Long contractId);
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java
new file mode 100644
index 0000000..0844616
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java
@@ -0,0 +1,300 @@
+package cn.iocoder.yudao.module.crm.service.invoice;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.invoice.CrmInvoiceMapper;
+import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
+import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
+import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
+import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.service.impl.DiffParseFunction;
+import com.mzt.logapi.starter.annotation.LogRecord;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+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.module.crm.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
+import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
+
+/**
+ * CRM 寮�绁� Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+@Slf4j
+public class CrmInvoiceServiceImpl implements CrmInvoiceService {
+
+ /**
+ * BPM 寮�绁ㄥ鎵规祦绋嬫爣璇�
+ */
+ public static final String BPM_PROCESS_DEFINITION_KEY = "crm-invoice-audit";
+
+ /**
+ * 鍙戠エ闄勪欢鐨勪笟鍔¤褰曠被鍨嬶紝瀵瑰簲 {@link cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum#CRM_INVOICE}
+ */
+ private static final String INVOICE_RECORD_TYPE = "crm_invoice";
+
+ @Resource
+ private CrmInvoiceMapper invoiceMapper;
+
+ @Resource
+ private CrmNoRedisDAO noRedisDAO;
+
+ @Resource
+ private CrmContractService contractService;
+ @Resource
+ private CrmPermissionService permissionService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+ @Resource
+ private StorageAttachmentApi storageAttachmentApi;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_CREATE_SUB_TYPE, bizNo = "{{#invoice.id}}",
+ success = CRM_INVOICE_CREATE_SUCCESS)
+ public Long createInvoice(CrmInvoiceSaveReqVO createReqVO) {
+ // 1.1 鏍¢獙鍙紑绁ㄩ噾棰濊秴杩囦笂闄�
+ validateInvoicePriceExceedsLimit(createReqVO);
+ // 1.2 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
+ validateRelationDataExists(createReqVO);
+ // 1.3 鐢熸垚寮�绁ㄧ紪鍙�
+ String no = noRedisDAO.generate(CrmNoRedisDAO.INVOICE_PREFIX);
+ if (invoiceMapper.selectByNo(no) != null) {
+ throw exception(INVOICE_NO_EXISTS);
+ }
+
+ // 2.1 鎻掑叆寮�绁�
+ CrmInvoiceDO invoice = BeanUtils.toBean(createReqVO, CrmInvoiceDO.class)
+ .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
+ invoiceMapper.insert(invoice);
+
+ // 2.2 缁戝畾鍙戠エ闄勪欢
+ if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) {
+ storageAttachmentApi.bindAttachments("file", INVOICE_RECORD_TYPE, invoice.getId(), createReqVO.getBlobIds());
+ }
+
+ // 3. 鍒涘缓鏁版嵁鏉冮檺
+ permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_INVOICE.getType())
+ .setBizId(invoice.getId()).setUserId(createReqVO.getOwnerUserId())
+ .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 璁剧疆褰撳墠鎿嶄綔鐨勪汉涓鸿礋璐d汉
+
+ // 4. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
+ LogRecordContext.putVariable("invoice", invoice);
+ return invoice.getId();
+ }
+
+ private void validateInvoicePriceExceedsLimit(CrmInvoiceSaveReqVO reqVO) {
+ // 1. 璁$畻鍓╀綑鍙紑绁ㄩ噾棰濓紝涓嶅寘鎷� reqVO 鑷韩
+ CrmContractDO contract = contractService.validateContract(reqVO.getContractId());
+ List<CrmInvoiceDO> invoices = invoiceMapper.selectListByContractIdAndStatus(reqVO.getContractId(),
+ List.of(CrmAuditStatusEnum.APPROVE.getStatus()));
+ if (reqVO.getId() != null) {
+ invoices.removeIf(invoice -> ObjectUtil.equal(invoice.getId(), reqVO.getId()));
+ }
+ BigDecimal notInvoicePrice = contract.getTotalPrice().subtract(
+ CollectionUtils.getSumValue(invoices, CrmInvoiceDO::getPrice, BigDecimal::add, BigDecimal.ZERO));
+ // 2. 鏍¢獙閲戦鏄惁瓒呰繃
+ if (reqVO.getPrice().compareTo(notInvoicePrice) > 0) {
+ throw exception(INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, notInvoicePrice);
+ }
+ }
+
+ private void validateRelationDataExists(CrmInvoiceSaveReqVO reqVO) {
+ if (reqVO.getOwnerUserId() != null) {
+ adminUserApi.validateUser(reqVO.getOwnerUserId()); // 鏍¢獙璐熻矗浜哄瓨鍦�
+ }
+ if (reqVO.getContractId() != null) {
+ CrmContractDO contract = contractService.validateContract(reqVO.getContractId());
+ if (ObjectUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
+ throw exception(INVOICE_CREATE_FAIL_CONTRACT_NOT_APPROVE);
+ }
+ reqVO.setCustomerId(contract.getCustomerId()); // 璁剧疆瀹㈡埛缂栧彿
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
+ success = CRM_INVOICE_UPDATE_SUCCESS)
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_INVOICE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
+ public void updateInvoice(CrmInvoiceSaveReqVO updateReqVO) {
+ Assert.notNull(updateReqVO.getId(), "寮�绁ㄧ紪鍙蜂笉鑳戒负绌�");
+ updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null); // 涓嶅厑璁镐慨鏀圭殑瀛楁
+ // 1.1 鏍¢獙瀛樺湪
+ CrmInvoiceDO oldInvoice = validateInvoiceExists(updateReqVO.getId());
+ updateReqVO.setOwnerUserId(oldInvoice.getOwnerUserId()).setCustomerId(oldInvoice.getCustomerId())
+ .setContractId(oldInvoice.getContractId()); // 璁剧疆宸插瓨鍦ㄧ殑鍊�
+ // 1.2 鏍¢獙鍙紑绁ㄩ噾棰濊秴杩囦笂闄�
+ validateInvoicePriceExceedsLimit(updateReqVO);
+
+ // 1.3 鍙湁鑽夌銆佸鎵逛腑锛屽彲浠ョ紪杈戯紱
+ if (!ObjectUtils.equalsAny(oldInvoice.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
+ CrmAuditStatusEnum.PROCESS.getStatus())) {
+ throw exception(INVOICE_UPDATE_FAIL_EDITING_PROHIBITED);
+ }
+
+ // 2. 鏇存柊寮�绁�
+ CrmInvoiceDO updateObj = BeanUtils.toBean(updateReqVO, CrmInvoiceDO.class);
+ invoiceMapper.updateById(updateObj);
+
+ // 3. 澧為噺鏇存柊鍙戠エ闄勪欢
+ storageAttachmentApi.updateAttachments("file", INVOICE_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
+
+ // 4. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
+ updateReqVO.setOwnerUserId(oldInvoice.getOwnerUserId()); // 閬垮厤鎿嶄綔鏃ュ織鍑虹幇鈥滃垹闄よ礋璐d汉鈥濈殑鎯呭喌
+ LogRecordContext.putVariable("invoice", oldInvoice);
+ LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldInvoice, CrmInvoiceSaveReqVO.class));
+ }
+
+ @Override
+ public void updateInvoiceAuditStatus(Long id, Integer bpmResult) {
+ // 1.1 鏍¢獙瀛樺湪
+ CrmInvoiceDO invoice = validateInvoiceExists(id);
+ // 1.2 鍙湁瀹℃壒涓紝鍙互鏇存柊瀹℃壒缁撴灉
+ if (ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
+ log.error("[updateInvoiceAuditStatus][invoice({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+ invoice.getId(), bpmResult);
+ throw exception(INVOICE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+
+ // 2. 鏇存柊寮�绁ㄥ鎵圭姸鎬�
+ Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+ invoiceMapper.updateById(new CrmInvoiceDO().setId(id).setAuditStatus(auditStatus));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
+ success = CRM_INVOICE_DELETE_SUCCESS)
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_INVOICE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
+ public void deleteInvoice(Long id) {
+ // 1.1 鏍¢獙瀛樺湪
+ CrmInvoiceDO invoice = validateInvoiceExists(id);
+ // 1.2 瀹℃壒閫氳繃鏃讹紝涓嶅厑璁稿垹闄�
+ if (ObjUtil.equal(invoice.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
+ throw exception(INVOICE_DELETE_FAIL_IS_APPROVE);
+ }
+
+ // 2.1 鍒犻櫎鍙戠エ闄勪欢
+ storageAttachmentApi.deleteAttachmentsByRecord(INVOICE_RECORD_TYPE, id);
+ // 2.2 鍒犻櫎寮�绁�
+ invoiceMapper.deleteById(id);
+ // 2.3 鍒犻櫎鏁版嵁鏉冮檺
+ permissionService.deletePermission(CrmBizTypeEnum.CRM_INVOICE.getType(), id);
+
+ // 3. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
+ LogRecordContext.putVariable("invoice", invoice);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
+ success = CRM_INVOICE_SUBMIT_SUCCESS)
+ public void submitInvoice(Long id, Long userId) {
+ // 1. 鏍¢獙寮�绁ㄦ槸鍚﹀湪瀹℃壒
+ CrmInvoiceDO invoice = validateInvoiceExists(id);
+ if (ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(INVOICE_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鍒涘缓寮�绁ㄥ鎵规祦绋嬪疄渚�
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id)));
+
+ // 3. 鏇存柊寮�绁ㄥ伐浣滄祦缂栧彿
+ invoiceMapper.updateById(new CrmInvoiceDO().setId(id).setProcessInstanceId(processInstanceId)
+ .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
+
+ // 4. 璁板綍鏃ュ織
+ LogRecordContext.putVariable("invoiceNo", invoice.getNo());
+ }
+
+ private CrmInvoiceDO validateInvoiceExists(Long id) {
+ CrmInvoiceDO invoice = invoiceMapper.selectById(id);
+ if (invoice == null) {
+ throw exception(INVOICE_NOT_EXISTS);
+ }
+ return invoice;
+ }
+
+ @Override
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_INVOICE, bizId = "#id", level = CrmPermissionLevelEnum.READ)
+ public CrmInvoiceDO getInvoice(Long id) {
+ return invoiceMapper.selectById(id);
+ }
+
+ @Override
+ public List<CrmInvoiceDO> getInvoiceList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return ListUtil.empty();
+ }
+ return invoiceMapper.selectByIds(ids);
+ }
+
+ @Override
+ public PageResult<CrmInvoiceDO> getInvoicePage(CrmInvoicePageReqVO pageReqVO, Long userId) {
+ return invoiceMapper.selectPage(pageReqVO, userId);
+ }
+
+ @Override
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
+ public PageResult<CrmInvoiceDO> getInvoicePageByCustomerId(CrmInvoicePageReqVO pageReqVO) {
+ return invoiceMapper.selectPageByCustomerId(pageReqVO);
+ }
+
+ @Override
+ public Long getAuditInvoiceCount(Long userId) {
+ return invoiceMapper.selectCountByAudit(userId);
+ }
+
+ @Override
+ public List<CrmInvoiceDO> getInvoiceListByContractIdAndStatus(Long contractId, Collection<Integer> auditStatuses) {
+ return invoiceMapper.selectListByContractIdAndStatus(contractId, auditStatuses);
+ }
+
+ @Override
+ public Map<Long, BigDecimal> getInvoicePriceMapByContractId(Collection<Long> contractIds) {
+ return invoiceMapper.selectInvoicePriceMapByContractId(contractIds);
+ }
+
+ @Override
+ public Long getInvoiceCountByContractId(Long contractId) {
+ return invoiceMapper.selectCountByContractId(contractId);
+ }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/listener/CrmInvoiceStatusListener.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/listener/CrmInvoiceStatusListener.java
new file mode 100644
index 0000000..17bcab3
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/listener/CrmInvoiceStatusListener.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.crm.service.invoice.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceService;
+import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 寮�绁ㄥ鎵圭殑缁撴灉鐨勭洃鍚櫒瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Component
+public class CrmInvoiceStatusListener extends BpmProcessInstanceStatusEventListener {
+
+ @Resource
+ private CrmInvoiceService invoiceService;
+
+ @Override
+ public String getProcessDefinitionKey() {
+ return CrmInvoiceServiceImpl.BPM_PROCESS_DEFINITION_KEY;
+ }
+
+ @Override
+ public void onEvent(BpmProcessInstanceStatusEvent event) {
+ invoiceService.updateInvoiceAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+ }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
index 0c68f1e..0652c25 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -14,6 +14,7 @@
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper;
@@ -23,9 +24,12 @@
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
+import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
@@ -72,11 +76,15 @@
private CrmReceivablePlanService receivablePlanService;
@Resource
private CrmPermissionService permissionService;
+ @Resource
+ private CrmInvoiceService invoiceService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private BpmProcessInstanceApi bpmProcessInstanceApi;
+ @Resource
+ private StorageAttachmentApi storageAttachmentApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -85,9 +93,11 @@
public Long createReceivable(CrmReceivableSaveReqVO createReqVO) {
// 1.1 鏍¢獙鍙洖娆鹃噾棰濊秴杩囦笂闄�
validateReceivablePriceExceedsLimit(createReqVO);
- // 1.2 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
+ // 1.2 鏍¢獙鍙洖娆鹃噾棰濅笉瓒呰繃绱宸插紑绁ㄩ噾棰濓紙鍏堝紑绁ㄥ悗鍥炴锛�
+ validateReceivablePriceExceedsInvoiceLimit(createReqVO);
+ // 1.3 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
validateRelationDataExists(createReqVO);
- // 1.3 鐢熸垚鍥炴缂栧彿
+ // 1.4 鐢熸垚鍥炴缂栧彿
String no = noRedisDAO.generate(CrmNoRedisDAO.RECEIVABLE_PREFIX);
if (receivableMapper.selectByNo(no) != null) {
throw exception(RECEIVABLE_NO_EXISTS);
@@ -131,6 +141,37 @@
}
}
+ /**
+ * 鏍¢獙绱鍥炴閲戦涓嶈秴杩囩疮璁″凡寮�绁ㄤ笖宸蹭笂浼犲彂绁ㄩ檮浠剁殑閲戦锛堝厛寮�绁ㄥ悗鍥炴锛�
+ */
+ private void validateReceivablePriceExceedsInvoiceLimit(CrmReceivableSaveReqVO reqVO) {
+ // 1. 璁$畻鍚堝悓绱鍥炴閲戦锛堜笉鍚湰娆★紝缁熻鍙e緞鍚屽悎鍚岄噾棰濇牎楠岋細瀹℃壒閫氳繃 + 瀹℃壒涓級
+ List<CrmReceivableDO> receivables = receivableMapper.selectListByContractIdAndStatus(reqVO.getContractId(),
+ Arrays.asList(CrmAuditStatusEnum.APPROVE.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus()));
+ if (reqVO.getId() != null) {
+ receivables.removeIf(receivable -> ObjectUtil.equal(receivable.getId(), reqVO.getId()));
+ }
+ BigDecimal receivablePriceSum = CollectionUtils.getSumValue(receivables, CrmReceivableDO::getPrice,
+ BigDecimal::add, BigDecimal.ZERO);
+
+ // 2. 璁$畻鍚堝悓涓嬨�屽鎵归�氳繃涓斿凡涓婁紶鍙戠エ闄勪欢銆嶇殑绱寮�绁ㄩ噾棰�
+ List<CrmInvoiceDO> invoices = invoiceService.getInvoiceListByContractIdAndStatus(reqVO.getContractId(),
+ List.of(CrmAuditStatusEnum.APPROVE.getStatus()));
+ BigDecimal invoicePriceSum = BigDecimal.ZERO;
+ for (CrmInvoiceDO invoice : invoices) {
+ if (CollUtil.isNotEmpty(storageAttachmentApi.listAttachments(
+ StorageRecordTypeEnum.CRM_INVOICE.getType(), invoice.getId()))) {
+ invoicePriceSum = invoicePriceSum.add(invoice.getPrice());
+ }
+ }
+
+ // 3. 鏍¢獙鏈鍥炴閲戦涓嶈秴杩囧墿浣欏彲鍥炴閲戦锛堝凡寮�绁ㄩ噾棰� - 宸插洖娆鹃噾棰濓級
+ BigDecimal availablePrice = invoicePriceSum.subtract(receivablePriceSum);
+ if (reqVO.getPrice().compareTo(availablePrice) > 0) {
+ throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_ENOUGH, availablePrice);
+ }
+ }
+
private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) {
if (reqVO.getOwnerUserId() != null) {
adminUserApi.validateUser(reqVO.getOwnerUserId()); // 鏍¢獙璐熻矗浜哄瓨鍦�
@@ -167,8 +208,10 @@
.setContractId(oldReceivable.getContractId()).setPlanId(oldReceivable.getPlanId()); // 璁剧疆宸插瓨鍦ㄧ殑鍊�
// 1.2 鏍¢獙鍙洖娆鹃噾棰濊秴杩囦笂闄�
validateReceivablePriceExceedsLimit(updateReqVO);
+ // 1.3 鏍¢獙鍙洖娆鹃噾棰濅笉瓒呰繃绱宸插紑绁ㄩ噾棰濓紙鍏堝紑绁ㄥ悗鍥炴锛�
+ validateReceivablePriceExceedsInvoiceLimit(updateReqVO);
- // 1.3 鍙湁鑽夌銆佸鎵逛腑锛屽彲浠ョ紪杈戯紱
+ // 1.4 鍙湁鑽夌銆佸鎵逛腑锛屽彲浠ョ紪杈戯紱
if (!ObjectUtils.equalsAny(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
CrmAuditStatusEnum.PROCESS.getStatus())) {
throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
index cc22b35..ff891ea 100644
--- a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -16,6 +16,11 @@
ErrorCode EMPLOYEE_EDUCATION_NOT_EXISTS = new ErrorCode(1_030_000_003, "鏁欒偛缁忓巻涓嶅瓨鍦�");
ErrorCode EMPLOYEE_WORK_HISTORY_NOT_EXISTS = new ErrorCode(1_030_000_004, "宸ヤ綔缁忓巻涓嶅瓨鍦�");
ErrorCode EMPLOYEE_EMERGENCY_CONTACT_NOT_EXISTS = new ErrorCode(1_030_000_005, "绱ф�ヨ仈绯讳汉涓嶅瓨鍦�");
+ ErrorCode EMPLOYEE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_030_000_006, "瀵煎叆鍛樺伐鏁版嵁涓嶈兘涓虹┖");
+ ErrorCode EMPLOYEE_DEPT_NAME_NOT_EXISTS = new ErrorCode(1_030_000_007, "閮ㄩ棬銆恵}銆戜笉瀛樺湪");
+ ErrorCode EMPLOYEE_POST_NAME_NOT_EXISTS = new ErrorCode(1_030_000_008, "宀椾綅銆恵}銆戜笉瀛樺湪");
+ ErrorCode EMPLOYEE_ROLE_NAME_NOT_EXISTS = new ErrorCode(1_030_000_009, "瑙掕壊銆恵}銆戜笉瀛樺湪");
+ ErrorCode EMPLOYEE_IMPORT_FIELD_REQUIRED = new ErrorCode(1_030_000_010, "瀵煎叆瀛楁銆恵}銆戜笉鑳戒负绌�");
// ========== 璇峰亣鐢宠 1-030-001-000 ==========
ErrorCode LEAVE_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_001_000, "璇峰亣鐢宠涓嶅瓨鍦�");
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
index 827ca30..989e1ce 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
@@ -9,6 +9,9 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportExcelVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeePageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO;
@@ -32,6 +35,7 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
+import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
@@ -184,4 +188,29 @@
ExcelUtils.write(response, "鍛樺伐淇℃伅.xls", "鏁版嵁", HrmEmployeeRespVO.class, list);
}
+ @GetMapping("/import-template")
+ @Operation(summary = "涓嬭浇鍛樺伐瀵煎叆妯℃澘")
+ @PreAuthorize("@ss.hasPermission('hrm:employee:import')")
+ public void importTemplate(HttpServletResponse response) throws IOException {
+ // 杈撳嚭绀轰緥鏁版嵁锛屾柟渚夸娇鐢ㄨ�呭弬鑰�
+ List<HrmEmployeeImportExcelVO> list = Arrays.asList(
+ HrmEmployeeImportExcelVO.builder().name("寮犱笁").gender(1).phone("13800138000")
+ .deptName("鐮斿彂閮�").postName("閮ㄩ棬缁忕悊").roleNames("鏅�氱鐞嗗憳")
+ .hireDate(LocalDate.of(2020, 3, 1)).employeeStatus(1)
+ .idCard("320102199001150011").birthday(LocalDate.of(1990, 1, 15))
+ .nation("姹夋棌").marriageStatus("宸插").age(36).education("鏈").politicalStatus("涓叡鍏氬憳")
+ .email("zhangsan@example.com").build()
+ );
+ ExcelUtils.write(response, "鍛樺伐瀵煎叆妯℃澘.xls", "鍛樺伐鍒楄〃", HrmEmployeeImportExcelVO.class, list);
+ }
+
+ @PostMapping("/import")
+ @Operation(summary = "瀵煎叆鍛樺伐")
+ @PreAuthorize("@ss.hasPermission('hrm:employee:import')")
+ public CommonResult<HrmEmployeeImportRespVO> importExcel(@Valid HrmEmployeeImportReqVO importReqVO)
+ throws Exception {
+ List<HrmEmployeeImportExcelVO> list = ExcelUtils.read(importReqVO.getFile(), HrmEmployeeImportExcelVO.class);
+ return success(employeeService.importEmployeeList(list, importReqVO));
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java
new file mode 100644
index 0000000..977f0c1
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDate;
+
+/**
+ * 鍛樺伐 Excel 瀵煎叆 VO
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class HrmEmployeeImportExcelVO {
+
+ @ExcelProperty("鍛樺伐濮撳悕")
+ private String name;
+
+ @ExcelProperty(value = "鎬у埆", converter = DictConvert.class)
+ @DictFormat("hrm_gender")
+ private Integer gender;
+
+ @ExcelProperty("鎵嬫満鍙�")
+ private String phone;
+
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @ExcelProperty("宀椾綅鍚嶇О")
+ private String postName;
+
+ @ExcelProperty("鐢ㄦ埛瑙掕壊")
+ private String roleNames;
+
+ @ExcelProperty("鍏ヨ亴鏃ユ湡")
+ private LocalDate hireDate;
+
+ @ExcelProperty(value = "鍛樺伐鐘舵��", converter = DictConvert.class)
+ @DictFormat("hrm_employee_status")
+ private Integer employeeStatus;
+
+ @ExcelProperty("韬唤璇佸彿")
+ private String idCard;
+
+ @ExcelProperty("鍑虹敓鏃ユ湡")
+ private LocalDate birthday;
+
+ @ExcelProperty("姘戞棌")
+ private String nation;
+
+ @ExcelProperty("濠氬Щ鐘舵��")
+ private String marriageStatus;
+
+ @ExcelProperty("骞撮緞")
+ private Integer age;
+
+ @ExcelProperty("瀛﹀巻")
+ private String education;
+
+ @ExcelProperty("鏀挎不闈㈣矊")
+ private String politicalStatus;
+
+ @ExcelProperty("閭")
+ private String email;
+
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportReqVO.java
new file mode 100644
index 0000000..ef12e07
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐瀵煎叆 Request VO")
+@Data
+public class HrmEmployeeImportReqVO {
+
+ @Schema(description = "Excel 鏂囦欢", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "Excel 鏂囦欢涓嶈兘涓虹┖")
+ private MultipartFile file;
+
+ @Schema(description = "鏄惁鏀寔鏇存柊锛岄粯璁� false", example = "true")
+ private Boolean updateSupport = false;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportRespVO.java
new file mode 100644
index 0000000..f49fc8d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐瀵煎叆 Response VO")
+@Data
+@Builder
+public class HrmEmployeeImportRespVO {
+
+ @Schema(description = "鍒涘缓鎴愬姛鐨勫憳宸ュ悕鏁扮粍", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<String> createNames;
+
+ @Schema(description = "鏇存柊鎴愬姛鐨勫憳宸ュ悕鏁扮粍", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<String> updateNames;
+
+ @Schema(description = "瀵煎叆澶辫触鐨勫憳宸ラ泦鍚堬紝key 涓哄憳宸ュ悕锛寁alue 涓哄け璐ュ師鍥�", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Map<String, String> failureNames;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
index 29d9d9f..262364a 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
@@ -64,6 +64,26 @@
@ExcelProperty("韬唤璇佸彿")
private String idCard;
+ @Schema(description = "姘戞棌")
+ @ExcelProperty("姘戞棌")
+ private String nation;
+
+ @Schema(description = "濠氬Щ鐘舵��")
+ @ExcelProperty("濠氬Щ鐘舵��")
+ private String marriageStatus;
+
+ @Schema(description = "骞撮緞")
+ @ExcelProperty("骞撮緞")
+ private Integer age;
+
+ @Schema(description = "瀛﹀巻")
+ @ExcelProperty("瀛﹀巻")
+ private String education;
+
+ @Schema(description = "鏀挎不闈㈣矊")
+ @ExcelProperty("鏀挎不闈㈣矊")
+ private String politicalStatus;
+
@Schema(description = "鍏ヨ亴鏃ユ湡")
@ExcelProperty("鍏ヨ亴鏃ユ湡")
private LocalDate hireDate;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
index 90a8d45..35fe0a2 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
@@ -52,6 +52,21 @@
message = "韬唤璇佸彿鏍煎紡涓嶆纭�")
private String idCard;
+ @Schema(description = "姘戞棌", example = "姹夋棌")
+ private String nation;
+
+ @Schema(description = "濠氬Щ鐘舵��", example = "宸插")
+ private String marriageStatus;
+
+ @Schema(description = "骞撮緞", example = "30")
+ private Integer age;
+
+ @Schema(description = "瀛﹀巻", example = "鏈")
+ private String education;
+
+ @Schema(description = "鏀挎不闈㈣矊", example = "涓叡鍏氬憳")
+ private String politicalStatus;
+
@Schema(description = "鍏ヨ亴鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2020-03-01")
@NotNull(message = "鍏ヨ亴鏃ユ湡涓嶈兘涓虹┖")
private LocalDate hireDate;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
index 471adb1..3207b95 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
@@ -89,6 +89,31 @@
private String idCard;
/**
+ * 姘戞棌
+ */
+ private String nation;
+
+ /**
+ * 濠氬Щ鐘舵��
+ */
+ private String marriageStatus;
+
+ /**
+ * 骞撮緞
+ */
+ private Integer age;
+
+ /**
+ * 瀛﹀巻
+ */
+ private String education;
+
+ /**
+ * 鏀挎不闈㈣矊
+ */
+ private String politicalStatus;
+
+ /**
* 鍏ヨ亴鏃ユ湡
*/
private LocalDate hireDate;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
index a50bd85..6ece4cb 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
@@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.hrm.service.employee;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportExcelVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeePageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO;
@@ -96,4 +99,14 @@
*/
HrmEmployeeDO getEmployeeByUserId(Long userId);
+ /**
+ * 瀵煎叆鍛樺伐鍒楄〃
+ *
+ * @param importEmployees 鍛樺伐瀵煎叆鍒楄〃
+ * @param importReqVO 瀵煎叆璇锋眰
+ * @return 瀵煎叆缁撴灉
+ */
+ HrmEmployeeImportRespVO importEmployeeList(List<HrmEmployeeImportExcelVO> importEmployees,
+ HrmEmployeeImportReqVO importReqVO);
+
}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
index 97975d7..02e3c20 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -3,10 +3,14 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEducationSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEmergencyContactSaveReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportExcelVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeePageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO;
@@ -30,12 +34,16 @@
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -43,6 +51,7 @@
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
/**
@@ -72,6 +81,9 @@
private AdminUserApi adminUserApi;
@Resource
private RoleApi roleApi;
+ @Resource
+ @Lazy // 鑷敞鍏ラ渶寤惰繜鍔犺浇锛屽惁鍒� bean 琚唬鐞嗗寘瑁呭悗娉ㄥ叆鍘熷鐗堟湰瀵艰嚧鍚姩澶辫触
+ private HrmEmployeeService self;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -322,4 +334,131 @@
return employeeMapper.selectByUserId(userId);
}
+ @Override
+ public HrmEmployeeImportRespVO importEmployeeList(List<HrmEmployeeImportExcelVO> importEmployees,
+ HrmEmployeeImportReqVO importReqVO) {
+ // 1. 鏍¢獙闈炵┖
+ importEmployees = filterList(importEmployees, item -> Objects.nonNull(item.getName()));
+ if (CollUtil.isEmpty(importEmployees)) {
+ throw exception(EMPLOYEE_IMPORT_LIST_IS_EMPTY);
+ }
+ // 2. 棰勫姞杞介儴闂�/宀椾綅/瑙掕壊 鍚嶇О -> 瀹炰綋 鏄犲皠
+ Map<String, DeptRespDTO> deptMap = deptApi.getDeptList().stream()
+ .collect(Collectors.toMap(DeptRespDTO::getName, dept -> dept, (v1, v2) -> v1));
+ Map<String, PostRespDTO> postMap = postApi.getPostList().stream()
+ .collect(Collectors.toMap(PostRespDTO::getName, post -> post, (v1, v2) -> v1));
+ Map<String, RoleRespDTO> roleMap = roleApi.getRoleList().stream()
+ .collect(Collectors.toMap(RoleRespDTO::getName, role -> role, (v1, v2) -> v1));
+ // 3. 閫愭潯澶勭悊
+ HrmEmployeeImportRespVO respVO = HrmEmployeeImportRespVO.builder()
+ .createNames(new ArrayList<>())
+ .updateNames(new ArrayList<>())
+ .failureNames(new LinkedHashMap<>())
+ .build();
+ for (HrmEmployeeImportExcelVO importEmployee : importEmployees) {
+ String name = importEmployee.getName();
+ try {
+ // 3.1 鍚嶇О杞� ID
+ Long deptId = parseDeptId(importEmployee.getDeptName(), deptMap);
+ Long postId = parsePostId(importEmployee.getPostName(), postMap);
+ Set<Long> roleIds = parseRoleIds(importEmployee.getRoleNames(), roleMap);
+ // 3.2 鏋勫缓淇濆瓨璇锋眰
+ HrmEmployeeSaveReqVO saveReqVO = buildImportSaveReqVO(importEmployee, deptId, postId, roleIds);
+ // 3.3 鍒ゆ柇瀛樺湪鎬э細鎸夋墜鏈哄彿
+ HrmEmployeeDO existEmployee = employeeMapper.selectByPhone(importEmployee.getPhone());
+ if (existEmployee == null) {
+ self.createEmployee(saveReqVO);
+ respVO.getCreateNames().add(name);
+ } else {
+ if (!Boolean.TRUE.equals(importReqVO.getUpdateSupport())) {
+ respVO.getFailureNames().put(name, EMPLOYEE_PHONE_EXISTS.getMsg());
+ continue;
+ }
+ saveReqVO.setId(existEmployee.getId());
+ self.updateEmployee(saveReqVO);
+ respVO.getUpdateNames().add(name);
+ }
+ } catch (ServiceException ex) {
+ respVO.getFailureNames().put(name, ex.getMessage());
+ } catch (Exception ex) {
+ respVO.getFailureNames().put(name, ex.getMessage());
+ }
+ }
+ return respVO;
+ }
+
+ private HrmEmployeeSaveReqVO buildImportSaveReqVO(HrmEmployeeImportExcelVO importEmployee,
+ Long deptId, Long postId, Set<Long> roleIds) {
+ if (StrUtil.isBlank(importEmployee.getName())) {
+ throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "鍛樺伐濮撳悕");
+ }
+ if (StrUtil.isBlank(importEmployee.getPhone())) {
+ throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "鎵嬫満鍙�");
+ }
+ if (importEmployee.getHireDate() == null) {
+ throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "鍏ヨ亴鏃ユ湡");
+ }
+ HrmEmployeeSaveReqVO reqVO = new HrmEmployeeSaveReqVO();
+ reqVO.setName(importEmployee.getName());
+ reqVO.setGender(importEmployee.getGender() != null ? importEmployee.getGender() : 1); // 榛樿鐢�
+ reqVO.setPhone(importEmployee.getPhone());
+ reqVO.setDeptId(deptId);
+ reqVO.setPostId(postId);
+ reqVO.setRoleIds(CollUtil.isEmpty(roleIds) ? null : new ArrayList<>(roleIds));
+ reqVO.setHireDate(importEmployee.getHireDate());
+ reqVO.setEmployeeStatus(importEmployee.getEmployeeStatus() != null
+ ? importEmployee.getEmployeeStatus() : 1); // 榛樿鍦ㄨ亴
+ reqVO.setEmail(importEmployee.getEmail());
+ reqVO.setBirthday(importEmployee.getBirthday());
+ reqVO.setIdCard(importEmployee.getIdCard());
+ reqVO.setNation(importEmployee.getNation());
+ reqVO.setMarriageStatus(importEmployee.getMarriageStatus());
+ reqVO.setAge(importEmployee.getAge());
+ reqVO.setEducation(importEmployee.getEducation());
+ reqVO.setPoliticalStatus(importEmployee.getPoliticalStatus());
+ reqVO.setRemark(importEmployee.getRemark());
+ return reqVO;
+ }
+
+ private Long parseDeptId(String deptName, Map<String, DeptRespDTO> deptMap) {
+ if (StrUtil.isBlank(deptName)) {
+ throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "閮ㄩ棬鍚嶇О");
+ }
+ DeptRespDTO dept = deptMap.get(deptName.trim());
+ if (dept == null) {
+ throw exception(EMPLOYEE_DEPT_NAME_NOT_EXISTS, deptName.trim());
+ }
+ return dept.getId();
+ }
+
+ private Long parsePostId(String postName, Map<String, PostRespDTO> postMap) {
+ if (StrUtil.isBlank(postName)) {
+ throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "宀椾綅鍚嶇О");
+ }
+ PostRespDTO post = postMap.get(postName.trim());
+ if (post == null) {
+ throw exception(EMPLOYEE_POST_NAME_NOT_EXISTS, postName.trim());
+ }
+ return post.getId();
+ }
+
+ private Set<Long> parseRoleIds(String roleNames, Map<String, RoleRespDTO> roleMap) {
+ if (StrUtil.isBlank(roleNames)) {
+ return Collections.emptySet();
+ }
+ Set<Long> roleIds = new LinkedHashSet<>();
+ for (String roleName : roleNames.split("[,锛宂")) {
+ String trimmed = roleName.trim();
+ if (StrUtil.isBlank(trimmed)) {
+ continue;
+ }
+ RoleRespDTO role = roleMap.get(trimmed);
+ if (role == null) {
+ throw exception(EMPLOYEE_ROLE_NAME_NOT_EXISTS, trimmed);
+ }
+ roleIds.add(role.getId());
+ }
+ return roleIds;
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java
index 13b563f..6deb38a 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java
@@ -31,6 +31,13 @@
List<DeptRespDTO> getDeptList(Collection<Long> ids);
/**
+ * 鑾峰緱鍏ㄩ儴閮ㄩ棬鍒楄〃
+ *
+ * @return 閮ㄩ棬淇℃伅鏁扮粍
+ */
+ List<DeptRespDTO> getDeptList();
+
+ /**
* 鏍¢獙閮ㄩ棬浠槸鍚︽湁鏁堛�傚涓嬫儏鍐碉紝瑙嗕负鏃犳晥锛�
* 1. 閮ㄩ棬缂栧彿涓嶅瓨鍦�
* 2. 閮ㄩ棬琚鐢�
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java
index 29a4506..4f9b527 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java
@@ -2,6 +2,7 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import org.springframework.stereotype.Service;
@@ -34,6 +35,12 @@
}
@Override
+ public List<DeptRespDTO> getDeptList() {
+ List<DeptDO> depts = deptService.getDeptList(new DeptListReqVO());
+ return BeanUtils.toBean(depts, DeptRespDTO.class);
+ }
+
+ @Override
public void validateDeptList(Collection<Long> ids) {
deptService.validateDeptList(ids);
}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java
index 379fed7..bced8f0 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java
@@ -27,6 +27,13 @@
List<PostRespDTO> getPostList(Collection<Long> ids);
+ /**
+ * 鑾峰緱鍏ㄩ儴宀椾綅鍒楄〃
+ *
+ * @return 宀椾綅淇℃伅鏁扮粍
+ */
+ List<PostRespDTO> getPostList();
+
default Map<Long, PostRespDTO> getPostMap(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return MapUtil.empty();
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java
index 9d87100..9d02d79 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java
@@ -32,4 +32,10 @@
return BeanUtils.toBean(list, PostRespDTO.class);
}
+ @Override
+ public List<PostRespDTO> getPostList() {
+ List<PostDO> list = postService.getPostList(null, null);
+ return BeanUtils.toBean(list, PostRespDTO.class);
+ }
+
}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApi.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApi.java
index 5162225..5ba3ad6 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApi.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApi.java
@@ -40,6 +40,13 @@
List<RoleRespDTO> getRoleList(Collection<Long> ids);
/**
+ * 鑾峰緱鍏ㄩ儴瑙掕壊鍒楄〃
+ *
+ * @return 瑙掕壊淇℃伅鏁扮粍
+ */
+ List<RoleRespDTO> getRoleList();
+
+ /**
* 鑾峰緱鎸囧畾缂栧彿鐨勮鑹� Map
*
* @param ids 瑙掕壊缂栧彿鏁扮粍
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApiImpl.java
index 87f5ef2..86c466b 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApiImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/RoleApiImpl.java
@@ -37,4 +37,10 @@
List<RoleDO> list = roleService.getRoleList(ids);
return BeanUtils.toBean(list, RoleRespDTO.class);
}
+
+ @Override
+ public List<RoleRespDTO> getRoleList() {
+ List<RoleDO> list = roleService.getRoleList();
+ return BeanUtils.toBean(list, RoleRespDTO.class);
+ }
}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
index bb0087d..67c57d7 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -46,6 +46,7 @@
// ===== CRM =====
CRM_SALE_QUOTATION("crm_sale_quotation", "CRM 閿�鍞姤浠峰崟"),
CRM_FOLLOW_UP_RECORD("crm_follow_up_record", "CRM 璺熻繘璁板綍"),
+ CRM_INVOICE("crm_invoice", "CRM 寮�绁�"),
// ===== HRM =====
HRM_LEAVE_APPLICATION("hrm_leave_application", "HRM 璇峰亣鐢宠"),
diff --git a/yudao-server/src/main/resources/application-jhhg.yaml b/yudao-server/src/main/resources/application-jhhg.yaml
new file mode 100644
index 0000000..9cc4b70
--- /dev/null
+++ b/yudao-server/src/main/resources/application-jhhg.yaml
@@ -0,0 +1,324 @@
+server:
+ port: 48080
+
+--- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
+spring:
+ autoconfigure:
+ # noinspection SpringBootApplicationYaml
+ exclude:
+ - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 榛樿 local 鐜锛屼笉寮�鍚� Quartz 鐨勮嚜鍔ㄩ厤缃�
+ - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Qdrant锛屾墜鍔ㄥ垱寤�
+ - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Redis锛屾墜鍔ㄥ垱寤�
+ - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Milvus锛屾墜鍔ㄥ垱寤�
+ - org.dromara.trans.config.TransServiceConfig # TODO 鑺嬭壙锛歟asy-trans 鍏煎 Spring Boot 4 鍚庣Щ闄わ紙TransServiceConfig 寮曠敤浜嗗凡绉婚櫎鐨� RestTemplateBuilder锛�
+ # 绂佺敤鏈厤缃� API Key 鐨勬ā鍨嬭嚜鍔ㄩ厤缃紙鍙繚鐣欓�氫箟鍗冮棶 Embedding 鐨勬墜鍔ㄩ厤缃級
+ - org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration
+ - org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration
+ - org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration
+ - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration
+ - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration
+ - org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration
+ - org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration
+ - org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration
+ - org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration
+ - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiEmbeddingConnectionAutoConfiguration
+ - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiTextEmbeddingAutoConfiguration
+ - org.springframework.ai.model.ollama.autoconfigure.OllamaApiAutoConfiguration
+ - org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration
+ - org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration
+ - org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration
+ # 鏁版嵁婧愰厤缃」
+ datasource:
+ druid: # Druid 銆愮洃鎺с�戠浉鍏崇殑鍏ㄥ眬閰嶇疆
+ web-stat-filter:
+ enabled: true
+ stat-view-servlet:
+ enabled: true
+ allow: # 璁剧疆鐧藉悕鍗曪紝涓嶅~鍒欏厑璁告墍鏈夎闂�
+ url-pattern: /druid/*
+ login-username: # 鎺у埗鍙扮鐞嗙敤鎴峰悕鍜屽瘑鐮�
+ login-password:
+ filter:
+ stat:
+ enabled: true
+ log-slow-sql: true # 鎱� SQL 璁板綍
+ slow-sql-millis: 100
+ merge-sql: true
+ wall:
+ config:
+ multi-statement-allow: true
+ dynamic: # 澶氭暟鎹簮閰嶇疆
+ druid: # Druid 銆愯繛鎺ユ睜銆戠浉鍏崇殑鍏ㄥ眬閰嶇疆
+ initial-size: 1 # 鍒濆杩炴帴鏁�
+ min-idle: 1 # 鏈�灏忚繛鎺ユ睜鏁伴噺
+ max-active: 20 # 鏈�澶ц繛鎺ユ睜鏁伴噺
+ max-wait: 60000 # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂达紝鍗曚綅锛氭绉掞紙1 鍒嗛挓锛�
+ time-between-eviction-runs-millis: 60000 # 閰嶇疆闂撮殧澶氫箙鎵嶈繘琛屼竴娆℃娴嬶紝妫�娴嬮渶瑕佸叧闂殑绌洪棽杩炴帴锛屽崟浣嶏細姣锛�1 鍒嗛挓锛�
+ min-evictable-idle-time-millis: 600000 # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�灏忕敓瀛樼殑鏃堕棿锛屽崟浣嶏細姣锛�10 鍒嗛挓锛�
+ max-evictable-idle-time-millis: 1800000 # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�澶х敓瀛樼殑鏃堕棿锛屽崟浣嶏細姣锛�30 鍒嗛挓锛�
+ validation-query: SELECT 1 FROM DUAL # 閰嶇疆妫�娴嬭繛鎺ユ槸鍚︽湁鏁�
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+ pool-prepared-statements: true # 鏄惁寮�鍚� PreparedStatement 缂撳瓨
+ max-pool-prepared-statement-per-connection-size: 20 # 姣忎釜杩炴帴缂撳瓨鐨� PreparedStatement 鏁伴噺
+ primary: master
+ datasource:
+ master:
+ url: jdbc:mysql://172.17.0.1:9002/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 杩炴帴鐨勭ず渚�
+ # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 杩炴帴鐨勭ず渚�
+ # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 杩炴帴鐨勭ず渚�
+ # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 杩炴帴鐨勭ず渚�
+ # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 杩炴帴鐨勭ず渚�
+ # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 杩炴帴鐨勭ず渚�
+ # url: jdbc:kingbase8://127.0.0.1:54321/test # 浜哄ぇ閲戜粨 KingbaseES 杩炴帴鐨勭ず渚�
+ # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 杩炴帴鐨勭ず渚�
+ # url: jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro # 鐎氶珮 HighGo 杩炴帴鐨勭ず渚�
+ username: root
+ password: jhhg@123456..
+ # username: sa # SQL Server 杩炴帴鐨勭ず渚�
+ # password: Yudao@2024 # SQL Server 杩炴帴鐨勭ず渚�
+ # username: SYSDBA # DM 杩炴帴鐨勭ず渚�
+ # password: SYSDBA001 # DM 杩炴帴鐨勭ず渚�
+ # username: root # OpenGauss 杩炴帴鐨勭ず渚�
+ # password: Yudao@2024 # OpenGauss 杩炴帴鐨勭ず渚�
+ # username: root # 鐎氶珮 HighGo 杩炴帴鐨勭ず渚�
+ # password: 123456 # 鐎氶珮 HighGo 杩炴帴鐨勭ず渚�
+ slave: # 妯℃嫙浠庡簱锛屽彲鏍规嵁鑷繁闇�瑕佷慨鏀�
+ lazy: true # 寮�鍚噿鍔犺浇锛屼繚璇佸惎鍔ㄩ�熷害
+ url: jdbc:mysql://172.17.0.1:9002/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
+ username: root
+ password: jhhg@123456
+# tdengine: # IoT 鏁版嵁搴擄紙闇�瑕� IoT 鐗╄仈缃戝啀寮�鍚櫌锛侊級
+# lazy: true # 寮�鍚噿鍔犺浇锛屼繚璇佸惎鍔ㄩ�熷害
+# url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true&enableAutoReconnect=true # 鍙傝锛歨ttps://t.zsxq.com/qaX9c锛屽紑鍚� TDengine WebSocket 鑷姩閲嶈繛
+# driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver
+# username: root
+# password: taosdata
+# druid:
+# validation-query: SELECT SERVER_STATUS() # TDengine 鏁版嵁婧愮殑鏈夋晥鎬ф鏌� SQL
+
+ # Redis 閰嶇疆銆俁edisson 榛樿鐨勯厤缃冻澶熶娇鐢紝涓�鑸笉闇�瑕佽繘琛岃皟浼�
+ data:
+ redis:
+ host: 172.17.0.1 # 鍦板潃
+ port: 6379 # 绔彛
+ database: 1 # 鏁版嵁搴撶储寮�
+# password: dev # 瀵嗙爜锛屽缓璁敓浜х幆澧冨紑鍚�
+
+--- #################### 瀹氭椂浠诲姟鐩稿叧閰嶇疆 ####################
+
+# Quartz 閰嶇疆椤癸紝瀵瑰簲 QuartzProperties 閰嶇疆绫�
+spring:
+ quartz:
+ auto-startup: true # 鏈湴寮�鍙戠幆澧冿紝灏介噺涓嶈寮�鍚� Job
+ scheduler-name: schedulerName # Scheduler 鍚嶅瓧銆傞粯璁や负 schedulerName
+ job-store-type: jdbc # Job 瀛樺偍鍣ㄧ被鍨嬨�傞粯璁や负 memory 琛ㄧず鍐呭瓨锛屽彲閫� jdbc 浣跨敤鏁版嵁搴撱��
+ wait-for-jobs-to-complete-on-shutdown: true # 搴旂敤鍏抽棴鏃讹紝鏄惁绛夊緟瀹氭椂浠诲姟鎵ц瀹屾垚銆傞粯璁や负 false 锛屽缓璁缃负 true
+ properties: # 娣诲姞 Quartz Scheduler 闄勫姞灞炴�э紝鏇村鍙互鐪� http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 鏂囨。
+ org:
+ quartz:
+ # Scheduler 鐩稿叧閰嶇疆
+ scheduler:
+ instanceName: schedulerName
+ instanceId: AUTO # 鑷姩鐢熸垚 instance ID
+ # JobStore 鐩稿叧閰嶇疆
+ jobStore:
+ # JobStore 瀹炵幇绫汇�傚彲瑙佸崥瀹細https://blog.csdn.net/weixin_42458219/article/details/122247162
+ class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
+ isClustered: true # 鏄泦缇ゆā寮�
+ clusterCheckinInterval: 15000 # 闆嗙兢妫�鏌ラ鐜囷紝鍗曚綅锛氭绉掋�傞粯璁や负 15000锛屽嵆 15 绉�
+ misfireThreshold: 60000 # misfire 闃�鍊硷紝鍗曚綅锛氭绉掋��
+ # 绾跨▼姹犵浉鍏抽厤缃�
+ threadPool:
+ threadCount: 25 # 绾跨▼姹犲ぇ灏忋�傞粯璁や负 10 銆�
+ threadPriority: 5 # 绾跨▼浼樺厛绾�
+ class: org.quartz.simpl.SimpleThreadPool # 绾跨▼姹犵被鍨�
+ jdbc: # 浣跨敤 JDBC 鐨� JobStore 鐨勬椂鍊欙紝JDBC 鐨勯厤缃�
+ initialize-schema: NEVER # 鏄惁鑷姩浣跨敤 SQL 鍒濆鍖� Quartz 琛ㄧ粨鏋勩�傝繖閲岃缃垚 never 锛屾垜浠墜鍔ㄥ垱寤鸿〃缁撴瀯銆�
+
+--- #################### 娑堟伅闃熷垪鐩稿叧 ####################
+
+# rocketmq 閰嶇疆椤癸紝瀵瑰簲 RocketMQProperties 閰嶇疆绫�
+rocketmq:
+ name-server: 127.0.0.1:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 閰嶇疆椤癸紝瀵瑰簲 RabbitProperties 閰嶇疆绫�
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 鏈嶅姟鐨勫湴鍧�
+ port: 5672 # RabbitMQ 鏈嶅姟鐨勭鍙�
+ username: rabbit # RabbitMQ 鏈嶅姟鐨勮处鍙�
+ password: rabbit # RabbitMQ 鏈嶅姟鐨勫瘑鐮�
+ # Kafka 閰嶇疆椤癸紝瀵瑰簲 KafkaProperties 閰嶇疆绫�
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 鎸囧畾 Kafka Broker 鍦板潃锛屽彲浠ヨ缃涓紝浠ラ�楀彿鍒嗛殧
+
+--- #################### 鏈嶅姟淇濋殰鐩稿叧閰嶇疆 ####################
+
+# Lock4j 閰嶇疆椤�
+lock4j:
+ acquire-timeout: 3000 # 鑾峰彇鍒嗗竷寮忛攣瓒呮椂鏃堕棿锛岄粯璁や负 3000 姣
+ expire: 30000 # 鍒嗗竷寮忛攣鐨勮秴鏃舵椂闂达紝榛樿涓� 30000 姣
+
+--- #################### 鐩戞帶鐩稿叧閰嶇疆 ####################
+
+# Actuator 鐩戞帶绔偣鐨勯厤缃」
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 鎻愪緵鐨� API 鎺ュ彛鐨勬牴鐩綍銆傞粯璁や负 /actuator
+ exposure:
+ include: '*' # 闇�瑕佸紑鏀剧殑绔偣銆傞粯璁ゅ�煎彧鎵撳紑 health 鍜� info 涓や釜绔偣銆傞�氳繃璁剧疆 * 锛屽彲浠ュ紑鏀炬墍鏈夌鐐广��
+
+# Spring Boot Admin 閰嶇疆椤�
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 瀹㈡埛绔殑鐩稿叧閰嶇疆
+ client:
+ url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 璁剧疆 Spring Boot Admin Server 鍦板潃
+ instance:
+ service-host-type: IP # 娉ㄥ唽瀹炰緥鏃讹紝浼樺厛浣跨敤 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
+ username: admin
+ password: admin
+ # Spring Boot Admin Server 鏈嶅姟绔殑鐩稿叧閰嶇疆
+ context-path: /admin # 閰嶇疆 Spring
+ # 鍏佽宓屽叆 iframe 鐨勫煙鍚嶏紙鏀寔閫氶厤绗︼級锛屽疄闄呴儴缃叉椂锛屽彲浠ユ敼涓� "'self' [浣犵殑鍏綉鍩熷悕]"
+ frame-ancestors: "'self' localhost localhost:48080 127.0.0.1 127.0.0.1:48080"
+
+# 鏃ュ織鏂囦欢閰嶇疆
+logging:
+ file:
+ name: logs/${spring.application.name}.log # 鏃ュ織鏂囦欢鍚嶏紝鐩稿浜庨」鐩牴鐩綍
+ level:
+ # 閰嶇疆鑷繁鍐欑殑 MyBatis Mapper 鎵撳嵃鏃ュ織
+ cn.iocoder.yudao.module.bpm.dal.mysql: debug
+ cn.iocoder.yudao.module.infra.dal.mysql: debug
+ cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # 閰嶇疆 ApiErrorLogMapper 鐨勬棩蹇楃骇鍒负 info锛岄伩鍏嶅拰 GlobalExceptionHandler 閲嶅鎵撳嵃
+ cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 閰嶇疆 JobLogMapper 鐨勬棩蹇楃骇鍒负 info
+ cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # 閰嶇疆 FileConfigMapper 鐨勬棩蹇楃骇鍒负 info
+ cn.iocoder.yudao.module.pay.dal.mysql: debug
+ cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 閰嶇疆 PayNotifyTaskMapper 鐨勬棩蹇楃骇鍒负 info
+ cn.iocoder.yudao.module.system.dal.mysql: debug
+ cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 閰嶇疆 SmsChannelMapper 鐨勬棩蹇楃骇鍒负 info
+ cn.iocoder.yudao.module.tool.dal.mysql: debug
+ cn.iocoder.yudao.module.member.dal.mysql: debug
+ cn.iocoder.yudao.module.trade.dal.mysql: debug
+ cn.iocoder.yudao.module.promotion.dal.mysql: debug
+ cn.iocoder.yudao.module.statistics.dal.mysql: debug
+ cn.iocoder.yudao.module.crm.dal.mysql: debug
+ cn.iocoder.yudao.module.erp.dal.mysql: debug
+ cn.iocoder.yudao.module.mes.dal.mysql: debug
+ cn.iocoder.yudao.module.wms.dal.mysql: debug
+ cn.iocoder.yudao.module.iot.dal.mysql: debug
+ cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG
+ cn.iocoder.yudao.module.iot.service.rule: debug
+ cn.iocoder.yudao.module.ai.dal.mysql: debug
+ cn.iocoder.yudao.module.im.dal.mysql: debug
+ org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 鑺嬭壙锛氬厛绂佺敤锛孲pring Boot 3.X 瀛樺湪閮ㄥ垎閿欒鐨� WARN 鎻愮ず
+
+debug: false
+
+yudao:
+ ai:
+ vector-store:
+ type: milvus # 鍚戦噺搴撶被鍨嬶細milvus
+ milvus:
+ initialize-schema: true
+ database-name: test
+ collection-name: knowledge_segment_jhhg
+ host: 36.213.180.217
+ port: 19530
+
+--- #################### 寰俊鍏紬鍙枫�佸皬绋嬪簭鐩稿叧閰嶇疆 ####################
+wx:
+ mp: # 鍏紬鍙烽厤缃紙蹇呭~锛夛紝鍙傝 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 鏂囨。
+# app-id: wx041349c6f39b268b # 娴嬭瘯鍙凤紙鐗涘笇灏ф彁渚涚殑锛�
+# secret: 5abee519483bc9f8cb37ce280e814bd0
+# app-id: wx5b23ba7a5589ecbb # 娴嬭瘯鍙凤紙鑷繁鐨勶級
+# secret: 2a7b3b20c537e52e74afd395eb85f61f
+ app-id: wxf56b1542b9e85f8a # 娴嬭瘯鍙凤紙Kongdy 鎻愪緵鐨勶級
+ secret: 496379dcef1ba869e9234de8d598cfd3
+ # 瀛樺偍閰嶇疆锛岃В鍐� AccessToken 鐨勮法鑺傜偣鐨勫叡浜�
+ config-storage:
+ type: RedisTemplate # 閲囩敤 RedisTemplate 鎿嶄綔 Redis锛屼細鑷姩浠� Spring 涓幏鍙�
+ key-prefix: wx # Redis Key 鐨勫墠缂�
+ http-client-type: HttpComponents # 閲囩敤 HttpComponents锛圚ttpClient 5锛夎姹傚井淇″叕浼楀彿骞冲彴
+ miniapp: # 灏忕▼搴忛厤缃紙蹇呭~锛夛紝鍙傝 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 鏂囨。
+ # appid: wx62056c0d5e8db250 # 娴嬭瘯鍙凤紙鐗涘笇灏ф彁渚涚殑锛�
+ # secret: 333ae72f41552af1e998fe1f54e1584a
+# appid: wx63c280fe3248a3e7 # wenhualian鐨勬帴鍙f祴璇曞彿
+# secret: 6f270509224a7ae1296bbf1c8cb97aed
+ appid: wxc4598c446f8a9cb3 # 娴嬭瘯鍙凤紙Kongdy 鎻愪緵鐨勶級
+ secret: 4a1a04e07f6a4a0751b39c3064a92c8b
+# appid: wx66186af0759f47c9 # 娴嬭瘯鍙凤紙puhui 鎻愪緵鐨勶級
+# secret: 3218bcbd112cbc614c7264ceb20144ac
+ config-storage:
+ type: RedisTemplate # 閲囩敤 RedisTemplate 鎿嶄綔 Redis锛屼細鑷姩浠� Spring 涓幏鍙�
+ key-prefix: wa # Redis Key 鐨勫墠缂�
+ http-client-type: HttpComponents # 閲囩敤 HttpComponents锛圚ttpClient 5锛夎姹傚井淇″皬绋嬪簭骞冲彴
+
+--- #################### 瓒呯骇绠$悊鍛樼浉鍏抽厤缃� ####################
+
+# 瓒呯骇绠$悊鍛橀厤缃」锛岃缃綋鍓嶉」鐩墍鏈夎嚜瀹氫箟鐨勯厤缃�
+yudao:
+ captcha:
+ enable: false # 鏈湴鐜锛屾殏鏃跺叧闂浘鐗囬獙璇佺爜锛屾柟渚跨櫥褰曠瓑鎺ュ彛鐨勬祴璇曪紱
+ security:
+ mock-enable: true
+ pay:
+ order-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/order # 鏀粯娓犻亾鐨勩�愭敮浠樸�戝洖璋冨湴鍧�
+ refund-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/refund # 鏀粯娓犻亾鐨勩�愰��娆俱�戝洖璋冨湴鍧�
+ transfer-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/transfer # 鏀粯娓犻亾鐨勩�愯浆璐︺�戝洖璋冨湴鍧�
+ access-log: # 璁块棶鏃ュ織鐨勯厤缃」
+ enable: false
+ demo: false # 鍏抽棴婕旂ず妯″紡
+ wxa-code:
+ env-version: develop # 灏忕▼搴忕増鏈�: 姝e紡鐗堜负 "release"锛涗綋楠岀増涓� "trial"锛涘紑鍙戠増涓� "develop"
+ wxa-subscribe-message:
+ miniprogram-state: developer # 璺宠浆灏忕▼搴忕被鍨嬶細寮�鍙戠増涓� 鈥渄eveloper鈥濓紱浣撻獙鐗堜负 鈥渢rial鈥濅负锛涙寮忕増涓� 鈥渇ormal鈥�
+ tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 鍦板浘鐨勫瘑閽� https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
+ storage: # 鏂囦欢涓婁紶閰嶇疆
+ path: /data/javaWork/mom/file # 鏂囦欢瀛樺偍鏍圭洰褰�
+ url-prefix: /admin-api/system/storage-blob # URL璺緞鍓嶇紑
+ domain: http://42.63.70.90:9003 # 鍩熷悕鍓嶇紑锛堟寜瀹為檯鍩熷悕閰嶇疆锛夛紝鍓嶇閰嶇疆濂藉儚涓嶅お瀵圭幇鍦ㄦ槸鐩磋繛鍚庣锛屾墍鏈夎鍚庣绔彛
+ expired: 120 # token杩囨湡鏃堕棿锛堝垎閽燂級
+ use-limit: 10 # token浣跨敤娆℃暟涓婇檺
+ jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWT瀵嗛挜
+ compress: true # 鏄惁鍘嬬缉鍥剧墖
+ need-compress-size: 10MB # 瑙﹀彂鍘嬬缉鐨勬枃浠跺ぇ灏忛槇鍊�
+ compress-quality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
+
+justauth:
+ enabled: true
+ type:
+ DINGTALK: # 閽夐拤
+ client-id: dingvrnreaje3yqvzhxg
+ client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
+ ignore-check-redirect-uri: true
+ WECHAT_ENTERPRISE: # 浼佷笟寰俊
+ client-id: wwd411c69a39ad2e54
+ client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
+ agent-id: 1000004
+ ignore-check-redirect-uri: true
+ # noinspection SpringBootApplicationYaml
+ WECHAT_MINI_PROGRAM: # 寰俊灏忕▼搴�
+ client-id: ${wx.miniapp.appid}
+ client-secret: ${wx.miniapp.secret}
+ ignore-check-redirect-uri: true
+ ignore-check-state: true # 寰俊灏忕▼搴忥紝涓嶄細浣跨敤鍒� state锛屾墍浠ヤ笉杩涜鏍¢獙
+ WECHAT_MP: # 寰俊鍏紬鍙�
+ client-id: ${wx.mp.app-id}
+ client-secret: ${wx.mp.secret}
+ ignore-check-redirect-uri: true
+ ALIPAY: # 鏀粯瀹濆皬绋嬪簭
+ client-id: xx
+ client-secret: xx
+ alipay-public-key: xx
+ ignore-check-redirect-uri: true
+ ignore-check-state: true
+ cache:
+ type: REDIS
+ prefix: 'social_auth_state:' # 缂撳瓨鍓嶇紑锛岀洰鍓嶅彧瀵� Redis 缂撳瓨鐢熸晥锛岄粯璁� JUSTAUTH::STATE::
+ timeout: 24h # 瓒呮椂鏃堕暱锛岀洰鍓嶅彧瀵� Redis 缂撳瓨鐢熸晥锛岄粯璁� 3 鍒嗛挓
--
Gitblit v1.9.3