From c9d4ba37c1f681b12e24014013fa9a28734bad42 Mon Sep 17 00:00:00 2001
From: xiaoyi <908147524@qq.com>
Date: 星期四, 20 八月 2026 17:00:02 +0800
Subject: [PATCH] feat 功能修正

---
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/DictTypeConstants.java                                           |   10 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java                       |   79 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryCheckSummaryRespVO.java               |   30 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultDO.java               |   65 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java                                            |   14 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java           |  219 +++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultSaveReqVO.java   |   48 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarService.java                |   29 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSaleCompensationStatusUtils.java                             |   23 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemRespVO.java        |   44 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApi.java                                                      |   26 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java                                             |   21 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationService.java                 |   47 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultRespVO.java      |   79 +
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationPageReqVO.java   |   33 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsService.java                                   |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanSaveReqVO.java          |   39 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/AfterSaleCompensationController.java     |  131 +
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationAuditReqVO.java  |   25 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/dto/MesProWorkOrderCreateReqDTO.java                               |   44 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java                                  |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarServiceImpl.java            |  112 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanServiceImpl.java                    |  112 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanPageReqVO.java          |   27 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastWorkOrderCreateReqVO.java                 |   34 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarRespVO.java     |   70 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java                                                          |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourReqVO.java                           |   24 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java                                                |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java                                  |   38 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                                        |    6 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanService.java                        |   29 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultItemMapper.java            |   27 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java                                            |   45 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryRespDTO.java                                   |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java                                      |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraisePageReqVO.java         |   27 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationSaveReqVO.java   |   48 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java         |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarSaveReqVO.java  |   46 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java                                                         |    3 
 yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java                              |    8 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java                   |  454 ++++++
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/listener/AfterSaleCompensationStatusListener.java |   54 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApiImpl.java                                                      |   55 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java                          |   16 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseItemDO.java                 |   60 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationRespVO.java      |   85 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemSaveReqVO.java     |   51 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenancecalendar/MesProMaintenanceCalendarDO.java              |   91 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java                                            |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarPageReqVO.java  |   35 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java            |   80 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java                                  |   19 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/compensation/AfterSaleCompensationDO.java               |   96 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsServiceImpl.java                               |   83 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/MesProAlternativePlanController.java            |   87 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanRespVO.java             |   60 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourStatisticsController.java                       |   47 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/alternativeplan/MesProAlternativePlanDO.java                      |   83 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/enums/DictTypeConstants.java                                                         |   16 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseItemMapper.java                  |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/alternativeplan/MesProAlternativePlanMapper.java                       |   31 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java                     |   90 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultPageReqVO.java   |   27 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java                                                    |   12 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultMapper.java                |   35 
 yudao-module-hrm/pom.xml                                                                                                                        |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java                                         |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseAuditReqVO.java        |   25 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleCompensationAuditStatusEnum.java                        |   40 
 docs/dv_telemetry_integration.md                                                                                                                |  124 +
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/compensation/AfterSaleCompensationMapper.java                |   30 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourRespVO.java                          |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/MesProMaintenanceCalendarController.java    |   87 +
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationServiceImpl.java             |  251 +++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultItemDO.java           |   60 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/listener/HrmPerformanceAppraiseStatusListener.java       |   54 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java                                          |    9 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryReqDTO.java                                    |   23 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java                      |   24 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenancecalendar/MesProMaintenanceCalendarMapper.java               |   30 
 82 files changed, 4,276 insertions(+), 4 deletions(-)

