From b852254d90e7d1955db31db154193ec8ee84d8b3 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 09 九月 2026 10:57:59 +0800
Subject: [PATCH] feat(hrm): 新增员工用工性质、班组归属字段及考勤异常处理优化

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java                           |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailPageReqVO.java                  |   35 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/task/MesWmStockTakingTaskDO.java                       |    7 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java                                        |   62 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java                |   14 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java                                         |   16 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApiImpl.java                                                       |   45 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java                                           |   14 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/MesWmTransactionController.java                      |   10 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineSaveReqVO.java |   37 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/plan/MesWmStockTakingPlanDO.java                       |   19 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanRespVO.java              |   12 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptPageReqVO.java          |   32 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryRespVO.java                           |   49 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java                           |    4 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingParticipantController.java                      |   92 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java                               |   57 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java                                 |   15 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmEquipmentReceiptStatusEnum.java                                        |   55 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java                                                |  128 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnDetailRespVO.java                                   |   57 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeService.java                                          |   26 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/job/HrmExpiryRemindJob.java                                                          |   28 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantAddReqVO.java                     |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalScheduleDetailSourceEnum.java                                        |   37 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationSaveReqVO.java                  |   40 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java                             |   43 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailService.java                                |   57 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java                                                         |   30 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java                                |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java               |   24 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryService.java                                      |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailRespVO.java                     |   45 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingPageReqVO.java                               |   27 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java                         |    8 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmRegularizeApplicationStatusListener.java                |   90 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApi.java                                                       |   25 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnRespVO.java                                         |   12 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApiImpl.java                                          |   65 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmSnStatusEnum.java                                                      |   37 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingServiceImpl.java                                         |  279 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java                   |   13 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java                                 |   26 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java                                       |    9 
 yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java                                                       |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeServiceImpl.java                                      |  171 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingParticipantDO.java                                |   76 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmRegularizeApplicationMapper.java                               |   45 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java                 |   12 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptLineMapper.java                   |   30 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeContractMapper.java                                    |   16 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationServiceImpl.java                            |  307 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/sn/MesWmSnDO.java                                                  |   41 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/sn/MesWmSnMapper.java                                                   |   34 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java            |    4 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java               |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/barcode/MesWmBarcodeController.java                              |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java              |    3 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java                                       |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptServiceImpl.java                    |  290 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java                                  |  152 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/cal/schedule/MesCalScheduleDetailMapper.java                               |   63 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java                            |   86 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java                                  |   14 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java                     |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryServiceImpl.java                                     |   72 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLinePageReqVO.java |   20 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java                          |   43 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java                                                    |   20 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingParticipantMapper.java                                 |   78 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnOutStockReqVO.java                                  |   23 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java                                    |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineService.java                    |   51 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/dto/HrmWorkHourSummaryRespDTO.java                                  |   62 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java                               |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmRegularizeApplicationDO.java                              |   97 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionPageReqVO.java                    |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSalaryController.java                             |  188 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java                                      |   37 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java                     |   23 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnService.java                                                    |   36 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java                                                  |   10 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java                           |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseLocationDO.java                            |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalarySaveReqVO.java                           |   50 
 yudao-module-mes/pom.xml                                                                                                                        |   12 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java                      |    9 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingService.java                                             |  121 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApi.java                                          |   33 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java                       |    8 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java                                    |   67 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineRespVO.java    |   52 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java           |   13 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java                                   |    7 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingMapper.java                                            |   27 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java                  |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailServiceImpl.java                            |  225 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanSaveReqVO.java           |    6 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmploymentTypeEnum.java                                                 |   41 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingSaveReqVO.java                               |   50 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckRespVO.java                |   30 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationService.java                                |  102 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockPageReqVO.java                               |   36 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingController.java                                 |  171 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockTakingTaskStatusEnum.java                                         |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigService.java                                    |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java                         |   67 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckReqVO.java                 |   27 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantUpdateReqVO.java                  |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java                                |    4 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourSummaryController.java                          |   49 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptDO.java                      |   60 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java                        |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java                                     |    1 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockTakingPlanAutoJob.java                                              |  101 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/MesCalScheduleDetailController.java                    |   74 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingRespVO.java                                  |   69 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantRespVO.java                       |   46 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/workhour/HrmWorkHourSummaryDO.java                                    |   69 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java                                                           |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmExpiryRemindRedisDAO.java                                               |   58 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryServiceImpl.java                                  |  124 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailSaveReqVO.java                  |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                                        |   31 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java            |  102 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/dto/MesCalTeamRespDTO.java                                               |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java                                                       |    1 
 yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java                                                 |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java             |   20 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/barcode/MesWmBarcodeConfigMapper.java                                   |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java                              |    2 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java         |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationPageReqVO.java                  |   39 
 yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java                                            |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java                   |   59 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockReqVO.java                                   |   38 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskMapper.java                        |   14 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java                         |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java                                        |   10 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineServiceImpl.java                |  119 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java                     |  243 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptMapper.java                       |   30 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryPageReqVO.java                        |   24 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryService.java                                         |   70 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/listener/MesWmStockTakingTaskResultStatusListener.java   |   46 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmRegularizeApplicationController.java                    |   91 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java                                  |  109 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryPageReqVO.java                           |   24 
 yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java                                            |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/listener/MesWmEquipmentReceiptStatusListener.java        |   43 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java                         |   18 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptRespVO.java             |   44 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryRespVO.java                              |   59 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigServiceImpl.java                                |    7 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingDO.java                                           |  130 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindServiceImpl.java                                       |  217 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/plan/MesWmStockTakingPlanMapper.java                        |   16 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptLineDO.java                  |   54 
 yudao-module-hrm/pom.xml                                                                                                                        |    6 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptService.java                        |   40 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/workhour/HrmWorkHourSummaryMapper.java                                     |   41 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationRespVO.java                     |   67 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructurePageReqVO.java                          |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/schedule/MesCalScheduleDetailDO.java                              |   83 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindService.java                                           |   28 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptLineController.java        |  120 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/MesWmSnController.java                                        |   37 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/barcode/MesWmBarcodeConfigDO.java                                  |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionRespVO.java                       |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java                                    |    7 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java                                            |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptSaveReqVO.java          |   38 
 172 files changed, 7,967 insertions(+), 104 deletions(-)

diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApi.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApi.java
new file mode 100644
index 0000000..f74e228
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApi.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.hrm.api.workhour;
+
+import cn.iocoder.yudao.module.hrm.api.workhour.dto.HrmWorkHourSummaryRespDTO;
+
+import java.util.List;
+
+/**
+ * HRM 鏈堝害宸ユ椂姹囨�� API 鎺ュ彛
+ * <p>
+ * 渚涙垚鏈牳绠楃瓑妯″潡璋冪敤
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmWorkHourSummaryApi {
+
+    /**
+     * 鑾峰彇鎸囧畾鍛ㄦ湡鐨勫叏閮ㄥ憳宸ュ伐鏃舵眹鎬�
+     *
+     * @param period 鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�
+     * @return 鍛樺伐宸ユ椂姹囨��
+     */
+    List<HrmWorkHourSummaryRespDTO> getSummaryByPeriod(String period);
+
+    /**
+     * 鑾峰彇鎸囧畾鍛樺伐鎸囧畾鍛ㄦ湡鐨勫伐鏃舵眹鎬�
+     *
+     * @param employeeId 鍛樺伐ID
+     * @param period 鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�
+     * @return 鍛樺伐宸ユ椂姹囨��
+     */
+    HrmWorkHourSummaryRespDTO getSummaryByEmployeeAndPeriod(Long employeeId, String period);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/dto/HrmWorkHourSummaryRespDTO.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/dto/HrmWorkHourSummaryRespDTO.java
new file mode 100644
index 0000000..7a0c1cb
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/dto/HrmWorkHourSummaryRespDTO.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.hrm.api.workhour.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * HRM 鏈堝害宸ユ椂姹囨�� Response DTO
+ * <p>
+ * 渚涙垚鏈牳绠楃瓑妯″潡璋冪敤锛岃幏鍙栨寚瀹氭牳绠楀懆鏈熺殑鍛樺伐鏈堝害宸ユ椂锛堣�冨嫟鍙e緞锛�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Data
+public class HrmWorkHourSummaryRespDTO {
+
+    /**
+     * 鍛樺伐ID锛圚rmEmployeeDO#getId()锛�
+     */
+    private Long employeeId;
+
+    /**
+     * 鍛樺伐缂栧彿
+     */
+    private String employeeNo;
+
+    /**
+     * 鍛樺伐濮撳悕
+     */
+    private String employeeName;
+
+    /**
+     * 閮ㄩ棬ID
+     */
+    private Long deptId;
+
+    /**
+     * 閮ㄩ棬鍚嶇О
+     */
+    private String deptName;
+
+    /**
+     * 鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�
+     */
+    private String period;
+
+    /**
+     * 鍑哄嫟澶╂暟
+     */
+    private Integer workDays;
+
+    /**
+     * 姝e父宸ユ椂锛堝皬鏃讹級
+     */
+    private BigDecimal workHours;
+
+    /**
+     * 鍔犵彮宸ユ椂锛堝皬鏃讹級
+     */
+    private BigDecimal overtimeHours;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
index 4946dc4..241652b 100644
--- a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -95,6 +95,7 @@
     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, "璇勫垎澶辫触锛屽師鍥狅細鏂规涓嶆槸宸插彂甯�/杩涜涓姸鎬�");
+    ErrorCode PERFORMANCE_APPRAISE_SCOPE_TEAM_EMPTY = new ErrorCode(1_030_008_010, "鎸囧畾鐝粍涓虹┖锛屾棤娉曠敓鎴愯鑰冩牳浜�");
 
     // ========== 钖叕绠$悊 1-030-005-000 ==========
     ErrorCode SALARY_STRUCTURE_NOT_EXISTS = new ErrorCode(1_030_005_000, "钖叕缁撴瀯涓嶅瓨鍦�");
@@ -123,4 +124,23 @@
     ErrorCode SALARY_PAYMENT_DELETE_FAIL = new ErrorCode(1_030_005_023, "鍒犻櫎澶辫触锛屽師鍥狅細浠呭緟鍙戞斁鐘舵�佸彲鍒犻櫎锛岃鍏堟挙閿�宸插彂鏀炬槑缁�");
     ErrorCode SALARY_PAYMENT_HAS_PAID_DETAIL = new ErrorCode(1_030_005_024, "鍒犻櫎澶辫触锛屽師鍥狅細瀛樺湪宸插彂鏀炬槑缁嗭紝璇峰厛鎾ら攢");
 
+    // ========== 杞鐢宠 1-030-006-000 ==========
+    ErrorCode REGULARIZE_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_006_000, "杞鐢宠涓嶅瓨鍦�");
+    ErrorCode REGULARIZE_APPLICATION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_006_001, "杞鐢宠鏇存柊澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+    ErrorCode REGULARIZE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_006_002, "杞鐢宠鎻愪氦澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+    ErrorCode REGULARIZE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_006_003, "鏇存柊杞鐢宠瀹℃壒鐘舵�佸け璐ワ紝鍘熷洜锛氫笉鏄鎵逛腑鐘舵��");
+    ErrorCode REGULARIZE_APPLICATION_NO_EXISTS = new ErrorCode(1_030_006_004, "鐢熸垚杞鐢宠鍗曞彿閲嶅锛岃閲嶈瘯");
+    ErrorCode REGULARIZE_APPLICATION_EXISTS = new ErrorCode(1_030_006_005, "璇ュ憳宸ュ凡瀛樺湪杞鐢宠锛岃鍕块噸澶嶆彁浜�");
+    ErrorCode REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_006_006, "杞瀹℃壒娴佺▼涓嶅瓨鍦�");
+
+    // ========== 鍩硅绠$悊 1-030-009-000 ==========
+    ErrorCode TRAINING_NOT_EXISTS = new ErrorCode(1_030_009_000, "鍩硅娲诲姩涓嶅瓨鍦�");
+    ErrorCode TRAINING_PARTICIPANT_NOT_EXISTS = new ErrorCode(1_030_009_001, "鍩硅鍙傝浜哄憳涓嶅瓨鍦�");
+    ErrorCode TRAINING_EMPLOYEE_LIST_EMPTY = new ErrorCode(1_030_009_002, "璇烽�夋嫨鍙傝鍛樺伐");
+    ErrorCode TRAINING_CERT_VALIDITY_REQUIRED = new ErrorCode(1_030_009_003, "鍚敤鑷姩鍙戣瘉鏃跺繀椤诲~鍐欒瘉涔︽湁鏁堟湡锛堟湀锛�");
+    ErrorCode TRAINING_CERT_TYPE_NOT_SUPPORT = new ErrorCode(1_030_009_004, "浠呭畨鍏ㄥ煿璁�/鎶�鑳藉煿璁敮鎸佽嚜鍔ㄥ彂璇�");
+    ErrorCode TRAINING_LOCKED = new ErrorCode(1_030_009_005, "鍩硅宸插畬鎴愭垨宸插彇娑堬紝涓嶅彲淇敼");
+    ErrorCode TRAINING_COMPLETE_FAIL_STATUS = new ErrorCode(1_030_009_006, "鍩硅瀹屾垚鎿嶄綔澶辫触锛屽師鍥狅細浠呰崏绋�/寰呭紑灞曠姸鎬佸彲瀹屾垚");
+    ErrorCode TRAINING_PARTICIPANT_EXISTS = new ErrorCode(1_030_009_007, "璇ュ憳宸ュ凡鍦ㄦ湰娆″煿璁弬璁悕鍗曚腑锛岃鍕块噸澶嶆坊鍔�");
+
 }
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmploymentTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmploymentTypeEnum.java
new file mode 100644
index 0000000..5a781b0
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmploymentTypeEnum.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.hrm.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鐢ㄥ伐鎬ц川鏋氫妇
+ */
+@RequiredArgsConstructor
+@Getter
+public enum HrmEmploymentTypeEnum implements ArrayValuable<Integer> {
+
+    FORMAL(1, "姝e紡"),
+    LABOR(2, "鍔冲姟"),
+    OUTSOURCING(3, "澶栧寘"),
+    INTERNSHIP(4, "瀹炰範"),
+    RETIRED_REHIRE(5, "閫�浼戣繑鑱�"),
+    OTHER(6, "鍏朵粬");
+
+    private final Integer type;
+    private final String name;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmEmploymentTypeEnum::getType).toArray(Integer[]::new);
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+    public static String getNameByType(Integer type) {
+        return Arrays.stream(values())
+                .filter(e -> e.getType().equals(type))
+                .findFirst()
+                .map(HrmEmploymentTypeEnum::getName)
+                .orElse("");
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/pom.xml b/yudao-module-hrm/pom.xml
index 58cea46..35e378e 100644
--- a/yudao-module-hrm/pom.xml
+++ b/yudao-module-hrm/pom.xml
@@ -49,6 +49,12 @@
             <artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
         </dependency>
 
+        <!-- 瀹氭椂浠诲姟鐩稿叧锛氳瘉涔�/鍚堝悓鍒版湡鎻愰啋姣忔棩浠诲姟 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-job</artifactId>
+        </dependency>
+
         <!-- Web 鐩稿叧 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApiImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApiImpl.java
new file mode 100644
index 0000000..ef99613
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/api/workhour/HrmWorkHourSummaryApiImpl.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.hrm.api.workhour;
+
+import cn.iocoder.yudao.module.hrm.api.workhour.dto.HrmWorkHourSummaryRespDTO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.workhour.HrmWorkHourSummaryDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.workhour.HrmWorkHourSummaryMapper;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+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.util.List;
+
+/**
+ * 鏈堝害宸ユ椂姹囨�� API 瀹炵幇
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+public class HrmWorkHourSummaryApiImpl implements HrmWorkHourSummaryApi {
+
+    @Resource
+    private HrmWorkHourSummaryMapper workHourSummaryMapper;
+    @Resource
+    private HrmEmployeeService employeeService;
+    @Resource
+    private DeptApi deptApi;
+
+    @Override
+    public List<HrmWorkHourSummaryRespDTO> getSummaryByPeriod(String period) {
+        return workHourSummaryMapper.selectListByPeriod(period).stream()
+                .map(this::convert)
+                .toList();
+    }
+
+    @Override
+    public HrmWorkHourSummaryRespDTO getSummaryByEmployeeAndPeriod(Long employeeId, String period) {
+        HrmWorkHourSummaryDO summary = workHourSummaryMapper.selectByEmployeeIdAndPeriod(employeeId, period);
+        return summary == null ? null : convert(summary);
+    }
+
+    private HrmWorkHourSummaryRespDTO convert(HrmWorkHourSummaryDO summary) {
+        HrmWorkHourSummaryRespDTO dto = new HrmWorkHourSummaryRespDTO();
+        dto.setEmployeeId(summary.getEmployeeId());
+        dto.setDeptId(summary.getDeptId());
+        dto.setPeriod(summary.getPeriod());
+        dto.setWorkDays(summary.getWorkDays());
+        dto.setWorkHours(summary.getWorkHours());
+        dto.setOvertimeHours(summary.getOvertimeHours());
+        HrmEmployeeDO employee = employeeService.getEmployee(summary.getEmployeeId());
+        if (employee != null) {
+            dto.setEmployeeNo(employee.getEmployeeNo());
+            dto.setEmployeeName(employee.getName());
+        }
+        if (summary.getDeptId() != null) {
+            DeptRespDTO dept = deptApi.getDept(summary.getDeptId());
+            if (dept != null) {
+                dto.setDeptName(dept.getName());
+            }
+        }
+        return dto;
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmRegularizeApplicationController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmRegularizeApplicationController.java
new file mode 100644
index 0000000..ff7840e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmRegularizeApplicationController.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmRegularizeApplicationService;
+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 java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 杞鐢宠")
+@RestController
+@RequestMapping("/hrm/regularize")
+@Validated
+public class HrmRegularizeApplicationController {
+
+    @Resource
+    private HrmRegularizeApplicationService regularizeApplicationService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓杞鐢宠")
+    @PreAuthorize("@ss.hasPermission('hrm:regularize:create')")
+    public CommonResult<Long> createRegularizeApplication(@Valid @RequestBody HrmRegularizeApplicationSaveReqVO createReqVO) {
+        return success(regularizeApplicationService.createRegularizeApplication(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊杞鐢宠")
+    @PreAuthorize("@ss.hasPermission('hrm:regularize:update')")
+    public CommonResult<Boolean> updateRegularizeApplication(@Valid @RequestBody HrmRegularizeApplicationSaveReqVO updateReqVO) {
+        regularizeApplicationService.updateRegularizeApplication(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎杞鐢宠")
+    @Parameter(name = "id", description = "鐢宠ID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:regularize:delete')")
+    public CommonResult<Boolean> deleteRegularizeApplication(@RequestParam("id") Long id) {
+        regularizeApplicationService.deleteRegularizeApplication(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱杞鐢宠璇︽儏")
+    @Parameter(name = "id", description = "鐢宠ID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:regularize:query')")
+    public CommonResult<HrmRegularizeApplicationRespVO> getRegularizeApplication(@RequestParam("id") Long id) {
+        return success(regularizeApplicationService.getRegularizeApplicationDetail(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱杞鐢宠鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('hrm:regularize:query')")
+    public CommonResult<PageResult<HrmRegularizeApplicationRespVO>> getRegularizeApplicationPage(@Valid HrmRegularizeApplicationPageReqVO pageReqVO) {
+        return success(regularizeApplicationService.getRegularizeApplicationPage(pageReqVO));
+    }
+
+    @PostMapping("/submit")
+    @Operation(summary = "鎻愪氦杞鐢宠瀹℃壒")
+    @Parameter(name = "id", description = "鐢宠ID", required = true)
+    @Parameter(name = "processDefinitionKey", description = "娴佺▼瀹氫箟Key", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:regularize:submit')")
+    public CommonResult<Boolean> submitRegularizeApplication(@RequestParam("id") Long id,
+                                                             @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        Long userId = SecurityFrameworkUtils.getLoginUserId();
+        regularizeApplicationService.submitRegularizeApplication(id, processDefinitionKey, userId);
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-definition-list")
+    @Operation(summary = "鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔夊垪琛�")
+    @PreAuthorize("@ss.hasPermission('hrm:regularize:query')")
+    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
+        return success(regularizeApplicationService.getApproveProcessDefinitionList());
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationPageReqVO.java
new file mode 100644
index 0000000..6d27382
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationPageReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval.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 org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - 杞鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmRegularizeApplicationPageReqVO extends PageParam {
+
+    @Schema(description = "鐢宠鍗曞彿", example = "ZZ202401001")
+    private String no;
+
+    @Schema(description = "鐢宠浜篒D", example = "1")
+    private Long userId;
+
+    @Schema(description = "閮ㄩ棬ID", example = "1")
+    private Long deptId;
+
+    @Schema(description = "褰撳墠鐢ㄥ伐鎬ц川", example = "2")
+    private Integer employmentType;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationRespVO.java
new file mode 100644
index 0000000..85ba6c9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationRespVO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 杞鐢宠 Response VO")
+@Data
+public class HrmRegularizeApplicationRespVO {
+
+    @Schema(description = "鐢宠ID", example = "1")
+    private Long id;
+
+    @Schema(description = "鐢宠鍗曞彿", example = "ZZ202401001")
+    private String no;
+
+    @Schema(description = "鐢宠浜篒D", example = "1")
+    private Long userId;
+
+    @Schema(description = "鐢宠浜哄鍚�", example = "寮犱笁")
+    private String userName;
+
+    @Schema(description = "閮ㄩ棬ID", example = "1")
+    private Long deptId;
+
+    @Schema(description = "閮ㄩ棬鍚嶇О", example = "鐮斿彂閮�")
+    private String deptName;
+
+    @Schema(description = "宀椾綅ID", example = "1")
+    private Long postId;
+
+    @Schema(description = "宀椾綅鍚嶇О", example = "Java 宸ョ▼甯�")
+    private String postName;
+
+    @Schema(description = "褰撳墠鐢ㄥ伐鎬ц川", example = "2")
+    private Integer employmentType;
+
+    @Schema(description = "褰撳墠鐢ㄥ伐鎬ц川鍚嶇О", example = "鍔冲姟")
+    private String employmentTypeName;
+
+    @Schema(description = "鎷熻浆姝f棩鏈�", example = "2024-02-01")
+    private LocalDate expectedDate;
+
+    @Schema(description = "杞鍘熷洜", example = "璇曠敤鏈熻〃鐜板悎鏍�")
+    private String reason;
+
+    @Schema(description = "瀹℃壒鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "瀹℃壒鐘舵�佸悕绉�", example = "鑽夌")
+    private String statusName;
+
+    @Schema(description = "BPM 娴佺▼瀹炰緥ID", example = "xxx")
+    private String processInstanceId;
+
+    @Schema(description = "瀹℃壒瀹屾垚鏃堕棿", example = "2024-01-01 10:00:00")
+    private LocalDateTime approveTime;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", example = "2024-01-01 09:00:00")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationSaveReqVO.java
new file mode 100644
index 0000000..7023f7d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationSaveReqVO.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval.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 = "绠$悊鍚庡彴 - 杞鐢宠鏂板/淇敼 Request VO")
+@Data
+public class HrmRegularizeApplicationSaveReqVO {
+
+    @Schema(description = "鐢宠ID", example = "1")
+    private Long id;
+
+    @Schema(description = "鐢宠浜篒D", example = "1")
+    private Long userId;
+
+    @Schema(description = "閮ㄩ棬ID", example = "1")
+    private Long deptId;
+
+    @Schema(description = "宀椾綅ID", example = "1")
+    private Long postId;
+
+    @Schema(description = "褰撳墠鐢ㄥ伐鎬ц川锛�1-姝e紡锛�2-鍔冲姟锛�3-澶栧寘锛�4-瀹炰範锛�5-閫�浼戣繑鑱橈紝6-鍏朵粬", example = "2")
+    private Integer employmentType;
+
+    @Schema(description = "鎷熻浆姝f棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-01")
+    @NotNull(message = "鎷熻浆姝f棩鏈熶笉鑳戒负绌�")
+    private LocalDate expectedDate;
+
+    @Schema(description = "杞鍘熷洜", requiredMode = Schema.RequiredMode.REQUIRED, example = "璇曠敤鏈熻〃鐜板悎鏍�")
+    @NotEmpty(message = "杞鍘熷洜涓嶈兘涓虹┖")
+    private String reason;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java
index 3854488..91f4a65 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java
@@ -12,6 +12,7 @@
 import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
 import cn.iocoder.yudao.module.hrm.service.certificate.HrmCertificateService;
 import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.remind.HrmExpiryRemindService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,6 +40,8 @@
     private HrmCertificateService certificateService;
     @Resource
     private HrmEmployeeService employeeService;
+    @Resource
+    private HrmExpiryRemindService expiryRemindService;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓璇佷功璧勮川")
@@ -77,6 +80,11 @@
     @Operation(summary = "鑾峰緱璇佷功璧勮川鍒嗛〉")
     @PreAuthorize("@ss.hasPermission('hrm:certificate:query')")
     public CommonResult<PageResult<HrmCertificateRespVO>> getCertificatePage(@Valid HrmCertificatePageReqVO pageReqVO) {
+        // 鎳掕Е鍙戯細鏈変汉鎵撳紑璇佷功鍒楄〃鏃堕『甯﹁ˉ鎵嵆灏嗗埌鏈熻瘉涔﹀苟鍙戦�佺珯鍐呬俊鎻愰啋锛坆est-effort锛�
+        try {
+            expiryRemindService.sendCertificateExpireRemind();
+        } catch (Exception ignored) {
+        }
         PageResult<HrmCertificateDO> pageResult = certificateService.getCertificatePage(pageReqVO);
         if (CollUtil.isEmpty(pageResult.getList())) {
             return success(PageResult.empty(pageResult.getTotal()));
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java
index c816d40..7fb9653 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/vo/HrmCertificatePageReqVO.java
@@ -21,4 +21,7 @@
     @Schema(description = "璇佷功鍚嶇О", example = "楂樺帇鐢靛伐鐗圭浣滀笟鎿嶄綔璇�")
     private String certName;
 
+    @Schema(description = "鍒版湡绫诲瀷锛�1=鍗冲皢鍒版湡锛�2=宸茶繃鏈燂級锛岄厤鍚堝悎瑙勫垪琛ㄧ瓫閫�", example = "1")
+    private Integer expiryType;
+
 }
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java
index d956af0..6668efd 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java
@@ -7,6 +7,7 @@
 import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractSaveReqVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractTerminateReqVO;
 import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeContractService;
+import cn.iocoder.yudao.module.hrm.service.remind.HrmExpiryRemindService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -26,6 +27,8 @@
 
     @Resource
     private HrmEmployeeContractService contractService;
+    @Resource
+    private HrmExpiryRemindService expiryRemindService;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓鍛樺伐鍚堝悓")
@@ -71,6 +74,11 @@
     @Operation(summary = "鑾峰緱鍛樺伐鍚堝悓鍒嗛〉")
     @PreAuthorize("@ss.hasPermission('hrm:employee-contract:query')")
     public CommonResult<PageResult<HrmEmployeeContractRespVO>> getContractPage(@Valid HrmEmployeeContractPageReqVO pageReqVO) {
+        // 鎳掕Е鍙戯細鏈変汉鎵撳紑鍚堝悓鍒楄〃鏃堕『甯﹁ˉ鎵嵆灏嗗埌鏈熷悎鍚屽苟鍙戦�佺珯鍐呬俊鎻愰啋锛坆est-effort锛�
+        try {
+            expiryRemindService.sendContractExpireRemind();
+        } catch (Exception ignored) {
+        }
         return success(contractService.getContractPage(pageReqVO));
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
index 989e1ce..86c357c 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
@@ -17,7 +17,10 @@
 import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSimpleRespVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmEmploymentTypeEnum;
 import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.mes.api.cal.MesCalTeamApi;
+import cn.iocoder.yudao.module.mes.api.cal.dto.MesCalTeamRespDTO;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.PostApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -56,6 +59,8 @@
     private PostApi postApi;
     @Resource
     private RoleApi roleApi;
+    @Resource
+    private MesCalTeamApi mesCalTeamApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓鍛樺伐")
@@ -121,7 +126,16 @@
                 .collect(Collectors.toSet());
         Map<Long, PostRespDTO> postMap = CollUtil.isEmpty(postIds) ? Collections.emptyMap() : postApi.getPostMap(postIds);
 
-        // 3. 鏀堕泦鎵�鏈夎鑹睮D
+        // 3. 鎵归噺鑾峰彇鐝粍淇℃伅锛堟墍灞炵彮缁勶級
+        Set<Long> teamIds = employeeList.stream()
+                .map(HrmEmployeeDO::getTeamId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, String> teamNameMap = CollUtil.isEmpty(teamIds) ? Collections.emptyMap() : mesCalTeamApi
+                .getTeamList(teamIds).stream()
+                .collect(Collectors.toMap(MesCalTeamRespDTO::getId, MesCalTeamRespDTO::getName, (v1, v2) -> v1));
+
+        // 4. 鏀堕泦鎵�鏈夎鑹睮D
         Set<Long> allRoleIds = new HashSet<>();
         for (HrmEmployeeDO employee : employeeList) {
             if (StrUtil.isNotBlank(employee.getRoleIds())) {
@@ -148,6 +162,16 @@
                 respVO.setPostName(postMap.get(employee.getPostId()).getName());
             }
 
+            // 濉厖鐢ㄥ伐鎬ц川鍚嶇О
+            if (employee.getEmploymentType() != null) {
+                respVO.setEmploymentTypeName(HrmEmploymentTypeEnum.getNameByType(employee.getEmploymentType()));
+            }
+
+            // 濉厖鎵�灞炵彮缁勫悕绉�
+            if (employee.getTeamId() != null && teamNameMap.containsKey(employee.getTeamId())) {
+                respVO.setTeamName(teamNameMap.get(employee.getTeamId()));
+            }
+
             // 瑙f瀽 roleIds 骞跺~鍏呰鑹插悕绉�
             if (StrUtil.isNotBlank(employee.getRoleIds())) {
                 List<Long> roleIds = JSONUtil.toList(employee.getRoleIds(), Long.class);
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
index 262364a..d4988aa 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
@@ -39,6 +39,20 @@
     @ExcelProperty("宀椾綅鍚嶇О")
     private String postName;
 
+    @Schema(description = "鐢ㄥ伐鎬ц川")
+    @ExcelProperty("鐢ㄥ伐鎬ц川")
+    private Integer employmentType;
+
+    @Schema(description = "鐢ㄥ伐鎬ц川鍚嶇О")
+    private String employmentTypeName;
+
+    @Schema(description = "鎵�灞炵彮缁処D")
+    private Long teamId;
+
+    @Schema(description = "鎵�灞炵彮缁勫悕绉�")
+    @ExcelProperty("鎵�灞炵彮缁�")
+    private String teamName;
+
     @Schema(description = "鍛樺伐濮撳悕")
     @ExcelProperty("鍛樺伐濮撳悕")
     private String name;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
index 35fe0a2..fbe9c3d 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
@@ -27,6 +27,12 @@
     @NotNull(message = "宀椾綅涓嶈兘涓虹┖")
     private Long postId;
 
+    @Schema(description = "鐢ㄥ伐鎬ц川", example = "1")
+    private Integer employmentType;
+
+    @Schema(description = "鎵�灞炵彮缁処D", example = "1")
+    private Long teamId;
+
     @Schema(description = "鍛樺伐濮撳悕", requiredMode = Schema.RequiredMode.REQUIRED, example = "寮犱笁")
     @NotEmpty(message = "鍛樺伐濮撳悕涓嶈兘涓虹┖")
     private String name;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java
index 894d5f9..31b1107 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 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;
@@ -179,10 +181,21 @@
         return success(buildResultRespVO(result, employeeService.getEmployee(employeeId)));
     }
 
+    @PostMapping("/generate-appraisees")
+    @Operation(summary = "鐢熸垚琚�冩牳浜猴紙鎸夎�冩牳鑼冨洿锛屽箓绛夛級")
+    @Parameter(name = "appraiseId", description = "鏂规缂栧彿", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:update')")
+    public CommonResult<Integer> generateAppraisees(@RequestParam("appraiseId") Long appraiseId) {
+        return success(appraiseService.generateAppraisees(appraiseId));
+    }
+
     // ========== 鍐呴儴鏂规硶锛氭嫾瑁� VO ==========
 
     private HrmPerformanceAppraiseRespVO buildAppraiseRespVO(HrmPerformanceAppraiseDO appraise) {
         HrmPerformanceAppraiseRespVO vo = BeanUtils.toBean(appraise, HrmPerformanceAppraiseRespVO.class);
+        if (StrUtil.isNotEmpty(appraise.getScopeTeamIds())) {
+            vo.setScopeTeamIds(JSONUtil.toList(appraise.getScopeTeamIds(), Long.class));
+        }
         vo.setStatusName(convertAppraiseStatusName(appraise.getStatus()));
         vo.setAuditStatusName(HrmAuditStatusEnum.getNameByStatus(appraise.getAuditStatus()));
         vo.setItemCount(appraiseService.getAppraiseItemList(appraise.getId()).size());
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java
index 19c04c4..2e73509 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java
@@ -8,6 +8,7 @@
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * HRM 缁╂晥鑰冩牳鏂规 Response VO
@@ -39,6 +40,9 @@
     @DictFormat(cn.iocoder.yudao.module.hrm.enums.DictTypeConstants.HRM_PERFORMANCE_SCOPE_TYPE)
     private Integer scopeType;
 
+    @Schema(description = "鎸囧畾鐝粍缂栧彿闆嗗悎锛坰copeType=2 鏃剁敓鏁堬紝鍏宠仈 MES 鎺掔彮缁勶級")
+    private List<Long> scopeTeamIds;
+
     @Schema(description = "鏂规鐘舵��", example = "0")
     private Integer status;
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java
index 648790c..fc09368 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseSaveReqVO.java
@@ -4,6 +4,8 @@
 import jakarta.validation.constraints.NotEmpty;
 import lombok.Data;
 
+import java.util.List;
+
 /**
  * HRM 缁╂晥鑰冩牳鏂规 淇濆瓨 Request VO
  */
@@ -27,6 +29,9 @@
     @Schema(description = "鑰冩牳鑼冨洿绫诲瀷: 1-鍏ㄩ儴鍛樺伐 2-鎸囧畾鐝粍", example = "1")
     private Integer scopeType;
 
+    @Schema(description = "鎸囧畾鐝粍缂栧彿闆嗗悎锛坰copeType=2 鏃跺繀濉紝鍏宠仈 MES 鎺掔彮缁勶級")
+    private List<Long> scopeTeamIds;
+
     @Schema(description = "澶囨敞")
     private String remark;
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSalaryController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSalaryController.java
new file mode 100644
index 0000000..5512ada
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSalaryController.java
@@ -0,0 +1,188 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+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.salary.vo.HrmEmployeeSalaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmEmployeeSalaryRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmEmployeeSalarySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmEmployeeSalaryService;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryStructureService;
+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.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍛樺伐钖叕妗f")
+@RestController
+@RequestMapping("/hrm/employee-salary")
+@Validated
+public class HrmEmployeeSalaryController {
+
+    @Resource
+    private HrmEmployeeSalaryService employeeSalaryService;
+    @Resource
+    private HrmEmployeeService employeeService;
+    @Resource
+    private HrmSalaryStructureService structureService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鍛樺伐钖叕妗f")
+    @PreAuthorize("@ss.hasPermission('hrm:salary:create')")
+    public CommonResult<Long> createEmployeeSalary(@Valid @RequestBody HrmEmployeeSalarySaveReqVO createReqVO) {
+        HrmEmployeeSalaryDO salary = BeanUtils.toBean(createReqVO, HrmEmployeeSalaryDO.class);
+        return success(employeeSalaryService.createEmployeeSalary(salary));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鍛樺伐钖叕妗f")
+    @PreAuthorize("@ss.hasPermission('hrm:salary:update')")
+    public CommonResult<Boolean> updateEmployeeSalary(@Valid @RequestBody HrmEmployeeSalarySaveReqVO updateReqVO) {
+        HrmEmployeeSalaryDO salary = BeanUtils.toBean(updateReqVO, HrmEmployeeSalaryDO.class);
+        employeeSalaryService.updateEmployeeSalary(salary);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鍛樺伐钖叕妗f")
+    @Parameter(name = "id", description = "妗fID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:salary:delete')")
+    public CommonResult<Boolean> deleteEmployeeSalary(@RequestParam("id") Long id) {
+        employeeSalaryService.deleteEmployeeSalary(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰彇鍛樺伐钖叕妗f")
+    @Parameter(name = "id", description = "妗fID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    public CommonResult<HrmEmployeeSalaryRespVO> getEmployeeSalary(@RequestParam("id") Long id) {
+        HrmEmployeeSalaryDO salary = employeeSalaryService.getEmployeeSalary(id);
+        if (salary == null) {
+            return success(null);
+        }
+        HrmEmployeeSalaryRespVO respVO = BeanUtils.toBean(salary, HrmEmployeeSalaryRespVO.class);
+        fillEmployeeInfo(respVO, salary.getUserId());
+        fillStructureInfo(respVO, salary.getSalaryStructureId());
+        return success(respVO);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰彇鍛樺伐钖叕妗f鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    public CommonResult<PageResult<HrmEmployeeSalaryRespVO>> getEmployeeSalaryPage(
+            @Valid HrmEmployeeSalaryPageReqVO pageReqVO) {
+        PageResult<HrmEmployeeSalaryDO> pageResult = employeeSalaryService.getEmployeeSalaryPage(pageReqVO);
+        List<HrmEmployeeSalaryDO> recordList = pageResult.getList();
+        if (CollUtil.isEmpty(recordList)) {
+            return success(new PageResult<>(Collections.emptyList(), pageResult.getTotal()));
+        }
+        // 鎵归噺鑾峰彇鍛樺伐淇℃伅
+        Set<Long> employeeIds = new HashSet<>();
+        Set<Long> structureIds = new HashSet<>();
+        for (HrmEmployeeSalaryDO record : recordList) {
+            if (record.getUserId() != null) {
+                employeeIds.add(record.getUserId());
+            }
+            if (record.getSalaryStructureId() != null) {
+                structureIds.add(record.getSalaryStructureId());
+            }
+        }
+        Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
+        for (Long employeeId : employeeIds) {
+            HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+            if (employee != null) {
+                employeeMap.put(employeeId, employee);
+            }
+        }
+        Map<Long, HrmSalaryStructureDO> structureMap = new HashMap<>();
+        for (Long structureId : structureIds) {
+            HrmSalaryStructureDO structure = structureService.getStructure(structureId);
+            if (structure != null) {
+                structureMap.put(structureId, structure);
+            }
+        }
+        List<HrmEmployeeSalaryRespVO> list = recordList.stream()
+                .map(salary -> {
+                    HrmEmployeeSalaryRespVO respVO = BeanUtils.toBean(salary, HrmEmployeeSalaryRespVO.class);
+                    HrmEmployeeDO employee = employeeMap.get(salary.getUserId());
+                    if (employee != null) {
+                        respVO.setEmployeeNo(employee.getEmployeeNo());
+                        respVO.setEmployeeName(employee.getName());
+                    }
+                    if (salary.getSalaryStructureId() != null) {
+                        HrmSalaryStructureDO structure = structureMap.get(salary.getSalaryStructureId());
+                        if (structure != null) {
+                            respVO.setSalaryStructureName(structure.getName());
+                        }
+                    }
+                    return respVO;
+                })
+                .toList();
+        return success(new PageResult<>(list, pageResult.getTotal()));
+    }
+
+    @GetMapping("/list-by-user")
+    @Operation(summary = "鑾峰彇鍛樺伐钖叕妗f鍘嗗彶鍒楄〃")
+    @Parameter(name = "userId", description = "鍛樺伐ID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    public CommonResult<List<HrmEmployeeSalaryRespVO>> getEmployeeSalaryList(
+            @RequestParam("userId") Long userId) {
+        List<HrmEmployeeSalaryDO> list = employeeSalaryService.getEmployeeSalaryList(userId);
+        return success(list.stream()
+                .map(salary -> {
+                    HrmEmployeeSalaryRespVO respVO = BeanUtils.toBean(salary, HrmEmployeeSalaryRespVO.class);
+                    fillEmployeeInfo(respVO, salary.getUserId());
+                    fillStructureInfo(respVO, salary.getSalaryStructureId());
+                    return respVO;
+                })
+                .toList());
+    }
+
+    @GetMapping("/get-latest")
+    @Operation(summary = "鑾峰彇鍛樺伐鏈�鏂扮敓鏁堢殑钖叕妗f")
+    @Parameter(name = "userId", description = "鍛樺伐ID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    public CommonResult<HrmEmployeeSalaryRespVO> getLatestEmployeeSalary(
+            @RequestParam("userId") Long userId) {
+        HrmEmployeeSalaryDO salary = employeeSalaryService.getLatestEmployeeSalary(userId);
+        if (salary == null) {
+            return success(null);
+        }
+        HrmEmployeeSalaryRespVO respVO = BeanUtils.toBean(salary, HrmEmployeeSalaryRespVO.class);
+        fillEmployeeInfo(respVO, salary.getUserId());
+        fillStructureInfo(respVO, salary.getSalaryStructureId());
+        return success(respVO);
+    }
+
+    private void fillEmployeeInfo(HrmEmployeeSalaryRespVO respVO, Long userId) {
+        HrmEmployeeDO employee = employeeService.getEmployee(userId);
+        if (employee != null) {
+            respVO.setEmployeeNo(employee.getEmployeeNo());
+            respVO.setEmployeeName(employee.getName());
+        }
+    }
+
+    private void fillStructureInfo(HrmEmployeeSalaryRespVO respVO, Long structureId) {
+        if (structureId != null) {
+            HrmSalaryStructureDO structure = structureService.getStructure(structureId);
+            if (structure != null) {
+                respVO.setSalaryStructureName(structure.getName());
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java
new file mode 100644
index 0000000..d34a446
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+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.salary.vo.HrmSalaryStructurePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryStructureRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryStructureSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryStructureService;
+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 = "绠$悊鍚庡彴 - HRM 钖叕缁撴瀯")
+@RestController
+@RequestMapping("/hrm/salary/structure")
+@Validated
+public class HrmSalaryStructureController {
+
+    @Resource
+    private HrmSalaryStructureService structureService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓钖叕缁撴瀯")
+    @PreAuthorize("@ss.hasPermission('hrm:salary:create')")
+    public CommonResult<Long> createStructure(@Valid @RequestBody HrmSalaryStructureSaveReqVO createReqVO) {
+        HrmSalaryStructureDO structure = BeanUtils.toBean(createReqVO, HrmSalaryStructureDO.class);
+        return success(structureService.createStructure(structure));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊钖叕缁撴瀯")
+    @PreAuthorize("@ss.hasPermission('hrm:salary:update')")
+    public CommonResult<Boolean> updateStructure(@Valid @RequestBody HrmSalaryStructureSaveReqVO updateReqVO) {
+        HrmSalaryStructureDO structure = BeanUtils.toBean(updateReqVO, HrmSalaryStructureDO.class);
+        structureService.updateStructure(structure);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎钖叕缁撴瀯")
+    @Parameter(name = "id", description = "缁撴瀯ID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:salary:delete')")
+    public CommonResult<Boolean> deleteStructure(@RequestParam("id") Long id) {
+        structureService.deleteStructure(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰彇钖叕缁撴瀯")
+    @Parameter(name = "id", description = "缁撴瀯ID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    public CommonResult<HrmSalaryStructureRespVO> getStructure(@RequestParam("id") Long id) {
+        HrmSalaryStructureDO structure = structureService.getStructure(id);
+        return success(BeanUtils.toBean(structure, HrmSalaryStructureRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰彇钖叕缁撴瀯鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    public CommonResult<PageResult<HrmSalaryStructureRespVO>> getStructurePage(HrmSalaryStructurePageReqVO pageReqVO) {
+        PageResult<HrmSalaryStructureDO> pageResult = structureService.getStructurePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, HrmSalaryStructureRespVO.class));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鑾峰彇钖叕缁撴瀯鍒楄〃")
+    @Parameter(name = "status", description = "鐘舵��")
+    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    public CommonResult<List<HrmSalaryStructureRespVO>> getStructureList(
+            @RequestParam(value = "status", required = false) Integer status) {
+        List<HrmSalaryStructureDO> list = structureService.getStructureList(status);
+        return success(BeanUtils.toBean(list, HrmSalaryStructureRespVO.class));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryPageReqVO.java
new file mode 100644
index 0000000..401d41b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryPageReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.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 = "绠$悊鍚庡彴 - 鍛樺伐钖叕妗f鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmEmployeeSalaryPageReqVO extends PageParam {
+
+    @Schema(description = "鍛樺伐ID", example = "1024")
+    private Long userId;
+
+    @Schema(description = "钖叕缁撴瀯ID", example = "1")
+    private Long salaryStructureId;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+    private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryRespVO.java
new file mode 100644
index 0000000..7726161
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalaryRespVO.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐钖叕妗f Response VO")
+@Data
+public class HrmEmployeeSalaryRespVO {
+
+    @Schema(description = "妗fID", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long id;
+
+    @Schema(description = "鍛樺伐ID")
+    private Long userId;
+
+    @Schema(description = "鍛樺伐缂栧彿")
+    private String employeeNo;
+
+    @Schema(description = "鍛樺伐濮撳悕")
+    private String employeeName;
+
+    @Schema(description = "钖叕缁撴瀯ID")
+    private Long salaryStructureId;
+
+    @Schema(description = "钖叕缁撴瀯鍚嶇О")
+    private String salaryStructureName;
+
+    @Schema(description = "鍩烘湰宸ヨ祫")
+    private BigDecimal baseSalary;
+
+    @Schema(description = "缁╂晥宸ヨ祫")
+    private BigDecimal performanceSalary;
+
+    @Schema(description = "椁愯ˉ")
+    private BigDecimal mealAllowance;
+
+    @Schema(description = "浜ら�氳ˉ璐�")
+    private BigDecimal transportAllowance;
+
+    @Schema(description = "鍏朵粬琛ヨ创")
+    private BigDecimal otherAllowance;
+
+    @Schema(description = "鐢熸晥鏃ユ湡")
+    private LocalDate effectiveDate;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalarySaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalarySaveReqVO.java
new file mode 100644
index 0000000..5e7b822
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSalarySaveReqVO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐钖叕妗f 鏂板/淇敼 Request VO")
+@Data
+public class HrmEmployeeSalarySaveReqVO {
+
+    @Schema(description = "妗fID", example = "1")
+    private Long id;
+
+    @Schema(description = "鍛樺伐ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "鍛樺伐涓嶈兘涓虹┖")
+    private Long userId;
+
+    @Schema(description = "钖叕缁撴瀯ID", example = "1")
+    private Long salaryStructureId;
+
+    @Schema(description = "鍩烘湰宸ヨ祫", example = "8000.00")
+    private BigDecimal baseSalary;
+
+    @Schema(description = "缁╂晥宸ヨ祫", example = "2000.00")
+    private BigDecimal performanceSalary;
+
+    @Schema(description = "椁愯ˉ", example = "300.00")
+    private BigDecimal mealAllowance;
+
+    @Schema(description = "浜ら�氳ˉ璐�", example = "200.00")
+    private BigDecimal transportAllowance;
+
+    @Schema(description = "鍏朵粬琛ヨ创", example = "100.00")
+    private BigDecimal otherAllowance;
+
+    @Schema(description = "鐢熸晥鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "鐢熸晥鏃ユ湡涓嶈兘涓虹┖")
+    private LocalDate effectiveDate;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructurePageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructurePageReqVO.java
new file mode 100644
index 0000000..7de7c72
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructurePageReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.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 = "绠$悊鍚庡彴 - 钖叕缁撴瀯鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmSalaryStructurePageReqVO extends PageParam {
+
+    @Schema(description = "缁撴瀯鍚嶇О", example = "鐢熶骇宀�")
+    private String name;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+    private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java
new file mode 100644
index 0000000..a3e1c59
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖叕缁撴瀯 Response VO")
+@Data
+public class HrmSalaryStructureRespVO {
+
+    @Schema(description = "缁撴瀯ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long id;
+
+    @Schema(description = "缁撴瀯鍚嶇О")
+    private String name;
+
+    @Schema(description = "鍩烘湰宸ヨ祫")
+    private BigDecimal baseSalary;
+
+    @Schema(description = "缁╂晥宸ヨ祫鍗犳瘮锛�%锛�")
+    private BigDecimal performanceRatio;
+
+    @Schema(description = "鍔犵彮宸ヨ祫鍊嶇巼")
+    private BigDecimal overtimePayRatio;
+
+    @Schema(description = "绀句繚鍩烘暟")
+    private BigDecimal socialSecurityBase;
+
+    @Schema(description = "鍏Н閲戝熀鏁�")
+    private BigDecimal housingFundBase;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java
new file mode 100644
index 0000000..417fdbc
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.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;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖叕缁撴瀯 鏂板/淇敼 Request VO")
+@Data
+public class HrmSalaryStructureSaveReqVO {
+
+    @Schema(description = "缁撴瀯ID", example = "1")
+    private Long id;
+
+    @Schema(description = "缁撴瀯鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鐢熶骇宀楄柂閰粨鏋�")
+    @NotEmpty(message = "缁撴瀯鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "鍩烘湰宸ヨ祫", example = "8000.00")
+    private BigDecimal baseSalary;
+
+    @Schema(description = "缁╂晥宸ヨ祫鍗犳瘮锛�%锛�", example = "20")
+    private BigDecimal performanceRatio;
+
+    @Schema(description = "鍔犵彮宸ヨ祫鍊嶇巼", example = "1.5")
+    private BigDecimal overtimePayRatio;
+
+    @Schema(description = "绀句繚鍩烘暟", example = "8000.00")
+    private BigDecimal socialSecurityBase;
+
+    @Schema(description = "鍏Н閲戝熀鏁�", example = "8000.00")
+    private BigDecimal housingFundBase;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingController.java
new file mode 100644
index 0000000..d37018b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingController.java
@@ -0,0 +1,171 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training;
+
+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.training.vo.HrmTrainingPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingParticipantRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingParticipantDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.training.HrmTrainingService;
+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.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+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.module.hrm.dal.dataobject.training.HrmTrainingDO.*;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍩硅娲诲姩")
+@RestController
+@RequestMapping("/hrm/training")
+@Validated
+public class HrmTrainingController {
+
+    @Resource
+    private HrmTrainingService trainingService;
+    @Resource
+    private HrmEmployeeService employeeService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鍩硅娲诲姩锛堝垵濮嬩负鑽夌鐘舵�侊級")
+    @PreAuthorize("@ss.hasPermission('hrm:training:create')")
+    public CommonResult<Long> createTraining(@Valid @RequestBody HrmTrainingSaveReqVO createReqVO) {
+        return success(trainingService.createTraining(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鍩硅娲诲姩锛堜粎鑽夌/寰呭紑灞曞彲鏀癸紝鐘舵�佽蛋 start/complete/cancel 娴佽浆锛�")
+    @PreAuthorize("@ss.hasPermission('hrm:training:update')")
+    public CommonResult<Boolean> updateTraining(@Valid @RequestBody HrmTrainingSaveReqVO updateReqVO) {
+        trainingService.updateTraining(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鍩硅娲诲姩锛堢骇鑱斿垹闄ゅ弬璁汉鍛橈紱宸插畬鎴�/宸插彇娑堜笉鍙垹锛�")
+    @Parameter(name = "ids", description = "缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:training:delete')")
+    public CommonResult<Boolean> deleteTraining(@RequestParam("ids") List<Long> ids) {
+        trainingService.deleteTraining(ids);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鍩硅娲诲姩璇︽儏锛堝惈鍙傝浜哄憳鍒楄〃锛�")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('hrm:training:query')")
+    public CommonResult<HrmTrainingRespVO> getTraining(@RequestParam("id") Long id) {
+        HrmTrainingDO training = trainingService.getTraining(id);
+        if (training == null) {
+            return success(null);
+        }
+        HrmTrainingRespVO respVO = BeanUtils.toBean(training, HrmTrainingRespVO.class);
+        fillDisplayNames(respVO);
+        respVO.setParticipants(buildParticipantList(trainingService.getParticipantsByTrainingId(id)));
+        return success(respVO);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鍩硅娲诲姩鍒嗛〉锛堝惈鍙傝浜烘暟锛�")
+    @PreAuthorize("@ss.hasPermission('hrm:training:query')")
+    public CommonResult<PageResult<HrmTrainingRespVO>> getTrainingPage(@Valid HrmTrainingPageReqVO pageReqVO) {
+        PageResult<HrmTrainingDO> pageResult = trainingService.getTrainingPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        Map<Long, Integer> participantCountMap = trainingService.getParticipantCountMap(
+                convertSet(pageResult.getList(), HrmTrainingDO::getId));
+        return success(BeanUtils.toBean(pageResult, HrmTrainingRespVO.class, vo -> {
+            fillDisplayNames(vo);
+            vo.setParticipantCount(participantCountMap.getOrDefault(vo.getId(), 0));
+        }));
+    }
+
+    @PostMapping("/start")
+    @Operation(summary = "寮�濮嬪煿璁紙鑽夌 鈫� 寰呭紑灞曪級")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('hrm:training:update')")
+    public CommonResult<Boolean> startTraining(@RequestParam("id") Long id) {
+        trainingService.startTraining(id);
+        return success(true);
+    }
+
+    @PostMapping("/complete")
+    @Operation(summary = "瀹屾垚鍩硅锛堣崏绋�/寰呭紑灞� 鈫� 宸插畬鎴愶紱寮�鍚嚜鍔ㄥ彂璇佹椂瀵瑰悎鏍间汉鍛樿仈鍔ㄥ彂鏀捐瘉涔︼級")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('hrm:training:update')")
+    public CommonResult<Boolean> completeTraining(@RequestParam("id") Long id) {
+        trainingService.completeTraining(id);
+        return success(true);
+    }
+
+    @PostMapping("/cancel")
+    @Operation(summary = "鍙栨秷鍩硅锛堣崏绋�/寰呭紑灞� 鈫� 宸插彇娑堬級")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('hrm:training:update')")
+    public CommonResult<Boolean> cancelTraining(@RequestParam("id") Long id) {
+        trainingService.cancelTraining(id);
+        return success(true);
+    }
+
+    // ========== 绉佹湁鏂规硶 ==========
+
+    private void fillDisplayNames(HrmTrainingRespVO respVO) {
+        respVO.setTrainingTypeName(convertTrainingTypeName(respVO.getTrainingType()));
+        respVO.setStatusName(convertStatusName(respVO.getStatus()));
+    }
+
+    private List<HrmTrainingParticipantRespVO> buildParticipantList(List<HrmTrainingParticipantDO> participants) {
+        if (CollUtil.isEmpty(participants)) {
+            return new ArrayList<>();
+        }
+        Map<Long, HrmEmployeeDO> employeeMap = employeeService.getEmployeeList(
+                        convertSet(participants, HrmTrainingParticipantDO::getEmployeeId)).stream()
+                .collect(java.util.stream.Collectors.toMap(HrmEmployeeDO::getId, e -> e, (a, b) -> a));
+        List<HrmTrainingParticipantRespVO> voList = BeanUtils.toBean(participants, HrmTrainingParticipantRespVO.class);
+        for (HrmTrainingParticipantRespVO vo : voList) {
+            HrmEmployeeDO employee = employeeMap.get(vo.getEmployeeId());
+            if (employee != null) {
+                vo.setEmployeeName(employee.getName());
+                vo.setEmployeeNo(employee.getEmployeeNo());
+            }
+        }
+        return voList;
+    }
+
+    private String convertTrainingTypeName(Integer trainingType) {
+        return switch (trainingType == null ? 0 : trainingType) {
+            case TYPE_SAFETY -> "瀹夊叏鍩硅";
+            case TYPE_SKILL -> "鎶�鑳藉煿璁�";
+            case TYPE_MANAGE -> "绠$悊鍩硅";
+            case TYPE_OTHER -> "鍏朵粬";
+            default -> "";
+        };
+    }
+
+    private String convertStatusName(Integer status) {
+        return switch (status == null ? -1 : status) {
+            case STATUS_DRAFT -> "鑽夌";
+            case STATUS_PLANNED -> "寰呭紑灞�";
+            case STATUS_COMPLETED -> "宸插畬鎴�";
+            case STATUS_CANCELED -> "宸插彇娑�";
+            default -> "";
+        };
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingParticipantController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingParticipantController.java
new file mode 100644
index 0000000..ead9861
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/HrmTrainingParticipantController.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingParticipantAddReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingParticipantRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingParticipantUpdateReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingParticipantDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.training.HrmTrainingService;
+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.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍩硅鍙傝浜哄憳")
+@RestController
+@RequestMapping("/hrm/training-participant")
+@Validated
+public class HrmTrainingParticipantController {
+
+    @Resource
+    private HrmTrainingService trainingService;
+    @Resource
+    private HrmEmployeeService employeeService;
+
+    @PostMapping("/add")
+    @Operation(summary = "鎵归噺娣诲姞鍙傝鍛樺伐锛堣嚜鍔ㄨ烦杩囧凡鍦ㄥ悕鍗曚腑鐨勫憳宸ワ級")
+    @PreAuthorize("@ss.hasPermission('hrm:training:update')")
+    public CommonResult<Integer> addParticipants(@Valid @RequestBody HrmTrainingParticipantAddReqVO addReqVO) {
+        return success(trainingService.addParticipants(addReqVO.getTrainingId(), addReqVO.getEmployeeIds()));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鍙傝璁板綍锛堝弬鍔犳儏鍐�/鑰冩牳鎴愮哗/鏄惁鍚堟牸锛�")
+    @PreAuthorize("@ss.hasPermission('hrm:training:update')")
+    public CommonResult<Boolean> updateParticipant(@Valid @RequestBody HrmTrainingParticipantUpdateReqVO updateReqVO) {
+        trainingService.updateParticipant(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鍙傝璁板綍")
+    @Parameter(name = "ids", description = "鍙傝璁板綍缂栧彿鏁扮粍", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:training:update')")
+    public CommonResult<Boolean> deleteParticipants(@RequestParam("ids") List<Long> ids) {
+        trainingService.deleteParticipants(ids);
+        return success(true);
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鑾峰緱鏌愭鍩硅鐨勫弬璁汉鍛樺垪琛�")
+    @Parameter(name = "trainingId", description = "鍩硅娲诲姩缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('hrm:training:query')")
+    public CommonResult<List<HrmTrainingParticipantRespVO>> getParticipantsByTrainingId(
+            @RequestParam("trainingId") Long trainingId) {
+        List<HrmTrainingParticipantDO> participants = trainingService.getParticipantsByTrainingId(trainingId);
+        return success(buildParticipantList(participants));
+    }
+
+    private List<HrmTrainingParticipantRespVO> buildParticipantList(List<HrmTrainingParticipantDO> participants) {
+        if (CollUtil.isEmpty(participants)) {
+            return new ArrayList<>();
+        }
+        Map<Long, HrmEmployeeDO> employeeMap = employeeService.getEmployeeList(
+                        convertSet(participants, HrmTrainingParticipantDO::getEmployeeId)).stream()
+                .collect(java.util.stream.Collectors.toMap(HrmEmployeeDO::getId, e -> e, (a, b) -> a));
+        List<HrmTrainingParticipantRespVO> voList = BeanUtils.toBean(participants, HrmTrainingParticipantRespVO.class);
+        for (HrmTrainingParticipantRespVO vo : voList) {
+            HrmEmployeeDO employee = employeeMap.get(vo.getEmployeeId());
+            if (employee != null) {
+                vo.setEmployeeName(employee.getName());
+                vo.setEmployeeNo(employee.getEmployeeNo());
+            }
+        }
+        return voList;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingPageReqVO.java
new file mode 100644
index 0000000..059d860
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training.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 = "绠$悊鍚庡彴 - HRM 鍩硅娲诲姩鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmTrainingPageReqVO extends PageParam {
+
+    @Schema(description = "鍩硅缂栧彿", example = "PX202409080001")
+    private String trainingNo;
+
+    @Schema(description = "鍩硅鍚嶇О", example = "2026骞村害鐢靛姏瀹夊叏鍩硅")
+    private String name;
+
+    @Schema(description = "鍩硅绫诲瀷锛�1=瀹夊叏鍩硅锛�2=鎶�鑳藉煿璁紝3=绠$悊鍩硅锛�4=鍏朵粬锛�", example = "1")
+    private Integer trainingType;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=寰呭紑灞曪紝20=宸插畬鎴愶紝30=宸插彇娑堬級", example = "10")
+    private Integer status;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantAddReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantAddReqVO.java
new file mode 100644
index 0000000..3cfb279
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantAddReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - HRM 鍩硅鍙傝浜哄憳锛堟壒閲忔坊鍔狅級 Request VO")
+@Data
+public class HrmTrainingParticipantAddReqVO {
+
+    @Schema(description = "鍩硅娲诲姩缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍩硅娲诲姩缂栧彿涓嶈兘涓虹┖")
+    private Long trainingId;
+
+    @Schema(description = "鍙傝鍛樺伐缂栧彿闆嗗悎", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "璇烽�夋嫨鍙傝鍛樺伐")
+    private List<Long> employeeIds;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantRespVO.java
new file mode 100644
index 0000000..e8f87cd
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantRespVO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - HRM 鍩硅鍙傝浜哄憳 Response VO")
+@Data
+public class HrmTrainingParticipantRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "鍩硅娲诲姩缂栧彿", example = "1")
+    private Long trainingId;
+
+    @Schema(description = "鍛樺伐缂栧彿", example = "1")
+    private Long employeeId;
+
+    @Schema(description = "鍛樺伐宸ュ彿", example = "EMP202401010001")
+    private String employeeNo;
+
+    @Schema(description = "鍛樺伐濮撳悕", example = "寮犱笁")
+    private String employeeName;
+
+    @Schema(description = "鏄惁瀹為檯鍙傚姞锛�0=鏈弬鍔狅紝1=鍙傚姞锛�", example = "true")
+    private Boolean attended;
+
+    @Schema(description = "鑰冩牳鎴愮哗", example = "90")
+    private BigDecimal score;
+
+    @Schema(description = "鏄惁鍚堟牸锛�0=涓嶅悎鏍硷紝1=鍚堟牸锛�", example = "true")
+    private Boolean isPass;
+
+    @Schema(description = "鑱斿姩鐢熸垚鐨勮瘉涔︾紪鍙凤紙涓虹┖琛ㄧず灏氭湭鍙戣瘉锛�", example = "1")
+    private Long certificateId;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantUpdateReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantUpdateReqVO.java
new file mode 100644
index 0000000..52de68f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingParticipantUpdateReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - HRM 鍩硅鍙傝浜哄憳锛堝弬鍔�/鎴愮哗/鍚堟牸锛夋洿鏂� Request VO")
+@Data
+public class HrmTrainingParticipantUpdateReqVO {
+
+    @Schema(description = "鍙傝璁板綍缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍙傝璁板綍缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "鏄惁瀹為檯鍙傚姞锛�0=鏈弬鍔狅紝1=鍙傚姞锛�", example = "true")
+    private Boolean attended;
+
+    @Schema(description = "鑰冩牳鎴愮哗", example = "90")
+    private BigDecimal score;
+
+    @Schema(description = "鏄惁鍚堟牸锛�0=涓嶅悎鏍硷紝1=鍚堟牸锛�", example = "true")
+    private Boolean isPass;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingRespVO.java
new file mode 100644
index 0000000..4214ff7
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingRespVO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - HRM 鍩硅娲诲姩 Response VO")
+@Data
+public class HrmTrainingRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "鍩硅缂栧彿", example = "PX202409080001")
+    private String trainingNo;
+
+    @Schema(description = "鍩硅鍚嶇О", example = "2026骞村害鐢靛姏瀹夊叏鍩硅")
+    private String name;
+
+    @Schema(description = "鍩硅绫诲瀷锛�1=瀹夊叏鍩硅锛�2=鎶�鑳藉煿璁紝3=绠$悊鍩硅锛�4=鍏朵粬锛�", example = "1")
+    private Integer trainingType;
+
+    @Schema(description = "鍩硅绫诲瀷鍚嶇О", example = "瀹夊叏鍩硅")
+    private String trainingTypeName;
+
+    @Schema(description = "璁插笀", example = "寮犲伐")
+    private String trainer;
+
+    @Schema(description = "鍩硅鍦扮偣", example = "瀹夊叏鍩硅瀹�")
+    private String location;
+
+    @Schema(description = "鍩硅瀛︽椂", example = "8")
+    private BigDecimal hours;
+
+    @Schema(description = "寮�濮嬫棩鏈�")
+    private LocalDate startDate;
+
+    @Schema(description = "缁撴潫鏃ユ湡")
+    private LocalDate endDate;
+
+    @Schema(description = "鏄惁鑷姩鍙戣瘉锛�0=鍚︼紝1=鏄級", example = "true")
+    private Boolean issueCertificate;
+
+    @Schema(description = "璇佷功鏈夋晥鏈燂紙鏈堬級", example = "12")
+    private Integer certValidityMonths;
+
+    @Schema(description = "鐘舵�侊紙0=鑽夌锛�10=寰呭紑灞曪紝20=宸插畬鎴愶紝30=宸插彇娑堬級", example = "10")
+    private Integer status;
+
+    @Schema(description = "鐘舵�佸悕绉�", example = "寰呭紑灞�")
+    private String statusName;
+
+    @Schema(description = "鍙傝浜烘暟", example = "5")
+    private Integer participantCount;
+
+    @Schema(description = "鍙傝浜哄憳鍒楄〃锛堣鎯呮帴鍙h繑鍥烇級")
+    private List<HrmTrainingParticipantRespVO> participants;
+
+    @Schema(description = "澶囨敞", example = "骞村害渚嬭鍩硅")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingSaveReqVO.java
new file mode 100644
index 0000000..4776423
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/training/vo/HrmTrainingSaveReqVO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.training.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;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - HRM 鍩硅娲诲姩鏂板/淇敼 Request VO")
+@Data
+public class HrmTrainingSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鍩硅鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026骞村害鐢靛姏瀹夊叏鍩硅")
+    @NotEmpty(message = "鍩硅鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "鍩硅绫诲瀷锛�1=瀹夊叏鍩硅锛�2=鎶�鑳藉煿璁紝3=绠$悊鍩硅锛�4=鍏朵粬锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍩硅绫诲瀷涓嶈兘涓虹┖")
+    private Integer trainingType;
+
+    @Schema(description = "璁插笀", example = "寮犲伐")
+    private String trainer;
+
+    @Schema(description = "鍩硅鍦扮偣", example = "瀹夊叏鍩硅瀹�")
+    private String location;
+
+    @Schema(description = "鍩硅瀛︽椂", example = "8")
+    private BigDecimal hours;
+
+    @Schema(description = "寮�濮嬫棩鏈�")
+    private LocalDate startDate;
+
+    @Schema(description = "缁撴潫鏃ユ湡")
+    private LocalDate endDate;
+
+    @Schema(description = "鏄惁鑷姩鍙戣瘉锛�0=鍚︼紝1=鏄紱浠呭畨鍏�/鎶�鑳藉煿璁敮鎸侊級", example = "true")
+    private Boolean issueCertificate;
+
+    @Schema(description = "璇佷功鏈夋晥鏈燂紙鏈堬級锛岃嚜鍔ㄥ彂璇佹椂蹇呭~锛岀敤浜庤绠楄瘉涔﹀埌鏈熸棩", example = "12")
+    private Integer certValidityMonths;
+
+    @Schema(description = "澶囨敞", example = "骞村害渚嬭鍩硅")
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourSummaryController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourSummaryController.java
new file mode 100644
index 0000000..2c407b1
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/HrmWorkHourSummaryController.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.workhour;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmWorkHourSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmWorkHourSummaryRespVO;
+import cn.iocoder.yudao.module.hrm.service.workhour.HrmWorkHourSummaryService;
+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.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鏈堝害宸ユ椂姹囨��")
+@RestController
+@RequestMapping("/hrm/work-hour-summary")
+@Validated
+public class HrmWorkHourSummaryController {
+
+    @Resource
+    private HrmWorkHourSummaryService workHourSummaryService;
+
+    @PostMapping("/generate-monthly")
+    @Operation(summary = "鐢熸垚鏈堝害宸ユ椂姹囨��")
+    @Parameter(name = "period", description = "鏍哥畻鍛ㄦ湡锛屾牸寮� yyyy-MM", required = true, example = "2026-08")
+    @PreAuthorize("@ss.hasPermission('hrm:work-hour:generate')")
+    public CommonResult<Boolean> generateMonthly(@RequestParam("period") String period) {
+        workHourSummaryService.generateMonthly(period);
+        return success(true);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鏈堝害宸ユ椂姹囨�诲垎椤�")
+    @PreAuthorize("@ss.hasPermission('hrm:work-hour:query')")
+    public CommonResult<PageResult<HrmWorkHourSummaryRespVO>> getPage(
+            @Valid HrmWorkHourSummaryPageReqVO pageReqVO) {
+        return success(workHourSummaryService.getWorkHourSummaryPage(pageReqVO));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryPageReqVO.java
new file mode 100644
index 0000000..a16537f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryPageReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.workhour.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 = "绠$悊鍚庡彴 - 鏈堝害宸ユ椂姹囨�诲垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmWorkHourSummaryPageReqVO extends PageParam {
+
+    @Schema(description = "鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�", example = "2026-08")
+    private String period;
+
+    @Schema(description = "鍛樺伐ID", example = "1024")
+    private Long employeeId;
+
+    @Schema(description = "閮ㄩ棬ID", example = "1")
+    private Long deptId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryRespVO.java
new file mode 100644
index 0000000..0ec540a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/workhour/vo/HrmWorkHourSummaryRespVO.java
@@ -0,0 +1,49 @@
+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;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鏈堝害宸ユ椂姹囨�� Response VO")
+@Data
+public class HrmWorkHourSummaryRespVO {
+
+    @Schema(description = "姹囨�籌D")
+    private Long id;
+
+    @Schema(description = "鍛樺伐ID")
+    private Long employeeId;
+
+    @Schema(description = "鍛樺伐缂栧彿")
+    private String employeeNo;
+
+    @Schema(description = "鍛樺伐濮撳悕")
+    private String employeeName;
+
+    @Schema(description = "閮ㄩ棬ID")
+    private Long deptId;
+
+    @Schema(description = "閮ㄩ棬鍚嶇О")
+    private String deptName;
+
+    @Schema(description = "鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�")
+    private String period;
+
+    @Schema(description = "鍑哄嫟澶╂暟")
+    private Integer workDays;
+
+    @Schema(description = "姝e父宸ユ椂锛堝皬鏃讹級")
+    private BigDecimal workHours;
+
+    @Schema(description = "鍔犵彮宸ユ椂锛堝皬鏃讹級")
+    private BigDecimal overtimeHours;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmRegularizeApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmRegularizeApplicationDO.java
new file mode 100644
index 0000000..1739e06
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmRegularizeApplicationDO.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.approval;
+
+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.time.LocalDate;
+
+/**
+ * 杞鐢宠 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_regularize_application")
+@KeySequence("hrm_regularize_application_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmRegularizeApplicationDO extends BaseDO {
+
+    /**
+     * 鐢宠ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鐢宠鍗曞彿
+     */
+    private String no;
+
+    /**
+     * 鐢宠浜篒D
+     *
+     * 鍏宠仈 AdminUserDO#getId()
+     */
+    private Long userId;
+
+    /**
+     * 閮ㄩ棬ID
+     *
+     * 鍏宠仈 DeptDO#getId()
+     */
+    private Long deptId;
+
+    /**
+     * 宀椾綅ID
+     *
+     * 鍏宠仈 PostDO#getId()
+     */
+    private Long postId;
+
+    /**
+     * 褰撳墠鐢ㄥ伐鎬ц川
+     *
+     * 1-姝e紡锛�2-鍔冲姟锛�3-澶栧寘锛�4-瀹炰範锛�5-閫�浼戣繑鑱橈紝6-鍏朵粬
+     */
+    private Integer employmentType;
+
+    /**
+     * 鎷熻浆姝f棩鏈�
+     */
+    private LocalDate expectedDate;
+
+    /**
+     * 杞鍘熷洜
+     */
+    private String reason;
+
+    /**
+     * 瀹℃壒鐘舵��
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * BPM 娴佺▼瀹炰緥ID
+     */
+    private String processInstanceId;
+
+    /**
+     * 瀹℃壒瀹屾垚鏃堕棿
+     */
+    private java.time.LocalDateTime approveTime;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
index 3207b95..5a07325 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
@@ -52,6 +52,20 @@
     private Long postId;
 
     /**
+     * 鐢ㄥ伐鎬ц川
+     *
+     * 1-姝e紡锛�2-鍔冲姟锛�3-澶栧寘锛�4-瀹炰範锛�5-閫�浼戣繑鑱橈紝6-鍏朵粬
+     */
+    private Integer employmentType;
+
+    /**
+     * 鎵�灞炵彮缁処D
+     *
+     * 鍏宠仈 MesCalTeamDO#getId()锛圡ES 鎺掔彮缁勶紝鐝粍/鎺掔彮/缁╂晥鍚屾簮锛�
+     */
+    private Long teamId;
+
+    /**
      * 鍛樺伐缂栧彿
      */
     private String employeeNo;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java
index b9d2475..63d6d87 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java
@@ -51,6 +51,11 @@
     private Integer scopeType;
 
     /**
+     * 鎸囧畾鐝粍缂栧彿闆嗗悎锛圝SON 鏁扮粍锛宻copeType=2 鏃剁敓鏁堬紝鍏宠仈 MES 鎺掔彮缁勶級
+     */
+    private String scopeTeamIds;
+
+    /**
      * 鏂规鐘舵��: 0-鑽夌 10-宸插彂甯� 20-杩涜涓� 30-宸插畬鎴�
      */
     private Integer status;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingDO.java
new file mode 100644
index 0000000..ff9e805
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingDO.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.training;
+
+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;
+import java.time.LocalDate;
+
+/**
+ * HRM 鍩硅娲诲姩 DO
+ *
+ * <p>绠$悊瀹夊叏鍩硅銆佹妧鑳藉煿璁瓑鍩硅璁″垝/娲诲姩锛岄厤鍚� hrm_training_participant 璁板綍鍙傝涓庤�冩牳缁撴灉锛�
+ * 鍩硅瀹屾垚锛坰tatus=宸插畬鎴愶級鏃跺鍚堟牸浜哄憳鑱斿姩鐢熸垚瀵瑰簲璇佷功璧勮川锛屾墦閫�"鍩硅鈫掕瘉涔︹啋鍒版湡鎻愰啋"鍚堣閾捐矾銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_training")
+@KeySequence("hrm_training_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmTrainingDO extends BaseDO {
+
+    /**
+     * 鍩硅绫诲瀷锛氬畨鍏ㄥ煿璁�
+     */
+    public static final int TYPE_SAFETY = 1;
+    /**
+     * 鍩硅绫诲瀷锛氭妧鑳藉煿璁�
+     */
+    public static final int TYPE_SKILL = 2;
+    /**
+     * 鍩硅绫诲瀷锛氱鐞嗗煿璁�
+     */
+    public static final int TYPE_MANAGE = 3;
+    /**
+     * 鍩硅绫诲瀷锛氬叾浠�
+     */
+    public static final int TYPE_OTHER = 4;
+
+    /**
+     * 鐘舵�侊細鑽夌
+     */
+    public static final int STATUS_DRAFT = 0;
+    /**
+     * 鐘舵�侊細寰呭紑灞�
+     */
+    public static final int STATUS_PLANNED = 10;
+    /**
+     * 鐘舵�侊細宸插畬鎴�
+     */
+    public static final int STATUS_COMPLETED = 20;
+    /**
+     * 鐘舵�侊細宸插彇娑�
+     */
+    public static final int STATUS_CANCELED = 30;
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鍩硅缂栧彿
+     */
+    private String trainingNo;
+
+    /**
+     * 鍩硅鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鍩硅绫诲瀷锛�1=瀹夊叏鍩硅锛�2=鎶�鑳藉煿璁紝3=绠$悊鍩硅锛�4=鍏朵粬锛�
+     */
+    private Integer trainingType;
+
+    /**
+     * 璁插笀
+     */
+    private String trainer;
+
+    /**
+     * 鍩硅鍦扮偣
+     */
+    private String location;
+
+    /**
+     * 鍩硅瀛︽椂
+     */
+    private BigDecimal hours;
+
+    /**
+     * 寮�濮嬫棩鏈�
+     */
+    private LocalDate startDate;
+
+    /**
+     * 缁撴潫鏃ユ湡
+     */
+    private LocalDate endDate;
+
+    /**
+     * 鏄惁鑷姩鍙戣瘉锛�0=鍚︼紝1=鏄紱浠呭畨鍏�/鎶�鑳藉煿璁敮鎸侊級
+     */
+    private Boolean issueCertificate;
+
+    /**
+     * 璇佷功鏈夋晥鏈燂紙鏈堬級锛岃嚜鍔ㄥ彂璇佹椂鐢ㄤ簬璁$畻璇佷功鍒版湡鏃�
+     */
+    private Integer certValidityMonths;
+
+    /**
+     * 鐘舵�侊紙0=鑽夌锛�10=寰呭紑灞曪紝20=宸插畬鎴愶紝30=宸插彇娑堬級
+     */
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingParticipantDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingParticipantDO.java
new file mode 100644
index 0000000..f8f1fbe
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/training/HrmTrainingParticipantDO.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.training;
+
+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
+ *
+ * <p>璁板綍鏌愭鍩硅鐨勫弬璁憳宸ャ�佹槸鍚﹀弬鍔犮�佽�冩牳鎴愮哗涓庢槸鍚﹀悎鏍硷紱鍩硅鑱斿姩鍙戣瘉鍚庡洖鍐� certificateId锛岄伩鍏嶉噸澶嶅彂璇併��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_training_participant")
+@KeySequence("hrm_training_participant_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmTrainingParticipantDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鍩硅娲诲姩缂栧彿
+     *
+     * 鍏宠仈 {@link HrmTrainingDO#getId()}
+     */
+    private Long trainingId;
+
+    /**
+     * 鍛樺伐缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO#getId()}
+     */
+    private Long employeeId;
+
+    /**
+     * 鏄惁瀹為檯鍙傚姞锛�0=鏈弬鍔狅紝1=鍙傚姞锛�
+     */
+    private Boolean attended;
+
+    /**
+     * 鑰冩牳鎴愮哗
+     */
+    private BigDecimal score;
+
+    /**
+     * 鏄惁鍚堟牸锛�0=涓嶅悎鏍硷紝1=鍚堟牸锛�
+     */
+    private Boolean isPass;
+
+    /**
+     * 鑱斿姩鐢熸垚璇佷功璧勮川缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO#getId()}锛�
+     * 鐢ㄤ簬骞傜瓑锛堝悓涓�鍙傝浜哄悓涓�鍩硅鍙彂涓�娆¤瘉锛�
+     */
+    private Long certificateId;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/workhour/HrmWorkHourSummaryDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/workhour/HrmWorkHourSummaryDO.java
new file mode 100644
index 0000000..d80802d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/workhour/HrmWorkHourSummaryDO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.workhour;
+
+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;
+
+/**
+ * 鏈堝害宸ユ椂姹囨�� DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_workhour_summary")
+@KeySequence("hrm_workhour_summary_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmWorkHourSummaryDO extends BaseDO {
+
+    /**
+     * 姹囨�籌D
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鍛樺伐ID
+     *
+     * 鍏宠仈 HrmEmployeeDO#getId()
+     */
+    private Long employeeId;
+
+    /**
+     * 閮ㄩ棬ID
+     */
+    private Long deptId;
+
+    /**
+     * 鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�
+     */
+    private String period;
+
+    /**
+     * 鍑哄嫟澶╂暟
+     */
+    private Integer workDays;
+
+    /**
+     * 姝e父宸ユ椂锛堝皬鏃讹級
+     */
+    private BigDecimal workHours;
+
+    /**
+     * 鍔犵彮宸ユ椂锛堝皬鏃讹級
+     */
+    private BigDecimal overtimeHours;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmRegularizeApplicationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmRegularizeApplicationMapper.java
new file mode 100644
index 0000000..cfd79d4
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmRegularizeApplicationMapper.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.approval;
+
+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.approval.vo.HrmRegularizeApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmRegularizeApplicationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 杞鐢宠 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmRegularizeApplicationMapper extends BaseMapperX<HrmRegularizeApplicationDO> {
+
+    default PageResult<HrmRegularizeApplicationDO> selectPage(HrmRegularizeApplicationPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmRegularizeApplicationDO>()
+                .likeIfPresent(HrmRegularizeApplicationDO::getNo, reqVO.getNo())
+                .eqIfPresent(HrmRegularizeApplicationDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(HrmRegularizeApplicationDO::getDeptId, reqVO.getDeptId())
+                .eqIfPresent(HrmRegularizeApplicationDO::getEmploymentType, reqVO.getEmploymentType())
+                .eqIfPresent(HrmRegularizeApplicationDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(HrmRegularizeApplicationDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(HrmRegularizeApplicationDO::getId));
+    }
+
+    default HrmRegularizeApplicationDO selectByNo(String no) {
+        return selectOne(HrmRegularizeApplicationDO::getNo, no);
+    }
+
+    default HrmRegularizeApplicationDO selectByProcessInstanceId(String processInstanceId) {
+        return selectOne(HrmRegularizeApplicationDO::getProcessInstanceId, processInstanceId);
+    }
+
+    default HrmRegularizeApplicationDO selectByUserIdAndStatusNot(Long userId, Integer status) {
+        return selectOne(new LambdaQueryWrapperX<HrmRegularizeApplicationDO>()
+                .eq(HrmRegularizeApplicationDO::getUserId, userId)
+                .ne(HrmRegularizeApplicationDO::getStatus, status)
+                .orderByDesc(HrmRegularizeApplicationDO::getId)
+                .last("LIMIT 1"));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java
index 2f6adbe..a973b94 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/certificate/HrmCertificateMapper.java
@@ -7,6 +7,9 @@
 import cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.time.LocalDate;
+import java.util.List;
+
 /**
  * HRM 璇佷功璧勮川 Mapper
  *
@@ -15,12 +18,40 @@
 @Mapper
 public interface HrmCertificateMapper extends BaseMapperX<HrmCertificateDO> {
 
+    /**
+     * 鍒版湡鎻愰啋澶╂暟
+     */
+    int REMIND_DAYS = 30;
+
     default PageResult<HrmCertificateDO> selectPage(HrmCertificatePageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<HrmCertificateDO>()
+        LambdaQueryWrapperX<HrmCertificateDO> query = new LambdaQueryWrapperX<HrmCertificateDO>()
                 .eqIfPresent(HrmCertificateDO::getEmployeeId, reqVO.getEmployeeId())
                 .eqIfPresent(HrmCertificateDO::getCertType, reqVO.getCertType())
-                .likeIfPresent(HrmCertificateDO::getCertName, reqVO.getCertName())
-                .orderByDesc(HrmCertificateDO::getId));
+                .likeIfPresent(HrmCertificateDO::getCertName, reqVO.getCertName());
+        // 鍒版湡绫诲瀷绛涢�夛紙閰嶅悎鍚堣鍒楄〃/寰呭姙鎻愰啋锛夛細1=鍗冲皢鍒版湡锛�2=宸茶繃鏈�
+        if (reqVO.getExpiryType() != null) {
+            LocalDate today = LocalDate.now();
+            if (reqVO.getExpiryType() == 1) { // 鍗冲皢鍒版湡
+                query.between(HrmCertificateDO::getExpireDate, today, today.plusDays(REMIND_DAYS));
+            } else if (reqVO.getExpiryType() == 2) { // 宸茶繃鏈�
+                query.isNotNull(HrmCertificateDO::getExpireDate)
+                        .lt(HrmCertificateDO::getExpireDate, today);
+            }
+        }
+        return selectPage(reqVO, query.orderByDesc(HrmCertificateDO::getId));
+    }
+
+    /**
+     * 鏌ヨ鏈夋晥鏈熻惤鍦� [start, end] 涔嬮棿鐨勮瘉涔︼紙鍒版湡鎻愰啋鐢級
+     *
+     * @param start 璧峰鏃ユ湡
+     * @param end 鎴鏃ユ湡
+     * @return 鍗冲皢鍒版湡璇佷功鍒楄〃
+     */
+    default List<HrmCertificateDO> selectListByExpireDateBetween(LocalDate start, LocalDate end) {
+        return selectList(new LambdaQueryWrapperX<HrmCertificateDO>()
+                .isNotNull(HrmCertificateDO::getExpireDate)
+                .between(HrmCertificateDO::getExpireDate, start, end));
     }
 
 }
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeContractMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeContractMapper.java
index 6d7e74f..7a958a8 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeContractMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeContractMapper.java
@@ -187,4 +187,20 @@
                 .orderByDesc(HrmEmployeeContractDO::getId));
     }
 
+    /**
+     * 鏌ヨ褰撳墠鐢熸晥銆佸皢鍦� [start, end] 涔嬮棿鍒版湡鐨勫浐瀹氭湡闄愬悎鍚岋紙鍒版湡鎻愰啋鐢級
+     *
+     * @param start 璧峰鏃ユ湡
+     * @param end 鎴鏃ユ湡
+     * @return 鍗冲皢鍒版湡鍚堝悓鍒楄〃
+     */
+    default List<HrmEmployeeContractDO> selectListExpiringBetween(LocalDate start, LocalDate end) {
+        return selectList(new LambdaQueryWrapperX<HrmEmployeeContractDO>()
+                .eq(HrmEmployeeContractDO::getTerminateStatus, TERMINATE_NORMAL)
+                .eq(HrmEmployeeContractDO::getIsCurrent, true)
+                .le(HrmEmployeeContractDO::getStartDate, start)
+                .isNotNull(HrmEmployeeContractDO::getEndDate)
+                .between(HrmEmployeeContractDO::getEndDate, start, end));
+    }
+
 }
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
index 1e992a2..64d5140 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.hrm.dal.mysql.employee;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -7,6 +8,7 @@
 import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -52,4 +54,10 @@
                 .orderByAsc(HrmEmployeeDO::getName));
     }
 
+    default List<HrmEmployeeDO> selectListByTeamIds(Collection<Long> teamIds) {
+        return selectList(new LambdaQueryWrapperX<HrmEmployeeDO>()
+                .inIfPresent(HrmEmployeeDO::getTeamId, CollUtil.isNotEmpty(teamIds) ? teamIds : null)
+                .orderByAsc(HrmEmployeeDO::getName));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java
index c864979..8662626 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java
@@ -21,4 +21,13 @@
                 .orderByDesc(HrmPerformanceAppraiseDO::getId));
     }
 
+    default HrmPerformanceAppraiseDO selectMonthlyCompletedByPeriod(String period) {
+        return selectOne(new LambdaQueryWrapperX<HrmPerformanceAppraiseDO>()
+                .eq(HrmPerformanceAppraiseDO::getPeriodType, 1)
+                .eq(HrmPerformanceAppraiseDO::getPeriod, period)
+                .eq(HrmPerformanceAppraiseDO::getStatus, 30)
+                .orderByDesc(HrmPerformanceAppraiseDO::getId)
+                .last("LIMIT 1"));
+    }
+
 }
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java
index d05bee2..48a0aae 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java
@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
 
+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.salary.vo.HrmEmployeeSalaryPageReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -36,4 +38,12 @@
                 .orderByDesc(HrmEmployeeSalaryDO::getEffectiveDate));
     }
 
+    default PageResult<HrmEmployeeSalaryDO> selectPage(HrmEmployeeSalaryPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmEmployeeSalaryDO>()
+                .eqIfPresent(HrmEmployeeSalaryDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(HrmEmployeeSalaryDO::getSalaryStructureId, reqVO.getSalaryStructureId())
+                .eqIfPresent(HrmEmployeeSalaryDO::getStatus, reqVO.getStatus())
+                .orderByDesc(HrmEmployeeSalaryDO::getId));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
index 4759301..dc48696 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
 
+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.salary.vo.HrmSalaryStructurePageReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -21,4 +23,11 @@
                 .orderByAsc(HrmSalaryStructureDO::getName));
     }
 
+    default PageResult<HrmSalaryStructureDO> selectPage(HrmSalaryStructurePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmSalaryStructureDO>()
+                .likeIfPresent(HrmSalaryStructureDO::getName, reqVO.getName())
+                .eqIfPresent(HrmSalaryStructureDO::getStatus, reqVO.getStatus())
+                .orderByDesc(HrmSalaryStructureDO::getId));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingMapper.java
new file mode 100644
index 0000000..2429294
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingMapper.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.training;
+
+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.training.vo.HrmTrainingPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * HRM 鍩硅娲诲姩 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmTrainingMapper extends BaseMapperX<HrmTrainingDO> {
+
+    default PageResult<HrmTrainingDO> selectPage(HrmTrainingPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmTrainingDO>()
+                .likeIfPresent(HrmTrainingDO::getName, reqVO.getName())
+                .eqIfPresent(HrmTrainingDO::getTrainingNo, reqVO.getTrainingNo())
+                .eqIfPresent(HrmTrainingDO::getTrainingType, reqVO.getTrainingType())
+                .eqIfPresent(HrmTrainingDO::getStatus, reqVO.getStatus())
+                .orderByDesc(HrmTrainingDO::getId));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingParticipantMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingParticipantMapper.java
new file mode 100644
index 0000000..adf6a7e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/training/HrmTrainingParticipantMapper.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.training;
+
+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.training.HrmTrainingParticipantDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * HRM 鍩硅鍙傝浜哄憳 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmTrainingParticipantMapper extends BaseMapperX<HrmTrainingParticipantDO> {
+
+    /**
+     * 鏌ヨ鏌愭鍩硅鐨勫叏閮ㄥ弬璁汉鍛�
+     *
+     * @param trainingId 鍩硅娲诲姩缂栧彿
+     * @return 鍙傝浜哄憳鍒楄〃
+     */
+    default List<HrmTrainingParticipantDO> selectListByTrainingId(Long trainingId) {
+        return selectList(new LambdaQueryWrapperX<HrmTrainingParticipantDO>()
+                .eq(HrmTrainingParticipantDO::getTrainingId, trainingId)
+                .orderByAsc(HrmTrainingParticipantDO::getId));
+    }
+
+    /**
+     * 鎵归噺鏌ヨ鎸囧畾鍩硅鐨勫弬璁汉鍛橈紙鍒嗛〉缁熻鍙傝浜烘暟鐢級
+     *
+     * @param trainingIds 鍩硅娲诲姩缂栧彿闆嗗悎
+     * @return 鍙傝浜哄憳鍒楄〃
+     */
+    default List<HrmTrainingParticipantDO> selectListByTrainingIds(Collection<Long> trainingIds) {
+        return selectList(new LambdaQueryWrapperX<HrmTrainingParticipantDO>()
+                .in(HrmTrainingParticipantDO::getTrainingId, trainingIds));
+    }
+
+    /**
+     * 鏌ヨ鏌愭鍩硅涓嬫煇浣嶅憳宸ョ殑鍙傝璁板綍锛堥槻閲嶅娣诲姞鐢級
+     *
+     * @param trainingId 鍩硅娲诲姩缂栧彿
+     * @param employeeId 鍛樺伐缂栧彿
+     * @return 鍙傝璁板綍锛屾棤鍒欒繑鍥� null
+     */
+    default HrmTrainingParticipantDO selectByTrainingIdAndEmployeeId(Long trainingId, Long employeeId) {
+        return selectOne(new LambdaQueryWrapperX<HrmTrainingParticipantDO>()
+                .eq(HrmTrainingParticipantDO::getTrainingId, trainingId)
+                .eq(HrmTrainingParticipantDO::getEmployeeId, employeeId));
+    }
+
+    /**
+     * 鍒犻櫎鏌愭鍩硅鐨勫叏閮ㄥ弬璁汉鍛橈紙鍩硅鍒犻櫎鏃剁骇鑱旓級
+     *
+     * @param trainingId 鍩硅娲诲姩缂栧彿
+     */
+    default void deleteByTrainingId(Long trainingId) {
+        delete(new LambdaQueryWrapperX<HrmTrainingParticipantDO>()
+                .eq(HrmTrainingParticipantDO::getTrainingId, trainingId));
+    }
+
+    /**
+     * 鍥炲啓鑱斿姩鐢熸垚鐨勮瘉涔︾紪鍙�
+     *
+     * @param id 鍙傝浜哄憳缂栧彿
+     * @param certificateId 璇佷功璧勮川缂栧彿
+     */
+    default void updateCertificateId(Long id, Long certificateId) {
+        update(null, new LambdaUpdateWrapper<HrmTrainingParticipantDO>()
+                .eq(HrmTrainingParticipantDO::getId, id)
+                .set(HrmTrainingParticipantDO::getCertificateId, certificateId));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/workhour/HrmWorkHourSummaryMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/workhour/HrmWorkHourSummaryMapper.java
new file mode 100644
index 0000000..74772a5
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/workhour/HrmWorkHourSummaryMapper.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.workhour;
+
+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.workhour.vo.HrmWorkHourSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.workhour.HrmWorkHourSummaryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鏈堝害宸ユ椂姹囨�� Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmWorkHourSummaryMapper extends BaseMapperX<HrmWorkHourSummaryDO> {
+
+    default PageResult<HrmWorkHourSummaryDO> selectPage(HrmWorkHourSummaryPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmWorkHourSummaryDO>()
+                .eqIfPresent(HrmWorkHourSummaryDO::getEmployeeId, reqVO.getEmployeeId())
+                .eqIfPresent(HrmWorkHourSummaryDO::getDeptId, reqVO.getDeptId())
+                .eqIfPresent(HrmWorkHourSummaryDO::getPeriod, reqVO.getPeriod())
+                .orderByDesc(HrmWorkHourSummaryDO::getId));
+    }
+
+    default HrmWorkHourSummaryDO selectByEmployeeIdAndPeriod(Long employeeId, String period) {
+        return selectOne(new LambdaQueryWrapperX<HrmWorkHourSummaryDO>()
+                .eq(HrmWorkHourSummaryDO::getEmployeeId, employeeId)
+                .eq(HrmWorkHourSummaryDO::getPeriod, period)
+                .last("LIMIT 1"));
+    }
+
+    default List<HrmWorkHourSummaryDO> selectListByPeriod(String period) {
+        return selectList(new LambdaQueryWrapperX<HrmWorkHourSummaryDO>()
+                .eq(HrmWorkHourSummaryDO::getPeriod, period)
+                .orderByAsc(HrmWorkHourSummaryDO::getEmployeeId));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmExpiryRemindRedisDAO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmExpiryRemindRedisDAO.java
new file mode 100644
index 0000000..5a4314d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmExpiryRemindRedisDAO.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.hrm.dal.redis;
+
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDate;
+
+/**
+ * HRM 璇佷功/鍚堝悓鍒版湡鎻愰啋 Redis DAO
+ *
+ * <p>鐢ㄤ簬鍒版湡绔欏唴淇$殑鍘婚噸锛堝悓涓�涓氬姟鍚屼竴鍒版湡鏃ュ彧鎻愰啋涓�娆★級涓庡綋澶╂壂鎻忛棬闂╋紙閬垮厤澶氬疄渚�/澶氭骞跺彂閲嶅鎵弿锛夛紝
+ * 鎬濊矾鍙傜収 MES 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Repository
+public class HrmExpiryRemindRedisDAO {
+
+    /**
+     * 鎻愰啋鍘婚噸 KEY 鐨勮繃鏈熸椂闂达紙60 澶╋級锛氬悓涓�涓氬姟璁板綍鍚屼竴鍒版湡鏃� 60 澶╁唴鍙彁閱掍竴娆�
+     */
+    private static final Duration REMIND_TIMEOUT = Duration.ofDays(60);
+    /**
+     * 鎵弿闂ㄩ棭鐨勮繃鏈熸椂闂达紙30 鍒嗛挓锛夛細閬垮厤澶氬疄渚�/澶氭骞跺彂閲嶅鎵弿
+     */
+    private static final Duration SCAN_LOCK_TIMEOUT = Duration.ofMinutes(30);
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 灏濊瘯鏍囪宸叉彁閱掞紙鍚屼竴涓氬姟璁板綍鍚屼竴鍒版湡鏃� 60 澶╁唴鍙彁閱掍竴娆★級
+     *
+     * @param biz 涓氬姟鏍囪瘑锛堝 cert=璇佷功璧勮川銆乧ontract=鍛樺伐鍚堝悓锛�
+     * @param bizId 涓氬姟璁板綍缂栧彿
+     * @param expireDate 鍒版湡鏃�
+     * @return true 琛ㄧず棣栨鏍囪鎴愬姛锛岄渶瑕佸彂閫佹彁閱�
+     */
+    public boolean tryMarkReminded(String biz, Long bizId, LocalDate expireDate) {
+        String key = HrmRedisKeyConstants.EXPIRE_REMIND + biz + ":" + bizId + ":" + expireDate;
+        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, "1", REMIND_TIMEOUT));
+    }
+
+    /**
+     * 灏濊瘯鑾峰彇褰撳ぉ鐨勬壂鎻忛棬闂╋紝閬垮厤澶氬疄渚嬪苟鍙戦噸澶嶆壂鎻�
+     *
+     * @param biz 涓氬姟鏍囪瘑锛堝 cert=璇佷功璧勮川銆乧ontract=鍛樺伐鍚堝悓锛�
+     * @param today 褰撳ぉ鏃ユ湡
+     * @return true 琛ㄧず鑾峰彇鎴愬姛锛堝彲浠ユ墽琛屾壂鎻忥級
+     */
+    public boolean tryAcquireScanLock(String biz, LocalDate today) {
+        String key = HrmRedisKeyConstants.EXPIRE_REMIND_SCAN + biz + ":" + today;
+        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, "1", SCAN_LOCK_TIMEOUT));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java
index 44009fd..894c2c0 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java
@@ -57,10 +57,40 @@
      */
     public static final String EMPLOYEE_CONTRACT_NO_PREFIX = "HT";
 
+    /**
+     * 鍩硅娲诲姩缂栧彿鍓嶇紑
+     */
+    public static final String TRAINING_NO_PREFIX = "PX";
+
+    /**
+     * 杞鐢宠缂栧彿鍓嶇紑
+     */
+    public static final String REGULARIZE_APPLICATION_NO_PREFIX = "ZZ";
+
     @Resource
     private StringRedisTemplate stringRedisTemplate;
 
     /**
+     * 鐢熸垚鍩硅娲诲姩缂栧彿锛屾牸寮忥細PX + yyyyMMdd + 4浣嶈嚜澧�
+     * 渚嬪锛歅X202401010001
+     *
+     * @return 鍩硅娲诲姩缂栧彿
+     */
+    public String generateTrainingNo() {
+        return generate(TRAINING_NO_PREFIX, 4);
+    }
+
+    /**
+     * 鐢熸垚杞鐢宠缂栧彿锛屾牸寮忥細ZZ + yyyyMMdd + 4浣嶈嚜澧�
+     * 渚嬪锛歓Z202401010001
+     *
+     * @return 杞鐢宠缂栧彿
+     */
+    public String generateRegularizeApplicationNo() {
+        return generate(REGULARIZE_APPLICATION_NO_PREFIX, 4);
+    }
+
+    /**
      * 鐢熸垚鍛樺伐缂栧彿锛屾牸寮忥細EMP + yyyyMMdd + 4浣嶈嚜澧�
      * 渚嬪锛欵MP202401010001
      *
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java
index 486bcef..5a6336d 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java
@@ -12,4 +12,14 @@
      */
     String NO = "hrm:no:";
 
+    /**
+     * 鍒版湡鎻愰啋鍘婚噸 KEY 鍓嶇紑锛堝悗鎺� 涓氬姟鏍囪瘑:id:鍒版湡鏃ユ湡锛�
+     */
+    String EXPIRE_REMIND = "hrm:remind:item:";
+
+    /**
+     * 鍒版湡鎻愰啋鎵弿闂ㄩ棭 KEY 鍓嶇紑锛堝悗鎺� 涓氬姟鏍囪瘑:褰撳ぉ鏃ユ湡锛�
+     */
+    String EXPIRE_REMIND_SCAN = "hrm:remind:scan:";
+
 }
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/job/HrmExpiryRemindJob.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/job/HrmExpiryRemindJob.java
new file mode 100644
index 0000000..d609e96
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/job/HrmExpiryRemindJob.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.hrm.job;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.module.hrm.service.remind.HrmExpiryRemindService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * HRM 璇佷功/鍚堝悓鍒版湡鎻愰啋瀹氭椂浠诲姟
+ *
+ * <p>姣忔棩鎵弿鍗冲皢鍒版湡锛堥粯璁� 30 澶╁唴锛屽彲鍦� infra_config 璋冩暣锛夌殑璇佷功璧勮川涓庡姵鍔ㄥ悎鍚岋紝
+ * 鍚戝湪宀楀憳宸ョ郴缁熻处鍙峰彂閫佺珯鍐呬俊鎻愰啋銆傚湪銆屽熀纭�璁炬柦 鈫� 瀹氭椂浠诲姟銆嶄腑浠� handlerName=hrmExpiryRemindJob 鍚敤銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Component
+public class HrmExpiryRemindJob implements JobHandler {
+
+    @Resource
+    private HrmExpiryRemindService expiryRemindService;
+
+    @Override
+    public String execute(String param) {
+        expiryRemindService.sendAll();
+        return "HRM 璇佷功/鍚堝悓鍒版湡鎻愰啋鎵ц瀹屾垚";
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationService.java
new file mode 100644
index 0000000..5106664
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationService.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.hrm.service.approval;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmRegularizeApplicationDO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 杞鐢宠 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmRegularizeApplicationService {
+
+    /**
+     * 瀹℃壒鍒嗙被缂栫爜锛氳浆姝e鎵�
+     */
+    String APPROVE_CATEGORY_CODE = "hrm_regularize_approve";
+
+    /**
+     * 鍒涘缓杞鐢宠
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 鐢宠ID
+     */
+    Long createRegularizeApplication(HrmRegularizeApplicationSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊杞鐢宠
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateRegularizeApplication(HrmRegularizeApplicationSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎杞鐢宠
+     *
+     * @param id 鐢宠ID
+     */
+    void deleteRegularizeApplication(Long id);
+
+    /**
+     * 鑾峰彇杞鐢宠
+     *
+     * @param id 鐢宠ID
+     * @return 鐢宠淇℃伅
+     */
+    HrmRegularizeApplicationDO getRegularizeApplication(Long id);
+
+    /**
+     * 鑾峰彇杞鐢宠璇︽儏
+     *
+     * @param id 鐢宠ID
+     * @return 鐢宠璇︽儏
+     */
+    HrmRegularizeApplicationRespVO getRegularizeApplicationDetail(Long id);
+
+    /**
+     * 鑾峰彇杞鐢宠鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉璇锋眰
+     * @return 鍒嗛〉缁撴灉
+     */
+    PageResult<HrmRegularizeApplicationRespVO> getRegularizeApplicationPage(HrmRegularizeApplicationPageReqVO pageReqVO);
+
+    /**
+     * 鎻愪氦杞鐢宠瀹℃壒
+     *
+     * @param id 鐢宠ID
+     * @param processDefinitionKey 娴佺▼瀹氫箟Key
+     * @param userId 鍙戣捣浜篒D
+     */
+    void submitRegularizeApplication(Long id, String processDefinitionKey, Long userId);
+
+    /**
+     * 鏇存柊杞鐢宠瀹℃壒鐘舵��
+     *
+     * @param id 鐢宠ID
+     * @param status 瀹℃壒鐘舵��
+     */
+    void updateRegularizeApplicationAuditStatus(Long id, Integer status);
+
+    /**
+     * 鏍规嵁娴佺▼瀹炰緥ID鑾峰彇杞鐢宠
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥ID
+     * @return 鐢宠淇℃伅
+     */
+    HrmRegularizeApplicationDO getRegularizeApplicationByProcessInstanceId(String processInstanceId);
+
+    /**
+     * 鑾峰彇杞瀹℃壒娴佺▼瀹氫箟鍒楄〃
+     *
+     * @return 娴佺▼瀹氫箟鍒楄〃
+     */
+    List<Map<String, Object>> getApproveProcessDefinitionList();
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationServiceImpl.java
new file mode 100644
index 0000000..660134a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationServiceImpl.java
@@ -0,0 +1,307 @@
+package cn.iocoder.yudao.module.hrm.service.approval;
+
+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.security.core.util.SecurityFrameworkUtils;
+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.approval.vo.HrmRegularizeApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmRegularizeApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmRegularizeApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmEmploymentTypeEnum;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.PostApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+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.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+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.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 杞鐢宠 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+@Slf4j
+public class HrmRegularizeApplicationServiceImpl implements HrmRegularizeApplicationService {
+
+    @Resource
+    private HrmRegularizeApplicationMapper regularizeApplicationMapper;
+    @Resource
+    private HrmNoRedisDAO noRedisDAO;
+    @Resource
+    private DeptApi deptApi;
+    @Resource
+    private PostApi postApi;
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private BpmProcessInstanceApi bpmProcessInstanceApi;
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createRegularizeApplication(HrmRegularizeApplicationSaveReqVO createReqVO) {
+        Long userId = createReqVO.getUserId();
+        if (userId == null) {
+            userId = getCurrentUserId();
+        }
+        HrmRegularizeApplicationDO existing = regularizeApplicationMapper.selectByUserIdAndStatusNot(userId,
+                HrmAuditStatusEnum.CANCEL.getStatus());
+        if (existing != null) {
+            throw exception(REGULARIZE_APPLICATION_EXISTS);
+        }
+        String no = generateUniqueNo();
+        AdminUserRespDTO user = adminUserApi.getUser(userId);
+        if (user == null) {
+            userId = getCurrentUserId();
+            user = adminUserApi.getUser(userId);
+        }
+        HrmRegularizeApplicationDO application = BeanUtils.toBean(createReqVO, HrmRegularizeApplicationDO.class);
+        application.setNo(no);
+        application.setUserId(userId);
+        application.setDeptId(createReqVO.getDeptId() != null ? createReqVO.getDeptId()
+                : (user != null ? user.getDeptId() : null));
+        application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
+        regularizeApplicationMapper.insert(application);
+        return application.getId();
+    }
+
+    private Long getCurrentUserId() {
+        return SecurityFrameworkUtils.getLoginUserId();
+    }
+
+    private String generateUniqueNo() {
+        String no = noRedisDAO.generateRegularizeApplicationNo();
+        if (regularizeApplicationMapper.selectByNo(no) != null) {
+            return generateUniqueNo();
+        }
+        return no;
+    }
+
+    @Override
+    public void updateRegularizeApplication(HrmRegularizeApplicationSaveReqVO updateReqVO) {
+        HrmRegularizeApplicationDO application = validateRegularizeApplicationExists(updateReqVO.getId());
+        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(REGULARIZE_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+        }
+        HrmRegularizeApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmRegularizeApplicationDO.class);
+        regularizeApplicationMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteRegularizeApplication(Long id) {
+        HrmRegularizeApplicationDO application = validateRegularizeApplicationExists(id);
+        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(REGULARIZE_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+        }
+        regularizeApplicationMapper.deleteById(id);
+    }
+
+    private HrmRegularizeApplicationDO validateRegularizeApplicationExists(Long id) {
+        HrmRegularizeApplicationDO application = regularizeApplicationMapper.selectById(id);
+        if (application == null) {
+            throw exception(REGULARIZE_APPLICATION_NOT_EXISTS);
+        }
+        return application;
+    }
+
+    @Override
+    public HrmRegularizeApplicationDO getRegularizeApplication(Long id) {
+        return regularizeApplicationMapper.selectById(id);
+    }
+
+    @Override
+    public HrmRegularizeApplicationRespVO getRegularizeApplicationDetail(Long id) {
+        HrmRegularizeApplicationDO application = getRegularizeApplication(id);
+        if (application == null) {
+            return null;
+        }
+        HrmRegularizeApplicationRespVO respVO = BeanUtils.toBean(application, HrmRegularizeApplicationRespVO.class);
+        fillUserNames(respVO, application);
+        if (application.getDeptId() != null) {
+            DeptRespDTO dept = deptApi.getDept(application.getDeptId());
+            if (dept != null) {
+                respVO.setDeptName(dept.getName());
+            }
+        }
+        if (application.getPostId() != null) {
+            Map<Long, PostRespDTO> postMap = postApi.getPostMap(Collections.singleton(application.getPostId()));
+            PostRespDTO post = postMap.get(application.getPostId());
+            if (post != null) {
+                respVO.setPostName(post.getName());
+            }
+        }
+        respVO.setEmploymentTypeName(HrmEmploymentTypeEnum.getNameByType(application.getEmploymentType()));
+        respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+        return respVO;
+    }
+
+    private void fillUserNames(HrmRegularizeApplicationRespVO respVO, HrmRegularizeApplicationDO application) {
+        if (application.getUserId() != null) {
+            AdminUserRespDTO user = adminUserApi.getUser(application.getUserId());
+            if (user != null) {
+                respVO.setUserName(user.getNickname());
+            }
+        }
+    }
+
+    @Override
+    public PageResult<HrmRegularizeApplicationRespVO> getRegularizeApplicationPage(HrmRegularizeApplicationPageReqVO pageReqVO) {
+        PageResult<HrmRegularizeApplicationDO> pageResult = regularizeApplicationMapper.selectPage(pageReqVO);
+        if (pageResult.getList().isEmpty()) {
+            return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
+        }
+        Set<Long> userIds = pageResult.getList().stream()
+                .map(HrmRegularizeApplicationDO::getUserId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        Set<Long> deptIds = pageResult.getList().stream()
+                .map(HrmRegularizeApplicationDO::getDeptId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+        Set<Long> postIds = pageResult.getList().stream()
+                .map(HrmRegularizeApplicationDO::getPostId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
+        List<HrmRegularizeApplicationRespVO> list = pageResult.getList().stream().map(app -> {
+            HrmRegularizeApplicationRespVO vo = BeanUtils.toBean(app, HrmRegularizeApplicationRespVO.class);
+            if (app.getUserId() != null && userMap.containsKey(app.getUserId())) {
+                vo.setUserName(userMap.get(app.getUserId()).getNickname());
+            }
+            if (app.getDeptId() != null && deptMap.containsKey(app.getDeptId())) {
+                vo.setDeptName(deptMap.get(app.getDeptId()).getName());
+            }
+            if (app.getPostId() != null && postMap.containsKey(app.getPostId())) {
+                vo.setPostName(postMap.get(app.getPostId()).getName());
+            }
+            vo.setEmploymentTypeName(HrmEmploymentTypeEnum.getNameByType(app.getEmploymentType()));
+            vo.setStatusName(HrmAuditStatusEnum.getNameByStatus(app.getStatus()));
+            return vo;
+        }).collect(Collectors.toList());
+        return new PageResult<>(list, pageResult.getTotal());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitRegularizeApplication(Long id, String processDefinitionKey, Long userId) {
+        HrmRegularizeApplicationDO application = validateRegularizeApplicationExists(id);
+        if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(REGULARIZE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
+        }
+        validateApproveCategoryAndProcessDefinition();
+        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+        if (processDefinition == null) {
+            throw exception(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+                .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
+        regularizeApplicationMapper.updateById(HrmRegularizeApplicationDO.builder()
+                .id(id)
+                .processInstanceId(processInstanceId)
+                .status(HrmAuditStatusEnum.PROCESS.getStatus())
+                .build());
+    }
+
+    private void validateApproveCategoryAndProcessDefinition() {
+        List<String> codes = Collections.singletonList(APPROVE_CATEGORY_CODE);
+        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(codes);
+        if (!categoryMap.containsKey(APPROVE_CATEGORY_CODE)) {
+            throw exception(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            throw exception(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+        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(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public List<Map<String, Object>> getApproveProcessDefinitionList() {
+        validateApproveCategoryAndProcessDefinition();
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+        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);
+            }
+        }
+        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
+    public void updateRegularizeApplicationAuditStatus(Long id, Integer status) {
+        HrmRegularizeApplicationDO application = validateRegularizeApplicationExists(id);
+        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+            log.error("[updateRegularizeApplicationAuditStatus][application({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]", id, status);
+            throw exception(REGULARIZE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+        regularizeApplicationMapper.updateById(HrmRegularizeApplicationDO.builder()
+                .id(id)
+                .status(status)
+                .approveTime(java.time.LocalDateTime.now())
+                .build());
+    }
+
+    @Override
+    public HrmRegularizeApplicationDO getRegularizeApplicationByProcessInstanceId(String processInstanceId) {
+        return regularizeApplicationMapper.selectByProcessInstanceId(processInstanceId);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmRegularizeApplicationStatusListener.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmRegularizeApplicationStatusListener.java
new file mode 100644
index 0000000..ff27e25
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmRegularizeApplicationStatusListener.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.hrm.service.approval.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmRegularizeApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmRegularizeApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmRegularizeApplicationService;
+import cn.iocoder.yudao.module.hrm.util.HrmAuditStatusUtils;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+
+/**
+ * 杞鐢宠瀹℃壒鐨勭粨鏋滅洃鍚櫒瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Component
+@Slf4j
+public class HrmRegularizeApplicationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+    /**
+     * 鍛樺伐鐘舵�侊細鍦ㄨ亴
+     */
+    private static final Integer EMPLOYEE_STATUS_ACTIVE = 1;
+
+    /**
+     * 鐢ㄥ伐鎬ц川锛氭寮�
+     */
+    private static final Integer EMPLOYMENT_TYPE_FORMAL = 1;
+
+    @Resource
+    private HrmRegularizeApplicationService regularizeApplicationService;
+    @Resource
+    private HrmRegularizeApplicationMapper regularizeApplicationMapper;
+    @Resource
+    private HrmEmployeeMapper employeeMapper;
+
+    @Override
+    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+        // 閫氳繃 businessKey 鏌ヨ杞鐢宠
+        Long applicationId;
+        try {
+            applicationId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            return;
+        }
+
+        HrmRegularizeApplicationDO application = regularizeApplicationMapper.selectById(applicationId);
+        if (application == null) {
+            return;
+        }
+
+        // 鍙湁瀹℃壒涓姸鎬佺殑杞鐢宠鎵嶅鐞�
+        if (!HrmAuditStatusEnum.PROCESS.getStatus().equals(application.getStatus())) {
+            log.warn("[onApplicationEvent] 杞鐢宠({}) 涓嶅浜庡鎵逛腑鐘舵�侊紝褰撳墠鐘舵��: {}", applicationId, application.getStatus());
+            return;
+        }
+
+        log.info("[onApplicationEvent] 杞鐢宠({}) 瀹℃壒鐘舵�佹洿鏂�: {}", applicationId, event.getStatus());
+        // 鏇存柊瀹℃壒鐘舵��
+        Integer auditStatus = HrmAuditStatusUtils.convertBpmResultToAuditStatus(event.getStatus());
+        regularizeApplicationService.updateRegularizeApplicationAuditStatus(applicationId, auditStatus);
+        // 瀹℃壒閫氳繃锛氬洖鍐欏憳宸ヤ富妗o紙鐘舵��=鍦ㄨ亴锛岀敤宸ユ�ц川=姝e紡锛岃浆姝f棩鏈� = 鎷熻浆姝f棩鏈熸垨褰撳ぉ锛�
+        if (HrmAuditStatusEnum.APPROVE.getStatus().equals(auditStatus)) {
+            writeBackEmployee(application);
+        }
+    }
+
+    private void writeBackEmployee(HrmRegularizeApplicationDO application) {
+        HrmEmployeeDO employee = employeeMapper.selectByUserId(application.getUserId());
+        if (employee == null) {
+            log.warn("[writeBackEmployee] 杞鐢宠({}) 鐢ㄦ埛({}) 鏈壘鍒板憳宸ユ。妗堬紝璺宠繃鍥炲啓", application.getId(), application.getUserId());
+            return;
+        }
+        LocalDate regularDate = application.getExpectedDate() != null ? application.getExpectedDate() : LocalDate.now();
+        employeeMapper.updateById(HrmEmployeeDO.builder()
+                .id(employee.getId())
+                .employeeStatus(EMPLOYEE_STATUS_ACTIVE)
+                .employmentType(EMPLOYMENT_TYPE_FORMAL)
+                .regularDate(regularDate)
+                .build());
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java
index ed99865..0ab04dd 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java
@@ -2,7 +2,9 @@
 
 import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
 import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmResignationApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
 import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
 import cn.iocoder.yudao.module.hrm.service.approval.HrmResignationApplicationService;
 import cn.iocoder.yudao.module.hrm.util.HrmAuditStatusUtils;
@@ -20,10 +22,17 @@
 @Slf4j
 public class HrmResignationApplicationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
 
+    /**
+     * 鍛樺伐鐘舵�侊細绂昏亴
+     */
+    private static final Integer EMPLOYEE_STATUS_RESIGNED = 3;
+
     @Resource
     private HrmResignationApplicationService resignationApplicationService;
     @Resource
     private HrmResignationApplicationMapper resignationApplicationMapper;
+    @Resource
+    private HrmEmployeeMapper employeeMapper;
 
     @Override
     public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
@@ -50,6 +59,25 @@
         // 鏇存柊瀹℃壒鐘舵��
         Integer auditStatus = HrmAuditStatusUtils.convertBpmResultToAuditStatus(event.getStatus());
         resignationApplicationService.updateResignationApplicationAuditStatus(applicationId, auditStatus);
+        // 瀹℃壒閫氳繃锛氬洖鍐欏憳宸ヤ富妗o紙鐘舵��=绂昏亴锛岀鑱屾棩鏈� = 瀹為檯绂昏亴鏃ユ湡鎴栭璁$鑱屾棩鏈燂級
+        if (HrmAuditStatusEnum.APPROVE.getStatus().equals(auditStatus)) {
+            writeBackEmployee(application);
+        }
+    }
+
+    private void writeBackEmployee(HrmResignationApplicationDO application) {
+        HrmEmployeeDO employee = employeeMapper.selectByUserId(application.getUserId());
+        if (employee == null) {
+            log.warn("[writeBackEmployee] 绂昏亴鐢宠({}) 鐢ㄦ埛({}) 鏈壘鍒板憳宸ユ。妗堬紝璺宠繃鍥炲啓", application.getId(), application.getUserId());
+            return;
+        }
+        java.time.LocalDate leaveDate = application.getActualDate() != null
+                ? application.getActualDate() : application.getExpectedDate();
+        employeeMapper.updateById(HrmEmployeeDO.builder()
+                .id(employee.getId())
+                .employeeStatus(EMPLOYEE_STATUS_RESIGNED)
+                .leaveDate(leaveDate)
+                .build());
     }
 
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java
index 76225df..8c3871f 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java
@@ -2,7 +2,9 @@
 
 import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmTransferApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
 import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmTransferApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
 import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
 import cn.iocoder.yudao.module.hrm.service.approval.HrmTransferApplicationService;
 import cn.iocoder.yudao.module.hrm.util.HrmAuditStatusUtils;
@@ -24,6 +26,8 @@
     private HrmTransferApplicationService transferApplicationService;
     @Resource
     private HrmTransferApplicationMapper transferApplicationMapper;
+    @Resource
+    private HrmEmployeeMapper employeeMapper;
 
     @Override
     public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
@@ -50,6 +54,23 @@
         // 鏇存柊瀹℃壒鐘舵��
         Integer auditStatus = HrmAuditStatusUtils.convertBpmResultToAuditStatus(event.getStatus());
         transferApplicationService.updateTransferApplicationAuditStatus(applicationId, auditStatus);
+        // 瀹℃壒閫氳繃锛氬洖鍐欏憳宸ヤ富妗o紙閮ㄩ棬 = 鐩爣閮ㄩ棬锛屽矖浣� = 鐩爣宀椾綅锛�
+        if (HrmAuditStatusEnum.APPROVE.getStatus().equals(auditStatus)) {
+            writeBackEmployee(application);
+        }
+    }
+
+    private void writeBackEmployee(HrmTransferApplicationDO application) {
+        HrmEmployeeDO employee = employeeMapper.selectByUserId(application.getUserId());
+        if (employee == null) {
+            log.warn("[writeBackEmployee] 璋冨矖鐢宠({}) 鐢ㄦ埛({}) 鏈壘鍒板憳宸ユ。妗堬紝璺宠繃鍥炲啓", application.getId(), application.getUserId());
+            return;
+        }
+        employeeMapper.updateById(HrmEmployeeDO.builder()
+                .id(employee.getId())
+                .deptId(application.getTargetDeptId())
+                .postId(application.getTargetPostId())
+                .build());
     }
 
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
index 352854b..3a1b8cf 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
@@ -64,4 +64,11 @@
      */
     void supplementClockIn(HrmAttendanceExceptionDO exception);
 
+    /**
+     * 鍔犵彮寮傚父瀹℃壒閫氳繃鍚庡洖鍐欏綋鏃ヨ�冨嫟璁板綍鐨勫姞鐝椂闀�
+     *
+     * @param exception 鑰冨嫟寮傚父鐢宠锛坱ype=鍔犵彮锛�
+     */
+    void recordOvertime(HrmAttendanceExceptionDO exception);
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
index f7ff44c..0d03bbd 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
@@ -173,8 +173,59 @@
                     .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
             record.setWorkHours(workHours);
         }
+        // 7. 璁$畻鍔犵彮鏃堕暱锛氳秴杩囪鍒欎笅鐝椂闂寸殑閮ㄥ垎
+        record.setOvertimeHours(computeOvertimeHours(rule.getWorkEndTime(), clockTimeOnly));
         attendanceRecordMapper.updateById(record);
         return record;
+    }
+
+    /**
+     * 璁$畻鍔犵彮鏃堕暱锛堝皬鏃讹級锛歝lockTimeOnly 鏅氫簬 workEndTime 鐨勯儴鍒嗭紝鍚﹀垯涓� 0
+     */
+    private BigDecimal computeOvertimeHours(LocalTime workEndTime, LocalTime clockTimeOnly) {
+        if (workEndTime == null || clockTimeOnly == null || !clockTimeOnly.isAfter(workEndTime)) {
+            return BigDecimal.ZERO;
+        }
+        return BigDecimal.valueOf(Duration.between(workEndTime, clockTimeOnly).toMinutes())
+                .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void recordOvertime(HrmAttendanceExceptionDO exception) {
+        // 浠呭鐞嗗姞鐝被鍨�
+        if (!HrmAttendanceExceptionTypeEnum.OVERTIME.getType().equals(exception.getType())
+                || exception.getExceptionTime() == null) {
+            return;
+        }
+        // 1. 鍙栧憳宸ヨ�冨嫟瑙勫垯锛岀敤瑙勫垯涓嬬彮鏃堕棿鍒板紓甯告椂闂�(瀹為檯涓嬬彮鐐�)鎶樼畻鍔犵彮鏃堕暱锛涙棤瑙勫垯鍒欐棤娉曟姌绠楋紝璺宠繃
+        BigDecimal overtimeHours = BigDecimal.ZERO;
+        HrmEmployeeDO employee = employeeService.getEmployee(exception.getUserId());
+        HrmAttendanceRuleDO rule = employee == null ? null
+                : attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
+        if (rule != null) {
+            overtimeHours = computeOvertimeHours(rule.getWorkEndTime(), exception.getExceptionTime().toLocalTime());
+        }
+        if (overtimeHours.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        // 2. 鏌ヨ褰撴棩鑰冨嫟璁板綍锛屼笉瀛樺湪鍒欒ˉ寤�
+        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(exception.getUserId(), exception.getExceptionDate());
+        if (record == null) {
+            record = HrmAttendanceRecordDO.builder()
+                    .userId(exception.getUserId())
+                    .deptId(employee != null ? employee.getDeptId() : exception.getDeptId())
+                    .date(exception.getExceptionDate())
+                    .dataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType())
+                    .build();
+            record.setOvertimeHours(overtimeHours);
+            attendanceRecordMapper.insert(record);
+            return;
+        }
+        // 3. 宸叉湁璁板綍鍒欑疮鍔犲姞鐝椂闀�
+        BigDecimal existed = record.getOvertimeHours() != null ? record.getOvertimeHours() : BigDecimal.ZERO;
+        record.setOvertimeHours(existed.add(overtimeHours));
+        attendanceRecordMapper.updateById(record);
     }
 
     @Override
@@ -187,11 +238,13 @@
         LocalDate exceptionDate = exception.getExceptionDate();
         LocalDateTime exceptionTime = exception.getExceptionTime();
         Long userId = exception.getUserId();
+        HrmEmployeeDO employee = employeeService.getEmployee(userId);
+        HrmAttendanceRuleDO rule = employee == null ? null
+                : attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
         // 鏌ヨ鑰冨嫟璁板綍
         HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, exceptionDate);
         if (record == null) {
             // 濡傛灉娌℃湁璁板綍锛屽垱寤轰竴鏉�
-            HrmEmployeeDO employee = employeeService.getEmployee(userId);
             record = HrmAttendanceRecordDO.builder()
                     .userId(userId)
                     .deptId(employee != null ? employee.getDeptId() : exception.getDeptId())
@@ -210,6 +263,8 @@
             // 涓嬬彮琛ュ崱
             record.setClockOutTime(exceptionTime);
             record.setClockOutType(HrmAttendanceClockTypeEnum.NORMAL.getType());
+            // 涓嬬彮琛ュ崱鍚屾椂鎶樼畻鍔犵彮鏃堕暱锛堣秴杩囪鍒欎笅鐝椂闂撮儴鍒嗭級
+            record.setOvertimeHours(computeOvertimeHours(rule == null ? null : rule.getWorkEndTime(), time));
         }
         // 鏇存柊鏁版嵁鏉ユ簮涓鸿ˉ鍗″鎵�
         record.setDataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType());
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java
index 102ccd9..33a7b52 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java
@@ -56,11 +56,15 @@
         // 鏇存柊瀹℃壒鐘舵��
         attendanceExceptionService.updateAttendanceExceptionAuditStatus(exceptionId, auditStatus);
 
-        // 琛ュ崱瀹℃壒閫氳繃鍚庯紝鏇存柊鑰冨嫟璁板綍
-        if (HrmAttendanceExceptionTypeEnum.SUPPLEMENT.getType().equals(exception.getType())
-                && HrmAuditStatusEnum.APPROVE.getStatus().equals(auditStatus)) {
-            log.info("[onApplicationEvent] 琛ュ崱鐢宠({}) 瀹℃壒閫氳繃锛屽紑濮嬫洿鏂拌�冨嫟璁板綍", exceptionId);
-            attendanceRecordService.supplementClockIn(exception);
+        // 琛ュ崱/鍔犵彮瀹℃壒閫氳繃鍚庯紝鏇存柊鑰冨嫟璁板綍锛堣ˉ鍗″洖濉笂涓嬬彮鏃堕棿锛屽姞鐝洖鍐欏姞鐝椂闀匡級
+        if (HrmAuditStatusEnum.APPROVE.getStatus().equals(auditStatus)) {
+            if (HrmAttendanceExceptionTypeEnum.SUPPLEMENT.getType().equals(exception.getType())) {
+                log.info("[onApplicationEvent] 琛ュ崱鐢宠({}) 瀹℃壒閫氳繃锛屽紑濮嬫洿鏂拌�冨嫟璁板綍", exceptionId);
+                attendanceRecordService.supplementClockIn(exception);
+            } else if (HrmAttendanceExceptionTypeEnum.OVERTIME.getType().equals(exception.getType())) {
+                log.info("[onApplicationEvent] 鍔犵彮鐢宠({}) 瀹℃壒閫氳繃锛屽紑濮嬪洖鍐欏姞鐝椂闀�", exceptionId);
+                attendanceRecordService.recordOvertime(exception);
+            }
         }
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
index 02e3c20..a857776 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -6,6 +6,9 @@
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.enums.HrmEmploymentTypeEnum;
+import cn.iocoder.yudao.module.mes.api.cal.MesCalTeamApi;
+import cn.iocoder.yudao.module.mes.api.cal.dto.MesCalTeamRespDTO;
 import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEducationSaveReqVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEmergencyContactSaveReqVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportExcelVO;
@@ -81,6 +84,8 @@
     private AdminUserApi adminUserApi;
     @Resource
     private RoleApi roleApi;
+    @Resource
+    private MesCalTeamApi mesCalTeamApi;
     @Resource
     @Lazy // 鑷敞鍏ラ渶寤惰繜鍔犺浇锛屽惁鍒� bean 琚唬鐞嗗寘瑁呭悗娉ㄥ叆鍘熷鐗堟湰瀵艰嚧鍚姩澶辫触
     private HrmEmployeeService self;
@@ -285,6 +290,17 @@
                 respVO.setRoleNames(roles.stream().map(RoleRespDTO::getName).collect(Collectors.joining(",")));
             }
         }
+        // 濉厖鐢ㄥ伐鎬ц川鍚嶇О
+        if (employee.getEmploymentType() != null) {
+            respVO.setEmploymentTypeName(HrmEmploymentTypeEnum.getNameByType(employee.getEmploymentType()));
+        }
+        // 濉厖鎵�灞炵彮缁勫悕绉�
+        if (employee.getTeamId() != null) {
+            List<MesCalTeamRespDTO> teams = mesCalTeamApi.getTeamList(Collections.singleton(employee.getTeamId()));
+            if (CollUtil.isNotEmpty(teams)) {
+                respVO.setTeamName(teams.get(0).getName());
+            }
+        }
         return respVO;
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java
index 5a5ed85..8367ecb 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java
@@ -76,4 +76,12 @@
      */
     List<HrmPerformanceAppraiseResultItemDO> getAppraiseResultItems(Long resultId);
 
+    /**
+     * 鐢熸垚琚�冩牳浜猴紙鎸夎�冩牳鑼冨洿锛氬叏閮ㄥ湪鑱屽憳宸� / 鎸囧畾鐝粍鍛樺伐锛夛紝宸插瓨鍦ㄧ粨鏋滅殑涓嶉噸澶嶇敓鎴�
+     *
+     * @param appraiseId 鑰冩牳鏂规缂栧彿
+     * @return 鏈鏂扮敓鎴愮殑琚�冩牳浜烘暟
+     */
+    Integer generateAppraisees(Long appraiseId);
+
 }
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java
index cf19574..69451fc 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java
@@ -2,6 +2,8 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 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;
@@ -11,7 +13,9 @@
 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.employee.HrmEmployeeDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.*;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
 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;
@@ -67,6 +71,17 @@
     private static final Integer SCORER_SUPERIOR = 2;
 
     /**
+     * 鑰冩牳鑼冨洿绫诲瀷锛�1 鍏ㄩ儴鍛樺伐 / 2 鎸囧畾鐝粍
+     */
+    private static final Integer SCOPE_ALL = 1;
+    private static final Integer SCOPE_TEAM = 2;
+
+    /**
+     * 鍦ㄨ亴鍛樺伐鐘舵��
+     */
+    private static final Integer EMPLOYEE_STATUS_ACTIVE = 1;
+
+    /**
      * BPM 缁╂晥鑰冩牳瀹℃壒鍒嗙被缂栫爜
      */
     private static final String APPRAISE_APPROVE_CATEGORY_CODE = "performance_appraise_approve";
@@ -80,6 +95,8 @@
     @Resource
     private HrmPerformanceAppraiseResultItemMapper resultItemMapper;
     @Resource
+    private HrmEmployeeMapper employeeMapper;
+    @Resource
     private BpmProcessInstanceApi bpmProcessInstanceApi;
     @Resource
     private BpmCategoryService bpmCategoryService;
@@ -92,6 +109,8 @@
         HrmPerformanceAppraiseDO appraise = BeanUtils.toBean(createReqVO, HrmPerformanceAppraiseDO.class, req -> req
                 .setStatus(STATUS_DRAFT)
                 .setAuditStatus(HrmAuditStatusEnum.DRAFT.getStatus()));
+        appraise.setScopeTeamIds(CollUtil.isEmpty(createReqVO.getScopeTeamIds())
+                ? null : JSONUtil.toJsonStr(createReqVO.getScopeTeamIds()));
         appraiseMapper.insert(appraise);
         return appraise.getId();
     }
@@ -104,6 +123,8 @@
                 .setStatus(oldAppraise.getStatus())
                 .setAuditStatus(oldAppraise.getAuditStatus())
                 .setProcessInstanceId(oldAppraise.getProcessInstanceId()));
+        updateObj.setScopeTeamIds(CollUtil.isEmpty(updateReqVO.getScopeTeamIds())
+                ? null : JSONUtil.toJsonStr(updateReqVO.getScopeTeamIds()));
         appraiseMapper.updateById(updateObj);
     }
 
@@ -391,6 +412,44 @@
         return resultItemMapper.selectListByResultId(resultId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Integer generateAppraisees(Long appraiseId) {
+        HrmPerformanceAppraiseDO appraise = validateAppraiseExists(appraiseId);
+        // 1. 鎸夎�冩牳鑼冨洿绛涢�夊憳宸�
+        List<HrmEmployeeDO> employees;
+        if (Objects.equals(appraise.getScopeType(), SCOPE_TEAM)) {
+            List<Long> teamIds = StrUtil.isEmpty(appraise.getScopeTeamIds())
+                    ? Collections.emptyList() : JSONUtil.toList(appraise.getScopeTeamIds(), Long.class);
+            if (CollUtil.isEmpty(teamIds)) {
+                throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_SCOPE_TEAM_EMPTY);
+            }
+            employees = employeeMapper.selectListByTeamIds(teamIds);
+        } else {
+            employees = employeeMapper.selectListByStatus(EMPLOYEE_STATUS_ACTIVE);
+        }
+        if (CollUtil.isEmpty(employees)) {
+            return 0;
+        }
+        // 2. 宸插瓨鍦ㄧ粨鏋滅殑涓嶅啀鐢熸垚锛堝箓绛夛級
+        List<HrmPerformanceAppraiseResultDO> existing = resultMapper.selectList(
+                new LambdaQueryWrapperX<HrmPerformanceAppraiseResultDO>()
+                        .eq(HrmPerformanceAppraiseResultDO::getAppraiseId, appraiseId));
+        Set<Long> existingEmployeeIds = convertSet(existing, HrmPerformanceAppraiseResultDO::getEmployeeId);
+        // 3. 鐢熸垚绌虹櫧缁撴灉琛�
+        int count = 0;
+        for (HrmEmployeeDO employee : employees) {
+            if (existingEmployeeIds.contains(employee.getId())) {
+                continue;
+            }
+            resultMapper.insert(new HrmPerformanceAppraiseResultDO()
+                    .setAppraiseId(appraiseId)
+                    .setEmployeeId(employee.getId()));
+            count++;
+        }
+        return count;
+    }
+
     // ========== 绉佹湁鏂规硶 ==========
 
     private Integer calcGrade(BigDecimal finalScore) {
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindService.java
new file mode 100644
index 0000000..4b462a5
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindService.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.hrm.service.remind;
+
+/**
+ * HRM 璇佷功/鍚堝悓鍒版湡鎻愰啋 Service 鎺ュ彛
+ *
+ * <p>瀵瑰嵆灏嗗埌鏈熺殑璇佷功璧勮川銆佸姵鍔ㄥ悎鍚屽悜鎸佽瘉/鍦ㄥ矖鍛樺伐鍙戦�佺珯鍐呬俊鎻愰啋锛�
+ * 婊¤冻鐢靛姏琛屼笟瀹夊叏鍩硅鍚堟牸璇併�佺壒绉嶄綔涓氳瘉涔︺�佷粠涓氳祫璐ㄧ瓑鍚堣缁湡瑕佹眰銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmExpiryRemindService {
+
+    /**
+     * 鍙戦�佽瘉涔﹁祫璐ㄥ埌鏈熸彁閱掞紙鍗冲皢鍒版湡 [today, today+N]锛�
+     */
+    void sendCertificateExpireRemind();
+
+    /**
+     * 鍙戦�佸姵鍔ㄥ悎鍚屽埌鏈熸彁閱掞紙鍗冲皢鍒版湡 [today, today+N]锛�
+     */
+    void sendContractExpireRemind();
+
+    /**
+     * 鍙戦�佸叏閮ㄥ埌鏈熸彁閱掞紙鍏堣瘉涔﹀悗鍚堝悓锛夛紝渚涙瘡鏃ュ畾鏃朵换鍔¤皟鐢�
+     */
+    void sendAll();
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindServiceImpl.java
new file mode 100644
index 0000000..72923c9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindServiceImpl.java
@@ -0,0 +1,217 @@
+package cn.iocoder.yudao.module.hrm.service.remind;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeContractDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.certificate.HrmCertificateMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeContractMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmExpiryRemindRedisDAO;
+import cn.iocoder.yudao.module.hrm.enums.HrmEmployeeStatusEnum;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
+import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
+import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+/**
+ * HRM 璇佷功/鍚堝悓鍒版湡鎻愰啋 Service 瀹炵幇绫�
+ *
+ * <p>鍙傜収 MES 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋瀹炵幇锛歊edis 鏃ラ攣闃插瀹炰緥骞跺彂閲嶅鎵弿銆佹寜涓氬姟璁板綍+鍒版湡鏃ュ幓閲嶏紝
+ * 鍚戝憳宸ョ郴缁熻处鍙凤紙hrm_employee.userId锛夊彂閫佺珯鍐呬俊锛涙彁閱掑ぉ鏁颁粠 infra_config 璇诲彇锛岄粯璁� 30 澶┿��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Slf4j
+@Service
+public class HrmExpiryRemindServiceImpl implements HrmExpiryRemindService {
+
+    /**
+     * 涓氬姟鏍囪瘑锛氳瘉涔﹁祫璐�
+     */
+    private static final String BIZ_CERT = "cert";
+    /**
+     * 涓氬姟鏍囪瘑锛氬憳宸ュ悎鍚�
+     */
+    private static final String BIZ_CONTRACT = "contract";
+
+    /**
+     * 璇佷功鍒版湡鎻愰啋澶╂暟閰嶇疆 KEY锛坕nfra_config锛�
+     */
+    private static final String CERT_REMIND_DAYS_KEY = "hrm.certificate.remind-days";
+    /**
+     * 鍚堝悓鍒版湡鎻愰啋澶╂暟閰嶇疆 KEY锛坕nfra_config锛�
+     */
+    private static final String CONTRACT_REMIND_DAYS_KEY = "hrm.contract.remind-days";
+    /**
+     * 鎻愰啋澶╂暟榛樿鍊�
+     */
+    private static final int DEFAULT_REMIND_DAYS = 30;
+
+    /**
+     * 璇佷功鍒版湡鎻愰啋绔欏唴淇℃ā鏉跨紪鍙�
+     */
+    private static final String CERT_REMIND_TEMPLATE = "hrm_certificate_expire_remind";
+    /**
+     * 鍚堝悓鍒版湡鎻愰啋绔欏唴淇℃ā鏉跨紪鍙�
+     */
+    private static final String CONTRACT_REMIND_TEMPLATE = "hrm_contract_expire_remind";
+
+    @Resource
+    private HrmCertificateMapper certificateMapper;
+    @Resource
+    private HrmEmployeeContractMapper contractMapper;
+    @Resource
+    private HrmExpiryRemindRedisDAO expiryRemindRedisDAO;
+    @Resource
+    private ConfigApi configApi;
+    @Resource
+    private NotifyMessageSendApi notifyMessageSendApi;
+    @Resource
+    private HrmEmployeeService employeeService;
+
+    @Override
+    public void sendAll() {
+        sendCertificateExpireRemind();
+        sendContractExpireRemind();
+    }
+
+    @Override
+    public void sendCertificateExpireRemind() {
+        try {
+            LocalDate today = LocalDate.now();
+            if (!expiryRemindRedisDAO.tryAcquireScanLock(BIZ_CERT, today)) {
+                return;
+            }
+            int remindDays = getRemindDays(CERT_REMIND_DAYS_KEY);
+            List<HrmCertificateDO> list = certificateMapper.selectListByExpireDateBetween(
+                    today, today.plusDays(remindDays));
+            if (CollUtil.isEmpty(list)) {
+                return;
+            }
+            Map<Long, HrmEmployeeDO> employeeMap = buildEmployeeMap(
+                    convertSet(list, HrmCertificateDO::getEmployeeId));
+            for (HrmCertificateDO certificate : list) {
+                if (certificate.getExpireDate() == null) {
+                    continue;
+                }
+                HrmEmployeeDO employee = employeeMap.get(certificate.getEmployeeId());
+                if (!canNotify(employee)) {
+                    continue;
+                }
+                if (!expiryRemindRedisDAO.tryMarkReminded(BIZ_CERT, certificate.getId(), certificate.getExpireDate())) {
+                    continue;
+                }
+                sendCertificateRemind(certificate, employee);
+            }
+        } catch (Exception ex) {
+            // best-effort锛氭彁閱掑け璐ヤ笉褰卞搷鍒楄〃鏌ヨ/瀹氭椂浠诲姟鏁翠綋
+            log.error("[sendCertificateExpireRemind][鍙戦�佽瘉涔﹀埌鏈熸彁閱掑け璐", ex);
+        }
+    }
+
+    @Override
+    public void sendContractExpireRemind() {
+        try {
+            LocalDate today = LocalDate.now();
+            if (!expiryRemindRedisDAO.tryAcquireScanLock(BIZ_CONTRACT, today)) {
+                return;
+            }
+            int remindDays = getRemindDays(CONTRACT_REMIND_DAYS_KEY);
+            List<HrmEmployeeContractDO> list = contractMapper.selectListExpiringBetween(
+                    today, today.plusDays(remindDays));
+            if (CollUtil.isEmpty(list)) {
+                return;
+            }
+            Map<Long, HrmEmployeeDO> employeeMap = buildEmployeeMap(
+                    convertSet(list, HrmEmployeeContractDO::getEmployeeId));
+            for (HrmEmployeeContractDO contract : list) {
+                if (contract.getEndDate() == null) {
+                    continue;
+                }
+                HrmEmployeeDO employee = employeeMap.get(contract.getEmployeeId());
+                if (!canNotify(employee)) {
+                    continue;
+                }
+                if (!expiryRemindRedisDAO.tryMarkReminded(BIZ_CONTRACT, contract.getId(), contract.getEndDate())) {
+                    continue;
+                }
+                sendContractRemind(contract, employee);
+            }
+        } catch (Exception ex) {
+            // best-effort锛氭彁閱掑け璐ヤ笉褰卞搷鍒楄〃鏌ヨ/瀹氭椂浠诲姟鏁翠綋
+            log.error("[sendContractExpireRemind][鍙戦�佸悎鍚屽埌鏈熸彁閱掑け璐", ex);
+        }
+    }
+
+    private boolean canNotify(HrmEmployeeDO employee) {
+        // 浠呭悜鏈夌郴缁熻处鍙蜂笖鍦ㄥ矖/璇曠敤鍛樺伐鍙戦�佹彁閱掞紱绂昏亴鍛樺伐锛堣处鍙峰宸插仠鐢級涓嶆墦鎵�
+        return employee != null && employee.getUserId() != null
+                && !HrmEmployeeStatusEnum.LEAVE.getStatus().equals(employee.getEmployeeStatus());
+    }
+
+    private void sendCertificateRemind(HrmCertificateDO certificate, HrmEmployeeDO employee) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("employeeName", StrUtil.nullToEmpty(employee.getName()));
+        params.put("certTypeName", StrUtil.nullToEmpty(convertCertTypeName(certificate.getCertType())));
+        params.put("certName", StrUtil.nullToEmpty(certificate.getCertName()));
+        params.put("expireDate", StrUtil.nullToEmpty(certificate.getExpireDate() == null ? null : certificate.getExpireDate().toString()));
+        notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
+                .setUserId(employee.getUserId())
+                .setTemplateCode(CERT_REMIND_TEMPLATE)
+                .setTemplateParams(params));
+    }
+
+    private void sendContractRemind(HrmEmployeeContractDO contract, HrmEmployeeDO employee) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("employeeName", StrUtil.nullToEmpty(employee.getName()));
+        params.put("contractNo", StrUtil.nullToEmpty(contract.getContractNo()));
+        params.put("endDate", StrUtil.nullToEmpty(contract.getEndDate() == null ? null : contract.getEndDate().toString()));
+        notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
+                .setUserId(employee.getUserId())
+                .setTemplateCode(CONTRACT_REMIND_TEMPLATE)
+                .setTemplateParams(params));
+    }
+
+    private Map<Long, HrmEmployeeDO> buildEmployeeMap(java.util.Collection<Long> employeeIds) {
+        if (CollUtil.isEmpty(employeeIds)) {
+            return Map.of();
+        }
+        return employeeService.getEmployeeList(employeeIds).stream()
+                .collect(java.util.stream.Collectors.toMap(HrmEmployeeDO::getId, e -> e, (a, b) -> a));
+    }
+
+    private int getRemindDays(String configKey) {
+        String value = configApi.getConfigValueByKey(configKey);
+        if (StrUtil.isBlank(value)) {
+            return DEFAULT_REMIND_DAYS;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException ex) {
+            log.warn("[getRemindDays][閰嶇疆 {} 鐨勫�� {} 闈炴硶锛屼娇鐢ㄩ粯璁ゅ�� {}]", configKey, value, DEFAULT_REMIND_DAYS);
+            return DEFAULT_REMIND_DAYS;
+        }
+    }
+
+    private String convertCertTypeName(Integer certType) {
+        return switch (certType == null ? 0 : certType) {
+            case 1 -> "鐗圭浣滀笟璇佷功";
+            case 2 -> "浠庝笟璧勮川";
+            case 3 -> "鎶�鑳界瓑绾ц瘉涔�";
+            case 4 -> "瀹夊叏鍩硅鍚堟牸璇�";
+            default -> "璇佷功";
+        };
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryService.java
new file mode 100644
index 0000000..d41a56c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryService.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmEmployeeSalaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐钖叕妗f Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmEmployeeSalaryService {
+
+    /**
+     * 鍒涘缓鍛樺伐钖叕妗f
+     *
+     * @param salary 妗f淇℃伅
+     * @return 妗fID
+     */
+    Long createEmployeeSalary(HrmEmployeeSalaryDO salary);
+
+    /**
+     * 鏇存柊鍛樺伐钖叕妗f
+     *
+     * @param salary 妗f淇℃伅
+     */
+    void updateEmployeeSalary(HrmEmployeeSalaryDO salary);
+
+    /**
+     * 鍒犻櫎鍛樺伐钖叕妗f
+     *
+     * @param id 妗fID
+     */
+    void deleteEmployeeSalary(Long id);
+
+    /**
+     * 鑾峰彇鍛樺伐钖叕妗f
+     *
+     * @param id 妗fID
+     * @return 妗f淇℃伅
+     */
+    HrmEmployeeSalaryDO getEmployeeSalary(Long id);
+
+    /**
+     * 鑾峰彇鍛樺伐钖叕妗f鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉璇锋眰
+     * @return 鍒嗛〉缁撴灉
+     */
+    PageResult<HrmEmployeeSalaryDO> getEmployeeSalaryPage(HrmEmployeeSalaryPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰彇鍛樺伐钖叕妗f鍘嗗彶鍒楄〃
+     *
+     * @param userId 鍛樺伐ID
+     * @return 妗f鍒楄〃
+     */
+    List<HrmEmployeeSalaryDO> getEmployeeSalaryList(Long userId);
+
+    /**
+     * 鑾峰彇鍛樺伐鏈�鏂扮敓鏁堢殑钖叕妗f
+     *
+     * @param userId 鍛樺伐ID
+     * @return 妗f淇℃伅
+     */
+    HrmEmployeeSalaryDO getLatestEmployeeSalary(Long userId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryServiceImpl.java
new file mode 100644
index 0000000..6c829eb
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSalaryServiceImpl.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmEmployeeSalaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.EMPLOYEE_SALARY_NOT_EXISTS;
+
+/**
+ * 鍛樺伐钖叕妗f Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class HrmEmployeeSalaryServiceImpl implements HrmEmployeeSalaryService {
+
+    @Resource
+    private HrmEmployeeSalaryMapper employeeSalaryMapper;
+
+    @Override
+    public Long createEmployeeSalary(HrmEmployeeSalaryDO salary) {
+        employeeSalaryMapper.insert(salary);
+        return salary.getId();
+    }
+
+    @Override
+    public void updateEmployeeSalary(HrmEmployeeSalaryDO salary) {
+        validateEmployeeSalaryExists(salary.getId());
+        employeeSalaryMapper.updateById(salary);
+    }
+
+    @Override
+    public void deleteEmployeeSalary(Long id) {
+        validateEmployeeSalaryExists(id);
+        employeeSalaryMapper.deleteById(id);
+    }
+
+    @Override
+    public HrmEmployeeSalaryDO getEmployeeSalary(Long id) {
+        return employeeSalaryMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<HrmEmployeeSalaryDO> getEmployeeSalaryPage(HrmEmployeeSalaryPageReqVO pageReqVO) {
+        return employeeSalaryMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<HrmEmployeeSalaryDO> getEmployeeSalaryList(Long userId) {
+        return employeeSalaryMapper.selectListByUserId(userId);
+    }
+
+    @Override
+    public HrmEmployeeSalaryDO getLatestEmployeeSalary(Long userId) {
+        return employeeSalaryMapper.selectLatestByUserId(userId);
+    }
+
+    private void validateEmployeeSalaryExists(Long id) {
+        if (employeeSalaryMapper.selectById(id) == null) {
+            throw exception(EMPLOYEE_SALARY_NOT_EXISTS);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
index 8279b4e..b6b375f 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -5,22 +5,30 @@
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationPageReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceSummaryDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseResultDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryCalculationDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDetailDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
 import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceSummaryMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.HrmPerformanceAppraiseMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.HrmPerformanceAppraiseResultMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSocialSecurityMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmLeaveTypeConfigMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryCalculationMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentDetailMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryStructureMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper;
 import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -63,6 +71,24 @@
     private static final BigDecimal DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO = new BigDecimal("0.005");
     private static final BigDecimal DEFAULT_HOUSING_FUND_PERSONAL_RATIO = new BigDecimal("0.12");
 
+    /**
+     * 鍔犵彮宸ヨ祫鍊嶇巼榛樿鍊硷紙钖叕缁撴瀯鏈厤缃椂浣跨敤锛�
+     */
+    private static final BigDecimal DEFAULT_OVERTIME_PAY_RATIO = new BigDecimal("1.5");
+
+    /**
+     * 榛樿搴斿嚭鍕ゅぉ鏁帮紙鏃犺�冨嫟姹囨�绘椂鐨勫厹搴曞�硷級
+     */
+    private static final int DEFAULT_WORK_DAYS = 22;
+
+    /**
+     * 缁╂晥宸ヨ祫妗g骇绯绘暟锛�1-浼樼 2-鑹ソ 3-鍚堟牸 4-寰呮敼杩�
+     */
+    private static final BigDecimal PERFORMANCE_GRADE_FACTOR_EXCELLENT = new BigDecimal("1.2");
+    private static final BigDecimal PERFORMANCE_GRADE_FACTOR_GOOD = new BigDecimal("1.0");
+    private static final BigDecimal PERFORMANCE_GRADE_FACTOR_PASS = new BigDecimal("0.8");
+    private static final BigDecimal PERFORMANCE_GRADE_FACTOR_IMPROVE = new BigDecimal("0.6");
+
     @Resource
     private HrmSalaryCalculationMapper salaryCalculationMapper;
     @Resource
@@ -87,6 +113,14 @@
     private HrmSalaryPaymentMapper salaryPaymentMapper;
     @Resource
     private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+    @Resource
+    private HrmAttendanceSummaryMapper attendanceSummaryMapper;
+    @Resource
+    private HrmPerformanceAppraiseMapper performanceAppraiseMapper;
+    @Resource
+    private HrmPerformanceAppraiseResultMapper performanceAppraiseResultMapper;
+    @Resource
+    private HrmSalaryStructureMapper salaryStructureMapper;
 
     @Override
     public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
@@ -204,7 +238,21 @@
         BigDecimal mealAllowance = employeeSalary.getMealAllowance() != null ? employeeSalary.getMealAllowance() : BigDecimal.ZERO;
         BigDecimal transportAllowance = employeeSalary.getTransportAllowance() != null ? employeeSalary.getTransportAllowance() : BigDecimal.ZERO;
         BigDecimal otherAllowance = employeeSalary.getOtherAllowance() != null ? employeeSalary.getOtherAllowance() : BigDecimal.ZERO;
+        // ===== 鑰冨嫟閾捐矾锛氬簲鍑哄嫟澶╂暟 / 鍔犵彮鏃堕暱 / 缂哄嫟鎵f =====
+        HrmAttendanceSummaryDO attendanceSummary = attendanceSummaryMapper.selectByUserIdAndYearMonth(employeeId, period);
+        int workDays = attendanceSummary != null && attendanceSummary.getWorkDays() != null
+                ? attendanceSummary.getWorkDays() : DEFAULT_WORK_DAYS;
+        BigDecimal overtimeHours = attendanceSummary != null && attendanceSummary.getOvertimeHours() != null
+                ? attendanceSummary.getOvertimeHours() : BigDecimal.ZERO;
+        // ===== 鍔犵彮宸ヨ祫 = 鏃惰柂 脳 鍔犵彮鏃堕暱 脳 鍊嶇巼锛堟椂钖� = 鍩烘湰宸ヨ祫 梅 (搴斿嚭鍕ゅぉ 脳 8)锛� =====
         BigDecimal overtimePay = BigDecimal.ZERO;
+        if (overtimeHours.compareTo(BigDecimal.ZERO) > 0 && workDays > 0) {
+            BigDecimal hourlyRate = baseSalary.divide(BigDecimal.valueOf(workDays * 8L), 4, RoundingMode.HALF_UP);
+            overtimePay = hourlyRate.multiply(overtimeHours).multiply(getOvertimePayRatio(employeeSalary, employee))
+                    .setScale(2, RoundingMode.HALF_UP);
+        }
+        // ===== 缁╂晥宸ヨ祫锛氭寜褰撴湀缁╂晥缁撴灉妗g骇绯绘暟鎶樼畻锛堟棤缁撴灉鎸夊叏棰濓級 =====
+        performanceSalary = calculatePerformanceSalary(performanceSalary, employeeId, period);
 
         // 搴斿彂宸ヨ祫鎬婚
         BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
@@ -238,8 +286,8 @@
         // 鍏Н閲戞墸闄� = 鍩烘暟 脳 鍏Н閲戜釜浜烘瘮渚�
         BigDecimal housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio).setScale(2, RoundingMode.HALF_UP);
 
-        // 璁$畻璇峰亣鎵f
-        BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary);
+        // 璁$畻缂哄嫟鎵f锛堣�冨嫟鍙e緞锛氱己鍕ゅぉ鏁� = 搴斿嚭鍕� 鈭� 瀹為檯鍑哄嫟锛涙棤鑰冨嫟姹囨�绘椂鍥為��鍒拌鍋囩敵璇峰彛寰勶級
+        BigDecimal leaveDeduction = calculateDeduction(attendanceSummary, baseSalary, workDays, employee, period);
 
         // 璁$畻涓◣鎵i櫎
         // 搴旂撼绋庢墍寰楅 = 搴斿彂宸ヨ祫 - 绀句繚鎵i櫎 - 鍏Н閲戞墸闄� - 璧峰緛鐐�(5000)
@@ -276,13 +324,66 @@
                 .otherDeduction(otherDeduction)
                 .leaveDeduction(leaveDeduction)
                 .actualSalary(actualSalary)
-                .workDays(22) // 榛樿宸ヤ綔鏃�
-                .overtimeHours(BigDecimal.ZERO)
+                .workDays(workDays) // 鍔ㄦ�佸簲鍑哄嫟澶╂暟锛堟潵鑷�冨嫟姹囨�伙級
+                .overtimeHours(overtimeHours) // 鍔犵彮鏃堕暱锛堟潵鑷�冨嫟姹囨�伙級
                 .status(0) // 寰呯‘璁�
                 .build();
     }
 
     /**
+     * 璁$畻缂哄嫟鎵f锛氫紭鍏堟寜鑰冨嫟姹囨�伙紙缂哄嫟澶╂暟 = 搴斿嚭鍕� 鈭� 瀹為檯鍑哄嫟锛夛紱鏃犳眹鎬绘椂鍥為��鍒拌鍋囩敵璇峰彛寰�
+     */
+    private BigDecimal calculateDeduction(HrmAttendanceSummaryDO attendanceSummary, BigDecimal baseSalary,
+                                          int workDays, HrmEmployeeDO employee, String period) {
+        if (attendanceSummary != null && workDays > 0 && attendanceSummary.getActualDays() != null) {
+            int absentDays = Math.max(0, workDays - attendanceSummary.getActualDays());
+            if (absentDays == 0) {
+                return BigDecimal.ZERO;
+            }
+            BigDecimal daySalary = baseSalary.divide(BigDecimal.valueOf(workDays), 4, RoundingMode.HALF_UP);
+            return daySalary.multiply(BigDecimal.valueOf(absentDays)).setScale(2, RoundingMode.HALF_UP);
+        }
+        return calculateLeaveDeduction(employee, period, baseSalary);
+    }
+
+    /**
+     * 璁$畻缁╂晥宸ヨ祫锛氭寜褰撴湀宸插畬鎴愪笖鍖归厤鏍哥畻鍛ㄦ湡鐨勬湀搴︾哗鏁堣�冩牳缁撴灉鐨勬。绾х郴鏁版姌绠�
+     */
+    private BigDecimal calculatePerformanceSalary(BigDecimal performanceSalary, Long employeeId, String period) {
+        HrmPerformanceAppraiseDO appraise = performanceAppraiseMapper.selectMonthlyCompletedByPeriod(period);
+        if (appraise == null) {
+            return performanceSalary;
+        }
+        HrmPerformanceAppraiseResultDO result = performanceAppraiseResultMapper
+                .selectByAppraiseIdAndEmployeeId(appraise.getId(), employeeId);
+        if (result == null || result.getGrade() == null) {
+            return performanceSalary;
+        }
+        BigDecimal factor = switch (result.getGrade()) {
+            case 1 -> PERFORMANCE_GRADE_FACTOR_EXCELLENT;
+            case 2 -> PERFORMANCE_GRADE_FACTOR_GOOD;
+            case 3 -> PERFORMANCE_GRADE_FACTOR_PASS;
+            case 4 -> PERFORMANCE_GRADE_FACTOR_IMPROVE;
+            default -> BigDecimal.ONE;
+        };
+        return performanceSalary.multiply(factor).setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 鑾峰彇鍔犵彮宸ヨ祫鍊嶇巼锛堣柂閰粨鏋勬湭閰嶇疆鏃堕粯璁� 1.5锛�
+     */
+    private BigDecimal getOvertimePayRatio(HrmEmployeeSalaryDO employeeSalary, HrmEmployeeDO employee) {
+        Long structureId = employeeSalary.getSalaryStructureId() != null
+                ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId();
+        if (structureId == null) {
+            return DEFAULT_OVERTIME_PAY_RATIO;
+        }
+        HrmSalaryStructureDO structure = salaryStructureMapper.selectById(structureId);
+        return structure != null && structure.getOvertimePayRatio() != null
+                ? structure.getOvertimePayRatio() : DEFAULT_OVERTIME_PAY_RATIO;
+    }
+
+    /**
      * 璁$畻璇峰亣鎵f
      *
      * @param employee   鍛樺伐淇℃伅
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java
new file mode 100644
index 0000000..496ddc1
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryStructurePageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
+
+import java.util.List;
+
+/**
+ * 钖叕缁撴瀯 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmSalaryStructureService {
+
+    /**
+     * 鍒涘缓钖叕缁撴瀯
+     *
+     * @param structure 缁撴瀯淇℃伅
+     * @return 缁撴瀯ID
+     */
+    Long createStructure(HrmSalaryStructureDO structure);
+
+    /**
+     * 鏇存柊钖叕缁撴瀯
+     *
+     * @param structure 缁撴瀯淇℃伅
+     */
+    void updateStructure(HrmSalaryStructureDO structure);
+
+    /**
+     * 鍒犻櫎钖叕缁撴瀯
+     *
+     * @param id 缁撴瀯ID
+     */
+    void deleteStructure(Long id);
+
+    /**
+     * 鑾峰彇钖叕缁撴瀯
+     *
+     * @param id 缁撴瀯ID
+     * @return 缁撴瀯淇℃伅
+     */
+    HrmSalaryStructureDO getStructure(Long id);
+
+    /**
+     * 鑾峰彇钖叕缁撴瀯鍒楄〃
+     *
+     * @param status 鐘舵�侊紙鍙�夛級
+     * @return 缁撴瀯鍒楄〃
+     */
+    List<HrmSalaryStructureDO> getStructureList(Integer status);
+
+    /**
+     * 鑾峰彇钖叕缁撴瀯鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉璇锋眰
+     * @return 鍒嗛〉缁撴灉
+     */
+    PageResult<HrmSalaryStructureDO> getStructurePage(HrmSalaryStructurePageReqVO pageReqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java
new file mode 100644
index 0000000..a1d7817
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryStructurePageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryStructureMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_STRUCTURE_NOT_EXISTS;
+
+/**
+ * 钖叕缁撴瀯 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class HrmSalaryStructureServiceImpl implements HrmSalaryStructureService {
+
+    @Resource
+    private HrmSalaryStructureMapper structureMapper;
+
+    @Override
+    public Long createStructure(HrmSalaryStructureDO structure) {
+        structureMapper.insert(structure);
+        return structure.getId();
+    }
+
+    @Override
+    public void updateStructure(HrmSalaryStructureDO structure) {
+        validateStructureExists(structure.getId());
+        structureMapper.updateById(structure);
+    }
+
+    @Override
+    public void deleteStructure(Long id) {
+        validateStructureExists(id);
+        structureMapper.deleteById(id);
+    }
+
+    @Override
+    public HrmSalaryStructureDO getStructure(Long id) {
+        return structureMapper.selectById(id);
+    }
+
+    @Override
+    public List<HrmSalaryStructureDO> getStructureList(Integer status) {
+        return structureMapper.selectListByStatus(status);
+    }
+
+    @Override
+    public PageResult<HrmSalaryStructureDO> getStructurePage(HrmSalaryStructurePageReqVO pageReqVO) {
+        return structureMapper.selectPage(pageReqVO);
+    }
+
+    private void validateStructureExists(Long id) {
+        if (structureMapper.selectById(id) == null) {
+            throw exception(SALARY_STRUCTURE_NOT_EXISTS);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingService.java
new file mode 100644
index 0000000..b9bdce1
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingService.java
@@ -0,0 +1,121 @@
+package cn.iocoder.yudao.module.hrm.service.training;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingParticipantUpdateReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingParticipantDO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * HRM 鍩硅娲诲姩 Service 鎺ュ彛
+ *
+ * <p>绠$悊瀹夊叏鍩硅銆佹妧鑳藉煿璁瓑鍩硅璁″垝涓庡弬璁�/鑰冩牳缁撴灉锛涘煿璁畬鎴愭椂瀵瑰悎鏍间汉鍛樿仈鍔ㄧ敓鎴愯瘉涔﹁祫璐ㄣ��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmTrainingService {
+
+    /**
+     * 鍒涘缓鍩硅娲诲姩
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 鍩硅娲诲姩ID
+     */
+    Long createTraining(HrmTrainingSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊鍩硅娲诲姩锛堜粎鑽夌/寰呭紑灞曞彲鏀癸紝涓斾笉鏀圭姸鎬侊級
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateTraining(HrmTrainingSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎鍩硅娲诲姩锛堢骇鑱斿垹闄ゅ弬璁汉鍛橈紱宸插畬鎴�/宸插彇娑堜笉鍙垹锛�
+     *
+     * @param ids 鍩硅娲诲姩ID闆嗗悎
+     */
+    void deleteTraining(Collection<Long> ids);
+
+    /**
+     * 鑾峰緱鍩硅娲诲姩
+     *
+     * @param id 鍩硅娲诲姩ID
+     * @return 鍩硅娲诲姩
+     */
+    HrmTrainingDO getTraining(Long id);
+
+    /**
+     * 鑾峰緱鍩硅娲诲姩鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 鍩硅娲诲姩鍒嗛〉
+     */
+    PageResult<HrmTrainingDO> getTrainingPage(HrmTrainingPageReqVO pageReqVO);
+
+    /**
+     * 寮�濮嬪煿璁紙鑽夌 鈫� 寰呭紑灞曪級
+     *
+     * @param id 鍩硅娲诲姩ID
+     */
+    void startTraining(Long id);
+
+    /**
+     * 瀹屾垚鍩硅锛堝緟寮�灞�/鑽夌 鈫� 宸插畬鎴愶級锛屽畬鎴愭椂瀵瑰悎鏍间汉鍛樿仈鍔ㄨ嚜鍔ㄥ彂璇�
+     *
+     * @param id 鍩硅娲诲姩ID
+     */
+    void completeTraining(Long id);
+
+    /**
+     * 鍙栨秷鍩硅锛堣崏绋�/寰呭紑灞� 鈫� 宸插彇娑堬級
+     *
+     * @param id 鍩硅娲诲姩ID
+     */
+    void cancelTraining(Long id);
+
+    /**
+     * 娣诲姞鍙傝鍛樺伐锛堣嚜鍔ㄨ烦杩囧凡鍦ㄥ悕鍗曚腑鐨勫憳宸ワ級
+     *
+     * @param trainingId 鍩硅娲诲姩ID
+     * @param employeeIds 鍛樺伐ID闆嗗悎
+     * @return 瀹為檯鏂板鏁伴噺
+     */
+    int addParticipants(Long trainingId, Collection<Long> employeeIds);
+
+    /**
+     * 鏇存柊鍙傝璁板綍锛堝弬鍔犳儏鍐�/鎴愮哗/鏄惁鍚堟牸锛�
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateParticipant(HrmTrainingParticipantUpdateReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎鍙傝璁板綍
+     *
+     * @param ids 鍙傝璁板綍ID闆嗗悎
+     */
+    void deleteParticipants(Collection<Long> ids);
+
+    /**
+     * 鑾峰緱鏌愭鍩硅鐨勫叏閮ㄥ弬璁褰�
+     *
+     * @param trainingId 鍩硅娲诲姩ID
+     * @return 鍙傝璁板綍鍒楄〃
+     */
+    List<HrmTrainingParticipantDO> getParticipantsByTrainingId(Long trainingId);
+
+    /**
+     * 鎵归噺缁熻鍚勫煿璁椿鍔ㄧ殑鍙傝浜烘暟锛堝垎椤靛垪琛ㄥ睍绀虹敤锛�
+     *
+     * @param trainingIds 鍩硅娲诲姩ID闆嗗悎
+     * @return 鍩硅娲诲姩ID 鈫� 鍙傝浜烘暟
+     */
+    Map<Long, Integer> getParticipantCountMap(Collection<Long> trainingIds);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingServiceImpl.java
new file mode 100644
index 0000000..6f60b31
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/training/HrmTrainingServiceImpl.java
@@ -0,0 +1,279 @@
+package cn.iocoder.yudao.module.hrm.service.training;
+
+import cn.hutool.core.collection.CollUtil;
+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.training.vo.HrmTrainingPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingParticipantUpdateReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.training.vo.HrmTrainingSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.certificate.HrmCertificateDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingParticipantDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.certificate.HrmCertificateMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.training.HrmTrainingMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.training.HrmTrainingParticipantMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.dal.dataobject.training.HrmTrainingDO.*;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * HRM 鍩硅娲诲姩 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class HrmTrainingServiceImpl implements HrmTrainingService {
+
+    @Resource
+    private HrmTrainingMapper trainingMapper;
+    @Resource
+    private HrmTrainingParticipantMapper participantMapper;
+    @Resource
+    private HrmCertificateMapper certificateMapper;
+    @Resource
+    private HrmNoRedisDAO noRedisDAO;
+
+    @Override
+    public Long createTraining(HrmTrainingSaveReqVO createReqVO) {
+        validateIssueCertificate(createReqVO.getTrainingType(), createReqVO.getIssueCertificate(),
+                createReqVO.getCertValidityMonths());
+        HrmTrainingDO training = BeanUtils.toBean(createReqVO, HrmTrainingDO.class);
+        training.setId(null);
+        training.setTrainingNo(noRedisDAO.generateTrainingNo());
+        training.setStatus(STATUS_DRAFT);
+        trainingMapper.insert(training);
+        return training.getId();
+    }
+
+    @Override
+    public void updateTraining(HrmTrainingSaveReqVO updateReqVO) {
+        HrmTrainingDO training = validateTrainingExists(updateReqVO.getId());
+        ensureMutable(training);
+        validateIssueCertificate(updateReqVO.getTrainingType(), updateReqVO.getIssueCertificate(),
+                updateReqVO.getCertValidityMonths());
+        HrmTrainingDO update = BeanUtils.toBean(updateReqVO, HrmTrainingDO.class);
+        update.setTrainingNo(training.getTrainingNo()); // 缂栧彿涓嶅彲鏀�
+        update.setStatus(null); // 鐘舵�佽蛋 start/complete/cancel 娴佽浆锛屼笉鍦ㄦ淇敼
+        trainingMapper.updateById(update);
+    }
+
+    @Override
+    public void deleteTraining(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            HrmTrainingDO training = validateTrainingExists(id);
+            ensureMutable(training);
+            participantMapper.deleteByTrainingId(id);
+            trainingMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public HrmTrainingDO getTraining(Long id) {
+        return trainingMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<HrmTrainingDO> getTrainingPage(HrmTrainingPageReqVO pageReqVO) {
+        return trainingMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void startTraining(Long id) {
+        HrmTrainingDO training = validateTrainingExists(id);
+        if (training.getStatus() != null && !training.getStatus().equals(STATUS_DRAFT)) {
+            throw exception(TRAINING_COMPLETE_FAIL_STATUS); // 浠呰崏绋垮彲寮�濮�
+        }
+        trainingMapper.updateById(HrmTrainingDO.builder().id(id).status(STATUS_PLANNED).build());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void completeTraining(Long id) {
+        HrmTrainingDO training = validateTrainingExists(id);
+        if (training.getStatus() == null
+                || (!training.getStatus().equals(STATUS_DRAFT) && !training.getStatus().equals(STATUS_PLANNED))) {
+            throw exception(TRAINING_COMPLETE_FAIL_STATUS);
+        }
+        trainingMapper.updateById(HrmTrainingDO.builder().id(id).status(STATUS_COMPLETED).build());
+        // 鑱斿姩鍙戣瘉锛氬煿璁畬鎴愬悗瀵瑰悎鏍间汉鍛樿嚜鍔ㄧ敓鎴愯瘉涔︼紙骞傜瓑锛屼簩娆″畬鎴愪笉閲嶅鍙戯級
+        if (Boolean.TRUE.equals(training.getIssueCertificate())) {
+            issueCertificatesForPassed(training);
+        }
+    }
+
+    @Override
+    public void cancelTraining(Long id) {
+        HrmTrainingDO training = validateTrainingExists(id);
+        if (training.getStatus() == null || training.getStatus().equals(STATUS_COMPLETED)
+                || training.getStatus().equals(STATUS_CANCELED)) {
+            throw exception(TRAINING_LOCKED);
+        }
+        trainingMapper.updateById(HrmTrainingDO.builder().id(id).status(STATUS_CANCELED).build());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int addParticipants(Long trainingId, Collection<Long> employeeIds) {
+        HrmTrainingDO training = validateTrainingExists(trainingId);
+        ensureMutable(training);
+        if (CollUtil.isEmpty(employeeIds)) {
+            return 0;
+        }
+        int count = 0;
+        for (Long employeeId : employeeIds) {
+            if (participantMapper.selectByTrainingIdAndEmployeeId(trainingId, employeeId) != null) {
+                continue; // 宸插湪鍚嶅崟涓紝璺宠繃涓嶉噸澶嶆坊鍔�
+            }
+            HrmTrainingParticipantDO participant = HrmTrainingParticipantDO.builder()
+                    .trainingId(trainingId)
+                    .employeeId(employeeId)
+                    .attended(false)
+                    .isPass(false)
+                    .build();
+            participantMapper.insert(participant);
+            count++;
+        }
+        return count;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateParticipant(HrmTrainingParticipantUpdateReqVO updateReqVO) {
+        HrmTrainingParticipantDO participant = validateParticipantExists(updateReqVO.getId());
+        HrmTrainingDO training = validateTrainingExists(participant.getTrainingId());
+        ensureMutable(training);
+        HrmTrainingParticipantDO update = HrmTrainingParticipantDO.builder()
+                .id(participant.getId())
+                .attended(updateReqVO.getAttended())
+                .score(updateReqVO.getScore())
+                .isPass(updateReqVO.getIsPass())
+                .remark(updateReqVO.getRemark())
+                .build();
+        participantMapper.updateById(update);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteParticipants(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        for (Long id : ids) {
+            HrmTrainingParticipantDO participant = validateParticipantExists(id);
+            HrmTrainingDO training = validateTrainingExists(participant.getTrainingId());
+            ensureMutable(training);
+            participantMapper.deleteById(id);
+        }
+    }
+
+    @Override
+    public List<HrmTrainingParticipantDO> getParticipantsByTrainingId(Long trainingId) {
+        return participantMapper.selectListByTrainingId(trainingId);
+    }
+
+    @Override
+    public Map<Long, Integer> getParticipantCountMap(Collection<Long> trainingIds) {
+        if (CollUtil.isEmpty(trainingIds)) {
+            return Collections.emptyMap();
+        }
+        return participantMapper.selectListByTrainingIds(trainingIds).stream()
+                .collect(Collectors.groupingBy(HrmTrainingParticipantDO::getTrainingId, Collectors.summingInt(p -> 1)));
+    }
+
+    // ========== 绉佹湁鏂规硶 ==========
+
+    private void issueCertificatesForPassed(HrmTrainingDO training) {
+        if (training.getCertValidityMonths() == null || training.getCertValidityMonths() <= 0) {
+            throw exception(TRAINING_CERT_VALIDITY_REQUIRED);
+        }
+        List<HrmTrainingParticipantDO> participants = participantMapper.selectListByTrainingId(training.getId());
+        if (CollUtil.isEmpty(participants)) {
+            return;
+        }
+        Integer certType = convertToCertificateType(training.getTrainingType());
+        LocalDate issueDate = LocalDate.now();
+        LocalDate expireDate = issueDate.plusMonths(training.getCertValidityMonths());
+        for (HrmTrainingParticipantDO participant : participants) {
+            if (!Boolean.TRUE.equals(participant.getIsPass()) || participant.getCertificateId() != null) {
+                continue;
+            }
+            HrmCertificateDO certificate = HrmCertificateDO.builder()
+                    .employeeId(participant.getEmployeeId())
+                    .certType(certType)
+                    .certName(training.getName())
+                    .issueDate(issueDate)
+                    .expireDate(expireDate)
+                    .remark("鐢卞煿璁��" + training.getTrainingNo() + "銆�" + training.getName() + "瀹屾垚鑷姩鍙戞斁")
+                    .build();
+            certificateMapper.insert(certificate);
+            participantMapper.updateCertificateId(participant.getId(), certificate.getId());
+        }
+    }
+
+    /**
+     * 鍩硅绫诲瀷 鈫� 璇佷功璧勮川绫诲瀷鏄犲皠锛氬畨鍏ㄥ煿璁啋4 瀹夊叏鍩硅鍚堟牸璇侊紝鎶�鑳藉煿璁啋3 鎶�鑳界瓑绾ц瘉涔�
+     */
+    private Integer convertToCertificateType(Integer trainingType) {
+        return switch (trainingType == null ? 0 : trainingType) {
+            case TYPE_SAFETY -> 4;
+            case TYPE_SKILL -> 3;
+            default -> null;
+        };
+    }
+
+    private void validateIssueCertificate(Integer trainingType, Boolean issueCertificate, Integer certValidityMonths) {
+        if (Boolean.TRUE.equals(issueCertificate)) {
+            if (trainingType == null
+                    || (!trainingType.equals(TYPE_SAFETY) && !trainingType.equals(TYPE_SKILL))) {
+                throw exception(TRAINING_CERT_TYPE_NOT_SUPPORT);
+            }
+            if (certValidityMonths == null || certValidityMonths <= 0) {
+                throw exception(TRAINING_CERT_VALIDITY_REQUIRED);
+            }
+        }
+    }
+
+    private HrmTrainingDO validateTrainingExists(Long id) {
+        HrmTrainingDO training = trainingMapper.selectById(id);
+        if (training == null) {
+            throw exception(TRAINING_NOT_EXISTS);
+        }
+        return training;
+    }
+
+    private HrmTrainingParticipantDO validateParticipantExists(Long id) {
+        HrmTrainingParticipantDO participant = participantMapper.selectById(id);
+        if (participant == null) {
+            throw exception(TRAINING_PARTICIPANT_NOT_EXISTS);
+        }
+        return participant;
+    }
+
+    /**
+     * 宸插畬鎴�/宸插彇娑堢殑鍩硅涓嶅厑璁稿啀淇敼
+     */
+    private void ensureMutable(HrmTrainingDO training) {
+        if (training.getStatus() != null
+                && (training.getStatus().equals(STATUS_COMPLETED) || training.getStatus().equals(STATUS_CANCELED))) {
+            throw exception(TRAINING_LOCKED);
+        }
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryService.java
new file mode 100644
index 0000000..394c781
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.hrm.service.workhour;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmWorkHourSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmWorkHourSummaryRespVO;
+
+/**
+ * 鏈堝害宸ユ椂姹囨�� Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmWorkHourSummaryService {
+
+    /**
+     * 鐢熸垚鎸囧畾鍛ㄦ湡鐨勬湀搴﹀伐鏃舵眹鎬伙紙鑰冨嫟鍙e緞锛屾寜鍛樺伐 upsert锛�
+     *
+     * @param period 鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�
+     */
+    void generateMonthly(String period);
+
+    /**
+     * 鑾峰彇鏈堝害宸ユ椂姹囨�诲垎椤�
+     *
+     * @param pageReqVO 鍒嗛〉璇锋眰
+     * @return 鍒嗛〉缁撴灉
+     */
+    PageResult<HrmWorkHourSummaryRespVO> getWorkHourSummaryPage(HrmWorkHourSummaryPageReqVO pageReqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryServiceImpl.java
new file mode 100644
index 0000000..0333ccb
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/workhour/HrmWorkHourSummaryServiceImpl.java
@@ -0,0 +1,124 @@
+package cn.iocoder.yudao.module.hrm.service.workhour;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmEmployeeWorkHourReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmWorkHourSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.workhour.vo.HrmWorkHourSummaryRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.workhour.HrmWorkHourSummaryDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRecordMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.workhour.HrmWorkHourSummaryMapper;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+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 org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.time.YearMonth;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鏈堝害宸ユ椂姹囨�� Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class HrmWorkHourSummaryServiceImpl implements HrmWorkHourSummaryService {
+
+    @Resource
+    private HrmWorkHourSummaryMapper workHourSummaryMapper;
+    @Resource
+    private HrmAttendanceRecordMapper attendanceRecordMapper;
+    @Resource
+    private HrmEmployeeService employeeService;
+    @Resource
+    private DeptApi deptApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void generateMonthly(String period) {
+        YearMonth yearMonth = YearMonth.parse(period);
+        HrmEmployeeWorkHourReqVO reqVO = new HrmEmployeeWorkHourReqVO();
+        reqVO.setStartDate(yearMonth.atDay(1));
+        reqVO.setEndDate(yearMonth.atEndOfMonth());
+        for (Map<String, Object> row : attendanceRecordMapper.selectWorkHourSummary(reqVO)) {
+            Long employeeId = toLong(row.get("user_id"));
+            if (employeeId == null) {
+                continue;
+            }
+            HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+            HrmWorkHourSummaryDO summary = workHourSummaryMapper.selectByEmployeeIdAndPeriod(employeeId, period);
+            if (summary == null) {
+                summary = new HrmWorkHourSummaryDO();
+                summary.setEmployeeId(employeeId);
+                summary.setPeriod(period);
+            }
+            summary.setDeptId(employee != null ? employee.getDeptId() : null);
+            summary.setWorkDays(toInteger(row.get("work_days")));
+            summary.setWorkHours(toDecimal(row.get("work_hours")));
+            summary.setOvertimeHours(toDecimal(row.get("overtime_hours")));
+            if (summary.getId() == null) {
+                workHourSummaryMapper.insert(summary);
+            } else {
+                workHourSummaryMapper.updateById(summary);
+            }
+        }
+    }
+
+    @Override
+    public PageResult<HrmWorkHourSummaryRespVO> getWorkHourSummaryPage(HrmWorkHourSummaryPageReqVO pageReqVO) {
+        PageResult<HrmWorkHourSummaryDO> pageResult = workHourSummaryMapper.selectPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
+        }
+        List<HrmWorkHourSummaryRespVO> list = pageResult.getList().stream()
+                .map(this::convert)
+                .toList();
+        return new PageResult<>(list, pageResult.getTotal());
+    }
+
+    private HrmWorkHourSummaryRespVO convert(HrmWorkHourSummaryDO summary) {
+        HrmWorkHourSummaryRespVO respVO = new HrmWorkHourSummaryRespVO();
+        respVO.setId(summary.getId());
+        respVO.setEmployeeId(summary.getEmployeeId());
+        respVO.setDeptId(summary.getDeptId());
+        respVO.setPeriod(summary.getPeriod());
+        respVO.setWorkDays(summary.getWorkDays());
+        respVO.setWorkHours(summary.getWorkHours());
+        respVO.setOvertimeHours(summary.getOvertimeHours());
+        respVO.setRemark(summary.getRemark());
+        respVO.setCreateTime(summary.getCreateTime());
+        HrmEmployeeDO employee = employeeService.getEmployee(summary.getEmployeeId());
+        if (employee != null) {
+            respVO.setEmployeeNo(employee.getEmployeeNo());
+            respVO.setEmployeeName(employee.getName());
+        }
+        if (summary.getDeptId() != null) {
+            DeptRespDTO dept = deptApi.getDept(summary.getDeptId());
+            if (dept != null) {
+                respVO.setDeptName(dept.getName());
+            }
+        }
+        return respVO;
+    }
+
+    private Long toLong(Object value) {
+        return value instanceof Number ? ((Number) value).longValue() : null;
+    }
+
+    private Integer toInteger(Object value) {
+        return value instanceof Number ? ((Number) value).intValue() : 0;
+    }
+
+    private BigDecimal toDecimal(Object value) {
+        return value instanceof Number ? new BigDecimal(value.toString()) : BigDecimal.ZERO;
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java
index eb29f85..ebb06b8 100644
--- a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java
+++ b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemCreateReqDTO.java
@@ -72,6 +72,9 @@
     @Schema(description = "鏄惁鎵规绠$悊", example = "false")
     private Boolean isBatchManaged;
 
+    @Schema(description = "鏄惁搴忓垪鍙风鐞�", example = "false")
+    private Boolean isSerialManaged;
+
     @Schema(description = "閲嶉噺(kg)", example = "0.5")
     private BigDecimal weight;
 
diff --git a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
index db505c1..a5f464c 100644
--- a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
+++ b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
@@ -81,6 +81,9 @@
     @Schema(description = "鏄惁鎵规绠$悊", example = "false")
     private Boolean isBatchManaged;
 
+    @Schema(description = "鏄惁搴忓垪鍙风鐞�", example = "false")
+    private Boolean isSerialManaged;
+
     @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
     private Boolean highValue;
 
diff --git a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java
index ca2d98e..b5aa824 100644
--- a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java
+++ b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemUpdateReqDTO.java
@@ -75,6 +75,9 @@
     @Schema(description = "鏄惁鎵规绠$悊", example = "false")
     private Boolean isBatchManaged;
 
+    @Schema(description = "鏄惁搴忓垪鍙风鐞�", example = "false")
+    private Boolean isSerialManaged;
+
     @Schema(description = "閲嶉噺(kg)", example = "0.5")
     private BigDecimal weight;
 
diff --git a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
index 9c98fd1..8f4d21c 100644
--- a/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
+++ b/yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -14,7 +14,8 @@
 
     MATERIAL(1, "鐗╂枡"),
     PRODUCT(2, "浜у搧"),
-    SEMI_FINISHED(3, "鍗婃垚鍝�");
+    SEMI_FINISHED(3, "鍗婃垚鍝�"),
+    EQUIPMENT(4, "璁惧");
 
     /**
      * 绫诲瀷缂栫爜
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
index de0c38d..6561cb6 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
@@ -78,6 +78,9 @@
     @Schema(description = "鏄惁鎵规绠$悊", example = "false")
     private Boolean isBatchManaged;
 
+    @Schema(description = "鏄惁搴忓垪鍙风鐞�", example = "false")
+    private Boolean isSerialManaged;
+
     @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
     private Boolean isHighValue;
 
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
index 594ea64..4893295 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/MdmItemTypeEnum.java
@@ -14,7 +14,8 @@
 
     MATERIAL(1, "鐗╂枡"),
     PRODUCT(2, "浜у搧"),
-    SEMI_FINISHED(3, "鍗婃垚鍝�");
+    SEMI_FINISHED(3, "鍗婃垚鍝�"),
+    EQUIPMENT(4, "璁惧");
 
     /**
      * 绫诲瀷缂栫爜
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApi.java
new file mode 100644
index 0000000..3887286
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApi.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.mes.api.cal;
+
+import cn.iocoder.yudao.module.mes.api.cal.dto.MesCalTeamRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MES 鐝粍 API 鎺ュ彛
+ *
+ * <p>渚� HRM 绛夋ā鍧楀紩鐢紝鎸夌彮缁� ID 鎵归噺鏌ヨ鐝粍缂栫爜/鍚嶇О锛岀敤浜庡憳宸ヤ富妗c�屾墍灞炵彮缁勩�嶅洖鏄俱��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesCalTeamApi {
+
+    /**
+     * 鎸夌彮缁� ID 闆嗗悎鏌ヨ鐝粍鍒楄〃
+     *
+     * @param teamIds 鐝粍 ID 闆嗗悎锛堜负绌鸿繑鍥炵┖鍒楄〃锛�
+     * @return 鐝粍鍒楄〃
+     */
+    List<MesCalTeamRespDTO> getTeamList(Collection<Long> teamIds);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/dto/MesCalTeamRespDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/dto/MesCalTeamRespDTO.java
new file mode 100644
index 0000000..09af9be
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/cal/dto/MesCalTeamRespDTO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mes.api.cal.dto;
+
+import lombok.Data;
+
+/**
+ * MES 鐝粍 Response DTO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Data
+public class MesCalTeamRespDTO {
+
+    /**
+     * 鐝粍缂栧彿
+     */
+    private Long id;
+
+    /**
+     * 鐝粍缂栫爜
+     */
+    private String code;
+
+    /**
+     * 鐝粍鍚嶇О
+     */
+    private String name;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/pom.xml b/yudao-module-mes/pom.xml
index 8cf5b36..989e0c1 100644
--- a/yudao-module-mes/pom.xml
+++ b/yudao-module-mes/pom.xml
@@ -103,6 +103,18 @@
             <artifactId>yudao-spring-boot-starter-excel</artifactId>
         </dependency>
 
+        <!-- 鏉$爜/浜岀淮鐮佺敓鎴愶紙zxing锛� -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.3.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.3.3</version>
+        </dependency>
+
         <!-- Test 娴嬭瘯鐩稿叧 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApiImpl.java
new file mode 100644
index 0000000..591aa40
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/cal/MesCalTeamApiImpl.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mes.api.cal;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.mes.api.cal.dto.MesCalTeamRespDTO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * MES 鐝粍 API 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesCalTeamApiImpl implements MesCalTeamApi {
+
+    @Resource
+    private MesCalTeamMapper teamMapper;
+
+    @Override
+    public List<MesCalTeamRespDTO> getTeamList(Collection<Long> teamIds) {
+        if (CollUtil.isEmpty(teamIds)) {
+            return Collections.emptyList();
+        }
+        List<MesCalTeamDO> teams = teamMapper.selectByIds(teamIds);
+        List<MesCalTeamRespDTO> list = new ArrayList<>(teams.size());
+        for (MesCalTeamDO team : teams) {
+            MesCalTeamRespDTO dto = new MesCalTeamRespDTO();
+            dto.setId(team.getId());
+            dto.setCode(team.getCode());
+            dto.setName(team.getName());
+            list.add(dto);
+        }
+        return list;
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/MesCalScheduleDetailController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/MesCalScheduleDetailController.java
new file mode 100644
index 0000000..a84872e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/MesCalScheduleDetailController.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.mes.controller.admin.cal.schedule;
+
+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.cal.schedule.vo.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.schedule.MesCalScheduleDetailDO;
+import cn.iocoder.yudao.module.mes.service.cal.schedule.MesCalScheduleDetailService;
+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/cal/schedule-detail")
+@Validated
+public class MesCalScheduleDetailController {
+
+    @Resource
+    private MesCalScheduleDetailService scheduleDetailService;
+
+    @PostMapping("/generate")
+    @Operation(summary = "鎸夌彮缁勬帓鐝敓鎴愬憳宸ョ骇鎺掔彮鏄庣粏")
+    @Parameter(name = "planId", description = "鎺掔彮璁″垝缂栧彿", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('mes:cal-schedule-detail:generate')")
+    public CommonResult<Boolean> generateDetails(@RequestParam("planId") Long planId) {
+        scheduleDetailService.generateDetails(planId);
+        return success(true);
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鍛樺伐绾ф帓鐝槑缁嗭紙浜哄伐寰皟锛�")
+    @PreAuthorize("@ss.hasPermission('mes:cal-schedule-detail:update')")
+    public CommonResult<Boolean> updateDetail(@Valid @RequestBody MesCalScheduleDetailSaveReqVO updateReqVO) {
+        scheduleDetailService.updateDetail(updateReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鍛樺伐绾ф帓鐝槑缁嗗垎椤�")
+    @PreAuthorize("@ss.hasPermission('mes:cal-schedule-detail:query')")
+    public CommonResult<PageResult<MesCalScheduleDetailRespVO>> getDetailPage(
+            @Valid MesCalScheduleDetailPageReqVO pageReqVO) {
+        PageResult<MesCalScheduleDetailDO> pageResult = scheduleDetailService.getDetailPage(pageReqVO);
+        List<MesCalScheduleDetailRespVO> list = BeanUtils.toBean(pageResult.getList(), MesCalScheduleDetailRespVO.class);
+        return success(new PageResult<>(list, pageResult.getTotal()));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鑾峰緱鍛樺伐绾ф帓鐝槑缁嗗垪琛�")
+    @PreAuthorize("@ss.hasPermission('mes:cal-schedule-detail:query')")
+    public CommonResult<List<MesCalScheduleDetailRespVO>> getDetailList(
+            @Valid MesCalScheduleDetailPageReqVO pageReqVO) {
+        return success(BeanUtils.toBean(scheduleDetailService.getDetailList(pageReqVO), MesCalScheduleDetailRespVO.class));
+    }
+
+    @GetMapping("/check")
+    @Operation(summary = "鍛樺伐鎺掔彮鍚堣鏍¢獙锛堣繛缁伐鏃�/浼戞伅闂撮殧锛�")
+    @PreAuthorize("@ss.hasPermission('mes:cal-schedule-detail:query')")
+    public CommonResult<List<MesCalScheduleDetailCheckRespVO>> checkCompliance(
+            @Valid MesCalScheduleDetailCheckReqVO checkReqVO) {
+        return success(scheduleDetailService.checkCompliance(checkReqVO));
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckReqVO.java
new file mode 100644
index 0000000..475e520
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鍛樺伐绾ф帓鐝槑缁� 鍚堣鏍¢獙 Request VO")
+@Data
+public class MesCalScheduleDetailCheckReqVO {
+
+    @NotNull(message = "鐝粍鎴愬憳鐢ㄦ埛缂栧彿涓嶈兘涓虹┖")
+    private Long userId;
+
+    @Schema(description = "璧峰鏃ユ湡")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime startDay;
+
+    @Schema(description = "缁撴潫鏃ユ湡")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime endDay;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckRespVO.java
new file mode 100644
index 0000000..7ce39f2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailCheckRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鍛樺伐绾ф帓鐝槑缁� 鍚堣鏍¢獙缁撴灉椤� VO")
+@Data
+public class MesCalScheduleDetailCheckRespVO {
+
+    @Schema(description = "鏃ユ湡")
+    private LocalDateTime day;
+
+    @Schema(description = "鐝鍚嶇О")
+    private String shiftName;
+
+    @Schema(description = "寮�濮嬫椂闂达紙HH:mm锛�")
+    private String startTime;
+
+    @Schema(description = "缁撴潫鏃堕棿锛圚H:mm锛�")
+    private String endTime;
+
+    @Schema(description = "杩濊绫诲瀷: 1-鍗曟棩鐤戜技杩炵画宸ユ椂瓒呴暱 2-鐩搁偦鐝闂翠紤鎭笉瓒�", example = "1")
+    private Integer issueType;
+
+    @Schema(description = "鍚堣鏍¢獙闂鎻忚堪锛屼负绌鸿〃绀鸿鐝鍚堣")
+    private String issue;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailPageReqVO.java
new file mode 100644
index 0000000..f06e68c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailPageReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鍛樺伐绾ф帓鐝槑缁� 鍒嗛〉/鍒楄〃 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MesCalScheduleDetailPageReqVO extends SortablePageParam {
+
+    @Schema(description = "鎺掔彮璁″垝缂栧彿", example = "1")
+    private Long planId;
+
+    @Schema(description = "鐝粍缂栧彿", example = "1")
+    private Long teamId;
+
+    @Schema(description = "鐝粍鎴愬憳鐢ㄦ埛缂栧彿", example = "1")
+    private Long userId;
+
+    @Schema(description = "寮�濮嬫棩鏈�")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime startDay;
+
+    @Schema(description = "缁撴潫鏃ユ湡")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime endDay;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailRespVO.java
new file mode 100644
index 0000000..bb656b3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailRespVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鍛樺伐绾ф帓鐝槑缁� Response VO")
+@Data
+public class MesCalScheduleDetailRespVO {
+
+    @Schema(description = "缂栧彿")
+    private Long id;
+
+    @Schema(description = "鎺掔彮璁″垝缂栧彿")
+    private Long planId;
+
+    @Schema(description = "鐝粍缂栧彿")
+    private Long teamId;
+
+    @Schema(description = "鐝粍鎴愬憳鐢ㄦ埛缂栧彿")
+    private Long userId;
+
+    @Schema(description = "鏃ユ湡")
+    private LocalDateTime day;
+
+    @Schema(description = "鐝缂栧彿")
+    private Long shiftId;
+
+    @Schema(description = "鐝鍚嶇О")
+    private String shiftName;
+
+    @Schema(description = "寮�濮嬫椂闂达紙HH:mm锛�")
+    private String startTime;
+
+    @Schema(description = "缁撴潫鏃堕棿锛圚H:mm锛�")
+    private String endTime;
+
+    @Schema(description = "鏉ユ簮: 1-鑷姩鐢熸垚 2-浜哄伐璋冩暣")
+    private Integer source;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailSaveReqVO.java
new file mode 100644
index 0000000..fc05c6a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/schedule/vo/MesCalScheduleDetailSaveReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鍛樺伐绾ф帓鐝槑缁� 浜哄伐璋冩暣 Request VO")
+@Data
+public class MesCalScheduleDetailSaveReqVO {
+
+    @NotNull(message = "鏄庣粏缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "鐝缂栧彿")
+    private Long shiftId;
+
+    @Schema(description = "鐝鍚嶇О")
+    private String shiftName;
+
+    @Schema(description = "寮�濮嬫椂闂达紙HH:mm锛�")
+    private String startTime;
+
+    @Schema(description = "缁撴潫鏃堕棿锛圚H:mm锛�")
+    private String endTime;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
index 7637381..094c1cd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
@@ -85,6 +85,10 @@
     @ExcelProperty("宸ヤ綔绔�")
     private String workstationName;
 
+    @Schema(description = "瀵瑰簲鐗╂枡缂栧彿", example = "1000")
+    @ExcelProperty("鐗╂枡缂栧彿")
+    private Long itemId;
+
     @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("鍒涘缓鏃堕棿")
     private LocalDateTime createTime;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
index 04d53fd..8337f80 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
@@ -61,4 +61,7 @@
     @Schema(description = "宸ヤ綔绔欑紪鍙�", example = "300")
     private Long workstationId;
 
+    @Schema(description = "瀵瑰簲鐗╂枡缂栧彿锛堢敱绯荤粺鍦ㄥ垱寤鸿澶囨椂鑷姩鐢熸垚锛屾棤闇�鍓嶇浼犲叆锛�", example = "1000")
+    private Long itemId;
+
     }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/barcode/MesWmBarcodeController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/barcode/MesWmBarcodeController.java
index 56a5f09..766e0d4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/barcode/MesWmBarcodeController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/barcode/MesWmBarcodeController.java
@@ -25,6 +25,7 @@
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -95,13 +96,37 @@
     @Operation(summary = "鐢熸垚鏉$爜鍐呭")
     @Parameter(name = "bizType", description = "涓氬姟绫诲瀷", required = true, example = "1")
     @Parameter(name = "bizCode", description = "涓氬姟缂栫爜", required = true, example = "WO202403070001")
+    @Parameter(name = "bizName", description = "涓氬姟鍚嶇О", required = false, example = "涓�鍙疯溅闂�")
     @PreAuthorize("@ss.hasPermission('mes:wm-barcode:query')")
     public CommonResult<String> generateBarcodeContent(@RequestParam("bizType") @NotNull(message = "涓氬姟绫诲瀷涓嶈兘涓虹┖") Integer bizType,
-                                                        @RequestParam("bizCode") @NotBlank(message = "涓氬姟缂栫爜涓嶈兘涓虹┖") String bizCode) {
-        String content = barcodeService.generateBarcodeContent(bizType, bizCode);
+                                                        @RequestParam("bizCode") @NotBlank(message = "涓氬姟缂栫爜涓嶈兘涓虹┖") String bizCode,
+                                                        @RequestParam(value = "bizName", required = false) String bizName) {
+        String content = barcodeService.generateBarcodeContent(bizType, bizCode, bizName);
         return success(content);
     }
 
+    @PostMapping("/render")
+    @Operation(summary = "鐢熸垚鏉$爜/浜岀淮鐮佸浘鐗�", description = "鏈嶅姟绔� zxing 鎴愬浘锛岃繑鍥� PNG Base64 渚涘嵆鏃堕瑙堬紱涓嶈惤搴�")
+    @Parameter(name = "content", description = "鏉$爜鍐呭", required = true)
+    @Parameter(name = "format", description = "鏉$爜鏍煎紡锛�1 浜岀淮鐮� / 2 EAN13 / 3 CODE39 / 4 UPC-A / 绌洪粯璁� CODE128锛�", required = false)
+    @Parameter(name = "width", description = "鍥剧墖瀹藉害", required = false)
+    @Parameter(name = "height", description = "鍥剧墖楂樺害", required = false)
+    @PreAuthorize("@ss.hasPermission('mes:wm-barcode:query')")
+    public CommonResult<String> renderBarcodeImage(@RequestParam("content") @NotBlank(message = "鏉$爜鍐呭涓嶈兘涓虹┖") String content,
+                                                   @RequestParam(value = "format", required = false) Integer format,
+                                                   @RequestParam(value = "width", required = false) Integer width,
+                                                   @RequestParam(value = "height", required = false) Integer height) {
+        return success(barcodeService.renderBarcodeImage(content, format, width, height));
+    }
+
+    @PostMapping("/parse")
+    @Operation(summary = "瑙f瀽鏉$爜鍐呭", description = "鎸夋潯鐮佸唴瀹圭簿纭懡涓垨妯℃澘鍙嶈В锛岃繑鍥炰笟鍔″瓧娈�")
+    @Parameter(name = "content", description = "鎵弿鍒扮殑鏉$爜鍐呭", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:wm-barcode:query')")
+    public CommonResult<Map<String, Object>> parseBarcodeContent(@RequestParam("content") @NotBlank(message = "鏉$爜鍐呭涓嶈兘涓虹┖") String content) {
+        return success(barcodeService.parseBarcodeContent(content));
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "瀵煎嚭鏉$爜娓呭崟 Excel")
     @PreAuthorize("@ss.hasPermission('mes:wm-barcode:export')")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java
new file mode 100644
index 0000000..d9cab39
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt;
+
+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.wm.equipmentreceipt.vo.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptDO;
+import cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt.MesWmEquipmentReceiptService;
+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 java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗�")
+@RestController
+@RequestMapping("/mes/wm/equipment-receipt")
+@Validated
+public class MesWmEquipmentReceiptController {
+
+    @Resource
+    private MesWmEquipmentReceiptService equipmentReceiptService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璁惧鍏ュ簱鍗�")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:create')")
+    public CommonResult<Long> create(@Valid @RequestBody MesWmEquipmentReceiptSaveReqVO reqVO) {
+        return success(equipmentReceiptService.createEquipmentReceipt(reqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "淇敼璁惧鍏ュ簱鍗�")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:update')")
+    public CommonResult<Boolean> update(@Valid @RequestBody MesWmEquipmentReceiptSaveReqVO reqVO) {
+        equipmentReceiptService.updateEquipmentReceipt(reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璁惧鍏ュ簱鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:delete')")
+    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
+        equipmentReceiptService.deleteEquipmentReceipt(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璁惧鍏ュ簱鍗�")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:query')")
+    public CommonResult<MesWmEquipmentReceiptRespVO> get(@RequestParam("id") Long id) {
+        return success(BeanUtils.toBean(equipmentReceiptService.getEquipmentReceipt(id), MesWmEquipmentReceiptRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璁惧鍏ュ簱鍗曞垎椤�")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:query')")
+    public CommonResult<PageResult<MesWmEquipmentReceiptRespVO>> page(@Valid MesWmEquipmentReceiptPageReqVO reqVO) {
+        return success(BeanUtils.toBean(equipmentReceiptService.getEquipmentReceiptPage(reqVO), MesWmEquipmentReceiptRespVO.class));
+    }
+
+    @PutMapping("/submit-approval")
+    @Operation(summary = "鎻愪氦璁惧鍏ュ簱鍗曞鎵�")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:submit')")
+    public CommonResult<Boolean> submitApproval(@RequestParam("id") Long id,
+                                                  @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        equipmentReceiptService.submitEquipmentReceiptApproval(id, processDefinitionKey);
+        return success(true);
+    }
+
+    @GetMapping("/approval-process-list")
+    @Operation(summary = "鑾峰緱璁惧鍏ュ簱瀹℃壒娴佺▼瀹氫箟鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:query')")
+    public CommonResult<List<Map<String, Object>>> approvalProcessList() {
+        return success(equipmentReceiptService.getEquipmentReceiptApprovalProcessDefinitionList());
+    }
+
+    @PutMapping("/finish")
+    @Operation(summary = "鎵ц璁惧鍏ュ簱")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:finish')")
+    public CommonResult<Boolean> finish(@RequestParam("id") Long id) {
+        equipmentReceiptService.finishEquipmentReceipt(id);
+        return success(true);
+    }
+
+    @PutMapping("/cancel")
+    @Operation(summary = "鍙栨秷璁惧鍏ュ簱鍗�")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:cancel')")
+    public CommonResult<Boolean> cancel(@RequestParam("id") Long id) {
+        equipmentReceiptService.cancelEquipmentReceipt(id);
+        return success(true);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptLineController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptLineController.java
new file mode 100644
index 0000000..963a7d4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptLineController.java
@@ -0,0 +1,120 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt;
+
+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.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line.*;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptLineDO;
+import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
+import cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt.MesWmEquipmentReceiptLineService;
+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.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗曡")
+@RestController
+@RequestMapping("/mes/wm/equipment-receipt-line")
+@Validated
+public class MesWmEquipmentReceiptLineController {
+
+    @Resource
+    private MesWmEquipmentReceiptLineService lineService;
+    @Resource
+    private MesDvMachineryService machineryService;
+    @Resource
+    private MdmItemApi mdmItemApi;
+    @Resource
+    private MdmUnitMeasureApi mdmUnitMeasureApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璁惧鍏ュ簱鍗曡")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:create')")
+    public CommonResult<Long> create(@Valid @RequestBody MesWmEquipmentReceiptLineSaveReqVO reqVO) {
+        return success(lineService.createEquipmentReceiptLine(reqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "淇敼璁惧鍏ュ簱鍗曡")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:update')")
+    public CommonResult<Boolean> update(@Valid @RequestBody MesWmEquipmentReceiptLineSaveReqVO reqVO) {
+        lineService.updateEquipmentReceiptLine(reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璁惧鍏ュ簱鍗曡")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:delete')")
+    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
+        lineService.deleteEquipmentReceiptLine(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璁惧鍏ュ簱鍗曡")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:query')")
+    public CommonResult<MesWmEquipmentReceiptLineRespVO> get(@RequestParam("id") Long id) {
+        MesWmEquipmentReceiptLineDO line = lineService.getEquipmentReceiptLine(id);
+        return success(buildResp(Collections.singletonList(line)).get(0));
+    }
+
+    @GetMapping("/list-by-receipt-id")
+    @Operation(summary = "鑾峰緱璁惧鍏ュ簱鍗曡鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:query')")
+    public CommonResult<List<MesWmEquipmentReceiptLineRespVO>> listByReceiptId(@RequestParam("receiptId") Long receiptId) {
+        return success(buildResp(lineService.getEquipmentReceiptLineListByReceiptId(receiptId)));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璁惧鍏ュ簱鍗曡鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:query')")
+    public CommonResult<PageResult<MesWmEquipmentReceiptLineRespVO>> page(@Valid MesWmEquipmentReceiptLinePageReqVO reqVO) {
+        PageResult<MesWmEquipmentReceiptLineDO> result = lineService.getEquipmentReceiptLinePage(reqVO);
+        return success(new PageResult<>(buildResp(result.getList()), result.getTotal()));
+    }
+
+    private List<MesWmEquipmentReceiptLineRespVO> buildResp(List<MesWmEquipmentReceiptLineDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        Map<Long, MesDvMachineryDO> machineryMap = machineryService.getMachineryMap(convertSet(list, MesWmEquipmentReceiptLineDO::getMachineryId));
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(convertSet(list, MesWmEquipmentReceiptLineDO::getItemId));
+        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureList(
+                convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)).getCheckedData()
+                .stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
+        return BeanUtils.toBean(list, MesWmEquipmentReceiptLineRespVO.class, vo -> {
+            MesDvMachineryDO machinery = machineryMap.get(vo.getMachineryId());
+            if (machinery != null) {
+                vo.setMachineryCode(machinery.getCode()).setMachineryName(machinery.getName());
+            }
+            MdmItemRespDTO item = itemMap.get(vo.getItemId());
+            if (item != null) {
+                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification());
+                MdmUnitMeasureRespDTO unit = unitMap.get(item.getUnitMeasureId());
+                if (unit != null) {
+                    vo.setUnitMeasureName(unit.getName());
+                }
+            }
+        });
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptPageReqVO.java
new file mode 100644
index 0000000..4b4a110
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptPageReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗曞垎椤� Request VO")
+@Data
+public class MesWmEquipmentReceiptPageReqVO extends PageParam {
+
+    @Schema(description = "鍗曟嵁缂栫爜", example = "ER20260908001")
+    private String code;
+
+    @Schema(description = "鍗曟嵁鍚嶇О", example = "璁惧鍏ュ簱")
+    private String name;
+
+    @Schema(description = "鍏ュ簱鏉ユ簮", example = "澶栬喘")
+    private String source;
+
+    @Schema(description = "鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "鍏ュ簱鏃ユ湡")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] receiptDate;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptRespVO.java
new file mode 100644
index 0000000..9fbbf11
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptRespVO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo;
+
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line.MesWmEquipmentReceiptLineRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗� Response VO")
+@Data
+public class MesWmEquipmentReceiptRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "鍗曟嵁缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ER20260908001")
+    private String code;
+
+    @Schema(description = "鍗曟嵁鍚嶇О", example = "璁惧鍏ュ簱")
+    private String name;
+
+    @Schema(description = "鍏ュ簱鏃ユ湡")
+    private LocalDateTime receiptDate;
+
+    @Schema(description = "鍏ュ簱鏉ユ簮", example = "澶栬喘")
+    private String source;
+
+    @Schema(description = "鐘舵��", example = "0")
+    private Integer status;
+
+    @Schema(description = "BPM 娴佺▼瀹炰緥缂栧彿")
+    private String processInstanceId;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    @Schema(description = "鏄庣粏琛�")
+    private List<MesWmEquipmentReceiptLineRespVO> lines;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptSaveReqVO.java
new file mode 100644
index 0000000..df861cd
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptSaveReqVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗曟柊澧�/淇敼 Request VO")
+@Data
+public class MesWmEquipmentReceiptSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鍗曟嵁缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ER20260908001")
+    @NotEmpty(message = "鍗曟嵁缂栫爜涓嶈兘涓虹┖")
+    private String code;
+
+    @Schema(description = "鍗曟嵁鍚嶇О", example = "璁惧鍏ュ簱")
+    private String name;
+
+    @Schema(description = "鍏ュ簱鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "鍏ュ簱鏃ユ湡涓嶈兘涓虹┖")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime receiptDate;
+
+    @Schema(description = "鍏ュ簱鏉ユ簮", example = "澶栬喘")
+    private String source;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLinePageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLinePageReqVO.java
new file mode 100644
index 0000000..6f112ae
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLinePageReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗曡鍒嗛〉 Request VO")
+@Data
+public class MesWmEquipmentReceiptLinePageReqVO extends PageParam {
+
+    @Schema(description = "鍏ュ簱鍗曠紪鍙�", example = "1")
+    private Long receiptId;
+
+    @Schema(description = "璁惧鍙拌处缂栧彿", example = "1")
+    private Long machineryId;
+
+    @Schema(description = "璁惧鐗╂枡缂栧彿", example = "1")
+    private Long itemId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineRespVO.java
new file mode 100644
index 0000000..8ece6ba
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineRespVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗曡 Response VO")
+@Data
+public class MesWmEquipmentReceiptLineRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "鍏ュ簱鍗曠紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long receiptId;
+
+    @Schema(description = "璁惧鍙拌处缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long machineryId;
+
+    @Schema(description = "璁惧缂栫爜")
+    private String machineryCode;
+
+    @Schema(description = "璁惧鍚嶇О")
+    private String machineryName;
+
+    @Schema(description = "璁惧鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long itemId;
+
+    @Schema(description = "鐗╂枡缂栫爜")
+    private String itemCode;
+
+    @Schema(description = "鐗╂枡鍚嶇О")
+    private String itemName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿")
+    private String specification;
+
+    @Schema(description = "璁¢噺鍗曚綅鍚嶇О")
+    private String unitMeasureName;
+
+    @Schema(description = "鍏ュ簱鏁伴噺", example = "1.00")
+    private BigDecimal quantity;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineSaveReqVO.java
new file mode 100644
index 0000000..a0e5cee
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/line/MesWmEquipmentReceiptLineSaveReqVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁惧鍏ュ簱鍗曡鏂板/淇敼 Request VO")
+@Data
+public class MesWmEquipmentReceiptLineSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鍏ュ簱鍗曠紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍏ュ簱鍗曠紪鍙蜂笉鑳戒负绌�")
+    private Long receiptId;
+
+    @Schema(description = "璁惧鍙拌处缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "璁惧涓嶈兘涓虹┖")
+    private Long machineryId;
+
+    @Schema(description = "璁惧鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "璁惧鐗╂枡涓嶈兘涓虹┖")
+    private Long itemId;
+
+    @Schema(description = "鍏ュ簱鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.00")
+    @NotNull(message = "鍏ュ簱鏁伴噺涓嶈兘涓虹┖")
+    @DecimalMin(value = "0.01", message = "鍏ュ簱鏁伴噺涓嶈兘灏忎簬 0.01")
+    private BigDecimal quantity;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/MesWmSnController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/MesWmSnController.java
index 3b49cc7..fb8b39b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/MesWmSnController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/MesWmSnController.java
@@ -8,8 +8,12 @@
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnDetailRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGroupRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnOutStockReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnRespVO;
 import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
@@ -92,6 +96,39 @@
         return success(true);
     }
 
+    @PostMapping("/in-stock")
+    @Operation(summary = "鍏ュ簱鐧昏 SN", description = "鎸� SN 寤虹珛鍗曚欢鍏ュ簱浜嬪姟骞跺叆鍙帮紝闇�鐗╂枡宸插惎鐢ㄥ簭鍒楀彿绠$悊")
+    @PreAuthorize("@ss.hasPermission('mes:wm-sn:update')")
+    public CommonResult<Boolean> registerSnInStock(@Valid @RequestBody MesWmSnInStockReqVO reqVO) {
+        snService.registerSnInStock(reqVO);
+        return success(true);
+    }
+
+    @PostMapping("/out-stock")
+    @Operation(summary = "鍑哄簱鏍搁攢 SN", description = "鎸� SN 寤虹珛鍗曚欢鍑哄簱浜嬪姟骞舵牳閿�")
+    @PreAuthorize("@ss.hasPermission('mes:wm-sn:update')")
+    public CommonResult<Boolean> outboundSn(@Valid @RequestBody MesWmSnOutStockReqVO reqVO) {
+        snService.outboundSn(reqVO);
+        return success(true);
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "鑾峰緱 SN 鍗曚欢鏄庣粏", description = "杩斿洖鐘舵�併�佸湪搴撲綅缃強鏈�杩戜竴绗斿簱瀛樹簨鍔�")
+    @Parameter(name = "code", description = "SN 鐮�", required = true, example = "SN20260305000001")
+    @PreAuthorize("@ss.hasPermission('mes:wm-sn:query')")
+    public CommonResult<MesWmSnDetailRespVO> getSnDetail(@RequestParam("code") @NotBlank(message = "SN 鐮佷笉鑳戒负绌�") String code) {
+        return success(snService.getSnDetail(code));
+    }
+
+    @GetMapping("/in-stock-page")
+    @Operation(summary = "SN 鐮佺姸鎬佸垎椤�", description = "鎸夌姸鎬�/鐗╂枡/搴撲綅绛夌淮搴﹀垎椤垫煡璇� SN锛堟敮鎸佺洏鐐规牳瀵癸級")
+    @PreAuthorize("@ss.hasPermission('mes:wm-sn:query')")
+    public CommonResult<PageResult<MesWmSnRespVO>> getSnInStockPage(@Valid MesWmSnInStockPageReqVO reqVO) {
+        PageResult<MesWmSnRespVO> pageResult = BeanUtils.toBean(snService.getSnInStockPage(reqVO), MesWmSnRespVO.class);
+        buildSnItemVO(pageResult.getList());
+        return success(pageResult);
+    }
+
     @GetMapping("/group-export-excel")
     @Operation(summary = "瀵煎嚭 SN 鐮佸垎缁� Excel")
     @PreAuthorize("@ss.hasPermission('mes:wm-sn:export')")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnDetailRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnDetailRespVO.java
new file mode 100644
index 0000000..5f5d656
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnDetailRespVO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - SN 鐮佸崟浠舵槑缁� Response VO")
+@Data
+public class MesWmSnDetailRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "SN 鐮�", example = "SN20260305000001")
+    private String code;
+
+    @Schema(description = "鐗╂枡缂栧彿", example = "1")
+    private Long itemId;
+
+    @Schema(description = "鎵规鍙�", example = "BATCH001")
+    private String batchCode;
+
+    @Schema(description = "鐘舵�侊紙1-鍦ㄥ簱 2-宸插嚭搴擄級", example = "1")
+    private Integer status;
+
+    @Schema(description = "鍦ㄥ簱浠撳簱 ID", example = "5")
+    private Long warehouseId;
+
+    @Schema(description = "鍦ㄥ簱搴撳尯 ID", example = "11")
+    private Long locationId;
+
+    @Schema(description = "鍦ㄥ簱搴撲綅 ID", example = "10")
+    private Long areaId;
+
+    // ==================== 鏈�杩戜竴绗斿簱瀛樹簨鍔★紙鍘诲悜/浣嶇疆鏉ユ簮锛� ====================
+
+    @Schema(description = "鏈�杩戜簨鍔$被鍨嬶紙1-鍏ュ簱 2-鍑哄簱锛�", example = "1")
+    private Integer lastTransactionType;
+
+    @Schema(description = "鏈�杩戜簨鍔$被鍨嬪悕绉�", example = "鍏ュ簱")
+    private String lastTransactionTypeName;
+
+    @Schema(description = "鏈�杩戜簨鍔″彉鍔ㄦ暟閲�", example = "1")
+    private BigDecimal lastTransactionQuantity;
+
+    @Schema(description = "鏈�杩戜簨鍔′笟鍔$被鍨�", example = "109")
+    private Integer lastTransactionBizType;
+
+    @Schema(description = "鏈�杩戜簨鍔′笟鍔″崟鍙�", example = "SN20260305000001")
+    private String lastTransactionBizCode;
+
+    @Schema(description = "鏈�杩戜簨鍔″彂鐢熸椂闂�", example = "2026-09-08 10:00:00")
+    private LocalDateTime lastTransactionTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockPageReqVO.java
new file mode 100644
index 0000000..53078e8
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockPageReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.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 = "绠$悊鍚庡彴 - SN 鐮佸湪搴�/鐘舵�佸垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesWmSnInStockPageReqVO extends PageParam {
+
+    @Schema(description = "SN 鐮�", example = "SN20260305000001")
+    private String code;
+
+    @Schema(description = "鐗╂枡缂栧彿", example = "1")
+    private Long itemId;
+
+    @Schema(description = "鎵规鍙�", example = "BATCH001")
+    private String batchCode;
+
+    @Schema(description = "浠撳簱 ID", example = "5")
+    private Long warehouseId;
+
+    @Schema(description = "搴撳尯 ID", example = "11")
+    private Long locationId;
+
+    @Schema(description = "搴撲綅 ID", example = "10")
+    private Long areaId;
+
+    @Schema(description = "鐘舵�侊紙1-鍦ㄥ簱 2-宸插嚭搴擄級", example = "1")
+    private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockReqVO.java
new file mode 100644
index 0000000..154b18a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnInStockReqVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - SN 鐮佸叆搴撶櫥璁� Request VO")
+@Data
+public class MesWmSnInStockReqVO {
+
+    @Schema(description = "SN 鐮�", requiredMode = Schema.RequiredMode.REQUIRED, example = "SN20260305000001")
+    @NotBlank(message = "SN 鐮佷笉鑳戒负绌�")
+    private String code;
+
+    @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐗╂枡缂栧彿涓嶈兘涓虹┖")
+    private Long itemId;
+
+    @Schema(description = "浠撳簱 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "浠撳簱涓嶈兘涓虹┖")
+    private Long warehouseId;
+
+    @Schema(description = "搴撳尯 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
+    @NotNull(message = "搴撳尯涓嶈兘涓虹┖")
+    private Long locationId;
+
+    @Schema(description = "搴撲綅 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @NotNull(message = "搴撲綅涓嶈兘涓虹┖")
+    private Long areaId;
+
+    @Schema(description = "鎵规鍙�", example = "BATCH001")
+    private String batchCode;
+
+    @Schema(description = "澶囨敞", example = "鎵爜鍏ュ簱")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnOutStockReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnOutStockReqVO.java
new file mode 100644
index 0000000..d21b34b
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnOutStockReqVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - SN 鐮佸嚭搴撴牳閿� Request VO")
+@Data
+public class MesWmSnOutStockReqVO {
+
+    @Schema(description = "SN 鐮�", requiredMode = Schema.RequiredMode.REQUIRED, example = "SN20260305000001")
+    @NotBlank(message = "SN 鐮佷笉鑳戒负绌�")
+    private String code;
+
+    @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐗╂枡缂栧彿涓嶈兘涓虹┖")
+    private Long itemId;
+
+    @Schema(description = "澶囨敞", example = "鎵爜鍑哄簱")
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnRespVO.java
index d831b2d..bc83b32 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/sn/vo/MesWmSnRespVO.java
@@ -48,6 +48,18 @@
     @Schema(description = "鐢熶骇宸ュ崟缂栧彿", example = "1")
     private Long workOrderId;
 
+    @Schema(description = "鐘舵�侊紙1-鍦ㄥ簱 2-宸插嚭搴擄級", example = "1")
+    private Integer status;
+
+    @Schema(description = "鍦ㄥ簱浠撳簱 ID", example = "5")
+    private Long warehouseId;
+
+    @Schema(description = "鍦ㄥ簱搴撳尯 ID", example = "11")
+    private Long locationId;
+
+    @Schema(description = "鍦ㄥ簱搴撲綅 ID", example = "10")
+    private Long areaId;
+
     @Schema(description = "鍒涘缓鏃堕棿")
     @ExcelProperty("鐢熸垚鏃堕棿")
     private LocalDateTime createTime;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanRespVO.java
index 1c68417..8a29c7b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanRespVO.java
@@ -44,6 +44,18 @@
     @ExcelProperty("鏄惁鍐荤粨搴撳瓨")
     private Boolean frozen;
 
+    @Schema(description = "鏄惁瀹氭湡鑷姩鐢熸垚鐩樼偣浠诲姟", example = "false")
+    @ExcelProperty("鏄惁瀹氭湡鑷姩鐢熸垚")
+    private Boolean autoGenerate;
+
+    @Schema(description = "鑷姩鐢熸垚鍛ㄦ湡 cron 琛ㄨ揪寮�", example = "0 0 8 * * ?")
+    @ExcelProperty("鑷姩鐢熸垚鍛ㄦ湡")
+    private String generateCron;
+
+    @Schema(description = "涓婃鑷姩鐢熸垚鏃堕棿")
+    @ExcelProperty("涓婃鑷姩鐢熸垚鏃堕棿")
+    private LocalDateTime lastGenerateTime;
+
     @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
     @ExcelProperty("鐘舵��")
     private Integer status;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanSaveReqVO.java
index 5db85f8..21928fc 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/plan/vo/MesWmStockTakingPlanSaveReqVO.java
@@ -40,6 +40,12 @@
     @NotNull(message = "鏄惁鍐荤粨搴撳瓨涓嶈兘涓虹┖")
     private Boolean frozen;
 
+    @Schema(description = "鏄惁瀹氭湡鑷姩鐢熸垚鐩樼偣浠诲姟", example = "false")
+    private Boolean autoGenerate;
+
+    @Schema(description = "鑷姩鐢熸垚鍛ㄦ湡 cron 琛ㄨ揪寮忥紝寮�鍚嚜鍔ㄧ敓鎴愭椂蹇呭~锛屽 0 0 8 * * ?", example = "0 0 8 * * ?")
+    private String generateCron;
+
     @Schema(description = "澶囨敞", example = "澶囨敞")
     private String remark;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
index badce9e..12f3008 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
@@ -113,14 +113,24 @@
                 buildTaskRespVOList(pageResult.getList()));
     }
 
-    @PutMapping("/finish")
-    @Operation(summary = "瀹屾垚鐩樼偣浠诲姟")
-    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:finish')")
-    public CommonResult<Boolean> finishStockTakingTask(@Valid @RequestBody MesWmStockTakingTaskFinishReqVO reqVO) {
-        stockTakingTaskService.finishStockTakingTask(reqVO.getId());
+    @PutMapping("/submit-approval")
+    @Operation(summary = "鎻愪氦鐩樼偣缁撴灉瀹℃壒")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @Parameter(name = "processDefinitionKey", description = "娴佺▼瀹氫箟 Key", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:update')")
+    public CommonResult<Boolean> submitStockTakingTaskResultApproval(@RequestParam("id") Long id,
+                                                                     @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        stockTakingTaskService.submitStockTakingResultApproval(id, processDefinitionKey);
         return success(true);
     }
 
+    @GetMapping("/approval-process-list")
+    @Operation(summary = "鑾峰緱鐩樼偣缁撴灉瀹℃壒娴佺▼瀹氫箟鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:query')")
+    public CommonResult<List<Map<String, Object>>> getStockTakingTaskResultApprovalProcessDefinitionList() {
+        return success(stockTakingTaskService.getStockTakingTaskResultApprovalProcessDefinitionList());
+    }
+
     @PutMapping("/cancel")
     @Operation(summary = "鍙栨秷鐩樼偣浠诲姟")
     @Parameter(name = "id", description = "缂栧彿", required = true)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java
index 4e3115b..ab9c6a0 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java
@@ -70,6 +70,9 @@
     @ExcelProperty("鐘舵��")
     private Integer status;
 
+    @Schema(description = "BPM 娴佺▼瀹炰緥缂栧彿锛岄潪绌鸿〃绀虹洏鐐圭粨鏋滃凡鎻愪氦瀹℃壒", example = "xxx")
+    private String processInstanceId;
+
     @Schema(description = "澶囨敞", example = "澶囨敞")
     @ExcelProperty("澶囨敞")
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/MesWmTransactionController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/MesWmTransactionController.java
index 10faf64..a3fd55c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/MesWmTransactionController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/MesWmTransactionController.java
@@ -153,6 +153,16 @@
                 return "澶栧崗鍙戞枡";
             case MesBizTypeConstants.WM_OUTSOURCE_RECPT:
                 return "澶栧崗鍏ュ簱";
+            case MesBizTypeConstants.WM_STOCK_TAKING:
+                return "鐩樼偣璋冩暣";
+            case MesBizTypeConstants.WM_SN:
+                return "SN 鍑哄叆搴�";
+            case MesBizTypeConstants.WM_ITEM_RECEIPT_CANCEL:
+                return "閲囪喘鍏ュ簱绾㈠啿";
+            case MesBizTypeConstants.DV_MACHINERY:
+                return "璁惧鍙拌处鍏ュ簱";
+            case MesBizTypeConstants.DV_EQUIPMENT_RECEIPT:
+                return "璁惧鍏ュ簱";
             default:
                 return "鏈煡绫诲瀷(" + bizType + ")";
         }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionPageReqVO.java
index 0cabd59..f50047f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionPageReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionPageReqVO.java
@@ -47,6 +47,9 @@
     @Schema(description = "搴撳瓨璁板綍ID", example = "1")
     private Long materialStockId;
 
+    @Schema(description = "SN 鐮�", example = "SN20260305000001")
+    private String snCode;
+
     @Schema(description = "寮�濮嬫椂闂�")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime startTime;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionRespVO.java
index e40218c..c9a0ca4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionRespVO.java
@@ -51,6 +51,9 @@
     @Schema(description = "鎵规鍙�", example = "BATCH001")
     private String batchCode;
 
+    @Schema(description = "SN 鐮�", example = "SN20260305000001")
+    private String snCode;
+
     // ========== 搴撳瓨浣嶇疆 ==========
 
     @Schema(description = "浠撳簱ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/schedule/MesCalScheduleDetailDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/schedule/MesCalScheduleDetailDO.java
new file mode 100644
index 0000000..275ae8a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/schedule/MesCalScheduleDetailDO.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.cal.schedule;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * MES 鍛樺伐绾ф帓鐝槑缁� DO
+ *
+ * 鐢辩彮缁勬帓鐝紙mes_cal_team_shift锛�+ 鐝粍鎴愬憳锛坢es_cal_team_member锛夊睍寮�鍒�"鍛樺伐脳鏃ッ楃彮娆�"锛�
+ * 浣滀负宸ユ椂缁熻涓庡�煎畧鍚堣鐨勫熀鍑嗐��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_cal_schedule_detail")
+@KeySequence("mes_cal_schedule_detail_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesCalScheduleDetailDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鎺掔彮璁″垝缂栧彿
+     */
+    private Long planId;
+    /**
+     * 鐝粍缂栧彿
+     *
+     * 鍏宠仈 {@link MesCalTeamDO#getId()}
+     */
+    private Long teamId;
+    /**
+     * 鐝粍鎴愬憳鐢ㄦ埛缂栧彿
+     *
+     * 鍏宠仈 system_users.id锛坢es_cal_team_member.userId锛�
+     */
+    private Long userId;
+    /**
+     * 鏃ユ湡
+     */
+    private LocalDateTime day;
+    /**
+     * 鐝缂栧彿
+     *
+     * 鍏宠仈 {@link MesCalPlanShiftDO#getId()}
+     */
+    private Long shiftId;
+    /**
+     * 鐝鍚嶇О锛堝啑浣欙級
+     */
+    private String shiftName;
+    /**
+     * 寮�濮嬫椂闂达紙HH:mm 鏍煎紡锛屽啑浣欙級
+     */
+    private String startTime;
+    /**
+     * 缁撴潫鏃堕棿锛圚H:mm 鏍煎紡锛屽啑浣欙級
+     */
+    private String endTime;
+    /**
+     * 鏉ユ簮: 1-鑷姩鐢熸垚 2-浜哄伐璋冩暣
+     */
+    private Integer source;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
index ae3949d..ad74141 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
@@ -94,5 +94,12 @@
      * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO#getId()}
      */
     private Long workstationId;
+    /**
+     * 瀵瑰簲鐗╂枡缂栧彿
+     *
+     * 鍏宠仈 MDM 鐗╂枡 {@link cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO#getId()}
+     * 璁惧鍒涘缓鏃惰嚜鍔ㄥ垱寤哄搴旂墿鏂欙紝骞朵互姝ゅ叧鑱旇澶囦粨搴撳瓨
+     */
+    private Long itemId;
 
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/barcode/MesWmBarcodeConfigDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/barcode/MesWmBarcodeConfigDO.java
index 7b38080..aa0fd6a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/barcode/MesWmBarcodeConfigDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/barcode/MesWmBarcodeConfigDO.java
@@ -26,6 +26,10 @@
      * 鍐呭鏍煎紡妯℃澘鍗犱綅绗�
      */
     public static final String PLACEHOLDER_BUSINESS_CODE = "{BUSINESSCODE}";
+    /**
+     * 鍐呭鏍煎紡妯℃澘鍗犱綅绗�
+     */
+    public static final String PLACEHOLDER_BUSINESS_NAME = "{BUSINESSNAME}";
 
     /**
      * 缂栧彿
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptDO.java
new file mode 100644
index 0000000..7e11549
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptDO.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt;
+
+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.time.LocalDateTime;
+
+/**
+ * MES 璁惧鍏ュ簱鍗� DO
+ */
+@TableName("mes_wm_equipment_receipt")
+@KeySequence("mes_wm_equipment_receipt_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesWmEquipmentReceiptDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鍗曟嵁缂栫爜
+     */
+    private String code;
+    /**
+     * 鍗曟嵁鍚嶇О
+     */
+    private String name;
+    /**
+     * 鍏ュ簱鏃ユ湡
+     */
+    private LocalDateTime receiptDate;
+    /**
+     * 鍏ュ簱鏉ユ簮
+     */
+    private String source;
+    /**
+     * 鐘舵��
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmEquipmentReceiptStatusEnum}
+     */
+    private Integer status;
+    /**
+     * BPM 娴佺▼瀹炰緥缂栧彿
+     */
+    private String processInstanceId;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptLineDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptLineDO.java
new file mode 100644
index 0000000..cfcd75e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptLineDO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt;
+
+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;
+
+/**
+ * MES 璁惧鍏ュ簱鍗曡 DO
+ */
+@TableName("mes_wm_equipment_receipt_line")
+@KeySequence("mes_wm_equipment_receipt_line_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesWmEquipmentReceiptLineDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鍏ュ簱鍗曠紪鍙�
+     *
+     * 鍏宠仈 {@link MesWmEquipmentReceiptDO#getId()}
+     */
+    private Long receiptId;
+    /**
+     * 璁惧鍙拌处缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO#getId()}
+     */
+    private Long machineryId;
+    /**
+     * 璁惧鐗╂枡缂栧彿锛堣澶囧彴璐﹀搴旂殑 MDM 鐗╂枡锛宮dm_item.id锛�
+     */
+    private Long itemId;
+    /**
+     * 鍏ュ簱鏁伴噺
+     */
+    private BigDecimal quantity;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/sn/MesWmSnDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/sn/MesWmSnDO.java
index abfce45..e2f81af 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/sn/MesWmSnDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/sn/MesWmSnDO.java
@@ -3,6 +3,11 @@
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmSnStatusEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -53,5 +58,39 @@
      * 鍏宠仈 {@link MesProWorkOrderDO#getId()}
      */
     private Long workOrderId;
+    /**
+     * 鎵规 ID
+     *
+     * 鍏宠仈 {@link MesWmBatchDO#getId()}
+     */
+    private Long batchId;
+    /**
+     * 鐘舵��
+     *
+     * 鏋氫妇 {@link MesWmSnStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 鍦ㄥ簱浠撳簱 ID
+     *
+     * 鍏宠仈 {@link MesWmWarehouseDO#getId()}
+     */
+    private Long warehouseId;
+    /**
+     * 鍦ㄥ簱搴撳尯 ID
+     *
+     * 鍏宠仈 {@link MesWmWarehouseLocationDO#getId()}
+     */
+    private Long locationId;
+    /**
+     * 鍦ㄥ簱搴撲綅 ID
+     *
+     * 鍏宠仈 {@link MesWmWarehouseAreaDO#getId()}
+     */
+    private Long areaId;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
 
-}
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/plan/MesWmStockTakingPlanDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/plan/MesWmStockTakingPlanDO.java
index 6689690..05ed0ad 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/plan/MesWmStockTakingPlanDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/plan/MesWmStockTakingPlanDO.java
@@ -68,6 +68,25 @@
     private Boolean frozen;
 
     /**
+     * 鏄惁瀹氭湡鑷姩鐢熸垚鐩樼偣浠诲姟
+     *
+     * 涓� true 鏃剁敱鍚庡彴瀹氭椂浠诲姟鎸� {@link #generateCron} 鍛ㄦ湡鑷姩鍒涘缓鐩樼偣浠诲姟
+     */
+    private Boolean autoGenerate;
+    /**
+     * 鑷姩鐢熸垚鍛ㄦ湡 cron 琛ㄨ揪寮�
+     *
+     * autoGenerate 涓� true 鏃跺繀濉紝Quartz 6 娈� cron锛屽 {@code 0 0 8 * * ?} 琛ㄧず姣忓ぉ 8 鐐�
+     */
+    private String generateCron;
+    /**
+     * 涓婃鑷姩鐢熸垚鏃堕棿
+     *
+     * 瀹氭椂浠诲姟鎹鍒ゆ柇鍛ㄦ湡鏄惁鍒版湡锛岄伩鍏嶅悓涓�鍛ㄦ湡閲嶅鐢熸垚
+     */
+    private LocalDateTime lastGenerateTime;
+
+    /**
      * 鐘舵��
      *
      * 鏋氫妇 {@link CommonStatusEnum}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/task/MesWmStockTakingTaskDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/task/MesWmStockTakingTaskDO.java
index 7681c84..6279c2e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/task/MesWmStockTakingTaskDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/task/MesWmStockTakingTaskDO.java
@@ -93,6 +93,13 @@
     private Integer status;
 
     /**
+     * BPM 娴佺▼瀹炰緥缂栧彿
+     *
+     * 闈炵┖琛ㄧず鐩樼偣缁撴灉宸叉彁浜� BPM 瀹℃壒锛屾鍦ㄧ瓑寰呭鎵圭粨鏋�
+     */
+    private String processInstanceId;
+
+    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java
index f574a36..7823920 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java
@@ -107,6 +107,10 @@
      * 鍏宠仈 {@link MesWmBatchDO#getCode()}
      */
     private String batchCode;
+    /**
+     * SN 鐮侊紙搴忓垪鍙风鐞嗙墿鏂欏崟鍝佽繘鍑猴級
+     */
+    private String snCode;
 
     // ==================== 搴撳瓨浣嶇疆 ====================
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java
index fc79560..6cad81c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseAreaDO.java
@@ -28,6 +28,11 @@
     public static final String WIP_VIRTUAL_AREA = "WIP_VIRTUAL_AREA";
 
     /**
+     * 璁惧浠撻粯璁ゅ簱浣嶇紪鐮侊紙閰嶅悎 {@link MesWmWarehouseDO#EQUIPMENT_WAREHOUSE} 浣跨敤锛�
+     */
+    public static final String EQUIPMENT_AREA = "SYS-EQUIP-BIN-01";
+
+    /**
      * 缂栧彿
      */
     @TableId
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java
index 1680f97..6ddb9fb 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java
@@ -32,6 +32,13 @@
     public static final String WIP_VIRTUAL_WAREHOUSE = "WIP_VIRTUAL_WAREHOUSE";
 
     /**
+     * 璁惧浠撶紪鐮�
+     *
+     * 璁惧鍒涘缓鏃惰嚜鍔ㄥ叆搴撶殑涓撶敤浠撳簱锛屽搴斿簱瀛樹簨鍔′粨搴撶淮搴︺��
+     */
+    public static final String EQUIPMENT_WAREHOUSE = "SYS-EQUIPMENT";
+
+    /**
      * 缂栧彿
      */
     @TableId
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseLocationDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseLocationDO.java
index 1c68ff8..f670a30 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseLocationDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseLocationDO.java
@@ -27,6 +27,11 @@
     public static final String WIP_VIRTUAL_LOCATION = "WIP_VIRTUAL_LOCATION";
 
     /**
+     * 璁惧浠撻�氱敤搴撳尯缂栫爜锛堥厤鍚� {@link MesWmWarehouseDO#EQUIPMENT_WAREHOUSE} 浣跨敤锛�
+     */
+    public static final String EQUIPMENT_LOCATION = "SYS-EQUIP-LOC-01";
+
+    /**
      * 缂栧彿
      */
     @TableId
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/cal/schedule/MesCalScheduleDetailMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/cal/schedule/MesCalScheduleDetailMapper.java
new file mode 100644
index 0000000..2089279
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/cal/schedule/MesCalScheduleDetailMapper.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.cal.schedule;
+
+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.cal.schedule.vo.MesCalScheduleDetailPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.schedule.MesCalScheduleDetailDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MES 鍛樺伐绾ф帓鐝槑缁� Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface MesCalScheduleDetailMapper extends BaseMapperX<MesCalScheduleDetailDO> {
+
+    default PageResult<MesCalScheduleDetailDO> selectPage(MesCalScheduleDetailPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesCalScheduleDetailDO>()
+                .eqIfPresent(MesCalScheduleDetailDO::getPlanId, reqVO.getPlanId())
+                .eqIfPresent(MesCalScheduleDetailDO::getTeamId, reqVO.getTeamId())
+                .eqIfPresent(MesCalScheduleDetailDO::getUserId, reqVO.getUserId())
+                .geIfPresent(MesCalScheduleDetailDO::getDay, reqVO.getStartDay())
+                .leIfPresent(MesCalScheduleDetailDO::getDay, reqVO.getEndDay())
+                .orderByAsc(MesCalScheduleDetailDO::getDay));
+    }
+
+    default List<MesCalScheduleDetailDO> selectList(MesCalScheduleDetailPageReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<MesCalScheduleDetailDO>()
+                .eqIfPresent(MesCalScheduleDetailDO::getPlanId, reqVO.getPlanId())
+                .eqIfPresent(MesCalScheduleDetailDO::getTeamId, reqVO.getTeamId())
+                .eqIfPresent(MesCalScheduleDetailDO::getUserId, reqVO.getUserId())
+                .geIfPresent(MesCalScheduleDetailDO::getDay, reqVO.getStartDay())
+                .leIfPresent(MesCalScheduleDetailDO::getDay, reqVO.getEndDay())
+                .orderByAsc(MesCalScheduleDetailDO::getDay));
+    }
+
+    default List<MesCalScheduleDetailDO> selectListByPlanId(Long planId) {
+        return selectList(MesCalScheduleDetailDO::getPlanId, planId);
+    }
+
+    default List<MesCalScheduleDetailDO> selectListByUserIdBetween(Long userId,
+                                                                   java.time.LocalDateTime startDay,
+                                                                   java.time.LocalDateTime endDay) {
+        return selectList(new LambdaQueryWrapperX<MesCalScheduleDetailDO>()
+                .eq(MesCalScheduleDetailDO::getUserId, userId)
+                .geIfPresent(MesCalScheduleDetailDO::getDay, startDay)
+                .leIfPresent(MesCalScheduleDetailDO::getDay, endDay)
+                .orderByAsc(MesCalScheduleDetailDO::getDay));
+    }
+
+    default void deleteByPlanId(Long planId) {
+        delete(MesCalScheduleDetailDO::getPlanId, planId);
+    }
+
+    default void deleteByTeamId(Long teamId) {
+        delete(MesCalScheduleDetailDO::getTeamId, teamId);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/barcode/MesWmBarcodeConfigMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/barcode/MesWmBarcodeConfigMapper.java
index ef4a148..6ee6234 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/barcode/MesWmBarcodeConfigMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/barcode/MesWmBarcodeConfigMapper.java
@@ -1,11 +1,14 @@
 package cn.iocoder.yudao.module.mes.dal.mysql.wm.barcode;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 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.wm.barcode.vo.config.MesWmBarcodeConfigPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.barcode.MesWmBarcodeConfigDO;
 import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
 
 /**
  * MES 鏉$爜閰嶇疆 Mapper
@@ -29,4 +32,10 @@
         return selectOne(MesWmBarcodeConfigDO::getBizType, bizType);
     }
 
+    default List<MesWmBarcodeConfigDO> selectEnableList() {
+        return selectList(new LambdaQueryWrapperX<MesWmBarcodeConfigDO>()
+                .eq(MesWmBarcodeConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
+                .orderByAsc(MesWmBarcodeConfigDO::getId));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptLineMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptLineMapper.java
new file mode 100644
index 0000000..9991bbd
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptLineMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.wm.equipmentreceipt;
+
+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.wm.equipmentreceipt.vo.line.MesWmEquipmentReceiptLinePageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptLineDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * MES 璁惧鍏ュ簱鍗曡 Mapper
+ */
+@Mapper
+public interface MesWmEquipmentReceiptLineMapper extends BaseMapperX<MesWmEquipmentReceiptLineDO> {
+
+    default PageResult<MesWmEquipmentReceiptLineDO> selectPage(MesWmEquipmentReceiptLinePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmEquipmentReceiptLineDO>()
+                .eqIfPresent(MesWmEquipmentReceiptLineDO::getReceiptId, reqVO.getReceiptId())
+                .eqIfPresent(MesWmEquipmentReceiptLineDO::getMachineryId, reqVO.getMachineryId())
+                .eqIfPresent(MesWmEquipmentReceiptLineDO::getItemId, reqVO.getItemId())
+                .orderByDesc(MesWmEquipmentReceiptLineDO::getId));
+    }
+
+    default List<MesWmEquipmentReceiptLineDO> selectListByReceiptId(Long receiptId) {
+        return selectList(MesWmEquipmentReceiptLineDO::getReceiptId, receiptId);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptMapper.java
new file mode 100644
index 0000000..9424c6c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.wm.equipmentreceipt;
+
+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.wm.equipmentreceipt.vo.MesWmEquipmentReceiptPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MES 璁惧鍏ュ簱鍗� Mapper
+ */
+@Mapper
+public interface MesWmEquipmentReceiptMapper extends BaseMapperX<MesWmEquipmentReceiptDO> {
+
+    default PageResult<MesWmEquipmentReceiptDO> selectPage(MesWmEquipmentReceiptPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmEquipmentReceiptDO>()
+                .likeIfPresent(MesWmEquipmentReceiptDO::getCode, reqVO.getCode())
+                .likeIfPresent(MesWmEquipmentReceiptDO::getName, reqVO.getName())
+                .likeIfPresent(MesWmEquipmentReceiptDO::getSource, reqVO.getSource())
+                .eqIfPresent(MesWmEquipmentReceiptDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(MesWmEquipmentReceiptDO::getReceiptDate, reqVO.getReceiptDate())
+                .orderByDesc(MesWmEquipmentReceiptDO::getId));
+    }
+
+    default MesWmEquipmentReceiptDO selectByCode(String code) {
+        return selectOne(MesWmEquipmentReceiptDO::getCode, code);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/sn/MesWmSnMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/sn/MesWmSnMapper.java
index 24d6543..fb721f7 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/sn/MesWmSnMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/sn/MesWmSnMapper.java
@@ -5,8 +5,11 @@
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGroupRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmSnStatusEnum;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -51,4 +54,35 @@
                 .eq(MesWmSnDO::getUuid, uuid));
     }
 
+    default MesWmSnDO selectByCode(String code) {
+        return selectOne(MesWmSnDO::getCode, code);
+    }
+
+    /**
+     * 鍑哄簱鏍搁攢锛氱疆涓哄凡鍑哄簱骞舵竻绌哄湪搴撳簱浣�
+     */
+    default void markOutStockAndClearLocation(Long id) {
+        update(null, new LambdaUpdateWrapper<MesWmSnDO>()
+                .eq(MesWmSnDO::getId, id)
+                .set(MesWmSnDO::getStatus, MesWmSnStatusEnum.OUT_STOCK.getStatus())
+                .set(MesWmSnDO::getWarehouseId, null)
+                .set(MesWmSnDO::getLocationId, null)
+                .set(MesWmSnDO::getAreaId, null));
+    }
+
+    /**
+     * 鎸� SN 鐘舵��/搴撲綅/鐗╂枡绛夌淮搴﹀垎椤垫煡璇�
+     */
+    default PageResult<MesWmSnDO> selectStatusPage(MesWmSnInStockPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmSnDO>()
+                .likeIfPresent(MesWmSnDO::getCode, reqVO.getCode())
+                .eqIfPresent(MesWmSnDO::getItemId, reqVO.getItemId())
+                .likeIfPresent(MesWmSnDO::getBatchCode, reqVO.getBatchCode())
+                .eqIfPresent(MesWmSnDO::getWarehouseId, reqVO.getWarehouseId())
+                .eqIfPresent(MesWmSnDO::getLocationId, reqVO.getLocationId())
+                .eqIfPresent(MesWmSnDO::getAreaId, reqVO.getAreaId())
+                .eqIfPresent(MesWmSnDO::getStatus, reqVO.getStatus())
+                .orderByDesc(MesWmSnDO::getId));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/plan/MesWmStockTakingPlanMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/plan/MesWmStockTakingPlanMapper.java
index 5e860bd..8a0f2bd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/plan/MesWmStockTakingPlanMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/plan/MesWmStockTakingPlanMapper.java
@@ -1,11 +1,14 @@
 package cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.plan;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 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.wm.stocktaking.plan.vo.MesWmStockTakingPlanPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.plan.MesWmStockTakingPlanDO;
 import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
 
 
 /**
@@ -20,6 +23,19 @@
         return selectOne(MesWmStockTakingPlanDO::getCode, code);
     }
 
+    /**
+     * 鏌ヨ鍚敤涓斿紑鍚畾鏈熻嚜鍔ㄧ敓鎴愮殑鐩樼偣鏂规
+     *
+     * @return 鐩樼偣鏂规鍒楄〃
+     */
+    default List<MesWmStockTakingPlanDO> selectEnableAutoGenerateList() {
+        return selectList(new LambdaQueryWrapperX<MesWmStockTakingPlanDO>()
+                .eq(MesWmStockTakingPlanDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
+                .eq(MesWmStockTakingPlanDO::getAutoGenerate, Boolean.TRUE)
+                .isNotNull(MesWmStockTakingPlanDO::getGenerateCron)
+                .orderByAsc(MesWmStockTakingPlanDO::getId));
+    }
+
     default PageResult<MesWmStockTakingPlanDO> selectPage(MesWmStockTakingPlanPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<MesWmStockTakingPlanDO>()
                 .likeIfPresent(MesWmStockTakingPlanDO::getCode, reqVO.getCode())
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskMapper.java
index 67c0918..09ff64a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskMapper.java
@@ -5,6 +5,7 @@
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -19,6 +20,19 @@
         return selectOne(MesWmStockTakingTaskDO::getCode, code);
     }
 
+    /**
+     * 娓呯┖ BPM 娴佺▼瀹炰緥缂栧彿
+     *
+     * 鐢ㄤ簬鐩樼偣缁撴灉瀹℃壒琚┏鍥�/鍙栨秷鍚庣疆绌猴紝浠诲姟鍥炲埌鍙噸鏂版彁浜ょ粨鏋滃鎵圭殑鐘舵��
+     *
+     * @param id 鐩樼偣浠诲姟缂栧彿
+     */
+    default void clearProcessInstanceId(Long id) {
+        update(null, new LambdaUpdateWrapper<MesWmStockTakingTaskDO>()
+                .eq(MesWmStockTakingTaskDO::getId, id)
+                .set(MesWmStockTakingTaskDO::getProcessInstanceId, null));
+    }
+
     default PageResult<MesWmStockTakingTaskDO> selectPage(MesWmStockTakingTaskPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<MesWmStockTakingTaskDO>()
                 .likeIfPresent(MesWmStockTakingTaskDO::getCode, reqVO.getCode())
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java
index 878347e..192ca52 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java
@@ -24,12 +24,27 @@
                 .eqIfPresent(MesWmTransactionDO::getBizType, reqVO.getBizType())
                 .likeIfPresent(MesWmTransactionDO::getBizCode, reqVO.getBizCode())
                 .eqIfPresent(MesWmTransactionDO::getMaterialStockId, reqVO.getMaterialStockId())
+                .eqIfPresent(MesWmTransactionDO::getSnCode, reqVO.getSnCode())
                 .geIfPresent(MesWmTransactionDO::getTransactionTime, reqVO.getStartTime())
                 .leIfPresent(MesWmTransactionDO::getTransactionTime, reqVO.getEndTime())
                 .orderByDesc(MesWmTransactionDO::getTransactionTime));
     }
 
     /**
+     * 鎸� SN 鐮佹煡璇㈡渶杩戜竴绗斿簱瀛樹簨鍔�
+     *
+     * @param snCode SN 鐮�
+     * @return 鏈�杩戜竴绗斾簨鍔★紝涓嶅瓨鍦ㄨ繑鍥� null
+     */
+    default MesWmTransactionDO selectLatestBySnCode(String snCode) {
+        return selectOne(new LambdaQueryWrapperX<MesWmTransactionDO>()
+                .eq(MesWmTransactionDO::getSnCode, snCode)
+                .orderByDesc(MesWmTransactionDO::getTransactionTime)
+                .orderByDesc(MesWmTransactionDO::getId)
+                .last("LIMIT 1"));
+    }
+
+    /**
      * 鏌ユ壘铏氭嫙绾胯竟搴撳簱瀛樿褰曞搴旂殑鍘熷鍑哄簱浠撳簱淇℃伅
      * <p>
      * 閫氳繃铏氭嫙绾胯竟搴撶殑 materialStockId锛屾壘鍒伴娆″叆搴撲簨鍔★紙IN锛夛紝
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index aa01418..c0f0a6b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -135,6 +135,9 @@
     ErrorCode CAL_TEAM_SHIFT_NOT_EXISTS = new ErrorCode(1_040_201_200, "鐝粍鎺掔彮璁板綍涓嶅瓨鍦�");
     ErrorCode CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH = new ErrorCode(1_040_201_201, "鐝粍鏁伴噺涓嶆弧瓒宠疆鐝柟寮忚姹�");
     ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH = new ErrorCode(1_040_201_202, "鐝鏁伴噺涓嶆弧瓒宠疆鐝柟寮忚姹�");
+    // ========== MES 鏃ュ巻鎺掔彮-鍛樺伐绾ф帓鐝槑缁嗭紙1-040-201-300锛� ==========
+    ErrorCode CAL_SCHEDULE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_201_300, "鍛樺伐鎺掔彮鏄庣粏涓嶅瓨鍦�");
+    ErrorCode CAL_SCHEDULE_DETAIL_GENERATE_TEAM_MEMBER_EMPTY = new ErrorCode(1_040_201_301, "璇ヨ鍒掑弬涓庣殑鐝粍鍧囨棤鐝粍鎴愬憳锛屾棤娉曠敓鎴愬憳宸ユ帓鐝槑缁�");
 
     // ========== MES 鏃ュ巻鎺掔彮-鎺掔彮璁″垝锛�1-040-202-000锛� ==========
     ErrorCode CAL_PLAN_NOT_EXISTS = new ErrorCode(1_040_202_000, "鎺掔彮璁″垝涓嶅瓨鍦�");
@@ -168,6 +171,8 @@
     ErrorCode DV_MACHINERY_HAS_MAINTEN_RECORD = new ErrorCode(1_040_301_005, "璁惧宸插叧鑱斾繚鍏昏褰曪紝鏃犳硶鍒犻櫎");
     ErrorCode DV_MACHINERY_HAS_REPAIR = new ErrorCode(1_040_301_006, "璁惧宸插叧鑱旂淮淇伐鍗曪紝鏃犳硶鍒犻櫎");
     ErrorCode DV_MACHINERY_WORKSTATION_NOT_EXISTS = new ErrorCode(1_040_301_008, "宸ヤ綔绔欑紪鐮佷笉瀛樺湪");
+    ErrorCode DV_MACHINERY_HAS_ITEM = new ErrorCode(1_040_301_009, "璁惧宸茶嚜鍔ㄧ敓鎴愮墿鏂欏苟鍏ヨ澶囦粨锛岀姝㈠垹闄わ紱濡傞渶绉婚櫎璇烽�氳繃搴撳瓨璋冩暣鍏堝鐞嗗簱瀛�");
+    ErrorCode DV_MACHINERY_EQUIP_MASTER_MISSING = new ErrorCode(1_040_301_010, "璁惧鍏ュ簱鑱斿姩鎵�闇�涓绘暟鎹湭鍒濆鍖栵紙璁惧浠撴垨鈥滆澶団�濆垎绫�/鈥滃彴鈥濊閲忓崟浣嶇己澶憋級锛岃鑱旂郴绠$悊鍛樺垵濮嬪寲");
 
     // ========== MES 璁惧绠$悊-璁惧鏁伴噰锛�1-040-308-000锛� ==========
     ErrorCode DV_TELEMETRY_NOT_EXISTS = new ErrorCode(1_040_308_000, "鏁伴噰閬ユ祴鏁版嵁涓嶅瓨鍦�");
@@ -669,6 +674,9 @@
     ErrorCode WM_STOCK_TAKING_PLAN_PARAM_NOT_EXISTS = new ErrorCode(1_040_714_104, "鐩樼偣鏂规鍙傛暟涓嶅瓨鍦�");
     ErrorCode WM_STOCK_TAKING_PLAN_PARAM_EMPTY = new ErrorCode(1_040_714_105, "鐩樼偣鏂规鍙傛暟涓嶈兘涓虹┖锛岃鍏堥厤缃洏鐐瑰弬鏁�");
     ErrorCode WM_STOCK_TAKING_PLAN_DYNAMIC_TIME_INVALID = new ErrorCode(1_040_714_106, "鍔ㄦ�佺洏鐐规柟妗堝繀椤昏缃紑濮嬫椂闂村拰缁撴潫鏃堕棿锛屼笖缁撴潫鏃堕棿蹇呴』鏅氫簬寮�濮嬫椂闂�");
+    ErrorCode WM_STOCK_TAKING_PLAN_AUTO_GENERATE_CRON_REQUIRED = new ErrorCode(1_040_714_107, "寮�鍚畾鏈熻嚜鍔ㄧ敓鎴愭椂蹇呴』閰嶇疆鐢熸垚鍛ㄦ湡 cron 琛ㄨ揪寮�");
+    ErrorCode WM_STOCK_TAKING_PLAN_AUTO_GENERATE_CRON_INVALID = new ErrorCode(1_040_714_108, "鐢熸垚鍛ㄦ湡 cron 琛ㄨ揪寮忎笉鍚堟硶");
+    ErrorCode WM_STOCK_TAKING_PLAN_NOT_AUTO_GENERATE = new ErrorCode(1_040_714_109, "璇ョ洏鐐规柟妗堟湭寮�鍚畾鏈熻嚜鍔ㄧ敓鎴�");
     ErrorCode WM_STOCK_TAKING_TASK_NOT_EXISTS = new ErrorCode(1_040_714_110, "鐩樼偣浠诲姟涓嶅瓨鍦�");
     ErrorCode WM_STOCK_TAKING_TASK_CODE_DUPLICATE = new ErrorCode(1_040_714_111, "鐩樼偣浠诲姟缂栫爜宸插瓨鍦�");
     ErrorCode WM_STOCK_TAKING_TASK_NOT_PREPARE = new ErrorCode(1_040_714_112, "鍙湁鑽夌鐘舵�佺殑鐩樼偣浠诲姟鎵嶅厑璁告鎿嶄綔");
@@ -681,6 +689,8 @@
     ErrorCode WM_STOCK_TAKING_TASK_RESULT_NOT_EXISTS = new ErrorCode(1_040_714_119, "鐩樼偣缁撴灉涓嶅瓨鍦�");
     ErrorCode WM_STOCK_TAKING_TASK_LINE_ALREADY_TAKEN = new ErrorCode(1_040_714_120, "璇ョ洏鐐规竻鍗曡宸叉湁鐩樼偣璁板綍锛屼笉鑳介噸澶嶇洏鐐�");
     ErrorCode WM_STOCK_TAKING_TASK_NOT_FINISHED = new ErrorCode(1_040_714_121, "鍙湁宸插畬鎴愮姸鎬佺殑鐩樼偣浠诲姟鎵嶈兘澶勭悊宸紓");
+    ErrorCode WM_STOCK_TAKING_TASK_NO_RESULT = new ErrorCode(1_040_714_122, "鐩樼偣浠诲姟鑷冲皯闇�瑕佷竴鏉$洏鐐圭粨鏋滄墠鍏佽鎻愪氦缁撴灉瀹℃壒");
+    ErrorCode WM_STOCK_TAKING_TASK_ALREADY_SUBMIT_APPROVAL = new ErrorCode(1_040_714_123, "鐩樼偣缁撴灉瀹℃壒宸叉彁浜わ紝璇峰嬁閲嶅鎻愪氦");
 
     // ========== MES 浠撳簱绠$悊-閿�鍞嚭搴撳崟锛�1-040-714-000锛� ==========
     ErrorCode WM_PRODUCT_SALES_NOT_EXISTS = new ErrorCode(1_040_714_000, "閿�鍞嚭搴撳崟涓嶅瓨鍦�");
@@ -763,6 +773,27 @@
     ErrorCode BARCODE_BIZ_TYPE_NOT_EXISTS = new ErrorCode(1_040_731_003, "涓氬姟绫诲瀷涓嶈兘涓虹┖");
     ErrorCode BARCODE_BIZ_CODE_NOT_EXISTS = new ErrorCode(1_040_731_004, "涓氬姟缂栫爜涓嶈兘涓虹┖");
     ErrorCode BARCODE_CONFIG_NOT_EXISTS = new ErrorCode(1_040_731_005, "鏉$爜閰嶇疆涓嶅瓨鍦�");
+    ErrorCode BARCODE_FORMAT_INVALID = new ErrorCode(1_040_731_006, "鏉$爜鏍煎紡涓嶆敮鎸佹覆鏌�");
+    ErrorCode BARCODE_CONTENT_EMPTY = new ErrorCode(1_040_731_007, "鏉$爜鍐呭涓嶈兘涓虹┖");
+
+    // ========== MES 浠撳簱绠$悊-SN 鐮侊紙1-040-732-000锛� ==========
+    ErrorCode WM_SN_NOT_EXISTS = new ErrorCode(1_040_732_000, "SN 鐮佷笉瀛樺湪");
+    ErrorCode WM_SN_ITEM_NOT_SERIAL = new ErrorCode(1_040_732_001, "鐗╂枡鏈惎鐢ㄥ簭鍒楀彿绠$悊锛屾棤娉曠櫥璁� SN");
+    ErrorCode WM_SN_ALREADY_IN_STOCK = new ErrorCode(1_040_732_002, "SN 宸插湪搴擄紝璇峰厛鍑哄簱鍐嶇櫥璁板叆搴�");
+    ErrorCode WM_SN_NOT_IN_STOCK = new ErrorCode(1_040_732_003, "SN 涓嶅湪搴擄紝鏃犳硶鍑哄簱");
+    ErrorCode WM_SN_ITEM_MISMATCH = new ErrorCode(1_040_732_004, "SN 涓庣墿鏂欎笉鍖归厤");
+    ErrorCode WM_SN_LOCATION_MISMATCH = new ErrorCode(1_040_732_005, "SN 涓嶅湪鎸囧畾搴撲綅鍦ㄥ簱锛屾棤娉曞嚭搴�");
+
+    // ========== MES 璁惧鍏ュ簱鍗曪紙1-040-733-000锛� ==========
+    ErrorCode WM_EQUIP_RECEIPT_NOT_EXISTS = new ErrorCode(1_040_733_000, "璁惧鍏ュ簱鍗曚笉瀛樺湪");
+    ErrorCode WM_EQUIP_RECEIPT_CODE_DUPLICATE = new ErrorCode(1_040_733_001, "璁惧鍏ュ簱鍗曠紪鐮佸凡瀛樺湪");
+    ErrorCode WM_EQUIP_RECEIPT_STATUS_NOT_PREPARE = new ErrorCode(1_040_733_002, "鍙湁鑽夌鐘舵�佹墠鍏佽姝ゆ搷浣�");
+    ErrorCode WM_EQUIP_RECEIPT_STATUS_NOT_APPROVED = new ErrorCode(1_040_733_003, "鍙湁宸插鎵圭姸鎬佹墠鍏佽鎵ц鍏ュ簱");
+    ErrorCode WM_EQUIP_RECEIPT_NO_LINE = new ErrorCode(1_040_733_004, "鑷冲皯闇�瑕佷竴鏉¤椤圭洰");
+    ErrorCode WM_EQUIP_RECEIPT_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_733_005, "宸插畬鎴愭垨宸插彇娑堢殑鍏ュ簱鍗曚笉鍏佽鍙栨秷");
+    ErrorCode WM_EQUIP_RECEIPT_LINE_NOT_EXISTS = new ErrorCode(1_040_733_100, "璁惧鍏ュ簱鍗曡涓嶅瓨鍦�");
+    ErrorCode WM_EQUIP_RECEIPT_MACHINERY_NOT_VALID = new ErrorCode(1_040_733_101, "鎵�閫夎澶囨湭鍏宠仈鐗╂枡锛屾棤娉曞叆搴�");
+    ErrorCode WM_EQUIP_RECEIPT_QUANTITY_INVALID = new ErrorCode(1_040_733_102, "鍏ュ簱鏁伴噺蹇呴』澶т簬 0");
 
     // ========== MES 浠撳簱绠$悊-瑁呯鍗曪紙1-040-740-000锛� ==========
     ErrorCode WM_PACKAGE_NOT_EXISTS = new ErrorCode(1_040_740_000, "瑁呯鍗曚笉瀛樺湪");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java
index b089810..34fa582 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java
@@ -66,6 +66,7 @@
     // ========== DV 璁惧妯″潡 [400, 500) ==========
 
     public static final int DV_MACHINERY = 400; // 璁惧锛歁esDvMachineryDO
+    public static final int DV_EQUIPMENT_RECEIPT = 401; // 璁惧鍏ュ簱鍗曪細MesWmEquipmentReceiptDO
 
     // ========== TM 宸ヨ澶瑰叿妯″潡 [500, 600) ==========
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalScheduleDetailSourceEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalScheduleDetailSourceEnum.java
new file mode 100644
index 0000000..d3cf402
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalScheduleDetailSourceEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.mes.enums.cal;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * MES 鍛樺伐鎺掔彮鏄庣粏鏉ユ簮鏋氫妇
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Getter
+@AllArgsConstructor
+public enum MesCalScheduleDetailSourceEnum implements ArrayValuable<Integer> {
+
+    AUTO(1, "鑷姩鐢熸垚"),
+    MANUAL(2, "浜哄伐璋冩暣");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesCalScheduleDetailSourceEnum::getSource).toArray(Integer[]::new);
+
+    /**
+     * 鏉ユ簮鍊�
+     */
+    private final Integer source;
+    /**
+     * 鏉ユ簮鍚�
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java
index 6ce49b6..7d3a613 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java
@@ -47,6 +47,7 @@
     WM_SALES_NOTICE_CODE("WM_SALES_NOTICE_CODE", "鍙戣揣閫氱煡鍗曠紪鐮�"),
     WM_RETURN_SALES_CODE("WM_RETURN_SALES_CODE", "閿�鍞��璐у崟缂栫爜"),
     WM_MISC_ISSUE_CODE("WM_MISC_ISSUE_CODE", "鏉傞」鍑哄簱鍗曠紪鐮�"),
+    WM_EQUIP_RECEIPT_CODE("WM_EQUIP_RECEIPT_CODE", "璁惧鍏ュ簱鍗曠紪鐮�"),
     PRO_ROUTE_CODE("PRO_ROUTE_CODE", "宸ヨ壓璺嚎缂栫爜"),
     PRO_FEEDBACK_CODE("PRO_FEEDBACK_CODE", "鐢熶骇鎶ュ伐鍗曠紪鐮�"),
     PRO_WORK_ORDER_CODE("PRO_WORK_ORDER_CODE", "鐢熶骇宸ュ崟缂栫爜"),
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmEquipmentReceiptStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmEquipmentReceiptStatusEnum.java
new file mode 100644
index 0000000..ab997fb
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmEquipmentReceiptStatusEnum.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.mes.enums.wm;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * MES 璁惧鍏ュ簱鍗曠姸鎬佹灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum MesWmEquipmentReceiptStatusEnum implements ArrayValuable<Integer> {
+
+    /**
+     * 鑽夌
+     */
+    PREPARE(MesOrderStatusConstants.PREPARE, "鑽夌"),
+    /**
+     * 瀹℃壒涓�
+     */
+    APPROVING(MesOrderStatusConstants.APPROVING, "瀹℃壒涓�"),
+    /**
+     * 宸插鎵�
+     */
+    APPROVED(MesOrderStatusConstants.APPROVED, "宸插鎵�"),
+    /**
+     * 宸插畬鎴�
+     */
+    FINISHED(MesOrderStatusConstants.FINISHED, "宸插畬鎴�"),
+    /**
+     * 宸插彇娑�
+     */
+    CANCELED(MesOrderStatusConstants.CANCELLED, "宸插彇娑�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(MesWmEquipmentReceiptStatusEnum::getStatus).toArray(Integer[]::new);
+
+    /**
+     * 鐘舵�佸��
+     */
+    private final Integer status;
+    /**
+     * 鐘舵�佸悕
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmSnStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmSnStatusEnum.java
new file mode 100644
index 0000000..7a2ba3d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmSnStatusEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.mes.enums.wm;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * MES SN 鐮佺姸鎬佹灇涓�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Getter
+@AllArgsConstructor
+public enum MesWmSnStatusEnum implements ArrayValuable<Integer> {
+
+    IN_STOCK(1, "鍦ㄥ簱"),
+    OUT_STOCK(2, "宸插嚭搴�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesWmSnStatusEnum::getStatus).toArray(Integer[]::new);
+
+    /**
+     * 鐘舵�佸��
+     */
+    private final Integer status;
+    /**
+     * 鐘舵�佸悕
+     */
+    private final String label;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockTakingTaskStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockTakingTaskStatusEnum.java
index 10d05ee..9e8c990 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockTakingTaskStatusEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockTakingTaskStatusEnum.java
@@ -23,15 +23,16 @@
      */
     PREPARE(MesOrderStatusConstants.PREPARE, "鑽夌"),
     /**
-     * 瀹℃壒涓�
+     * 鐩樼偣涓紙鍚洏鐐圭粨鏋滃鎵逛腑锛�
      *
-     * 瀵瑰簲 MesWmStockTakingTaskService#submitStockTakingTask 鏂规硶
+     * 瀵瑰簲 MesWmStockTakingTaskService#submitStockTakingTask 寮�濮嬬洏鐐广��
+     * #submitStockTakingResultApproval 鎻愪氦缁撴灉瀹℃壒
      */
-    APPROVING(MesOrderStatusConstants.APPROVING, "瀹℃壒涓�"),
+    APPROVING(MesOrderStatusConstants.APPROVING, "鐩樼偣涓�"),
     /**
      * 宸插畬鎴�
      *
-     * 瀵瑰簲 MesWmStockTakingTaskService#finishStockTakingTask 鏂规硶
+     * 鐩樼偣缁撴灉瀹℃壒閫氳繃鍚庯紝鐢辩粨鏋滃鎵瑰洖璋冪疆涓哄凡瀹屾垚骞惰嚜鍔ㄦ牳閿�宸紓
      */
     FINISHED(MesOrderStatusConstants.FINISHED, "宸插畬鎴�"),
     /**
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockTakingPlanAutoJob.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockTakingPlanAutoJob.java
new file mode 100644
index 0000000..0094e3f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockTakingPlanAutoJob.java
@@ -0,0 +1,101 @@
+package cn.iocoder.yudao.module.mes.job.wm;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.plan.MesWmStockTakingPlanDO;
+import cn.iocoder.yudao.module.mes.service.wm.stocktaking.plan.MesWmStockTakingPlanService;
+import cn.iocoder.yudao.module.mes.service.wm.stocktaking.task.MesWmStockTakingTaskService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.quartz.CronExpression;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * MES 瀹氭湡鐩樼偣鑷姩鐢熸垚 Job
+ *
+ * <p>鎵弿鍚敤涓斿紑鍚畾鏈熻嚜鍔ㄧ敓鎴愮殑鐩樼偣鏂规锛屾寜鍚勬柟妗堥厤缃殑鐢熸垚鍛ㄦ湡 cron 鍒ゆ柇鏄惁鍒版湡锛�
+ * 鍒版湡鍒欐寜鏂规鑷姩鍒涘缓鐩樼偣浠诲姟锛岄伩鍏嶄汉宸ラ�愭湡寤哄崟銆傚悗鍙拌皟搴� cron 寤鸿閰嶇疆涓烘瘡鍒嗛挓鎵ц涓�娆°��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Component
+@Slf4j
+public class MesWmStockTakingPlanAutoJob implements JobHandler {
+
+    @Resource
+    private MesWmStockTakingPlanService stockTakingPlanService;
+    @Resource
+    private MesWmStockTakingTaskService stockTakingTaskService;
+
+    @Override
+    @TenantJob
+    public String execute(String param) {
+        // 1. 鑾峰彇鍚敤涓斿紑鍚畾鏈熻嚜鍔ㄧ敓鎴愮殑鏂规
+        List<MesWmStockTakingPlanDO> plans = stockTakingPlanService.getEnableAutoGeneratePlanList();
+        if (CollUtil.isEmpty(plans)) {
+            return "瀹氭湡鐩樼偣锛氭棤鍚敤鐨勮嚜鍔ㄧ敓鎴愭柟妗堬紝璺宠繃";
+        }
+
+        // 2. 閫愪釜鏂规鍒ゆ柇鍛ㄦ湡鏄惁鍒版湡锛屽埌鏈熷垯鑷姩鐢熸垚浠诲姟
+        Date now = new Date();
+        int successCount = 0;
+        List<String> errors = new ArrayList<>();
+        for (MesWmStockTakingPlanDO plan : plans) {
+            try {
+                if (shouldGenerate(plan, now)) {
+                    Long taskId = stockTakingTaskService.createStockTakingTaskFromPlan(plan.getId());
+                    successCount++;
+                    log.info("[execute] 鏂规({}) 鑷姩鐢熸垚鐩樼偣浠诲姟({}) 鎴愬姛", plan.getId(), taskId);
+                }
+            } catch (Exception e) {
+                log.error("[execute] 鏂规({}) 鑷姩鐢熸垚鐩樼偣浠诲姟澶辫触", plan.getId(), e);
+                errors.add(String.format("鏂规[%s]锛�%s", plan.getName(), e.toString()));
+            }
+        }
+
+        // 3. 姹囨�绘墽琛岀粨鏋�
+        String msg = String.format("瀹氭湡鐩樼偣锛氭壂鎻� %s 涓柟妗堬紝鏈鐢熸垚 %s 涓换鍔�", plans.size(), successCount);
+        if (CollUtil.isNotEmpty(errors)) {
+            msg += "锛屽け璐ユ槑缁嗭細" + String.join("锛�", errors);
+        }
+        return msg;
+    }
+
+    /**
+     * 鍒ゆ柇鏂规褰撳墠鍛ㄦ湡鏄惁鍒版湡
+     *
+     * <p>浠庢湭鐢熸垚杩囧垯浠呰褰曞熀绾挎椂闂村苟浠庝笅涓�鍛ㄦ湡寮�濮嬭Е鍙戯紙鍒氬惎鐢ㄤ笉绔嬪嵆鐢熸垚锛夛紱
+     * 鍚﹀垯鎸夈�屼笂娆$敓鎴愭椂闂� + cron銆嶈绠椾笅涓�娆¤Е鍙戞椂闂达紝钀藉湪褰撳墠鏃堕棿涔嬪墠鍗充负鍒版湡銆�
+     *
+     * @param plan 鐩樼偣鏂规
+     * @param now  褰撳墠鏃堕棿
+     * @return 鏄惁鍒版湡
+     */
+    private boolean shouldGenerate(MesWmStockTakingPlanDO plan, Date now) {
+        CronExpression cron;
+        try {
+            cron = new CronExpression(plan.getGenerateCron());
+        } catch (Exception e) {
+            log.warn("[shouldGenerate] 鏂规({}) cron 琛ㄨ揪寮�({}) 涓嶅悎娉曪紝璺宠繃",
+                    plan.getId(), plan.getGenerateCron());
+            return false;
+        }
+        LocalDateTime lastTime = plan.getLastGenerateTime();
+        if (lastTime == null) {
+            // 棣栨鎵弿锛氫粎璁板綍鍩虹嚎鏃堕棿锛屼笉绔嬪嵆鐢熸垚
+            stockTakingPlanService.updateStockTakingPlanLastGenerateTime(plan.getId(), LocalDateTime.now());
+            return false;
+        }
+        Date last = Date.from(lastTime.atZone(ZoneId.systemDefault()).toInstant());
+        Date next = cron.getNextValidTimeAfter(last);
+        return next != null && !next.after(now);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailService.java
new file mode 100644
index 0000000..18ffeb0
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailService.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.mes.service.cal.schedule;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailCheckReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailCheckRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.schedule.MesCalScheduleDetailDO;
+
+import java.util.List;
+
+/**
+ * MES 鍛樺伐绾ф帓鐝槑缁� Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesCalScheduleDetailService {
+
+    /**
+     * 鎸夌彮缁勬帓鐝敓鎴愬憳宸ョ骇鎺掔彮鏄庣粏锛堝箓绛夛細鍏堟竻鐞嗚璁″垝鏃ф槑缁嗗啀灞曞紑鐝粍鎴愬憳锛�
+     *
+     * @param planId 鎺掔彮璁″垝缂栧彿
+     */
+    void generateDetails(Long planId);
+
+    /**
+     * 鑾峰緱鍛樺伐绾ф帓鐝槑缁嗗垎椤�
+     *
+     * @param reqVO 鏌ヨ鏉′欢
+     * @return 鍒嗛〉缁撴灉
+     */
+    PageResult<MesCalScheduleDetailDO> getDetailPage(MesCalScheduleDetailPageReqVO reqVO);
+
+    /**
+     * 鑾峰緱鍛樺伐绾ф帓鐝槑缁嗗垪琛�
+     *
+     * @param reqVO 鏌ヨ鏉′欢
+     * @return 鏄庣粏鍒楄〃
+     */
+    List<MesCalScheduleDetailDO> getDetailList(MesCalScheduleDetailPageReqVO reqVO);
+
+    /**
+     * 鏇存柊鍛樺伐绾ф帓鐝槑缁嗭紙浜哄伐寰皟锛宻ource 缃负 2-浜哄伐璋冩暣锛�
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateDetail(MesCalScheduleDetailSaveReqVO updateReqVO);
+
+    /**
+     * 鍛樺伐鎺掔彮鍚堣鏍¢獙锛堝崟鏃ヨ繛缁伐鏃惰秴闀裤�佺浉閭荤彮娆¢棿浼戞伅涓嶈冻锛�
+     *
+     * @param reqVO 鏍¢獙鏉′欢
+     * @return 鏍¢獙缁撴灉鍒楄〃锛宨tem.issue 涓虹┖琛ㄧず璇ョ彮娆″悎瑙�
+     */
+    List<MesCalScheduleDetailCheckRespVO> checkCompliance(MesCalScheduleDetailCheckReqVO reqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailServiceImpl.java
new file mode 100644
index 0000000..58d1676
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/schedule/MesCalScheduleDetailServiceImpl.java
@@ -0,0 +1,225 @@
+package cn.iocoder.yudao.module.mes.service.cal.schedule;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailCheckReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailCheckRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.schedule.vo.MesCalScheduleDetailSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.schedule.MesCalScheduleDetailDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamMemberDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanShiftMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.schedule.MesCalScheduleDetailMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamMemberMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalScheduleDetailSourceEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_SCHEDULE_DETAIL_GENERATE_TEAM_MEMBER_EMPTY;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_SCHEDULE_DETAIL_NOT_EXISTS;
+
+/**
+ * MES 鍛樺伐绾ф帓鐝槑缁� Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+@Slf4j
+public class MesCalScheduleDetailServiceImpl implements MesCalScheduleDetailService {
+
+    /**
+     * 鍚堣鏍¢獙闃堝�硷細鍗曠彮鐤戜技杩炵画宸ユ椂涓婇檺锛堝皬鏃讹級
+     */
+    private static final long MAX_CONTINUOUS_WORK_HOURS = 12;
+    /**
+     * 鍚堣鏍¢獙闃堝�硷細鐩搁偦鐝鏈�灏戜紤鎭棿闅旓紙灏忔椂锛�
+     */
+    private static final long MIN_REST_HOURS = 8;
+    private static final int MINUTES_PER_DAY = 24 * 60;
+
+    @Resource
+    private MesCalScheduleDetailMapper scheduleDetailMapper;
+    @Resource
+    private MesCalTeamShiftMapper teamShiftMapper;
+    @Resource
+    private MesCalTeamMemberMapper teamMemberMapper;
+    @Resource
+    private MesCalPlanShiftMapper planShiftMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void generateDetails(Long planId) {
+        // 1. 鏌ヨ璇ヨ鍒掍笅鐨勭彮缁勬帓鐝紙鐝粍 脳 鏃ユ湡 脳 鐝锛�
+        List<MesCalTeamShiftDO> teamShifts = teamShiftMapper.selectListByPlanId(planId);
+        if (teamShifts.isEmpty()) {
+            scheduleDetailMapper.deleteByPlanId(planId);
+            throw exception(CAL_SCHEDULE_DETAIL_GENERATE_TEAM_MEMBER_EMPTY);
+        }
+        Set<Long> teamIds = teamShifts.stream().map(MesCalTeamShiftDO::getTeamId).collect(Collectors.toSet());
+        // 2. 鍔犺浇鐝瀹氫箟锛堢彮娆″悕绉�/璧锋鏃堕棿锛変笌鐝粍鎴愬憳
+        Map<Long, MesCalPlanShiftDO> shiftMap = planShiftMapper.selectListByPlanId(planId).stream()
+                .collect(Collectors.toMap(MesCalPlanShiftDO::getId, shift -> shift, (a, b) -> a));
+        List<MesCalTeamMemberDO> members = teamMemberMapper.selectListByTeamIds(teamIds);
+        if (members.isEmpty()) {
+            scheduleDetailMapper.deleteByPlanId(planId);
+            throw exception(CAL_SCHEDULE_DETAIL_GENERATE_TEAM_MEMBER_EMPTY);
+        }
+        Map<Long, List<MesCalTeamMemberDO>> memberMap = members.stream()
+                .collect(Collectors.groupingBy(MesCalTeamMemberDO::getTeamId));
+        // 3. 灞曞紑涓哄憳宸ョ骇鏄庣粏锛堝憳宸� 脳 鏃ユ湡 脳 鐝锛夛紝鍏堟竻鐞嗘棫鏁版嵁淇濊瘉骞傜瓑
+        scheduleDetailMapper.deleteByPlanId(planId);
+        List<MesCalScheduleDetailDO> details = new ArrayList<>();
+        for (MesCalTeamShiftDO teamShift : teamShifts) {
+            MesCalPlanShiftDO shift = shiftMap.get(teamShift.getShiftId());
+            if (shift == null) {
+                continue;
+            }
+            for (MesCalTeamMemberDO member : memberMap.getOrDefault(teamShift.getTeamId(), Collections.emptyList())) {
+                details.add(MesCalScheduleDetailDO.builder()
+                        .planId(planId)
+                        .teamId(teamShift.getTeamId())
+                        .userId(member.getUserId())
+                        .day(teamShift.getDay())
+                        .shiftId(teamShift.getShiftId())
+                        .shiftName(shift.getName())
+                        .startTime(shift.getStartTime())
+                        .endTime(shift.getEndTime())
+                        .source(MesCalScheduleDetailSourceEnum.AUTO.getSource())
+                        .build());
+            }
+        }
+        if (!details.isEmpty()) {
+            scheduleDetailMapper.insertBatch(details);
+        }
+    }
+
+    @Override
+    public PageResult<MesCalScheduleDetailDO> getDetailPage(MesCalScheduleDetailPageReqVO reqVO) {
+        return scheduleDetailMapper.selectPage(reqVO);
+    }
+
+    @Override
+    public List<MesCalScheduleDetailDO> getDetailList(MesCalScheduleDetailPageReqVO reqVO) {
+        return scheduleDetailMapper.selectList(reqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateDetail(MesCalScheduleDetailSaveReqVO updateReqVO) {
+        MesCalScheduleDetailDO detail = scheduleDetailMapper.selectById(updateReqVO.getId());
+        if (detail == null) {
+            throw exception(CAL_SCHEDULE_DETAIL_NOT_EXISTS);
+        }
+        MesCalScheduleDetailDO updateObj = MesCalScheduleDetailDO.builder()
+                .id(updateReqVO.getId())
+                .shiftId(updateReqVO.getShiftId())
+                .shiftName(updateReqVO.getShiftName())
+                .startTime(updateReqVO.getStartTime())
+                .endTime(updateReqVO.getEndTime())
+                .remark(updateReqVO.getRemark())
+                .source(MesCalScheduleDetailSourceEnum.MANUAL.getSource())
+                .build();
+        scheduleDetailMapper.updateById(updateObj);
+    }
+
+    @Override
+    public List<MesCalScheduleDetailCheckRespVO> checkCompliance(MesCalScheduleDetailCheckReqVO reqVO) {
+        List<MesCalScheduleDetailDO> details =
+                scheduleDetailMapper.selectListByUserIdBetween(reqVO.getUserId(), reqVO.getStartDay(), reqVO.getEndDay());
+        List<MesCalScheduleDetailCheckRespVO> result = new ArrayList<>();
+        for (int i = 0; i < details.size(); i++) {
+            MesCalScheduleDetailDO detail = details.get(i);
+            MesCalScheduleDetailCheckRespVO respVO = new MesCalScheduleDetailCheckRespVO();
+            respVO.setDay(detail.getDay());
+            respVO.setShiftName(detail.getShiftName());
+            respVO.setStartTime(detail.getStartTime());
+            respVO.setEndTime(detail.getEndTime());
+            // 鏍¢獙 1锛氬崟鐝枒浼艰繛缁伐鏃惰秴闀�
+            Long duration = shiftDurationMinutes(detail.getStartTime(), detail.getEndTime());
+            if (duration != null && duration > MAX_CONTINUOUS_WORK_HOURS * 60) {
+                respVO.setIssueType(1);
+                respVO.setIssue("鍗曠彮鐤戜技杩炵画宸ユ椂瓒呴暱锛�" + formatMinutes(duration) + "锛夛紝鍙兘杩濆弽杩炵画宸ユ椂涓婇檺");
+            }
+            // 鏍¢獙 2锛氫笌鍓嶄竴鐝闂撮殧浼戞伅涓嶈冻锛堜粎鍦ㄥ悓涓�澶╂垨鐩搁偦澶╂墠姣旇緝锛�
+            if (i > 0) {
+                MesCalScheduleDetailDO prev = details.get(i - 1);
+                Long rest = restMinutesBetween(prev, detail);
+                if (rest != null && rest < MIN_REST_HOURS * 60) {
+                    respVO.setIssueType(2);
+                    respVO.setIssue("涓庡墠涓�涓彮娆¢棿闅斾粎 " + formatMinutes(rest) + "锛屼紤鎭椂闂翠笉瓒�");
+                }
+            }
+            result.add(respVO);
+        }
+        return result;
+    }
+
+    /**
+     * 璁$畻鍗曠彮宸ユ椂锛堝垎閽燂級锛岃法鍗堝鐝鑷姩+24h锛涙椂闂存牸寮忛潪娉曟椂杩斿洖 null
+     */
+    private Long shiftDurationMinutes(String startTime, String endTime) {
+        Integer start = toMinutes(startTime);
+        Integer end = toMinutes(endTime);
+        if (start == null || end == null) {
+            return null;
+        }
+        int duration = end - start;
+        if (duration < 0) {
+            duration += MINUTES_PER_DAY;
+        }
+        return (long) duration;
+    }
+
+    /**
+     * 璁$畻鐩搁偦涓ゅぉ鐝涔嬮棿鐨勪紤鎭棿闅旓紙鍒嗛挓锛夛紱
+     * 鐝鑻ヨ法鍗堝锛岀粨鏉熷垎閽熸暟鎸夎秴杩� MINUTES_PER_DAY 璁★紙鍗� day + 鐝鏃堕暱锛夛紝
+     * 涓嬩竴鐝寮�濮嬪垎閽熸寜鏂扮殑涓�澶╋紙+MINUTES_PER_DAY锛夎銆�
+     */
+    private Long restMinutesBetween(MesCalScheduleDetailDO prev, MesCalScheduleDetailDO next) {
+        Integer prevStart = toMinutes(prev.getStartTime());
+        Integer prevEnd = toMinutes(prev.getEndTime());
+        Integer nextStart = toMinutes(next.getStartTime());
+        if (prevStart == null || prevEnd == null || nextStart == null) {
+            return null;
+        }
+        int prevDuration = prevEnd - prevStart;
+        if (prevDuration < 0) {
+            prevDuration += MINUTES_PER_DAY;
+        }
+        int prevEndMinutes = prevStart + prevDuration; // 鐩稿 prev.day 闆剁偣锛屽彲鑳借秴杩囦竴澶�
+        int nextStartMinutes = MINUTES_PER_DAY + nextStart; // 涓嬩竴鐝鍦ㄦ柊鐨勪竴澶�
+        return (long) (nextStartMinutes - prevEndMinutes);
+    }
+
+    private Integer toMinutes(String time) {
+        if (time == null) {
+            return null;
+        }
+        String[] parts = time.split(":");
+        if (parts.length != 2) {
+            return null;
+        }
+        try {
+            return Integer.parseInt(parts[0].trim()) * 60 + Integer.parseInt(parts[1].trim());
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    private String formatMinutes(long minutes) {
+        long hours = minutes / 60;
+        long mins = minutes % 60;
+        return mins == 0 ? hours + " 灏忔椂" : hours + " 灏忔椂 " + mins + " 鍒嗛挓";
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java
index 35ded24..45568db 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java
@@ -3,30 +3,51 @@
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+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.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemCreateReqDTO;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
+import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum;
+import cn.iocoder.yudao.module.mes.api.item.MesMdItemApi;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.MesDvMachineryImportExcelVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.MesDvMachineryImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.MesDvMachineryPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.MesDvMachinerySaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryTypeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkshopDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
 import cn.iocoder.yudao.module.mes.service.dv.checkplan.MesDvCheckPlanMachineryService;
 import cn.iocoder.yudao.module.mes.service.dv.checkrecord.MesDvCheckRecordService;
 import cn.iocoder.yudao.module.mes.service.dv.maintenrecord.MesDvMaintenRecordService;
 import cn.iocoder.yudao.module.mes.service.dv.repair.MesDvRepairService;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
+import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkshopService;
 import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -43,6 +64,15 @@
 @Service
 @Validated
 public class MesDvMachineryServiceImpl implements MesDvMachineryService {
+
+    /**
+     * 璁惧鑱斿姩鐗╂枡鐨勭被鍨嬬紪鐮侊紙mes_md_item_type / mdm_item_category 涓�滆澶団�濊鐨� code锛�
+     */
+    private static final String EQUIPMENT_ITEM_TYPE_CODE = "EQUIPMENT";
+    /**
+     * 璁惧鑱斿姩鐗╂枡鐨勮閲忓崟浣嶇紪鐮侊紙mes_md_unit_measure / mdm_unit_measure 涓�滃彴鈥濊鐨� code锛�
+     */
+    private static final String EQUIPMENT_UNIT_CODE = "TAI";
 
     @Resource
     private MesDvMachineryMapper machineryMapper;
@@ -71,7 +101,30 @@
     @Lazy
     private MesDvRepairService repairService;
 
+    // ========== 璁惧鐗╂枡鑱斿姩渚濊禆 ==========
+    @Resource
+    private MdmItemApi mdmItemApi;
+    @Resource
+    private MdmUnitMeasureApi mdmUnitMeasureApi;
+    @Resource
+    private MesMdItemApi mesMdItemApi;
+    @Resource
+    @Lazy
+    private MesMdItemTypeService mesMdItemTypeService;
+    @Resource
+    @Lazy
+    private MesMdUnitMeasureService mesMdUnitMeasureService;
+    @Resource
+    private MesWmTransactionService wmTransactionService;
+    @Resource
+    private MesWmWarehouseService wmWarehouseService;
+    @Resource
+    private MesWmWarehouseLocationService wmLocationService;
+    @Resource
+    private MesWmWarehouseAreaService wmAreaService;
+
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createMachinery(MesDvMachinerySaveReqVO createReqVO) {
         // 鏍¢獙璁惧绫诲瀷瀛樺湪
         machineryTypeService.getMachineryType(createReqVO.getMachineryTypeId());
@@ -80,13 +133,20 @@
         // 鏍¢獙缂栫爜鍞竴
         validateMachineryCodeUnique(null, createReqVO.getCode());
 
-        // 鎻掑叆
+        // 鍒涘缓瀵瑰簲 MDM 鐗╂枡骞跺悓姝� MES 鐗╂枡锛堝悓涓�浜嬪姟锛屽け璐ユ暣浣撳洖婊氾級
+        Long itemId = createEquipmentMaterial(createReqVO.getCode(), createReqVO.getName(),
+                createReqVO.getSpecification(), createReqVO.getRemark());
+
+        // 鎻掑叆璁惧鍙拌处
         MesDvMachineryDO machinery = BeanUtils.toBean(createReqVO, MesDvMachineryDO.class);
+        machinery.setItemId(itemId);
         machineryMapper.insert(machinery);
 
         // 鑷姩鐢熸垚鏉$爜
         barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.MACHINERY.getValue(),
                 machinery.getId(), machinery.getCode(), machinery.getName());
+        // 鑷姩鍏ヨ澶囦粨锛堝簱瀛� + 娴佹按锛�
+        createEquipmentStock(itemId, machinery.getId(), machinery.getCode());
         return machinery.getId();
     }
 
@@ -94,6 +154,7 @@
     public void updateMachinery(MesDvMachinerySaveReqVO updateReqVO) {
         // 鏍¢獙瀛樺湪
         validateMachineryExists(updateReqVO.getId());
+        MesDvMachineryDO existMachinery = machineryMapper.selectById(updateReqVO.getId());
         // 鏍¢獙璁惧绫诲瀷瀛樺湪
         machineryTypeService.getMachineryType(updateReqVO.getMachineryTypeId());
         // 鏍¢獙杞﹂棿瀛樺湪
@@ -103,6 +164,8 @@
 
         // 鏇存柊
         MesDvMachineryDO updateObj = BeanUtils.toBean(updateReqVO, MesDvMachineryDO.class);
+        // 鐗╂枡鍏宠仈鐢卞垱寤烘椂鑷姩鐢熸垚锛屾洿鏂颁笉鍏佽娓呯┖锛堝墠绔篃涓嶄紶鍏� itemId锛�
+        updateObj.setItemId(existMachinery.getItemId());
         machineryMapper.updateById(updateObj);
     }
 
@@ -110,6 +173,11 @@
     public void deleteMachinery(Long id) {
         // 鏍¢獙瀛樺湪
         validateMachineryExists(id);
+        // 鏍¢獙宸茬敓鎴愮墿鏂欏苟鍏ヨ澶囦粨锛氶伩鍏嶅簱瀛�/娴佹按澶卞幓涓氬姟涓讳綋锛岀姝㈠垹闄�
+        MesDvMachineryDO machinery = machineryMapper.selectById(id);
+        if (machinery.getItemId() != null) {
+            throw exception(DV_MACHINERY_HAS_ITEM);
+        }
         // 鏍¢獙鍏宠仈鏁版嵁
         if (checkPlanMachineryService.getCheckPlanMachineryCountByMachineryId(id) > 0) {
             throw exception(DV_MACHINERY_HAS_CHECK_PLAN);
@@ -279,10 +347,15 @@
                 machinery.setMachineryTypeId(machineryType.getId());
                 machinery.setWorkshopId(workshop.getId());
                 machinery.setWorkstationId(workstationId);
+                // 鍒涘缓瀵瑰簲 MDM 鐗╂枡骞跺悓姝� MES 鐗╂枡锛堝悓涓�浜嬪姟锛�
+                machinery.setItemId(createEquipmentMaterial(machinery.getCode(), machinery.getName(),
+                        machinery.getSpecification(), machinery.getRemark()));
                 machineryMapper.insert(machinery);
                 // 鑷姩鐢熸垚鏉$爜
                 barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.MACHINERY.getValue(),
                         machinery.getId(), machinery.getCode(), machinery.getName());
+                // 鑷姩鍏ヨ澶囦粨锛堝簱瀛� + 娴佹按锛�
+                createEquipmentStock(machinery.getItemId(), machinery.getId(), machinery.getCode());
                 respVO.getCreateCodes().add(importItem.getCode());
             } else if (updateSupport) {
                 // 3.5.2 鏇存柊
@@ -291,6 +364,8 @@
                 updateObj.setMachineryTypeId(machineryType.getId());
                 updateObj.setWorkshopId(workshop.getId());
                 updateObj.setWorkstationId(workstationId);
+                // 鐗╂枡鍏宠仈鐢卞垱寤烘椂鑷姩鐢熸垚锛屾洿鏂颁笉鍏佽娓呯┖
+                updateObj.setItemId(existMachinery.getItemId());
                 machineryMapper.updateById(updateObj);
                 respVO.getUpdateCodes().add(importItem.getCode());
             } else {
@@ -301,4 +376,77 @@
         return respVO;
     }
 
+    /**
+     * 鍒涘缓璁惧瀵瑰簲鐗╂枡锛氬厛鍦� MDM 寤轰富鏁版嵁鐗╂枡锛屽啀鏄惧紡鍚屾 MES 鐗╂枡锛堜袱濂椾富鏁版嵁 id 瀵归綈锛夈��
+     *
+     * @return MDM 鐗╂枡缂栧彿锛�= MES 鐗╂枡缂栧彿锛�
+     */
+    private Long createEquipmentMaterial(String code, String name, String specification, String remark) {
+        // 1. 瑙f瀽鈥滆澶団�濈被鍨嬶紙MES 渚э級涓庘�滃彴鈥濆崟浣嶏紙MDM 渚� + MES 渚э級锛岀己澶卞垯鎻愮ず涓绘暟鎹湭鍒濆鍖�
+        MesMdItemTypeDO itemType = mesMdItemTypeService.getItemTypeByCode(EQUIPMENT_ITEM_TYPE_CODE);
+        if (itemType == null) {
+            throw exception(DV_MACHINERY_EQUIP_MASTER_MISSING);
+        }
+        if (mesMdUnitMeasureService.getUnitMeasureByCode(EQUIPMENT_UNIT_CODE) == null) {
+            throw exception(DV_MACHINERY_EQUIP_MASTER_MISSING);
+        }
+        CommonResult<MdmUnitMeasureRespDTO> unitResult = mdmUnitMeasureApi.getUnitMeasureByCode(EQUIPMENT_UNIT_CODE);
+        MdmUnitMeasureRespDTO mdmUnit = unitResult.isSuccess() ? unitResult.getData() : null;
+        if (mdmUnit == null) {
+            throw exception(DV_MACHINERY_EQUIP_MASTER_MISSING);
+        }
+
+        // 2. 鍒涘缓 MDM 鐗╂枡锛涘垎绫讳娇鐢ㄢ�滆澶団�濆垎绫伙紙鍏� id 涓� MES 绫诲瀷 id 瀵归綈锛岀敱鍒濆鍖栬剼鏈繚璇侊級
+        MdmItemCreateReqDTO createReqDTO = new MdmItemCreateReqDTO();
+        createReqDTO.setCode(code);
+        createReqDTO.setName(name);
+        createReqDTO.setSpecification(specification);
+        createReqDTO.setRemark(remark);
+        createReqDTO.setCategoryId(itemType.getId());
+        createReqDTO.setUnitMeasureId(mdmUnit.getId());
+        createReqDTO.setItemType(MdmItemTypeEnum.EQUIPMENT.getType());
+        createReqDTO.setStatus(0);
+        createReqDTO.setIsBatchManaged(false);
+        Long mdmItemId = mdmItemApi.createItem(createReqDTO).getCheckedData();
+
+        // 3. 鏄惧紡鍚屾 MES 鐗╂枡锛圡dmItemCreateReqDTO 鏃� syncMes 寮�鍏筹紝涓嶄細鑷姩鍚屾锛�
+        MesMdItemSyncReqDTO syncReqDTO = new MesMdItemSyncReqDTO();
+        syncReqDTO.setCode(code);
+        syncReqDTO.setName(name);
+        syncReqDTO.setSpecification(specification);
+        syncReqDTO.setRemark(remark);
+        syncReqDTO.setUnitCode(EQUIPMENT_UNIT_CODE);
+        syncReqDTO.setItemTypeCode(EQUIPMENT_ITEM_TYPE_CODE);
+        syncReqDTO.setBatchFlag(false);
+        syncReqDTO.setSafeStockFlag(false);
+        syncReqDTO.setStatus(0);
+        syncReqDTO.setMdmItemId(mdmItemId);
+        mesMdItemApi.createItem(syncReqDTO);
+        return mdmItemId;
+    }
+
+    /**
+     * 璁惧鑷姩鍏ヨ澶囦粨锛氬垱寤轰竴鏉″叆搴撴暟閲忎负 1 鐨勫簱瀛樹簨鍔★紝浠撳簱/搴撳尯/搴撲綅鍙栫郴缁熷唴缃澶囦粨銆�
+     * 搴撳瓨琛屻�佹暟閲忎笌娴佹按缁熶竴缁� {@link MesWmTransactionService}锛岀姝㈢洿鎺ュ啓搴撳瓨琛ㄣ��
+     */
+    private void createEquipmentStock(Long itemId, Long machineryId, String machineryCode) {
+        MesWmWarehouseDO warehouse = wmWarehouseService.getWarehouseByCode(MesWmWarehouseDO.EQUIPMENT_WAREHOUSE);
+        MesWmWarehouseLocationDO location = wmLocationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.EQUIPMENT_LOCATION);
+        MesWmWarehouseAreaDO area = wmAreaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.EQUIPMENT_AREA);
+        if (warehouse == null || location == null || area == null) {
+            throw exception(DV_MACHINERY_EQUIP_MASTER_MISSING);
+        }
+        wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO()
+                .setType(MesWmTransactionTypeEnum.IN.getType())
+                .setItemId(itemId)
+                .setQuantity(BigDecimal.ONE)
+                .setWarehouseId(warehouse.getId())
+                .setLocationId(location.getId())
+                .setAreaId(area.getId())
+                .setBizType(MesBizTypeConstants.DV_MACHINERY)
+                .setBizId(machineryId)
+                .setBizCode(machineryCode)
+                .setBizLineId(machineryId));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigService.java
index b45428f..02637ac 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigService.java
@@ -6,6 +6,8 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.barcode.MesWmBarcodeConfigDO;
 import jakarta.validation.Valid;
 
+import java.util.List;
+
 /**
  * MES 鏉$爜閰嶇疆 Service 鎺ュ彛
  *
@@ -67,4 +69,11 @@
      */
     MesWmBarcodeConfigDO validateBarcodeConfigByBizType(Integer bizType);
 
+    /**
+     * 鑾峰彇鍚敤鐘舵�佺殑鏉$爜閰嶇疆鍒楄〃锛堢敤浜庢壂鐮佽В鏋愭椂鍙嶆煡妯℃澘锛�
+     *
+     * @return 鍚敤鐘舵�佺殑鏉$爜閰嶇疆鍒楄〃
+     */
+    List<MesWmBarcodeConfigDO> getEnableBarcodeConfigList();
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigServiceImpl.java
index d97951f..5c58f36 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeConfigServiceImpl.java
@@ -12,6 +12,8 @@
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.List;
+
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
 
@@ -109,4 +111,9 @@
         return config;
     }
 
+    @Override
+    public List<MesWmBarcodeConfigDO> getEnableBarcodeConfigList() {
+        return barcodeConfigMapper.selectEnableList();
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeService.java
index 3004475..25003e5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeService.java
@@ -7,6 +7,7 @@
 import jakarta.validation.Valid;
 
 import java.util.Collection;
+import java.util.Map;
 
 /**
  * MES 鏉$爜娓呭崟 Service 鎺ュ彛
@@ -85,9 +86,32 @@
      *
      * @param bizType 涓氬姟绫诲瀷
      * @param bizCode 涓氬姟缂栫爜
+     * @param bizName 涓氬姟鍚嶇О
      * @return 鐢熸垚鐨勬潯鐮佸唴瀹�
      */
-    String generateBarcodeContent(Integer bizType, String bizCode);
+    String generateBarcodeContent(Integer bizType, String bizCode, String bizName);
+
+    /**
+     * 娓叉煋鏉$爜/浜岀淮鐮佸浘鐗囷紙鏈嶅姟绔� zxing 鎴愬浘锛屽嵆鏃堕瑙堢敤 Base64锛�
+     *
+     * @param content 鏉$爜鍐呭
+     * @param format 鏉$爜鏍煎紡 {@link cn.iocoder.yudao.module.mes.enums.wm.BarcodeFormatEnum}
+     * @param width 鍥剧墖瀹藉害
+     * @param height 鍥剧墖楂樺害
+     * @return PNG 鍥剧墖 Base64 data-URL
+     */
+    String renderBarcodeImage(String content, Integer format, Integer width, Integer height);
+
+    /**
+     * 瑙f瀽鏉$爜鍐呭锛堟湇鍔$鎵爜瑙f瀽锛�
+     *
+     * <p>浼樺厛鎸夋潯鐮佸唴瀹圭簿纭懡涓凡鏈夋潯鐮佽褰曪紝杩斿洖涓氬姟瀵硅薄淇℃伅锛�
+     * 鍚﹀垯閬嶅巻鍚敤鐘舵�佺殑鏉$爜閰嶇疆锛屾寜 contentFormat 妯℃澘鍙嶈В鍑哄悇鍗犱綅绗﹀瓧娈点��
+     *
+     * @param content 鎵弿鍒扮殑鏉$爜鍐呭
+     * @return 瑙f瀽缁撴灉锛坢atched/bizType/bizCode 绛夊瓧娈碉級
+     */
+    Map<String, Object> parseBarcodeContent(String content);
 
     /**
      * 鏍规嵁鏉$爜閰嶇疆缂栧彿鑾峰彇鐩稿叧鑱旂殑鏉$爜鏁伴噺
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeServiceImpl.java
index 2dea524..23bc7d5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/barcode/MesWmBarcodeServiceImpl.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.mes.service.wm.barcode;
 
+import cn.hutool.core.codec.Base64;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
@@ -11,11 +12,26 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.barcode.MesWmBarcodeConfigDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.barcode.MesWmBarcodeDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.barcode.MesWmBarcodeMapper;
+import cn.iocoder.yudao.module.mes.enums.wm.BarcodeFormatEnum;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -27,7 +43,13 @@
  */
 @Service
 @Validated
+@Slf4j
 public class MesWmBarcodeServiceImpl implements MesWmBarcodeService {
+
+    /**
+     * 鍐呭鏍煎紡妯℃澘涓殑鍗犱綅绗﹁瘑鍒鍒�
+     */
+    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{[A-Za-z0-9_]+\\}");
 
     @Resource
     private MesWmBarcodeMapper barcodeMapper;
@@ -43,7 +65,8 @@
 
         // 2. 鐢熸垚鏉$爜鍐呭锛屽苟鏍¢獙鍞竴鎬�
         String content = generateAndValidateContent(createReqVO.getId(),
-                createReqVO.getContent(), config.getContentFormat(), createReqVO.getBizCode());
+                createReqVO.getContent(), config.getContentFormat(), createReqVO.getBizCode(),
+                createReqVO.getBizName());
 
         // 3. 淇濆瓨鏉$爜璁板綍
         MesWmBarcodeDO barcode = BeanUtils.toBean(createReqVO, MesWmBarcodeDO.class).setContent(content)
@@ -53,7 +76,7 @@
     }
 
     @Override
-    public String generateBarcodeContent(Integer bizType, String bizCode) {
+    public String generateBarcodeContent(Integer bizType, String bizCode, String bizName) {
         // 1.1 鏍¢獙鍙傛暟
         if (bizType == null) {
             throw exception(BARCODE_BIZ_TYPE_NOT_EXISTS);
@@ -65,7 +88,7 @@
         MesWmBarcodeConfigDO config = barcodeConfigService.validateBarcodeConfigByBizType(bizType);
 
         // 2. 鐢熸垚鏉$爜鍐呭
-        return generateBarcodeContent(config.getContentFormat(), bizCode);
+        return generateBarcodeContent(config.getContentFormat(), bizCode, bizName);
     }
 
     @Override
@@ -78,7 +101,8 @@
 
         // 2. 鐢熸垚鏉$爜鍐呭锛屽苟鏍¢獙鍞竴鎬�
         String content = generateAndValidateContent(updateReqVO.getId(),
-                updateReqVO.getContent(), config.getContentFormat(), updateReqVO.getBizCode());
+                updateReqVO.getContent(), config.getContentFormat(), updateReqVO.getBizCode(),
+                updateReqVO.getBizName());
 
         // 3. 鏇存柊锛堝埛鏂� configId 鍜� format锛岀‘淇濅笌鏈�鏂伴厤缃竴鑷达級
         MesWmBarcodeDO updateObj = BeanUtils.toBean(updateReqVO, MesWmBarcodeDO.class)
@@ -131,10 +155,10 @@
      * @param bizCode 涓氬姟缂栫爜
      * @return 鏈�缁堟潯鐮佸唴瀹�
      */
-    private String generateAndValidateContent(Long id, String content, String contentFormat, String bizCode) {
+    private String generateAndValidateContent(Long id, String content, String contentFormat, String bizCode, String bizName) {
         // 1. 濡傛灉鍓嶇鏈紶閫掑唴瀹癸紝鍒欒嚜鍔ㄧ敓鎴�
         if (StrUtil.isBlank(content)) {
-            content = generateBarcodeContent(contentFormat, bizCode);
+            content = generateBarcodeContent(contentFormat, bizCode, bizName);
         }
         // 2. 鏍¢獙鏉$爜鍐呭鍞竴鎬э紙鎺掗櫎鑷韩锛�
         if (StrUtil.isNotBlank(content)) {
@@ -151,13 +175,144 @@
      *
      * @param contentFormat 鍐呭鏍煎紡妯℃澘
      * @param bizCode 涓氬姟缂栫爜
+     * @param bizName 涓氬姟鍚嶇О
      * @return 鏉$爜鍐呭
      */
-    private String generateBarcodeContent(String contentFormat, String bizCode) {
+    private String generateBarcodeContent(String contentFormat, String bizCode, String bizName) {
         if (StrUtil.isBlank(contentFormat)) {
             return bizCode;
         }
-        return contentFormat.replace(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_CODE, bizCode);
+        // 1. 鏇挎崲宸叉敮鎸佺殑鍗犱綅绗�
+        String content = contentFormat
+                .replace(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_CODE, StrUtil.nullToEmpty(bizCode))
+                .replace(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_NAME, StrUtil.nullToEmpty(bizName));
+        // 2. 娓呯┖妯℃澘涓叾瀹冩湭濉厖鐨勬湭鐭ュ崰浣嶇锛岄伩鍏� {XXX} 娈嬬暀褰卞搷鎵爜
+        return PLACEHOLDER_PATTERN.matcher(content).replaceAll("");
+    }
+
+    @Override
+    public String renderBarcodeImage(String content, Integer format, Integer width, Integer height) {
+        if (StrUtil.isBlank(content)) {
+            throw exception(BARCODE_CONTENT_EMPTY);
+        }
+        int w = width != null && width > 0 ? width : 200;
+        int h = height != null && height > 0 ? height : 200;
+        try {
+            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, toZxingFormat(format), w, h);
+            BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            ImageIO.write(image, "png", out);
+            return "data:image/png;base64," + Base64.encode(out.toByteArray());
+        } catch (Exception e) {
+            log.error("[renderBarcodeImage] 鏉$爜娓叉煋澶辫触锛宑ontent={}, format={}", content, format, e);
+            throw new RuntimeException("鏉$爜娓叉煋澶辫触");
+        }
+    }
+
+    /**
+     * 灏嗛」鐩潯鐮佹牸寮忔灇涓惧�兼槧灏勪负 zxing 鏉$爜鏍煎紡
+     *
+     * @param format 椤圭洰鏉$爜鏍煎紡 {@link BarcodeFormatEnum}
+     * @return zxing 鏉$爜鏍煎紡
+     */
+    private BarcodeFormat toZxingFormat(Integer format) {
+        if (format == null) {
+            return BarcodeFormat.CODE_128;
+        }
+        switch (format) {
+            case 1: // BarcodeFormatEnum.QR_CODE
+                return BarcodeFormat.QR_CODE;
+            case 2: // BarcodeFormatEnum.EAN13
+                return BarcodeFormat.EAN_13;
+            case 3: // BarcodeFormatEnum.CODE39
+                return BarcodeFormat.CODE_39;
+            case 4: // BarcodeFormatEnum.UPC_A
+                return BarcodeFormat.UPC_A;
+            default:
+                throw exception(BARCODE_FORMAT_INVALID);
+        }
+    }
+
+    @Override
+    public Map<String, Object> parseBarcodeContent(String content) {
+        if (StrUtil.isBlank(content)) {
+            throw exception(BARCODE_CONTENT_EMPTY);
+        }
+        // 1. 浼樺厛鎸夋潯鐮佸唴瀹圭簿纭懡涓凡鏈夋潯鐮佽褰�
+        MesWmBarcodeDO barcode = barcodeMapper.selectByContent(content);
+        if (barcode != null) {
+            Map<String, Object> result = new LinkedHashMap<>();
+            result.put("matched", true);
+            result.put("barcodeId", barcode.getId());
+            result.put("configId", barcode.getConfigId());
+            result.put("format", barcode.getFormat());
+            result.put("bizType", barcode.getBizType());
+            result.put("bizId", barcode.getBizId());
+            result.put("bizCode", barcode.getBizCode());
+            result.put("bizName", barcode.getBizName());
+            result.put("content", barcode.getContent());
+            return result;
+        }
+        // 2. 鏈懡涓椂锛岄亶鍘嗗惎鐢ㄧ姸鎬佺殑閰嶇疆鎸夊唴瀹规牸寮忔ā鏉垮弽瑙�
+        List<MesWmBarcodeConfigDO> configs = barcodeConfigService.getEnableBarcodeConfigList();
+        for (MesWmBarcodeConfigDO config : configs) {
+            Map<String, Object> fields = matchByTemplate(config.getContentFormat(), content);
+            if (fields != null) {
+                Map<String, Object> result = new LinkedHashMap<>();
+                result.put("matched", false);
+                result.put("configId", config.getId());
+                result.put("format", config.getFormat());
+                result.put("bizType", config.getBizType());
+                result.put("bizCode", fields.get(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_CODE));
+                result.put("bizName", fields.get(MesWmBarcodeConfigDO.PLACEHOLDER_BUSINESS_NAME));
+                result.put("fields", fields);
+                return result;
+            }
+        }
+        // 3. 鍧囨湭鍛戒腑
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("matched", false);
+        result.put("content", content);
+        return result;
+    }
+
+    /**
+     * 鎸夊唴瀹规牸寮忔ā鏉垮弽瑙f潯鐮佸唴瀹�
+     *
+     * @param contentFormat 鍐呭鏍煎紡妯℃澘
+     * @param content 寰呰В鏋愮殑鏉$爜鍐呭
+     * @return 鍗犱綅绗� -> 瀹為檯鍊肩殑鏄犲皠锛涗笉鍖归厤杩斿洖 null
+     */
+    private Map<String, Object> matchByTemplate(String contentFormat, String content) {
+        if (StrUtil.isBlank(contentFormat) || !contentFormat.contains("{")) {
+            return null;
+        }
+        // 1. 灏嗘ā鏉胯浆涓烘鍒欙細瀛楅潰閲忚浆涔夛紝鍗犱綅绗﹁浆涓烘崟鑾风粍
+        List<String> placeholders = new ArrayList<>();
+        StringBuilder regexSb = new StringBuilder();
+        Matcher matcher = PLACEHOLDER_PATTERN.matcher(contentFormat);
+        int lastEnd = 0;
+        while (matcher.find()) {
+            regexSb.append(Pattern.quote(contentFormat.substring(lastEnd, matcher.start())));
+            placeholders.add(matcher.group());
+            regexSb.append("(.+?)");
+            lastEnd = matcher.end();
+        }
+        if (placeholders.isEmpty()) {
+            return null;
+        }
+        regexSb.append(Pattern.quote(contentFormat.substring(lastEnd)));
+
+        // 2. 鍏ㄩ噺鍖归厤骞舵彁鍙栧悇鍗犱綅绗﹀��
+        Matcher contentMatcher = Pattern.compile(regexSb.toString()).matcher(content);
+        if (!contentMatcher.matches()) {
+            return null;
+        }
+        Map<String, Object> fields = new LinkedHashMap<>();
+        for (int i = 0; i < placeholders.size(); i++) {
+            fields.put(placeholders.get(i), contentMatcher.group(i + 1));
+        }
+        return fields;
     }
 
     @Override
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineService.java
new file mode 100644
index 0000000..5ffed52
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineService.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line.MesWmEquipmentReceiptLinePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line.MesWmEquipmentReceiptLineSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptLineDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * MES 璁惧鍏ュ簱鍗曡 Service 鎺ュ彛
+ */
+public interface MesWmEquipmentReceiptLineService {
+
+    /**
+     * 鍒涘缓璁惧鍏ュ簱鍗曡
+     */
+    Long createEquipmentReceiptLine(@Valid MesWmEquipmentReceiptLineSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊璁惧鍏ュ簱鍗曡
+     */
+    void updateEquipmentReceiptLine(@Valid MesWmEquipmentReceiptLineSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎璁惧鍏ュ簱鍗曡
+     */
+    void deleteEquipmentReceiptLine(Long id);
+
+    /**
+     * 鎸夊叆搴撳崟缂栧彿鍒犻櫎琛岋紙绾ц仈鍒犻櫎锛�
+     */
+    void deleteByReceiptId(Long receiptId);
+
+    /**
+     * 鑾峰緱璁惧鍏ュ簱鍗曡
+     */
+    MesWmEquipmentReceiptLineDO getEquipmentReceiptLine(Long id);
+
+    /**
+     * 鎸夊叆搴撳崟缂栧彿鑾峰緱琛屽垪琛�
+     */
+    List<MesWmEquipmentReceiptLineDO> getEquipmentReceiptLineListByReceiptId(Long receiptId);
+
+    /**
+     * 鑾峰緱璁惧鍏ュ簱鍗曡鍒嗛〉
+     */
+    PageResult<MesWmEquipmentReceiptLineDO> getEquipmentReceiptLinePage(MesWmEquipmentReceiptLinePageReqVO pageReqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineServiceImpl.java
new file mode 100644
index 0000000..4db772f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptLineServiceImpl.java
@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt;
+
+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.module.mes.controller.admin.wm.equipmentreceipt.vo.line.MesWmEquipmentReceiptLinePageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.line.MesWmEquipmentReceiptLineSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptLineDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.equipmentreceipt.MesWmEquipmentReceiptLineMapper;
+import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+
+/**
+ * MES 璁惧鍏ュ簱鍗曡 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+public class MesWmEquipmentReceiptLineServiceImpl implements MesWmEquipmentReceiptLineService {
+
+    @Resource
+    private MesWmEquipmentReceiptLineMapper equipmentReceiptLineMapper;
+    @Resource
+    private MesDvMachineryService machineryService;
+    @Resource
+    @Lazy
+    private MesWmEquipmentReceiptService equipmentReceiptService;
+
+    @Override
+    public Long createEquipmentReceiptLine(MesWmEquipmentReceiptLineSaveReqVO createReqVO) {
+        // 鏍¢獙鍏ュ簱鍗曞彲缂栬緫
+        equipmentReceiptService.validateEquipmentReceiptEditable(createReqVO.getReceiptId());
+        // 鏍¢獙璁惧瀛樺湪涓斿凡鍏宠仈鐗╂枡
+        validateMachineryHasItem(createReqVO.getMachineryId());
+        // 鏍¢獙鏁伴噺
+        validateQuantity(createReqVO.getQuantity());
+
+        MesWmEquipmentReceiptLineDO line = BeanUtils.toBean(createReqVO, MesWmEquipmentReceiptLineDO.class);
+        equipmentReceiptLineMapper.insert(line);
+        return line.getId();
+    }
+
+    @Override
+    public void updateEquipmentReceiptLine(MesWmEquipmentReceiptLineSaveReqVO updateReqVO) {
+        // 鏍¢獙琛屽瓨鍦�
+        validateEquipmentReceiptLineExists(updateReqVO.getId());
+        // 鏍¢獙鍏ュ簱鍗曞彲缂栬緫
+        equipmentReceiptService.validateEquipmentReceiptEditable(updateReqVO.getReceiptId());
+        // 鏍¢獙璁惧瀛樺湪涓斿凡鍏宠仈鐗╂枡
+        validateMachineryHasItem(updateReqVO.getMachineryId());
+        // 鏍¢獙鏁伴噺
+        validateQuantity(updateReqVO.getQuantity());
+
+        MesWmEquipmentReceiptLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmEquipmentReceiptLineDO.class);
+        equipmentReceiptLineMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteEquipmentReceiptLine(Long id) {
+        // 鏍¢獙琛屽瓨鍦�
+        MesWmEquipmentReceiptLineDO line = validateEquipmentReceiptLineExists(id);
+        // 鏍¢獙鍏ュ簱鍗曞彲缂栬緫
+        equipmentReceiptService.validateEquipmentReceiptEditable(line.getReceiptId());
+
+        equipmentReceiptLineMapper.deleteById(id);
+    }
+
+    @Override
+    public void deleteByReceiptId(Long receiptId) {
+        equipmentReceiptLineMapper.delete(MesWmEquipmentReceiptLineDO::getReceiptId, receiptId);
+    }
+
+    @Override
+    public MesWmEquipmentReceiptLineDO getEquipmentReceiptLine(Long id) {
+        return equipmentReceiptLineMapper.selectById(id);
+    }
+
+    @Override
+    public List<MesWmEquipmentReceiptLineDO> getEquipmentReceiptLineListByReceiptId(Long receiptId) {
+        return equipmentReceiptLineMapper.selectListByReceiptId(receiptId);
+    }
+
+    @Override
+    public PageResult<MesWmEquipmentReceiptLineDO> getEquipmentReceiptLinePage(MesWmEquipmentReceiptLinePageReqVO pageReqVO) {
+        return equipmentReceiptLineMapper.selectPage(pageReqVO);
+    }
+
+    private MesWmEquipmentReceiptLineDO validateEquipmentReceiptLineExists(Long id) {
+        MesWmEquipmentReceiptLineDO line = equipmentReceiptLineMapper.selectById(id);
+        if (line == null) {
+            throw exception(WM_EQUIP_RECEIPT_LINE_NOT_EXISTS);
+        }
+        return line;
+    }
+
+    private void validateMachineryHasItem(Long machineryId) {
+        machineryService.validateMachineryExists(machineryId);
+        MesDvMachineryDO machinery = machineryService.getMachinery(machineryId);
+        if (machinery == null || machinery.getItemId() == null) {
+            throw exception(WM_EQUIP_RECEIPT_MACHINERY_NOT_VALID);
+        }
+    }
+
+    private void validateQuantity(BigDecimal quantity) {
+        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+            throw exception(WM_EQUIP_RECEIPT_QUANTITY_INVALID);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptService.java
new file mode 100644
index 0000000..1c8a618
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptService.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.MesWmEquipmentReceiptPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.MesWmEquipmentReceiptSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+public interface MesWmEquipmentReceiptService {
+
+    String APPROVE_CATEGORY_CODE = "equipment_receipt_approve";
+
+    Long createEquipmentReceipt(@Valid MesWmEquipmentReceiptSaveReqVO createReqVO);
+
+    void updateEquipmentReceipt(@Valid MesWmEquipmentReceiptSaveReqVO updateReqVO);
+
+    void deleteEquipmentReceipt(Long id);
+
+    MesWmEquipmentReceiptDO getEquipmentReceipt(Long id);
+
+    PageResult<MesWmEquipmentReceiptDO> getEquipmentReceiptPage(MesWmEquipmentReceiptPageReqVO pageReqVO);
+
+    void submitEquipmentReceiptApproval(Long id, String processDefinitionKey);
+
+    void updateEquipmentReceiptAuditStatus(Long id, Integer bpmResult);
+
+    List<Map<String, Object>> getEquipmentReceiptApprovalProcessDefinitionList();
+
+    void finishEquipmentReceipt(Long id);
+
+    void cancelEquipmentReceipt(Long id);
+
+    MesWmEquipmentReceiptDO validateEquipmentReceiptExists(Long id);
+
+    void validateEquipmentReceiptEditable(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptServiceImpl.java
new file mode 100644
index 0000000..4b6c700
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptServiceImpl.java
@@ -0,0 +1,290 @@
+package cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt;
+
+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.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.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.MesWmEquipmentReceiptPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.MesWmEquipmentReceiptSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptLineDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.equipmentreceipt.MesWmEquipmentReceiptMapper;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmEquipmentReceiptStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+
+/**
+ * MES 璁惧鍏ュ簱鍗� Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class MesWmEquipmentReceiptServiceImpl implements MesWmEquipmentReceiptService {
+
+    @Resource
+    private MesWmEquipmentReceiptMapper equipmentReceiptMapper;
+    @Resource
+    @Lazy
+    private MesWmEquipmentReceiptLineService equipmentReceiptLineService;
+    @Resource
+    private MesWmTransactionService wmTransactionService;
+    @Resource
+    private MesWmWarehouseService wmWarehouseService;
+    @Resource
+    private MesWmWarehouseLocationService wmLocationService;
+    @Resource
+    private MesWmWarehouseAreaService wmAreaService;
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Override
+    public Long createEquipmentReceipt(MesWmEquipmentReceiptSaveReqVO createReqVO) {
+        // 鏍¢獙缂栫爜鍞竴
+        validateCodeUnique(null, createReqVO.getCode());
+
+        MesWmEquipmentReceiptDO receipt = BeanUtils.toBean(createReqVO, MesWmEquipmentReceiptDO.class);
+        receipt.setStatus(MesWmEquipmentReceiptStatusEnum.PREPARE.getStatus());
+        equipmentReceiptMapper.insert(receipt);
+        return receipt.getId();
+    }
+
+    @Override
+    public void updateEquipmentReceipt(MesWmEquipmentReceiptSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        validateEquipmentReceiptExistsAndPrepare(updateReqVO.getId());
+        // 鏍¢獙缂栫爜鍞竴
+        validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
+
+        MesWmEquipmentReceiptDO updateObj = BeanUtils.toBean(updateReqVO, MesWmEquipmentReceiptDO.class);
+        equipmentReceiptMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteEquipmentReceipt(Long id) {
+        // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        validateEquipmentReceiptExistsAndPrepare(id);
+
+        // 绾ц仈鍒犻櫎琛�
+        equipmentReceiptLineService.deleteByReceiptId(id);
+        // 鍒犻櫎涓昏〃
+        equipmentReceiptMapper.deleteById(id);
+    }
+
+    @Override
+    public MesWmEquipmentReceiptDO getEquipmentReceipt(Long id) {
+        return equipmentReceiptMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MesWmEquipmentReceiptDO> getEquipmentReceiptPage(MesWmEquipmentReceiptPageReqVO pageReqVO) {
+        return equipmentReceiptMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitEquipmentReceiptApproval(Long id, String processDefinitionKey) {
+        // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
+        validateEquipmentReceiptExistsAndPrepare(id);
+        // 鏍¢獙鑷冲皯鏈変竴鏉¤
+        List<MesWmEquipmentReceiptLineDO> lines = equipmentReceiptLineService.getEquipmentReceiptLineListByReceiptId(id);
+        if (CollUtil.isEmpty(lines)) {
+            throw exception(WM_EQUIP_RECEIPT_NO_LINE);
+        }
+        // 鍒涘缓 BPM 娴佺▼瀹炰緥
+        String processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+        // 鎻愪氦瀹℃壒锛堣崏绋� 鈫� 瀹℃壒涓級
+        equipmentReceiptMapper.updateById(new MesWmEquipmentReceiptDO()
+                .setId(id)
+                .setStatus(MesWmEquipmentReceiptStatusEnum.APPROVING.getStatus())
+                .setProcessInstanceId(processInstanceId));
+    }
+
+    @Override
+    public List<Map<String, Object>> getEquipmentReceiptApprovalProcessDefinitionList() {
+        return getProcessDefinitionListByCategory(APPROVE_CATEGORY_CODE);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateEquipmentReceiptAuditStatus(Long id, Integer bpmResult) {
+        // 1. 鏍¢獙瀛樺湪
+        MesWmEquipmentReceiptDO receipt = validateEquipmentReceiptExists(id);
+        if (!MesWmEquipmentReceiptStatusEnum.APPROVING.getStatus().equals(receipt.getStatus())) {
+            log.warn("[updateEquipmentReceiptAuditStatus] 璁惧鍏ュ簱鍗�({}) 涓嶅浜庡鎵逛腑鐘舵��", id);
+            return;
+        }
+        // 2. 鏍规嵁瀹℃壒缁撴灉鏇存柊鐘舵��
+        Integer newStatus = convertBpmResultToStatus(bpmResult);
+        if (newStatus != null) {
+            equipmentReceiptMapper.updateById(new MesWmEquipmentReceiptDO().setId(id).setStatus(newStatus));
+        }
+    }
+
+    private Integer convertBpmResultToStatus(Integer bpmResult) {
+        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            return MesWmEquipmentReceiptStatusEnum.APPROVED.getStatus();
+        }
+        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            return MesWmEquipmentReceiptStatusEnum.PREPARE.getStatus();
+        }
+        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            return MesWmEquipmentReceiptStatusEnum.CANCELED.getStatus();
+        }
+        return null;
+    }
+
+    private List<Map<String, Object>> getProcessDefinitionListByCategory(String categoryCode) {
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(categoryCode);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            return Collections.emptyList();
+        }
+        // 杩囨护婵�娲荤姸鎬侊紝淇濈暀鏈�鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
+            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
+            if (pd == null || pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+        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());
+            result.add(item);
+        }
+        return result;
+    }
+
+    private Long getCurrentUserId() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null && authentication.getPrincipal() instanceof Long) {
+            return (Long) authentication.getPrincipal();
+        }
+        return 1L; // 榛樿鐢ㄦ埛
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void finishEquipmentReceipt(Long id) {
+        // 鏍¢獙瀛樺湪
+        MesWmEquipmentReceiptDO receipt = validateEquipmentReceiptExists(id);
+        if (ObjUtil.notEqual(MesWmEquipmentReceiptStatusEnum.APPROVED.getStatus(), receipt.getStatus())) {
+            throw exception(WM_EQUIP_RECEIPT_STATUS_NOT_APPROVED);
+        }
+
+        // 2. 閬嶅巻鎵�鏈夎锛屽浐瀹氳澶囦粨涓夊厓缁勶紝鍒涘缓 IN 搴撳瓨浜嬪姟
+        createEquipmentStockTransaction(receipt);
+
+        // 3. 鎵ц鍏ュ簱锛堝凡瀹℃壒 鈫� 宸插畬鎴愶級
+        equipmentReceiptMapper.updateById(new MesWmEquipmentReceiptDO()
+                .setId(id).setStatus(MesWmEquipmentReceiptStatusEnum.FINISHED.getStatus()));
+    }
+
+    private void createEquipmentStockTransaction(MesWmEquipmentReceiptDO receipt) {
+        // 鍥哄畾浣跨敤璁惧浠撲笁鍏冪粍
+        MesWmWarehouseDO warehouse = wmWarehouseService.getWarehouseByCode(MesWmWarehouseDO.EQUIPMENT_WAREHOUSE);
+        MesWmWarehouseLocationDO location = wmLocationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.EQUIPMENT_LOCATION);
+        MesWmWarehouseAreaDO area = wmAreaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.EQUIPMENT_AREA);
+
+        List<MesWmEquipmentReceiptLineDO> lines = equipmentReceiptLineService.getEquipmentReceiptLineListByReceiptId(receipt.getId());
+        wmTransactionService.createTransactionList(convertList(lines, line -> new MesWmTransactionSaveReqDTO()
+                .setType(MesWmTransactionTypeEnum.IN.getType())
+                .setItemId(line.getItemId())
+                .setQuantity(line.getQuantity() == null ? BigDecimal.ONE : line.getQuantity())
+                .setWarehouseId(warehouse.getId()).setLocationId(location.getId()).setAreaId(area.getId())
+                .setBizType(MesBizTypeConstants.DV_EQUIPMENT_RECEIPT).setBizId(receipt.getId())
+                .setBizCode(receipt.getCode()).setBizLineId(line.getId())));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelEquipmentReceipt(Long id) {
+        // 鏍¢獙瀛樺湪
+        MesWmEquipmentReceiptDO receipt = validateEquipmentReceiptExists(id);
+
+        // 宸插畬鎴愬拰宸插彇娑堢姸鎬佷笉鍏佽鍙栨秷
+        if (ObjUtil.equal(MesWmEquipmentReceiptStatusEnum.FINISHED.getStatus(), receipt.getStatus())
+                || ObjUtil.equal(MesWmEquipmentReceiptStatusEnum.CANCELED.getStatus(), receipt.getStatus())) {
+            throw exception(WM_EQUIP_RECEIPT_CANCEL_NOT_ALLOWED);
+        }
+
+        // 鍙栨秷
+        equipmentReceiptMapper.updateById(new MesWmEquipmentReceiptDO()
+                .setId(id).setStatus(MesWmEquipmentReceiptStatusEnum.CANCELED.getStatus()));
+    }
+
+    @Override
+    public MesWmEquipmentReceiptDO validateEquipmentReceiptExists(Long id) {
+        MesWmEquipmentReceiptDO receipt = equipmentReceiptMapper.selectById(id);
+        if (receipt == null) {
+            throw exception(WM_EQUIP_RECEIPT_NOT_EXISTS);
+        }
+        return receipt;
+    }
+
+    private void validateCodeUnique(Long id, String code) {
+        MesWmEquipmentReceiptDO receipt = equipmentReceiptMapper.selectByCode(code);
+        if (receipt == null) {
+            return;
+        }
+        if (id == null || ObjUtil.notEqual(id, receipt.getId())) {
+            throw exception(WM_EQUIP_RECEIPT_CODE_DUPLICATE);
+        }
+    }
+
+    private MesWmEquipmentReceiptDO validateEquipmentReceiptExistsAndPrepare(Long id) {
+        MesWmEquipmentReceiptDO receipt = validateEquipmentReceiptExists(id);
+        if (ObjUtil.notEqual(MesWmEquipmentReceiptStatusEnum.PREPARE.getStatus(), receipt.getStatus())) {
+            throw exception(WM_EQUIP_RECEIPT_STATUS_NOT_PREPARE);
+        }
+        return receipt;
+    }
+
+    @Override
+    public void validateEquipmentReceiptEditable(Long id) {
+        validateEquipmentReceiptExistsAndPrepare(id);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/listener/MesWmEquipmentReceiptStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/listener/MesWmEquipmentReceiptStatusListener.java
new file mode 100644
index 0000000..1112504
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/listener/MesWmEquipmentReceiptStatusListener.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.mes.service.wm.equipmentreceipt.MesWmEquipmentReceiptService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 璁惧鍏ュ簱鍗� BPM 瀹℃壒鐘舵�佺洃鍚櫒
+ */
+@Component
+@Slf4j
+public class MesWmEquipmentReceiptStatusListener
+        extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
+
+    @Resource
+    private MesWmEquipmentReceiptService equipmentReceiptService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return MesWmEquipmentReceiptService.APPROVE_CATEGORY_CODE;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        Long id;
+        try {
+            id = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勮澶囧叆搴撳崟缂栧彿", event.getBusinessKey());
+            return;
+        }
+        log.info("[onEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宔quipmentReceiptId={}, status={}", id, event.getStatus());
+        try {
+            equipmentReceiptService.updateEquipmentReceiptAuditStatus(id, event.getStatus());
+            log.info("[onEvent] 璁惧鍏ュ簱鍗�({}) 瀹℃壒鐘舵�佹洿鏂板畬鎴�", id);
+        } catch (Exception e) {
+            log.error("[onEvent] 鏇存柊璁惧鍏ュ簱鍗曞鎵圭姸鎬佸け璐ワ紝equipmentReceiptId={}", id, e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnService.java
index 617954f..387fbf9 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnService.java
@@ -3,6 +3,10 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGroupRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnDetailRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnOutStockReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO;
 
@@ -45,4 +49,34 @@
      */
     void deleteSnByUuid(String uuid);
 
-}
+    /**
+     * 鍏ュ簱鐧昏锛氭寜 SN 寤虹珛搴撳瓨浜嬪姟骞跺叆鍙帮紙鍗曚欢鍏ュ簱锛�
+     *
+     * @param reqVO 鍏ュ簱鐧昏璇锋眰
+     */
+    void registerSnInStock(MesWmSnInStockReqVO reqVO);
+
+    /**
+     * 鍑哄簱鏍搁攢锛氭寜 SN 寤虹珛鍑哄簱浜嬪姟骞舵牳閿�锛堝崟浠跺嚭搴擄級
+     *
+     * @param reqVO 鍑哄簱鏍搁攢璇锋眰
+     */
+    void outboundSn(MesWmSnOutStockReqVO reqVO);
+
+    /**
+     * 鑾峰緱 SN 鍗曚欢鏄庣粏锛堢姸鎬併�佸湪搴撲綅缃�佹渶杩戜竴绗斿簱瀛樹簨鍔★級
+     *
+     * @param snCode SN 鐮�
+     * @return SN 鍗曚欢鏄庣粏
+     */
+    MesWmSnDetailRespVO getSnDetail(String snCode);
+
+    /**
+     * 鎸夌姸鎬�/鐗╂枡/搴撲綅绛夌淮搴﹀垎椤垫煡璇� SN 鐮侊紙鍦ㄥ簱鏍稿锛�
+     *
+     * @param reqVO 鍒嗛〉鏌ヨ
+     * @return SN 鐮佸垎椤�
+     */
+    PageResult<MesWmSnDO> getSnInStockPage(MesWmSnInStockPageReqVO reqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java
index e98ef33..2bed5cd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java
@@ -2,28 +2,48 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.IdUtil;
+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.module.mes.controller.admin.wm.sn.vo.MesWmSnDetailRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGroupRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnOutStockReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.sn.MesWmSnMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmSnStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
 import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
+import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
 
 /**
  * MES SN 鐮� Service 瀹炵幇绫�
@@ -37,6 +57,8 @@
 
     @Resource
     private MesWmSnMapper snMapper;
+    @Resource
+    private MesWmTransactionMapper transactionMapper;
 
     @Resource
     private MesMdAutoCodeRecordService autoCodeRecordService;
@@ -46,6 +68,14 @@
     private MesProWorkOrderService workOrderService;
     @Resource
     private MesWmBarcodeService barcodeService;
+    @Resource
+    private MesWmTransactionService transactionService;
+    @Resource
+    private MesWmWarehouseService warehouseService;
+    @Resource
+    private MesWmWarehouseLocationService locationService;
+    @Resource
+    private MesWmWarehouseAreaService areaService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -99,4 +129,100 @@
         snMapper.deleteByUuid(uuid);
     }
 
-}
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void registerSnInStock(MesWmSnInStockReqVO reqVO) {
+        // 1. 鏍¢獙 SN 瀛樺湪涓旂墿鏂欏尮閰�
+        MesWmSnDO sn = validateSnExists(reqVO.getCode());
+        validateSnItemMatch(sn, reqVO.getItemId());
+        // 2. 鏍¢獙鐗╂枡鍚敤搴忓垪鍙风鐞�
+        MesMdmItemDO item = mdmItemService.validateItemExists(reqVO.getItemId());
+        if (!Boolean.TRUE.equals(item.getIsSerialManaged())) {
+            throw exception(WM_SN_ITEM_NOT_SERIAL);
+        }
+        // 3. 鐘舵�佹牎楠岋細宸插湪搴撳垯鎷掔粷閲嶅鐧昏
+        if (ObjUtil.equal(sn.getStatus(), MesWmSnStatusEnum.IN_STOCK.getStatus())) {
+            throw exception(WM_SN_ALREADY_IN_STOCK);
+        }
+        // 4. 鏍¢獙搴撳瓨浣嶇疆瀛樺湪锛堝喕缁撲笌娣锋斁瑙勫垯鐢变簨鍔″唴閮ㄥ厹搴曪級
+        warehouseService.validateWarehouseExists(reqVO.getWarehouseId());
+        locationService.validateWarehouseLocationExists(reqVO.getLocationId());
+        areaService.validateWarehouseAreaExists(reqVO.getAreaId());
+        // 5. 鍒涘缓鍗曚欢鍏ュ簱浜嬪姟锛�+1锛�
+        transactionService.createTransaction(new MesWmTransactionSaveReqDTO()
+                .setType(MesWmTransactionTypeEnum.IN.getType())
+                .setBizType(MesBizTypeConstants.WM_SN)
+                .setBizId(sn.getId()).setBizCode(sn.getCode()).setBizLineId(sn.getId())
+                .setItemId(sn.getItemId()).setQuantity(BigDecimal.ONE)
+                .setBatchId(sn.getBatchId()).setBatchCode(sn.getBatchCode())
+                .setSnCode(sn.getCode())
+                .setWarehouseId(reqVO.getWarehouseId()).setLocationId(reqVO.getLocationId()).setAreaId(reqVO.getAreaId()));
+        // 6. 鏇存柊 SN锛氬湪搴� + 璁板綍搴撲綅
+        snMapper.updateById(new MesWmSnDO().setId(sn.getId())
+                .setStatus(MesWmSnStatusEnum.IN_STOCK.getStatus())
+                .setWarehouseId(reqVO.getWarehouseId()).setLocationId(reqVO.getLocationId()).setAreaId(reqVO.getAreaId())
+                .setRemark(reqVO.getRemark()));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void outboundSn(MesWmSnOutStockReqVO reqVO) {
+        // 1. 鏍¢獙 SN 瀛樺湪涓旂墿鏂欏尮閰�
+        MesWmSnDO sn = validateSnExists(reqVO.getCode());
+        validateSnItemMatch(sn, reqVO.getItemId());
+        // 2. 鐘舵�佹牎楠岋細蹇呴』鍦ㄥ簱涓旇褰曡繃搴撲綅
+        if (!ObjUtil.equal(sn.getStatus(), MesWmSnStatusEnum.IN_STOCK.getStatus())
+                || sn.getWarehouseId() == null || sn.getLocationId() == null || sn.getAreaId() == null) {
+            throw exception(WM_SN_NOT_IN_STOCK);
+        }
+        // 3. 鍒涘缓鍗曚欢鍑哄簱浜嬪姟锛�-1锛夛紝鎸� SN 鍦ㄥ簱浣嶇疆绮剧‘瀹氫綅搴撳瓨璁板綍
+        transactionService.createTransaction(new MesWmTransactionSaveReqDTO()
+                .setType(MesWmTransactionTypeEnum.OUT.getType())
+                .setBizType(MesBizTypeConstants.WM_SN)
+                .setBizId(sn.getId()).setBizCode(sn.getCode()).setBizLineId(sn.getId())
+                .setItemId(sn.getItemId()).setQuantity(BigDecimal.ONE.negate())
+                .setBatchId(sn.getBatchId()).setBatchCode(sn.getBatchCode())
+                .setSnCode(sn.getCode())
+                .setWarehouseId(sn.getWarehouseId()).setLocationId(sn.getLocationId()).setAreaId(sn.getAreaId()));
+        // 4. 鏇存柊 SN锛氬凡鍑哄簱 + 娓呯┖鍦ㄥ簱浣嶇疆
+        snMapper.markOutStockAndClearLocation(sn.getId());
+        snMapper.updateById(new MesWmSnDO().setId(sn.getId()).setRemark(reqVO.getRemark()));
+    }
+
+    @Override
+    public MesWmSnDetailRespVO getSnDetail(String snCode) {
+        MesWmSnDO sn = validateSnExists(snCode);
+        MesWmSnDetailRespVO resp = BeanUtils.toBean(sn, MesWmSnDetailRespVO.class);
+        MesWmTransactionDO transaction = transactionMapper.selectLatestBySnCode(snCode);
+        if (transaction != null) {
+            MesWmTransactionTypeEnum typeEnum = MesWmTransactionTypeEnum.valueOf(transaction.getType());
+            resp.setLastTransactionType(transaction.getType());
+            resp.setLastTransactionTypeName(typeEnum != null ? typeEnum.getName() : null);
+            resp.setLastTransactionQuantity(transaction.getQuantity());
+            resp.setLastTransactionBizType(transaction.getBizType());
+            resp.setLastTransactionBizCode(transaction.getBizCode());
+            resp.setLastTransactionTime(transaction.getTransactionTime());
+        }
+        return resp;
+    }
+
+    @Override
+    public PageResult<MesWmSnDO> getSnInStockPage(MesWmSnInStockPageReqVO reqVO) {
+        return snMapper.selectStatusPage(reqVO);
+    }
+
+    private MesWmSnDO validateSnExists(String code) {
+        MesWmSnDO sn = snMapper.selectByCode(code);
+        if (sn == null) {
+            throw exception(WM_SN_NOT_EXISTS);
+        }
+        return sn;
+    }
+
+    private void validateSnItemMatch(MesWmSnDO sn, Long itemId) {
+        if (ObjUtil.notEqual(sn.getItemId(), itemId)) {
+            throw exception(WM_SN_ITEM_MISMATCH);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java
index e661c58..ea5053e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java
@@ -6,6 +6,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.plan.MesWmStockTakingPlanDO;
 import jakarta.validation.Valid;
 
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -107,4 +108,21 @@
      */
     PageResult<MesWmStockTakingPlanDO> getStockTakingPlanPage(MesWmStockTakingPlanPageReqVO pageReqVO);
 
+    /**
+     * 鑾峰彇鍚敤涓斿紑鍚畾鏈熻嚜鍔ㄧ敓鎴愮殑鐩樼偣鏂规鍒楄〃
+     *
+     * <p>渚涘悗鍙板畾鏃朵换鍔℃壂鎻忥細鎸夊悇鏂规閰嶇疆鐨� cron 鍛ㄦ湡鑷姩鍒涘缓鐩樼偣浠诲姟銆�
+     *
+     * @return 鐩樼偣鏂规鍒楄〃
+     */
+    List<MesWmStockTakingPlanDO> getEnableAutoGeneratePlanList();
+
+    /**
+     * 鏇存柊鐩樼偣鏂规涓婃鑷姩鐢熸垚鏃堕棿
+     *
+     * @param id 鐩樼偣鏂规缂栧彿
+     * @param time 涓婃鑷姩鐢熸垚鏃堕棿
+     */
+    void updateStockTakingPlanLastGenerateTime(Long id, LocalDateTime time);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java
index 621996a..ba290f6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java
@@ -2,6 +2,7 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -12,10 +13,12 @@
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.plan.MesWmStockTakingPlanMapper;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockTakingTypeEnum;
 import jakarta.annotation.Resource;
+import org.quartz.CronExpression;
 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.Collections;
 import java.util.List;
@@ -122,6 +125,16 @@
         return stockTakingPlanMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public List<MesWmStockTakingPlanDO> getEnableAutoGeneratePlanList() {
+        return stockTakingPlanMapper.selectEnableAutoGenerateList();
+    }
+
+    @Override
+    public void updateStockTakingPlanLastGenerateTime(Long id, LocalDateTime time) {
+        stockTakingPlanMapper.updateById(new MesWmStockTakingPlanDO().setId(id).setLastGenerateTime(time));
+    }
+
     private void validatePlanCodeUnique(Long id, String code) {
         MesWmStockTakingPlanDO plan = stockTakingPlanMapper.selectByCode(code);
         if (plan == null) {
@@ -164,6 +177,16 @@
                 throw exception(WM_STOCK_TAKING_PLAN_DYNAMIC_TIME_INVALID);
             }
         }
+
+        // 鏍¢獙瀹氭湡鑷姩鐢熸垚閰嶇疆锛氬紑鍚嚜鍔ㄧ敓鎴愭椂 cron 蹇呭~涓斿悎娉�
+        if (Boolean.TRUE.equals(saveReqVO.getAutoGenerate())) {
+            if (StrUtil.isBlank(saveReqVO.getGenerateCron())) {
+                throw exception(WM_STOCK_TAKING_PLAN_AUTO_GENERATE_CRON_REQUIRED);
+            }
+            if (!CronExpression.isValidExpression(saveReqVO.getGenerateCron())) {
+                throw exception(WM_STOCK_TAKING_PLAN_AUTO_GENERATE_CRON_INVALID);
+            }
+        }
     }
 
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java
index 1fed457..93662a1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java
@@ -21,6 +21,7 @@
 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
 import cn.iocoder.yudao.module.mes.service.wm.stocktaking.plan.MesWmStockTakingPlanParamService;
 import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -52,6 +53,10 @@
     private MesWmMaterialStockService materialStockService;
     @Resource
     private MesWmStockTakingTaskService stockTakingTaskService;
+    // 涓� ResultService 鐩镐簰寮曠敤锛圧esultServiceImpl 宸叉敞鍏� LineService锛夛紝椤荤敤 @Lazy 鎵撶牬寰幆
+    @Lazy
+    @Resource
+    private MesWmStockTakingTaskResultService stockTakingTaskResultService;
     @Resource
     private cn.iocoder.yudao.module.mdm.api.item.MdmItemApi mdmItemApi;
 
@@ -74,7 +79,8 @@
         // 3. 鎵归噺鐢熸垚鏄庣粏琛�
         List<MesWmStockTakingTaskLineDO> lines = convertList(stocks, stock -> MesWmStockTakingTaskLineDO.builder()
                 .taskId(task.getId()).materialStockId(stock.getId()).itemId(stock.getItemId())
-                .batchId(stock.getBatchId()).quantity(stock.getQuantity()).takingQuantity(BigDecimal.ZERO)
+                .batchId(stock.getBatchId()).batchCode(stock.getBatchCode())
+                .quantity(stock.getQuantity()).takingQuantity(BigDecimal.ZERO)
                 .warehouseId(stock.getWarehouseId()).locationId(stock.getLocationId()).areaId(stock.getAreaId())
                 .status(MesWmStockTakingTaskLineStatusEnum.LOSS.getStatus()).build());
         stockTakingTaskLineMapper.insertBatch(lines);
@@ -244,6 +250,8 @@
         if (CollUtil.isEmpty(list)) {
             return respVO;
         }
+        // 0. 鏍¢獙鐩樼偣浠诲姟澶勪簬鐩樼偣涓姸鎬侊紙涓庝汉宸ュ綍鍏ョ洏鐐圭粨鏋滃彛寰勪竴鑷达紝浠呯洏鐐逛腑鍙鍏ワ級
+        stockTakingTaskService.validateStockTakingTaskExistsAndApproving(taskId);
         // 1. 鑾峰彇鐩樼偣浠诲姟琛�
         List<MesWmStockTakingTaskLineDO> lines = stockTakingTaskLineMapper.selectListByTaskId(taskId);
         if (CollUtil.isEmpty(lines)) {
@@ -283,6 +291,8 @@
                 continue;
             }
             updateStockTakingTaskLineTakingQuantity(matched.getId(), row.getTakingQuantity());
+            // 鍚屾鍐欑洏鐐圭粨鏋滐紝淇濊瘉宸紓鏍搁攢鏉ユ簮锛坱ask_result锛変竴鑷�
+            stockTakingTaskResultService.upsertResultForImport(matched, row.getTakingQuantity());
             respVO.setSuccessCount(respVO.getSuccessCount() + 1);
         }
         return respVO;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java
index 162accb..c087989 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java
@@ -3,8 +3,10 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskLineDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskResultDO;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -67,4 +69,15 @@
      */
     List<MesWmStockTakingTaskResultDO> getStockTakingTaskResultListByTaskId(Long taskId);
 
+    /**
+     * Excel 瀵煎叆鍦烘櫙鍚屾鐩樼偣缁撴灉
+     *
+     * <p>鑻ョ洏鐐硅宸插瓨鍦ㄧ洏鐐圭粨鏋滃垯瑕嗙洊鍏跺疄鐩樻暟閲忥紱鍚﹀垯鎸夌洏鐐硅缁村害鏂板缓涓�鏉$洏鐐圭粨鏋滐紝
+     * 浣跨函瀵煎叆鐨勫疄鐩樻暟鎹篃杩涘叆 {@code task_result}锛屼笌浜哄伐/鎵爜褰曞叆鍙e緞涓�鑷达紝淇濊瘉瀹屾垚鏃跺樊寮傛牳閿�鏈夋嵁鍙緷銆�
+     *
+     * @param line           鐩樼偣浠诲姟琛岋紙鎵胯浇璐﹂潰鏁伴噺涓庡簱瀛樼淮搴︼級
+     * @param takingQuantity 瀹炵洏鏁伴噺
+     */
+    void upsertResultForImport(MesWmStockTakingTaskLineDO line, BigDecimal takingQuantity);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java
index f7c8664..a7fbc02 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java
@@ -15,6 +15,7 @@
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_STOCK_TAKING_TASK_LINE_ALREADY_TAKEN;
@@ -132,6 +133,29 @@
         return stockTakingTaskResultMapper.selectListByTaskId(taskId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void upsertResultForImport(MesWmStockTakingTaskLineDO line, BigDecimal takingQuantity) {
+        // 1. 鏌ヨ璇ヤ换鍔′笅鏄惁宸插瓨鍦ㄨ琛岀殑鐩樼偣缁撴灉
+        MesWmStockTakingTaskResultDO existing = stockTakingTaskResultMapper.selectListByTaskId(line.getTaskId()).stream()
+                .filter(result -> Objects.equals(result.getLineId(), line.getId()))
+                .findFirst().orElse(null);
+        if (existing != null) {
+            // 2. 宸插瓨鍦細瑕嗙洊瀹炵洏鏁伴噺锛堥噸澶嶅鍏ヤ互鏈�鍚庝竴娆′负鍑嗭級
+            existing.setTakingQuantity(takingQuantity);
+            stockTakingTaskResultMapper.updateById(existing);
+            return;
+        }
+        // 3. 涓嶅瓨鍦細鎸夌洏鐐硅缁村害鏂板缓锛岃处闈㈡暟閲忓彇琛岃处闈㈡暟閲忥紝淇濊瘉鏍搁攢宸鍙e緞涓�鑷�
+        MesWmStockTakingTaskResultDO result = MesWmStockTakingTaskResultDO.builder()
+                .taskId(line.getTaskId()).lineId(line.getId()).materialStockId(line.getMaterialStockId())
+                .itemId(line.getItemId()).batchId(line.getBatchId()).batchCode(line.getBatchCode())
+                .warehouseId(line.getWarehouseId()).locationId(line.getLocationId()).areaId(line.getAreaId())
+                .quantity(line.getQuantity()).takingQuantity(takingQuantity)
+                .build();
+        stockTakingTaskResultMapper.insert(result);
+    }
+
     /**
      * 鏍¢獙鐩樼偣缁撴灉鏄惁瀛樺湪
      *
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
index 25feac0..a83c2a6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
@@ -6,12 +6,20 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO;
 import jakarta.validation.Valid;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * MES 鐩樼偣浠诲姟 Service 鎺ュ彛
  *
  * @author 瓒呯骇绠$悊鍛�
  */
 public interface MesWmStockTakingTaskService {
+
+    /**
+     * 鐩樼偣缁撴灉瀹℃壒 BPM 鍒嗙被缂栫爜
+     */
+    String APPROVE_CATEGORY_CODE = "stock_taking_result_approve";
 
     /**
      * 鍒涘缓鐩樼偣浠诲姟
@@ -36,18 +44,47 @@
     void deleteStockTakingTask(Long id);
 
     /**
-     * 鎻愪氦鐩樼偣浠诲姟
+     * 寮�濮嬬洏鐐癸紙鑽夌 鈫� 鐩樼偣涓級
+     *
+     * <p>浠呰崏绋跨姸鎬佸彲鎵ц锛氭牎楠屼换鍔¤闈炵┖鍚庣疆涓虹洏鐐逛腑锛岃嫢寮�鍚喕缁撳垯鍐荤粨浠诲姟琛岀殑搴撳瓨銆�
+     * 鐩樼偣涓彲褰曞叆瀹炵洏鏁伴噺銆丒xcel 瀵煎叆瀹炵洏銆�
      *
      * @param id 鐩樼偣浠诲姟缂栧彿
      */
     void submitStockTakingTask(Long id);
 
     /**
-     * 瀹屾垚鐩樼偣浠诲姟
+     * 鎻愪氦鐩樼偣缁撴灉瀹℃壒锛堢洏鐐逛腑 鈫� 鐩樼偣涓� + 鍒涘缓 BPM 娴佺▼瀹炰緥锛�
      *
-     * @param id 鐩樼偣浠诲姟缂栧彿
+     * <p>褰曞叆/瀵煎叆瀹屾垚鍚庢彁浜ょ粨鏋滃鎵癸細鏍¢獙鐩樼偣涓瓨鍦ㄤ笖鑷冲皯涓�鏉$洏鐐圭粨鏋溿�佷笖缁撴灉瀹℃壒鏈彁浜よ繃锛�
+     * 鍒涘缓 BPM 娴佺▼瀹炰緥骞跺洖鍐� {@code processInstanceId}銆傚鎵归�氳繃鍚庢墠鎵ц宸紓鏍搁攢骞剁疆涓哄凡瀹屾垚銆�
+     *
+     * @param id                  鐩樼偣浠诲姟缂栧彿
+     * @param processDefinitionKey BPM 娴佺▼瀹氫箟 Key
      */
-    void finishStockTakingTask(Long id);
+    void submitStockTakingResultApproval(Long id, String processDefinitionKey);
+
+    /**
+     * 鏇存柊鐩樼偣浠诲姟缁撴灉瀹℃壒鐘舵�侊紙BPM 瀹℃壒鍥炶皟锛�
+     *
+     * <p>浠呯洏鐐逛腑鐘舵�佸鐞嗭細
+     * <ul>
+     *     <li>瀹℃壒閫氳繃锛氳В鍐诲簱瀛橈紝鍚屼簨鍔¤嚜鍔ㄦ牳閿�鐩樼泩/鐩樹簭骞剁疆涓哄凡瀹屾垚锛�</li>
+     *     <li>瀹℃壒椹冲洖锛氭竻绌烘祦绋嬪疄渚嬬紪鍙凤紝浠诲姟淇濇寔鐩樼偣涓紝鍙慨鏀瑰疄鐩樺悗閲嶆柊鎻愪氦锛�</li>
+     *     <li>瀹℃壒鍙栨秷锛氳В鍐诲簱瀛樺苟缃负宸插彇娑堛��</li>
+     * </ul>
+     *
+     * @param id        鐩樼偣浠诲姟缂栧彿
+     * @param bpmResult BPM 瀹℃壒缁撴灉锛堥�氳繃/椹冲洖/鍙栨秷锛�
+     */
+    void updateStockTakingTaskResultAuditStatus(Long id, Integer bpmResult);
+
+    /**
+     * 鑾峰彇鐩樼偣缁撴灉瀹℃壒鍙敤鐨� BPM 娴佺▼瀹氫箟鍒楄〃
+     *
+     * @return 娴佺▼瀹氫箟鍒楄〃锛坕d/key/name锛�
+     */
+    List<Map<String, Object>> getStockTakingTaskResultApprovalProcessDefinitionList();
 
     /**
      * 鍙栨秷鐩樼偣浠诲姟
@@ -55,6 +92,17 @@
      * @param id 鐩樼偣浠诲姟缂栧彿
      */
     void cancelStockTakingTask(Long id);
+
+    /**
+     * 鐢辩洏鐐规柟妗堟寜鍛ㄦ湡鑷姩鐢熸垚鐩樼偣浠诲姟
+     *
+     * <p>渚涘悗鍙板畾鏃朵换鍔¤皟鐢細鍩轰簬宸插惎鐢ㄤ笖寮�鍚嚜鍔ㄧ敓鎴愮殑鏂规鍒涘缓鑽夌鐩樼偣浠诲姟骞剁敓鎴愭槑缁嗚锛�
+     * 浠诲姟缂栫爜鑷姩鐢熸垚銆傚垱寤烘垚鍔熷悗鍥炲啓鏂规涓婃鑷姩鐢熸垚鏃堕棿锛岄伩鍏嶅悓涓�鍛ㄦ湡閲嶅鐢熸垚銆�
+     *
+     * @param planId 鐩樼偣鏂规缂栧彿
+     * @return 鐩樼偣浠诲姟缂栧彿
+     */
+    Long createStockTakingTaskFromPlan(Long planId);
 
     /**
      * 鑾峰緱鐩樼偣浠诲姟
@@ -95,16 +143,5 @@
      * @return 鐩樼偣浠诲姟鍒嗛〉缁撴灉
      */
     PageResult<MesWmStockTakingTaskDO> getStockTakingTaskPage(MesWmStockTakingTaskPageReqVO pageReqVO);
-
-    /**
-     * 澶勭悊鐩樼偣宸紓锛岃皟鏁村簱瀛�
-     *
-     * 璇ユ柟娉曚細姣旇緝鐩樼偣缁撴灉涓庡簱瀛樺彴璐︾殑宸紓锛�
-     * - 鐩樼泩锛氬簱瀛樺鍔狅紝鍒涘缓鍏ュ簱浜嬪姟
-     * - 鐩樹簭锛氬簱瀛樺噺灏戯紝鍒涘缓鍑哄簱浜嬪姟
-     *
-     * @param id 鐩樼偣浠诲姟缂栧彿
-     */
-    void processStockTakingDifference(Long id);
 
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
index a192e7a..c419242 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
@@ -9,26 +9,40 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskLineDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskResultDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.plan.MesWmStockTakingPlanDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.task.MesWmStockTakingTaskMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockTakingTaskStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
 import cn.iocoder.yudao.module.mes.service.wm.stocktaking.plan.MesWmStockTakingPlanService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+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.enums.task.BpmTaskStatusEnum;
+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.context.annotation.Lazy;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
-import java.util.List;
+import java.time.LocalDateTime;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
 
 /**
@@ -38,10 +52,16 @@
  */
 @Service
 @Validated
+@Slf4j
 public class MesWmStockTakingTaskServiceImpl implements MesWmStockTakingTaskService {
 
     @Resource
     private MesWmStockTakingTaskMapper stockTakingTaskMapper;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
 
     @Resource
     private MesWmMaterialStockService materialStockService;
@@ -56,6 +76,9 @@
 
     @Resource
     private MesWmTransactionService wmTransactionService;
+
+    @Resource
+    private MesMdAutoCodeRecordService mesMdAutoCodeRecordService;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -82,6 +105,38 @@
             stockTakingTaskLineService.generateStockTakingLines(task, true);
         }
         return task.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createStockTakingTaskFromPlan(Long planId) {
+        // 1. 鏍¢獙鏂规瀛樺湪銆佸凡鍚敤涓斿紑鍚畾鏈熻嚜鍔ㄧ敓鎴�
+        MesWmStockTakingPlanDO plan = stockTakingPlanService.validateStockTakingPlanEnabled(planId);
+        if (!Boolean.TRUE.equals(plan.getAutoGenerate())) {
+            throw exception(WM_STOCK_TAKING_PLAN_NOT_AUTO_GENERATE);
+        }
+
+        // 2. 鑷姩鐢熸垚浠诲姟缂栫爜锛堣鍒欙細鐩樼偣浠诲姟缂栫爜锛�
+        String code = mesMdAutoCodeRecordService.generateAutoCode(
+                MesMdAutoCodeRuleCodeEnum.WM_STOCK_TAKING_CODE.getCode());
+
+        // 3. 澶嶇敤鍒涘缓閫昏緫鐢熸垚鑽夌浠诲姟锛堝唴閮ㄦ牎楠岀紪鐮佸敮涓�銆佹柟妗堝彲鐢紝骞舵寜鏂规鐢熸垚鐩樼偣鏄庣粏琛岋級
+        Long taskId = createStockTakingTask(new MesWmStockTakingTaskSaveReqVO()
+                .setCode(code)
+                .setName(plan.getName())
+                .setTakingDate(LocalDateTime.now())
+                .setType(plan.getType())
+                .setUserId(getCurrentUserId())
+                .setPlanId(planId)
+                .setStartTime(plan.getStartTime())
+                .setEndTime(plan.getEndTime())
+                .setBlindFlag(plan.getBlindFlag())
+                .setFrozen(plan.getFrozen())
+                .setRemark("鏂规瀹氭湡鑷姩鐢熸垚"));
+
+        // 4. 鍥炲啓鏈鐢熸垚鏃堕棿锛岄伩鍏嶅悓涓�鍛ㄦ湡閲嶅鐢熸垚锛堜笌鍒涘缓鍚屼簨鍔★級
+        stockTakingPlanService.updateStockTakingPlanLastGenerateTime(planId, LocalDateTime.now());
+        return taskId;
     }
 
     @Override
@@ -143,19 +198,114 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void finishStockTakingTask(Long id) {
-        // 1. 鏍¢獙浠诲姟瀛樺湪涓斾负瀹℃壒涓姸鎬�
+    public void submitStockTakingResultApproval(Long id, String processDefinitionKey) {
+        // 1.1 鏍¢獙浠诲姟瀛樺湪涓斿浜庣洏鐐逛腑鐘舵��
         MesWmStockTakingTaskDO task = validateStockTakingTaskExistsAndApproving(id);
-
-        // 2. 鏇存柊浠诲姟鐘舵�佷负宸插畬鎴�
-        stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
-                .setStatus(MesWmStockTakingTaskStatusEnum.FINISHED.getStatus()));
-
-        // 3. 瑙e喕搴撳瓨
-        if (Boolean.TRUE.equals(task.getFrozen())) {
-            List<MesWmStockTakingTaskLineDO> lines = stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id);
-            updateMaterialStockFrozen(lines, false);
+        // 1.2 鏍¢獙缁撴灉瀹℃壒鏈彁浜よ繃锛堥伩鍏嶉噸澶嶆彁浜わ級
+        if (task.getProcessInstanceId() != null) {
+            throw exception(WM_STOCK_TAKING_TASK_ALREADY_SUBMIT_APPROVAL);
         }
+        // 1.3 鏍¢獙鑷冲皯鏈変竴鏉$洏鐐圭粨鏋�
+        List<MesWmStockTakingTaskResultDO> results = stockTakingTaskResultService.getStockTakingTaskResultListByTaskId(id);
+        if (CollUtil.isEmpty(results)) {
+            throw exception(WM_STOCK_TAKING_TASK_NO_RESULT);
+        }
+
+        // 2. 鍒涘缓 BPM 娴佺▼瀹炰緥
+        String processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+
+        // 3. 鍥炲啓娴佺▼瀹炰緥缂栧彿锛堜换鍔′繚鎸佺洏鐐逛腑锛岀瓑寰呭鎵癸級
+        stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO()
+                .setId(id).setProcessInstanceId(processInstanceId));
+    }
+
+    @Override
+    public List<Map<String, Object>> getStockTakingTaskResultApprovalProcessDefinitionList() {
+        return getProcessDefinitionListByCategory(APPROVE_CATEGORY_CODE);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateStockTakingTaskResultAuditStatus(Long id, Integer bpmResult) {
+        // 1. 鏍¢獙浠诲姟瀛樺湪
+        MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
+        if (!MesWmStockTakingTaskStatusEnum.APPROVING.getStatus().equals(task.getStatus())) {
+            log.warn("[updateStockTakingTaskResultAuditStatus] 鐩樼偣浠诲姟({}) 涓嶅浜庣洏鐐逛腑鐘舵�侊紝蹇界暐瀹℃壒浜嬩欢", id);
+            return;
+        }
+        // 2. 瀹℃壒閫氳繃锛氬厛缃负宸插畬鎴愶紝鍐嶈В鍐诲苟鍚屼簨鍔¤嚜鍔ㄦ牳閿�宸紓
+        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            // 2.1 鍏堢疆涓哄凡瀹屾垚锛氫换鍔$姸鎬佸彉鏇村悗锛屽悗缁噸澶嶇殑瀹℃壒浜嬩欢璇诲彇鍒� FINISHED 鍗宠蹇界暐锛岄伩鍏嶉噸澶嶆牳閿�
+            stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
+                    .setStatus(MesWmStockTakingTaskStatusEnum.FINISHED.getStatus()));
+            // 2.2 瑙e喕搴撳瓨锛氶』鍦ㄥ樊寮傛牳閿�鍓嶈В鍐伙紝鍚﹀垯鏍搁攢浜嬪姟浼氳搴撳瓨鍐荤粨鏍¢獙鎷︽埅
+            if (Boolean.TRUE.equals(task.getFrozen())) {
+                updateMaterialStockFrozen(stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id), false);
+            }
+            // 2.3 鑷姩鏍搁攢鐩樼偣宸紓锛氱洏鐩堝叆搴� / 鐩樹簭鍑哄簱锛屽悓浜嬪姟鍐呮洿鏂板簱瀛樺彴璐︼紱浠讳竴姝ュけ璐ユ暣浣撳洖婊氾紝浠诲姟鍥為��涓虹洏鐐逛腑
+            doProcessStockTakingDifference(task);
+            return;
+        }
+        // 3. 瀹℃壒椹冲洖锛氭竻绌烘祦绋嬪疄渚嬬紪鍙凤紝浠诲姟淇濇寔鐩樼偣涓紝鍙慨鏀瑰疄鐩樺悗閲嶆柊鎻愪氦缁撴灉瀹℃壒
+        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            stockTakingTaskMapper.clearProcessInstanceId(id);
+            return;
+        }
+        // 4. 瀹℃壒鍙栨秷锛氳В鍐诲簱瀛樺苟缃负宸插彇娑�
+        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            if (Boolean.TRUE.equals(task.getFrozen())) {
+                updateMaterialStockFrozen(stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id), false);
+            }
+            stockTakingTaskMapper.clearProcessInstanceId(id);
+            stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
+                    .setStatus(MesWmStockTakingTaskStatusEnum.CANCELED.getStatus()));
+        }
+    }
+
+    /**
+     * 鑾峰彇鍒嗙被涓嬬殑鍙敤 BPM 娴佺▼瀹氫箟锛堣繃婊ゆ寕璧锋祦绋嬶紝鍚� Key 淇濈暀鏈�鏂扮増鏈級
+     *
+     * @param categoryCode 娴佺▼鍒嗙被缂栫爜
+     * @return 娴佺▼瀹氫箟鍒楄〃锛坕d/key/name锛�
+     */
+    private List<Map<String, Object>> getProcessDefinitionListByCategory(String categoryCode) {
+        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(categoryCode);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            return Collections.emptyList();
+        }
+        // 杩囨护婵�娲荤姸鎬侊紝淇濈暀鏈�鏂扮増鏈�
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
+            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
+            if (pd == null || pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+        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());
+            result.add(item);
+        }
+        return result;
+    }
+
+    private Long getCurrentUserId() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null && authentication.getPrincipal() instanceof Long) {
+            return (Long) authentication.getPrincipal();
+        }
+        return 1L; // 榛樿鐢ㄦ埛
     }
 
     @Override
@@ -168,6 +318,8 @@
         }
         task.setStatus(MesWmStockTakingTaskStatusEnum.CANCELED.getStatus());
         stockTakingTaskMapper.updateById(task);
+        // 鍙栨秷鍚庢竻绌烘祦绋嬪疄渚嬬紪鍙凤紝閬垮厤娈嬬暀瀹℃壒鏍囪褰卞搷鍚庣画閲嶆柊寮�濮嬬洏鐐�
+        stockTakingTaskMapper.clearProcessInstanceId(id);
         if (Boolean.TRUE.equals(task.getFrozen())) {
             updateMaterialStockFrozen(stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id), false);
         }
@@ -229,20 +381,26 @@
         }
     }
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void processStockTakingDifference(Long id) {
-        // 1. 鏍¢獙浠诲姟瀛樺湪涓斿凡瀹屾垚
-        MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
-        if (!MesWmStockTakingTaskStatusEnum.FINISHED.getStatus().equals(task.getStatus())) {
-            throw exception(WM_STOCK_TAKING_TASK_NOT_FINISHED);
-        }
-
-        // 2. 鑾峰彇鐩樼偣缁撴灉鍒楄〃
-        List<MesWmStockTakingTaskResultDO> results = stockTakingTaskResultService.getStockTakingTaskResultListByTaskId(id);
+    /**
+     * 鑷姩鏍搁攢鐩樼偣宸紓锛氭寜鐩樼偣缁撴灉閫愯鐢熸垚 IN/OUT 搴撳瓨浜嬪姟鏇存柊搴撳瓨鍙拌处
+     *
+     * <p>浠呯敱 {@link #updateStockTakingTaskResultAuditStatus} 鐨勫鎵归�氳繃鍒嗘敮鍦ㄥ悓浜嬪姟鍐呰皟鐢紱
+     * 澶辫触鏁翠綋鍥炴粴锛屼换鍔″洖閫�涓虹洏鐐逛腑鐘舵�侊紝閬垮厤閲嶅璋冩暣銆�
+     *
+     * @param task 宸茬疆涓哄凡瀹屾垚銆佸凡瑙e喕鐨勭洏鐐逛换鍔�
+     */
+    private void doProcessStockTakingDifference(MesWmStockTakingTaskDO task) {
+        // 1. 鑾峰彇鐩樼偣缁撴灉鍒楄〃
+        List<MesWmStockTakingTaskResultDO> results = stockTakingTaskResultService.getStockTakingTaskResultListByTaskId(task.getId());
         if (CollUtil.isEmpty(results)) {
             return; // 鏃犵洏鐐圭粨鏋滐紝鏃犻渶澶勭悊
         }
+
+        // 2. 寤虹珛 琛屽彿 -> 鐩樼偣琛� 鏄犲皠锛氫互鐩樼偣琛岋紙搴撳瓨蹇収锛変负鍑嗗洖濉簱瀛樼紪鍙蜂笌缁村害锛�
+        //    閬垮厤鍑哄簱鏍搁攢鎸� (鐗╂枡+浣嶇疆+鎵规) 缁勫悎閿尮閰嶅埌鍏跺畠渚涘簲鍟�/搴撲綅鐨勮处闈㈣鑰屾墸閿欏簱瀛�
+        Map<Long, MesWmStockTakingTaskLineDO> lineMap = convertMap(
+                stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(task.getId()),
+                MesWmStockTakingTaskLineDO::getId);
 
         // 3. 閬嶅巻缁撴灉锛屽鐞嗗樊寮�
         for (MesWmStockTakingTaskResultDO result : results) {
@@ -253,37 +411,30 @@
                 continue;
             }
 
-            // 璁$畻宸紓锛氱洏鐐规暟閲� - 璐﹂潰鏁伴噺
+            // 璁$畻宸紓锛氱洏鐐规暟閲� - 璐﹂潰鏁伴噺锛堢洏鐩堜负姝c�佺洏浜忎负璐燂級
             BigDecimal difference = takingQuantity.subtract(bookQuantity);
-
             if (difference.compareTo(BigDecimal.ZERO) == 0) {
                 continue; // 鏃犲樊寮傦紝璺宠繃
             }
 
-            // 4. 鍒涘缓搴撳瓨璋冩暣浜嬪姟
+            MesWmStockTakingTaskLineDO line = result.getLineId() != null ? lineMap.get(result.getLineId()) : null;
+
+            // 4. 鍒涘缓搴撳瓨璋冩暣浜嬪姟锛氱洏鐩堜紶姝f暟鍏ュ簱锛涚洏浜忕洿鎺ヤ紶璐熸暟鍑哄簱锛坈reateTransaction 鏍¢獙鍑哄簱鏁伴噺蹇呴』涓鸿礋锛�
             MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
-                    .setItemId(result.getItemId())
-                    .setBatchId(result.getBatchId())
-                    .setBatchCode(result.getBatchCode())
-                    .setWarehouseId(result.getWarehouseId())
-                    .setLocationId(result.getLocationId())
-                    .setAreaId(result.getAreaId())
+                    .setType(difference.compareTo(BigDecimal.ZERO) > 0
+                            ? MesWmTransactionTypeEnum.IN.getType() : MesWmTransactionTypeEnum.OUT.getType())
+                    .setQuantity(difference)
+                    .setItemId(line != null ? line.getItemId() : result.getItemId())
+                    .setBatchId(line != null ? line.getBatchId() : result.getBatchId())
+                    .setBatchCode(line != null ? line.getBatchCode() : result.getBatchCode())
+                    .setWarehouseId(line != null ? line.getWarehouseId() : result.getWarehouseId())
+                    .setLocationId(line != null ? line.getLocationId() : result.getLocationId())
+                    .setAreaId(line != null ? line.getAreaId() : result.getAreaId())
+                    .setMaterialStockId(line != null ? line.getMaterialStockId() : result.getMaterialStockId())
                     .setBizId(task.getId())
                     .setBizCode(task.getCode())
-                    .setBizLineId(result.getLineId());
-
-            if (difference.compareTo(BigDecimal.ZERO) > 0) {
-                // 鐩樼泩锛氬簱瀛樺鍔�
-                transaction.setType(MesWmTransactionTypeEnum.IN.getType())
-                        .setQuantity(difference)
-                        .setBizType(MesBizTypeConstants.WM_STOCK_TAKING);
-            } else {
-                // 鐩樹簭锛氬簱瀛樺噺灏�
-                transaction.setType(MesWmTransactionTypeEnum.OUT.getType())
-                        .setQuantity(difference.negate()) // 鍑哄簱鏁伴噺浼犳鏁帮紝浣嗗唴閮ㄤ細杞负璐熸暟
-                        .setBizType(MesBizTypeConstants.WM_STOCK_TAKING);
-            }
-
+                    .setBizLineId(result.getLineId())
+                    .setBizType(MesBizTypeConstants.WM_STOCK_TAKING);
             wmTransactionService.createTransaction(transaction);
         }
     }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/listener/MesWmStockTakingTaskResultStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/listener/MesWmStockTakingTaskResultStatusListener.java
new file mode 100644
index 0000000..0ec8424
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/listener/MesWmStockTakingTaskResultStatusListener.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.mes.service.wm.stocktaking.task.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.mes.service.wm.stocktaking.task.MesWmStockTakingTaskService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * MES 鐩樼偣缁撴灉瀹℃壒 BPM 鐘舵�佺洃鍚櫒
+ *
+ * 鐩戝惉鐩樼偣缁撴灉瀹℃壒娴佺▼瀹炰緥鐘舵�佸彉鏇翠簨浠讹細瀹℃壒閫氳繃鍚庤В鍐诲簱瀛樺苟鑷姩鏍搁攢鐩樼偣宸紓缃负宸插畬鎴愶紱
+ * 椹冲洖鍚庝换鍔′繚鎸佺洏鐐逛腑鍙慨鏀瑰疄鐩橀噸鏂版彁浜わ紱鍙栨秷鍒欒В鍐诲苟缃负宸插彇娑�
+ */
+@Component
+@Slf4j
+public class MesWmStockTakingTaskResultStatusListener
+        extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
+
+    @Resource
+    private MesWmStockTakingTaskService stockTakingTaskService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return MesWmStockTakingTaskService.APPROVE_CATEGORY_CODE;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        Long id;
+        try {
+            id = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勭洏鐐逛换鍔$紪鍙�", event.getBusinessKey());
+            return;
+        }
+        log.info("[onEvent] 鏀跺埌鐩樼偣缁撴灉瀹℃壒浜嬩欢锛宻tockTakingTaskId={}, status={}", id, event.getStatus());
+        try {
+            stockTakingTaskService.updateStockTakingTaskResultAuditStatus(id, event.getStatus());
+            log.info("[onEvent] 鐩樼偣浠诲姟({}) 缁撴灉瀹℃壒鐘舵�佹洿鏂板畬鎴�", id);
+        } catch (Exception e) {
+            log.error("[onEvent] 鏇存柊鐩樼偣浠诲姟缁撴灉瀹℃壒鐘舵�佸け璐ワ紝stockTakingTaskId={}", id, e);
+        }
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
index 031dc5d..0b02f4c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -128,7 +128,7 @@
         // 3. 鎻掑叆浜嬪姟娴佹按
         MesWmTransactionDO transaction = MesWmTransactionDO.builder()
                 .type(reqDTO.getType()).quantity(reqDTO.getQuantity()).transactionTime(LocalDateTime.now())
-                .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode())
+                .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode()).snCode(reqDTO.getSnCode())
                 .warehouseId(reqDTO.getWarehouseId()).locationId(reqDTO.getLocationId()).areaId(reqDTO.getAreaId())
                 .bizType(reqDTO.getBizType()).bizId(reqDTO.getBizId()).bizCode(reqDTO.getBizCode()).bizLineId(reqDTO.getBizLineId())
                 .materialStockId(materialStock.getId()).relatedTransactionId(reqDTO.getRelatedTransactionId())
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
index d0ae46a..a321888 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
@@ -69,6 +69,10 @@
      * 鎵规鍙�
      */
     private String batchCode;
+    /**
+     * SN 鐮侊紙搴忓垪鍙风鐞嗙墿鏂欏崟鍝佽繘鍑猴級
+     */
+    private String snCode;
 
     // ===== 搴撳瓨浣嶇疆 =====
 

--
Gitblit v1.9.3