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