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