diff --git a/docs/dv_telemetry_integration.md b/docs/dv_telemetry_integration.md
new file mode 100644
index 0000000..0dcdab0
--- /dev/null
+++ b/docs/dv_telemetry_integration.md
@@ -0,0 +1,124 @@
+# 璁惧鏁伴噰锛堣澶囨暟鎹噰闆嗭級路 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 璺敱 | 璇存槑 |
+|------|------|------|
+| 璁惧鏁伴噰 | 璁惧绠$悊 鈫� 璁惧鏁伴噰 | 瀹炴椂鏁版嵁锛堝悇璁惧鏈�鏂伴仴娴嬶級+ 鍘嗗彶璁板綍涓や釜 Tab + 鎷夊彇鎸夐挳 + 瀹炴椂鑷姩鍒锋柊 |
+| 璁惧鍙拌处 | 璁惧绠$悊 鈫� 璁惧鍙拌处 | 琛ㄥ崟鏂板銆屾暟閲囪澶嘔D銆嶅瓧娈碉紱鏌ョ湅璇︽儏鏂板銆屾暟閲囨暟鎹�峊ab 灞曠ず璇ヨ澶囧疄鏃朵俊鍙� |
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. **鎷夊彇鏁版嵁**锛氶〉闈㈢偣鍑汇�屾媺鍙栨渶鏂版暟鎹�嶁啋 璋� `/mes/dv/telemetry/pull` 鈫� 鍚庣璋冨閮ㄦ暟閲囨帴鍙� `GET /api/jxjs/mesTb/getDeviceDetails`锛堣繎 5 鍒嗛挓璁惧鏁版嵁锛夆啋 閫愭潯鍐欏叆 `mes_dv_telemetry` 鈫� 杩斿洖鏈鏂板鏉℃暟/璁惧鏁�/鎷夊彇鏃堕棿銆�
+2. **瀹炴椂鏌ョ湅**锛氬疄鏃舵暟鎹� Tab 璋� `/mes/dv/telemetry/latest`锛屾寜銆岃澶�+淇″彿銆嶅幓閲嶄繚鐣欐渶鏂颁竴鏉★紱鍙紑鍚�屽疄鏃惰嚜鍔ㄥ埛鏂般�嶆瘡 30 绉掕疆璇竴娆°��
+3. **鍘嗗彶鏌ョ湅**锛氬巻鍙茶褰� Tab 璋� `/mes/dv/telemetry/page` 鍒嗛〉锛屾敮鎸佽澶囧悕绉�/淇″彿鍚嶇О/鏄惁寮傚父/閬ユ祴鏃堕棿鍖洪棿杩囨护銆�
+4. **瀹氭椂鎷夊彇**锛氱郴缁熷唴缃畾鏃朵换鍔°�岃澶囨暟閲囨暟鎹媺鍙栥�嶆瘡 5 鍒嗛挓鑷姩鎷夊彇涓�娆★紝閬垮厤杩� 5 鍒嗛挓绐楀彛鏁版嵁涓㈠け锛堥〉闈㈡棤闇�鎰熺煡锛夈��
+5. **璁惧鍏宠仈**锛氳澶囧彴璐﹁〃鍗曟墜宸ョ淮鎶ゃ�屾暟閲囪澶嘔D銆嶏紙`tb_device_id`锛屽搴斿閮ㄦ暟閲� `tbDeviceId`锛夛紱鍙拌处璇︽儏銆屾暟閲囨暟鎹�峊ab 鐢ㄨ ID 璋� `/latest?tbDeviceId=xx` 灞曠ず璇ヨ澶囧疄鏃朵俊鍙枫�傛湭濉暟閲囪澶嘔D鏃惰 Tab 缃伆銆�
+
+## API
+
+### 1. 鎷夊彇鏁版嵁 `GET /mes/dv/telemetry/pull`
+
+鏃犺姹傚弬鏁般�傛潈闄� `mes:dv-telemetry:query`銆�
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "recordCount": 120,
+    "deviceCount": 8,
+    "pullTime": "2026-08-20 10:00:00"
+  }
+}
+```
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| recordCount | Integer | 鏈鎷夊彇鏂板鏁版嵁鏉℃暟 |
+| deviceCount | Integer | 鏈鎷夊彇娑夊強璁惧鏁� |
+| pullTime | String | 鎷夊彇鏃堕棿 |
+
+> 澶栭儴鎺ュ彛鏈厤缃� `base-url` / 璋冪敤澶辫触 / 杩斿洖寮傚父鏃讹紝杩斿洖閿欒鐮� `鏁伴噰鏁版嵁鎷夊彇澶辫触锛歺xx`锛涙帴鍙f甯镐絾杩� 5 鍒嗛挓鏃犳暟鎹椂 recordCount=0锛堜笉鎶ラ敊锛夈��
+
+### 2. 瀹炴椂鏁版嵁 `GET /mes/dv/telemetry/latest`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| tbDeviceId | String | 鍚� | 鏁伴噰璁惧ID |
+| deviceName | String | 鍚� | 璁惧鍚嶇О锛堟ā绯婏級 |
+
+**鍝嶅簲锛�** `data` 涓烘暟缁勶紝姣忛」涓轰竴鏉°�岃澶�+淇″彿銆嶆渶鏂伴仴娴嬭褰曪紙瀛楁瑙佷笅琛級銆�
+
+### 3. 鍘嗗彶璁板綍 `GET /mes/dv/telemetry/page`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| pageNo / pageSize | Integer | 鏄� | 鍒嗛〉 |
+| tbDeviceId | String | 鍚� | 鏁伴噰璁惧ID |
+| deviceName | String | 鍚� | 璁惧鍚嶇О锛堟ā绯婏級 |
+| paramName | String | 鍚� | 淇″彿鍚嶇О锛堟ā绯婏級 |
+| whetherAnomaly | Boolean | 鍚� | 鏄惁寮傚父锛坱rue/false锛� |
+| shiftName | String | 鍚� | 鐝 |
+| telemetryDataTime | String[] | 鍚� | 閬ユ祴鏃堕棿鍖洪棿锛宍yyyy-MM-dd HH:mm:ss` |
+
+**鍝嶅簲锛�** `data` 涓� `PageResult`锛宍records` 瀛楁鍚屼笅琛ㄣ��
+
+### 4. 璁惧涓嬫媺 `GET /mes/dv/telemetry/device-list`
+
+鏃犲弬鏁般�傝繑鍥為仴娴嬭〃涓凡鍑虹幇鐨勮澶囧幓閲嶅垪琛細`[{ tbDeviceId, deviceName }]`锛屼緵绛涢�変笅鎷変娇鐢ㄣ��
+
+### 閬ユ祴璁板綍瀛楁锛坙atest / page 閫氱敤锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 缂栧彿 |
+| tbDeviceId | String | 鏁伴噰璁惧ID |
+| deviceName | String | 璁惧鍚嶇О |
+| paramName | String | 淇″彿鍚嶇О |
+| paramKeyName | String | 璁惧KEY鍚嶇О |
+| standardValue | String | 鏍囧噯鍊� |
+| timelyValue | String | 淇″彿鍊硷紙瀹炴椂锛� |
+| avgValue | String | 鍧囧�� |
+| maxValue | String | 鏈�澶у�� |
+| minValue | String | 鏈�灏忓�� |
+| whetherAnomaly | Boolean | 鏄惁寮傚父锛坱rue=寮傚父锛� |
+| telemetryDataTime | String | 閬ユ祴鏁版嵁鏃堕棿 |
+| billNo | String | 鍗曟嵁鍙� |
+| shiftName | String | 鐝 |
+| pullTime | String | 鎷夊彇鏃堕棿 |
+| createTime | String | 鍒涘缓鏃堕棿 |
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| deviceName | 瀹炴椂/鍘嗗彶鍒楄〃銆佺瓫閫� | 璁惧鍚嶇О |
+| paramName | 瀹炴椂/鍘嗗彶鍒楄〃 | 淇″彿鍚嶇О |
+| timelyValue | 瀹炴椂/鍘嗗彶鍒楄〃 | 淇″彿鍊硷紙瀹炴椂锛� |
+| whetherAnomaly | 瀹炴椂/鍘嗗彶鍒楄〃銆佸巻鍙茬瓫閫� | 甯冨皵娓叉煋涓� Tag锛氭甯�(缁�)/寮傚父(绾�)锛涚瓫閫夌敤甯冨皵閫夐」 姝e父/寮傚父 |
+| shiftName | 瀹炴椂/鍘嗗彶鍒楄〃 | 鐝 |
+| telemetryDataTime | 瀹炴椂/鍘嗗彶鍒楄〃銆佸巻鍙茬瓫閫� | 閬ユ祴鏃堕棿锛宍YYYY-MM-DD HH:mm:ss` |
+| standardValue / avgValue / maxValue / minValue | 瀹炴椂鍒楄〃 | 鏍囧噯鍊� / 鍧囧�� / 鏈�澶у�� / 鏈�灏忓�� |
+| billNo | 鍘嗗彶鍒楄〃 | 鍗曟嵁鍙� |
+| pullTime | 鍘嗗彶鍒楄〃 | 鎷夊彇鏃堕棿 |
+| 鏈�杩戞媺鍙栨椂闂� | 椤甸潰椤堕儴 | 鎷夊彇鎴愬姛鍚庡洖鏄� pullTime |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鏁版嵁鎷夊彇 | 鎵嬪姩鎸夐挳 + 瀹氭椂浠诲姟锛堟瘡 5 鍒嗛挓锛夊潎鍙Е鍙戯紝鍐欏叆 `mes_dv_telemetry` |
+| 瀹炴椂鏁版嵁 | 鎸夈�宼bDeviceId + paramKeyName銆嶅幓閲嶄繚鐣欐渶鏂帮紝灞曠ず鏈�杩戜竴娆℃媺鍙栫粨鏋� |
+| 鍘嗗彶鏁版嵁 | 姣忔鎷夊彇鍏ㄩ噺鍏ュ簱锛堝惈閲嶅淇″彿锛夛紝鎸夐仴娴嬫椂闂村�掑簭鍒嗛〉 |
+| 璁惧鍏宠仈 | 璁惧鍙拌处 `tb_device_id` 涓庡閮ㄦ暟閲� `tbDeviceId` 瀵瑰簲锛涙湭濉垯鍙拌处璇︽儏銆屾暟閲囨暟鎹�峊ab 绂佺敤 |
+| 鏄惁寮傚父 | 鍚庣 `bit` 瀛楁鏄犲皠 Boolean锛涘墠绔洿鎺ユ寜甯冨皵娓叉煋锛屼笉渚濊禆瀛楀吀 |
+| 瀹氭椂浠诲姟 | 榛樿姣� 5 鍒嗛挓鎵ц锛屽彲鍦ㄣ�岀郴缁熺鐞� 鈫� 瀹氭椂浠诲姟銆嶈皟鏁�/鍋滅敤 |
+
+## 娉ㄦ剰浜嬮」
+
+- 鏁伴噰鏈嶅姟鍦板潃鍦ㄥ悗绔� `yudao.mes.telemetry.base-url` 閰嶇疆锛坄application-local.yaml`锛夛紝鏈厤缃椂 `/pull` 鎶ャ�屾暟閲囨暟鎹媺鍙栧け璐ャ�嶃��
+- 澶栭儴鎺ュ彛浠呬繚鐣欒繎 5 鍒嗛挓鏁版嵁锛岃秴杩囦笉鎷夊嵆涓紱寤鸿淇濇寔瀹氭椂浠诲姟杩愯锛岄〉闈㈡墜鍔ㄦ媺鍙栦粎鍋氬疄鏃跺埛鏂扮敤銆�
+- 鍘嗗彶璁板綍鏁版嵁閲忎細鎸佺画澧為暱锛屾煡璇㈠缓璁甫閬ユ祴鏃堕棿鍖洪棿杩囨护銆�
+- 鏁伴噰閬ユ祴涓哄閮ㄦ暟鎹紝鍙灞曠ず锛屾棤鏂板/淇敼/鍒犻櫎/瀵煎嚭鎿嶄綔锛屼粎涓�涓�屾煡璇€�嶆寜閽潈闄� `mes:dv-telemetry:query`銆�
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/AfterSaleCompensationController.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/AfterSaleCompensationController.java
new file mode 100644
index 0000000..638d7ba
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/AfterSaleCompensationController.java
@@ -0,0 +1,131 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.compensation;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationAuditReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationRespVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.compensation.AfterSaleCompensationDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleCompensationAuditStatusEnum;
+import cn.iocoder.yudao.module.aftersales.service.compensation.AfterSaleCompensationService;
+import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+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.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗�")
+@RestController
+@RequestMapping("/aftersales/compensation")
+@Validated
+public class AfterSaleCompensationController {
+
+    @Resource
+    private AfterSaleCompensationService compensationService;
+
+    @Resource
+    private CrmCustomerApi customerApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璧斾粯/鏈嶅姟琛ュ伩鍗�")
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:create')")
+    public CommonResult<Long> createCompensation(@Valid @RequestBody AfterSaleCompensationSaveReqVO createReqVO) {
+        return success(compensationService.createCompensation(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊璧斾粯/鏈嶅姟琛ュ伩鍗�")
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:update')")
+    public CommonResult<Boolean> updateCompensation(@Valid @RequestBody AfterSaleCompensationSaveReqVO updateReqVO) {
+        compensationService.updateCompensation(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璧斾粯/鏈嶅姟琛ュ伩鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:delete')")
+    public CommonResult<Boolean> deleteCompensation(@RequestParam("id") Long id) {
+        compensationService.deleteCompensation(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璧斾粯/鏈嶅姟琛ュ伩鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:query')")
+    public CommonResult<AfterSaleCompensationRespVO> getCompensation(@RequestParam("id") Long id) {
+        AfterSaleCompensationDO compensation = compensationService.getCompensation(id);
+        return success(compensation != null ? BeanUtils.toBean(compensation, AfterSaleCompensationRespVO.class, vo -> {
+            vo.setAuditStatusName(AfterSaleCompensationAuditStatusEnum.getNameByStatus(compensation.getAuditStatus()));
+        }) : null);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璧斾粯/鏈嶅姟琛ュ伩鍗曞垎椤�")
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:query')")
+    public CommonResult<PageResult<AfterSaleCompensationRespVO>> getCompensationPage(@Valid AfterSaleCompensationPageReqVO pageReqVO) {
+        PageResult<AfterSaleCompensationDO> pageResult = compensationService.getCompensationPage(pageReqVO);
+        List<AfterSaleCompensationRespVO> list = buildCompensationDetailList(pageResult.getList());
+        return success(new PageResult<>(list, pageResult.getTotal()));
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "鎻愪氦璧斾粯/鏈嶅姟琛ュ伩鍗曞鎵�")
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:submit')")
+    public CommonResult<Boolean> submitCompensation(@RequestParam("id") Long id,
+                                                    @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        compensationService.submitCompensation(id, processDefinitionKey, getLoginUserId());
+        return success(true);
+    }
+
+    @PutMapping("/audit")
+    @Operation(summary = "瀹℃壒璧斾粯/鏈嶅姟琛ュ伩鍗曪紙閫氳繃/椹冲洖锛�")
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:audit')")
+    public CommonResult<Boolean> auditCompensation(@Valid @RequestBody AfterSaleCompensationAuditReqVO auditReqVO) {
+        compensationService.auditCompensation(auditReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰緱璧斾粯/鏈嶅姟琛ュ伩鍗曞鎵规祦绋嬪畾涔夊垪琛�")
+    @PreAuthorize("@ss.hasPermission('aftersales:compensation:query')")
+    public CommonResult<List<Map<String, Object>>> getCompensationApproveProcessDefinitionList() {
+        return success(compensationService.getCompensationApproveProcessDefinitionList());
+    }
+
+    // ========== 鍐呴儴鏂规硶锛氭壒閲忔嫾瑁呭叧鑱斿悕绉� ==========
+
+    private List<AfterSaleCompensationRespVO> buildCompensationDetailList(List<AfterSaleCompensationDO> compensationList) {
+        if (CollUtil.isEmpty(compensationList)) {
+            return Collections.emptyList();
+        }
+        // 鎵归噺鍔犺浇瀹㈡埛鍚嶇О
+        Set<Long> customerIds = convertSet(compensationList, AfterSaleCompensationDO::getCustomerId);
+        Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap(customerIds);
+        // 鎷艰鍚嶇О瀛楁
+        return BeanUtils.toBean(compensationList, AfterSaleCompensationRespVO.class, vo -> {
+            CrmCustomerRespDTO customer = customerMap.get(vo.getCustomerId());
+            if (customer != null) vo.setCustomerName(customer.getName());
+            vo.setAuditStatusName(AfterSaleCompensationAuditStatusEnum.getNameByStatus(vo.getAuditStatus()));
+        });
+    }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationAuditReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationAuditReqVO.java
new file mode 100644
index 0000000..1ce26b9
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationAuditReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� 瀹℃壒 Request VO
+ */
+@Schema(description = "鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� - 瀹℃壒 Request VO")
+@Data
+public class AfterSaleCompensationAuditReqVO {
+
+    @Schema(description = "璧斾粯鍗曠紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "璧斾粯鍗曠紪鍙蜂笉鑳戒负绌�")
+    private Long id;
+
+    @Schema(description = "瀹℃壒缁撴灉锛�20 閫氳繃 / 30 椹冲洖锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    @NotNull(message = "瀹℃壒缁撴灉涓嶈兘涓虹┖")
+    private Integer auditStatus;
+
+    @Schema(description = "瀹℃壒澶囨敞", example = "鍚屾剰璧斾粯")
+    private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationPageReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationPageReqVO.java
new file mode 100644
index 0000000..ea19c61
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationPageReqVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� 鍒嗛〉 Request VO
+ */
+@Schema(description = "鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� - 鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AfterSaleCompensationPageReqVO extends PageParam {
+
+    @Schema(description = "璧斾粯鍗曞彿", example = "PS20260820001")
+    private String compensationNo;
+
+    @Schema(description = "璧斾粯绫诲瀷", example = "1")
+    private Integer compensationType;
+
+    @Schema(description = "鍏宠仈鍞悗宸ュ崟缂栧彿", example = "1")
+    private Long ticketId;
+
+    @Schema(description = "瀹㈡埛缂栧彿", example = "1")
+    private Long customerId;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer auditStatus;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationRespVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationRespVO.java
new file mode 100644
index 0000000..83b375d
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationRespVO.java
@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+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;
+
+/**
+ * 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� Response VO
+ */
+@Schema(description = "鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class AfterSaleCompensationRespVO {
+
+    @Schema(description = "璧斾粯鍗曠紪鍙�", example = "1")
+    @ExcelProperty("璧斾粯鍗曠紪鍙�")
+    private Long id;
+
+    @Schema(description = "璧斾粯鍗曞彿", example = "PS20260820001")
+    @ExcelProperty("璧斾粯鍗曞彿")
+    private String compensationNo;
+
+    @Schema(description = "璧斾粯绫诲瀷: 1-鏁呴殰璧斾粯 2-鏈嶅姟琛ュ伩", example = "1")
+    @ExcelProperty(value = "璧斾粯绫诲瀷", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.aftersales.enums.DictTypeConstants.AFTER_SALES_COMPENSATION_TYPE)
+    private Integer compensationType;
+
+    @Schema(description = "鍏宠仈鍞悗宸ュ崟缂栧彿", example = "1")
+    private Long ticketId;
+
+    @Schema(description = "瀹㈡埛缂栧彿", example = "1")
+    private Long customerId;
+
+    @Schema(description = "瀹㈡埛鍚嶇О", example = "鍗庤兘鐢靛姏")
+    private String customerName;
+
+    @Schema(description = "鍏宠仈閿�鍞鍗曠紪鍙�", example = "1")
+    private Long saleOrderId;
+
+    @Schema(description = "璧斾粯閲戦", example = "1000.00")
+    @ExcelProperty("璧斾粯閲戦")
+    private BigDecimal amount;
+
+    @Schema(description = "璧斾粯瀵硅薄", example = "寮犱笁")
+    @ExcelProperty("璧斾粯瀵硅薄")
+    private String payee;
+
+    @Schema(description = "璧斾粯鍘熷洜", example = "浜у搧鏁呴殰瀵艰嚧鎹熷け")
+    @ExcelProperty("璧斾粯鍘熷洜")
+    private String reason;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer auditStatus;
+
+    @Schema(description = "瀹℃壒鐘舵�佸悕绉�", example = "鏈彁浜�")
+    private String auditStatusName;
+
+    @Schema(description = "瀹℃壒娴佺▼瀹炰緥缂栧彿")
+    private String processInstanceId;
+
+    @Schema(description = "瀹℃牳浜�", example = "1")
+    private Long auditUserId;
+
+    @Schema(description = "瀹℃牳鏃堕棿")
+    private LocalDateTime auditTime;
+
+    @Schema(description = "瀹℃牳澶囨敞")
+    private String auditRemark;
+
+    @Schema(description = "鏀粯鐘舵�侊紙璐﹀姟鑱斿姩棰勭暀锛�")
+    private Integer payStatus;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationSaveReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationSaveReqVO.java
new file mode 100644
index 0000000..9c75ec4
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationSaveReqVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� 淇濆瓨 Request VO
+ */
+@Schema(description = "鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� - 淇濆瓨 Request VO")
+@Data
+public class AfterSaleCompensationSaveReqVO {
+
+    @Schema(description = "璧斾粯鍗曠紪鍙�", example = "1")
+    private Long id;
+
+    @Schema(description = "璧斾粯绫诲瀷: 1-鏁呴殰璧斾粯 2-鏈嶅姟琛ュ伩", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "璧斾粯绫诲瀷涓嶈兘涓虹┖")
+    private Integer compensationType;
+
+    @Schema(description = "鍏宠仈鍞悗宸ュ崟缂栧彿", example = "1")
+    private Long ticketId;
+
+    @Schema(description = "瀹㈡埛缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "瀹㈡埛涓嶈兘涓虹┖")
+    private Long customerId;
+
+    @Schema(description = "鍏宠仈閿�鍞鍗曠紪鍙�", example = "1")
+    private Long saleOrderId;
+
+    @Schema(description = "璧斾粯閲戦", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000.00")
+    @NotNull(message = "璧斾粯閲戦涓嶈兘涓虹┖")
+    private BigDecimal amount;
+
+    @Schema(description = "璧斾粯瀵硅薄", example = "寮犱笁")
+    private String payee;
+
+    @Schema(description = "璧斾粯鍘熷洜", requiredMode = Schema.RequiredMode.REQUIRED, example = "浜у搧鏁呴殰瀵艰嚧鎹熷け")
+    @NotEmpty(message = "璧斾粯鍘熷洜涓嶈兘涓虹┖")
+    private String reason;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/compensation/AfterSaleCompensationDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/compensation/AfterSaleCompensationDO.java
new file mode 100644
index 0000000..a1ffb70
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/compensation/AfterSaleCompensationDO.java
@@ -0,0 +1,96 @@
+package cn.iocoder.yudao.module.aftersales.dal.dataobject.compensation;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleCompensationAuditStatusEnum;
+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
+ *
+ * 瀵瑰簲 xlsx 鍞悗 2銆屾晠闅滆禂浠樸�佹湇鍔¤ˉ鍋库�﹀叏娴佺▼杩芥函銆�
+ */
+@TableName("after_sale_compensation")
+@KeySequence("after_sale_compensation_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AfterSaleCompensationDO extends BaseDO {
+
+    /**
+     * 璧斾粯鍗曠紪鍙�
+     */
+    @TableId
+    private Long id;
+    /**
+     * 璧斾粯鍗曞彿
+     */
+    private String compensationNo;
+    /**
+     * 璧斾粯绫诲瀷: 1-鏁呴殰璧斾粯 2-鏈嶅姟琛ュ伩
+     */
+    private Integer compensationType;
+    /**
+     * 鍏宠仈鍞悗宸ュ崟缂栧彿锛屽叧鑱� after_sale_ticket.id
+     */
+    private Long ticketId;
+    /**
+     * 瀹㈡埛缂栧彿
+     */
+    private Long customerId;
+    /**
+     * 鍏宠仈閿�鍞鍗曠紪鍙�
+     */
+    private Long saleOrderId;
+    /**
+     * 璧斾粯閲戦
+     */
+    private BigDecimal amount;
+    /**
+     * 璧斾粯瀵硅薄
+     */
+    private String payee;
+    /**
+     * 璧斾粯鍘熷洜
+     */
+    private String reason;
+    /**
+     * 瀹℃壒鐘舵��
+     *
+     * 鏋氫妇 {@link AfterSaleCompensationAuditStatusEnum}
+     */
+    private Integer auditStatus;
+    /**
+     * 瀹℃壒娴佺▼瀹炰緥缂栧彿
+     */
+    private String processInstanceId;
+    /**
+     * 瀹℃牳浜�
+     */
+    private Long auditUserId;
+    /**
+     * 瀹℃牳鏃堕棿
+     */
+    private LocalDateTime auditTime;
+    /**
+     * 瀹℃牳澶囨敞
+     */
+    private String auditRemark;
+    /**
+     * 鏀粯鐘舵�侊紙璐﹀姟鑱斿姩棰勭暀锛屾帴璐㈠姟鍚庝娇鐢級
+     */
+    private Integer payStatus;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/compensation/AfterSaleCompensationMapper.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/compensation/AfterSaleCompensationMapper.java
new file mode 100644
index 0000000..a6d3f7f
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/compensation/AfterSaleCompensationMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.aftersales.dal.mysql.compensation;
+
+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.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationPageReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.compensation.AfterSaleCompensationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� Mapper
+ */
+@Mapper
+public interface AfterSaleCompensationMapper extends BaseMapperX<AfterSaleCompensationDO> {
+
+    default AfterSaleCompensationDO selectByCompensationNo(String compensationNo) {
+        return selectOne(AfterSaleCompensationDO::getCompensationNo, compensationNo);
+    }
+
+    default PageResult<AfterSaleCompensationDO> selectPage(AfterSaleCompensationPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AfterSaleCompensationDO>()
+                .likeIfPresent(AfterSaleCompensationDO::getCompensationNo, reqVO.getCompensationNo())
+                .eqIfPresent(AfterSaleCompensationDO::getCompensationType, reqVO.getCompensationType())
+                .eqIfPresent(AfterSaleCompensationDO::getTicketId, reqVO.getTicketId())
+                .eqIfPresent(AfterSaleCompensationDO::getCustomerId, reqVO.getCustomerId())
+                .eqIfPresent(AfterSaleCompensationDO::getAuditStatus, reqVO.getAuditStatus())
+                .orderByDesc(AfterSaleCompensationDO::getId));
+    }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleCompensationAuditStatusEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleCompensationAuditStatusEnum.java
new file mode 100644
index 0000000..4b5d2b6
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleCompensationAuditStatusEnum.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗鐨勮禂浠�/琛ュ伩鍗曞鎵圭姸鎬�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum AfterSaleCompensationAuditStatusEnum implements ArrayValuable<Integer> {
+
+    DRAFT(0, "鏈彁浜�"),
+    PROCESS(10, "瀹℃壒涓�"),
+    APPROVE(20, "瀹℃牳閫氳繃"),
+    REJECT(30, "瀹℃牳涓嶉�氳繃"),
+    CANCEL(40, "宸插彇娑�");
+
+    private final Integer status;
+    private final String name;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleCompensationAuditStatusEnum::getStatus).toArray(Integer[]::new);
+
+    public static String getNameByStatus(Integer status) {
+        if (status == null) return null;
+        for (AfterSaleCompensationAuditStatusEnum e : values()) {
+            if (e.getStatus().equals(status)) return e.getName();
+        }
+        return null;
+    }
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/DictTypeConstants.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/DictTypeConstants.java
new file mode 100644
index 0000000..57da9db
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/DictTypeConstants.java
@@ -0,0 +1,10 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+/**
+ * 鍞悗妯″潡瀛楀吀绫诲瀷
+ */
+public interface DictTypeConstants {
+
+    String AFTER_SALES_COMPENSATION_TYPE = "after_sale_compensation_type";
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java
index 1796711..033251f 100644
--- a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java
@@ -35,4 +35,13 @@
     ErrorCode REPAIR_NOT_EXISTS = new ErrorCode(1_022_003_000, "缁翠慨璁板綍涓嶅瓨鍦�");
     ErrorCode REPAIR_UPDATE_FAIL_STATUS = new ErrorCode(1_022_003_001, "鏇存柊缁翠慨鐘舵�佸け璐ワ紝鍘熷洜锛氬凡缁忔槸璇ョ姸鎬�");
 
+    // ========== 璧斾粯/鏈嶅姟琛ュ伩鍗� 1-022-004-000 ==========
+    ErrorCode COMPENSATION_NOT_EXISTS = new ErrorCode(1_022_004_000, "璧斾粯/鏈嶅姟琛ュ伩鍗曚笉瀛樺湪");
+    ErrorCode COMPENSATION_NO_EXISTS = new ErrorCode(1_022_004_001, "鐢熸垚璧斾粯鍗曞彿閲嶅锛岃閲嶈瘯");
+    ErrorCode COMPENSATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_022_004_002, "鎻愪氦璧斾粯鍗曞鎵瑰け璐ワ紝鍘熷洜锛氳禂浠樺崟涓嶆槸鑽夌鐘舵��");
+    ErrorCode COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_022_004_003, "璧斾粯鍗曞鎵规祦绋嬩笉瀛樺湪鎴栨湭婵�娲伙紝璇锋鏌� BPM 閰嶇疆");
+    ErrorCode COMPENSATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_022_004_004, "鏇存柊璧斾粯鍗曞鎵圭姸鎬佸け璐ワ紝鍘熷洜锛氳禂浠樺崟涓嶅浜庡鎵逛腑");
+    ErrorCode COMPENSATION_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_022_004_005, "璧斾粯鍗曚笉鏄崏绋跨姸鎬侊紝鏃犳硶鍒犻櫎");
+    ErrorCode COMPENSATION_TICKET_NOT_EXISTS = new ErrorCode(1_022_004_006, "鍏宠仈鐨勫敭鍚庡伐鍗曚笉瀛樺湪");
+
 }
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationService.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationService.java
new file mode 100644
index 0000000..6d7a05a
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationService.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.aftersales.service.compensation;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationAuditReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.compensation.AfterSaleCompensationDO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� Service 鎺ュ彛
+ */
+public interface AfterSaleCompensationService {
+
+    Long createCompensation(AfterSaleCompensationSaveReqVO createReqVO);
+
+    void updateCompensation(AfterSaleCompensationSaveReqVO updateReqVO);
+
+    void deleteCompensation(Long id);
+
+    AfterSaleCompensationDO getCompensation(Long id);
+
+    PageResult<AfterSaleCompensationDO> getCompensationPage(AfterSaleCompensationPageReqVO pageReqVO);
+
+    /**
+     * 鎻愪氦璧斾粯鍗曞鎵�
+     */
+    void submitCompensation(Long id, String processDefinitionKey, Long userId);
+
+    /**
+     * 瀹℃壒璧斾粯鍗曪紙閫氳繃/椹冲洖锛�
+     */
+    void auditCompensation(AfterSaleCompensationAuditReqVO auditReqVO);
+
+    /**
+     * 鏇存柊璧斾粯鍗曞鎵圭姸鎬侊紙BPM 鐩戝惉鍥炶皟锛�
+     */
+    void updateCompensationAuditStatus(Long id, Integer bpmResult);
+
+    /**
+     * 鑾峰緱璧斾粯鍗曞鎵规祦绋嬪畾涔夊垪琛�
+     */
+    List<Map<String, Object>> getCompensationApproveProcessDefinitionList();
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationServiceImpl.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationServiceImpl.java
new file mode 100644
index 0000000..b888bef
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationServiceImpl.java
@@ -0,0 +1,251 @@
+package cn.iocoder.yudao.module.aftersales.service.compensation;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationAuditReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.compensation.vo.AfterSaleCompensationSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.compensation.AfterSaleCompensationDO;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.compensation.AfterSaleCompensationMapper;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleCompensationAuditStatusEnum;
+import cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.aftersales.util.AfterSaleCompensationStatusUtils.convertBpmResultToAuditStatus;
+
+/**
+ * 鍞悗璧斾粯/鏈嶅姟琛ュ伩鍗� Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class AfterSaleCompensationServiceImpl implements AfterSaleCompensationService {
+
+    private static final Integer AUDIT_DRAFT = 0;
+    private static final Integer AUDIT_PASS = 20;
+
+    /**
+     * BPM 璧斾粯瀹℃壒鍒嗙被缂栫爜
+     */
+    private static final String COMPENSATION_APPROVE_CATEGORY_CODE = "compensation_approve";
+
+    @Resource
+    private AfterSaleCompensationMapper compensationMapper;
+    @Resource
+    private BpmProcessInstanceApi bpmProcessInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createCompensation(AfterSaleCompensationSaveReqVO createReqVO) {
+        String compensationNo = generateCompensationNo();
+        if (compensationMapper.selectByCompensationNo(compensationNo) != null) {
+            throw exception(ErrorCodeConstants.COMPENSATION_NO_EXISTS);
+        }
+        AfterSaleCompensationDO entity = BeanUtils.toBean(createReqVO, AfterSaleCompensationDO.class, req -> req
+                .setCompensationNo(compensationNo)
+                .setAuditStatus(AUDIT_DRAFT));
+        compensationMapper.insert(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateCompensation(AfterSaleCompensationSaveReqVO updateReqVO) {
+        AfterSaleCompensationDO exists = compensationMapper.selectById(updateReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS);
+        }
+        if (ObjUtil.notEqual(exists.getAuditStatus(), AUDIT_DRAFT)) {
+            throw exception(ErrorCodeConstants.COMPENSATION_DELETE_FAIL_NOT_DRAFT);
+        }
+        AfterSaleCompensationDO entity = BeanUtils.toBean(updateReqVO, AfterSaleCompensationDO.class, req -> req
+                .setCompensationNo(exists.getCompensationNo())
+                .setAuditStatus(exists.getAuditStatus()));
+        compensationMapper.updateById(entity);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteCompensation(Long id) {
+        AfterSaleCompensationDO exists = compensationMapper.selectById(id);
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS);
+        }
+        if (ObjUtil.notEqual(exists.getAuditStatus(), AUDIT_DRAFT)) {
+            throw exception(ErrorCodeConstants.COMPENSATION_DELETE_FAIL_NOT_DRAFT);
+        }
+        compensationMapper.deleteById(id);
+    }
+
+    @Override
+    public AfterSaleCompensationDO getCompensation(Long id) {
+        return compensationMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<AfterSaleCompensationDO> getCompensationPage(AfterSaleCompensationPageReqVO pageReqVO) {
+        return compensationMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void submitCompensation(Long id, String processDefinitionKey, Long userId) {
+        // 1. 鏍¢獙璧斾粯鍗曟槸鍚﹀湪鑽夌鐘舵��
+        AfterSaleCompensationDO compensation = compensationMapper.selectById(id);
+        if (compensation == null) {
+            throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS);
+        }
+        if (ObjUtil.notEqual(compensation.getAuditStatus(), AfterSaleCompensationAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(ErrorCodeConstants.COMPENSATION_SUBMIT_FAIL_NOT_DRAFT);
+        }
+
+        // 2. 鏍¢獙璧斾粯瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦紝涓斾紶鍏ョ殑娴佺▼瀹氫箟澶勪簬婵�娲荤姸鎬�
+        validateCompensationApproveCategoryAndProcessDefinition();
+        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+        if (processDefinition == null) {
+            throw exception(ErrorCodeConstants.COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鍒涘缓璧斾粯瀹℃壒娴佺▼瀹炰緥
+        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+
+        // 4. 鏇存柊璧斾粯鍗曟祦绋嬬紪鍙蜂笌瀹℃壒鐘舵��
+        compensationMapper.updateById(new AfterSaleCompensationDO().setId(id).setProcessInstanceId(processInstanceId)
+                .setAuditStatus(AfterSaleCompensationAuditStatusEnum.PROCESS.getStatus()));
+    }
+
+    @Override
+    public void auditCompensation(AfterSaleCompensationAuditReqVO auditReqVO) {
+        AfterSaleCompensationDO exists = compensationMapper.selectById(auditReqVO.getId());
+        if (exists == null) {
+            throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS);
+        }
+        AfterSaleCompensationDO update = new AfterSaleCompensationDO();
+        update.setId(exists.getId());
+        update.setAuditStatus(auditReqVO.getAuditStatus());
+        update.setAuditUserId(getLoginUserId());
+        update.setAuditTime(LocalDateTime.now());
+        if (StrUtil.isNotBlank(auditReqVO.getRemark())) {
+            update.setAuditRemark(auditReqVO.getRemark());
+        }
+        compensationMapper.updateById(update);
+    }
+
+    @Override
+    public void updateCompensationAuditStatus(Long id, Integer bpmResult) {
+        // 1.1 鏍¢獙璧斾粯鍗曟槸鍚﹀瓨鍦�
+        AfterSaleCompensationDO compensation = compensationMapper.selectById(id);
+        if (compensation == null) {
+            throw exception(ErrorCodeConstants.COMPENSATION_NOT_EXISTS);
+        }
+        // 1.2 鍙湁瀹℃壒涓紝鍙互鏇存柊瀹℃壒缁撴灉
+        if (ObjUtil.notEqual(compensation.getAuditStatus(), AfterSaleCompensationAuditStatusEnum.PROCESS.getStatus())) {
+            log.error("[updateCompensationAuditStatus][compensation({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]", compensation.getId(), bpmResult);
+            throw exception(ErrorCodeConstants.COMPENSATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+
+        // 2. 鏇存柊璧斾粯鍗曞鎵圭粨鏋�
+        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+        compensationMapper.updateById(new AfterSaleCompensationDO().setId(id).setAuditStatus(auditStatus));
+    }
+
+    @Override
+    public List<Map<String, Object>> getCompensationApproveProcessDefinitionList() {
+        // 1. 鏍¢獙璧斾粯瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+        validateCompensationApproveCategoryAndProcessDefinition();
+
+        // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(COMPENSATION_APPROVE_CATEGORY_CODE);
+
+        // 3. 鑾峰彇娴佺▼瀹氫箟璇︽儏
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+
+        // 4. 杩囨护锛氬彧淇濈暀婵�娲荤姸鎬侊紝涓旂浉鍚� key 鍙繚鐣欐渶鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (ProcessDefinition pd : processDefinitions) {
+            if (pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+
+        // 5. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            item.put("version", pd.getVersion());
+            result.add(item);
+        }
+        return result;
+    }
+
+    /**
+     * 鏍¢獙 BPM 璧斾粯瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+     */
+    private void validateCompensationApproveCategoryAndProcessDefinition() {
+        // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(COMPENSATION_APPROVE_CATEGORY_CODE));
+        if (!categoryMap.containsKey(COMPENSATION_APPROVE_CATEGORY_CODE)) {
+            throw exception(ErrorCodeConstants.COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 2. 鏍¢獙鍒嗙被涓嬫槸鍚︽湁鍙敤鐨勬祦绋嬪畾涔�
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(COMPENSATION_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            throw exception(ErrorCodeConstants.COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鏍¢獙鏄惁鏈夋縺娲荤姸鎬佺殑娴佺▼瀹氫箟
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+        boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended());
+        if (!hasActiveDefinition) {
+            throw exception(ErrorCodeConstants.COMPENSATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+    }
+
+    private String generateCompensationNo() {
+        return "PS" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+                + RandomUtil.randomNumbers(4);
+    }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/listener/AfterSaleCompensationStatusListener.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/listener/AfterSaleCompensationStatusListener.java
new file mode 100644
index 0000000..dc95be8
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/listener/AfterSaleCompensationStatusListener.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.aftersales.service.compensation.listener;
+
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.compensation.AfterSaleCompensationDO;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.compensation.AfterSaleCompensationMapper;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleCompensationAuditStatusEnum;
+import cn.iocoder.yudao.module.aftersales.service.compensation.AfterSaleCompensationService;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 璧斾粯/鏈嶅姟琛ュ伩鍗曞鎵圭殑缁撴灉鐩戝惉鍣ㄥ疄鐜扮被
+ *
+ * 閫氳繃 businessKey锛堣禂浠樺崟ID锛夊尮閰嶏紝鑰屼笉鏄浐瀹氱殑娴佺▼瀹氫箟 Key锛�
+ * 杩欐牱鍙互鏀寔鍒嗙被涓嬩换鎰� Key 鐨勬祦绋嬫ā鍨嬨��
+ */
+@Component
+@Slf4j
+public class AfterSaleCompensationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+    @Resource
+    private AfterSaleCompensationService compensationService;
+    @Resource
+    private AfterSaleCompensationMapper compensationMapper;
+
+    @Override
+    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+        // 閫氳繃 businessKey 鏌ヨ璧斾粯鍗�
+        Long compensationId;
+        try {
+            compensationId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+        }
+
+        AfterSaleCompensationDO compensation = compensationMapper.selectById(compensationId);
+        if (compensation == null) {
+            return; // 涓嶆槸璧斾粯鍗曪紝蹇界暐
+        }
+
+        // 鍙湁瀹℃壒涓姸鎬佺殑璧斾粯鍗曟墠澶勭悊
+        if (!AfterSaleCompensationAuditStatusEnum.PROCESS.getStatus().equals(compensation.getAuditStatus())) {
+            log.warn("[onApplicationEvent] 璧斾粯鍗�({}) 涓嶅浜庡鎵逛腑鐘舵�侊紝褰撳墠鐘舵��: {}", compensationId, compensation.getAuditStatus());
+            return;
+        }
+
+        log.info("[onApplicationEvent] 璧斾粯鍗�({}) 瀹℃壒鐘舵�佹洿鏂�: {}", compensationId, event.getStatus());
+        // 鏇存柊瀹℃壒鐘舵��
+        compensationService.updateCompensationAuditStatus(compensationId, event.getStatus());
+    }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSaleCompensationStatusUtils.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSaleCompensationStatusUtils.java
new file mode 100644
index 0000000..946b81b
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSaleCompensationStatusUtils.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.aftersales.util;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleCompensationAuditStatusEnum;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+
+/**
+ * 鍞悗璧斾粯/琛ュ伩鍗曟祦绋嬪伐鍏风被
+ */
+public class AfterSaleCompensationStatusUtils {
+
+    /**
+     * BPM 瀹℃壒缁撴灉杞崲
+     */
+    public static Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+        Integer auditStatus = BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult) ? AfterSaleCompensationAuditStatusEnum.APPROVE.getStatus()
+                : BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult) ? AfterSaleCompensationAuditStatusEnum.REJECT.getStatus()
+                : BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult) ? AfterSaleCompensationAuditStatusEnum.CANCEL.getStatus() : null;
+        Assert.notNull(auditStatus, "BPM 瀹℃壒缁撴灉({}) 杞崲澶辫触", bpmResult);
+        return auditStatus;
+    }
+
+}
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java
index 2106626..3b45950 100644
--- a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java
@@ -4,6 +4,7 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastReqVO;
 import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastRespVO;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastWorkOrderCreateReqVO;
 import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
 import cn.iocoder.yudao.module.bi.service.decision.BiForecastService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -46,4 +47,11 @@
         return success(true);
     }
 
+    @PostMapping("/generate-work-order")
+    @Operation(summary = "璐熻嵎棰勬祴鑱斿姩鍒涘缓鐢熶骇宸ュ崟")
+    @PreAuthorize("@ss.hasPermission('bi:decision-forecast:generate-work-order')")
+    public CommonResult<Long> generateWorkOrder(@Valid @RequestBody DecisionForecastWorkOrderCreateReqVO reqVO) {
+        return success(forecastService.createWorkOrderFromForecast(reqVO));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastWorkOrderCreateReqVO.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastWorkOrderCreateReqVO.java
new file mode 100644
index 0000000..7474f04
--- /dev/null
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastWorkOrderCreateReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 璐熻嵎棰勬祴鑱斿姩鍒涘缓宸ュ崟 Request VO")
+@Data
+public class DecisionForecastWorkOrderCreateReqVO {
+
+    @Schema(description = "璐熻嵎棰勬祴鏁版嵁缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "璐熻嵎棰勬祴鏁版嵁缂栧彿涓嶈兘涓虹┖")
+    private Long forecastId;
+
+    @Schema(description = "宸ュ崟鍚嶇О", example = "璐熻嵎棰勬祴-鍙戠數宸ュ崟")
+    private String workOrderName;
+
+    @Schema(description = "宸ュ崟绫诲瀷锛�4鐢靛姏杩愮淮/5鍙戠數/6妫�淇級", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "宸ュ崟绫诲瀷涓嶈兘涓虹┖")
+    private Integer workOrderType;
+
+    @Schema(description = "闇�姹傛棩鏈�", example = "2026-08-20 00:00:00")
+    private LocalDateTime requestDate;
+
+    @Schema(description = "鐢熶骇鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.00")
+    private BigDecimal quantity;
+
+    @Schema(description = "澶囨敞", example = "鐢辫礋鑽烽娴嬭嚜鍔ㄧ敓鎴�")
+    private String remark;
+
+}
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java
index a1f8b40..fae7fbc 100644
--- a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java
@@ -37,4 +37,7 @@
     ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_080_201, "棰勮璁板綍涓嶅瓨鍦�");
     ErrorCode ALERT_RECORD_ALREADY_HANDLED = new ErrorCode(1_080_202, "棰勮璁板綍宸插鐞�");
 
+    // ========== 鍐崇瓥鏀寔 - 璐熻嵎棰勬祴 1_080_221 ~ 1_080_240 ==========
+    ErrorCode BI_FORECAST_NOT_EXISTS = new ErrorCode(1_080_221, "璐熻嵎棰勬祴鏁版嵁涓嶅瓨鍦�");
+
 }
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java
index e494aac..de4abcf 100644
--- a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.bi.service.decision;
 
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastWorkOrderCreateReqVO;
 import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
 
 import java.time.LocalDateTime;
@@ -16,4 +17,12 @@
 
     void generateForecast(String forecastCode, LocalDateTime pointTime);
 
+    /**
+     * 璐熻嵎棰勬祴鑱斿姩鍒涘缓鐢熶骇宸ュ崟锛堣繍缁�/鍙戠數/妫�淇被锛�
+     *
+     * @param reqVO 鍒涘缓璇锋眰
+     * @return 宸ュ崟缂栧彿
+     */
+    Long createWorkOrderFromForecast(DecisionForecastWorkOrderCreateReqVO reqVO);
+
 }
\ No newline at end of file
diff --git a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java
index 5455fb5..6f183d3 100644
--- a/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java
+++ b/yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java
@@ -1,14 +1,22 @@
 package cn.iocoder.yudao.module.bi.service.decision;
 
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastWorkOrderCreateReqVO;
 import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
 import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiForecastDataMapper;
+import cn.iocoder.yudao.module.mes.api.workorder.MesProWorkOrderApi;
+import cn.iocoder.yudao.module.mes.api.workorder.dto.MesProWorkOrderCreateReqDTO;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.bi.enums.ErrorCodeConstants.BI_FORECAST_NOT_EXISTS;
 
 /**
  * 璐熻嵎棰勬祴 Service 瀹炵幇
@@ -21,8 +29,15 @@
 @Service
 public class BiForecastServiceImpl implements BiForecastService {
 
+    /**
+     * 宸ュ崟鏉ユ簮绫诲瀷锛氳礋鑽烽娴嬶紙涓庡瓧鍏� mes_pro_work_order_source_type 鐨� value=4 涓�鑷达級
+     */
+    private static final Integer WORK_ORDER_SOURCE_TYPE_FORECAST = 4;
+
     @Resource
     private BiForecastDataMapper forecastDataMapper;
+    @Resource
+    private MesProWorkOrderApi mesProWorkOrderApi;
 
     @Override
     public List<BiForecastDataDO> getForecastList(String forecastCode, LocalDateTime beginTime, LocalDateTime endTime) {
@@ -48,4 +63,32 @@
         forecastDataMapper.insert(forecast);
     }
 
-}
\ No newline at end of file
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createWorkOrderFromForecast(DecisionForecastWorkOrderCreateReqVO reqVO) {
+        // 1. 鏍¢獙棰勬祴鏁版嵁瀛樺湪
+        BiForecastDataDO forecast = forecastDataMapper.selectById(reqVO.getForecastId());
+        if (forecast == null) {
+            throw exception(BI_FORECAST_NOT_EXISTS);
+        }
+        // 2. 缁勮宸ュ崟 DTO 骞惰皟鐢� MES API 鍒涘缓锛堟棤浜у搧宸ュ崟锛岃烦 BOM锛�
+        MesProWorkOrderCreateReqDTO dto = new MesProWorkOrderCreateReqDTO();
+        dto.setCode(generateWorkOrderCode());
+        dto.setName(reqVO.getWorkOrderName() != null
+                ? reqVO.getWorkOrderName()
+                : ("璐熻嵎棰勬祴-" + forecast.getForecastName()));
+        dto.setType(reqVO.getWorkOrderType());
+        dto.setOrderSourceType(WORK_ORDER_SOURCE_TYPE_FORECAST);
+        dto.setOrderSourceCode(forecast.getForecastCode());
+        dto.setRequestDate(reqVO.getRequestDate());
+        dto.setQuantity(reqVO.getQuantity());
+        dto.setRemark(reqVO.getRemark());
+        return mesProWorkOrderApi.createWorkOrder(dto);
+    }
+
+    private String generateWorkOrderCode() {
+        return "GZ" + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss")
+                + String.format("%03d", (int) (Math.random() * 1000));
+    }
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
index 1ef8b6f..e6a0cf1 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
@@ -57,8 +57,8 @@
             BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class);
             return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null);
         });
-        // 鎺掑簭
-        result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort));
+        // 鎺掑簭锛氭棤 process_definition_info 鐨勬祦绋嬪畾涔� sort 涓� null锛屾斁鏈�鍚庯紝閬垮厤 NPE
+        result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort, Comparator.nullsLast(Comparator.naturalOrder())));
         return result;
     }
 
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 94c57cd..c377dd6 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
@@ -82,6 +82,18 @@
     ErrorCode ATTENDANCE_EXCEPTION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_004_015, "鑰冨嫟寮傚父瀹℃壒娴佺▼涓嶅瓨鍦�");
     ErrorCode ATTENDANCE_CLOCK_ALREADY_CLOCKED = new ErrorCode(1_030_004_016, "宸叉墦杩囧崱锛岃鍕块噸澶嶆墦鍗�");
 
+    // ========== 缁╂晥鑰冩牳 1-030-008-000 ==========
+    ErrorCode PERFORMANCE_APPRAISE_NOT_EXISTS = new ErrorCode(1_030_008_000, "缁╂晥鑰冩牳鏂规涓嶅瓨鍦�");
+    ErrorCode PERFORMANCE_APPRAISE_ITEM_NOT_EXISTS = new ErrorCode(1_030_008_001, "鑰冩牳鎸囨爣涓嶅瓨鍦�");
+    ErrorCode PERFORMANCE_APPRAISE_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_008_002, "缁╂晥鑰冩牳鏂规鏇存柊澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+    ErrorCode PERFORMANCE_APPRAISE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_008_003, "缁╂晥鑰冩牳鏂规鎻愪氦澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+    ErrorCode PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_008_004, "鏇存柊缁╂晥鑰冩牳瀹℃壒鐘舵�佸け璐ワ紝鍘熷洜锛氫笉鏄鎵逛腑鐘舵��");
+    ErrorCode PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_008_005, "缁╂晥鑰冩牳瀹℃壒娴佺▼涓嶅瓨鍦ㄦ垨鏈縺娲伙紝璇锋鏌� BPM 閰嶇疆");
+    ErrorCode PERFORMANCE_APPRAISE_ITEM_EMPTY = new ErrorCode(1_030_008_006, "鑰冩牳鎸囨爣涓嶈兘涓虹┖");
+    ErrorCode PERFORMANCE_APPRAISE_RESULT_NOT_EXISTS = new ErrorCode(1_030_008_007, "鑰冩牳缁撴灉涓嶅瓨鍦�");
+    ErrorCode PERFORMANCE_APPRAISE_RESULT_ITEM_NOT_EXISTS = new ErrorCode(1_030_008_008, "鑰冩牳鎸囨爣璇勫垎涓嶅瓨鍦�");
+    ErrorCode PERFORMANCE_APPRAISE_SCORE_FAIL_STATUS = new ErrorCode(1_030_008_009, "璇勫垎澶辫触锛屽師鍥狅細鏂规涓嶆槸宸插彂甯�/杩涜涓姸鎬�");
+
     // ========== 钖叕绠$悊 1-030-005-000 ==========
     ErrorCode SALARY_STRUCTURE_NOT_EXISTS = new ErrorCode(1_030_005_000, "钖叕缁撴瀯涓嶅瓨鍦�");
     ErrorCode EMPLOYEE_SALARY_NOT_EXISTS = new ErrorCode(1_030_005_001, "鍛樺伐钖叕妗f涓嶅瓨鍦�");
diff --git a/yudao-module-hrm/pom.xml b/yudao-module-hrm/pom.xml
index df07d9c..58cea46 100644
--- a/yudao-module-hrm/pom.xml
+++ b/yudao-module-hrm/pom.xml
@@ -36,6 +36,13 @@
             <version>${revision}</version>
         </dependency>
 
+        <!-- MES 涓氬姟缁勪欢锛氬伐鏃剁粺璁¤法妯″潡鑱氬悎鎶ュ伐鏁版嵁 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-mes-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- 涓氬姟缁勪欢 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java
new file mode 100644
index 0000000..894d5f9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java
@@ -0,0 +1,219 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.performanceappraise.vo.*;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.*;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.performanceappraise.HrmPerformanceAppraiseService;
+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.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 缁╂晥鑰冩牳鏂规")
+@RestController
+@RequestMapping("/hrm/performance-appraise")
+@Validated
+public class HrmPerformanceAppraiseController {
+
+    @Resource
+    private HrmPerformanceAppraiseService appraiseService;
+    @Resource
+    private HrmEmployeeService employeeService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓缁╂晥鑰冩牳鏂规")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:create')")
+    public CommonResult<Long> createAppraise(@Valid @RequestBody HrmPerformanceAppraiseSaveReqVO createReqVO) {
+        return success(appraiseService.createAppraise(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊缁╂晥鑰冩牳鏂规")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:update')")
+    public CommonResult<Boolean> updateAppraise(@Valid @RequestBody HrmPerformanceAppraiseSaveReqVO updateReqVO) {
+        appraiseService.updateAppraise(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎缁╂晥鑰冩牳鏂规")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:delete')")
+    public CommonResult<Boolean> deleteAppraise(@RequestParam("ids") List<Long> ids) {
+        appraiseService.deleteAppraise(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱缁╂晥鑰冩牳鏂规")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:query')")
+    public CommonResult<HrmPerformanceAppraiseRespVO> getAppraise(@RequestParam("id") Long id) {
+        HrmPerformanceAppraiseDO appraise = appraiseService.getAppraise(id);
+        if (appraise == null) {
+            return success(null);
+        }
+        return success(buildAppraiseRespVO(appraise));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱缁╂晥鑰冩牳鏂规鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:query')")
+    public CommonResult<PageResult<HrmPerformanceAppraiseRespVO>> getAppraisePage(@Valid HrmPerformanceAppraisePageReqVO pageReqVO) {
+        PageResult<HrmPerformanceAppraiseDO> pageResult = appraiseService.getAppraisePage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        List<HrmPerformanceAppraiseRespVO> list = pageResult.getList().stream()
+                .map(this::buildAppraiseRespVO).collect(Collectors.toList());
+        return success(new PageResult<>(list, pageResult.getTotal()));
+    }
+
+    // ========== 鑰冩牳鎸囨爣 ==========
+
+    @GetMapping("/item-list")
+    @Operation(summary = "鑾峰緱缁╂晥鑰冩牳鎸囨爣鍒楄〃")
+    @Parameter(name = "appraiseId", description = "鏂规缂栧彿", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:query')")
+    public CommonResult<List<HrmPerformanceAppraiseItemRespVO>> getAppraiseItemList(@RequestParam("appraiseId") Long appraiseId) {
+        return success(BeanUtils.toBean(appraiseService.getAppraiseItemList(appraiseId), HrmPerformanceAppraiseItemRespVO.class));
+    }
+
+    @PostMapping("/item-save")
+    @Operation(summary = "淇濆瓨缁╂晥鑰冩牳鎸囨爣锛堟暣琛ㄦ浛鎹級")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:update')")
+    public CommonResult<Boolean> saveAppraiseItems(@Valid @RequestBody HrmPerformanceAppraiseItemSaveReqVO saveReqVO) {
+        appraiseService.saveAppraiseItems(saveReqVO);
+        return success(true);
+    }
+
+    // ========== 瀹℃壒 ==========
+
+    @PostMapping("/submit")
+    @Operation(summary = "鎻愪氦缁╂晥鑰冩牳鏂规瀹℃壒")
+    @Parameter(name = "id", description = "鏂规缂栧彿", required = true)
+    @Parameter(name = "processDefinitionKey", description = "娴佺▼瀹氫箟Key", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:submit')")
+    public CommonResult<Boolean> submitAppraise(@RequestParam("id") Long id,
+                                                @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        appraiseService.submitAppraise(id, processDefinitionKey, getLoginUserId());
+        return success(true);
+    }
+
+    @PutMapping("/audit")
+    @Operation(summary = "瀹℃壒缁╂晥鑰冩牳鏂规锛堥�氳繃/椹冲洖锛�")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:audit')")
+    public CommonResult<Boolean> auditAppraise(@Valid @RequestBody HrmPerformanceAppraiseAuditReqVO auditReqVO) {
+        appraiseService.auditAppraise(auditReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰緱缁╂晥鑰冩牳鏂规瀹℃壒娴佺▼瀹氫箟鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:query')")
+    public CommonResult<List<Map<String, Object>>> getAppraiseApproveProcessDefinitionList() {
+        return success(appraiseService.getAppraiseApproveProcessDefinitionList());
+    }
+
+    // ========== 璇勫垎/缁撴灉 ==========
+
+    @GetMapping("/result-page")
+    @Operation(summary = "鑾峰緱缁╂晥鑰冩牳缁撴灉鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:query')")
+    public CommonResult<PageResult<HrmPerformanceAppraiseResultRespVO>> getAppraiseResultPage(
+            @Valid HrmPerformanceAppraiseResultPageReqVO pageReqVO) {
+        PageResult<HrmPerformanceAppraiseResultDO> pageResult = appraiseService.getAppraiseResultPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        Map<Long, HrmEmployeeDO> employeeMap = employeeService.getEmployeeList(
+                convertSet(pageResult.getList(), HrmPerformanceAppraiseResultDO::getEmployeeId)).stream()
+                .collect(Collectors.toMap(HrmEmployeeDO::getId, e -> e, (a, b) -> a));
+        List<HrmPerformanceAppraiseResultRespVO> list = pageResult.getList().stream()
+                .map(result -> buildResultRespVO(result, employeeMap.get(result.getEmployeeId())))
+                .collect(Collectors.toList());
+        return success(new PageResult<>(list, pageResult.getTotal()));
+    }
+
+    @PostMapping("/result-save")
+    @Operation(summary = "淇濆瓨鍛樺伐鑰冩牳缁撴灉锛堣嚜鍔ㄨ绠楃患鍚堝緱鍒嗕笌妗g骇锛�")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:update')")
+    public CommonResult<Boolean> saveAppraiseResult(@Valid @RequestBody HrmPerformanceAppraiseResultSaveReqVO saveReqVO) {
+        appraiseService.saveAppraiseResult(saveReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/result")
+    @Operation(summary = "鑾峰緱鍛樺伐鑰冩牳缁撴灉锛堝惈鎸囨爣鏄庣粏锛�")
+    @Parameter(name = "appraiseId", description = "鏂规缂栧彿", required = true, example = "1")
+    @Parameter(name = "employeeId", description = "鍛樺伐缂栧彿", required = true, example = "100")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:query')")
+    public CommonResult<HrmPerformanceAppraiseResultRespVO> getAppraiseResult(
+            @RequestParam("appraiseId") Long appraiseId,
+            @RequestParam("employeeId") Long employeeId) {
+        HrmPerformanceAppraiseResultDO result = appraiseService.getAppraiseResult(appraiseId, employeeId);
+        if (result == null) {
+            return success(null);
+        }
+        return success(buildResultRespVO(result, employeeService.getEmployee(employeeId)));
+    }
+
+    // ========== 鍐呴儴鏂规硶锛氭嫾瑁� VO ==========
+
+    private HrmPerformanceAppraiseRespVO buildAppraiseRespVO(HrmPerformanceAppraiseDO appraise) {
+        HrmPerformanceAppraiseRespVO vo = BeanUtils.toBean(appraise, HrmPerformanceAppraiseRespVO.class);
+        vo.setStatusName(convertAppraiseStatusName(appraise.getStatus()));
+        vo.setAuditStatusName(HrmAuditStatusEnum.getNameByStatus(appraise.getAuditStatus()));
+        vo.setItemCount(appraiseService.getAppraiseItemList(appraise.getId()).size());
+        HrmPerformanceAppraiseResultPageReqVO reqVO = new HrmPerformanceAppraiseResultPageReqVO();
+        reqVO.setAppraiseId(appraise.getId());
+        reqVO.setPageSize(1);
+        reqVO.setPageNo(1);
+        vo.setResultCount(Math.toIntExact(appraiseService.getAppraiseResultPage(reqVO).getTotal()));
+        return vo;
+    }
+
+    private HrmPerformanceAppraiseResultRespVO buildResultRespVO(HrmPerformanceAppraiseResultDO result, HrmEmployeeDO employee) {
+        HrmPerformanceAppraiseResultRespVO vo = BeanUtils.toBean(result, HrmPerformanceAppraiseResultRespVO.class);
+        if (employee != null) {
+            vo.setEmployeeName(employee.getName());
+        }
+        List<HrmPerformanceAppraiseResultItemDO> itemDOs = appraiseService.getAppraiseResultItems(result.getId());
+        if (CollUtil.isNotEmpty(itemDOs)) {
+            vo.setItems(BeanUtils.toBean(itemDOs, HrmPerformanceAppraiseResultRespVO.Item.class));
+        }
+        return vo;
+    }
+
+    private String convertAppraiseStatusName(Integer status) {
+        return switch (status == null ? -1 : status) {
+            case 0 -> "鑽夌";
+            case 10 -> "宸插彂甯�";
+            case 20 -> "杩涜涓�";
+            case 30 -> "宸插畬鎴�";
+            default -> "";
+        };
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseAuditReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseAuditReqVO.java
new file mode 100644
index 0000000..2828995
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseAuditReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 瀹℃壒 Request VO
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳鏂规 - 瀹℃壒 Request VO")
+@Data
+public class HrmPerformanceAppraiseAuditReqVO {
+
+    @Schema(description = "鏂规缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鏂规缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "瀹℃壒缁撴灉锛�20 閫氳繃 / 30 椹冲洖锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    @NotNull(message = "瀹℃壒缁撴灉涓嶈兘涓虹┖")
+    private Integer auditStatus;
+
+    @Schema(description = "瀹℃壒澶囨敞", example = "鍚屾剰鍙戝竷")
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemRespVO.java
new file mode 100644
index 0000000..da75f5e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemRespVO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * HRM 缁╂晥鑰冩牳鎸囨爣 Response VO
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳鎸囨爣 Response VO")
+@Data
+public class HrmPerformanceAppraiseItemRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鑰冩牳鏂规缂栧彿", example = "1")
+    private Long appraiseId;
+
+    @Schema(description = "鎸囨爣鍚嶇О", example = "宸ヤ綔涓氱哗")
+    @ExcelProperty("鎸囨爣鍚嶇О")
+    private String itemName;
+
+    @Schema(description = "鏉冮噸", example = "30.00")
+    @ExcelProperty("鏉冮噸")
+    private BigDecimal weight;
+
+    @Schema(description = "璇勫垎鏍囧噯", example = "鎸夊搴︾洰鏍囪揪鎴愮巼璇勫垎")
+    @ExcelProperty("璇勫垎鏍囧噯")
+    private String scoringStandard;
+
+    @Schema(description = "璇勫垎浜虹被鍨�", example = "1")
+    @ExcelProperty(value = "璇勫垎浜虹被鍨�", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.hrm.enums.DictTypeConstants.HRM_PERFORMANCE_SCORER_TYPE)
+    private Integer scorerType;
+
+    @Schema(description = "鎺掑簭", example = "1")
+    private Integer sort;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemSaveReqVO.java
new file mode 100644
index 0000000..a58fd7b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemSaveReqVO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * HRM 缁╂晥鑰冩牳鎸囨爣 淇濆瓨 Request VO锛堟暣琛ㄦ浛鎹級
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳鎸囨爣 - 淇濆瓨 Request VO锛堟暣琛ㄦ浛鎹級")
+@Data
+public class HrmPerformanceAppraiseItemSaveReqVO {
+
+    @Schema(description = "鑰冩牳鏂规缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鑰冩牳鏂规涓嶈兘涓虹┖")
+    private Long appraiseId;
+
+    @Schema(description = "鑰冩牳鎸囨爣鍒楄〃")
+    @Valid
+    private List<Item> items;
+
+    @Schema(description = "鑰冩牳鎸囨爣")
+    @Data
+    public static class Item {
+
+        @Schema(description = "鎸囨爣缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+        private Long id;
+
+        @Schema(description = "鎸囨爣鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "宸ヤ綔涓氱哗")
+        @NotNull(message = "鎸囨爣鍚嶇О涓嶈兘涓虹┖")
+        private String itemName;
+
+        @Schema(description = "鏉冮噸", requiredMode = Schema.RequiredMode.REQUIRED, example = "30.00")
+        @NotNull(message = "鏉冮噸涓嶈兘涓虹┖")
+        private BigDecimal weight;
+
+        @Schema(description = "璇勫垎鏍囧噯", example = "鎸夊搴︾洰鏍囪揪鎴愮巼璇勫垎")
+        private String scoringStandard;
+
+        @Schema(description = "璇勫垎浜虹被鍨�: 1-鑷瘎 2-涓婄骇璇�", example = "1")
+        private Integer scorerType;
+
+        @Schema(description = "鎺掑簭", example = "1")
+        private Integer sort;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraisePageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraisePageReqVO.java
new file mode 100644
index 0000000..68c214d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraisePageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 鍒嗛〉 Request VO
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳鏂规 - 鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmPerformanceAppraisePageReqVO extends PageParam {
+
+    @Schema(description = "鏂规鍚嶇О", example = "8鏈堢哗鏁堣�冩牳")
+    private String name;
+
+    @Schema(description = "鑰冩牳鍛ㄦ湡绫诲瀷", example = "1")
+    private Integer periodType;
+
+    @Schema(description = "鏂规鐘舵��", example = "0")
+    private Integer status;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java
new file mode 100644
index 0000000..19c04c4
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+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.time.LocalDateTime;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 Response VO
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳鏂规 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmPerformanceAppraiseRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鏂规鍚嶇О", example = "8鏈堢哗鏁堣�冩牳")
+    @ExcelProperty("鏂规鍚嶇О")
+    private String name;
+
+    @Schema(description = "鑰冩牳鍛ㄦ湡绫诲瀷", example = "1")
+    @ExcelProperty(value = "鑰冩牳鍛ㄦ湡绫诲瀷", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.hrm.enums.DictTypeConstants.HRM_PERFORMANCE_PERIOD_TYPE)
+    private Integer periodType;
+
+    @Schema(description = "鑰冩牳鍛ㄦ湡鎻忚堪", example = "2026-08")
+    @ExcelProperty("鑰冩牳鍛ㄦ湡")
+    private String period;
+
+    @Schema(description = "鑰冩牳鑼冨洿绫诲瀷", example = "1")
+    @ExcelProperty(value = "鑰冩牳鑼冨洿", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.hrm.enums.DictTypeConstants.HRM_PERFORMANCE_SCOPE_TYPE)
+    private Integer scopeType;
+
+    @Schema(description = "鏂规鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "鏂规鐘舵�佸悕绉�", example = "鑽夌")
+    private String statusName;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer auditStatus;
+
+    @Schema(description = "瀹℃壒鐘舵�佸悕绉�", example = "鑽夌")
+    private String auditStatusName;
+
+    @Schema(description = "瀹℃壒娴佺▼瀹炰緥缂栧彿")
+    private String processInstanceId;
+
+    @Schema(description = "瀹℃牳浜�", example = "1")
+    private Long auditUserId;
+
+    @Schema(description = "瀹℃牳鏃堕棿")
+    private LocalDateTime auditTime;
+
+    @Schema(description = "瀹℃牳澶囨敞")
+    private String auditRemark;
+
+    @Schema(description = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鑰冩牳鎸囨爣鏁�", example = "5")
+    private Integer itemCount;
+
+    @Schema(description = "宸茶瘎鍒嗕汉鏁�", example = "3")
+    private Integer resultCount;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultPageReqVO.java
new file mode 100644
index 0000000..aac2d7c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * HRM 缁╂晥鑰冩牳缁撴灉 鍒嗛〉 Request VO
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳缁撴灉 - 鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmPerformanceAppraiseResultPageReqVO extends PageParam {
+
+    @Schema(description = "鑰冩牳鏂规缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long appraiseId;
+
+    @Schema(description = "鍛樺伐缂栧彿", example = "100")
+    private Long employeeId;
+
+    @Schema(description = "妗g骇", example = "1")
+    private Integer grade;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultRespVO.java
new file mode 100644
index 0000000..3266aae
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultRespVO.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * HRM 缁╂晥鑰冩牳缁撴灉 Response VO锛堟寜鍛樺伐鑱氬悎锛�
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳缁撴灉 Response VO锛堟寜鍛樺伐鑱氬悎锛�")
+@Data
+public class HrmPerformanceAppraiseResultRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鑰冩牳鏂规缂栧彿", example = "1")
+    private Long appraiseId;
+
+    @Schema(description = "鍛樺伐缂栧彿", example = "100")
+    private Long employeeId;
+
+    @Schema(description = "鍛樺伐鍚嶇О", example = "寮犱笁")
+    @ExcelProperty("鍛樺伐")
+    private String employeeName;
+
+    @Schema(description = "鑷瘎鎬诲垎", example = "90.00")
+    @ExcelProperty("鑷瘎鎬诲垎")
+    private BigDecimal selfScore;
+
+    @Schema(description = "涓婄骇璇勬�诲垎", example = "85.00")
+    @ExcelProperty("涓婄骇璇勬�诲垎")
+    private BigDecimal superiorScore;
+
+    @Schema(description = "缁煎悎寰楀垎", example = "87.50")
+    @ExcelProperty("缁煎悎寰楀垎")
+    private BigDecimal finalScore;
+
+    @Schema(description = "妗g骇", example = "2")
+    @ExcelProperty(value = "妗g骇", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.hrm.enums.DictTypeConstants.HRM_PERFORMANCE_GRADE)
+    private Integer grade;
+
+    @Schema(description = "璇勮")
+    @ExcelProperty("璇勮")
+    private String comment;
+
+    @Schema(description = "鎸囨爣璇勫垎鏄庣粏")
+    private List<Item> items;
+
+    @Schema(description = "鎸囨爣璇勫垎鏄庣粏椤�")
+    @Data
+    public static class Item {
+
+        @Schema(description = "缁撴灉鏄庣粏缂栧彿", example = "1")
+        private Long id;
+
+        @Schema(description = "鑰冩牳鎸囨爣缂栧彿", example = "1")
+        private Long itemId;
+
+        @Schema(description = "鎸囨爣鍚嶇О", example = "宸ヤ綔涓氱哗")
+        private String itemName;
+
+        @Schema(description = "鏉冮噸", example = "30.00")
+        private BigDecimal weight;
+
+        @Schema(description = "鑷瘎寰楀垎", example = "90.00")
+        private BigDecimal selfScore;
+
+        @Schema(description = "涓婄骇璇勫緱鍒�", example = "85.00")
+        private BigDecimal superiorScore;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultSaveReqVO.java
new file mode 100644
index 0000000..dcf2b09
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultSaveReqVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * HRM 缁╂晥鑰冩牳缁撴灉 淇濆瓨 Request VO锛堟寜鍛樺伐閫愭寚鏍囨墦鍒嗭級
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳缁撴灉 - 淇濆瓨 Request VO锛堟寜鍛樺伐閫愭寚鏍囨墦鍒嗭級")
+@Data
+public class HrmPerformanceAppraiseResultSaveReqVO {
+
+    @Schema(description = "鑰冩牳鏂规缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鑰冩牳鏂规涓嶈兘涓虹┖")
+    private Long appraiseId;
+
+    @Schema(description = "鍛樺伐缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "鍛樺伐涓嶈兘涓虹┖")
+    private Long employeeId;
+
+    @Schema(description = "璇勮")
+    private String comment;
+
+    @Schema(description = "鎸囨爣璇勫垎鏄庣粏")
+    @Valid
+    private List<Item> items;
+
+    @Schema(description = "鎸囨爣璇勫垎鏄庣粏椤�")
+    @Data
+    public static class Item {
+
+        @Schema(description = "鑰冩牳鎸囨爣缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "鑰冩牳鎸囨爣涓嶈兘涓虹┖")
+        private Long itemId;
+
+        @Schema(description = "鑷瘎寰楀垎", example = "90.00")
+        private BigDecimal selfScore;
+
+        @Schema(description = "涓婄骇璇勫緱鍒�", example = "85.00")
+        private BigDecimal superiorScore;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java
new file mode 100644
index 0000000..648790c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 淇濆瓨 Request VO
+ */
+@Schema(description = "HRM 缁╂晥鑰冩牳鏂规 - 淇濆瓨 Request VO")
+@Data
+public class HrmPerformanceAppraiseSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鏂规鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "8鏈堢哗鏁堣�冩牳")
+    @NotEmpty(message = "鏂规鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "鑰冩牳鍛ㄦ湡绫诲瀷: 1-鏈堝害 2-瀛e害 3-骞村害", example = "1")
+    private Integer periodType;
+
+    @Schema(description = "鑰冩牳鍛ㄦ湡鎻忚堪, 濡� 2026-08", example = "2026-08")
+    private String period;
+
+    @Schema(description = "鑰冩牳鑼冨洿绫诲瀷: 1-鍏ㄩ儴鍛樺伐 2-鎸囧畾鐝粍", example = "1")
+    private Integer scopeType;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourStatisticsController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourStatisticsController.java
new file mode 100644
index 0000000..bafb59f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourStatisticsController.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.workhour;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourRespVO;
+import cn.iocoder.yudao.module.hrm.service.workhour.HrmWorkHourStatisticsService;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryReqDTO;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 宸ユ椂缁熻")
+@RestController
+@RequestMapping("/hrm/work-hour-statistics")
+@Validated
+public class HrmWorkHourStatisticsController {
+
+    @Resource
+    private HrmWorkHourStatisticsService workHourStatisticsService;
+
+    @GetMapping("/employee-summary")
+    @Operation(summary = "鍛樺伐宸ユ椂缁熻锛堣�冨嫟鍙e緞锛�")
+    @PreAuthorize("@ss.hasPermission('hrm:work-hour-statistics:query')")
+    public CommonResult<List<HrmEmployeeWorkHourRespVO>> getEmployeeWorkHourSummary(
+            HrmEmployeeWorkHourReqVO reqVO) {
+        return success(workHourStatisticsService.getEmployeeWorkHourSummary(reqVO));
+    }
+
+    @GetMapping("/team-summary")
+    @Operation(summary = "鐝粍宸ユ椂缁熻锛堟姤宸ュ彛寰勶級")
+    @PreAuthorize("@ss.hasPermission('hrm:work-hour-statistics:query')")
+    public CommonResult<List<MesProWorkHourSummaryRespDTO>> getTeamWorkHourSummary(
+            MesProWorkHourSummaryReqDTO reqDTO) {
+        return success(workHourStatisticsService.getTeamWorkHourSummary(reqDTO));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourReqVO.java
new file mode 100644
index 0000000..fcd418f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐宸ユ椂缁熻 Request VO")
+@Data
+public class HrmEmployeeWorkHourReqVO {
+
+    @Schema(description = "鍛樺伐缂栧彿")
+    private Long userId;
+
+    @Schema(description = "閮ㄩ棬缂栧彿")
+    private Long deptId;
+
+    @Schema(description = "寮�濮嬫棩鏈�")
+    private LocalDate startDate;
+
+    @Schema(description = "缁撴潫鏃ユ湡")
+    private LocalDate endDate;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourRespVO.java
new file mode 100644
index 0000000..afb6d5e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐宸ユ椂缁熻 Response VO")
+@Data
+public class HrmEmployeeWorkHourRespVO {
+
+    @Schema(description = "鍛樺伐缂栧彿")
+    private Long userId;
+
+    @Schema(description = "鍛樺伐濮撳悕")
+    private String userName;
+
+    @Schema(description = "閮ㄩ棬缂栧彿")
+    private Long deptId;
+
+    @Schema(description = "閮ㄩ棬鍚嶇О")
+    private String deptName;
+
+    @Schema(description = "鍑哄嫟澶╂暟")
+    private Integer workDays;
+
+    @Schema(description = "姝e父宸ユ椂锛堜汉鏃讹級")
+    private BigDecimal workHours;
+
+    @Schema(description = "鍔犵彮宸ユ椂锛堜汉鏃讹級")
+    private BigDecimal overtimeHours;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java
new file mode 100644
index 0000000..b9d2475
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 DO
+ *
+ * 瀵瑰簲 xlsx 浜哄姏 1銆岀哗鏁堣�冩牳鍏ㄦ祦绋嬩笟鍔°��
+ */
+@TableName("hrm_performance_appraise")
+@KeySequence("hrm_performance_appraise_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmPerformanceAppraiseDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鏂规鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鑰冩牳鍛ㄦ湡绫诲瀷: 1-鏈堝害 2-瀛e害 3-骞村害
+     */
+    private Integer periodType;
+
+    /**
+     * 鑰冩牳鍛ㄦ湡鎻忚堪, 濡� 2026-08 / 2026-Q3
+     */
+    private String period;
+
+    /**
+     * 鑰冩牳鑼冨洿绫诲瀷: 1-鍏ㄩ儴鍛樺伐 2-鎸囧畾鐝粍
+     */
+    private Integer scopeType;
+
+    /**
+     * 鏂规鐘舵��: 0-鑽夌 10-宸插彂甯� 20-杩涜涓� 30-宸插畬鎴�
+     */
+    private Integer status;
+
+    /**
+     * 瀹℃壒鐘舵��
+     *
+     * 鏋氫妇 {@link HrmAuditStatusEnum}
+     */
+    private Integer auditStatus;
+
+    /**
+     * 瀹℃壒娴佺▼瀹炰緥缂栧彿
+     */
+    private String processInstanceId;
+
+    /**
+     * 瀹℃牳浜�
+     */
+    private Long auditUserId;
+
+    /**
+     * 瀹℃牳鏃堕棿
+     */
+    private LocalDateTime auditTime;
+
+    /**
+     * 瀹℃牳澶囨敞
+     */
+    private String auditRemark;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseItemDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseItemDO.java
new file mode 100644
index 0000000..9d14ecb
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseItemDO.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * HRM 缁╂晥鑰冩牳鎸囨爣 DO
+ */
+@TableName("hrm_performance_appraise_item")
+@KeySequence("hrm_performance_appraise_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmPerformanceAppraiseItemDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鑰冩牳鏂规缂栧彿
+     */
+    private Long appraiseId;
+
+    /**
+     * 鎸囨爣鍚嶇О
+     */
+    private String itemName;
+
+    /**
+     * 鏉冮噸
+     */
+    private BigDecimal weight;
+
+    /**
+     * 璇勫垎鏍囧噯
+     */
+    private String scoringStandard;
+
+    /**
+     * 璇勫垎浜虹被鍨�: 1-鑷瘎 2-涓婄骇璇�
+     */
+    private Integer scorerType;
+
+    /**
+     * 鎺掑簭
+     */
+    private Integer sort;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultDO.java
new file mode 100644
index 0000000..2902f8a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultDO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * HRM 缁╂晥鑰冩牳缁撴灉 DO锛堟寜鍛樺伐姹囨�伙級
+ */
+@TableName("hrm_performance_appraise_result")
+@KeySequence("hrm_performance_appraise_result_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmPerformanceAppraiseResultDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鑰冩牳鏂规缂栧彿
+     */
+    private Long appraiseId;
+
+    /**
+     * 鍛樺伐缂栧彿
+     */
+    private Long employeeId;
+
+    /**
+     * 鑷瘎鎬诲垎
+     */
+    private BigDecimal selfScore;
+
+    /**
+     * 涓婄骇璇勬�诲垎
+     */
+    private BigDecimal superiorScore;
+
+    /**
+     * 缁煎悎寰楀垎
+     */
+    private BigDecimal finalScore;
+
+    /**
+     * 妗g骇: 1-浼樼 2-鑹ソ 3-鍚堟牸 4-寰呮敼杩�
+     */
+    private Integer grade;
+
+    /**
+     * 璇勮
+     */
+    private String comment;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultItemDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultItemDO.java
new file mode 100644
index 0000000..495ecfa
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultItemDO.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * HRM 缁╂晥鑰冩牳缁撴灉鏄庣粏 DO锛堟寜鍛樺伐脳鎸囨爣锛�
+ */
+@TableName("hrm_performance_appraise_result_item")
+@KeySequence("hrm_performance_appraise_result_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmPerformanceAppraiseResultItemDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鑰冩牳缁撴灉缂栧彿锛屽叧鑱� hrm_performance_appraise_result.id
+     */
+    private Long resultId;
+
+    /**
+     * 鑰冩牳鎸囨爣缂栧彿锛屽叧鑱� hrm_performance_appraise_item.id
+     */
+    private Long itemId;
+
+    /**
+     * 鎸囨爣鍚嶇О锛堝啑浣欙級
+     */
+    private String itemName;
+
+    /**
+     * 鏉冮噸锛堝啑浣欙級
+     */
+    private BigDecimal weight;
+
+    /**
+     * 鑷瘎寰楀垎
+     */
+    private BigDecimal selfScore;
+
+    /**
+     * 涓婄骇璇勫緱鍒�
+     */
+    private BigDecimal superiorScore;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
index 1c0d329..4b7beb7 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
@@ -4,11 +4,14 @@
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.time.LocalDate;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 鑰冨嫟鎵撳崱璁板綍 Mapper
@@ -29,6 +32,22 @@
                 .orderByDesc(HrmAttendanceRecordDO::getDate));
     }
 
+    /**
+     * 鍛樺伐宸ユ椂缁熻锛堣�冨嫟鍙e緞锛夛細鎸夊憳宸ヨ仛鍚堝嚭鍕ゅぉ鏁般�佹甯稿伐鏃朵笌鍔犵彮宸ユ椂
+     */
+    default List<Map<String, Object>> selectWorkHourSummary(HrmEmployeeWorkHourReqVO reqVO) {
+        return selectMaps(new QueryWrapper<HrmAttendanceRecordDO>()
+                .select("user_id",
+                        "COUNT(*) AS work_days",
+                        "SUM(IFNULL(work_hours, 0)) AS work_hours",
+                        "SUM(IFNULL(overtime_hours, 0)) AS overtime_hours")
+                .eq(reqVO.getUserId() != null, "user_id", reqVO.getUserId())
+                .eq(reqVO.getDeptId() != null, "dept_id", reqVO.getDeptId())
+                .ge(reqVO.getStartDate() != null, "date", reqVO.getStartDate())
+                .le(reqVO.getEndDate() != null, "date", reqVO.getEndDate())
+                .groupBy("user_id"));
+    }
+
     default HrmAttendanceRecordDO selectByUserIdAndDate(Long userId, LocalDate date) {
         return selectOne(new LambdaQueryWrapperX<HrmAttendanceRecordDO>()
                 .eq(HrmAttendanceRecordDO::getUserId, userId)
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseItemMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseItemMapper.java
new file mode 100644
index 0000000..50b0144
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseItemMapper.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * HRM 缁╂晥鑰冩牳鎸囨爣 Mapper
+ */
+@Mapper
+public interface HrmPerformanceAppraiseItemMapper extends BaseMapperX<HrmPerformanceAppraiseItemDO> {
+
+    default List<HrmPerformanceAppraiseItemDO> selectListByAppraiseId(Long appraiseId) {
+        return selectList(new LambdaQueryWrapperX<HrmPerformanceAppraiseItemDO>()
+                .eq(HrmPerformanceAppraiseItemDO::getAppraiseId, appraiseId)
+                .orderByAsc(HrmPerformanceAppraiseItemDO::getSort)
+                .orderByAsc(HrmPerformanceAppraiseItemDO::getId));
+    }
+
+    default void deleteByAppraiseId(Long appraiseId) {
+        delete(new LambdaQueryWrapperX<HrmPerformanceAppraiseItemDO>()
+                .eq(HrmPerformanceAppraiseItemDO::getAppraiseId, appraiseId));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java
new file mode 100644
index 0000000..c864979
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise;
+
+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.module.hrm.controller.admin.performanceappraise.vo.HrmPerformanceAppraisePageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 Mapper
+ */
+@Mapper
+public interface HrmPerformanceAppraiseMapper extends BaseMapperX<HrmPerformanceAppraiseDO> {
+
+    default PageResult<HrmPerformanceAppraiseDO> selectPage(HrmPerformanceAppraisePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmPerformanceAppraiseDO>()
+                .likeIfPresent(HrmPerformanceAppraiseDO::getName, reqVO.getName())
+                .eqIfPresent(HrmPerformanceAppraiseDO::getPeriodType, reqVO.getPeriodType())
+                .eqIfPresent(HrmPerformanceAppraiseDO::getStatus, reqVO.getStatus())
+                .orderByDesc(HrmPerformanceAppraiseDO::getId));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultItemMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultItemMapper.java
new file mode 100644
index 0000000..d65f8c3
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultItemMapper.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseResultItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * HRM 缁╂晥鑰冩牳缁撴灉鏄庣粏 Mapper
+ */
+@Mapper
+public interface HrmPerformanceAppraiseResultItemMapper extends BaseMapperX<HrmPerformanceAppraiseResultItemDO> {
+
+    default List<HrmPerformanceAppraiseResultItemDO> selectListByResultId(Long resultId) {
+        return selectList(new LambdaQueryWrapperX<HrmPerformanceAppraiseResultItemDO>()
+                .eq(HrmPerformanceAppraiseResultItemDO::getResultId, resultId)
+                .orderByAsc(HrmPerformanceAppraiseResultItemDO::getId));
+    }
+
+    default void deleteByResultId(Long resultId) {
+        delete(new LambdaQueryWrapperX<HrmPerformanceAppraiseResultItemDO>()
+                .eq(HrmPerformanceAppraiseResultItemDO::getResultId, resultId));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultMapper.java
new file mode 100644
index 0000000..923144b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise;
+
+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.module.hrm.controller.admin.performanceappraise.vo.HrmPerformanceAppraiseResultPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseResultDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * HRM 缁╂晥鑰冩牳缁撴灉 Mapper
+ */
+@Mapper
+public interface HrmPerformanceAppraiseResultMapper extends BaseMapperX<HrmPerformanceAppraiseResultDO> {
+
+    default HrmPerformanceAppraiseResultDO selectByAppraiseIdAndEmployeeId(Long appraiseId, Long employeeId) {
+        return selectOne(new LambdaQueryWrapperX<HrmPerformanceAppraiseResultDO>()
+                .eq(HrmPerformanceAppraiseResultDO::getAppraiseId, appraiseId)
+                .eq(HrmPerformanceAppraiseResultDO::getEmployeeId, employeeId));
+    }
+
+    default PageResult<HrmPerformanceAppraiseResultDO> selectPage(HrmPerformanceAppraiseResultPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmPerformanceAppraiseResultDO>()
+                .eqIfPresent(HrmPerformanceAppraiseResultDO::getAppraiseId, reqVO.getAppraiseId())
+                .eqIfPresent(HrmPerformanceAppraiseResultDO::getEmployeeId, reqVO.getEmployeeId())
+                .eqIfPresent(HrmPerformanceAppraiseResultDO::getGrade, reqVO.getGrade())
+                .orderByAsc(HrmPerformanceAppraiseResultDO::getEmployeeId));
+    }
+
+    default void deleteByAppraiseId(Long appraiseId) {
+        delete(new LambdaQueryWrapperX<HrmPerformanceAppraiseResultDO>()
+                .eq(HrmPerformanceAppraiseResultDO::getAppraiseId, appraiseId));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/enums/DictTypeConstants.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/enums/DictTypeConstants.java
new file mode 100644
index 0000000..4f6e349
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/enums/DictTypeConstants.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.hrm.enums;
+
+/**
+ * HRM 妯″潡瀛楀吀绫诲瀷
+ */
+public interface DictTypeConstants {
+
+    String HRM_PERFORMANCE_PERIOD_TYPE = "hrm_performance_period_type";
+
+    String HRM_PERFORMANCE_SCOPE_TYPE = "hrm_performance_scope_type";
+
+    String HRM_PERFORMANCE_SCORER_TYPE = "hrm_performance_scorer_type";
+
+    String HRM_PERFORMANCE_GRADE = "hrm_performance_grade";
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java
new file mode 100644
index 0000000..5a5ed85
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.hrm.service.performanceappraise;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo.*;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseItemDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseResultDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseResultItemDO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 Service 鎺ュ彛
+ */
+public interface HrmPerformanceAppraiseService {
+
+    Long createAppraise(HrmPerformanceAppraiseSaveReqVO createReqVO);
+
+    void updateAppraise(HrmPerformanceAppraiseSaveReqVO updateReqVO);
+
+    void deleteAppraise(Collection<Long> ids);
+
+    HrmPerformanceAppraiseDO getAppraise(Long id);
+
+    PageResult<HrmPerformanceAppraiseDO> getAppraisePage(HrmPerformanceAppraisePageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱鑰冩牳鎸囨爣鍒楄〃
+     */
+    List<HrmPerformanceAppraiseItemDO> getAppraiseItemList(Long appraiseId);
+
+    /**
+     * 淇濆瓨鑰冩牳鎸囨爣锛堟暣琛ㄦ浛鎹級
+     */
+    void saveAppraiseItems(HrmPerformanceAppraiseItemSaveReqVO saveReqVO);
+
+    /**
+     * 鎻愪氦缁╂晥鑰冩牳鏂规瀹℃壒
+     */
+    void submitAppraise(Long id, String processDefinitionKey, Long userId);
+
+    /**
+     * 瀹℃壒缁╂晥鑰冩牳鏂规锛堥�氳繃/椹冲洖锛宮anual锛�
+     */
+    void auditAppraise(HrmPerformanceAppraiseAuditReqVO auditReqVO);
+
+    /**
+     * 鏇存柊缁╂晥鑰冩牳鏂规瀹℃壒鐘舵�侊紙BPM 鐩戝惉鍥炶皟锛�
+     */
+    void updateAppraiseAuditStatus(Long id, Integer bpmResult);
+
+    /**
+     * 鑾峰緱缁╂晥鑰冩牳鏂规瀹℃壒娴佺▼瀹氫箟鍒楄〃
+     */
+    List<Map<String, Object>> getAppraiseApproveProcessDefinitionList();
+
+    /**
+     * 淇濆瓨鍛樺伐鑰冩牳缁撴灉锛堟寜鍛樺伐閫愭寚鏍囨墦鍒嗭紝鑷姩璁$畻缁煎悎寰楀垎涓庢。绾э級
+     */
+    void saveAppraiseResult(HrmPerformanceAppraiseResultSaveReqVO saveReqVO);
+
+    /**
+     * 鑾峰緱鑰冩牳缁撴灉鍒嗛〉
+     */
+    PageResult<HrmPerformanceAppraiseResultDO> getAppraiseResultPage(HrmPerformanceAppraiseResultPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱鑰冩牳缁撴灉锛堟寜鍛樺伐鑱氬悎锛屽惈鎸囨爣鏄庣粏锛�
+     */
+    HrmPerformanceAppraiseResultDO getAppraiseResult(Long appraiseId, Long employeeId);
+
+    /**
+     * 鑾峰緱鑰冩牳缁撴灉鎸囨爣鏄庣粏
+     */
+    List<HrmPerformanceAppraiseResultItemDO> getAppraiseResultItems(Long resultId);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java
new file mode 100644
index 0000000..cf19574
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java
@@ -0,0 +1,454 @@
+package cn.iocoder.yudao.module.hrm.service.performanceappraise;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo.*;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.*;
+import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.*;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.*;
+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.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.hrm.util.HrmAuditStatusUtils.convertBpmResultToAuditStatus;
+
+/**
+ * HRM 缁╂晥鑰冩牳鏂规 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class HrmPerformanceAppraiseServiceImpl implements HrmPerformanceAppraiseService {
+
+    /**
+     * 鏂规鐘舵�侊細0 鑽夌 / 10 宸插彂甯� / 20 杩涜涓� / 30 宸插畬鎴�
+     */
+    private static final Integer STATUS_DRAFT = 0;
+    private static final Integer STATUS_PUBLISHED = 10;
+    private static final Integer STATUS_PROCESSING = 20;
+
+    /**
+     * 妗g骇锛�1 浼樼(鈮�90) / 2 鑹ソ(鈮�80) / 3 鍚堟牸(鈮�60) / 4 寰呮敼杩�(<60)
+     */
+    private static final BigDecimal GRADE_EXCELLENT = new BigDecimal("90");
+    private static final BigDecimal GRADE_GOOD = new BigDecimal("80");
+    private static final BigDecimal GRADE_PASS = new BigDecimal("60");
+    private static final Integer GRADE_EXCELLENT_CODE = 1;
+    private static final Integer GRADE_GOOD_CODE = 2;
+    private static final Integer GRADE_PASS_CODE = 3;
+    private static final Integer GRADE_IMPROVE_CODE = 4;
+
+    /**
+     * 璇勫垎浜虹被鍨嬶細1 鑷瘎 / 2 涓婄骇璇�
+     */
+    private static final Integer SCORER_SELF = 1;
+    private static final Integer SCORER_SUPERIOR = 2;
+
+    /**
+     * BPM 缁╂晥鑰冩牳瀹℃壒鍒嗙被缂栫爜
+     */
+    private static final String APPRAISE_APPROVE_CATEGORY_CODE = "performance_appraise_approve";
+
+    @Resource
+    private HrmPerformanceAppraiseMapper appraiseMapper;
+    @Resource
+    private HrmPerformanceAppraiseItemMapper itemMapper;
+    @Resource
+    private HrmPerformanceAppraiseResultMapper resultMapper;
+    @Resource
+    private HrmPerformanceAppraiseResultItemMapper resultItemMapper;
+    @Resource
+    private BpmProcessInstanceApi bpmProcessInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createAppraise(HrmPerformanceAppraiseSaveReqVO createReqVO) {
+        HrmPerformanceAppraiseDO appraise = BeanUtils.toBean(createReqVO, HrmPerformanceAppraiseDO.class, req -> req
+                .setStatus(STATUS_DRAFT)
+                .setAuditStatus(HrmAuditStatusEnum.DRAFT.getStatus()));
+        appraiseMapper.insert(appraise);
+        return appraise.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateAppraise(HrmPerformanceAppraiseSaveReqVO updateReqVO) {
+        HrmPerformanceAppraiseDO oldAppraise = validateAppraiseCanModify(updateReqVO.getId());
+        HrmPerformanceAppraiseDO updateObj = BeanUtils.toBean(updateReqVO, HrmPerformanceAppraiseDO.class, req -> req
+                .setStatus(oldAppraise.getStatus())
+                .setAuditStatus(oldAppraise.getAuditStatus())
+                .setProcessInstanceId(oldAppraise.getProcessInstanceId()));
+        appraiseMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteAppraise(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            validateAppraiseCanModify(id);
+            // 鍒犻櫎鎸囨爣涓庣粨鏋�
+            itemMapper.deleteByAppraiseId(id);
+            List<HrmPerformanceAppraiseResultDO> results = resultMapper.selectList(
+                    new LambdaQueryWrapperX<HrmPerformanceAppraiseResultDO>()
+                            .eq(HrmPerformanceAppraiseResultDO::getAppraiseId, id));
+            for (HrmPerformanceAppraiseResultDO result : results) {
+                resultItemMapper.deleteByResultId(result.getId());
+            }
+            resultMapper.deleteByAppraiseId(id);
+            appraiseMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public HrmPerformanceAppraiseDO getAppraise(Long id) {
+        return appraiseMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<HrmPerformanceAppraiseDO> getAppraisePage(HrmPerformanceAppraisePageReqVO pageReqVO) {
+        return appraiseMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<HrmPerformanceAppraiseItemDO> getAppraiseItemList(Long appraiseId) {
+        return itemMapper.selectListByAppraiseId(appraiseId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveAppraiseItems(HrmPerformanceAppraiseItemSaveReqVO saveReqVO) {
+        HrmPerformanceAppraiseDO appraise = validateAppraiseCanModify(saveReqVO.getAppraiseId());
+        // 鏁磋〃鏇挎崲
+        itemMapper.deleteByAppraiseId(appraise.getId());
+        if (CollUtil.isEmpty(saveReqVO.getItems())) {
+            return;
+        }
+        int sort = 0;
+        for (HrmPerformanceAppraiseItemSaveReqVO.Item item : saveReqVO.getItems()) {
+            Integer itemSort = item.getSort() != null ? item.getSort() : sort++;
+            HrmPerformanceAppraiseItemDO itemDO = BeanUtils.toBean(item, HrmPerformanceAppraiseItemDO.class, req -> req
+                    .setAppraiseId(appraise.getId())
+                    .setSort(itemSort));
+            itemMapper.insert(itemDO);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitAppraise(Long id, String processDefinitionKey, Long userId) {
+        // 1. 鏍¢獙鏂规鏄惁鍦ㄨ崏绋跨姸鎬�
+        HrmPerformanceAppraiseDO appraise = appraiseMapper.selectById(id);
+        if (appraise == null) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_NOT_EXISTS);
+        }
+        if (ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_SUBMIT_FAIL_NOT_DRAFT);
+        }
+        if (CollUtil.isEmpty(itemMapper.selectListByAppraiseId(id))) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_ITEM_EMPTY);
+        }
+
+        // 2. 鏍¢獙瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦紝涓斾紶鍏ョ殑娴佺▼瀹氫箟澶勪簬婵�娲荤姸鎬�
+        validateAppraiseApproveCategoryAndProcessDefinition();
+        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+        if (processDefinition == null) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鍒涘缓瀹℃壒娴佺▼瀹炰緥
+        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+
+        // 4. 鏇存柊鏂规娴佺▼缂栧彿涓庡鎵圭姸鎬�
+        appraiseMapper.updateById(new HrmPerformanceAppraiseDO().setId(id).setProcessInstanceId(processInstanceId)
+                .setAuditStatus(HrmAuditStatusEnum.PROCESS.getStatus()));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void auditAppraise(HrmPerformanceAppraiseAuditReqVO auditReqVO) {
+        HrmPerformanceAppraiseDO appraise = validateAppraiseExists(auditReqVO.getId());
+        if (ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+        Integer auditStatus = auditReqVO.getAuditStatus();
+        if (!Objects.equals(auditStatus, HrmAuditStatusEnum.APPROVE.getStatus())
+                && !Objects.equals(auditStatus, HrmAuditStatusEnum.REJECT.getStatus())) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+        HrmPerformanceAppraiseDO update = new HrmPerformanceAppraiseDO()
+                .setId(appraise.getId())
+                .setAuditStatus(auditStatus)
+                .setAuditUserId(getLoginUserId())
+                .setAuditTime(LocalDateTime.now());
+        if (auditReqVO.getRemark() != null) {
+            update.setAuditRemark(auditReqVO.getRemark());
+        }
+        // 瀹℃壒閫氳繃 -> 鏂规鍙戝竷
+        if (Objects.equals(auditStatus, HrmAuditStatusEnum.APPROVE.getStatus())) {
+            update.setStatus(STATUS_PUBLISHED);
+        }
+        appraiseMapper.updateById(update);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateAppraiseAuditStatus(Long id, Integer bpmResult) {
+        // 1.1 鏍¢獙鏂规鏄惁瀛樺湪
+        HrmPerformanceAppraiseDO appraise = appraiseMapper.selectById(id);
+        if (appraise == null) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_NOT_EXISTS);
+        }
+        // 1.2 鍙湁瀹℃壒涓紝鍙互鏇存柊瀹℃壒缁撴灉
+        if (ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+            log.error("[updateAppraiseAuditStatus][appraise({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]", appraise.getId(), bpmResult);
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+
+        // 2. 鏇存柊鏂规瀹℃壒缁撴灉
+        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
+        HrmPerformanceAppraiseDO update = new HrmPerformanceAppraiseDO().setId(id).setAuditStatus(auditStatus);
+        if (Objects.equals(auditStatus, HrmAuditStatusEnum.APPROVE.getStatus())) {
+            update.setStatus(STATUS_PUBLISHED);
+        }
+        appraiseMapper.updateById(update);
+    }
+
+    @Override
+    public List<Map<String, Object>> getAppraiseApproveProcessDefinitionList() {
+        // 1. 鏍¢獙瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+        validateAppraiseApproveCategoryAndProcessDefinition();
+
+        // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(APPRAISE_APPROVE_CATEGORY_CODE);
+
+        // 3. 鑾峰彇娴佺▼瀹氫箟璇︽儏
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+
+        // 4. 杩囨护锛氬彧淇濈暀婵�娲荤姸鎬侊紝涓旂浉鍚� key 鍙繚鐣欐渶鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (ProcessDefinition pd : processDefinitions) {
+            if (pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+
+        // 5. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            item.put("version", pd.getVersion());
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveAppraiseResult(HrmPerformanceAppraiseResultSaveReqVO saveReqVO) {
+        // 1. 鏍¢獙鏂规鐘舵�佸厑璁歌瘎鍒�
+        HrmPerformanceAppraiseDO appraise = appraiseMapper.selectById(saveReqVO.getAppraiseId());
+        if (appraise == null) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_NOT_EXISTS);
+        }
+        if (!Objects.equals(appraise.getStatus(), STATUS_PUBLISHED)
+                && !Objects.equals(appraise.getStatus(), STATUS_PROCESSING)) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_SCORE_FAIL_STATUS);
+        }
+
+        // 2. 鍔犺浇鎸囨爣闆嗗悎锛屾牎楠屾寚鏍囪瘎鍒嗘槸鍚﹀畬鏁�
+        List<HrmPerformanceAppraiseItemDO> items = itemMapper.selectListByAppraiseId(appraise.getId());
+        if (CollUtil.isEmpty(items)) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_ITEM_EMPTY);
+        }
+        Map<Long, HrmPerformanceAppraiseItemDO> itemMap = convertMap(items, HrmPerformanceAppraiseItemDO::getId);
+        if (CollUtil.isEmpty(saveReqVO.getItems()) || saveReqVO.getItems().size() != items.size()) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_RESULT_ITEM_NOT_EXISTS);
+        }
+        for (HrmPerformanceAppraiseResultSaveReqVO.Item score : saveReqVO.getItems()) {
+            if (!itemMap.containsKey(score.getItemId())) {
+                throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_RESULT_ITEM_NOT_EXISTS);
+            }
+        }
+
+        // 3. 鎸夊憳宸� upsert 缁撴灉
+        Long resultId;
+        HrmPerformanceAppraiseResultDO oldResult = resultMapper.selectByAppraiseIdAndEmployeeId(appraise.getId(), saveReqVO.getEmployeeId());
+        if (oldResult != null) {
+            resultId = oldResult.getId();
+        } else {
+            HrmPerformanceAppraiseResultDO newResult = new HrmPerformanceAppraiseResultDO()
+                    .setAppraiseId(appraise.getId())
+                    .setEmployeeId(saveReqVO.getEmployeeId());
+            resultMapper.insert(newResult);
+            resultId = newResult.getId();
+        }
+        resultItemMapper.deleteByResultId(resultId);
+
+        // 4. 璁$畻鍗曟寚鏍囧緱鍒嗐�佹�诲垎涓庢。绾�
+        BigDecimal sumWeight = BigDecimal.ZERO;
+        BigDecimal weightedFinal = BigDecimal.ZERO;
+        BigDecimal weightedSelf = BigDecimal.ZERO;
+        BigDecimal weightedSuperior = BigDecimal.ZERO;
+        for (HrmPerformanceAppraiseResultSaveReqVO.Item score : saveReqVO.getItems()) {
+            HrmPerformanceAppraiseItemDO item = itemMap.get(score.getItemId());
+            BigDecimal weight = item.getWeight() == null ? BigDecimal.ONE : item.getWeight();
+            BigDecimal selfScore = score.getSelfScore() == null ? BigDecimal.ZERO : score.getSelfScore();
+            BigDecimal superiorScore = score.getSuperiorScore() == null ? BigDecimal.ZERO : score.getSuperiorScore();
+            // 鍗曟寚鏍囧垎锛�1 鑷瘎 / 2 涓婄骇璇� / 鍏朵粬 涓よ�呭钩鍧�
+            BigDecimal itemScore;
+            if (Objects.equals(item.getScorerType(), SCORER_SELF)) {
+                itemScore = selfScore;
+            } else if (Objects.equals(item.getScorerType(), SCORER_SUPERIOR)) {
+                itemScore = superiorScore;
+            } else {
+                itemScore = selfScore.add(superiorScore).divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_UP);
+            }
+            sumWeight = sumWeight.add(weight);
+            weightedFinal = weightedFinal.add(weight.multiply(itemScore));
+            weightedSelf = weightedSelf.add(weight.multiply(selfScore));
+            weightedSuperior = weightedSuperior.add(weight.multiply(superiorScore));
+
+            // 淇濆瓨鏄庣粏
+            resultItemMapper.insert(new HrmPerformanceAppraiseResultItemDO()
+                    .setResultId(resultId)
+                    .setItemId(item.getId())
+                    .setItemName(item.getItemName())
+                    .setWeight(weight)
+                    .setSelfScore(selfScore)
+                    .setSuperiorScore(superiorScore));
+        }
+        BigDecimal selfTotal = weightedSelf.divide(sumWeight, 1, RoundingMode.HALF_UP);
+        BigDecimal superiorTotal = weightedSuperior.divide(sumWeight, 1, RoundingMode.HALF_UP);
+        BigDecimal finalScore = weightedFinal.divide(sumWeight, 1, RoundingMode.HALF_UP);
+
+        // 5. 鏇存柊缁撴灉
+        resultMapper.updateById(new HrmPerformanceAppraiseResultDO()
+                .setId(resultId)
+                .setSelfScore(selfTotal)
+                .setSuperiorScore(superiorTotal)
+                .setFinalScore(finalScore)
+                .setGrade(calcGrade(finalScore))
+                .setComment(saveReqVO.getComment()));
+
+        // 6. 鏂规鐘舵�佹祦杞細宸插彂甯� -> 杩涜涓�
+        if (Objects.equals(appraise.getStatus(), STATUS_PUBLISHED)) {
+            appraiseMapper.updateById(new HrmPerformanceAppraiseDO().setId(appraise.getId()).setStatus(STATUS_PROCESSING));
+        }
+    }
+
+    @Override
+    public PageResult<HrmPerformanceAppraiseResultDO> getAppraiseResultPage(HrmPerformanceAppraiseResultPageReqVO pageReqVO) {
+        return resultMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public HrmPerformanceAppraiseResultDO getAppraiseResult(Long appraiseId, Long employeeId) {
+        return resultMapper.selectByAppraiseIdAndEmployeeId(appraiseId, employeeId);
+    }
+
+    @Override
+    public List<HrmPerformanceAppraiseResultItemDO> getAppraiseResultItems(Long resultId) {
+        return resultItemMapper.selectListByResultId(resultId);
+    }
+
+    // ========== 绉佹湁鏂规硶 ==========
+
+    private Integer calcGrade(BigDecimal finalScore) {
+        if (finalScore == null) {
+            return null;
+        }
+        if (finalScore.compareTo(GRADE_EXCELLENT) >= 0) {
+            return GRADE_EXCELLENT_CODE;
+        }
+        if (finalScore.compareTo(GRADE_GOOD) >= 0) {
+            return GRADE_GOOD_CODE;
+        }
+        if (finalScore.compareTo(GRADE_PASS) >= 0) {
+            return GRADE_PASS_CODE;
+        }
+        return GRADE_IMPROVE_CODE;
+    }
+
+    private HrmPerformanceAppraiseDO validateAppraiseExists(Long id) {
+        HrmPerformanceAppraiseDO appraise = appraiseMapper.selectById(id);
+        if (appraise == null) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_NOT_EXISTS);
+        }
+        return appraise;
+    }
+
+    private HrmPerformanceAppraiseDO validateAppraiseCanModify(Long id) {
+        HrmPerformanceAppraiseDO appraise = validateAppraiseExists(id);
+        if (ObjUtil.notEqual(appraise.getStatus(), STATUS_DRAFT)) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_FAIL_NOT_DRAFT);
+        }
+        return appraise;
+    }
+
+    /**
+     * 鏍¢獙 BPM 缁╂晥鑰冩牳瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+     */
+    private void validateAppraiseApproveCategoryAndProcessDefinition() {
+        // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(APPRAISE_APPROVE_CATEGORY_CODE));
+        if (!categoryMap.containsKey(APPRAISE_APPROVE_CATEGORY_CODE)) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 2. 鏍¢獙鍒嗙被涓嬫槸鍚︽湁鍙敤鐨勬祦绋嬪畾涔�
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(APPRAISE_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+
+        // 3. 鏍¢獙鏄惁鏈夋縺娲荤姸鎬佺殑娴佺▼瀹氫箟
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+        boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended());
+        if (!hasActiveDefinition) {
+            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/listener/HrmPerformanceAppraiseStatusListener.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/listener/HrmPerformanceAppraiseStatusListener.java
new file mode 100644
index 0000000..12bc056
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/listener/HrmPerformanceAppraiseStatusListener.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.hrm.service.performanceappraise.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.HrmPerformanceAppraiseMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.service.performanceappraise.HrmPerformanceAppraiseService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 缁╂晥鑰冩牳鏂规瀹℃壒鐨勭粨鏋滅洃鍚櫒瀹炵幇绫�
+ *
+ * 閫氳繃 businessKey锛堟柟妗圛D锛夊尮閰嶏紝鑰屼笉鏄浐瀹氱殑娴佺▼瀹氫箟 Key锛�
+ * 杩欐牱鍙互鏀寔鍒嗙被涓嬩换鎰� Key 鐨勬祦绋嬫ā鍨嬨��
+ */
+@Component
+@Slf4j
+public class HrmPerformanceAppraiseStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+    @Resource
+    private HrmPerformanceAppraiseService appraiseService;
+    @Resource
+    private HrmPerformanceAppraiseMapper appraiseMapper;
+
+    @Override
+    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+        // 閫氳繃 businessKey 鏌ヨ鑰冩牳鏂规
+        Long appraiseId;
+        try {
+            appraiseId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+        }
+
+        HrmPerformanceAppraiseDO appraise = appraiseMapper.selectById(appraiseId);
+        if (appraise == null) {
+            return; // 涓嶆槸缁╂晥鑰冩牳鏂规锛屽拷鐣�
+        }
+
+        // 鍙湁瀹℃壒涓姸鎬佺殑鏂规鎵嶅鐞�
+        if (!HrmAuditStatusEnum.PROCESS.getStatus().equals(appraise.getAuditStatus())) {
+            log.warn("[onApplicationEvent] 鑰冩牳鏂规({}) 涓嶅浜庡鎵逛腑鐘舵�侊紝褰撳墠鐘舵��: {}", appraiseId, appraise.getAuditStatus());
+            return;
+        }
+
+        log.info("[onApplicationEvent] 鑰冩牳鏂规({}) 瀹℃壒鐘舵�佹洿鏂�: {}", appraiseId, event.getStatus());
+        // 鏇存柊瀹℃壒鐘舵��
+        appraiseService.updateAppraiseAuditStatus(appraiseId, event.getStatus());
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsService.java
new file mode 100644
index 0000000..b68feae
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsService.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.service.workhour;
+
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourRespVO;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryReqDTO;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryRespDTO;
+
+import java.util.List;
+
+/**
+ * HRM 宸ユ椂缁熻 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmWorkHourStatisticsService {
+
+    /**
+     * 鍛樺伐宸ユ椂缁熻锛堣�冨嫟鍙e緞锛�
+     */
+    List<HrmEmployeeWorkHourRespVO> getEmployeeWorkHourSummary(HrmEmployeeWorkHourReqVO reqVO);
+
+    /**
+     * 鐝粍宸ユ椂缁熻锛堟姤宸ュ彛寰勶紝璺ㄦā鍧楄仛鍚� MES锛�
+     */
+    List<MesProWorkHourSummaryRespDTO> getTeamWorkHourSummary(MesProWorkHourSummaryReqDTO reqDTO);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsServiceImpl.java
new file mode 100644
index 0000000..1d9a463
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsServiceImpl.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.hrm.service.workhour;
+
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRecordMapper;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.mes.api.task.MesProTaskApi;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryReqDTO;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * HRM 宸ユ椂缁熻 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+public class HrmWorkHourStatisticsServiceImpl implements HrmWorkHourStatisticsService {
+
+    @Resource
+    private HrmAttendanceRecordMapper attendanceRecordMapper;
+    @Resource
+    private HrmEmployeeService employeeService;
+    @Resource
+    private DeptApi deptApi;
+    @Resource
+    private MesProTaskApi mesProTaskApi;
+
+    @Override
+    public List<HrmEmployeeWorkHourRespVO> getEmployeeWorkHourSummary(HrmEmployeeWorkHourReqVO reqVO) {
+        List<Map<String, Object>> rows = attendanceRecordMapper.selectWorkHourSummary(reqVO);
+        List<HrmEmployeeWorkHourRespVO> list = new ArrayList<>(rows.size());
+        for (Map<String, Object> row : rows) {
+            HrmEmployeeWorkHourRespVO vo = new HrmEmployeeWorkHourRespVO();
+            Long userId = row.get("user_id") instanceof Number
+                    ? ((Number) row.get("user_id")).longValue() : null;
+            vo.setUserId(userId);
+            vo.setWorkDays(row.get("work_days") instanceof Number
+                    ? ((Number) row.get("work_days")).intValue() : 0);
+            vo.setWorkHours(toDecimal(row.get("work_hours")));
+            vo.setOvertimeHours(toDecimal(row.get("overtime_hours")));
+            // 濉厖鍛樺伐濮撳悕涓庨儴闂�
+            if (userId != null) {
+                HrmEmployeeDO employee = employeeService.getEmployee(userId);
+                if (employee != null) {
+                    vo.setUserName(employee.getName());
+                    vo.setDeptId(employee.getDeptId());
+                    if (employee.getDeptId() != null) {
+                        DeptRespDTO dept = deptApi.getDept(employee.getDeptId());
+                        if (dept != null) {
+                            vo.setDeptName(dept.getName());
+                        }
+                    }
+                }
+            }
+            list.add(vo);
+        }
+        return list;
+    }
+
+    @Override
+    public List<MesProWorkHourSummaryRespDTO> getTeamWorkHourSummary(MesProWorkHourSummaryReqDTO reqDTO) {
+        return mesProTaskApi.getTeamWorkHourSummary(reqDTO);
+    }
+
+    private BigDecimal toDecimal(Object value) {
+        if (value instanceof Number) {
+            return new BigDecimal(value.toString());
+        }
+        return BigDecimal.ZERO;
+    }
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApi.java
new file mode 100644
index 0000000..57f80a3
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApi.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.mes.api.task;
+
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryReqDTO;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryRespDTO;
+
+import java.util.List;
+
+/**
+ * MES 鐢熶骇浠诲姟 API 鎺ュ彛
+ *
+ * <p>渚� HRM 妯″潡璋冪敤锛屽疄鐜般�屽伐鏃剁粺璁°�嶈法妯″潡鑱斿姩锛氭寜鐝粍鑱氬悎鎶ュ伐浠诲姟鐨勫崰鐢ㄥ伐鏃�
+ * 锛堢敓浜ф椂闀� duration 鍗曚綅涓轰汉鏃ワ紝1 浜烘棩 = 8 浜烘椂锛夈��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesProTaskApi {
+
+    /**
+     * 鐝粍宸ユ椂缁熻锛堟姤宸ュ彛寰勶級
+     *
+     * @param reqDTO 鏃堕棿鑼冨洿
+     * @return 鎸夌彮缁勮仛鍚堢殑鎶ュ伐宸ユ椂鍒楄〃
+     */
+    List<MesProWorkHourSummaryRespDTO> getTeamWorkHourSummary(MesProWorkHourSummaryReqDTO reqDTO);
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryReqDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryReqDTO.java
new file mode 100644
index 0000000..4865608
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryReqDTO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.mes.api.task.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * MES 鐢熶骇浠诲姟鎶ュ伐宸ユ椂缁熻 Request DTO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "RPC 鏈嶅姟 - MES 鐢熶骇浠诲姟鎶ュ伐宸ユ椂缁熻 Request DTO")
+@Data
+public class MesProWorkHourSummaryReqDTO {
+
+    @Schema(description = "寮�濮嬫椂闂�")
+    private LocalDateTime startTime;
+
+    @Schema(description = "缁撴潫鏃堕棿")
+    private LocalDateTime endTime;
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryRespDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryRespDTO.java
new file mode 100644
index 0000000..d4e2cb8
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryRespDTO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.api.task.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * MES 鐢熶骇浠诲姟鎶ュ伐宸ユ椂缁熻 Response DTO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "RPC 鏈嶅姟 - MES 鐢熶骇浠诲姟鎶ュ伐宸ユ椂缁熻 Response DTO")
+@Data
+public class MesProWorkHourSummaryRespDTO {
+
+    @Schema(description = "鐝粍缂栧彿")
+    private Long teamId;
+
+    @Schema(description = "鐝粍鍚嶇О")
+    private String teamName;
+
+    @Schema(description = "浠诲姟鏁�")
+    private Integer taskCount;
+
+    @Schema(description = "鎬荤敓浜ф椂闀匡紙浜烘棩锛�")
+    private Integer totalDuration;
+
+    @Schema(description = "鎬诲伐鏃讹紙浜烘椂 = 浜烘棩 脳 8锛�")
+    private BigDecimal workHours;
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java
index 7534a17..c60d004 100644
--- a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java
@@ -1,10 +1,14 @@
 package cn.iocoder.yudao.module.mes.api.workorder;
 
+import cn.iocoder.yudao.module.mes.api.workorder.dto.MesProWorkOrderCreateReqDTO;
+
 /**
  * MES 鐢熶骇宸ュ崟 API 鎺ュ彛
  *
  * <p>渚� CRM 妯″潡璋冪敤锛屽疄鐜板悎鍚屾寚浠ゃ�屼笅鍙戙�嶇湡瀹炶法妯″潡鑱斿姩锛氭墽琛屾寚浠ゆ椂纭鐩爣宸ュ崟銆�
  * 鍙栨秷鎸囦护鏃跺彇娑堢洰鏍囧伐鍗曪紝浣� CRM 鍚堝悓鎸囦护涓嶅啀鍙槸璁板綍涓庣姸鎬佹祦杞��
+ *
+ * <p>鍚屾椂渚� BI 鍐崇瓥鏀寔妯″潡璋冪敤锛屽疄鐜般�岃礋鑽烽娴嬨�嶈仈鍔ㄥ垱寤鸿繍缁�/鍙戠數/妫�淇被宸ュ崟銆�
  *
  * @author 瓒呯骇绠$悊鍛�
  */
@@ -28,4 +32,14 @@
      */
     void cancelWorkOrder(Long id);
 
+    /**
+     * 鍒涘缓宸ュ崟锛堣崏绋跨姸鎬侊級
+     *
+     * <p>鐢ㄤ簬鍐崇瓥鏀寔銆岃礋鑽烽娴嬨�嶈仈鍔ㄥ垱寤鸿繍缁�/鍙戠數/妫�淇被鐢熶骇宸ュ崟銆�
+     *
+     * @param reqDTO 鍒涘缓璇锋眰
+     * @return 宸ュ崟缂栧彿
+     */
+    Long createWorkOrder(MesProWorkOrderCreateReqDTO reqDTO);
+
 }
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/dto/MesProWorkOrderCreateReqDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/dto/MesProWorkOrderCreateReqDTO.java
new file mode 100644
index 0000000..5626590
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/dto/MesProWorkOrderCreateReqDTO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.mes.api.workorder.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * MES 鐢熶骇宸ュ崟鍒涘缓 Request DTO
+ *
+ * <p>渚� BI 鍐崇瓥鏀寔妯″潡璋冪敤锛屽疄鐜般�岃礋鑽烽娴嬨�嶈仈鍔ㄥ垱寤鸿繍缁�/鍙戠數/妫�淇被宸ュ崟銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "RPC 鏈嶅姟 - MES 鐢熶骇宸ュ崟鍒涘缓 Request DTO")
+@Data
+public class MesProWorkOrderCreateReqDTO {
+
+    @Schema(description = "宸ュ崟缂栫爜", example = "WO-001")
+    private String code;
+
+    @Schema(description = "宸ュ崟鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "璐熻嵎棰勬祴-鍙戠數宸ュ崟")
+    private String name;
+
+    @Schema(description = "宸ュ崟绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
+    private Integer type;
+
+    @Schema(description = "鏉ユ簮绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
+    private Integer orderSourceType;
+
+    @Schema(description = "鏉ユ簮鍗曟嵁缂栧彿", example = "load_forecast_daily")
+    private String orderSourceCode;
+
+    @Schema(description = "闇�姹傛棩鏈�", example = "2026-08-20 00:00:00")
+    private LocalDateTime requestDate;
+
+    @Schema(description = "鐢熶骇鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.00")
+    private BigDecimal quantity;
+
+    @Schema(description = "澶囨敞", example = "鐢辫礋鑽烽娴嬭嚜鍔ㄧ敓鎴�")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApiImpl.java
new file mode 100644
index 0000000..c3559fb
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApiImpl.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.mes.api.task;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryReqDTO;
+import cn.iocoder.yudao.module.mes.api.task.dto.MesProWorkHourSummaryRespDTO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MES 鐢熶骇浠诲姟 API 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesProTaskApiImpl implements MesProTaskApi {
+
+    @Resource
+    private MesProTaskMapper taskMapper;
+
+    @Override
+    public List<MesProWorkHourSummaryRespDTO> getTeamWorkHourSummary(MesProWorkHourSummaryReqDTO reqDTO) {
+        List<Map<String, Object>> rows = taskMapper.selectWorkHourSummary(
+                reqDTO == null ? null : reqDTO.getStartTime(),
+                reqDTO == null ? null : reqDTO.getEndTime());
+        List<MesProWorkHourSummaryRespDTO> list = new ArrayList<>(rows.size());
+        for (Map<String, Object> row : rows) {
+            MesProWorkHourSummaryRespDTO dto = new MesProWorkHourSummaryRespDTO();
+            dto.setTeamId(numberToLong(row.get("team_id")));
+            dto.setTeamName(StrUtil.toStringOrNull(row.get("team_name")));
+            dto.setTaskCount(numberToInt(row.get("task_count")));
+            dto.setTotalDuration(numberToInt(row.get("total_duration")));
+            int totalDuration = dto.getTotalDuration() == null ? 0 : dto.getTotalDuration();
+            dto.setWorkHours(BigDecimal.valueOf(totalDuration * 8L));
+            list.add(dto);
+        }
+        return list;
+    }
+
+    private Long numberToLong(Object value) {
+        return value instanceof Number ? ((Number) value).longValue() : null;
+    }
+
+    private Integer numberToInt(Object value) {
+        return value instanceof Number ? ((Number) value).intValue() : null;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java
index 559ef11..863bbc0 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java
@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.module.mes.api.workorder;
 
+import cn.iocoder.yudao.module.mes.api.workorder.dto.MesProWorkOrderCreateReqDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -27,4 +30,10 @@
         workOrderService.cancelWorkOrder(id);
     }
 
+    @Override
+    public Long createWorkOrder(MesProWorkOrderCreateReqDTO reqDTO) {
+        MesProWorkOrderSaveReqVO workOrderReq = BeanUtils.toBean(reqDTO, MesProWorkOrderSaveReqVO.class);
+        return workOrderService.createWorkOrder(workOrderReq);
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java
index 05fa244..5e3670b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java
@@ -3,6 +3,7 @@
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryCheckSummaryRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryDeviceRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryLatestReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPageReqVO;
@@ -63,4 +64,19 @@
         return success(telemetryService.getDeviceList());
     }
 
+    @GetMapping("/check")
+    @Operation(summary = "渚涚數鍙傛暟姣斿鏍¢獙鏄庣粏锛堝悇璁惧鏈�鏂板弬鏁� vs 鏍囧噯鍊硷級")
+    @PreAuthorize("@ss.hasPermission('mes:dv-telemetry:query')")
+    public CommonResult<List<MesDvTelemetryRespVO>> getCheckList(MesDvTelemetryLatestReqVO reqVO) {
+        List<MesDvTelemetryDO> list = telemetryService.getLatestTelemetry(reqVO);
+        return success(BeanUtils.toBean(list, MesDvTelemetryRespVO.class));
+    }
+
+    @GetMapping("/check-summary")
+    @Operation(summary = "渚涚數鍙傛暟姣斿鏍¢獙缁熻锛堟寜璁惧鑱氬悎寮傚父鐜囷級")
+    @PreAuthorize("@ss.hasPermission('mes:dv-telemetry:query')")
+    public CommonResult<List<MesDvTelemetryCheckSummaryRespVO>> getCheckSummary(MesDvTelemetryLatestReqVO reqVO) {
+        return success(telemetryService.getCheckSummary(reqVO));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryCheckSummaryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryCheckSummaryRespVO.java
new file mode 100644
index 0000000..4aaceb4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryCheckSummaryRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鏁伴噰姣斿鏍¢獙缁熻 Response VO")
+@Data
+public class MesDvTelemetryCheckSummaryRespVO {
+
+    @Schema(description = "鏁伴噰璁惧ID")
+    private String tbDeviceId;
+
+    @Schema(description = "璁惧鍚嶇О")
+    private String deviceName;
+
+    @Schema(description = "鍙傛暟鎬绘暟")
+    private Integer totalCount;
+
+    @Schema(description = "姝e父鍙傛暟鏁�")
+    private Integer normalCount;
+
+    @Schema(description = "寮傚父鍙傛暟鏁�")
+    private Integer anomalyCount;
+
+    @Schema(description = "寮傚父鐜囷紙%锛�")
+    private BigDecimal anomalyRate;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/MesProAlternativePlanController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/MesProAlternativePlanController.java
new file mode 100644
index 0000000..66288e4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/MesProAlternativePlanController.java
@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.alternativeplan.MesProAlternativePlanDO;
+import cn.iocoder.yudao.module.mes.service.pro.alternativeplan.MesProAlternativePlanService;
+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.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 浣滀笟鏇夸唬鏂规")
+@RestController
+@RequestMapping("/mes/pro/alternative-plan")
+@Validated
+public class MesProAlternativePlanController {
+
+    @Resource
+    private MesProAlternativePlanService planService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓浣滀笟鏇夸唬鏂规")
+    @PreAuthorize("@ss.hasPermission('mes:pro-alternative-plan:create')")
+    public CommonResult<Long> createPlan(@Valid @RequestBody MesProAlternativePlanSaveReqVO createReqVO) {
+        return success(planService.createPlan(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊浣滀笟鏇夸唬鏂规")
+    @PreAuthorize("@ss.hasPermission('mes:pro-alternative-plan:update')")
+    public CommonResult<Boolean> updatePlan(@Valid @RequestBody MesProAlternativePlanSaveReqVO updateReqVO) {
+        planService.updatePlan(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎浣滀笟鏇夸唬鏂规")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-alternative-plan:delete')")
+    public CommonResult<Boolean> deletePlan(@RequestParam("ids") List<Long> ids) {
+        planService.deletePlan(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱浣滀笟鏇夸唬鏂规")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pro-alternative-plan:query')")
+    public CommonResult<MesProAlternativePlanRespVO> getPlan(@RequestParam("id") Long id) {
+        MesProAlternativePlanDO plan = planService.getPlan(id);
+        return success(plan != null ? BeanUtils.toBean(plan, MesProAlternativePlanRespVO.class) : null);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱浣滀笟鏇夸唬鏂规鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pro-alternative-plan:query')")
+    public CommonResult<PageResult<MesProAlternativePlanRespVO>> getPlanPage(@Valid MesProAlternativePlanPageReqVO pageReqVO) {
+        PageResult<MesProAlternativePlanDO> pageResult = planService.getPlanPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        return success(BeanUtils.toBean(pageResult, MesProAlternativePlanRespVO.class));
+    }
+
+    @PutMapping("/publish")
+    @Operation(summary = "鍙戝竷浣滀笟鏇夸唬鏂规")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-alternative-plan:update')")
+    public CommonResult<Boolean> publishPlan(@RequestParam("id") Long id) {
+        planService.publishPlan(id);
+        return success(true);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanPageReqVO.java
new file mode 100644
index 0000000..5a79907
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 浣滀笟鏇夸唬鏂规鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesProAlternativePlanPageReqVO extends PageParam {
+
+    @Schema(description = "鏂规缂栫爜", example = "TD20260820001")
+    private String code;
+
+    @Schema(description = "鏂规鍚嶇О", example = "绾胯矾宸℃鏇夸唬鏂规")
+    private String name;
+
+    @Schema(description = "涓诲伐搴忕紪鍙�", example = "100")
+    private Long mainProcessId;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸插彂甯冿級", example = "0")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanRespVO.java
new file mode 100644
index 0000000..b1c050f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanRespVO.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo;
+
+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.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 浣滀笟鏇夸唬鏂规 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesProAlternativePlanRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鏂规缂栫爜", example = "TD20260820001")
+    @ExcelProperty("鏂规缂栫爜")
+    private String code;
+
+    @Schema(description = "鏂规鍚嶇О", example = "绾胯矾宸℃鏇夸唬鏂规")
+    @ExcelProperty("鏂规鍚嶇О")
+    private String name;
+
+    @Schema(description = "涓诲伐搴忕紪鍙�", example = "100")
+    private Long mainProcessId;
+
+    @Schema(description = "涓诲伐搴忓悕绉�", example = "绾胯矾宸℃")
+    @ExcelProperty("涓诲伐搴�")
+    private String mainProcessName;
+
+    @Schema(description = "鏇夸唬宸ュ簭缂栧彿", example = "101")
+    private Long altProcessId;
+
+    @Schema(description = "鏇夸唬宸ュ簭鍚嶇О", example = "鏃犱汉鏈哄贰妫�")
+    @ExcelProperty("鏇夸唬宸ュ簭")
+    private String altProcessName;
+
+    @Schema(description = "鏇夸唬鏉′欢", example = "璁惧鏁呴殰/璐熻嵎鍙楅檺/浜哄憳涓嶈冻")
+    @ExcelProperty("鏇夸唬鏉′欢")
+    private String altCondition;
+
+    @Schema(description = "閫傜敤鑼冨洿", example = "110kV 绾胯矾")
+    @ExcelProperty("閫傜敤鑼冨洿")
+    private String scope;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸插彂甯冿級", example = "0")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanSaveReqVO.java
new file mode 100644
index 0000000..babc2da
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanSaveReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 浣滀笟鏇夸唬鏂规鏂板/淇敼 Request VO")
+@Data
+public class MesProAlternativePlanSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鏂规鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "绾胯矾宸℃鏇夸唬鏂规")
+    @NotEmpty(message = "鏂规鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "涓诲伐搴忕紪鍙�", example = "100")
+    private Long mainProcessId;
+
+    @Schema(description = "涓诲伐搴忓悕绉�", example = "绾胯矾宸℃")
+    private String mainProcessName;
+
+    @Schema(description = "鏇夸唬宸ュ簭缂栧彿", example = "101")
+    private Long altProcessId;
+
+    @Schema(description = "鏇夸唬宸ュ簭鍚嶇О", example = "鏃犱汉鏈哄贰妫�")
+    private String altProcessName;
+
+    @Schema(description = "鏇夸唬鏉′欢", example = "璁惧鏁呴殰/璐熻嵎鍙楅檺/浜哄憳涓嶈冻")
+    private String altCondition;
+
+    @Schema(description = "閫傜敤鑼冨洿", example = "110kV 绾胯矾")
+    private String scope;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/MesProMaintenanceCalendarController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/MesProMaintenanceCalendarController.java
new file mode 100644
index 0000000..d975a42
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/MesProMaintenanceCalendarController.java
@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenancecalendar.MesProMaintenanceCalendarDO;
+import cn.iocoder.yudao.module.mes.service.pro.maintenancecalendar.MesProMaintenanceCalendarService;
+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.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 鐢靛姏杩愮淮鏃ュ巻")
+@RestController
+@RequestMapping("/mes/pro/maintenance-calendar")
+@Validated
+public class MesProMaintenanceCalendarController {
+
+    @Resource
+    private MesProMaintenanceCalendarService calendarService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鐢靛姏杩愮淮鏃ュ巻")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-calendar:create')")
+    public CommonResult<Long> createCalendar(@Valid @RequestBody MesProMaintenanceCalendarSaveReqVO createReqVO) {
+        return success(calendarService.createCalendar(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鐢靛姏杩愮淮鏃ュ巻")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-calendar:update')")
+    public CommonResult<Boolean> updateCalendar(@Valid @RequestBody MesProMaintenanceCalendarSaveReqVO updateReqVO) {
+        calendarService.updateCalendar(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鐢靛姏杩愮淮鏃ュ巻")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-calendar:delete')")
+    public CommonResult<Boolean> deleteCalendar(@RequestParam("ids") List<Long> ids) {
+        calendarService.deleteCalendar(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鐢靛姏杩愮淮鏃ュ巻")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-calendar:query')")
+    public CommonResult<MesProMaintenanceCalendarRespVO> getCalendar(@RequestParam("id") Long id) {
+        MesProMaintenanceCalendarDO calendar = calendarService.getCalendar(id);
+        return success(calendar != null ? BeanUtils.toBean(calendar, MesProMaintenanceCalendarRespVO.class) : null);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鐢靛姏杩愮淮鏃ュ巻鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-calendar:query')")
+    public CommonResult<PageResult<MesProMaintenanceCalendarRespVO>> getCalendarPage(@Valid MesProMaintenanceCalendarPageReqVO pageReqVO) {
+        PageResult<MesProMaintenanceCalendarDO> pageResult = calendarService.getCalendarPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        return success(BeanUtils.toBean(pageResult, MesProMaintenanceCalendarRespVO.class));
+    }
+
+    @PutMapping("/publish")
+    @Operation(summary = "鍙戝竷鐢靛姏杩愮淮鏃ュ巻")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:pro-maintenance-calendar:update')")
+    public CommonResult<Boolean> publishCalendar(@RequestParam("id") Long id) {
+        calendarService.publishCalendar(id);
+        return success(true);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarPageReqVO.java
new file mode 100644
index 0000000..59faf1e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarPageReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐢靛姏杩愮淮鏃ュ巻鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesProMaintenanceCalendarPageReqVO extends PageParam {
+
+    @Schema(description = "鏃ュ巻缂栫爜", example = "RL20260820001")
+    private String code;
+
+    @Schema(description = "鏃ュ巻鍚嶇О", example = "8鏈堝彉鐢佃繍缁存帓鐝�")
+    private String name;
+
+    @Schema(description = "浣滀笟鏃ユ湡", example = "2026-08-20")
+    private LocalDate workDate;
+
+    @Schema(description = "鐝", example = "1")
+    private Integer shift;
+
+    @Schema(description = "浣滀笟绫诲瀷锛�1鍙樼數杩愮淮/2绾胯矾宸℃/3鍙戠數杩愮淮/4鍌ㄨ兘鍏呮斁鐢�/5鐢电綉璋冨害锛�", example = "1")
+    private Integer workType;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸插彂甯冿級", example = "0")
+    private Integer status;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarRespVO.java
new file mode 100644
index 0000000..7567485
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarRespVO.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+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.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐢靛姏杩愮淮鏃ュ巻 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesProMaintenanceCalendarRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鏃ュ巻缂栫爜", example = "RL20260820001")
+    @ExcelProperty("鏃ュ巻缂栫爜")
+    private String code;
+
+    @Schema(description = "鏃ュ巻鍚嶇О", example = "8鏈堝彉鐢佃繍缁存帓鐝�")
+    @ExcelProperty("鏃ュ巻鍚嶇О")
+    private String name;
+
+    @Schema(description = "浣滀笟鏃ユ湡", example = "2026-08-20")
+    @ExcelProperty("浣滀笟鏃ユ湡")
+    private LocalDate workDate;
+
+    @Schema(description = "鐝", example = "1")
+    @ExcelProperty(value = "鐝", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_CAL_SHIFT_TYPE)
+    private Integer shift;
+
+    @Schema(description = "浣滀笟绫诲瀷", example = "1")
+    @ExcelProperty(value = "浣滀笟绫诲瀷", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_PRO_CALENDAR_WORK_TYPE)
+    private Integer workType;
+
+    @Schema(description = "浣滀笟鍖哄煙/绾胯矾", example = "110kV 鍩庡尯绾�")
+    @ExcelProperty("浣滀笟鍖哄煙")
+    private String region;
+
+    @Schema(description = "鍊肩彮鐝粍缂栧彿", example = "100")
+    private Long teamId;
+
+    @Schema(description = "鍊肩彮鐝粍鍚嶇О", example = "鍙樼數杩愮淮涓�鐝�")
+    @ExcelProperty("鍊肩彮鐝粍")
+    private String teamName;
+
+    @Schema(description = "鍊肩彮浜哄憳", example = "寮犱笁,鏉庡洓")
+    @ExcelProperty("鍊肩彮浜哄憳")
+    private String personnel;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=宸插彂甯冿級", example = "0")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarSaveReqVO.java
new file mode 100644
index 0000000..1d46968
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarSaveReqVO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐢靛姏杩愮淮鏃ュ巻鏂板/淇敼 Request VO")
+@Data
+public class MesProMaintenanceCalendarSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鏃ュ巻鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "8鏈堝彉鐢佃繍缁存帓鐝�")
+    @NotEmpty(message = "鏃ュ巻鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "浣滀笟鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08-20")
+    @NotNull(message = "浣滀笟鏃ユ湡涓嶈兘涓虹┖")
+    private LocalDate workDate;
+
+    @Schema(description = "鐝", example = "1")
+    private Integer shift;
+
+    @Schema(description = "浣滀笟绫诲瀷锛�1鍙樼數杩愮淮/2绾胯矾宸℃/3鍙戠數杩愮淮/4鍌ㄨ兘鍏呮斁鐢�/5鐢电綉璋冨害锛�", example = "1")
+    private Integer workType;
+
+    @Schema(description = "浣滀笟鍖哄煙/绾胯矾", example = "110kV 鍩庡尯绾�")
+    private String region;
+
+    @Schema(description = "鍊肩彮鐝粍缂栧彿", example = "100")
+    private Long teamId;
+
+    @Schema(description = "鍊肩彮鐝粍鍚嶇О", example = "鍙樼數杩愮淮涓�鐝�")
+    private String teamName;
+
+    @Schema(description = "鍊肩彮浜哄憳", example = "寮犱笁,鏉庡洓")
+    private String personnel;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/alternativeplan/MesProAlternativePlanDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/alternativeplan/MesProAlternativePlanDO.java
new file mode 100644
index 0000000..52ce806
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/alternativeplan/MesProAlternativePlanDO.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pro.alternativeplan;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * MES 浣滀笟鏇夸唬鏂规 DO
+ *
+ * @author xiaoyi
+ */
+@TableName("mes_pro_alternative_plan")
+@KeySequence("mes_pro_alternative_plan_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesProAlternativePlanDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鏂规缂栫爜
+     */
+    private String code;
+
+    /**
+     * 鏂规鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 涓诲伐搴忕紪鍙�
+     */
+    private Long mainProcessId;
+
+    /**
+     * 涓诲伐搴忓悕绉�
+     */
+    private String mainProcessName;
+
+    /**
+     * 鏇夸唬宸ュ簭缂栧彿
+     */
+    private Long altProcessId;
+
+    /**
+     * 鏇夸唬宸ュ簭鍚嶇О
+     */
+    private String altProcessName;
+
+    /**
+     * 鏇夸唬鏉′欢锛堝锛氳澶囨晠闅�/璐熻嵎鍙楅檺/浜哄憳涓嶈冻锛�
+     */
+    private String altCondition;
+
+    /**
+     * 閫傜敤鑼冨洿
+     */
+    private String scope;
+
+    /**
+     * 鐘舵��
+     *
+     * 0: 鑽夌
+     * 10: 宸插彂甯�
+     */
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenancecalendar/MesProMaintenanceCalendarDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenancecalendar/MesProMaintenanceCalendarDO.java
new file mode 100644
index 0000000..411c0cc
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenancecalendar/MesProMaintenanceCalendarDO.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenancecalendar;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDate;
+
+/**
+ * MES 鐢靛姏杩愮淮鏃ュ巻 DO
+ *
+ * @author xiaoyi
+ */
+@TableName("mes_pro_maintenance_calendar")
+@KeySequence("mes_pro_maintenance_calendar_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesProMaintenanceCalendarDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鏃ュ巻缂栫爜
+     */
+    private String code;
+
+    /**
+     * 鏃ュ巻鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 浣滀笟鏃ユ湡
+     */
+    private LocalDate workDate;
+
+    /**
+     * 鐝锛堝瓧鍏� {@link DictTypeConstants#MES_CAL_SHIFT_TYPE}锛�
+     */
+    private Integer shift;
+
+    /**
+     * 浣滀笟绫诲瀷锛堝瓧鍏� {@link DictTypeConstants#MES_PRO_CALENDAR_WORK_TYPE}锛�
+     */
+    private Integer workType;
+
+    /**
+     * 浣滀笟鍖哄煙/绾胯矾
+     */
+    private String region;
+
+    /**
+     * 鍊肩彮鐝粍缂栧彿
+     */
+    private Long teamId;
+
+    /**
+     * 鍊肩彮鐝粍鍚嶇О
+     */
+    private String teamName;
+
+    /**
+     * 鍊肩彮浜哄憳
+     */
+    private String personnel;
+
+    /**
+     * 鐘舵��
+     *
+     * 0: 鑽夌
+     * 10: 宸插彂甯�
+     */
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/alternativeplan/MesProAlternativePlanMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/alternativeplan/MesProAlternativePlanMapper.java
new file mode 100644
index 0000000..755bdc6
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/alternativeplan/MesProAlternativePlanMapper.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.alternativeplan;
+
+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.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.alternativeplan.MesProAlternativePlanDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MES 浣滀笟鏇夸唬鏂规 Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface MesProAlternativePlanMapper extends BaseMapperX<MesProAlternativePlanDO> {
+
+    default MesProAlternativePlanDO selectByCode(String code) {
+        return selectOne(MesProAlternativePlanDO::getCode, code);
+    }
+
+    default PageResult<MesProAlternativePlanDO> selectPage(MesProAlternativePlanPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesProAlternativePlanDO>()
+                .likeIfPresent(MesProAlternativePlanDO::getCode, reqVO.getCode())
+                .likeIfPresent(MesProAlternativePlanDO::getName, reqVO.getName())
+                .eqIfPresent(MesProAlternativePlanDO::getMainProcessId, reqVO.getMainProcessId())
+                .eqIfPresent(MesProAlternativePlanDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesProAlternativePlanDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenancecalendar/MesProMaintenanceCalendarMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenancecalendar/MesProMaintenanceCalendarMapper.java
new file mode 100644
index 0000000..ed556c7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenancecalendar/MesProMaintenanceCalendarMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenancecalendar;
+
+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.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenancecalendar.MesProMaintenanceCalendarDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MES 鐢靛姏杩愮淮鏃ュ巻 Mapper
+ *
+ * @author xiaoyi
+ */
+@Mapper
+public interface MesProMaintenanceCalendarMapper extends BaseMapperX<MesProMaintenanceCalendarDO> {
+
+    default PageResult<MesProMaintenanceCalendarDO> selectPage(MesProMaintenanceCalendarPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesProMaintenanceCalendarDO>()
+                .likeIfPresent(MesProMaintenanceCalendarDO::getCode, reqVO.getCode())
+                .likeIfPresent(MesProMaintenanceCalendarDO::getName, reqVO.getName())
+                .eqIfPresent(MesProMaintenanceCalendarDO::getWorkDate, reqVO.getWorkDate())
+                .eqIfPresent(MesProMaintenanceCalendarDO::getShift, reqVO.getShift())
+                .eqIfPresent(MesProMaintenanceCalendarDO::getWorkType, reqVO.getWorkType())
+                .eqIfPresent(MesProMaintenanceCalendarDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesProMaintenanceCalendarDO::getWorkDate)
+                .orderByDesc(MesProMaintenanceCalendarDO::getId));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
index 503445b..21ca9d4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
@@ -8,11 +8,14 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 /**
  * MES 鐢熶骇浠诲姟 Mapper
@@ -74,6 +77,24 @@
                 .orderByDesc(MesProTaskDO::getId));
     }
 
+    /**
+     * 鐝粍鎶ュ伐宸ユ椂缁熻锛氭寜鐝粍鑱氬悎浠诲姟鏁颁笌鐢熶骇鏃堕暱锛坉uration 浜烘棩锛�
+     *
+     * @param startTime 鐢熶骇寮�濮嬫椂闂翠笅闄�
+     * @param endTime   鐢熶骇寮�濮嬫椂闂翠笂闄�
+     */
+    default List<Map<String, Object>> selectWorkHourSummary(LocalDateTime startTime, LocalDateTime endTime) {
+        return selectMaps(new QueryWrapper<MesProTaskDO>()
+                .select("team_id", "team_name",
+                        "COUNT(*) AS task_count",
+                        "SUM(IFNULL(duration, 0)) AS total_duration")
+                // 鎶ュ伐鍙e緞锛氭帓闄よ崏绋�/宸插彇娑堬紝浠呯粺璁″疄闄呰繘鍏ョ敓浜х殑浠诲姟
+                .notIn("status", MesProTaskStatusEnum.PREPARE.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus())
+                .ge(startTime != null, "start_time", startTime)
+                .le(endTime != null, "start_time", endTime)
+                .groupBy("team_id", "team_name"));
+    }
+
     default void updateProducedQuantity(Long id,
                                         BigDecimal incrProducedQuantity,
                                         BigDecimal incrQualifyQuantity,
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
index 08b2d3a..d0f62a0 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
@@ -139,4 +139,7 @@
     String MES_PD_PRICE_OBJECT_TYPE = "mes_pd_price_object_type";       // 瀹氫环瀵硅薄绫诲瀷锛�1鏈嶅姟濂楅/2鐢靛帇绛夌骇/3鐢ㄧ數绫诲瀷锛�
     String MES_PD_DISCOUNT_POLICY_TYPE = "mes_pd_discount_policy_type"; // 浼樻儬绛栫暐绫诲瀷锛�1鎶樻墸/2绔嬪噺锛�
     String MES_PD_TIERED_PRICE_STATUS = "mes_pd_tiered_price_status";   // 闃舵鐢典环鍚敤鐘舵��
+
+    // ========== 宸ヨ壓璁捐-浣滀笟鏇夸唬鏂规/鐢靛姏杩愮淮鏃ュ巻锛圥RO-EXT锛� ==========
+    String MES_PRO_CALENDAR_WORK_TYPE = "mes_pro_calendar_work_type"; // MES 鐢靛姏杩愮淮鏃ュ巻浣滀笟绫诲瀷锛�1鍙樼數杩愮淮/2绾胯矾宸℃/3鍙戠數杩愮淮/4鍌ㄨ兘鍏呮斁鐢�/5鐢电綉璋冨害锛�
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index fbf7bba..261841f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -836,4 +836,10 @@
     ErrorCode WMS_APPROVAL_STATUS_INVALID = new ErrorCode(1_040_900_004, "鍗曟嵁鐘舵�佷笉姝g‘锛屾棤娉曟彁浜ゅ鎵�");
     ErrorCode WMS_APPROVAL_ALREADY_SUBMITTED = new ErrorCode(1_040_900_005, "鍗曟嵁宸叉彁浜ゅ鎵癸紝璇峰嬁閲嶅鎻愪氦");
 
+    // ========== MES 宸ヨ壓璁捐-浣滀笟鏇夸唬鏂规/鐢靛姏杩愮淮鏃ュ巻锛�1-040-901-000锛� ==========
+    ErrorCode MES_PRO_ALTERNATIVE_PLAN_NOT_EXISTS = new ErrorCode(1_040_901_000, "浣滀笟鏇夸唬鏂规涓嶅瓨鍦�");
+    ErrorCode MES_PRO_ALTERNATIVE_PLAN_NOT_PREPARE = new ErrorCode(1_040_901_001, "浣滀笟鏇夸唬鏂规鐘舵�佷笉姝g‘锛屾棤娉曟墽琛岃鎿嶄綔");
+    ErrorCode MES_PRO_MAINTENANCE_CALENDAR_NOT_EXISTS = new ErrorCode(1_040_901_100, "鐢靛姏杩愮淮鏃ュ巻涓嶅瓨鍦�");
+    ErrorCode MES_PRO_MAINTENANCE_CALENDAR_NOT_PREPARE = new ErrorCode(1_040_901_101, "鐢靛姏杩愮淮鏃ュ巻鐘舵�佷笉姝g‘锛屾棤娉曟墽琛岃鎿嶄綔");
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java
index 74e7cfd..6f7617a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java
@@ -17,7 +17,8 @@
 
     ORDER(1, "瀹㈡埛璁㈠崟"),
     STORE(2, "搴撳瓨澶囪揣"),
-    MAINTENANCE_PLAN(3, "杩愮淮璁″垝");
+    MAINTENANCE_PLAN(3, "杩愮淮璁″垝"),
+    FORECAST(4, "璐熻嵎棰勬祴");
 
     public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesProWorkOrderSourceTypeEnum::getType).toArray(Integer[]::new);
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java
index 74e1ec7..99b391a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.mes.service.dv.telemetry;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryCheckSummaryRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryDeviceRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryLatestReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPageReqVO;
@@ -32,6 +33,11 @@
     List<MesDvTelemetryDeviceRespVO> getDeviceList();
 
     /**
+     * 渚涚數鍙傛暟姣斿鏍¢獙缁熻锛氭寜璁惧鑱氬悎鏈�鏂板弬鏁帮紝杈撳嚭姝e父/寮傚父鏁颁笌寮傚父鐜�
+     */
+    List<MesDvTelemetryCheckSummaryRespVO> getCheckSummary(MesDvTelemetryLatestReqVO reqVO);
+
+    /**
      * 璋冪敤澶栭儴鏁伴噰鎺ュ彛鎷夊彇杩� 5 鍒嗛挓璁惧鏁版嵁骞跺叆搴�
      */
     MesDvTelemetryPullRespVO pullTelemetry();
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java
index b5678e4..d50642f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java
@@ -6,6 +6,7 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryCheckSummaryRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryDeviceRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryLatestReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.telemetry.vo.MesDvTelemetryPageReqVO;
@@ -17,6 +18,8 @@
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -59,6 +62,41 @@
     }
 
     @Override
+    public List<MesDvTelemetryCheckSummaryRespVO> getCheckSummary(MesDvTelemetryLatestReqVO reqVO) {
+        List<MesDvTelemetryDO> list = getLatestTelemetry(reqVO);
+        Map<String, MesDvTelemetryCheckSummaryRespVO> deviceMap = new LinkedHashMap<>();
+        for (MesDvTelemetryDO row : list) {
+            String key = StrUtil.blankToDefault(row.getTbDeviceId(), "");
+            MesDvTelemetryCheckSummaryRespVO vo = deviceMap.get(key);
+            if (vo == null) {
+                vo = new MesDvTelemetryCheckSummaryRespVO();
+                vo.setTbDeviceId(row.getTbDeviceId());
+                vo.setDeviceName(row.getDeviceName());
+                vo.setTotalCount(0);
+                vo.setNormalCount(0);
+                vo.setAnomalyCount(0);
+                deviceMap.put(key, vo);
+            }
+            vo.setTotalCount(vo.getTotalCount() + 1);
+            if (Boolean.TRUE.equals(row.getWhetherAnomaly())) {
+                vo.setAnomalyCount(vo.getAnomalyCount() + 1);
+            } else {
+                vo.setNormalCount(vo.getNormalCount() + 1);
+            }
+        }
+        List<MesDvTelemetryCheckSummaryRespVO> result = new ArrayList<>(deviceMap.values());
+        for (MesDvTelemetryCheckSummaryRespVO vo : result) {
+            if (vo.getTotalCount() != null && vo.getTotalCount() > 0) {
+                BigDecimal rate = BigDecimal.valueOf(vo.getAnomalyCount() * 100.0 / vo.getTotalCount());
+                vo.setAnomalyRate(rate.setScale(1, RoundingMode.HALF_UP));
+            } else {
+                vo.setAnomalyRate(BigDecimal.ZERO);
+            }
+        }
+        return result;
+    }
+
+    @Override
     public List<MesDvTelemetryDeviceRespVO> getDeviceList() {
         List<MesDvTelemetryDeviceRespVO> result = new ArrayList<>();
         for (MesDvTelemetryDO row : telemetryMapper.selectDeviceList()) {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanService.java
new file mode 100644
index 0000000..fa89de6
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.service.pro.alternativeplan;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.alternativeplan.MesProAlternativePlanDO;
+
+import java.util.Collection;
+
+/**
+ * MES 浣滀笟鏇夸唬鏂规 Service 鎺ュ彛
+ *
+ * @author xiaoyi
+ */
+public interface MesProAlternativePlanService {
+
+    Long createPlan(MesProAlternativePlanSaveReqVO createReqVO);
+
+    void updatePlan(MesProAlternativePlanSaveReqVO updateReqVO);
+
+    void deletePlan(Collection<Long> ids);
+
+    MesProAlternativePlanDO getPlan(Long id);
+
+    PageResult<MesProAlternativePlanDO> getPlanPage(MesProAlternativePlanPageReqVO pageReqVO);
+
+    void publishPlan(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanServiceImpl.java
new file mode 100644
index 0000000..15dc926
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanServiceImpl.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.mes.service.pro.alternativeplan;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.alternativeplan.vo.MesProAlternativePlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.alternativeplan.MesProAlternativePlanDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.alternativeplan.MesProAlternativePlanMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_PRO_ALTERNATIVE_PLAN_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_PRO_ALTERNATIVE_PLAN_NOT_PREPARE;
+
+/**
+ * MES 浣滀笟鏇夸唬鏂规 Service 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+public class MesProAlternativePlanServiceImpl implements MesProAlternativePlanService {
+
+    private static final Integer STATUS_DRAFT = 0;
+    private static final Integer STATUS_PUBLISHED = 10;
+
+    @Resource
+    private MesProAlternativePlanMapper planMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createPlan(MesProAlternativePlanSaveReqVO createReqVO) {
+        MesProAlternativePlanDO plan = BeanUtils.toBean(createReqVO, MesProAlternativePlanDO.class, req -> req
+                .setCode(generateCode())
+                .setStatus(STATUS_DRAFT));
+        planMapper.insert(plan);
+        return plan.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePlan(MesProAlternativePlanSaveReqVO updateReqVO) {
+        MesProAlternativePlanDO oldPlan = validatePlanCanModify(updateReqVO.getId());
+        MesProAlternativePlanDO updateObj = BeanUtils.toBean(updateReqVO, MesProAlternativePlanDO.class, req -> req
+                .setCode(oldPlan.getCode())
+                .setStatus(oldPlan.getStatus()));
+        planMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deletePlan(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            validatePlanCanModify(id);
+            planMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public MesProAlternativePlanDO getPlan(Long id) {
+        return planMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MesProAlternativePlanDO> getPlanPage(MesProAlternativePlanPageReqVO pageReqVO) {
+        return planMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void publishPlan(Long id) {
+        MesProAlternativePlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
+            throw exception(MES_PRO_ALTERNATIVE_PLAN_NOT_PREPARE);
+        }
+        planMapper.updateById(new MesProAlternativePlanDO().setId(id).setStatus(STATUS_PUBLISHED));
+    }
+
+    private String generateCode() {
+        return "TD" + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss")
+                + String.format("%03d", (int) (Math.random() * 1000));
+    }
+
+    private MesProAlternativePlanDO validatePlanExists(Long id) {
+        MesProAlternativePlanDO plan = planMapper.selectById(id);
+        if (plan == null) {
+            throw exception(MES_PRO_ALTERNATIVE_PLAN_NOT_EXISTS);
+        }
+        return plan;
+    }
+
+    private MesProAlternativePlanDO validatePlanCanModify(Long id) {
+        MesProAlternativePlanDO plan = validatePlanExists(id);
+        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
+            throw exception(MES_PRO_ALTERNATIVE_PLAN_NOT_PREPARE);
+        }
+        return plan;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarService.java
new file mode 100644
index 0000000..2dbe4c9
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.service.pro.maintenancecalendar;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenancecalendar.MesProMaintenanceCalendarDO;
+
+import java.util.Collection;
+
+/**
+ * MES 鐢靛姏杩愮淮鏃ュ巻 Service 鎺ュ彛
+ *
+ * @author xiaoyi
+ */
+public interface MesProMaintenanceCalendarService {
+
+    Long createCalendar(MesProMaintenanceCalendarSaveReqVO createReqVO);
+
+    void updateCalendar(MesProMaintenanceCalendarSaveReqVO updateReqVO);
+
+    void deleteCalendar(Collection<Long> ids);
+
+    MesProMaintenanceCalendarDO getCalendar(Long id);
+
+    PageResult<MesProMaintenanceCalendarDO> getCalendarPage(MesProMaintenanceCalendarPageReqVO pageReqVO);
+
+    void publishCalendar(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarServiceImpl.java
new file mode 100644
index 0000000..1f2e7e7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarServiceImpl.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.mes.service.pro.maintenancecalendar;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenancecalendar.vo.MesProMaintenanceCalendarSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.maintenancecalendar.MesProMaintenanceCalendarDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.maintenancecalendar.MesProMaintenanceCalendarMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_PRO_MAINTENANCE_CALENDAR_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_PRO_MAINTENANCE_CALENDAR_NOT_PREPARE;
+
+/**
+ * MES 鐢靛姏杩愮淮鏃ュ巻 Service 瀹炵幇绫�
+ *
+ * @author xiaoyi
+ */
+@Service
+@Validated
+public class MesProMaintenanceCalendarServiceImpl implements MesProMaintenanceCalendarService {
+
+    private static final Integer STATUS_DRAFT = 0;
+    private static final Integer STATUS_PUBLISHED = 10;
+
+    @Resource
+    private MesProMaintenanceCalendarMapper calendarMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createCalendar(MesProMaintenanceCalendarSaveReqVO createReqVO) {
+        MesProMaintenanceCalendarDO calendar = BeanUtils.toBean(createReqVO, MesProMaintenanceCalendarDO.class, req -> req
+                .setCode(generateCode())
+                .setStatus(STATUS_DRAFT));
+        calendarMapper.insert(calendar);
+        return calendar.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateCalendar(MesProMaintenanceCalendarSaveReqVO updateReqVO) {
+        MesProMaintenanceCalendarDO oldCalendar = validateCalendarCanModify(updateReqVO.getId());
+        MesProMaintenanceCalendarDO updateObj = BeanUtils.toBean(updateReqVO, MesProMaintenanceCalendarDO.class, req -> req
+                .setCode(oldCalendar.getCode())
+                .setStatus(oldCalendar.getStatus()));
+        calendarMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteCalendar(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            validateCalendarCanModify(id);
+            calendarMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public MesProMaintenanceCalendarDO getCalendar(Long id) {
+        return calendarMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MesProMaintenanceCalendarDO> getCalendarPage(MesProMaintenanceCalendarPageReqVO pageReqVO) {
+        return calendarMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void publishCalendar(Long id) {
+        MesProMaintenanceCalendarDO calendar = validateCalendarExists(id);
+        if (!Objects.equals(calendar.getStatus(), STATUS_DRAFT)) {
+            throw exception(MES_PRO_MAINTENANCE_CALENDAR_NOT_PREPARE);
+        }
+        calendarMapper.updateById(new MesProMaintenanceCalendarDO().setId(id).setStatus(STATUS_PUBLISHED));
+    }
+
+    private String generateCode() {
+        return "RL" + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss")
+                + String.format("%03d", (int) (Math.random() * 1000));
+    }
+
+    private MesProMaintenanceCalendarDO validateCalendarExists(Long id) {
+        MesProMaintenanceCalendarDO calendar = calendarMapper.selectById(id);
+        if (calendar == null) {
+            throw exception(MES_PRO_MAINTENANCE_CALENDAR_NOT_EXISTS);
+        }
+        return calendar;
+    }
+
+    private MesProMaintenanceCalendarDO validateCalendarCanModify(Long id) {
+        MesProMaintenanceCalendarDO calendar = validateCalendarExists(id);
+        if (!Objects.equals(calendar.getStatus(), STATUS_DRAFT)) {
+            throw exception(MES_PRO_MAINTENANCE_CALENDAR_NOT_PREPARE);
+        }
+        return calendar;
+    }
+
+}

--
Gitblit v1.9.3