xiaoyi
4 天以前 c9d4ba37c1f681b12e24014013fa9a28734bad42
feat 功能修正
已添加64个文件
已修改18个文件
4280 ■■■■■ 文件已修改
docs/dv_telemetry_integration.md 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/AfterSaleCompensationController.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationAuditReqVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationPageReqVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationRespVO.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/compensation/vo/AfterSaleCompensationSaveReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/compensation/AfterSaleCompensationDO.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/compensation/AfterSaleCompensationMapper.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleCompensationAuditStatusEnum.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/DictTypeConstants.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationService.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/AfterSaleCompensationServiceImpl.java 251 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/compensation/listener/AfterSaleCompensationStatusListener.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSaleCompensationStatusUtils.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastWorkOrderCreateReqVO.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseAuditReqVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemRespVO.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseItemSaveReqVO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraisePageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultPageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultRespVO.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseResultSaveReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourStatisticsController.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmEmployeeWorkHourRespVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseItemDO.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultDO.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseResultItemDO.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseItemMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultItemMapper.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseResultMapper.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/enums/DictTypeConstants.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java 454 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/listener/HrmPerformanceAppraiseStatusListener.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourStatisticsServiceImpl.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApi.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryReqDTO.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/task/dto/MesProWorkHourSummaryRespDTO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApi.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/dto/MesProWorkOrderCreateReqDTO.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/task/MesProTaskApiImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/workorder/MesProWorkOrderApiImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/MesDvTelemetryController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/telemetry/vo/MesDvTelemetryCheckSummaryRespVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/MesProAlternativePlanController.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanPageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanRespVO.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/alternativeplan/vo/MesProAlternativePlanSaveReqVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/MesProMaintenanceCalendarController.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarPageReqVO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarRespVO.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/vo/MesProMaintenanceCalendarSaveReqVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/alternativeplan/MesProAlternativePlanDO.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/maintenancecalendar/MesProMaintenanceCalendarDO.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/alternativeplan/MesProAlternativePlanMapper.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/maintenancecalendar/MesProMaintenanceCalendarMapper.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/telemetry/MesDvTelemetryServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/alternativeplan/MesProAlternativePlanServiceImpl.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenancecalendar/MesProMaintenanceCalendarServiceImpl.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/dv_telemetry_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,124 @@
# è®¾å¤‡æ•°é‡‡ï¼ˆè®¾å¤‡æ•°æ®é‡‡é›†ï¼‰Â· å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
| é¡µé¢ | è·¯ç”± | è¯´æ˜Ž |
|------|------|------|
| è®¾å¤‡æ•°é‡‡ | è®¾å¤‡ç®¡ç† â†’ è®¾å¤‡æ•°é‡‡ | å®žæ—¶æ•°æ®ï¼ˆå„设备最新遥测)+ åŽ†å²è®°å½•ä¸¤ä¸ª Tab + æ‹‰å–按钮 + å®žæ—¶è‡ªåŠ¨åˆ·æ–° |
| è®¾å¤‡å°è´¦ | è®¾å¤‡ç®¡ç† â†’ è®¾å¤‡å°è´¦ | è¡¨å•新增「数采设备ID」字段;查看详情新增「数采数据」Tab å±•示该设备实时信号 |
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
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. **设备关联**:设备台账表单手工维护「数采设备ID」(`tb_device_id`,对应外部数采 `tbDeviceId`);台账详情「数采数据」Tab ç”¨è¯¥ ID è°ƒ `/latest?tbDeviceId=xx` å±•示该设备实时信号。未填数采设备ID时该 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` / è°ƒç”¨å¤±è´¥ / è¿”回异常时,返回错误码 `数采数据拉取失败:xxx`;接口正常但近 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 | å¦ | æ˜¯å¦å¼‚常(true/false) |
| shiftName | String | å¦ | ç­æ¬¡ |
| telemetryDataTime | String[] | å¦ | é¥æµ‹æ—¶é—´åŒºé—´ï¼Œ`yyyy-MM-dd HH:mm:ss` |
**响应:** `data` ä¸º `PageResult`,`records` å­—段同下表。
### 4. è®¾å¤‡ä¸‹æ‹‰ `GET /mes/dv/telemetry/device-list`
无参数。返回遥测表中已出现的设备去重列表:`[{ tbDeviceId, deviceName }]`,供筛选下拉使用。
### é¥æµ‹è®°å½•字段(latest / 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 | æ˜¯å¦å¼‚常(true=异常) |
| telemetryDataTime | String | é¥æµ‹æ•°æ®æ—¶é—´ |
| billNo | String | å•据号 |
| shiftName | String | ç­æ¬¡ |
| pullTime | String | æ‹‰å–æ—¶é—´ |
| createTime | String | åˆ›å»ºæ—¶é—´ |
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| deviceName | å®žæ—¶/历史列表、筛选 | è®¾å¤‡åç§° |
| paramName | å®žæ—¶/历史列表 | ä¿¡å·åç§° |
| timelyValue | å®žæ—¶/历史列表 | ä¿¡å·å€¼ï¼ˆå®žæ—¶ï¼‰ |
| whetherAnomaly | å®žæ—¶/历史列表、历史筛选 | å¸ƒå°”渲染为 Tag:正常(绿)/异常(红);筛选用布尔选项 æ­£å¸¸/异常 |
| shiftName | å®žæ—¶/历史列表 | ç­æ¬¡ |
| telemetryDataTime | å®žæ—¶/历史列表、历史筛选 | é¥æµ‹æ—¶é—´ï¼Œ`YYYY-MM-DD HH:mm:ss` |
| standardValue / avgValue / maxValue / minValue | å®žæ—¶åˆ—表 | æ ‡å‡†å€¼ / å‡å€¼ / æœ€å¤§å€¼ / æœ€å°å€¼ |
| billNo | åŽ†å²åˆ—è¡¨ | å•据号 |
| pullTime | åŽ†å²åˆ—è¡¨ | æ‹‰å–æ—¶é—´ |
| æœ€è¿‘拉取时间 | é¡µé¢é¡¶éƒ¨ | æ‹‰å–成功后回显 pullTime |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| æ•°æ®æ‹‰å– | æ‰‹åŠ¨æŒ‰é’® + å®šæ—¶ä»»åŠ¡ï¼ˆæ¯ 5 åˆ†é’Ÿï¼‰å‡å¯è§¦å‘,写入 `mes_dv_telemetry` |
| å®žæ—¶æ•°æ® | æŒ‰ã€ŒtbDeviceId + paramKeyName」去重保留最新,展示最近一次拉取结果 |
| åŽ†å²æ•°æ® | æ¯æ¬¡æ‹‰å–全量入库(含重复信号),按遥测时间倒序分页 |
| è®¾å¤‡å…³è” | è®¾å¤‡å°è´¦ `tb_device_id` ä¸Žå¤–部数采 `tbDeviceId` å¯¹åº”;未填则台账详情「数采数据」Tab ç¦ç”¨ |
| æ˜¯å¦å¼‚常 | åŽç«¯ `bit` å­—段映射 Boolean;前端直接按布尔渲染,不依赖字典 |
| å®šæ—¶ä»»åŠ¡ | é»˜è®¤æ¯ 5 åˆ†é’Ÿæ‰§è¡Œï¼Œå¯åœ¨ã€Œç³»ç»Ÿç®¡ç† â†’ å®šæ—¶ä»»åŠ¡ã€è°ƒæ•´/停用 |
## æ³¨æ„äº‹é¡¹
- æ•°é‡‡æœåŠ¡åœ°å€åœ¨åŽç«¯ `yudao.mes.telemetry.base-url` é…ç½®ï¼ˆ`application-local.yaml`),未配置时 `/pull` æŠ¥ã€Œæ•°é‡‡æ•°æ®æ‹‰å–失败」。
- å¤–部接口仅保留近 5 åˆ†é’Ÿæ•°æ®ï¼Œè¶…过不拉即丢;建议保持定时任务运行,页面手动拉取仅做实时刷新用。
- åŽ†å²è®°å½•æ•°æ®é‡ä¼šæŒç»­å¢žé•¿ï¼ŒæŸ¥è¯¢å»ºè®®å¸¦é¥æµ‹æ—¶é—´åŒºé—´è¿‡æ»¤ã€‚
- æ•°é‡‡é¥æµ‹ä¸ºå¤–部数据,只读展示,无新增/修改/删除/导出操作,仅一个「查询」按钮权限 `mes:dv-telemetry:query`。
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()));
        });
    }
}
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;
}
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;
}
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;
}
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;
}
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;
}
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));
    }
}
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;
    }
}
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";
}
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, "关联的售后工单不存在");
}
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();
}
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);
    }
}
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());
    }
}
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;
    }
}
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));
    }
}
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;
}
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, "负荷预测数据不存在");
}
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);
}
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);
    }
}
    @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));
    }
}
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;
    }
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, "员工薪酬档案不存在");
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>
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 = "保存员工考核结果(自动计算综合得分与档级)")
    @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 -> "";
        };
    }
}
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;
}
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;
}
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;
    }
}
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;
}
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;
}
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 = "档级", example = "1")
    private Integer grade;
}
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 = "档级", example = "2")
    @ExcelProperty(value = "档级", 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;
    }
}
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;
    }
}
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-季度 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;
}
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 = "员工工时统计(考勤口径)")
    @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));
    }
}
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;
}
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 = "正常工时(人时)")
    private BigDecimal workHours;
    @Schema(description = "加班工时(人时)")
    private BigDecimal overtimeHours;
}
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-季度 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;
}
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;
}
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;
    /**
     * æ¡£çº§: 1-优秀 2-良好 3-合格 4-待改进
     */
    private Integer grade;
    /**
     * è¯„语
     */
    private String comment;
}
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;
}
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));
    }
    /**
     * å‘˜å·¥å·¥æ—¶ç»Ÿè®¡ï¼ˆè€ƒå‹¤å£å¾„):按员工聚合出勤天数、正常工时与加班工时
     */
    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)
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));
    }
}
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));
    }
}
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));
    }
}
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));
    }
}
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";
}
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);
    /**
     * å®¡æ‰¹ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆï¼ˆé€šè¿‡/驳回,manual)
     */
    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);
}
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;
    /**
     * æ¡£çº§ï¼š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);
        }
    }
}
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(方案ID)匹配,而不是固定的流程定义 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());
    }
}
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 {
    /**
     * å‘˜å·¥å·¥æ—¶ç»Ÿè®¡ï¼ˆè€ƒå‹¤å£å¾„)
     */
    List<HrmEmployeeWorkHourRespVO> getEmployeeWorkHourSummary(HrmEmployeeWorkHourReqVO reqVO);
    /**
     * ç­ç»„工时统计(报工口径,跨模块聚合 MES)
     */
    List<MesProWorkHourSummaryRespDTO> getTeamWorkHourSummary(MesProWorkHourSummaryReqDTO reqDTO);
}
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;
    }
}
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);
}
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;
}
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;
}
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);
}
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;
}
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;
    }
}
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);
    }
}
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));
    }
}
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 = "正常参数数")
    private Integer normalCount;
    @Schema(description = "异常参数数")
    private Integer anomalyCount;
    @Schema(description = "异常率(%)")
    private BigDecimal anomalyRate;
}
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);
    }
}
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;
}
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;
}
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;
}
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);
    }
}
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;
}
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;
}
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;
}
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;
}
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;
}
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));
    }
}
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));
    }
}
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));
    }
    /**
     * ç­ç»„报工工时统计:按班组聚合任务数与生产时长(duration äººæ—¥ï¼‰
     *
     * @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")
                // æŠ¥å·¥å£å¾„:排除草稿/已取消,仅统计实际进入生产的任务
                .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,
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";   // é˜¶æ¢¯ç”µä»·å¯ç”¨çŠ¶æ€
    // ========== å·¥è‰ºè®¾è®¡-作业替代方案/电力运维日历(PRO-EXT) ==========
    String MES_PRO_CALENDAR_WORK_TYPE = "mes_pro_calendar_work_type"; // MES ç”µåŠ›è¿ç»´æ—¥åŽ†ä½œä¸šç±»åž‹ï¼ˆ1变电运维/2线路巡检/3发电运维/4储能充放电/5电网调度)
}
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, "单据状态不正确,无法提交审批");
    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, "作业替代方案状态不正确,无法执行该操作");
    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, "电力运维日历状态不正确,无法执行该操作");
}
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);
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();
    /**
     * ä¾›ç”µå‚数比对校验统计:按设备聚合最新参数,输出正常/异常数与异常率
     */
    List<MesDvTelemetryCheckSummaryRespVO> getCheckSummary(MesDvTelemetryLatestReqVO reqVO);
    /**
     * è°ƒç”¨å¤–部数采接口拉取近 5 åˆ†é’Ÿè®¾å¤‡æ•°æ®å¹¶å…¥åº“
     */
    MesDvTelemetryPullRespVO pullTelemetry();
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()) {
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);
}
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;
    }
}
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);
}
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;
    }
}