From d41fe2e95f3f64c6e3a7229acd9e74e673513a0a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 09 七月 2026 16:13:59 +0800
Subject: [PATCH] 1.人力资源大模块开发 2.仓库大模块开发
---
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceClockTypeEnum.java | 31
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java | 67
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java | 275
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigRespVO.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeWorkHistoryDO.java | 84
docs/hrm_attendance_frontend.md | 604 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeDO.java | 8
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java | 29
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java | 331
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java | 314
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java | 226
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java | 80
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationSaveReqVO.java | 43
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeWorkHistoryController.java | 48
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmTaxRateConfigController.java | 86
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java | 16
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java | 82
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceSummaryDO.java | 89
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigRespVO.java | 47
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java | 15
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java | 68
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java | 60
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRecordDO.java | 96
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java | 84
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecurityConfigController.java | 85
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java | 324
sql/mysql/mes_wm_arrival_notice_purchase_order.sql | 11
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java | 9
sql/mysql/hrm_leave_type_config_init.sql | 26
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigServiceImpl.java | 66
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingCreateReqVO.java | 31
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingServiceImpl.java | 74
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityServiceImpl.java | 81
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java | 10
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmTransferApplicationMapper.java | 38
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java | 24
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java | 40
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java | 169
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java | 120
pom.xml | 4
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationPageReqVO.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java | 57
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java | 52
sql/mysql/hrm_salary_payment_detail_init.sql | 19
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentService.java | 76
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSalaryDO.java | 85
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmLeaveApplicationStatusListener.java | 55
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsStockApiImpl.java | 238
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDetailDO.java | 73
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java | 190
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordPageReqVO.java | 35
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java | 73
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryRespVO.java | 74
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java | 220
.claude/settings.local.json | 6
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java | 94
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java | 35
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java | 108
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java | 107
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wms/approval/WmsWarehouseApprovalConfigMapper.java | 26
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalService.java | 76
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java | 82
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java | 4
yudao-module-mes/pom.xml | 21
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java | 4
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryService.java | 36
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeStatusEnum.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceHolidayController.java | 92
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java | 67
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java | 27
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java | 43
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java | 112
yudao-module-erp/pom.xml | 7
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transfer/MesWmTransferDO.java | 12
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java | 70
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceHolidayTypeEnum.java | 29
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleSaveReqVO.java | 48
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEmergencyContactController.java | 48
docs/wls_module_integration.md | 730 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistorySaveReqVO.java | 49
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java | 113
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentPageReqVO.java | 24
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactSaveReqVO.java | 39
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceDataSourceEnum.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmLeaveTypeConfigDO.java | 57
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigRespVO.java | 81
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSimpleRespVO.java | 25
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java | 10
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEmergencyContactDO.java | 67
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java | 3
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecurityConfigMapper.java | 33
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java | 103
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingService.java | 52
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java | 84
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceSummaryController.java | 155
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java | 58
docs/wm_arrival_notice_purchase_order.md | 148
docs/salary_calculation.md | 153
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java | 68
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmDegreeEnum.java | 32
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java | 48
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java | 107
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java | 48
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java | 133
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRuleController.java | 132
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmTransferTypeEnum.java | 38
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingServiceImpl.java | 105
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java | 22
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionServiceImpl.java | 154
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentMapper.java | 37
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalServiceImpl.java | 142
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java | 76
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecurityConfigDO.java | 123
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java | 7
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistoryRespVO.java | 63
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityPageReqVO.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRuleDO.java | 80
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSocialSecurityDO.java | 77
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java | 121
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java | 95
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java | 43
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigSaveReqVO.java | 52
yudao-server/pom.xml | 9
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java | 198
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java | 164
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java | 154
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java | 24
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeSaveReqVO.java | 5
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeMapper.java | 24
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRuleMapper.java | 40
sql/mysql/mes_wm_sn_fix.sql | 21
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionService.java | 50
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactRespVO.java | 50
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/WmStatusTransitionUtils.java | 185
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmResignationApplicationMapper.java | 43
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java | 74
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEducationDO.java | 76
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentDetailMapper.java | 43
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceExceptionMapper.java | 36
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRulePageReqVO.java | 26
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationPageReqVO.java | 33
docs/wm_module_analysis.md | 357
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleService.java | 72
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wms/approval/WmsWarehouseApprovalConfigDO.java | 57
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java | 9
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java | 107
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java | 134
.claude/rules/sql-syntax.md | 40
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java | 12
sql/mysql/erp_mes_warehouse_mapping.sql | 26
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java | 5
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java | 6
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationService.java | 36
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAuditStatusEnum.java | 40
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryServiceImpl.java | 59
sql/mysql/hrm_employee_update.sql | 25
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/mapping/ErpMesWarehouseMappingConvert.java | 27
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderRespDTO.java | 40
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java | 112
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java | 187
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java | 72
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java | 68
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactServiceImpl.java | 59
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationPageReqVO.java | 36
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsTransactionApiImpl.java | 55
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/ErpMesWarehouseMappingController.java | 73
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java | 23
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionApi.java | 24
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionPageReqVO.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java | 46
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java | 55
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java | 19
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceExceptionTypeEnum.java | 30
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigServiceImpl.java | 69
sql/mysql/report_module_init.sql | 70
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java | 126
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionService.java | 58
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceSummaryMapper.java | 38
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java | 54
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java | 117
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationRespVO.java | 58
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingUpdateReqVO.java | 20
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationPageReqVO.java | 33
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java | 15
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecuritySaveReqVO.java | 45
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java | 24
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java | 4
docs/hrm_delete_api.md | 409 +
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/mapping/ErpMesWarehouseMappingMapper.java | 35
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java | 22
yudao-module-wms-api/pom.xml | 33
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java | 50
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmResignationTypeEnum.java | 38
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java | 55
sql/mysql/hrm_attendance_supplement.sql | 157
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigSaveReqVO.java | 95
sql/mysql/hrm_deduction_init.sql | 180
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java | 150
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java | 11
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmLeaveTypeConfigMapper.java | 27
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigService.java | 45
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferService.java | 8
sql/mysql/wls_database_init.sql | 76
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsPurchaseInboundReqDTO.java | 73
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java | 108
docs/leave_deduction_frontend.md | 573 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleRespVO.java | 67
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityRespVO.java | 52
sql/mysql/hrm_module_init.sql | 594 +
sql/mysql/hrm_employee_social_security_update.sql | 105
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java | 76
sql/mysql/wls_menu_init.sql | 35
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java | 17
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationServiceImpl.java | 59
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockDetailRespDTO.java | 68
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java | 59
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmTaxRateConfigDO.java | 78
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigSaveReqVO.java | 27
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryService.java | 66
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmTaxRateConfigMapper.java | 32
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java | 3
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java | 43
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSocialSecurityMapper.java | 43
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayPageReqVO.java | 29
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigService.java | 46
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmLeaveTypeEnum.java | 41
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryCalculationMapper.java | 60
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java | 39
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java | 108
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wms/approval/WmsApprovalController.java | 71
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java | 39
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingService.java | 49
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java | 27
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java | 287
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java | 78
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java | 57
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java | 41
docs/wm_module_architecture.md | 440 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java | 99
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java | 218
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java | 63
docs/hrm_frontend.md | 803 ++
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/mapping/ErpMesWarehouseMappingDO.java | 53
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java | 141
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java | 580 +
docs/salary_deduction.md | 721 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java | 14
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockQueryReqDTO.java | 36
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsSalesOutboundReqDTO.java | 73
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityService.java | 56
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java | 47
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserCreateReqDTO.java | 56
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactService.java | 36
yudao-module-hrm/pom.xml | 81
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEducationController.java | 48
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingPageReqVO.java | 25
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java | 52
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailPageReqVO.java | 27
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailRespVO.java | 58
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java | 8
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionCreateReqDTO.java | 74
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationSaveReqVO.java | 86
yudao-module-hrm-api/pom.xml | 33
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java | 55
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskResultMapper.java | 6
266 files changed, 22,697 insertions(+), 23 deletions(-)
diff --git a/.claude/rules/sql-syntax.md b/.claude/rules/sql-syntax.md
new file mode 100644
index 0000000..74e1b19
--- /dev/null
+++ b/.claude/rules/sql-syntax.md
@@ -0,0 +1,40 @@
+# SQL 璇硶瑙勮寖
+
+## 瑙勫垯
+
+- MySQL `ALTER TABLE ADD COLUMN` 涓嶆敮鎸� `IF NOT EXISTS` 璇硶锛屽嵆浣� MySQL 8.0+ 涔熶笉鏀寔銆�
+- 娣诲姞瀛楁鍓嶉渶瑕佸厛妫�鏌ュ瓧娈垫槸鍚﹀瓨鍦紝鎴栫洿鎺ユ墽琛屽苟蹇界暐"瀛楁宸插瓨鍦�"閿欒銆�
+
+## 姝g‘绀轰緥
+
+```sql
+-- 姝g‘锛氱洿鎺ユ坊鍔犲瓧娈�
+ALTER TABLE mes_wm_sn ADD COLUMN uuid VARCHAR(64) COMMENT '鎵规UUID' AFTER id;
+
+-- 姝g‘锛氬厛妫�鏌ュ啀娣诲姞锛堝吋瀹硅剼鏈級
+SET @exists = (SELECT COUNT(*) FROM information_schema.columns
+ WHERE table_schema = DATABASE() AND table_name = 'mes_wm_sn' AND column_name = 'uuid');
+SET @sql = IF(@exists = 0,
+ 'ALTER TABLE mes_wm_sn ADD COLUMN uuid VARCHAR(64) COMMENT ''鎵规UUID'' AFTER id',
+ 'SELECT 1');
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+```
+
+## 閿欒绀轰緥
+
+```sql
+-- 閿欒锛欰DD COLUMN 涓嶆敮鎸� IF NOT EXISTS
+ALTER TABLE mes_wm_sn ADD COLUMN IF NOT EXISTS uuid VARCHAR(64) COMMENT '鎵规UUID' AFTER id;
+```
+
+## IF NOT EXISTS 鏀寔鍦烘櫙
+
+| 璇彞 | 鏄惁鏀寔 |
+|------|---------|
+| CREATE TABLE IF NOT EXISTS | 鉁� 鏀寔 |
+| CREATE INDEX IF NOT EXISTS | 鉁� 鏀寔锛圡ySQL 8.0+锛� |
+| ALTER TABLE ADD COLUMN IF NOT EXISTS | 鉂� 涓嶆敮鎸� |
+| ALTER TABLE ADD INDEX IF NOT EXISTS | 鉁� 鏀寔锛圡ySQL 8.0+锛� |
+| ALTER TABLE ADD CONSTRAINT IF NOT EXISTS | 鉂� 涓嶆敮鎸� |
\ No newline at end of file
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index ec7ef59..2157789 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -23,7 +23,11 @@
"Bash(dir /s /b \"C:\\\\Program Files \\(x86\\)\\\\MySQL\\\\*\\\\bin\\\\mysql.exe\")",
"Bash(cmd.exe /c \"where mysql\")",
"Bash(cmd.exe /c \"dir /s /b C:\\\\ProgramData\\\\MySQL\\\\*\\\\mysql.exe\")",
- "Bash(cmd.exe /c \"dir /s /b C:\\\\Program Files\\\\MySQL\\\\*\\\\mysql.exe\")"
+ "Bash(cmd.exe /c \"dir /s /b C:\\\\Program Files\\\\MySQL\\\\*\\\\mysql.exe\")",
+ "Bash(mvn install *)",
+ "Bash(mvn dependency:tree -pl yudao-module-report)",
+ "Bash(xargs -I {} basename {} .java)",
+ "Bash(xargs grep -l \"HrmLeaveTypeEnum\\\\|HrmAuditStatusEnum\")"
],
"deny": [
"Bash(rm -rf *)",
diff --git a/docs/hrm_attendance_frontend.md b/docs/hrm_attendance_frontend.md
new file mode 100644
index 0000000..8891eda
--- /dev/null
+++ b/docs/hrm_attendance_frontend.md
@@ -0,0 +1,604 @@
+# HRM 鑰冨嫟绠$悊琛ュ厖鍔熻兘 - 鍓嶇淇敼鏂囨。
+
+## 姒傝堪
+
+鏈琛ュ厖浜嗚�冨嫟绠$悊鐨勬牳蹇冨姛鑳斤細鑰冨嫟瑙勫垯绠$悊銆佽妭鍋囨棩閰嶇疆銆佸憳宸ユ墦鍗°�佽�冨嫟缁熻姹囨�汇��
+
+---
+
+## 涓�銆佽�冨嫟瑙勫垯绠$悊
+
+### 娑夊強椤甸潰
+
+- 鑰冨嫟瑙勫垯鍒楄〃椤甸潰锛堟柊澧烇級
+- 鑰冨嫟瑙勫垯鏂板/缂栬緫寮圭獥锛堟柊澧烇級
+
+### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/attendance/rule/create | 鏂板鑰冨嫟瑙勫垯 |
+| PUT | /hrm/attendance/rule/update | 鏇存柊鑰冨嫟瑙勫垯 |
+| DELETE | /hrm/attendance/rule/delete | 鍒犻櫎鑰冨嫟瑙勫垯 |
+| GET | /hrm/attendance/rule/get | 鑾峰緱鑰冨嫟瑙勫垯璇︽儏 |
+| GET | /hrm/attendance/rule/page | 鑾峰緱鑰冨嫟瑙勫垯鍒嗛〉鍒楄〃 |
+| GET | /hrm/attendance/rule/list | 鑾峰緱鑰冨嫟瑙勫垯鍒楄〃锛堝惎鐢ㄧ姸鎬侊級 |
+| GET | /hrm/attendance/rule/export-excel | 瀵煎嚭鑰冨嫟瑙勫垯 Excel |
+
+**璇锋眰鍙傛暟锛坈reate/update锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鍚� | 瑙勫垯ID锛堜慨鏀规椂蹇呭~锛� |
+| name | String | 鏄� | 瑙勫垯鍚嶇О |
+| deptId | Long | 鍚� | 閫傜敤閮ㄩ棬ID锛堢┖琛ㄧず鍏ㄥ眬锛� |
+| workStartTime | String | 鏄� | 涓婄彮鏃堕棿锛屾牸寮忥細HH:mm:ss |
+| workEndTime | String | 鏄� | 涓嬬彮鏃堕棿锛屾牸寮忥細HH:mm:ss |
+| lateAllowMinutes | Integer | 鍚� | 杩熷埌鍏佽鍒嗛挓鏁帮紝榛樿 0 |
+| earlyAllowMinutes | Integer | 鍚� | 鏃╅��鍏佽鍒嗛挓鏁帮紝榛樿 0 |
+| overtimeThreshold | BigDecimal | 鍚� | 鍔犵彮璧风畻鏃堕暱锛堝皬鏃讹級锛岄粯璁� 1 |
+| status | Integer | 鍚� | 鐘舵�侊細0-鍚敤锛�1-绂佺敤 |
+| remark | String | 鍚� | 澶囨敞 |
+
+**鍝嶅簲锛�** `{ "code": 200, "data": 1, "msg": "鎿嶄綔鎴愬姛" }`
+
+### 鍓嶇淇敼鐐�
+
+#### 1. 璺敱閰嶇疆
+
+```js
+// src/router/modules/hrm.js
+{
+ path: 'rule',
+ name: 'HrmAttendanceRule',
+ component: () => import('@/views/hrm/attendance/rule/index'),
+ meta: { title: '鑰冨嫟瑙勫垯', icon: 'setting' }
+}
+```
+
+#### 2. 鍒楄〃椤甸潰琛ㄦ牸
+
+```html
+<el-table :data="list">
+ <el-table-column label="瑙勫垯鍚嶇О" prop="name" />
+ <el-table-column label="閫傜敤閮ㄩ棬" prop="deptName">
+ <template #default="{ row }">
+ {{ row.deptName || '鍏ㄥ眬' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婄彮鏃堕棿" prop="workStartTime" />
+ <el-table-column label="涓嬬彮鏃堕棿" prop="workEndTime" />
+ <el-table-column label="杩熷埌鍏佽(鍒嗛挓)" prop="lateAllowMinutes" />
+ <el-table-column label="鏃╅��鍏佽(鍒嗛挓)" prop="earlyAllowMinutes" />
+ <el-table-column label="鍔犵彮璧风畻(灏忔椂)" prop="overtimeThreshold" />
+ <el-table-column label="鐘舵��" prop="status">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 0 ? 'success' : 'danger'">
+ {{ row.status === 0 ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="180">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="handleUpdate(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row.id)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+</el-table>
+```
+
+#### 3. 鏂板/缂栬緫琛ㄥ崟
+
+```html
+<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+ <el-form-item label="瑙勫垯鍚嶇О" prop="name">
+ <el-input v-model="form.name" placeholder="璇疯緭鍏ヨ鍒欏悕绉�" />
+ </el-form-item>
+ <el-form-item label="閫傜敤閮ㄩ棬" prop="deptId">
+ <el-tree-select
+ v-model="form.deptId"
+ :data="deptList"
+ :props="{ label: 'name', value: 'id' }"
+ placeholder="璇烽�夋嫨閮ㄩ棬锛堜笉閫夎〃绀哄叏灞�锛�"
+ clearable
+ check-strictly
+ />
+ </el-form-item>
+ <el-form-item label="涓婄彮鏃堕棿" prop="workStartTime">
+ <el-time-picker v-model="form.workStartTime" value-format="HH:mm:ss" placeholder="閫夋嫨涓婄彮鏃堕棿" />
+ </el-form-item>
+ <el-form-item label="涓嬬彮鏃堕棿" prop="workEndTime">
+ <el-time-picker v-model="form.workEndTime" value-format="HH:mm:ss" placeholder="閫夋嫨涓嬬彮鏃堕棿" />
+ </el-form-item>
+ <el-form-item label="杩熷埌鍏佽" prop="lateAllowMinutes">
+ <el-input-number v-model="form.lateAllowMinutes" :min="0" :max="60" placeholder="鍒嗛挓" />
+ </el-form-item>
+ <el-form-item label="鏃╅��鍏佽" prop="earlyAllowMinutes">
+ <el-input-number v-model="form.earlyAllowMinutes" :min="0" :max="60" placeholder="鍒嗛挓" />
+ </el-form-item>
+ <el-form-item label="鍔犵彮璧风畻" prop="overtimeThreshold">
+ <el-input-number v-model="form.overtimeThreshold" :min="0" :precision="1" placeholder="灏忔椂" />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio :value="0">鍚敤</el-radio>
+ <el-radio :value="1">绂佺敤</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+</el-form>
+```
+
+#### 4. API 鏂规硶
+
+```js
+// src/api/hrm/attendance.js
+export function getAttendanceRulePage(params) {
+ return request({ url: '/hrm/attendance/rule/page', method: 'get', params })
+}
+
+export function getAttendanceRuleList() {
+ return request({ url: '/hrm/attendance/rule/list', method: 'get' })
+}
+
+export function createAttendanceRule(data) {
+ return request({ url: '/hrm/attendance/rule/create', method: 'post', data })
+}
+
+export function updateAttendanceRule(data) {
+ return request({ url: '/hrm/attendance/rule/update', method: 'put', data })
+}
+
+export function deleteAttendanceRule(id) {
+ return request({ url: '/hrm/attendance/rule/delete', method: 'delete', params: { id } })
+}
+```
+
+---
+
+## 浜屻�佽妭鍋囨棩閰嶇疆
+
+### 娑夊強椤甸潰
+
+- 鑺傚亣鏃ラ厤缃垪琛ㄩ〉闈紙鏂板锛�
+- 鑺傚亣鏃ユ柊澧�/缂栬緫寮圭獥锛堟柊澧烇級
+
+### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/attendance/holiday/create | 鏂板鑺傚亣鏃� |
+| PUT | /hrm/attendance/holiday/update | 鏇存柊鑺傚亣鏃� |
+| DELETE | /hrm/attendance/holiday/delete | 鍒犻櫎鑺傚亣鏃� |
+| GET | /hrm/attendance/holiday/get | 鑾峰緱鑺傚亣鏃ヨ鎯� |
+| GET | /hrm/attendance/holiday/page | 鑾峰緱鑺傚亣鏃ュ垎椤靛垪琛� |
+| GET | /hrm/attendance/holiday/export-excel | 瀵煎嚭鑺傚亣鏃� Excel |
+
+**璇锋眰鍙傛暟锛坈reate/update锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鍚� | 鑺傚亣鏃D锛堜慨鏀规椂蹇呭~锛� |
+| name | String | 鏄� | 鑺傚亣鏃ュ悕绉� |
+| date | String | 鏄� | 鏃ユ湡锛屾牸寮忥細yyyy-MM-dd |
+| type | Integer | 鏄� | 绫诲瀷锛�1-浼戞伅鏃ワ紝2-宸ヤ綔鏃� |
+| isLegal | Integer | 鍚� | 鏄惁娉曞畾鑺傚亣鏃ワ細0-鍚︼紝1-鏄� |
+| remark | String | 鍚� | 澶囨敞 |
+
+### 鍓嶇淇敼鐐�
+
+#### 1. 鍒楄〃椤甸潰
+
+```html
+<el-table :data="list">
+ <el-table-column label="鑺傚亣鏃ュ悕绉�" prop="name" />
+ <el-table-column label="鏃ユ湡" prop="date" />
+ <el-table-column label="绫诲瀷" prop="type">
+ <template #default="{ row }">
+ <el-tag :type="row.type === 1 ? 'warning' : 'success'">
+ {{ row.type === 1 ? '浼戞伅鏃�' : '宸ヤ綔鏃�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="娉曞畾鑺傚亣鏃�" prop="isLegal">
+ <template #default="{ row }">
+ <el-tag v-if="row.isLegal === 1" type="danger">娉曞畾</el-tag>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶囨敞" prop="remark" />
+ <el-table-column label="鎿嶄綔" width="180">
+ <template #default="{ row }">
+ <el-button type="primary" link @click="handleUpdate(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row.id)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+</el-table>
+```
+
+#### 2. 琛ㄥ崟
+
+```html
+<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+ <el-form-item label="鑺傚亣鏃ュ悕绉�" prop="name">
+ <el-input v-model="form.name" placeholder="濡傦細鏄ヨ妭銆佸浗搴嗚妭" />
+ </el-form-item>
+ <el-form-item label="鏃ユ湡" prop="date">
+ <el-date-picker v-model="form.date" value-format="YYYY-MM-DD" placeholder="閫夋嫨鏃ユ湡" />
+ </el-form-item>
+ <el-form-item label="绫诲瀷" prop="type">
+ <el-radio-group v-model="form.type">
+ <el-radio :value="1">浼戞伅鏃�</el-radio>
+ <el-radio :value="2">宸ヤ綔鏃ワ紙璋冧紤锛�</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="娉曞畾鑺傚亣鏃�" prop="isLegal">
+ <el-checkbox v-model="form.isLegal" :true-value="1" :false-value="0">鏄硶瀹氳妭鍋囨棩</el-checkbox>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" />
+ </el-form-item>
+</el-form>
+```
+
+#### 3. API 鏂规硶
+
+```js
+export function getAttendanceHolidayPage(params) {
+ return request({ url: '/hrm/attendance/holiday/page', method: 'get', params })
+}
+
+export function createAttendanceHoliday(data) {
+ return request({ url: '/hrm/attendance/holiday/create', method: 'post', data })
+}
+
+export function updateAttendanceHoliday(data) {
+ return request({ url: '/hrm/attendance/holiday/update', method: 'put', data })
+}
+
+export function deleteAttendanceHoliday(id) {
+ return request({ url: '/hrm/attendance/holiday/delete', method: 'delete', params: { id } })
+}
+```
+
+---
+
+## 涓夈�佸憳宸ユ墦鍗�
+
+### 娑夊強椤甸潰
+
+- 鑰冨嫟璁板綍鍒楄〃椤甸潰锛堝凡鏈夛紝鎵╁睍鍔熻兘锛�
+- 鎵撳崱鎸夐挳/缁勪欢锛堟柊澧烇級
+
+### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/attendance/record/clock-in | 涓婄彮鎵撳崱 |
+| POST | /hrm/attendance/record/clock-out | 涓嬬彮鎵撳崱 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| location | String | 鏄� | 鎵撳崱鍦扮偣 |
+
+**鍝嶅簲锛堟墦鍗℃垚鍔燂級锛�**
+
+```json
+{
+ "code": 200,
+ "data": {
+ "id": 1,
+ "userId": 100,
+ "userName": "寮犱笁",
+ "deptId": 1,
+ "deptName": "鐮斿彂閮�",
+ "date": "2024-01-15",
+ "clockInTime": "2024-01-15T09:05:00",
+ "clockInType": 1,
+ "clockOutTime": null,
+ "clockOutType": null,
+ "workHours": null,
+ "location": "鍏徃鍓嶅彴"
+ }
+}
+```
+
+### 鍓嶇淇敼鐐�
+
+#### 1. 鎵撳崱鎸夐挳缁勪欢
+
+```html
+<template>
+ <div class="clock-container">
+ <el-button type="primary" size="large" @click="handleClockIn">
+ <el-icon><Clock /></el-icon>
+ 涓婄彮鎵撳崱
+ </el-button>
+ <el-button type="success" size="large" @click="handleClockOut">
+ <el-icon><Clock /></el-icon>
+ 涓嬬彮鎵撳崱
+ </el-button>
+ <div v-if="clockStatus" class="clock-status">
+ <p>浠婃棩鎵撳崱鐘舵�侊細</p>
+ <p>涓婄彮锛歿{ clockStatus.clockInTime || '鏈墦鍗�' }}
+ <el-tag v-if="clockStatus.clockInType === 2" type="warning">杩熷埌</el-tag>
+ </p>
+ <p>涓嬬彮锛歿{ clockStatus.clockOutTime || '鏈墦鍗�' }}
+ <el-tag v-if="clockStatus.clockOutType === 3" type="warning">鏃╅��</el-tag>
+ </p>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { clockIn, clockOut } from '@/api/hrm/attendance'
+import { ElMessage } from 'element-plus'
+
+const location = ref('鍏徃鍓嶅彴')
+
+const handleClockIn = async () => {
+ try {
+ const res = await clockIn({ location: location.value })
+ ElMessage.success('涓婄彮鎵撳崱鎴愬姛')
+ // 鏇存柊鎵撳崱鐘舵�佹樉绀�
+ } catch (e) {
+ ElMessage.error(e.message || '鎵撳崱澶辫触')
+ }
+}
+
+const handleClockOut = async () => {
+ try {
+ const res = await clockOut({ location: location.value })
+ ElMessage.success('涓嬬彮鎵撳崱鎴愬姛')
+ } catch (e) {
+ ElMessage.error(e.message || '鎵撳崱澶辫触')
+ }
+}
+</script>
+```
+
+#### 2. API 鏂规硶
+
+```js
+export function clockIn(params) {
+ return request({ url: '/hrm/attendance/record/clock-in', method: 'post', params })
+}
+
+export function clockOut(params) {
+ return request({ url: '/hrm/attendance/record/clock-out', method: 'post', params })
+}
+```
+
+---
+
+## 鍥涖�佽�冨嫟缁熻姹囨��
+
+### 娑夊強椤甸潰
+
+- 鑰冨嫟缁熻鍒楄〃椤甸潰锛堟柊澧烇級
+- 缁熻鐢熸垚寮圭獥锛堟柊澧烇級
+
+### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /hrm/attendance/summary/page | 鑾峰緱鑰冨嫟缁熻鍒嗛〉鍒楄〃 |
+| GET | /hrm/attendance/summary/get | 鑾峰緱鍛樺伐鏈堝害姹囨�� |
+| POST | /hrm/attendance/summary/generate | 鐢熸垚鏈堝害鑰冨嫟姹囨�� |
+| GET | /hrm/attendance/summary/export-excel | 瀵煎嚭鑰冨嫟缁熻 Excel |
+
+**璇锋眰鍙傛暟锛坓enerate锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| yearMonth | String | 鏄� | 骞存湀锛屾牸寮忥細yyyy-MM |
+| deptId | Long | 鍚� | 閮ㄩ棬ID锛堟寚瀹氶儴闂ㄦ椂濉啓锛� |
+| userId | Long | 鍚� | 鍛樺伐ID锛堟寚瀹氬憳宸ユ椂濉啓锛� |
+
+### 鍓嶇淇敼鐐�
+
+#### 1. 鍒楄〃椤甸潰
+
+```html
+<el-form :inline="true">
+ <el-form-item label="骞存湀">
+ <el-date-picker v-model="queryParams.yearMonth" type="month" value-format="YYYY-MM" placeholder="閫夋嫨骞存湀" />
+ </el-form-item>
+ <el-form-item label="閮ㄩ棬">
+ <el-tree-select v-model="queryParams.deptId" :data="deptList" clearable check-strictly placeholder="閫夋嫨閮ㄩ棬" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ <el-button type="success" @click="handleGenerate">鐢熸垚缁熻</el-button>
+ <el-button type="warning" @click="handleExport">瀵煎嚭</el-button>
+ </el-form-item>
+</el-form>
+
+<el-table :data="list">
+ <el-table-column label="鍛樺伐濮撳悕" prop="userName" />
+ <el-table-column label="閮ㄩ棬" prop="deptName" />
+ <el-table-column label="骞存湀" prop="yearMonth" />
+ <el-table-column label="搴斿嚭鍕ゅぉ鏁�" prop="workDays" />
+ <el-table-column label="瀹為檯鍑哄嫟澶╂暟" prop="actualDays" />
+ <el-table-column label="杩熷埌娆℃暟" prop="lateCount">
+ <template #default="{ row }">
+ <el-tag v-if="row.lateCount > 0" type="warning">{{ row.lateCount }}</el-tag>
+ <span v-else>{{ row.lateCount }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏃╅��娆℃暟" prop="earlyCount">
+ <template #default="{ row }">
+ <el-tag v-if="row.earlyCount > 0" type="warning">{{ row.earlyCount }}</el-tag>
+ <span v-else>{{ row.earlyCount }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="缂哄崱娆℃暟" prop="absentCount">
+ <template #default="{ row }">
+ <el-tag v-if="row.absentCount > 0" type="danger">{{ row.absentCount }}</el-tag>
+ <span v-else>{{ row.absentCount }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍔犵彮鏃堕暱(h)" prop="overtimeHours" />
+ <el-table-column label="璇峰亣鏃堕暱(h)" prop="leaveHours" />
+</el-table>
+```
+
+#### 2. 鐢熸垚缁熻寮圭獥
+
+```html
+<el-dialog title="鐢熸垚鑰冨嫟缁熻" v-model="generateVisible">
+ <el-form :model="generateForm" label-width="100px">
+ <el-form-item label="骞存湀">
+ <el-date-picker v-model="generateForm.yearMonth" type="month" value-format="YYYY-MM" placeholder="閫夋嫨骞存湀" />
+ </el-form-item>
+ <el-form-item label="鐢熸垚鑼冨洿">
+ <el-radio-group v-model="generateForm.rangeType">
+ <el-radio value="all">鍏ㄥ叕鍙�</el-radio>
+ <el-radio value="dept">鎸囧畾閮ㄩ棬</el-radio>
+ <el-radio value="user">鎸囧畾鍛樺伐</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item v-if="generateForm.rangeType === 'dept'" label="閮ㄩ棬">
+ <el-tree-select v-model="generateForm.deptId" :data="deptList" check-strictly placeholder="閫夋嫨閮ㄩ棬" />
+ </el-form-item>
+ <el-form-item v-if="generateForm.rangeType === 'user'" label="鍛樺伐">
+ <el-select v-model="generateForm.userId" filterable placeholder="閫夋嫨鍛樺伐">
+ <el-option v-for="item in employeeList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="generateVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleGenerateSubmit">鐢熸垚</el-button>
+ </template>
+</el-dialog>
+```
+
+#### 3. API 鏂规硶
+
+```js
+export function getAttendanceSummaryPage(params) {
+ return request({ url: '/hrm/attendance/summary/page', method: 'get', params })
+}
+
+export function getMonthlySummary(params) {
+ return request({ url: '/hrm/attendance/summary/get', method: 'get', params })
+}
+
+export function generateSummary(params) {
+ return request({ url: '/hrm/attendance/summary/generate', method: 'post', params })
+}
+```
+
+---
+
+## 浜斻�佽�冨嫟寮傚父鐢宠鎻愪氦
+
+### 淇敼鐐�
+
+鍘熸彁浜ゆ帴鍙� `/hrm/attendance/exception/submit` 闇�澧炲姞 `processDefinitionKey` 鍙傛暟銆�
+
+**鍘熸帴鍙o細**
+```js
+POST /hrm/attendance/exception/submit?id=xxx
+```
+
+**鏂版帴鍙o細**
+```js
+POST /hrm/attendance/exception/submit?id=xxx&processDefinitionKey=xxx
+```
+
+### 鍓嶇淇敼
+
+```html
+<!-- 鎻愪氦寮圭獥澧炲姞娴佺▼閫夋嫨 -->
+<el-dialog title="鎻愪氦琛ュ崱鐢宠" v-model="submitVisible">
+ <el-form :model="submitForm" label-width="100px">
+ <el-form-item label="瀹℃壒娴佺▼">
+ <el-select v-model="submitForm.processDefinitionKey" placeholder="閫夋嫨瀹℃壒娴佺▼">
+ <el-option v-for="item in processList" :key="item.key" :label="item.name" :value="item.key" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="submitVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit">鎻愪氦</el-button>
+ </template>
+</el-dialog>
+```
+
+```js
+// 鎻愪氦鏂规硶
+const handleSubmit = async () => {
+ await submitException({
+ id: currentId,
+ processDefinitionKey: submitForm.processDefinitionKey
+ })
+ ElMessage.success('鎻愪氦鎴愬姛')
+ submitVisible = false
+ getList()
+}
+```
+
+---
+
+## 鍏�佸瓧鍏告暟鎹厤缃�
+
+鍦ㄧ郴缁熷瓧鍏哥鐞嗕腑娣诲姞浠ヤ笅瀛楀吀锛�
+
+### 1. hrm_clock_type - 鎵撳崱绫诲瀷
+
+| 瀛楀吀鍊� | 瀛楀吀鏍囩 |
+|--------|----------|
+| 1 | 姝e父 |
+| 2 | 杩熷埌 |
+| 3 | 鏃╅�� |
+| 4 | 缂哄崱 |
+
+### 2. hrm_holiday_type - 鑺傚亣鏃ョ被鍨�
+
+| 瀛楀吀鍊� | 瀛楀吀鏍囩 |
+|--------|----------|
+| 1 | 浼戞伅鏃� |
+| 2 | 宸ヤ綔鏃� |
+
+### 3. hrm_is_legal_holiday - 鏄惁娉曞畾鑺傚亣鏃�
+
+| 瀛楀吀鍊� | 瀛楀吀鏍囩 |
+|--------|----------|
+| 0 | 鍚� |
+| 1 | 鏄� |
+
+### 4. hrm_data_source - 鏁版嵁鏉ユ簮
+
+| 瀛楀吀鍊� | 瀛楀吀鏍囩 |
+|--------|----------|
+| 1 | 鎵撳崱 |
+| 2 | 瀵煎叆 |
+| 3 | 琛ュ崱瀹℃壒 |
+
+---
+
+## 涓冦�佽彍鍗曢厤缃�
+
+鍦ㄧ郴缁熻彍鍗曚腑纭浠ヤ笅鑿滃崟宸查厤缃細
+
+| 鑿滃崟ID | 鑿滃崟鍚嶇О | 璺敱璺緞 | 缁勪欢璺緞 |
+|--------|----------|----------|----------|
+| 6013 | 鑰冨嫟瑙勫垯 | rule | hrm/attendance/rule/index |
+| 6014 | 鑺傚亣鏃ラ厤缃� | holiday | hrm/attendance/holiday/index |
+| 6015 | 鑰冨嫟缁熻 | summary | hrm/attendance/summary/index |
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. **鎵撳崱閫昏緫**锛氭墦鍗′細鑷姩鍒ゆ柇鏄惁宸ヤ綔鏃ャ�佽繜鍒般�佹棭閫�锛屽墠绔彧闇�浼犲叆鎵撳崱鍦扮偣鍗冲彲
+2. **鑰冨嫟瑙勫垯浼樺厛绾�**锛氶儴闂ㄤ笓灞炶鍒欎紭鍏堬紝鍏舵鏌ユ壘鍏ㄥ眬瑙勫垯锛堜笉閫夐儴闂級
+3. **鑺傚亣鏃ュ垽鏂�**锛氭湁閰嶇疆鍒欐寜閰嶇疆锛屾棤閰嶇疆鍒欐寜鍛ㄦ湯鍒ゆ柇锛堝懆鍏懆鏃ヤ负浼戞伅鏃ワ級
+4. **琛ュ崱瀹℃壒**锛氬鎵归�氳繃鍚庝細鑷姩鏇存柊鑰冨嫟璁板綍锛屾棤闇�鎵嬪姩鎿嶄綔
+5. **缁熻鐢熸垚**锛氬缓璁瘡鏈堢粨鏉熷悗鎵嬪姩瑙﹀彂鐢熸垚缁熻锛屾垨閰嶇疆瀹氭椂浠诲姟鑷姩鐢熸垚
\ No newline at end of file
diff --git a/docs/hrm_delete_api.md b/docs/hrm_delete_api.md
new file mode 100644
index 0000000..488923e
--- /dev/null
+++ b/docs/hrm_delete_api.md
@@ -0,0 +1,409 @@
+# HRM 妯″潡鍒犻櫎鎺ュ彛琛ュ叏
+
+## 姒傝堪
+
+涓� HRM 妯″潡涓己澶卞垹闄ゆ帴鍙g殑 Controller 琛ュ叏鍒犻櫎鍔熻兘锛屼富瑕佹秹鍙婂憳宸ョ浉鍏冲瓙妯″潡锛堟暀鑲茬粡鍘嗐�佸伐浣滅粡鍘嗐�佺揣鎬ヨ仈绯讳汉锛変互鍙婅柂璧勬ā鍧楃殑鎾ら攢/鍒犻櫎鍔熻兘銆�
+
+## 涓�銆佸憳宸ユā鍧楀垹闄ゆ帴鍙�
+
+### 娑夊強椤甸潰
+
+- 鍛樺伐璇︽儏椤碉紙鏁欒偛缁忓巻銆佸伐浣滅粡鍘嗐�佺揣鎬ヨ仈绯讳汉妯″潡锛�
+
+### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| DELETE | /hrm/employee/education/delete | 鍒犻櫎鏁欒偛缁忓巻 |
+| DELETE | /hrm/employee/work-history/delete | 鍒犻櫎宸ヤ綔缁忓巻 |
+| DELETE | /hrm/employee/emergency-contact/delete | 鍒犻櫎绱ф�ヨ仈绯讳汉 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 璁板綍ID |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 200,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+### 鍓嶇淇敼鐐�
+
+#### 1. 鏁欒偛缁忓巻鍒犻櫎鎸夐挳
+
+```html
+<el-button type="danger" size="small" @click="handleDeleteEducation(row)">
+ 鍒犻櫎
+</el-button>
+```
+
+#### 2. 宸ヤ綔缁忓巻鍒犻櫎鎸夐挳
+
+```html
+<el-button type="danger" size="small" @click="handleDeleteWorkHistory(row)">
+ 鍒犻櫎
+</el-button>
+```
+
+#### 3. 绱ф�ヨ仈绯讳汉鍒犻櫎鎸夐挳
+
+```html
+<el-button type="danger" size="small" @click="handleDeleteEmergencyContact(row)">
+ 鍒犻櫎
+</el-button>
+```
+
+#### 4. 鏂规硶瀹炵幇
+
+```js
+// 鍒犻櫎鏁欒偛缁忓巻
+handleDeleteEducation(row) {
+ this.$modal.confirm('鏄惁纭鍒犻櫎璇ユ暀鑲茬粡鍘嗭紵').then(() => {
+ return deleteEducation(row.id);
+ }).then(() => {
+ this.getEducationList();
+ this.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ }).catch(() => {});
+},
+
+// 鍒犻櫎宸ヤ綔缁忓巻
+handleDeleteWorkHistory(row) {
+ this.$modal.confirm('鏄惁纭鍒犻櫎璇ュ伐浣滅粡鍘嗭紵').then(() => {
+ return deleteWorkHistory(row.id);
+ }).then(() => {
+ this.getWorkHistoryList();
+ this.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ }).catch(() => {});
+},
+
+// 鍒犻櫎绱ф�ヨ仈绯讳汉
+handleDeleteEmergencyContact(row) {
+ this.$modal.confirm('鏄惁纭鍒犻櫎璇ョ揣鎬ヨ仈绯讳汉锛�').then(() => {
+ return deleteEmergencyContact(row.id);
+ }).then(() => {
+ this.getEmergencyContactList();
+ this.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ }).catch(() => {});
+},
+```
+
+#### 5. API 鎺ュ彛瀹氫箟
+
+```js
+// api/hrm/employee.js
+
+// 鍒犻櫎鏁欒偛缁忓巻
+export function deleteEducation(id) {
+ return request({
+ url: '/hrm/employee/education/delete',
+ method: 'delete',
+ params: { id }
+ });
+}
+
+// 鍒犻櫎宸ヤ綔缁忓巻
+export function deleteWorkHistory(id) {
+ return request({
+ url: '/hrm/employee/work-history/delete',
+ method: 'delete',
+ params: { id }
+ });
+}
+
+// 鍒犻櫎绱ф�ヨ仈绯讳汉
+export function deleteEmergencyContact(id) {
+ return request({
+ url: '/hrm/employee/emergency-contact/delete',
+ method: 'delete',
+ params: { id }
+ });
+}
+```
+
+---
+
+## 浜屻�佽柂璧勬牳绠楁ā鍧楁帴鍙�
+
+### 娑夊強椤甸潰
+
+- 钖叕鏍哥畻鍒楄〃椤�
+
+### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/salary/calculation/revoke | 鎵归噺鎾ら攢钖叕鏍哥畻纭 |
+| DELETE | /hrm/salary/calculation/delete | 鍒犻櫎钖叕鏍哥畻璁板綍锛堜粎寰呯‘璁ょ姸鎬侊級 |
+
+**鎾ら攢纭璇锋眰锛�**
+
+```json
+{
+ "ids": [1, 2, 3]
+}
+```
+
+**鍒犻櫎璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 鏍哥畻璁板綍ID |
+
+**鎾ら攢纭閫昏緫璇存槑锛�**
+
+1. 浠呭凡纭鐘舵�侊紙status=10锛夌殑鏍哥畻璁板綍鍙互鎾ら攢
+2. 鎾ら攢鍚庣姸鎬佹仮澶嶄负寰呯‘璁わ紙status=0锛�
+3. 鍚屾椂鍒犻櫎鍏宠仈鐨勮柂璧勫彂鏀炬槑缁�
+4. 濡傛灉宸插彂鏀惧垯涓嶅厑璁告挙閿�
+
+**鍒犻櫎閫昏緫璇存槑锛�**
+
+1. 浠呭緟纭鐘舵�侊紙status=0锛夌殑鏍哥畻璁板綍鍙互鍒犻櫎
+2. 宸茬‘璁ゆ垨宸插彂鏀剧殑璁板綍涓嶅彲鍒犻櫎
+
+### 鍓嶇淇敼鐐�
+
+```js
+// api/hrm/salary/calculation.js
+
+// 鎾ら攢钖叕鏍哥畻纭
+export function revokeSalaryCalculation(ids) {
+ return request({
+ url: '/hrm/salary/calculation/revoke',
+ method: 'post',
+ data: { ids }
+ });
+}
+
+// 鍒犻櫎钖叕鏍哥畻璁板綍
+export function deleteSalaryCalculation(id) {
+ return request({
+ url: '/hrm/salary/calculation/delete',
+ method: 'delete',
+ params: { id }
+ });
+}
+```
+
+```html
+<!-- 鎾ら攢纭鎸夐挳 -->
+<el-button type="warning" size="small" @click="handleRevoke(row)"
+ v-if="row.status === 10">
+ 鎾ら攢纭
+</el-button>
+
+<!-- 鍒犻櫎鎸夐挳 -->
+<el-button type="danger" size="small" @click="handleDelete(row)"
+ v-if="row.status === 0">
+ 鍒犻櫎
+</el-button>
+```
+
+```js
+// 鎾ら攢纭
+handleRevoke(row) {
+ this.$modal.confirm('鎾ら攢纭鍚庯紝璇ヨ褰曞皢鎭㈠涓哄緟纭鐘舵�侊紝鍏宠仈鐨勫彂鏀炬槑缁嗗皢琚垹闄ゃ�傛槸鍚︾户缁紵').then(() => {
+ return revokeSalaryCalculation([row.id]);
+ }).then(() => {
+ this.getList();
+ this.$modal.msgSuccess('鎾ら攢鎴愬姛');
+ }).catch(() => {});
+},
+
+// 鍒犻櫎
+handleDelete(row) {
+ this.$modal.confirm('鏄惁纭鍒犻櫎璇ヨ柂閰牳绠楄褰曪紵').then(() => {
+ return deleteSalaryCalculation(row.id);
+ }).then(() => {
+ this.getList();
+ this.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ }).catch(() => {});
+},
+```
+
+---
+
+## 涓夈�佽柂璧勫彂鏀炬ā鍧楁帴鍙�
+
+### 娑夊強椤甸潰
+
+- 钖祫鍙戞斁鍒楄〃椤�
+- 钖祫鍙戞斁鏄庣粏椤�
+
+### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/salary/payment/revoke-detail | 鎵归噺鎾ら攢钖祫鍙戞斁鏄庣粏 |
+| DELETE | /hrm/salary/payment/delete | 鍒犻櫎钖祫鍙戞斁鍙拌处锛堜粎寰呭彂鏀剧姸鎬侊級 |
+
+**鎾ら攢鍙戞斁璇锋眰锛�**
+
+```json
+{
+ "detailIds": [1, 2, 3]
+}
+```
+
+**鍒犻櫎璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 鍙戞斁鍙拌处ID |
+
+**鎾ら攢鍙戞斁閫昏緫璇存槑锛�**
+
+1. 浠呭凡鍙戞斁鐘舵�侊紙status=10锛夌殑鏄庣粏鍙互鎾ら攢
+2. 鎾ら攢鍚庢槑缁嗙姸鎬佹仮澶嶄负寰呭彂鏀撅紙status=0锛�
+3. 鍚屾椂灏嗗叧鑱旂殑鏍哥畻璁板綍鎭㈠涓哄凡纭锛坰tatus=10锛�
+4. 濡傛灉鏁翠釜鍙拌处鐨勬墍鏈夋槑缁嗛兘琚挙閿�锛屽彴璐︾姸鎬佷篃浼氭仮澶嶄负寰呭彂鏀�
+
+**鍒犻櫎鍙拌处閫昏緫璇存槑锛�**
+
+1. 浠呭緟鍙戞斁鐘舵�侊紙status=0锛夌殑鍙拌处鍙互鍒犻櫎
+2. 濡傛灉瀛樺湪宸插彂鏀炬槑缁嗭紝闇�鍏堟挙閿�鎵�鏈夋槑缁嗘墠鑳藉垹闄�
+3. 鍒犻櫎鍙拌处浼氬悓鏃跺垹闄ゆ墍鏈夊緟鍙戞斁鏄庣粏
+
+### 鍓嶇淇敼鐐�
+
+```js
+// api/hrm/salary/payment.js
+
+// 鎾ら攢钖祫鍙戞斁鏄庣粏
+export function revokeSalaryPaymentDetail(detailIds) {
+ return request({
+ url: '/hrm/salary/payment/revoke-detail',
+ method: 'post',
+ data: { detailIds }
+ });
+}
+
+// 鍒犻櫎钖祫鍙戞斁鍙拌处
+export function deleteSalaryPayment(id) {
+ return request({
+ url: '/hrm/salary/payment/delete',
+ method: 'delete',
+ params: { id }
+ });
+}
+```
+
+```html
+<!-- 鏄庣粏鍒楄〃 - 鎾ら攢鍙戞斁鎸夐挳 -->
+<el-button type="warning" size="small" @click="handleRevokeDetail(row)"
+ v-if="row.status === 10">
+ 鎾ら攢鍙戞斁
+</el-button>
+
+<!-- 鍙拌处鍒楄〃 - 鍒犻櫎鎸夐挳 -->
+<el-button type="danger" size="small" @click="handleDelete(row)"
+ v-if="row.status === 0">
+ 鍒犻櫎
+</el-button>
+```
+
+```js
+// 鎾ら攢鍙戞斁鏄庣粏
+handleRevokeDetail(row) {
+ this.$modal.confirm('鎾ら攢鍙戞斁鍚庯紝璇ユ槑缁嗗皢鎭㈠涓哄緟鍙戞斁鐘舵�侊紝鍏宠仈鐨勬牳绠楄褰曞皢鎭㈠涓哄凡纭銆傛槸鍚︾户缁紵').then(() => {
+ return revokeSalaryPaymentDetail([row.id]);
+ }).then(() => {
+ this.getDetailList();
+ this.$modal.msgSuccess('鎾ら攢鎴愬姛');
+ }).catch(() => {});
+},
+
+// 鍒犻櫎鍙戞斁鍙拌处
+handleDelete(row) {
+ this.$modal.confirm('鍒犻櫎鍚庡皢鍚屾椂鍒犻櫎鎵�鏈夊緟鍙戞斁鏄庣粏锛屾槸鍚︾户缁紵').then(() => {
+ return deleteSalaryPayment(row.id);
+ }).then(() => {
+ this.getList();
+ this.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ }).catch(() => {});
+},
+```
+
+---
+
+## 鍥涖�佺姸鎬佹祦杞鏄�
+
+### 钖叕鏍哥畻鐘舵��
+
+| 鐘舵�佸�� | 鐘舵�佸悕绉� | 璇存槑 |
+|--------|----------|------|
+| 0 | 寰呯‘璁� | 鍒濆鐘舵�侊紝鍙垹闄ゃ�佸彲纭 |
+| 10 | 宸茬‘璁� | 鍙挙閿�纭 |
+| 20 | 宸插彂鏀� | 宸插畬鎴愬彂鏀撅紝涓嶅彲鎿嶄綔 |
+
+### 钖祫鍙戞斁鏄庣粏鐘舵��
+
+| 鐘舵�佸�� | 鐘舵�佸悕绉� | 璇存槑 |
+|--------|----------|------|
+| 0 | 寰呭彂鏀� | 鍒濆鐘舵�� |
+| 10 | 宸插彂鏀� | 鍙挙閿�鍙戞斁 |
+
+### 鐘舵�佹祦杞浘
+
+```
+钖叕鏍哥畻:
+ 寰呯‘璁�(0) --纭--> 宸茬‘璁�(10) --鍙戞斁--> 宸插彂鏀�(20)
+ 宸茬‘璁�(10) --鎾ら攢纭--> 寰呯‘璁�(0)
+ 寰呯‘璁�(0) --鍒犻櫎--> (鍒犻櫎)
+
+钖祫鍙戞斁鏄庣粏:
+ 寰呭彂鏀�(0) --鍙戞斁--> 宸插彂鏀�(10)
+ 宸插彂鏀�(10) --鎾ら攢鍙戞斁--> 寰呭彂鏀�(0)
+
+钖祫鍙戞斁鍙拌处:
+ 寰呭彂鏀�(0) --鍒犻櫎--> (鍒犻櫎)
+ 娉細闇�鍏堟挙閿�鎵�鏈夊凡鍙戞斁鏄庣粏鎵嶈兘鍒犻櫎
+```
+
+---
+
+## 浜斻�佸凡鏈夊垹闄ゆ帴鍙h鏄�
+
+浠ヤ笅妯″潡宸叉湁鍒犻櫎鎺ュ彛锛屾棤闇�鍓嶇棰濆淇敼锛�
+
+| 妯″潡 | 鍒犻櫎鎺ュ彛璺緞 |
+|------|-------------|
+| 鍛樺伐涓昏〃 | /hrm/employee/delete |
+| 璇峰亣鐢宠 | /hrm/leave/delete |
+| 璋冨矖鐢宠 | /hrm/transfer/delete |
+| 绂昏亴鐢宠 | /hrm/resignation/delete |
+| 鑰冨嫟寮傚父鐢宠 | /hrm/attendance/exception/delete |
+| 绀句繚鍏Н閲戦厤缃� | /hrm/salary/social-security-config/delete |
+| 涓◣绋庣巼閰嶇疆 | /hrm/salary/tax-rate-config/delete |
+| 绀句繚鍏Н閲戞柟妗� | /hrm/salary/social-security-scheme/delete |
+| 鍛樺伐绀句繚妗f | /hrm/salary/employee-social-security/delete |
+
+## 鍏�佷笉鎻愪緵鍒犻櫎鎺ュ彛璇存槑
+
+浠ヤ笅妯″潡涓嶆彁渚涘垹闄ゆ帴鍙o紙瀹¤绫绘暟鎹笉鍏佽鐩存帴鍒犻櫎锛夛細
+
+- 鑰冨嫟璁板綍锛�/hrm/attendance/record锛�- 鑰冨嫟鏁版嵁灞炰簬瀹¤鏁版嵁
+
+## 涓冦�佹潈闄愯鏄�
+
+| 鏉冮檺鏍囪瘑 | 璇存槑 |
+|----------|------|
+| hrm:employee:delete | 鍛樺伐鍙婂瓙妯″潡鍒犻櫎鏉冮檺 |
+| hrm:salary:confirm | 钖叕鏍哥畻纭/鎾ら攢鏉冮檺 |
+| hrm:salary:delete | 钖叕鏍哥畻/鍙戞斁鍒犻櫎鏉冮檺 |
+| hrm:salary:pay | 钖祫鍙戞斁/鎾ら攢鏉冮檺 |
+
+## 鍏�佹敞鎰忎簨椤�
+
+1. 鍒犻櫎鎿嶄綔闇�瑕佷簩娆$‘璁わ紝閬垮厤璇垹
+2. 钖叕鏍哥畻纭鍚庝笉鍙洿鎺ュ垹闄わ紝闇�鍏堟挙閿�纭
+3. 钖祫鍙戞斁鍚庝笉鍙洿鎺ュ垹闄ゅ彴璐︼紝闇�鍏堟挙閿�鎵�鏈夊凡鍙戞斁鏄庣粏
+4. 鎾ら攢鎿嶄綔浼氱骇鑱旀洿鏂板叧鑱旀暟鎹紙濡傚彂鏀炬槑缁嗐�佹牳绠楃姸鎬佺瓑锛�
+5. 鍒犻櫎鎿嶄綔闇�鍒锋柊鍒楄〃鏁版嵁
\ No newline at end of file
diff --git a/docs/hrm_frontend.md b/docs/hrm_frontend.md
new file mode 100644
index 0000000..ec42f1c
--- /dev/null
+++ b/docs/hrm_frontend.md
@@ -0,0 +1,803 @@
+# HRM 浜哄姏璧勬簮妯″潡鍓嶇寮�鍙戞枃妗�
+
+## 姒傝堪
+
+HRM妯″潡鍖呭惈鍛樺伐绠$悊銆佷汉浜嬪鎵广�佽�冨嫟绠$悊銆佽柂閰鐞嗗洓澶у姛鑳芥ā鍧椼��
+
+---
+
+## 涓�銆佸憳宸ョ鐞�
+
+### 1.1 娑夊強椤甸潰
+
+- 鍛樺伐鍒楄〃椤� `hrm/employee/index.vue`
+- 鍛樺伐璇︽儏椤� `hrm/employee/detail.vue`
+- 鍛樺伐琛ㄥ崟寮圭獥 `hrm/employee/EmployeeForm.vue`
+- 鏁欒偛缁忓巻缁勪欢 `hrm/employee/components/EducationList.vue`
+- 宸ヤ綔缁忓巻缁勪欢 `hrm/employee/components/WorkHistoryList.vue`
+- 绱ф�ヨ仈绯讳汉缁勪欢 `hrm/employee/components/EmergencyContactList.vue`
+
+### 1.2 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /hrm/employee/page | 鍛樺伐鍒嗛〉鍒楄〃 |
+| GET | /hrm/employee/get | 鑾峰彇鍛樺伐璇︽儏 |
+| POST | /hrm/employee/create | 鏂板鍛樺伐锛堝悓姝ュ垱寤虹郴缁熺敤鎴枫�佹暀鑲茬粡鍘嗐�佸伐浣滅粡鍘嗐�佺揣鎬ヨ仈绯讳汉锛� |
+| PUT | /hrm/employee/update | 鏇存柊鍛樺伐 |
+| DELETE | /hrm/employee/delete | 鍒犻櫎鍛樺伐 |
+| GET | /hrm/employee/education/list | 鑾峰彇鏁欒偛缁忓巻鍒楄〃 |
+| GET | /hrm/employee/work-history/list | 鑾峰彇宸ヤ綔缁忓巻鍒楄〃 |
+| GET | /hrm/employee/emergency-contact/list | 鑾峰彇绱ф�ヨ仈绯讳汉鍒楄〃 |
+
+**娉ㄦ剰锛氭暀鑲茬粡鍘嗐�佸伐浣滅粡鍘嗐�佺揣鎬ヨ仈绯讳汉鍙湪鏂板鍛樺伐鏃跺悓姝ユ坊鍔狅紝涓嶆彁渚涘崟鐙淮鎶ゆ帴鍙c��**
+
+### 1.3 鍛樺伐鍒嗛〉鏌ヨ
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| pageNo | Integer | 鏄� | 椤电爜 |
+| pageSize | Integer | 鏄� | 姣忛〉鏁伴噺 |
+| employeeNo | String | 鍚� | 鍛樺伐缂栧彿 |
+| name | String | 鍚� | 鍛樺伐濮撳悕 |
+| deptId | Long | 鍚� | 閮ㄩ棬ID |
+| employeeStatus | Integer | 鍚� | 鍛樺伐鐘舵�侊細1-鍦ㄨ亴锛�2-璇曠敤锛�3-绂昏亴 |
+
+**鍝嶅簲缁撴瀯锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "deptId": 100,
+ "deptName": "鎶�鏈儴",
+ "postId": 1,
+ "postName": "Java寮�鍙戝伐绋嬪笀",
+ "employeeNo": "EMP001",
+ "name": "寮犱笁",
+ "gender": 1,
+ "birthday": "1990-01-15",
+ "phone": "13800138000",
+ "email": "zhangsan@example.com",
+ "idCard": "320102199001150011",
+ "hireDate": "2020-03-01",
+ "regularDate": "2020-06-01",
+ "leaveDate": null,
+ "employeeStatus": 1,
+ "annualLeaveBalance": 5.00,
+ "sickLeaveBalance": 10.00,
+ "bankName": "涓浗宸ュ晢閾惰",
+ "bankAccount": "6222021234567890123",
+ "address": "姹熻嫃鐪佸崡浜競",
+ "createTime": "2024-01-01 10:00:00"
+ }
+ ],
+ "total": 100
+ }
+}
+```
+
+### 1.4 鍛樺伐琛ㄥ崟鏁版嵁
+
+**娉ㄦ剰锛氭柊澧炲憳宸ユ椂浼氳嚜鍔ㄥ垱寤虹郴缁熺敤鎴疯处鍙凤紝鐢ㄦ埛鍚嶅拰榛樿瀵嗙爜鍧囦负鎵嬫満鍙枫��**
+
+```js
+data() {
+ return {
+ form: {
+ id: undefined,
+ deptId: undefined,
+ postId: undefined,
+ employeeNo: undefined, // 绯荤粺鑷姩鐢熸垚
+ name: undefined,
+ gender: undefined,
+ birthday: undefined,
+ phone: undefined, // 鎵嬫満鍙蜂綔涓虹郴缁熺敤鎴疯处鍙凤紝蹇呭~涓斿敮涓�
+ email: undefined,
+ idCard: undefined,
+ hireDate: undefined,
+ regularDate: undefined,
+ employeeStatus: 1,
+ annualLeaveBalance: 0,
+ sickLeaveBalance: 0,
+ salaryStructureId: undefined, // 钖叕缁撴瀯ID
+ baseSalary: undefined, // 鍩烘湰宸ヨ祫锛堢敤浜庤柂閰牳绠楋級
+ roleIds: [], // 瑙掕壊ID鍒楄〃锛堢敤浜庡垎閰嶇郴缁熺敤鎴疯鑹诧級
+ bankName: undefined,
+ bankAccount: undefined,
+ address: undefined,
+ remark: undefined
+ },
+ // 鏁欒偛缁忓巻鍒楄〃
+ educationList: [],
+ // 宸ヤ綔缁忓巻鍒楄〃
+ workHistoryList: [],
+ // 绱ф�ヨ仈绯讳汉鍒楄〃
+ emergencyContactList: [],
+ // 瑙掕壊閫夐」鍒楄〃
+ roleOptions: []
+ }
+}
+```
+
+### 1.5 瑙掕壊閫夋嫨缁勪欢
+
+```html
+<el-form-item label="鐢ㄦ埛瑙掕壊" prop="roleIds">
+ <el-select v-model="form.roleIds" multiple placeholder="璇烽�夋嫨瑙掕壊">
+ <el-option v-for="role in roleOptions" :key="role.id" :label="role.name" :value="role.id" />
+ </el-select>
+</el-form-item>
+```
+
+**鑾峰彇瑙掕壊鍒楄〃鎺ュ彛锛�**
+```
+GET /system/role/simple-list
+```
+
+### 1.6 鏁欒偛缁忓巻琛ㄥ崟
+
+```html
+<el-table :data="educationList" border>
+ <el-table-column label="瀛︽牎鍚嶇О" prop="schoolName" />
+ <el-table-column label="涓撲笟" prop="major" />
+ <el-table-column label="瀛﹀巻" prop="degree">
+ <template #default="{ row }">
+ <dict-tag :type="DICT_TYPE.HRM_DEGREE" :value="row.degree" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏ュ鏃堕棿" prop="startDate" />
+ <el-table-column label="姣曚笟鏃堕棿" prop="endDate" />
+ <el-table-column label="璇佷功缂栧彿" prop="certificateNo" />
+ <el-table-column label="鎿嶄綔" width="120">
+ <template #default="{ $index, row }">
+ <el-button link type="primary" @click="editEducation($index)">缂栬緫</el-button>
+ <el-button link type="danger" @click="deleteEducation($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+</el-table>
+```
+
+### 1.6 瀛楀吀绫诲瀷
+
+| 瀛楀吀绫诲瀷 | 璇存槑 |
+|---------|------|
+| hrm_degree | 瀛﹀巻锛�1-楂樹腑锛�2-澶т笓锛�3-鏈锛�4-纭曞+锛�5-鍗氬+ |
+| hrm_employee_status | 鍛樺伐鐘舵�侊細1-鍦ㄨ亴锛�2-璇曠敤锛�3-绂昏亴 |
+| hrm_gender | 鎬у埆锛�1-鐢凤紝2-濂� |
+
+---
+
+## 浜屻�佷汉浜嬪鎵� - 璇峰亣鐢宠
+
+### 2.1 娑夊強椤甸潰
+
+- 璇峰亣鐢宠鍒楄〃椤� `hrm/approval/leave/index.vue`
+- 璇峰亣鐢宠琛ㄥ崟寮圭獥 `hrm/approval/leave/LeaveForm.vue`
+
+### 2.2 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /hrm/leave/page | 璇峰亣鐢宠鍒嗛〉鍒楄〃 |
+| GET | /hrm/leave/get | 鑾峰彇璇峰亣鐢宠璇︽儏 |
+| POST | /hrm/leave/create | 鏂板璇峰亣鐢宠锛堣崏绋匡級 |
+| PUT | /hrm/leave/update | 鏇存柊璇峰亣鐢宠 |
+| DELETE | /hrm/leave/delete | 鍒犻櫎璇峰亣鐢宠 |
+| POST | /hrm/leave/submit | 鎻愪氦璇峰亣鐢宠锛堝彂璧峰鎵癸級 |
+| POST | /hrm/leave/cancel | 鎾ら攢璇峰亣鐢宠 |
+
+### 2.3 璇峰亣鐢宠琛ㄥ崟鏁版嵁
+
+```js
+data() {
+ return {
+ form: {
+ id: undefined,
+ leaveType: undefined, // 璇峰亣绫诲瀷
+ startTime: undefined, // 寮�濮嬫椂闂�
+ endTime: undefined, // 缁撴潫鏃堕棿
+ duration: undefined, // 璇峰亣鏃堕暱锛堝ぉ锛�
+ reason: undefined, // 璇峰亣鍘熷洜
+ fileUrl: undefined, // 闄勪欢
+ remark: undefined
+ },
+ leaveTypeOptions: [
+ { value: 1, label: '浜嬪亣' },
+ { value: 2, label: '鐥呭亣' },
+ { value: 3, label: '骞村亣' },
+ { value: 4, label: '濠氬亣' },
+ { value: 5, label: '浜у亣' },
+ { value: 6, label: '涓у亣' }
+ ]
+ }
+}
+```
+
+### 2.4 琛ㄥ崟绀轰緥
+
+```html
+<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+ <el-form-item label="璇峰亣绫诲瀷" prop="leaveType">
+ <el-select v-model="form.leaveType" placeholder="璇烽�夋嫨璇峰亣绫诲瀷">
+ <el-option v-for="item in leaveTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="寮�濮嬫椂闂�" prop="startTime">
+ <el-date-picker v-model="form.startTime" type="datetime" placeholder="璇烽�夋嫨寮�濮嬫椂闂�" />
+ </el-form-item>
+ <el-form-item label="缁撴潫鏃堕棿" prop="endTime">
+ <el-date-picker v-model="form.endTime" type="datetime" placeholder="璇烽�夋嫨缁撴潫鏃堕棿" @change="calcDuration" />
+ </el-form-item>
+ <el-form-item label="璇峰亣鏃堕暱" prop="duration">
+ <el-input-number v-model="form.duration" :precision="2" :min="0.5" :step="0.5" />
+ <span style="margin-left: 10px;">澶�</span>
+ <span v-if="form.leaveType === 3" style="margin-left: 10px; color: #999;">
+ 骞村亣浣欓锛歿{ annualLeaveBalance }} 澶�
+ </span>
+ </el-form-item>
+ <el-form-item label="璇峰亣鍘熷洜" prop="reason">
+ <el-input v-model="form.reason" type="textarea" :rows="3" placeholder="璇疯緭鍏ヨ鍋囧師鍥�" maxlength="500" show-word-limit />
+ </el-form-item>
+ <el-form-item label="闄勪欢" prop="fileUrl">
+ <UploadFile v-model="form.fileUrl" />
+ </el-form-item>
+</el-form>
+```
+
+### 2.5 鍒楄〃椤电姸鎬佹爣绛�
+
+```html
+<dict-tag :type="DICT_TYPE.HRM_AUDIT_STATUS" :value="row.status" />
+```
+
+| 鐘舵�佸�� | 鐘舵�佸悕 | 鏍囩棰滆壊 |
+|--------|--------|---------|
+| 0 | 鑽夌 | info |
+| 10 | 瀹℃壒涓� | warning |
+| 20 | 瀹℃壒閫氳繃 | success |
+| 30 | 瀹℃壒椹冲洖 | danger |
+| 40 | 宸叉挙閿� | info |
+
+---
+
+## 涓夈�佷汉浜嬪鎵� - 璋冨矖鐢宠
+
+### 3.1 娑夊強椤甸潰
+
+- 璋冨矖鐢宠鍒楄〃椤� `hrm/approval/transfer/index.vue`
+- 璋冨矖鐢宠琛ㄥ崟寮圭獥 `hrm/approval/transfer/TransferForm.vue`
+
+### 3.2 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /hrm/transfer/page | 璋冨矖鐢宠鍒嗛〉鍒楄〃 |
+| GET | /hrm/transfer/get | 鑾峰彇璋冨矖鐢宠璇︽儏 |
+| POST | /hrm/transfer/create | 鏂板璋冨矖鐢宠锛堣崏绋匡級 |
+| PUT | /hrm/transfer/update | 鏇存柊璋冨矖鐢宠 |
+| DELETE | /hrm/transfer/delete | 鍒犻櫎璋冨矖鐢宠 |
+| POST | /hrm/transfer/submit | 鎻愪氦璋冨矖鐢宠锛堝彂璧峰鎵癸級 |
+| POST | /hrm/transfer/cancel | 鎾ら攢璋冨矖鐢宠 |
+
+### 3.3 璋冨矖鐢宠琛ㄥ崟鏁版嵁
+
+```js
+data() {
+ return {
+ form: {
+ id: undefined,
+ currentDeptId: undefined, // 鍘熼儴闂↖D锛堣嚜鍔ㄨ幏鍙栧綋鍓嶅憳宸ラ儴闂級
+ currentPostId: undefined, // 鍘熷矖浣岻D锛堣嚜鍔ㄨ幏鍙栧綋鍓嶅憳宸ュ矖浣嶏級
+ targetDeptId: undefined, // 鐩爣閮ㄩ棬ID
+ targetPostId: undefined, // 鐩爣宀椾綅ID
+ transferType: undefined, // 璋冨矖绫诲瀷锛�1-骞宠皟锛�2-鍗囪亴锛�3-闄嶈亴
+ effectiveDate: undefined, // 鐢熸晥鏃ユ湡
+ reason: undefined, // 璋冨矖鍘熷洜
+ fileUrl: undefined, // 闄勪欢
+ remark: undefined
+ },
+ transferTypeOptions: [
+ { value: 1, label: '骞宠皟' },
+ { value: 2, label: '鍗囪亴' },
+ { value: 3, label: '闄嶈亴' }
+ ]
+ }
+}
+```
+
+### 3.4 琛ㄥ崟绀轰緥
+
+```html
+<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍘熼儴闂�">
+ <el-input :value="currentDeptName" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍘熷矖浣�">
+ <el-input :value="currentPostName" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐩爣閮ㄩ棬" prop="targetDeptId">
+ <DeptTreeSelect v-model="form.targetDeptId" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐩爣宀椾綅" prop="targetPostId">
+ <el-select v-model="form.targetPostId" placeholder="璇烽�夋嫨鐩爣宀椾綅">
+ <el-option v-for="post in postOptions" :key="post.id" :label="post.name" :value="post.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="璋冨矖绫诲瀷" prop="transferType">
+ <el-radio-group v-model="form.transferType">
+ <el-radio v-for="item in transferTypeOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="鐢熸晥鏃ユ湡" prop="effectiveDate">
+ <el-date-picker v-model="form.effectiveDate" type="date" placeholder="璇烽�夋嫨鐢熸晥鏃ユ湡" />
+ </el-form-item>
+ <el-form-item label="璋冨矖鍘熷洜" prop="reason">
+ <el-input v-model="form.reason" type="textarea" :rows="3" placeholder="璇疯緭鍏ヨ皟宀楀師鍥�" maxlength="500" show-word-limit />
+ </el-form-item>
+</el-form>
+```
+
+---
+
+## 鍥涖�佷汉浜嬪鎵� - 绂昏亴鐢宠
+
+### 4.1 娑夊強椤甸潰
+
+- 绂昏亴鐢宠鍒楄〃椤� `hrm/approval/resignation/index.vue`
+- 绂昏亴鐢宠琛ㄥ崟寮圭獥 `hrm/approval/resignation/ResignationForm.vue`
+
+### 4.2 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /hrm/resignation/page | 绂昏亴鐢宠鍒嗛〉鍒楄〃 |
+| GET | /hrm/resignation/get | 鑾峰彇绂昏亴鐢宠璇︽儏 |
+| POST | /hrm/resignation/create | 鏂板绂昏亴鐢宠锛堣崏绋匡級 |
+| PUT | /hrm/resignation/update | 鏇存柊绂昏亴鐢宠 |
+| DELETE | /hrm/resignation/delete | 鍒犻櫎绂昏亴鐢宠 |
+| POST | /hrm/resignation/submit | 鎻愪氦绂昏亴鐢宠锛堝彂璧峰鎵癸級 |
+| POST | /hrm/resignation/cancel | 鎾ら攢绂昏亴鐢宠 |
+| PUT | /hrm/resignation/handover | 鏇存柊浜ゆ帴鐘舵�� |
+
+### 4.3 绂昏亴鐢宠琛ㄥ崟鏁版嵁
+
+```js
+data() {
+ return {
+ form: {
+ id: undefined,
+ resignationType: undefined, // 绂昏亴绫诲瀷锛�1-涓诲姩绂昏亴锛�2-琚姩绂昏亴锛�3-鍚堝悓鍒版湡
+ expectedDate: undefined, // 棰勮绂昏亴鏃ユ湡
+ reason: undefined, // 绂昏亴鍘熷洜
+ handoverUserId: undefined, // 浜ゆ帴浜篒D
+ fileUrl: undefined, // 闄勪欢
+ remark: undefined
+ },
+ resignationTypeOptions: [
+ { value: 1, label: '涓诲姩绂昏亴' },
+ { value: 2, label: '琚姩绂昏亴' },
+ { value: 3, label: '鍚堝悓鍒版湡' }
+ ]
+ }
+}
+```
+
+### 4.4 浜ゆ帴鐘舵��
+
+| 鐘舵�佸�� | 鐘舵�佸悕 |
+|--------|--------|
+| 0 | 鏈紑濮� |
+| 10 | 杩涜涓� |
+| 20 | 宸插畬鎴� |
+
+---
+
+## 浜斻�佽�冨嫟绠$悊
+
+### 5.1 娑夊強椤甸潰
+
+- 鑰冨嫟璁板綍鍒楄〃椤� `hrm/attendance/record/index.vue`
+- 鑰冨嫟璁板綍瀵煎叆寮圭獥 `hrm/attendance/record/ImportForm.vue`
+- 鑰冨嫟寮傚父鐢宠椤� `hrm/attendance/exception/index.vue`
+
+### 5.2 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /hrm/attendance/record/page | 鑰冨嫟璁板綍鍒嗛〉鍒楄〃 |
+| GET | /hrm/attendance/record/statistics | 鑰冨嫟缁熻锛堟寜鏈堬級 |
+| POST | /hrm/attendance/record/import | 瀵煎叆鑰冨嫟璁板綍 |
+| GET | /hrm/attendance/record/export | 瀵煎嚭鑰冨嫟璁板綍 |
+| GET | /hrm/attendance/exception/page | 鑰冨嫟寮傚父鐢宠鍒嗛〉鍒楄〃 |
+| POST | /hrm/attendance/exception/create | 鏂板鑰冨嫟寮傚父鐢宠 |
+| POST | /hrm/attendance/exception/submit | 鎻愪氦鑰冨嫟寮傚父鐢宠 |
+
+### 5.3 鑰冨嫟璁板綍鏌ヨ鍙傛暟
+
+```js
+data() {
+ return {
+ queryParams: {
+ pageNo: 1,
+ pageSize: 10,
+ userId: undefined, // 鍛樺伐ID
+ deptId: undefined, // 閮ㄩ棬ID
+ date: [], // 鑰冨嫟鏃ユ湡鑼冨洿
+ clockInType: undefined, // 涓婄彮鎵撳崱绫诲瀷
+ clockOutType: undefined // 涓嬬彮鎵撳崱绫诲瀷
+ }
+ }
+}
+```
+
+### 5.4 鑰冨嫟璁板綍鍝嶅簲缁撴瀯
+
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "userId": 1,
+ "userName": "寮犱笁",
+ "deptId": 100,
+ "deptName": "鎶�鏈儴",
+ "date": "2024-01-15",
+ "clockInTime": "2024-01-15 08:55:00",
+ "clockOutTime": "2024-01-15 18:30:00",
+ "clockInType": 1,
+ "clockInTypeName": "姝e父",
+ "clockOutType": 1,
+ "clockOutTypeName": "姝e父",
+ "workHours": 9.58,
+ "overtimeHours": 0.50,
+ "location": "鍏徃鎬婚儴",
+ "dataSource": 1,
+ "dataSourceName": "鎵撳崱"
+ }
+ ],
+ "total": 100
+ }
+}
+```
+
+### 5.5 鎵撳崱绫诲瀷
+
+| 绫诲瀷鍊� | 绫诲瀷鍚� | 棰滆壊 |
+|--------|--------|------|
+| 1 | 姝e父 | success |
+| 2 | 杩熷埌/鏃╅�� | warning |
+| 3 | 缂哄崱 | danger |
+
+### 5.6 瀵煎叆妯℃澘
+
+| 鍒楀悕 | 蹇呭~ | 璇存槑 |
+|------|------|------|
+| 鍛樺伐缂栧彿 | 鏄� | |
+| 鍛樺伐濮撳悕 | 鏄� | |
+| 鑰冨嫟鏃ユ湡 | 鏄� | 鏍煎紡锛歽yyy-MM-dd |
+| 涓婄彮鎵撳崱鏃堕棿 | 鍚� | 鏍煎紡锛欻H:mm:ss |
+| 涓嬬彮鎵撳崱鏃堕棿 | 鍚� | 鏍煎紡锛欻H:mm:ss |
+| 鎵撳崱鍦扮偣 | 鍚� | |
+| 澶囨敞 | 鍚� | |
+
+---
+
+## 鍏�佽柂閰鐞�
+
+### 6.1 娑夊強椤甸潰
+
+- 钖叕缁撴瀯閰嶇疆椤� `hrm/salary/structure/index.vue`
+- 鍛樺伐钖叕妗f椤� `hrm/salary/employee-salary/index.vue`
+- 钖叕鏍哥畻椤� `hrm/salary/calculation/index.vue`
+- 钖祫鍙戞斁椤� `hrm/salary/payment/index.vue`
+
+### 6.2 钖叕鏍哥畻API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /hrm/salary/calculation/page | 钖叕鏍哥畻鍒嗛〉鍒楄〃 |
+| POST | /hrm/salary/calculation/calculate | 鎵ц钖叕鏍哥畻锛堟寜鍛ㄦ湡锛� |
+| POST | /hrm/salary/calculation/confirm | 纭钖叕鏍哥畻 |
+| GET | /hrm/salary/calculation/export | 瀵煎嚭钖叕鏍哥畻琛� |
+
+### 6.3 钖叕鏍哥畻鏌ヨ鍙傛暟
+
+```js
+data() {
+ return {
+ queryParams: {
+ pageNo: 1,
+ pageSize: 10,
+ period: undefined, // 鏍哥畻鍛ㄦ湡锛堝 2024-01锛�
+ userId: undefined, // 鍛樺伐ID
+ deptId: undefined, // 閮ㄩ棬ID
+ status: undefined // 鐘舵�侊細0-寰呯‘璁わ紝10-宸茬‘璁わ紝20-宸插彂鏀�
+ }
+ }
+}
+```
+
+### 6.4 钖叕鏍哥畻鍝嶅簲缁撴瀯
+
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "no": "SC202401001",
+ "period": "2024-01",
+ "userId": 1,
+ "userName": "寮犱笁",
+ "deptId": 100,
+ "deptName": "鎶�鏈儴",
+ "baseSalary": 10000.00,
+ "performanceSalary": 2000.00,
+ "overtimePay": 500.00,
+ "mealAllowance": 300.00,
+ "transportAllowance": 200.00,
+ "otherAllowance": 0.00,
+ "socialSecurityDeduction": 1050.00,
+ "housingFundDeduction": 1200.00,
+ "taxDeduction": 290.00,
+ "otherDeduction": 0.00,
+ "actualSalary": 10460.00,
+ "workDays": 22,
+ "overtimeHours": 8.00,
+ "status": 0,
+ "statusName": "寰呯‘璁�"
+ }
+ ],
+ "total": 100
+ }
+}
+```
+
+### 6.5 钖叕鏍哥畻鐘舵��
+
+| 鐘舵�佸�� | 鐘舵�佸悕 | 棰滆壊 |
+|--------|--------|------|
+| 0 | 寰呯‘璁� | warning |
+| 10 | 宸茬‘璁� | success |
+| 20 | 宸插彂鏀� | info |
+
+---
+
+## 涓冦�佽彍鍗曢厤缃�
+
+```js
+// router/modules/hrm.js
+export default {
+ path: '/hrm',
+ component: Layout,
+ name: 'Hrm',
+ meta: { title: '浜轰簨绠$悊', icon: 'ep:user' },
+ children: [
+ {
+ path: 'employee',
+ name: 'HrmEmployee',
+ component: () => import('@/views/hrm/employee/index.vue'),
+ meta: { title: '鍛樺伐绠$悊' }
+ },
+ {
+ path: 'leave',
+ name: 'HrmLeave',
+ component: () => import('@/views/hrm/approval/leave/index.vue'),
+ meta: { title: '璇峰亣鐢宠' }
+ },
+ {
+ path: 'transfer',
+ name: 'HrmTransfer',
+ component: () => import('@/views/hrm/approval/transfer/index.vue'),
+ meta: { title: '璋冨矖鐢宠' }
+ },
+ {
+ path: 'resignation',
+ name: 'HrmResignation',
+ component: () => import('@/views/hrm/approval/resignation/index.vue'),
+ meta: { title: '绂昏亴鐢宠' }
+ },
+ {
+ path: 'attendance-record',
+ name: 'HrmAttendanceRecord',
+ component: () => import('@/views/hrm/attendance/record/index.vue'),
+ meta: { title: '鑰冨嫟璁板綍' }
+ },
+ {
+ path: 'attendance-exception',
+ name: 'HrmAttendanceException',
+ component: () => import('@/views/hrm/attendance/exception/index.vue'),
+ meta: { title: '鑰冨嫟寮傚父' }
+ },
+ {
+ path: 'salary-calculation',
+ name: 'HrmSalaryCalculation',
+ component: () => import('@/views/hrm/salary/calculation/index.vue'),
+ meta: { title: '钖叕鏍哥畻' }
+ },
+ {
+ path: 'salary-payment',
+ name: 'HrmSalaryPayment',
+ component: () => import('@/views/hrm/salary/payment/index.vue'),
+ meta: { title: '钖祫鍙戞斁' }
+ }
+ ]
+}
+```
+
+---
+
+## 鍏�丄PI 鎺ュ彛鏂囦欢
+
+```js
+// api/hrm/employee.js
+import request from '@/utils/request'
+
+export function getEmployeePage(data) {
+ return request({ url: '/hrm/employee/page', method: 'GET', params: data })
+}
+
+export function getEmployee(id) {
+ return request({ url: '/hrm/employee/get', method: 'GET', params: { id } })
+}
+
+export function createEmployee(data) {
+ return request({ url: '/hrm/employee/create', method: 'POST', data })
+}
+
+export function updateEmployee(data) {
+ return request({ url: '/hrm/employee/update', method: 'PUT', data })
+}
+
+export function deleteEmployee(id) {
+ return request({ url: '/hrm/employee/delete', method: 'DELETE', params: { id } })
+}
+
+// api/hrm/leave.js
+import request from '@/utils/request'
+
+export function getLeavePage(data) {
+ return request({ url: '/hrm/leave/page', method: 'GET', params: data })
+}
+
+export function getLeave(id) {
+ return request({ url: '/hrm/leave/get', method: 'GET', params: { id } })
+}
+
+export function createLeave(data) {
+ return request({ url: '/hrm/leave/create', method: 'POST', data })
+}
+
+export function updateLeave(data) {
+ return request({ url: '/hrm/leave/update', method: 'PUT', data })
+}
+
+export function deleteLeave(id) {
+ return request({ url: '/hrm/leave/delete', method: 'DELETE', params: { id } })
+}
+
+export function submitLeave(id) {
+ return request({ url: '/hrm/leave/submit', method: 'POST', params: { id } })
+}
+
+export function cancelLeave(id) {
+ return request({ url: '/hrm/leave/cancel', method: 'POST', params: { id } })
+}
+
+// api/hrm/attendance.js
+import request from '@/utils/request'
+
+export function getAttendanceRecordPage(data) {
+ return request({ url: '/hrm/attendance/record/page', method: 'GET', params: data })
+}
+
+export function importAttendanceRecord(data) {
+ return request({ url: '/hrm/attendance/record/import', method: 'POST', data })
+}
+
+export function exportAttendanceRecord(data) {
+ return request({ url: '/hrm/attendance/record/export', method: 'GET', params: data, responseType: 'blob' })
+}
+
+// api/hrm/salary.js
+import request from '@/utils/request'
+
+export function getSalaryCalculationPage(data) {
+ return request({ url: '/hrm/salary/calculation/page', method: 'GET', params: data })
+}
+
+export function calculateSalary(data) {
+ return request({ url: '/hrm/salary/calculation/calculate', method: 'POST', data })
+}
+
+export function confirmSalary(data) {
+ return request({ url: '/hrm/salary/calculation/confirm', method: 'POST', data })
+}
+
+export function exportSalaryCalculation(data) {
+ return request({ url: '/hrm/salary/calculation/export', method: 'GET', params: data, responseType: 'blob' })
+}
+```
+
+---
+
+## 涔濄�丅PM 娴佺▼闆嗘垚
+
+### 9.1 娴佺▼瀹氫箟閰嶇疆
+
+闇�瑕佸湪 BPM 绯荤粺涓厤缃互涓嬪鎵瑰垎绫诲拰娴佺▼瀹氫箟锛�
+
+| 涓氬姟绫诲瀷 | 鍒嗙被缂栫爜 | 娴佺▼瀹氫箟Key | 璇存槑 |
+|---------|---------|------------|------|
+| 璇峰亣鐢宠 | hrm_leave_approve | hrm_leave | 璇峰亣瀹℃壒娴佺▼ |
+| 璋冨矖鐢宠 | hrm_transfer_approve | hrm_transfer | 璋冨矖瀹℃壒娴佺▼ |
+| 绂昏亴鐢宠 | hrm_resignation_approve | hrm_resignation | 绂昏亴瀹℃壒娴佺▼ |
+
+### 9.2 鎻愪氦瀹℃壒鎺ュ彛
+
+**璇峰亣鐢宠鎻愪氦锛�**
+
+```
+POST /hrm/leave/submit?id=1&processDefinitionKey=hrm_leave
+```
+
+**鑾峰彇鍙敤娴佺▼瀹氫箟鍒楄〃锛�**
+
+```
+GET /hrm/leave/approve-process-definition-list
+```
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+[
+ {
+ "id": "hrm_leave:1:xxx",
+ "key": "hrm_leave",
+ "name": "璇峰亣瀹℃壒娴佺▼",
+ "version": 1
+ }
+]
+```
+
+### 9.3 瀹℃壒鐘舵�佹祦杞�
+
+| 鐘舵�佸�� | 鐘舵�佸悕 | 璇存槑 |
+|--------|--------|------|
+| 0 | 鑽夌 | 鏂板缓鏈彁浜� |
+| 10 | 瀹℃壒涓� | 宸叉彁浜PM娴佺▼ |
+| 20 | 瀹℃壒閫氳繃 | BPM娴佺▼瀹℃壒閫氳繃 |
+| 30 | 瀹℃壒椹冲洖 | BPM娴佺▼瀹℃壒椹冲洖 |
+| 40 | 宸叉挙閿� | BPM娴佺▼琚挙閿� |
+
+---
+
+## 鍗併�佹敞鎰忎簨椤�
+
+1. **鏁版嵁鏉冮檺**锛氬垪琛ㄦ煡璇細鑷姩鏍规嵁褰撳墠鐢ㄦ埛鐨勯儴闂ㄦ潈闄愯繃婊ゆ暟鎹�
+2. **瀹℃壒娴佺▼**锛氭彁浜ょ敵璇峰悗锛岀姸鎬佸彉涓�"瀹℃壒涓�"锛岄渶绛夊緟BPM娴佺▼瀹屾垚
+3. **骞村亣鏍¢獙**锛氳鍋囩被鍨嬩负骞村亣鏃讹紝闇�鏍¢獙骞村亣浣欓鏄惁鍏呰冻
+4. **鑰冨嫟瀵煎叆**锛氬鍏ユ椂浼氳嚜鍔ㄦ牴鎹�冨嫟瑙勫垯鍒ゆ柇鎵撳崱绫诲瀷
+5. **钖叕鏍哥畻**锛氭牳绠楀墠闇�纭繚褰撴湀鑰冨嫟鏁版嵁宸插鍏ュ畬鎴�
+6. **鏁版嵁杩芥函**锛氭墍鏈夋搷浣滈兘浼氳褰曟搷浣滄棩蹇楋紝鏀寔3涓湀鏁版嵁杩芥函
diff --git a/docs/leave_deduction_frontend.md b/docs/leave_deduction_frontend.md
new file mode 100644
index 0000000..8e11558
--- /dev/null
+++ b/docs/leave_deduction_frontend.md
@@ -0,0 +1,573 @@
+# 璇峰亣绫诲瀷鎵f閰嶇疆 & 钖叕鏍哥畻鑱斿姩 & 钖祫鍙戞斁娴佺▼
+
+## 姒傝堪
+
+钖叕鏍哥畻妯″潡鏂板璇峰亣鎵f鍔熻兘锛屽苟瀹炵幇钖叕鏍哥畻鍒拌柂璧勫彂鏀剧殑鑷姩娴佽浆銆�
+
+## 娑夊強椤甸潰
+
+- 钖叕绠$悊 -> 钖叕璁剧疆 -> 璇峰亣鎵f閰嶇疆
+- 钖叕绠$悊 -> 钖叕鏍哥畻 -> 钖叕鏍哥畻棰勮
+- 钖叕绠$悊 -> 钖祫鍙戞斁 -> 钖祫鍙戞斁鍙拌处
+- 钖叕绠$悊 -> 钖祫鍙戞斁 -> 钖祫鍙戞斁鏄庣粏
+
+---
+
+## 涓�銆佽鍋囨墸娆鹃厤缃�
+
+### 1. 鑾峰彇璇峰亣绫诲瀷閰嶇疆鍒楄〃
+
+**璇锋眰锛�**
+```
+GET /admin-api/hrm/leave-type-config/list
+```
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "id": 1,
+ "leaveType": 1,
+ "name": "浜嬪亣",
+ "deductionRatio": 1.0000,
+ "remark": "鍏ㄩ鎵f",
+ "status": 0
+ },
+ {
+ "id": 2,
+ "leaveType": 2,
+ "name": "鐥呭亣",
+ "deductionRatio": 0.5000,
+ "remark": "鎸�50%鎵f",
+ "status": 0
+ },
+ {
+ "id": 3,
+ "leaveType": 3,
+ "name": "骞村亣",
+ "deductionRatio": 0.0000,
+ "remark": "涓嶆墸娆�",
+ "status": 0
+ }
+ ],
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+**瀛楁璇存槑锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭 |
+| leaveType | Integer | 璇峰亣绫诲瀷锛�1-浜嬪亣, 2-鐥呭亣, 3-骞村亣, 4-濠氬亣, 5-浜у亣, 6-涓у亣 |
+| name | String | 绫诲瀷鍚嶇О |
+| deductionRatio | BigDecimal | 鎵f姣斾緥锛�0-1涔嬮棿锛屽0.5琛ㄧず鎵�50%锛� |
+| remark | String | 澶囨敞 |
+| status | Integer | 鐘舵�侊細0-鍚敤, 1-绂佺敤 |
+
+### 2. 鏇存柊璇峰亣绫诲瀷閰嶇疆
+
+**璇锋眰锛�**
+```
+PUT /admin-api/hrm/leave-type-config/update
+```
+
+**璇锋眰浣擄細**
+```json
+{
+ "id": 2,
+ "deductionRatio": 0.3000,
+ "remark": "鎸�30%鎵f",
+ "status": 0
+}
+```
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 浜屻�佽柂閰牳绠楋紙宸查泦鎴愯鍋囨墸娆撅級
+
+### 1. 棰勮钖叕鏍哥畻
+
+**璇锋眰锛�**
+```
+GET /admin-api/hrm/salary/calculation/preview?period=2026-02&deptId=103
+```
+
+**鍙傛暟璇存槑锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| period | String | 鏄� | 鏍哥畻鍛ㄦ湡锛屾牸寮忓2026-02 |
+| deptId | Long | 鍚� | 閮ㄩ棬ID锛屼笉浼犲垯鏌ヨ鍏ㄩ儴 |
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "id": null,
+ "no": null,
+ "period": "2026-02",
+ "userId": 1,
+ "userName": "寮犱笁",
+ "deptId": 103,
+ "deptName": "鐮斿彂閮�",
+ "baseSalary": 10000.00,
+ "performanceSalary": 2000.00,
+ "overtimePay": 0.00,
+ "mealAllowance": 500.00,
+ "transportAllowance": 300.00,
+ "otherAllowance": 0.00,
+ "socialSecurityDeduction": 1020.00,
+ "housingFundDeduction": 1200.00,
+ "taxDeduction": 0.00,
+ "otherDeduction": 0.00,
+ "leaveDeduction": 460.00,
+ "actualSalary": 10620.00,
+ "workDays": 22,
+ "overtimeHours": 0.00,
+ "status": null,
+ "remark": null
+ }
+ ],
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+**鏂板瀛楁璇存槑锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| leaveDeduction | BigDecimal | 璇峰亣鎵f閲戦 |
+
+### 2. 鎵归噺纭钖叕鏍哥畻
+
+**璇锋眰锛�**
+```
+POST /admin-api/hrm/salary/calculation/confirm
+```
+
+**璇锋眰浣擄細**
+```json
+{
+ "ids": [1, 2, 3]
+}
+```
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 涓夈�佽鍋囨墸娆捐绠楄鍒�
+
+### 璁$畻鍏紡
+
+```
+璇峰亣鎵f = 鏃ュ伐璧� 脳 璇峰亣澶╂暟 脳 鎵f姣斾緥
+鏃ュ伐璧� = 鍩烘湰宸ヨ祫 / 21.75
+```
+
+### 鎵f瑙勫垯
+
+| 璇峰亣绫诲瀷 | 榛樿鎵f姣斾緥 | 璇存槑 |
+|----------|-------------|------|
+| 浜嬪亣 | 100% | 鍏ㄩ鎵f |
+| 鐥呭亣 | 50% | 鎸夋瘮渚嬫墸娆� |
+| 骞村亣 | 0% | 涓嶆墸娆� |
+| 濠氬亣 | 0% | 涓嶆墸娆� |
+| 浜у亣 | 0% | 涓嶆墸娆� |
+| 涓у亣 | 0% | 涓嶆墸娆� |
+
+### 璁$畻绀轰緥
+
+鍛樺伐寮犱笁锛�
+- 鍩烘湰宸ヨ祫锛�10000鍏�
+- 鏃ュ伐璧勶細10000 / 21.75 = 460鍏�
+- 璇峰亣璁板綍锛氫簨鍋�2澶╋紝鐥呭亣1澶�
+
+璁$畻锛�
+- 浜嬪亣鎵f锛�460 脳 2 脳 100% = 920鍏�
+- 鐥呭亣鎵f锛�460 脳 1 脳 50% = 230鍏�
+- 鎬昏鍋囨墸娆撅細920 + 230 = 1150鍏�
+
+---
+
+## 鍥涖�佹暟鎹簱鍙樻洿
+
+鎵ц浠ヤ笅SQL鑴氭湰锛�
+
+```sql
+-- 璇峰亣绫诲瀷鎵f閰嶇疆琛�
+CREATE TABLE IF NOT EXISTS hrm_leave_type_config (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭',
+ leave_type INT NOT NULL COMMENT '璇峰亣绫诲瀷锛�1-浜嬪亣, 2-鐥呭亣, 3-骞村亣, 4-濠氬亣, 5-浜у亣, 6-涓у亣',
+ name VARCHAR(32) NOT NULL COMMENT '绫诲瀷鍚嶇О',
+ deduction_ratio DECIMAL(5,4) NOT NULL DEFAULT 0.0000 COMMENT '鎵f姣斾緥锛�0-1涔嬮棿锛�',
+ remark VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
+ status INT NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤, 1-绂佺敤',
+ create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ creator VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ updater VARCHAR(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ deleted BIT NOT NULL DEFAULT 0 COMMENT '鏄惁鍒犻櫎'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '璇峰亣绫诲瀷鎵f閰嶇疆';
+
+-- 鍒濆鍖栭粯璁ゆ暟鎹�
+INSERT INTO hrm_leave_type_config (leave_type, name, deduction_ratio, remark, status) VALUES
+(1, '浜嬪亣', 1.0000, '鍏ㄩ鎵f', 0),
+(2, '鐥呭亣', 0.5000, '鎸�50%鎵f', 0),
+(3, '骞村亣', 0.0000, '涓嶆墸娆�', 0),
+(4, '濠氬亣', 0.0000, '涓嶆墸娆�', 0),
+(5, '浜у亣', 0.0000, '涓嶆墸娆�', 0),
+(6, '涓у亣', 0.0000, '涓嶆墸娆�', 0);
+
+-- 钖叕鏍哥畻琛ㄦ坊鍔犺鍋囨墸娆惧瓧娈�
+ALTER TABLE hrm_salary_calculation ADD COLUMN leave_deduction DECIMAL(10,2) DEFAULT 0.00 COMMENT '璇峰亣鎵f' AFTER other_deduction;
+```
+
+---
+
+## 浜斻�佸墠绔慨鏀圭偣
+
+### 1. 璇峰亣鎵f閰嶇疆椤甸潰
+
+```html
+<!-- 閰嶇疆鍒楄〃琛ㄦ牸 -->
+<el-table :data="configList">
+ <el-table-column prop="name" label="璇峰亣绫诲瀷" />
+ <el-table-column prop="deductionRatio" label="鎵f姣斾緥">
+ <template #default="{ row }">
+ {{ (row.deductionRatio * 100).toFixed(0) }}%
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" />
+ <el-table-column label="鎿嶄綔">
+ <template #default="{ row }">
+ <el-button @click="handleEdit(row)">缂栬緫</el-button>
+ </template>
+ </el-table-column>
+</el-table>
+
+<!-- 缂栬緫寮圭獥 -->
+<el-dialog v-model="editVisible" title="缂栬緫鎵f姣斾緥">
+ <el-form>
+ <el-form-item label="鎵f姣斾緥">
+ <el-input-number v-model="editForm.deductionRatio" :min="0" :max="100" :step="10" />
+ <span>%</span>
+ </el-form-item>
+ <el-form-item label="澶囨敞">
+ <el-input v-model="editForm.remark" />
+ </el-form-item>
+ </el-form>
+</el-dialog>
+```
+
+### 2. 钖叕鏍哥畻琛ㄦ牸鏂板鍒�
+
+```html
+<el-table-column prop="leaveDeduction" label="璇峰亣鎵f" />
+```
+
+### 3. 鎵归噺纭鎸夐挳
+
+```html
+<el-button @click="handleConfirm">鎵归噺纭</el-button>
+```
+
+```js
+handleConfirm() {
+ const ids = this.selectedRows.map(row => row.id);
+ if (ids.length === 0) {
+ this.$message.warning('璇烽�夋嫨瑕佺‘璁ょ殑璁板綍');
+ return;
+ }
+ request.post('/hrm/salary/calculation/confirm', { ids }).then(res => {
+ this.$message.success('纭鎴愬姛');
+ this.getList();
+ });
+}
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. 璇峰亣鎵f鍙绠楀凡瀹℃壒閫氳繃鐨勮鍋囪褰�
+2. 鍙湁鍚敤鐨勮鍋囩被鍨嬮厤缃墠浼氬弬涓庤绠�
+3. 鏃ュ伐璧勬寜鍥藉鏍囧噯21.75澶╄绠�
+4. 钖叕鏍哥畻棰勮鎺ュ彛瀹炴椂璁$畻锛屼笉渚濊禆鏁版嵁搴撳凡瀛樺偍鐨勬暟鎹�
+
+---
+
+## 鍏�佽柂璧勫彂鏀炬祦绋�
+
+### 涓氬姟娴佺▼鍥�
+
+```
+钖叕鏍哥畻(寰呯‘璁�) 鈫� 鎵归噺纭 鈫� 钖叕鏍哥畻(宸茬‘璁�) 鈫� [鍏ㄩ儴纭鍚庤嚜鍔ㄧ敓鎴愬彂鏀惧彴璐 鈫� 钖祫鍙戞斁(寰呭彂鏀�) 鈫� 鍙戞斁钖祫 鈫� 钖祫鍙戞斁(宸插彂鏀�)
+```
+
+**鑷姩鐢熸垚瑙勫垯**锛氬綋璇ュ懆鏈熸墍鏈夎柂閰牳绠楄褰曢兘纭鍚庯紝绯荤粺鑷姩鐢熸垚钖祫鍙戞斁鍙拌处銆�
+
+### 1. 鐢熸垚钖祫鍙戞斁鍙拌处
+
+灏嗗凡纭鐨勮柂閰牳绠楄褰曡嚜鍔ㄨ浆鍏ュ彂鏀惧彴璐︺��
+
+**璇锋眰锛�**
+```
+POST /admin-api/hrm/salary/payment/generate?period=2026-02
+```
+
+**鍙傛暟璇存槑锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| period | String | 鏄� | 鏍哥畻鍛ㄦ湡 |
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": 1,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+杩斿洖鍙戞斁鍙拌处ID銆�
+
+### 2. 鑾峰彇钖祫鍙戞斁鍙拌处鍒嗛〉
+
+**璇锋眰锛�**
+```
+GET /admin-api/hrm/salary/payment/page?pageNo=1&pageSize=20
+```
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "no": "SF202602010001",
+ "period": "2026-02",
+ "totalAmount": 150000.00,
+ "totalCount": 15,
+ "paymentMethod": null,
+ "paymentTime": null,
+ "status": 0,
+ "fileUrl": null,
+ "remark": null
+ }
+ ],
+ "total": 1
+ },
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+**瀛楁璇存槑锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鍙戞斁鍙拌处ID |
+| no | String | 鍙戞斁鍗曞彿 |
+| period | String | 鍙戞斁鍛ㄦ湡 |
+| totalAmount | BigDecimal | 鍙戞斁鎬婚噾棰� |
+| totalCount | Integer | 鍙戞斁浜烘暟 |
+| paymentMethod | Integer | 鍙戞斁鏂瑰紡锛�1-閾惰杞处锛�2-鐜伴噾 |
+| paymentTime | LocalDateTime | 鍙戞斁鏃堕棿 |
+| status | Integer | 鐘舵�侊細0-寰呭彂鏀撅紝10-宸插彂鏀� |
+| fileUrl | String | 閾惰鍥炲崟闄勪欢 |
+| remark | String | 澶囨敞 |
+
+### 3. 鑾峰彇钖祫鍙戞斁鏄庣粏鍒楄〃
+
+**璇锋眰锛�**
+```
+GET /admin-api/hrm/salary/payment/detail/list?paymentId=1
+```
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "id": 1,
+ "paymentId": 1,
+ "calculationId": 1,
+ "userId": 1,
+ "userName": "寮犱笁",
+ "deptId": 103,
+ "deptName": "鐮斿彂閮�",
+ "actualSalary": 10620.00,
+ "status": 0,
+ "paymentTime": null,
+ "remark": null
+ }
+ ],
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+### 4. 鍙戞斁钖祫锛堟暣涓彴璐︼級
+
+**璇锋眰锛�**
+```
+POST /admin-api/hrm/salary/payment/pay?id=1
+```
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+鍙戞斁鍚庯紝鍙拌处鐘舵�佸彉涓哄凡鍙戞斁锛屾墍鏈夋槑缁嗙姸鎬佸彉涓哄凡鍙戞斁銆�
+
+### 5. 鎵归噺鍙戞斁钖祫鏄庣粏
+
+鍙�夋嫨鎬у彂鏀鹃儴鍒嗗憳宸ョ殑钖祫銆�
+
+**璇锋眰锛�**
+```
+POST /admin-api/hrm/salary/payment/pay-detail
+```
+
+**璇锋眰浣擄細**
+```json
+{
+ "detailIds": [1, 2, 3]
+}
+```
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": true,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+---
+
+## 涓冦�佽柂璧勫彂鏀炬暟鎹簱鍙樻洿
+
+```sql
+-- 钖祫鍙戞斁鏄庣粏琛�
+CREATE TABLE IF NOT EXISTS hrm_salary_payment_detail (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭',
+ payment_id BIGINT NOT NULL COMMENT '鍙戞斁鍙拌处ID',
+ calculation_id BIGINT NOT NULL COMMENT '钖叕鏍哥畻ID',
+ user_id BIGINT NOT NULL COMMENT '鍛樺伐ID',
+ dept_id BIGINT DEFAULT NULL COMMENT '閮ㄩ棬ID',
+ actual_salary DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '瀹炲彂宸ヨ祫',
+ status INT NOT NULL DEFAULT 0 COMMENT '鍙戞斁鐘舵�侊細0-寰呭彂鏀撅紝10-宸插彂鏀�',
+ payment_time DATETIME DEFAULT NULL COMMENT '鍙戞斁鏃堕棿',
+ remark VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
+ create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ creator VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ updater VARCHAR(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ deleted BIT NOT NULL DEFAULT 0 COMMENT '鏄惁鍒犻櫎',
+ INDEX idx_payment_id (payment_id),
+ INDEX idx_calculation_id (calculation_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '钖祫鍙戞斁鏄庣粏';
+```
+
+---
+
+## 鍏�佽柂璧勫彂鏀惧墠绔慨鏀圭偣
+
+### 1. 钖祫鍙戞斁鍙拌处椤甸潰
+
+```html
+<!-- 鐢熸垚鍙戞斁鍙拌处鎸夐挳 -->
+<el-button @click="handleGenerate">鐢熸垚鍙戞斁鍙拌处</el-button>
+
+<!-- 鍙拌处鍒楄〃 -->
+<el-table :data="paymentList">
+ <el-table-column prop="no" label="鍙戞斁鍗曞彿" />
+ <el-table-column prop="period" label="鍙戞斁鍛ㄦ湡" />
+ <el-table-column prop="totalAmount" label="鍙戞斁鎬婚噾棰�" />
+ <el-table-column prop="totalCount" label="鍙戞斁浜烘暟" />
+ <el-table-column prop="status" label="鐘舵��">
+ <template #default="{ row }">
+ {{ row.status === 0 ? '寰呭彂鏀�' : '宸插彂鏀�' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔">
+ <template #default="{ row }">
+ <el-button @click="handleViewDetail(row)">鏌ョ湅鏄庣粏</el-button>
+ <el-button v-if="row.status === 0" @click="handlePay(row)">鍙戞斁钖祫</el-button>
+ </template>
+ </el-table-column>
+</el-table>
+```
+
+### 2. 鍙戞斁鏄庣粏寮圭獥
+
+```html
+<el-dialog v-model="detailVisible" title="鍙戞斁鏄庣粏">
+ <el-table :data="detailList">
+ <el-table-column prop="userName" label="鍛樺伐濮撳悕" />
+ <el-table-column prop="deptName" label="閮ㄩ棬" />
+ <el-table-column prop="actualSalary" label="瀹炲彂宸ヨ祫" />
+ <el-table-column prop="status" label="鐘舵��">
+ <template #default="{ row }">
+ {{ row.status === 0 ? '寰呭彂鏀�' : '宸插彂鏀�' }}
+ </template>
+ </el-table-column>
+ </el-table>
+</el-dialog>
+```
+
+### 3. 鐢熸垚鍙戞斁鍙拌处鏂规硶
+
+```js
+handleGenerate() {
+ this.$prompt('璇疯緭鍏ュ彂鏀惧懆鏈�', '鐢熸垚鍙戞斁鍙拌处', {
+ inputPattern: /^\d{4}-\d{2}$/,
+ inputErrorMessage: '鍛ㄦ湡鏍煎紡涓嶆纭紝濡�2026-02'
+ }).then(({ value }) => {
+ request.post(`/hrm/salary/payment/generate?period=${value}`).then(res => {
+ this.$message.success('鐢熸垚鎴愬姛');
+ this.getList();
+ });
+ });
+}
+```
+
+---
+
+## 娉ㄦ剰浜嬮」锛堣柂璧勫彂鏀撅級
+
+1. 鐢熸垚鍙戞斁鍙拌处鍓嶏紝蹇呴』鍏堢‘璁よ柂閰牳绠楄褰�
+2. 姣忎釜鍛ㄦ湡鍙兘鐢熸垚涓�涓彂鏀惧彴璐�
+3. 鐢熸垚鍙拌处鍚庯紝钖叕鏍哥畻鐘舵�佸彉涓�"宸插彂鏀�"
+4. 鍙戞斁钖祫鍚庝笉鍙挙閿�
\ No newline at end of file
diff --git a/docs/salary_calculation.md b/docs/salary_calculation.md
new file mode 100644
index 0000000..473de45
--- /dev/null
+++ b/docs/salary_calculation.md
@@ -0,0 +1,153 @@
+# 钖叕鏍哥畻鍔熻兘
+
+## 娑夊強椤甸潰
+
+- 钖叕鏍哥畻鍒楄〃椤甸潰
+- 钖叕鏍哥畻璇︽儏椤甸潰
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/salary/calculation/calculate | 鎵ц钖叕鏍哥畻 |
+| POST | /hrm/salary/calculation/confirm | 纭钖叕鏍哥畻 |
+| POST | /hrm/salary/calculation/save | 鎵归噺淇濆瓨钖叕鏍哥畻璁板綍 |
+| GET | /hrm/salary/calculation/page | 鑾峰彇钖叕鏍哥畻鍒嗛〉鍒楄〃 |
+| GET | /hrm/salary/calculation/get | 鑾峰彇钖叕鏍哥畻璇︽儏 |
+| GET | /hrm/salary/calculation/preview | 棰勮閮ㄩ棬涓嬫墍鏈夊憳宸ョ殑钖祫鏍哥畻淇℃伅 |
+| GET | /hrm/salary/calculation/export-excel | 瀵煎嚭钖叕鏍哥畻 Excel |
+
+**钖叕鏍哥畻璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| period | String | 鏄� | 鏍哥畻鍛ㄦ湡锛屾牸寮忓 2024-01 |
+| deptId | Long | 鍚� | 閮ㄩ棬ID锛屼负绌哄垯鏍哥畻鎵�鏈夐儴闂� |
+
+**棰勮鎺ュ彛璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| period | String | 鏄� | 鏍哥畻鍛ㄦ湡锛屾牸寮忓 2024-01 |
+| deptId | Long | 鏄� | 閮ㄩ棬ID |
+
+**鎵归噺淇濆瓨鎺ュ彛璇锋眰浣擄細**
+
+```json
+{
+ "period": "2024-01",
+ "list": [
+ {
+ "id": null,
+ "userId": 1,
+ "deptId": 100,
+ "baseSalary": 10000.00,
+ "performanceSalary": 2000.00,
+ "overtimePay": 500.00,
+ "mealAllowance": 300.00,
+ "transportAllowance": 200.00,
+ "otherAllowance": 0.00,
+ "socialSecurityDeduction": 1050.00,
+ "housingFundDeduction": 1200.00,
+ "taxDeduction": 290.00,
+ "otherDeduction": 0.00,
+ "actualSalary": 10460.00,
+ "workDays": 22,
+ "overtimeHours": 8.00,
+ "remark": ""
+ }
+ ]
+}
+```
+
+**钖叕鏍哥畻璁板綍鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鏍哥畻ID |
+| no | String | 鏍哥畻鍗曞彿 |
+| period | String | 鏍哥畻鍛ㄦ湡 |
+| userId | Long | 鍛樺伐ID |
+| deptId | Long | 閮ㄩ棬ID |
+| baseSalary | BigDecimal | 鍩烘湰宸ヨ祫 |
+| performanceSalary | BigDecimal | 缁╂晥宸ヨ祫 |
+| overtimePay | BigDecimal | 鍔犵彮宸ヨ祫 |
+| mealAllowance | BigDecimal | 椁愯ˉ |
+| transportAllowance | BigDecimal | 浜ら�氳ˉ璐� |
+| otherAllowance | BigDecimal | 鍏朵粬琛ヨ创 |
+| socialSecurityDeduction | BigDecimal | 绀句繚鎵i櫎 |
+| housingFundDeduction | BigDecimal | 鍏Н閲戞墸闄� |
+| taxDeduction | BigDecimal | 涓◣鎵i櫎 |
+| otherDeduction | BigDecimal | 鍏朵粬鎵i櫎 |
+| actualSalary | BigDecimal | 瀹炲彂宸ヨ祫 |
+| workDays | Integer | 鍑哄嫟澶╂暟 |
+| overtimeHours | BigDecimal | 鍔犵彮鏃堕暱 |
+| status | Integer | 鐘舵�侊細0-寰呯‘璁わ紝10-宸茬‘璁わ紝20-宸插彂鏀� |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 钖叕鏍哥畻鎸夐挳锛堟坊鍔犻儴闂ㄩ�夋嫨锛�
+
+```html
+<el-form-item label="鏍哥畻閮ㄩ棬">
+ <el-select v-model="calculationDeptId" placeholder="鍏ㄩ儴閮ㄩ棬" clearable>
+ <el-option label="鍏ㄩ儴閮ㄩ棬" value="" />
+ <el-option v-for="dept in deptList" :key="dept.id" :label="dept.name" :value="dept.id" />
+ </el-select>
+</el-form-item>
+<el-button type="primary" @click="handleCalculate">鎵ц鏍哥畻</el-button>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+ return {
+ calculationPeriod: '', // 鏍哥畻鍛ㄦ湡
+ calculationDeptId: null, // 鏍哥畻閮ㄩ棬ID
+ deptList: [], // 閮ㄩ棬鍒楄〃
+ }
+}
+```
+
+### 3. 鏂规硶
+
+```js
+// 鎵ц钖叕鏍哥畻
+handleCalculate() {
+ if (!this.calculationPeriod) {
+ this.$message.warning('璇烽�夋嫨鏍哥畻鍛ㄦ湡')
+ return
+ }
+ const params = {
+ period: this.calculationPeriod
+ }
+ if (this.calculationDeptId) {
+ params.deptId = this.calculationDeptId
+ }
+ request.post('/hrm/salary/calculation/calculate', null, { params })
+ .then(res => {
+ this.$message.success('钖叕鏍哥畻鎵ц鎴愬姛')
+ this.getList() // 鍒锋柊鍒楄〃
+ })
+ .catch(err => {
+ this.$message.error(err.message || '钖叕鏍哥畻鎵ц澶辫触')
+ })
+}
+```
+
+## 鏍哥畻閫昏緫璇存槑
+
+1. **鑾峰彇鍛樺伐**锛氭煡璇㈡墍鏈夊湪鑱岋紙鐘舵��=1锛夊拰璇曠敤鏈燂紙鐘舵��=2锛夊憳宸�
+2. **鑾峰彇钖叕妗f**锛氶�氳繃 `selectLatestByUserId(userId)` 鑾峰彇鍛樺伐鏈�鏂扮敓鏁堢殑钖叕妗f
+3. **钖叕妗f涓嶅瓨鍦ㄦ椂**锛氫娇鐢ㄥ憳宸ヨ〃鐨� `baseSalary` 瀛楁浣滀负鍩烘湰宸ヨ祫锛屽叾浠栬柂璧勯」鐩涓� 0
+4. **璁$畻瀹炲彂宸ヨ祫**锛氬疄鍙戝伐璧� = 鍩烘湰宸ヨ祫 + 缁╂晥宸ヨ祫 + 鍚勯」琛ヨ创 - 鍚勯」鎵i櫎
+5. **闃叉閲嶅鏍哥畻**锛氬悓涓�鍛樺伐鍚屼竴鍛ㄦ湡鍙敓鎴愪竴鏉℃牳绠楄褰�
+6. **鏍哥畻鐘舵��**锛氬垵濮嬬姸鎬佷负寰呯‘璁�(0)锛岀‘璁ゅ悗鍙樹负宸茬‘璁�(10)
+
+## 娉ㄦ剰浜嬮」
+
+- 钖叕鏍哥畻鍓嶉渶瑕佺‘淇濆憳宸ユ湁钖叕妗f鎴栧憳宸ヨ〃涓湁鍩烘湰宸ヨ祫
+- 绀句繚銆佸叕绉噾銆佷釜绋庢墸闄ょ洰鍓嶈涓� 0锛屽悗缁渶瑕佸鎺ュ叿浣撹绠楁湇鍔�
+- 鏍哥畻鍛ㄦ湡鏍煎紡涓� YYYY-MM锛堝 2024-01锛�
+- 鍚屼竴鍛樺伐鍚屼竴鍛ㄦ湡鍙兘鏍哥畻涓�娆★紝閲嶅璋冪敤浼氳烦杩囧凡鏍哥畻鍛樺伐
\ No newline at end of file
diff --git a/docs/salary_deduction.md b/docs/salary_deduction.md
new file mode 100644
index 0000000..f3d446c
--- /dev/null
+++ b/docs/salary_deduction.md
@@ -0,0 +1,721 @@
+# 绀句繚鍏Н閲戜釜绋庢墸闄ゅ姛鑳�
+
+## 娑夊強椤甸潰
+
+- 钖叕绠$悊 -> 绀句繚鍏Н閲戞柟妗�
+- 钖叕绠$悊 -> 涓◣绋庣巼閰嶇疆
+- 钖叕绠$悊 -> 钖叕鏍哥畻
+- 鍛樺伐绠$悊 -> 鍛樺伐绀句繚鍏Н閲戞。妗�
+
+## 鏁版嵁搴撹〃
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| hrm_employee | 鍛樺伐鍩烘湰淇℃伅琛� |
+| hrm_employee_salary | 鍛樺伐钖叕妗f琛� |
+| hrm_employee_social_security | 鍛樺伐绀句繚鍏Н閲戞。妗堣〃锛堢粦瀹氭柟妗� + 涓汉鍖栧熀鏁帮級 |
+| hrm_social_security_scheme | 绀句繚鍏Н閲戞柟妗堣〃锛堟瘮渚嬫ā鏉匡級 |
+| hrm_tax_rate_config | 涓◣绋庣巼閰嶇疆琛紙绱繘绋庣巼锛� |
+
+**璁捐璇存槑锛�**
+- **绀句繚鍏Н閲戞柟妗�**锛氬畾涔夊悇椤圭即绾虫瘮渚嬶紝鍙澶氫釜鍛樺伐澶嶇敤
+- **鍛樺伐绀句繚鍏Н閲戞。妗�**锛氬憳宸ョ粦瀹氭柟妗堬紝璁剧疆涓汉鍖栫殑缂寸撼鍩烘暟
+- 涓�涓ぞ淇濇柟妗堝彲浠ョ粦瀹氬涓憳宸ワ紝涓�涓憳宸ュ彲浠ユ湁澶氫釜绀句繚妗f锛堟寜鐢熸晥鏃ユ湡鍙栨渶鏂帮級
+
+## 琛ㄥ叧鑱斿叧绯�
+
+**閲嶈璇存槑锛氬憳宸ヤ笌绯荤粺鐢ㄦ埛閫氳繃鎵嬫満鍙风粦瀹氾紝userId 鏄郴缁熺敤鎴稩D锛屼笉鏄憳宸D銆�**
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 琛ㄥ叧鑱斿叧绯诲浘 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鈹�
+鈹� system_users (绯荤粺鐢ㄦ埛琛�) 鈹�
+鈹� 鈹溾攢 id (鐢ㄦ埛ID) 鈫愨攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� userId 瀛楁鍏宠仈姝� ID 鈹�
+鈹� 鈹溾攢 username (鐢ㄦ埛鍚嶏紝绛変簬鎵嬫満鍙�) 鈹�
+鈹� 鈹溾攢 mobile (鎵嬫満鍙�) 鈹�
+鈹� 鈹斺攢 nickname (鏄电О锛岀瓑浜庡憳宸ュ鍚�) 鈹�
+鈹� 鈹�
+鈹� hrm_employee (鍛樺伐琛�) 鈹�
+鈹� 鈹溾攢 id (鍛樺伐ID) 鈹�
+鈹� 鈹溾攢 user_id 鈫� 鍏宠仈绯荤粺鐢ㄦ埛ID (system_users.id) 鈫� 缁戝畾鍏崇郴 鈹�
+鈹� 鈹溾攢 phone (鎵嬫満鍙�) 鈫� 鍒涘缓鍛樺伐鏃跺悓姝ュ垱寤虹敤鎴�(鐢ㄦ埛鍚�=鎵嬫満鍙�) 鈹�
+鈹� 鈹溾攢 deptId (閮ㄩ棬ID) 鈹�
+鈹� 鈹溾攢 baseSalary (鍩烘湰宸ヨ祫锛屽厹搴曟暟鎹�) 鈹�
+鈹� 鈹斺攢 salaryStructureId 鈹�
+鈹� 鈹�
+鈹� hrm_employee_salary (鍛樺伐钖叕妗f) 鈹�
+鈹� 鈹溾攢 userId 鈫� 鍏宠仈绯荤粺鐢ㄦ埛ID (system_users.id) 鈹�
+鈹� 鈹溾攢 baseSalary (鍩烘湰宸ヨ祫) 鈹�
+鈹� 鈹溾攢 performanceSalary (缁╂晥宸ヨ祫) 鈹�
+鈹� 鈹斺攢 effectiveDate (鐢熸晥鏃ユ湡) 鈹�
+鈹� 鈹�
+鈹� hrm_employee_social_security (鍛樺伐绀句繚鍏Н閲戞。妗�) 鈹�
+鈹� 鈹溾攢 userId 鈫� 鍏宠仈绯荤粺鐢ㄦ埛ID (system_users.id) 鈹�
+鈹� 鈹溾攢 socialSecuritySchemeId 鈫� 鍏宠仈绀句繚鍏Н閲戞柟妗圛D 鈹�
+鈹� 鈹溾攢 socialSecurityBase (绀句繚缂寸撼鍩烘暟) 鈹�
+鈹� 鈹溾攢 housingFundBase (鍏Н閲戠即绾冲熀鏁�) 鈹�
+鈹� 鈹斺攢 effectiveDate (鐢熸晥鏃ユ湡) 鈹�
+鈹� 鈹�
+鈹� hrm_social_security_scheme (绀句繚鍏Н閲戞柟妗�) 鈹�
+鈹� 鈹溾攢 id (鏂规ID) 鈫愨攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� socialSecuritySchemeId 鍏宠仈姝� ID 鈹�
+鈹� 鈹溾攢 name (鏂规鍚嶇О) 鈹�
+鈹� 鈹溾攢 effectiveDate (鐢熸晥鏃ユ湡) 鈹�
+鈹� 鈹溾攢 鍚勯櫓绉嶇即绾虫瘮渚嬶紙鍏昏�併�佸尰鐤椼�佸け涓氥�佸伐浼ゃ�佺敓鑲层�佸叕绉噾锛� 鈹�
+鈹� 鈹斺攢 鐘舵�併�佸娉� 鈹�
+鈹� 鈹�
+鈹� hrm_salary_calculation (钖叕鏍哥畻璁板綍) 鈹�
+鈹� 鈹溾攢 userId 鈫� 鍏宠仈绯荤粺鐢ㄦ埛ID (system_users.id) 鈹�
+鈹� 鈹溾攢 deptId (閮ㄩ棬ID锛屾潵鑷憳宸ヨ〃) 鈹�
+鈹� 鈹斺攢 鍚勯」钖祫鍜屾墸闄� 鈹�
+鈹� 鈹�
+鈹� hrm_tax_rate_config (鍏ㄥ眬涓◣绋庣巼閰嶇疆) 鈹�
+鈹� 鈹斺攢 7绾х疮杩涚◣鐜囪〃 鈹�
+鈹� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**缁戝畾閫昏緫璇存槑锛�**
+
+1. 鍒涘缓鍛樺伐鏃讹紝绯荤粺鑷姩鍒涘缓涓�涓郴缁熺敤鎴凤紙鐢ㄦ埛鍚�=鎵嬫満鍙凤級
+2. 鍒涘缓鐢ㄦ埛鍚庯紝杩斿洖鐨� userId 瀛樺叆鍛樺伐鐨� `user_id` 瀛楁
+3. 钖叕妗f銆佺ぞ淇濇。妗堛�佹牳绠楄褰曚腑鐨� `userId` 閮芥槸绯荤粺鐢ㄦ埛ID
+4. 鏌ヨ鍛樺伐淇℃伅鏃讹紝鍙互閫氳繃 `userId` 鍏宠仈绯荤粺鐢ㄦ埛鑾峰彇鐧诲綍璐﹀彿淇℃伅
+
+**绀句繚鍏Н閲戦厤缃鏄庯細**
+
+- 绀句繚鍏Н閲戞柟妗堝畾涔夊悇椤圭即绾虫瘮渚嬶紝鍙澶氫釜鍛樺伐澶嶇敤
+- 鍛樺伐绀句繚鍏Н閲戞。妗堢粦瀹氭柟妗堬紝骞惰缃釜浜哄寲鐨勭即绾冲熀鏁�
+- 钖叕鏍哥畻鏃讹細鑾峰彇鍛樺伐鏈�鏂扮ぞ淇濇。妗� 鈫� 鑾峰彇缁戝畾鐨勬柟妗� 鈫� 浣跨敤鏂规鐨勬瘮渚嬭绠楁墸闄�
+
+## 鏁版嵁鑾峰彇浼樺厛绾�
+
+钖叕鏍哥畻鏃讹紝鍚勬暟鎹殑鑾峰彇浼樺厛绾э細
+
+| 鏁版嵁椤� | 浼樺厛鏉ユ簮 | 鍏滃簳鏉ユ簮 |
+|------|---------|---------|
+| 鍩烘湰宸ヨ祫 | `hrm_employee_salary.baseSalary` | `hrm_employee.baseSalary` |
+| 缁╂晥宸ヨ祫 | `hrm_employee_salary.performanceSalary` | 0 |
+| 鍚勯」琛ヨ创 | `hrm_employee_salary.*Allowance` | 0 |
+| 绀句繚鍩烘暟 | `hrm_employee_social_security.socialSecurityBase` | 鍩烘湰宸ヨ祫 |
+| 鍏Н閲戝熀鏁� | `hrm_employee_social_security.housingFundBase` | 鍩烘湰宸ヨ祫 |
+| 绀句繚姣斾緥 | `hrm_social_security_scheme.*PersonalRatio`锛堥�氳繃妗f鐨勬柟妗圛D鑾峰彇锛� | 榛樿姣斾緥 |
+| 鍏Н閲戞瘮渚� | `hrm_social_security_scheme.housingFundPersonalRatio`锛堥�氳繃妗f鐨勬柟妗圛D鑾峰彇锛� | 12% |
+| 涓◣绋庣巼 | `hrm_tax_rate_config` 鎸夊簲绾崇◣鎵�寰楅鍖归厤 | - |
+
+**榛樿缂寸撼姣斾緥锛�**
+
+| 闄╃ | 涓汉姣斾緥 |
+|------|---------|
+| 鍏昏�佷繚闄� | 8% |
+| 鍖荤枟淇濋櫓 | 2% |
+| 澶变笟淇濋櫓 | 0.5% |
+| 鍏Н閲� | 12% |
+
+**userId 浣跨敤鍦烘櫙锛�**
+
+| 鍦烘櫙 | 浣跨敤鏂瑰紡 |
+|------|---------|
+| 鍒涘缓钖叕妗f | userId = employee.getUserId() |
+| 鍒涘缓绀句繚妗f | userId = employee.getUserId() |
+| 钖叕鏍哥畻璁板綍 | userId = employee.getUserId() |
+| 鏌ヨ钖叕妗f | selectLatestByUserId(userId) |
+| 鏌ヨ绀句繚妗f | selectLatestByUserId(userId) |
+
+## API
+
+### 绀句繚鍏Н閲戞柟妗�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/salary/social-security-scheme/create | 鍒涘缓绀句繚鍏Н閲戞柟妗� |
+| PUT | /hrm/salary/social-security-scheme/update | 鏇存柊绀句繚鍏Н閲戞柟妗� |
+| DELETE | /hrm/salary/social-security-scheme/delete | 鍒犻櫎绀句繚鍏Н閲戞柟妗� |
+| GET | /hrm/salary/social-security-scheme/get | 鑾峰彇绀句繚鍏Н閲戞柟妗� |
+| GET | /hrm/salary/social-security-scheme/list | 鑾峰彇绀句繚鍏Н閲戞柟妗堝垪琛� |
+
+**绀句繚鍏Н閲戞柟妗堝瓧娈碉細**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鏂规ID |
+| name | String | 鏂规鍚嶇О |
+| effectiveDate | Date | 鐢熸晥鏃ユ湡 |
+| pensionCompanyRatio | BigDecimal | 鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚嬶紙濡� 0.16 琛ㄧず 16%锛� |
+| pensionPersonalRatio | BigDecimal | 鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚� |
+| medicalCompanyRatio | BigDecimal | 鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥 |
+| medicalPersonalRatio | BigDecimal | 鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥 |
+| unemploymentCompanyRatio | BigDecimal | 澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥 |
+| unemploymentPersonalRatio | BigDecimal | 澶变笟淇濋櫓涓汉缂寸撼姣斾緥 |
+| workInjuryRatio | BigDecimal | 宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥 |
+| maternityRatio | BigDecimal | 鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥 |
+| housingFundCompanyRatio | BigDecimal | 鍏Н閲戜紒涓氱即绾虫瘮渚� |
+| housingFundPersonalRatio | BigDecimal | 鍏Н閲戜釜浜虹即绾虫瘮渚� |
+| status | Integer | 鐘舵�侊細0-鍚敤锛�1-绂佺敤 |
+| remark | String | 澶囨敞 |
+
+### 涓◣绋庣巼閰嶇疆
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/salary/tax-rate-config/create-list | 鎵归噺鍒涘缓涓◣绋庣巼閰嶇疆 |
+| PUT | /hrm/salary/tax-rate-config/update | 鏇存柊涓◣绋庣巼閰嶇疆 |
+| DELETE | /hrm/salary/tax-rate-config/delete | 鍒犻櫎涓◣绋庣巼閰嶇疆 |
+| GET | /hrm/salary/tax-rate-config/get | 鑾峰彇涓◣绋庣巼閰嶇疆 |
+| GET | /hrm/salary/tax-rate-config/list | 鑾峰彇涓◣绋庣巼閰嶇疆鍒楄〃 |
+| GET | /hrm/salary/tax-rate-config/get-effective | 鑾峰彇鐢熸晥鐨勪釜绋庣◣鐜囬厤缃垪琛� |
+
+**涓◣绋庣巼閰嶇疆瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 閰嶇疆ID |
+| name | String | 閰嶇疆鍚嶇О |
+| effectiveDate | Date | 鐢熸晥鏃ユ湡 |
+| level | Integer | 绋庣巼绾ф暟锛�1-7锛� |
+| minAmount | BigDecimal | 搴旂撼绋庢墍寰楅涓嬮檺锛堝惈锛� |
+| maxAmount | BigDecimal | 搴旂撼绋庢墍寰楅涓婇檺锛堜笉鍚級锛�0琛ㄧず鏃犱笂闄� |
+| taxRate | BigDecimal | 绋庣巼锛堝 0.03 琛ㄧず 3%锛� |
+| quickDeduction | BigDecimal | 閫熺畻鎵i櫎鏁� |
+
+### 鍛樺伐绀句繚鍏Н閲戞。妗�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/salary/employee-social-security/create | 鍒涘缓鍛樺伐绀句繚鍏Н閲戞。妗� |
+| PUT | /hrm/salary/employee-social-security/update | 鏇存柊鍛樺伐绀句繚鍏Н閲戞。妗� |
+| DELETE | /hrm/salary/employee-social-security/delete | 鍒犻櫎鍛樺伐绀句繚鍏Н閲戞。妗� |
+| GET | /hrm/salary/employee-social-security/get | 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗� |
+| GET | /hrm/salary/employee-social-security/page | 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堝垎椤� |
+| GET | /hrm/salary/employee-social-security/list-by-user | 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堝巻鍙插垪琛� |
+| GET | /hrm/salary/employee-social-security/get-latest | 鑾峰彇鍛樺伐鏈�鏂扮敓鏁堢殑绀句繚鍏Н閲戞。妗� |
+
+**鍛樺伐绀句繚鍏Н閲戞。妗堝瓧娈碉細**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 妗fID |
+| userId | Long | 绯荤粺鐢ㄦ埛ID锛堝叧鑱斿憳宸ヨ〃锛� |
+| userName | String | 鍛樺伐濮撳悕锛堝搷搴斿瓧娈碉級 |
+| deptName | String | 閮ㄩ棬鍚嶇О锛堝搷搴斿瓧娈碉級 |
+| socialSecuritySchemeId | Long | 绀句繚鍏Н閲戞柟妗圛D |
+| schemeName | String | 鏂规鍚嶇О锛堝搷搴斿瓧娈碉級 |
+| socialSecurityBase | BigDecimal | 绀句繚缂寸撼鍩烘暟锛堝憳宸ヤ釜鎬у寲璁剧疆锛� |
+| housingFundBase | BigDecimal | 鍏Н閲戠即绾冲熀鏁帮紙鍛樺伐涓�у寲璁剧疆锛� |
+| effectiveDate | Date | 鐢熸晥鏃ユ湡 |
+| status | Integer | 鐘舵�侊細0-鍚敤锛�1-绂佺敤 |
+| remark | String | 澶囨敞 |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 涓◣绋庣巼閰嶇疆椤甸潰
+
+```html
+<template>
+ <div class="app-container">
+ <!-- 琛ㄦ牸灞曠ず7绾х疮杩涚◣鐜� -->
+ <el-table :data="list">
+ <el-table-column label="绾ф暟" prop="level" width="80" />
+ <el-table-column label="搴旂撼绋庢墍寰楅">
+ <template #default="{ row }">
+ {{ row.minAmount }} ~ {{ row.maxAmount === 0 ? '鈭�' : row.maxAmount }}
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庣巼" width="100">
+ <template #default="{ row }">
+ {{ (row.taxRate * 100).toFixed(0) }}%
+ </template>
+ </el-table-column>
+ <el-table-column label="閫熺畻鎵i櫎鏁�" prop="quickDeduction" />
+ <el-table-column label="鎿嶄綔" width="100">
+ <template #default="{ row }">
+ <el-button link type="primary" @click="handleEdit(row)">缂栬緫</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+</template>
+```
+
+### 2. 绀句繚鍏Н閲戞柟妗堥〉闈�
+
+```html
+<template>
+ <div class="app-container">
+ <!-- 宸ュ叿鏍� -->
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" @click="handleAdd">鏂板鏂规</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 琛ㄦ牸 -->
+ <el-table :data="list">
+ <el-table-column label="鏂规鍚嶇О" prop="name" />
+ <el-table-column label="鐢熸晥鏃ユ湡" prop="effectiveDate" />
+ <el-table-column label="鍏昏��(浼佷笟)" width="100">
+ <template #default="{ row }">
+ {{ (row.pensionCompanyRatio * 100).toFixed(1) }}%
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏昏��(涓汉)" width="100">
+ <template #default="{ row }">
+ {{ (row.pensionPersonalRatio * 100).toFixed(1) }}%
+ </template>
+ </el-table-column>
+ <el-table-column label="鍖荤枟(涓汉)" width="100">
+ <template #default="{ row }">
+ {{ (row.medicalPersonalRatio * 100).toFixed(1) }}%
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏Н閲�(涓汉)" width="100">
+ <template #default="{ row }">
+ {{ (row.housingFundPersonalRatio * 100).toFixed(1) }}%
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" prop="status">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 0 ? 'success' : 'info'">
+ {{ row.status === 0 ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150">
+ <template #default="{ row }">
+ <el-button link type="primary" @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button link type="danger" @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog :title="title" v-model="open" width="700px">
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+ <el-form-item label="鏂规鍚嶇О" prop="name">
+ <el-input v-model="form.name" placeholder="濡傦細2024骞村崡閫氬競鏍囧噯" />
+ </el-form-item>
+ <el-form-item label="鐢熸晥鏃ユ湡" prop="effectiveDate">
+ <el-date-picker v-model="form.effectiveDate" type="date" placeholder="閫夋嫨鐢熸晥鏃ユ湡" />
+ </el-form-item>
+
+ <el-divider content-position="left">鍏昏�佷繚闄�</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浼佷笟姣斾緥" prop="pensionCompanyRatio">
+ <el-input-number v-model="form.pensionCompanyRatio" :min="0" :max="1" :step="0.01" :precision="4" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓汉姣斾緥" prop="pensionPersonalRatio">
+ <el-input-number v-model="form.pensionPersonalRatio" :min="0" :max="1" :step="0.01" :precision="4" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-divider content-position="left">鍖荤枟淇濋櫓</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浼佷笟姣斾緥" prop="medicalCompanyRatio">
+ <el-input-number v-model="form.medicalCompanyRatio" :min="0" :max="1" :step="0.01" :precision="4" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓汉姣斾緥" prop="medicalPersonalRatio">
+ <el-input-number v-model="form.medicalPersonalRatio" :min="0" :max="1" :step="0.01" :precision="4" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-divider content-position="left">澶变笟/宸ヤ激/鐢熻偛淇濋櫓</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="澶变笟浼佷笟" prop="unemploymentCompanyRatio">
+ <el-input-number v-model="form.unemploymentCompanyRatio" :min="0" :max="1" :step="0.001" :precision="4" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="澶变笟涓汉" prop="unemploymentPersonalRatio">
+ <el-input-number v-model="form.unemploymentPersonalRatio" :min="0" :max="1" :step="0.001" :precision="4" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="宸ヤ激姣斾緥" prop="workInjuryRatio">
+ <el-input-number v-model="form.workInjuryRatio" :min="0" :max="1" :step="0.001" :precision="4" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢熻偛姣斾緥" prop="maternityRatio">
+ <el-input-number v-model="form.maternityRatio" :min="0" :max="1" :step="0.001" :precision="4" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-divider content-position="left">鍏Н閲�</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浼佷笟姣斾緥" prop="housingFundCompanyRatio">
+ <el-input-number v-model="form.housingFundCompanyRatio" :min="0" :max="1" :step="0.01" :precision="4" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓汉姣斾緥" prop="housingFundPersonalRatio">
+ <el-input-number v-model="form.housingFundPersonalRatio" :min="0" :max="1" :step="0.01" :precision="4" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="open = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+export default {
+ data() {
+ return {
+ form: {
+ id: null,
+ name: '',
+ effectiveDate: null,
+ pensionCompanyRatio: 0.16,
+ pensionPersonalRatio: 0.08,
+ medicalCompanyRatio: 0.095,
+ medicalPersonalRatio: 0.02,
+ unemploymentCompanyRatio: 0.005,
+ unemploymentPersonalRatio: 0.005,
+ workInjuryRatio: 0.005,
+ maternityRatio: 0.005,
+ housingFundCompanyRatio: 0.12,
+ housingFundPersonalRatio: 0.12,
+ status: 0,
+ remark: '',
+ },
+ rules: {
+ name: [{ required: true, message: '璇疯緭鍏ユ柟妗堝悕绉�', trigger: 'blur' }],
+ effectiveDate: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃ユ湡', trigger: 'change' }],
+ },
+ list: [],
+ open: false,
+ title: '',
+ }
+ },
+ methods: {
+ getList() {
+ request.get('/hrm/salary/social-security-scheme/list', { params: { status: 0 } })
+ .then(res => { this.list = res.data })
+ },
+ handleAdd() {
+ this.resetForm()
+ this.title = '鏂板绀句繚鍏Н閲戞柟妗�'
+ this.open = true
+ },
+ handleEdit(row) {
+ this.resetForm()
+ this.title = '缂栬緫绀句繚鍏Н閲戞柟妗�'
+ this.form = { ...row }
+ this.open = true
+ },
+ submitForm() {
+ this.$refs.formRef.validate(valid => {
+ if (valid) {
+ const url = this.form.id
+ ? '/hrm/salary/social-security-scheme/update'
+ : '/hrm/salary/social-security-scheme/create'
+ request.post(url, this.form)
+ .then(() => {
+ this.$message.success('鎿嶄綔鎴愬姛')
+ this.open = false
+ this.getList()
+ })
+ }
+ })
+ },
+ }
+}
+</script>
+```
+
+### 3. 鍛樺伐绀句繚鍏Н閲戞。妗堥〉闈�
+
+```html
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储鏍� -->
+ <el-form :model="queryParams" ref="queryFormRef" :inline="true">
+ <el-form-item label="鍛樺伐濮撳悕" prop="userName">
+ <el-input v-model="queryParams.userName" placeholder="璇疯緭鍏ュ憳宸ュ鍚�" />
+ </el-form-item>
+ <el-form-item label="閮ㄩ棬" prop="deptId">
+ <el-select v-model="queryParams.deptId" placeholder="璇烽�夋嫨閮ㄩ棬" clearable>
+ <el-option v-for="dept in deptList" :key="dept.id" :label="dept.name" :value="dept.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 宸ュ叿鏍� -->
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 琛ㄦ牸 -->
+ <el-table :data="list">
+ <el-table-column label="鍛樺伐濮撳悕" prop="userName" />
+ <el-table-column label="閮ㄩ棬" prop="deptName" />
+ <el-table-column label="绀句繚鏂规" prop="schemeName" />
+ <el-table-column label="绀句繚鍩烘暟" prop="socialSecurityBase" />
+ <el-table-column label="鍏Н閲戝熀鏁�" prop="housingFundBase" />
+ <el-table-column label="鐢熸晥鏃ユ湡" prop="effectiveDate" />
+ <el-table-column label="鐘舵��" prop="status">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 0 ? 'success' : 'info'">
+ {{ row.status === 0 ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="200">
+ <template #default="{ row }">
+ <el-button link type="primary" @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button link type="primary" @click="handleHistory(row)">鍘嗗彶</el-button>
+ <el-button link type="danger" @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog :title="title" v-model="open" width="500px">
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+ <el-form-item label="鍛樺伐" prop="userId">
+ <el-select v-model="form.userId" placeholder="璇烽�夋嫨鍛樺伐" filterable>
+ <el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id" />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="绀句繚鏂规" prop="socialSecuritySchemeId">
+ <el-select v-model="form.socialSecuritySchemeId" placeholder="璇烽�夋嫨绀句繚鏂规">
+ <el-option v-for="scheme in schemeList" :key="scheme.id" :label="scheme.name" :value="scheme.id" />
+ </el-select>
+ </el-form-item>
+
+ <el-divider content-position="left">缂寸撼鍩烘暟锛堝憳宸ヤ釜鎬у寲璁剧疆锛�</el-divider>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绀句繚鍩烘暟" prop="socialSecurityBase">
+ <el-input-number v-model="form.socialSecurityBase" :min="0" :precision="2" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏Н閲戝熀鏁�" prop="housingFundBase">
+ <el-input-number v-model="form.housingFundBase" :min="0" :precision="2" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="鐢熸晥鏃ユ湡" prop="effectiveDate">
+ <el-date-picker v-model="form.effectiveDate" type="date" placeholder="閫夋嫨鐢熸晥鏃ユ湡" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="open = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 鍘嗗彶璁板綍寮圭獥 -->
+ <el-dialog title="绀句繚鍏Н閲戝巻鍙茶褰�" v-model="historyOpen" width="600px">
+ <el-table :data="historyList">
+ <el-table-column label="绀句繚鏂规" prop="schemeName" />
+ <el-table-column label="绀句繚鍩烘暟" prop="socialSecurityBase" />
+ <el-table-column label="鍏Н閲戝熀鏁�" prop="housingFundBase" />
+ <el-table-column label="鐢熸晥鏃ユ湡" prop="effectiveDate" />
+ <el-table-column label="鐘舵��">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 0 ? 'success' : 'info'">
+ {{ row.status === 0 ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+export default {
+ data() {
+ return {
+ queryParams: {
+ userName: '',
+ deptId: null,
+ },
+ form: {
+ id: null,
+ userId: null,
+ socialSecuritySchemeId: null,
+ socialSecurityBase: 0,
+ housingFundBase: 0,
+ effectiveDate: null,
+ remark: '',
+ },
+ rules: {
+ userId: [{ required: true, message: '璇烽�夋嫨鍛樺伐', trigger: 'change' }],
+ socialSecuritySchemeId: [{ required: true, message: '璇烽�夋嫨绀句繚鏂规', trigger: 'change' }],
+ socialSecurityBase: [{ required: true, message: '璇疯緭鍏ョぞ淇濆熀鏁�', trigger: 'blur' }],
+ housingFundBase: [{ required: true, message: '璇疯緭鍏ュ叕绉噾鍩烘暟', trigger: 'blur' }],
+ effectiveDate: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃ユ湡', trigger: 'change' }],
+ },
+ list: [],
+ userList: [],
+ schemeList: [],
+ deptList: [],
+ historyList: [],
+ open: false,
+ historyOpen: false,
+ title: '',
+ }
+ },
+ created() {
+ this.getList()
+ this.getSchemeList()
+ },
+ methods: {
+ getSchemeList() {
+ request.get('/hrm/salary/social-security-scheme/list', { params: { status: 0 } })
+ .then(res => { this.schemeList = res.data })
+ },
+ getList() {
+ request.get('/hrm/salary/employee-social-security/page', { params: this.queryParams })
+ .then(res => {
+ this.list = res.data.list
+ this.total = res.data.total
+ })
+ },
+ handleAdd() {
+ this.resetForm()
+ this.title = '鏂板鍛樺伐绀句繚鍏Н閲戞。妗�'
+ this.open = true
+ },
+ handleEdit(row) {
+ this.resetForm()
+ this.title = '缂栬緫鍛樺伐绀句繚鍏Н閲戞。妗�'
+ this.form = { ...row }
+ this.open = true
+ },
+ handleHistory(row) {
+ request.get('/hrm/salary/employee-social-security/list-by-user', { params: { userId: row.userId } })
+ .then(res => {
+ this.historyList = res.data
+ this.historyOpen = true
+ })
+ },
+ submitForm() {
+ this.$refs.formRef.validate(valid => {
+ if (valid) {
+ const url = this.form.id
+ ? '/hrm/salary/employee-social-security/update'
+ : '/hrm/salary/employee-social-security/create'
+ request.post(url, this.form)
+ .then(() => {
+ this.$message.success('鎿嶄綔鎴愬姛')
+ this.open = false
+ this.getList()
+ })
+ }
+ })
+ },
+ handleDelete(row) {
+ this.$confirm('纭鍒犻櫎璇ユ。妗�?', '鎻愮ず').then(() => {
+ request.delete('/hrm/salary/employee-social-security/delete', { params: { id: row.id } })
+ .then(() => {
+ this.$message.success('鍒犻櫎鎴愬姛')
+ this.getList()
+ })
+ })
+ },
+ }
+}
+</script>
+```
+
+## 鎵i櫎璁$畻閫昏緫璇存槑
+
+### 绀句繚鎵i櫎锛堜釜浜洪儴鍒嗭級
+
+姣忎釜鍛樺伐浣跨敤鍏剁粦瀹氭柟妗堜腑鐨勭即绾虫瘮渚嬭绠楋細
+
+```
+绀句繚鎵i櫎 = 绀句繚鍩烘暟 脳 (鍏昏�佷繚闄╀釜浜烘瘮渚� + 鍖荤枟淇濋櫓涓汉姣斾緥 + 澶变笟淇濋櫓涓汉姣斾緥)
+```
+
+- 鍩烘暟鍙栧�硷細鍛樺伐绀句繚妗f鐨� `socialSecurityBase`
+- 姣斾緥鍙栧�硷細閫氳繃 `socialSecuritySchemeId` 鑾峰彇鏂规鐨勬瘮渚�
+- 鑻ユ柟妗堜腑姣斾緥涓虹┖锛屼娇鐢ㄩ粯璁ゅ�硷細鍏昏��8%銆佸尰鐤�2%銆佸け涓�0.5%
+
+### 鍏Н閲戞墸闄わ紙涓汉閮ㄥ垎锛�
+
+姣忎釜鍛樺伐浣跨敤鍏剁粦瀹氭柟妗堜腑鐨勭即绾虫瘮渚嬭绠楋細
+
+```
+鍏Н閲戞墸闄� = 鍏Н閲戝熀鏁� 脳 鍏Н閲戜釜浜烘瘮渚�
+```
+
+- 鍩烘暟鍙栧�硷細鍛樺伐绀句繚妗f鐨� `housingFundBase`
+- 姣斾緥鍙栧�硷細閫氳繃 `socialSecuritySchemeId` 鑾峰彇鏂规鐨勬瘮渚�
+- 鑻ユ柟妗堜腑姣斾緥涓虹┖锛屼娇鐢ㄩ粯璁ゅ�硷細12%
+
+### 涓◣鎵i櫎
+
+```
+搴旂撼绋庢墍寰楅 = 搴斿彂宸ヨ祫 - 绀句繚鎵i櫎 - 鍏Н閲戞墸闄� - 5000锛堣捣寰佺偣锛�
+涓◣ = 搴旂撼绋庢墍寰楅 脳 绋庣巼 - 閫熺畻鎵i櫎鏁�
+```
+
+- 鑻ュ簲绾崇◣鎵�寰楅 鈮� 0锛屽垯涓◣涓� 0
+- 鏍规嵁7绾х疮杩涚◣鐜囪〃鏌ユ壘瀵瑰簲妗d綅
+
+## 绀轰緥璁$畻
+
+鍋囪鍛樺伐锛�
+- 鍩烘湰宸ヨ祫锛�10000 鍏�
+- 缁╂晥宸ヨ祫锛�2000 鍏�
+- 绀句繚鍩烘暟锛�10000 鍏�
+- 鍏Н閲戝熀鏁帮細10000 鍏�
+
+鎸� 2024 骞存爣鍑嗚绠楋細
+
+1. **搴斿彂宸ヨ祫** = 10000 + 2000 = 12000 鍏�
+
+2. **绀句繚鎵i櫎** = 10000 脳 (8% + 2% + 0.5%) = 1050 鍏�
+
+3. **鍏Н閲戞墸闄�** = 10000 脳 12% = 1200 鍏�
+
+4. **搴旂撼绋庢墍寰楅** = 12000 - 1050 - 1200 - 5000 = 4750 鍏�
+
+5. **涓◣** = 4750 脳 3% - 0 = 142.5 鍏�
+
+6. **瀹炲彂宸ヨ祫** = 12000 - 1050 - 1200 - 142.5 = 9607.5 鍏�
+
+## 娉ㄦ剰浜嬮」
+
+- 绀句繚鍏Н閲戦厤缃渶瑕佹牴鎹綋鍦版斂绛栬缃瘮渚嬪拰鍩烘暟涓婁笅闄�
+- 涓◣璧峰緛鐐圭洰鍓嶅浐瀹氫负 5000 鍏�/鏈�
+- 鍛樺伐绀句繚鍏Н閲戞。妗堝彲鍦ㄥ憳宸ョ鐞嗕腑缁存姢锛屾敮鎸佹寜鐢熸晥鏃ユ湡绠$悊鍘嗗彶璁板綍
+- 钖叕鏍哥畻鏃朵細鑷姩璁$畻鍚勯」鎵i櫎锛屾棤闇�鎵嬪姩骞查
diff --git a/docs/wls_module_integration.md b/docs/wls_module_integration.md
new file mode 100644
index 0000000..036c8e8
--- /dev/null
+++ b/docs/wls_module_integration.md
@@ -0,0 +1,730 @@
+# 浠撳偍鐗╂祦妯″潡鍓嶇鏂囨。
+
+## 涓�銆佸姛鑳芥杩�
+
+灏哅ES妯″潡鐨勪粨搴撶鐞嗘彁鍙栦负鐙珛鐨勪竴绾ц彍鍗�"浠撳偍鐗╂祦"锛屽苟瀹炵幇锛�
+- ERP閲囪喘鍏ュ簱瀹℃牳 鈫� MES搴撳瓨鍚屾澧炲姞
+- ERP閿�鍞嚭搴撳鏍� 鈫� MES搴撳瓨鍚屾鍑忓皯
+- 浠撳簱绾у埆瀹℃壒娴佺▼閰嶇疆锛堟瘡涓粨搴撳彲鐙珛鍚敤/绂佺敤瀹℃壒锛�
+
+---
+
+## 浜屻�丼QL鑴氭湰鎵ц椤哄簭
+
+```bash
+# 1. 鑿滃崟璋冩暣锛堝皢浠撳簱绠$悊鎻愬彇涓轰竴绾ц彍鍗曪級
+sql/mysql/wls_menu_init.sql
+
+# 2. 鏁版嵁搴撳垵濮嬪寲锛堝鎵归厤缃〃 + 鍗曟嵁BPM瀛楁锛�
+sql/mysql/wls_database_init.sql
+
+# 3. ERP浠撳簱涓嶮ES浠撳簱鏄犲皠琛�
+sql/mysql/erp_mes_warehouse_mapping.sql
+```
+
+---
+
+## 涓夈�佽彍鍗曠粨鏋�
+
+### 3.1 鏂拌彍鍗曠粨鏋�
+
+```
+浠撳偍鐗╂祦锛堜竴绾ц彍鍗曪紝id=5780锛�
+鈹溾攢鈹� 浠撳簱璁剧疆
+鈹溾攢鈹� 搴撳瓨鐜版湁閲�
+鈹溾攢鈹� 閲囪喘鍏ュ簱
+鈹溾攢鈹� 鍒拌揣閫氱煡
+鈹溾攢鈹� 閲囪喘閫�璐�
+鈹溾攢鈹� 鐢熶骇棰嗘枡
+鈹溾攢鈹� 鐢熶骇閫�鏂�
+鈹溾攢鈹� 浜у搧鍏ュ簱
+鈹溾攢鈹� 鍙戣揣閫氱煡
+鈹溾攢鈹� 閿�鍞嚭搴�
+鈹溾攢鈹� 閿�鍞��璐�
+鈹溾攢鈹� 杞Щ璋冩嫧
+鈹溾攢鈹� 搴撳瓨鐩樼偣
+鈹溾攢鈹� 瑁呯绠$悊
+鈹溾攢鈹� 鏉$爜閰嶇疆
+鈹溾攢鈹� 璧嬭兘绠$悊
+鈹溾攢鈹� SN鐮�
+鈹溾攢鈹� 澶栧崗鍙戞枡
+鈹溾攢鈹� 澶栧崗鍏ュ簱
+鈹溾攢鈹� 鏉傞」鍏ュ簱
+鈹溾攢鈹� 鏉傞」鍑哄簱
+鈹溾攢鈹� 瀹℃壒閰嶇疆锛堟柊澧烇級
+鈹斺攢鈹� 浠撳簱鏄犲皠锛堟柊澧烇紝鍦‥RP鑿滃崟涓嬶級
+```
+
+### 3.2 鍓嶇璺敱閰嶇疆
+
+**鏂规涓�锛氳矾寰勫埆鍚嶏紙鎺ㄨ崘锛�**
+
+```js
+// vite.config.js
+export default defineConfig({
+ resolve: {
+ alias: {
+ '@/views/wls': path.resolve(__dirname, 'src/views/mes/wm')
+ }
+ }
+})
+```
+
+**鏂规浜岋細澶嶅埗缁勪欢**
+
+灏� `src/views/mes/wm` 鐩綍澶嶅埗涓� `src/views/wls`
+
+---
+
+## 鍥涖�佹柊澧為〉闈�
+
+### 4.1 瀹℃壒閰嶇疆椤甸潰
+
+**璺緞**锛歚views/wls/approval/config/index.vue`
+
+**鍔熻兘**锛�
+1. 浠撳簱鍒楄〃灞曠ず
+2. 姣忎釜浠撳簱鍙厤缃叆搴�/鍑哄簱/璋冩嫧/鐩樼偣鏄惁鍚敤瀹℃壒
+3. 閫夋嫨瀹℃壒娴佺▼锛堜粠BPM娴佺▼鍒楄〃锛�
+
+```vue
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储鏍� -->
+ <el-form :inline="true">
+ <el-form-item label="浠撳簱">
+ <el-select v-model="queryParams.warehouseId" placeholder="璇烽�夋嫨浠撳簱" clearable>
+ <el-option v-for="warehouse in warehouseList" :key="warehouse.id"
+ :label="warehouse.warehouseName" :value="warehouse.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 閰嶇疆琛ㄦ牸 -->
+ <el-table :data="configList" v-loading="loading">
+ <el-table-column prop="warehouseName" label="浠撳簱鍚嶇О" width="150" />
+ <el-table-column label="鍏ュ簱瀹℃壒" width="120">
+ <template #default="{ row }">
+ <el-switch v-model="row.inboundEnabled"
+ @change="handleApprovalChange(row, 'INBOUND', $event)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍑哄簱瀹℃壒" width="120">
+ <template #default="{ row }">
+ <el-switch v-model="row.outboundEnabled"
+ @change="handleApprovalChange(row, 'OUTBOUND', $event)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="璋冩嫧瀹℃壒" width="120">
+ <template #default="{ row }">
+ <el-switch v-model="row.transferEnabled"
+ @change="handleApprovalChange(row, 'TRANSFER', $event)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鐩樼偣瀹℃壒" width="120">
+ <template #default="{ row }">
+ <el-switch v-model="row.checkEnabled"
+ @change="handleApprovalChange(row, 'CHECK', $event)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="100">
+ <template #default="{ row }">
+ <el-button link type="primary" @click="handleConfigProcess(row)">閰嶇疆娴佺▼</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 娴佺▼閰嶇疆寮圭獥 -->
+ <el-dialog v-model="processDialogVisible" title="閰嶇疆瀹℃壒娴佺▼" width="500px">
+ <el-form label-width="100px">
+ <el-form-item label="涓氬姟绫诲瀷">
+ <el-select v-model="currentConfig.bizType">
+ <el-option label="鍏ュ簱" value="INBOUND" />
+ <el-option label="鍑哄簱" value="OUTBOUND" />
+ <el-option label="璋冩嫧" value="TRANSFER" />
+ <el-option label="鐩樼偣" value="CHECK" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="瀹℃壒娴佺▼">
+ <el-select v-model="currentConfig.processDefinitionKey" placeholder="璇烽�夋嫨娴佺▼">
+ <el-option v-for="process in processList" :key="process.key"
+ :label="process.name" :value="process.key" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="processDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSaveProcessConfig">淇濆瓨</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import * as ApprovalApi from '@/api/wms/approval'
+
+const warehouseList = ref([])
+const configList = ref([])
+const processList = ref([])
+const loading = ref(false)
+const queryParams = ref({ warehouseId: null })
+
+const processDialogVisible = ref(false)
+const currentConfig = ref({ warehouseId: null, bizType: null, processDefinitionKey: null })
+
+// 鍔犺浇浠撳簱鍒楄〃锛堜粠浠撳簱璁剧疆妯″潡鑾峰彇锛�
+const loadWarehouseList = async () => {
+ // 璋冪敤浠撳簱璁剧疆鎺ュ彛鑾峰彇鍒楄〃
+}
+
+// 鍔犺浇瀹℃壒閰嶇疆
+const loadConfigList = async (warehouseId) => {
+ if (!warehouseId) return
+ loading.value = true
+ try {
+ configList.value = await ApprovalApi.getApprovalConfigList(warehouseId)
+ } finally {
+ loading.value = false
+ }
+}
+
+// 鍒囨崲瀹℃壒寮�鍏�
+const handleApprovalChange = async (row, bizType, enabled) => {
+ await ApprovalApi.saveApprovalConfig({
+ warehouseId: row.warehouseId,
+ bizType: bizType,
+ approvalEnabled: enabled
+ })
+}
+
+// 閰嶇疆娴佺▼
+const handleConfigProcess = async (row) => {
+ currentConfig.value = { warehouseId: row.warehouseId, bizType: null, processDefinitionKey: null }
+ // 鍔犺浇鍙敤娴佺▼鍒楄〃
+ processList.value = await ApprovalApi.getAvailableProcessList(row.warehouseId, 'INBOUND')
+ processDialogVisible.value = true
+}
+
+// 淇濆瓨娴佺▼閰嶇疆
+const handleSaveProcessConfig = async () => {
+ await ApprovalApi.saveApprovalConfig(currentConfig.value)
+ processDialogVisible.value = false
+ loadConfigList(queryParams.value.warehouseId)
+}
+
+onMounted(() => {
+ loadWarehouseList()
+})
+</script>
+```
+
+### 4.2 浠撳簱鏄犲皠閰嶇疆椤甸潰
+
+**璺緞**锛歚views/erp/warehouse/mapping/index.vue`锛堟斁鍦‥RP浠撳簱绠$悊涓嬶級
+
+**鍔熻兘**锛�
+1. 閰嶇疆ERP浠撳簱涓嶮ES浠撳簱鐨勬槧灏勫叧绯�
+2. 鏀寔鏂板銆佷慨鏀广�佸垹闄ゆ槧灏�
+
+```vue
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储鏍� -->
+ <el-form :inline="true">
+ <el-form-item label="ERP浠撳簱">
+ <el-select v-model="queryParams.erpWarehouseId" placeholder="璇烽�夋嫨ERP浠撳簱" clearable>
+ <el-option v-for="warehouse in erpWarehouseList" :key="warehouse.id"
+ :label="warehouse.name" :value="warehouse.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="MES浠撳簱">
+ <el-select v-model="queryParams.mesWarehouseId" placeholder="璇烽�夋嫨MES浠撳簱" clearable>
+ <el-option v-for="warehouse in mesWarehouseList" :key="warehouse.id"
+ :label="warehouse.warehouseName" :value="warehouse.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 鏄犲皠琛ㄦ牸 -->
+ <el-table :data="mappingList" v-loading="loading">
+ <el-table-column prop="erpWarehouseName" label="ERP浠撳簱" />
+ <el-table-column prop="mesWarehouseName" label="MES浠撳簱" />
+ <el-table-column prop="status" label="鐘舵��">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 1 ? 'success' : 'danger'">
+ {{ row.status === 1 ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150">
+ <template #default="{ row }">
+ <el-button link type="primary" @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button link type="danger" @click="handleDelete(row.id)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <el-pagination v-model:current-page="queryParams.pageNo" v-model:page-size="queryParams.pageSize"
+ :total="total" @change="handleQuery" />
+
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
+ <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
+ <el-form-item label="ERP浠撳簱" prop="erpWarehouseId">
+ <el-select v-model="formData.erpWarehouseId" placeholder="璇烽�夋嫨ERP浠撳簱">
+ <el-option v-for="warehouse in erpWarehouseList" :key="warehouse.id"
+ :label="warehouse.name" :value="warehouse.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="MES浠撳簱" prop="mesWarehouseId">
+ <el-select v-model="formData.mesWarehouseId" placeholder="璇烽�夋嫨MES浠撳簱">
+ <el-option v-for="warehouse in mesWarehouseList" :key="warehouse.id"
+ :label="warehouse.warehouseName" :value="warehouse.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��">
+ <el-radio-group v-model="formData.status">
+ <el-radio :value="1">鍚敤</el-radio>
+ <el-radio :value="0">绂佺敤</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import * as MappingApi from '@/api/erp/warehouseMapping'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const loading = ref(false)
+const total = ref(0)
+const mappingList = ref([])
+const queryParams = reactive({
+ pageNo: 1,
+ pageSize: 10,
+ erpWarehouseId: null,
+ mesWarehouseId: null
+})
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const formRef = ref()
+const formData = reactive({
+ id: null,
+ erpWarehouseId: null,
+ mesWarehouseId: null,
+ status: 1
+})
+const formRules = {
+ erpWarehouseId: [{ required: true, message: '璇烽�夋嫨ERP浠撳簱', trigger: 'change' }],
+ mesWarehouseId: [{ required: true, message: '璇烽�夋嫨MES浠撳簱', trigger: 'change' }]
+}
+
+// ERP浠撳簱鍒楄〃锛堜粠ERP浠撳簱鎺ュ彛鑾峰彇锛�
+const erpWarehouseList = ref([])
+// MES浠撳簱鍒楄〃锛堜粠MES浠撳簱鎺ュ彛鑾峰彇锛�
+const mesWarehouseList = ref([])
+
+// 鍔犺浇鏄犲皠鍒楄〃
+const loadMappingList = async () => {
+ loading.value = true
+ try {
+ const res = await MappingApi.getMappingPage(queryParams)
+ mappingList.value = res.list
+ total.value = res.total
+ } finally {
+ loading.value = false
+ }
+}
+
+// 鏂板
+const handleAdd = () => {
+ dialogTitle.value = '鏂板浠撳簱鏄犲皠'
+ Object.assign(formData, { id: null, erpWarehouseId: null, mesWarehouseId: null, status: 1 })
+ dialogVisible.value = true
+}
+
+// 缂栬緫
+const handleEdit = (row) => {
+ dialogTitle.value = '缂栬緫浠撳簱鏄犲皠'
+ Object.assign(formData, row)
+ dialogVisible.value = true
+}
+
+// 鎻愪氦
+const handleSubmit = async () => {
+ await formRef.value.validate()
+ if (formData.id) {
+ await MappingApi.updateMapping(formData)
+ ElMessage.success('鏇存柊鎴愬姛')
+ } else {
+ await MappingApi.createMapping(formData)
+ ElMessage.success('鍒涘缓鎴愬姛')
+ }
+ dialogVisible.value = false
+ loadMappingList()
+}
+
+// 鍒犻櫎
+const handleDelete = async (id) => {
+ await ElMessageBox.confirm('纭畾鍒犻櫎璇ユ槧灏勶紵', '鎻愮ず', { type: 'warning' })
+ await MappingApi.deleteMapping(id)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ loadMappingList()
+}
+
+onMounted(() => {
+ loadMappingList()
+ // 鍔犺浇浠撳簱鍒楄〃
+})
+</script>
+```
+
+---
+
+## 浜斻�丄PI鎺ュ彛
+
+### 5.1 瀹℃壒閰嶇疆鎺ュ彛
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mes/wms/approval/config/get | 鑾峰彇鍗曚釜瀹℃壒閰嶇疆 |
+| GET | /mes/wms/approval/config/list | 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃� |
+| POST | /mes/wms/approval/config/save | 淇濆瓨瀹℃壒閰嶇疆 |
+| GET | /mes/wms/approval/process-list | 鑾峰彇鍙敤瀹℃壒娴佺▼ |
+
+**璇锋眰鍙傛暟璇︽儏锛�**
+
+#### GET /mes/wms/approval/config/get
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| warehouseId | Long | 鏄� | 浠撳簱ID |
+| bizType | String | 鏄� | 涓氬姟绫诲瀷(INBOUND/OUTBOUND/TRANSFER/CHECK) |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "id": 1,
+ "warehouseId": 100,
+ "bizType": "INBOUND",
+ "approvalEnabled": true,
+ "processDefinitionKey": "wms_inbound_approve",
+ "categoryCode": "wms_approve"
+ }
+}
+```
+
+#### POST /mes/wms/approval/config/save
+
+**璇锋眰浣擄細**
+
+```json
+{
+ "warehouseId": 100,
+ "bizType": "INBOUND",
+ "approvalEnabled": true,
+ "processDefinitionKey": "wms_inbound_approve"
+}
+```
+
+#### GET /mes/wms/approval/process-list
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| warehouseId | Long | 鏄� | 浠撳簱ID |
+| bizType | String | 鏄� | 涓氬姟绫诲瀷 |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": [
+ { "key": "wms_inbound_approve", "name": "鍏ュ簱瀹℃壒娴佺▼" },
+ { "key": "wms_common_approve", "name": "閫氱敤瀹℃壒娴佺▼" }
+ ]
+}
+```
+
+### 5.2 浠撳簱鏄犲皠鎺ュ彛
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /erp/warehouse-mapping/page | 鍒嗛〉鏌ヨ鏄犲皠鍒楄〃 |
+| GET | /erp/warehouse-mapping/get | 鑾峰彇鍗曚釜鏄犲皠 |
+| POST | /erp/warehouse-mapping/create | 鏂板鏄犲皠 |
+| PUT | /erp/warehouse-mapping/update | 鏇存柊鏄犲皠 |
+| DELETE | /erp/warehouse-mapping/delete | 鍒犻櫎鏄犲皠 |
+
+**璇锋眰鍙傛暟璇︽儏锛�**
+
+#### GET /erp/warehouse-mapping/page
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| pageNo | Integer | 鏄� | 椤电爜 |
+| pageSize | Integer | 鏄� | 姣忛〉鏁伴噺 |
+| erpWarehouseId | Long | 鍚� | ERP浠撳簱ID |
+| mesWarehouseId | Long | 鍚� | MES浠撳簱ID |
+| status | Integer | 鍚� | 鐘舵�� |
+
+**鍝嶅簲锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "erpWarehouseId": 1,
+ "erpWarehouseName": "涓讳粨搴�",
+ "mesWarehouseId": 100,
+ "mesWarehouseName": "鎬讳粨",
+ "status": 1
+ }
+ ],
+ "total": 1
+ }
+}
+```
+
+#### POST /erp/warehouse-mapping/create
+
+**璇锋眰浣擄細**
+
+```json
+{
+ "erpWarehouseId": 1,
+ "mesWarehouseId": 100,
+ "status": 1
+}
+```
+
+---
+
+## 鍏�丄PI鏂囦欢鍒涘缓
+
+### 6.1 瀹℃壒閰嶇疆API
+
+鍒涘缓鏂囦欢锛歚src/api/wms/approval/index.js`
+
+```js
+import request from '@/utils/request'
+
+// 鑾峰彇瀹℃壒閰嶇疆
+export function getApprovalConfig(warehouseId, bizType) {
+ return request({
+ url: '/mes/wms/approval/config/get',
+ method: 'get',
+ params: { warehouseId, bizType }
+ })
+}
+
+// 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃�
+export function getApprovalConfigList(warehouseId) {
+ return request({
+ url: '/mes/wms/approval/config/list',
+ method: 'get',
+ params: { warehouseId }
+ })
+}
+
+// 淇濆瓨瀹℃壒閰嶇疆
+export function saveApprovalConfig(data) {
+ return request({
+ url: '/mes/wms/approval/config/save',
+ method: 'post',
+ data: data
+ })
+}
+
+// 鑾峰彇鍙敤瀹℃壒娴佺▼鍒楄〃
+export function getAvailableProcessList(warehouseId, bizType) {
+ return request({
+ url: '/mes/wms/approval/process-list',
+ method: 'get',
+ params: { warehouseId, bizType }
+ })
+}
+```
+
+### 6.2 浠撳簱鏄犲皠API
+
+鍒涘缓鏂囦欢锛歚src/api/erp/warehouseMapping/index.js`
+
+```js
+import request from '@/utils/request'
+
+// 鍒嗛〉鏌ヨ鏄犲皠鍒楄〃
+export function getMappingPage(params) {
+ return request({
+ url: '/erp/warehouse-mapping/page',
+ method: 'get',
+ params: params
+ })
+}
+
+// 鑾峰彇鍗曚釜鏄犲皠
+export function getMapping(id) {
+ return request({
+ url: '/erp/warehouse-mapping/get',
+ method: 'get',
+ params: { id }
+ })
+}
+
+// 鏂板鏄犲皠
+export function createMapping(data) {
+ return request({
+ url: '/erp/warehouse-mapping/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 鏇存柊鏄犲皠
+export function updateMapping(data) {
+ return request({
+ url: '/erp/warehouse-mapping/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鍒犻櫎鏄犲皠
+export function deleteMapping(id) {
+ return request({
+ url: '/erp/warehouse-mapping/delete',
+ method: 'delete',
+ params: { id }
+ })
+}
+```
+
+---
+
+## 涓冦�佹暟鎹槧灏勯厤缃�
+
+### 7.1 ERP浜у搧涓嶮DM鐗╂枡
+
+閫氳繃鏉$爜(barCode)鍏宠仈锛�
+- ERP浜у搧鐨刡arCode瀛楁鍊� = MDM鐗╂枡鐨刡arCode瀛楁鍊�
+- 绯荤粺鑷姩閫氳繃barCode鍖归厤锛屾棤闇�棰濆閰嶇疆琛�
+
+### 7.2 ERP浠撳簱涓嶮ES浠撳簱
+
+闇�瑕佸湪浠撳簱鏄犲皠椤甸潰閰嶇疆鏄犲皠鍏崇郴锛�
+
+| ERP浠撳簱 | MES浠撳簱 | 璇存槑 |
+|---------|---------|------|
+| 涓讳粨搴�(ID=1) | 鎬讳粨(ID=100) | 榛樿鏄犲皠 |
+
+**娉ㄦ剰**锛欵RP浠撳簱蹇呴』閰嶇疆鏄犲皠鍚庯紝ERP閲囪喘鍏ュ簱/閿�鍞嚭搴撳鏍告墠鑳藉悓姝ES搴撳瓨銆�
+
+---
+
+## 鍏�佷笟鍔℃祦绋�
+
+### 8.1 閲囪喘鍏ュ簱娴佺▼锛圗RP 鈫� MES锛�
+
+```
+ERP閲囪喘鍏ュ簱鍗曞垱寤�
+ 鈫�
+ERP閲囪喘鍏ュ簱鍗曞鏍�
+ 鈫�
+璋冪敤 WmsStockApi.purchaseInbound()
+ 鈫�
+鏌ユ壘ERP浠撳簱 鈫� MES浠撳簱鏄犲皠
+ 鈫�
+鏌ユ壘ERP浜у搧(barCode) 鈫� MDM鐗╂枡(barCode)
+ 鈫�
+鍒涘缓MES搴撳瓨浜嬪姟璁板綍
+ 鈫�
+MES搴撳瓨澧炲姞
+```
+
+### 8.2 閿�鍞嚭搴撴祦绋嬶紙ERP 鈫� MES锛�
+
+```
+ERP閿�鍞嚭搴撳崟鍒涘缓
+ 鈫�
+ERP閿�鍞嚭搴撳崟瀹℃牳
+ 鈫�
+璋冪敤 WmsStockApi.salesOutbound()
+ 鈫�
+鏌ユ壘浠撳簱鏄犲皠 + 浜у搧鏄犲皠
+ 鈫�
+鍒涘缓MES搴撳瓨浜嬪姟璁板綍锛堟暟閲忎负璐燂級
+ 鈫�
+MES搴撳瓨鍑忓皯
+```
+
+### 8.3 瀹℃壒娴佺▼锛堝彲閫夛級
+
+```
+MES鍏ュ簱鍗曞垱寤猴紙鑽夌锛�
+ 鈫�
+妫�鏌ヤ粨搴撳鎵归厤缃� 鈫� 鏄惁闇�瑕佸鎵癸紵
+ 鈫� (闇�瑕佸鎵�)
+鎻愪氦瀹℃壒 鈫� BPM娴佺▼鍚姩
+ 鈫�
+瀹℃壒閫氳繃 鈫� 鍗曟嵁鐘舵�佹洿鏂� 鈫� 鎵ц鍏ュ簱
+ 鈫� (涓嶉渶瑕佸鎵�)
+鐩存帴鎵ц鍏ュ簱
+```
+
+---
+
+## 涔濄�佹敞鎰忎簨椤�
+
+1. **鎵ц椤哄簭**锛氬厛鎵цSQL鑴氭湰锛屽啀閮ㄧ讲鍚庣锛屾渶鍚庤皟鏁村墠绔�
+2. **鏄犲皠閰嶇疆**锛欵RP浠撳簱蹇呴』鍏堥厤缃槧灏勬墠鑳藉悓姝ES搴撳瓨
+3. **鏉冮檺鍓嶇紑**锛�
+ - 瀹℃壒閰嶇疆锛歚mes:wm-warehouse:query`銆乣mes:wm-warehouse:update`
+ - 浠撳簱鏄犲皠锛歚erp:warehouse:query`銆乣erp:warehouse:update`銆乣erp:warehouse:delete`
+4. **瀹℃壒娴佺▼**锛氶渶鍦˙PM绠$悊鍚庡彴鍒涘缓"浠撳簱瀹℃牳(wms_approve)"鍒嗙被涓嬬殑娴佺▼
+5. **鐗╂枡鏉$爜**锛歁DM鐗╂枡蹇呴』濉啓barCode锛屼笖涓嶦RP浜у搧barCode涓�鑷�
+
+---
+
+## 鍗併�佹祴璇曢獙璇�
+
+### 10.1 鍔熻兘娴嬭瘯
+
+1. **鑿滃崟楠岃瘉**锛氱櫥褰曠郴缁燂紝纭"浠撳偍鐗╂祦"浣滀负涓�绾ц彍鍗曟樉绀�
+2. **鏄犲皠閰嶇疆**锛氬湪ERP浠撳簱绠$悊涓嬶紝閰嶇疆ERP浠撳簱涓嶮ES浠撳簱鏄犲皠
+3. **閲囪喘鍏ュ簱**锛�
+ - ERP鍒涘缓閲囪喘鍏ュ簱鍗� 鈫� 瀹℃牳
+ - 鏌ヨMES搴撳瓨鐜版湁閲� 鈫� 纭搴撳瓨澧炲姞
+4. **閿�鍞嚭搴�**锛�
+ - ERP鍒涘缓閿�鍞嚭搴撳崟 鈫� 瀹℃牳
+ - 鏌ヨMES搴撳瓨鐜版湁閲� 鈫� 纭搴撳瓨鍑忓皯
+5. **瀹℃壒娴佺▼**锛�
+ - 閰嶇疆浠撳簱鍏ュ簱瀹℃壒 鈫� 鍚敤
+ - 鍒涘缓MES鍏ュ簱鍗� 鈫� 鎻愬鎵� 鈫� 瀹℃壒閫氳繃 鈫� 鐘舵�佸彉鏇�
+
+### 10.2 寮傚父鍦烘櫙
+
+1. 鏈厤缃粨搴撴槧灏� 鈫� ERP瀹℃牳鏃舵姤閿欐彁绀�"浠撳簱鏄犲皠涓嶅瓨鍦�"
+2. 鐗╂枡鏉$爜涓嶅尮閰� 鈫� ERP瀹℃牳鏃舵姤閿欐彁绀�"鐗╂枡涓嶅瓨鍦�"
+3. 搴撳瓨涓嶈冻 鈫� 閿�鍞嚭搴撴椂鎶ラ敊鎻愮ず"搴撳瓨涓嶈冻"
\ No newline at end of file
diff --git a/docs/wm_arrival_notice_purchase_order.md b/docs/wm_arrival_notice_purchase_order.md
new file mode 100644
index 0000000..c16875e
--- /dev/null
+++ b/docs/wm_arrival_notice_purchase_order.md
@@ -0,0 +1,148 @@
+# 鍒拌揣閫氱煡鍗曞叧鑱旈噰璐鍗曞姛鑳�
+
+## 姒傝堪
+
+灏嗗埌璐ч�氱煡鍗曠殑閲囪喘璁㈠崟鍙蜂粠鎵嬪姩杈撳叆鏀逛负鍏宠仈 ERP 閲囪喘璁㈠崟锛屽疄鐜版暟鎹仈鍔ㄥ拰鏍¢獙銆�
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 璺緞 | 璇存槑 |
+|------|------|------|
+| 鍒拌揣閫氱煡鍗曞垪琛ㄩ〉 | `wls/arrivalnotice/index.vue` | 鍒拌揣閫氱煡鍗曠鐞� |
+| 鍒拌揣閫氱煡鍗曡鎯�/缂栬緫椤� | `wls/arrivalnotice/form.vue` | 鏂板/缂栬緫鍒拌揣閫氱煡鍗� |
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/rpc-api/erp/purchase-order/get` | 鏍规嵁 ID 鑾峰彇閲囪喘璁㈠崟 |
+| GET | `/rpc-api/erp/purchase-order/list-by-supplier` | 鏍规嵁渚涘簲鍟嗚幏鍙栭噰璐鍗曞垪琛紙宸插鏍革級 |
+| POST | `/mes/wm-arrival-notice/create` | 鍒涘缓鍒拌揣閫氱煡鍗� |
+| PUT | `/mes/wm-arrival-notice/update` | 鏇存柊鍒拌揣閫氱煡鍗� |
+
+**璇锋眰鍙傛暟鍙樻洿锛坈reate/update锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| purchaseOrderId | Long | 鍚� | 閲囪喘璁㈠崟缂栧彿锛堟柊澧烇級 |
+| purchaseOrderCode | String | 鍚� | 閲囪喘璁㈠崟鍙凤紙鍐椾綑锛屽悗绔嚜鍔ㄥ~鍏咃級 |
+| vendorId | Long | 鏄� | 渚涘簲鍟嗙紪鍙凤紙鍘熸湁锛� |
+| ... | ... | ... | 鍏朵粬鍘熸湁瀛楁涓嶅彉 |
+
+**鍝嶅簲鍙樻洿锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| purchaseOrderId | Long | 閲囪喘璁㈠崟缂栧彿锛堟柊澧烇級 |
+| purchaseOrderCode | String | 閲囪喘璁㈠崟鍙凤紙鍘熸湁锛� |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 琛ㄥ崟瀛楁淇敼
+
+```html
+<!-- 鍘熸潵鐨勮緭鍏ユ -->
+<el-form-item label="閲囪喘璁㈠崟鍙�" prop="purchaseOrderCode">
+ <el-input v-model="formData.purchaseOrderCode" placeholder="璇疯緭鍏ラ噰璐鍗曞彿" />
+</el-form-item>
+
+<!-- 鏀逛负涓嬫媺閫夋嫨妗� -->
+<el-form-item label="閲囪喘璁㈠崟" prop="purchaseOrderId">
+ <el-select
+ v-model="formData.purchaseOrderId"
+ placeholder="璇烽�夋嫨閲囪喘璁㈠崟"
+ clearable
+ filterable
+ @change="handlePurchaseOrderChange"
+ >
+ <el-option
+ v-for="order in purchaseOrderList"
+ :key="order.id"
+ :label="order.no"
+ :value="order.id"
+ >
+ <span>{{ order.no }}</span>
+ <span style="color: #999; margin-left: 10px;">{{ order.orderTime }}</span>
+ </el-option>
+ </el-select>
+</el-form-item>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+ return {
+ // 鏂板锛氶噰璐鍗曞垪琛�
+ purchaseOrderList: [],
+ formData: {
+ purchaseOrderId: undefined, // 鏂板
+ purchaseOrderCode: '', // 淇濈暀锛堟樉绀虹敤锛�
+ // ... 鍏朵粬瀛楁
+ },
+ }
+}
+```
+
+### 3. 鏂规硶
+
+```js
+// 渚涘簲鍟嗗彉鏇存椂锛屽姞杞藉搴旂殑閲囪喘璁㈠崟鍒楄〃
+handleVendorChange(vendorId) {
+ if (!vendorId) {
+ this.purchaseOrderList = [];
+ this.formData.purchaseOrderId = undefined;
+ this.formData.purchaseOrderCode = '';
+ return;
+ }
+ getPurchaseOrderListBySupplier(vendorId).then(res => {
+ this.purchaseOrderList = res.data || [];
+ });
+}
+
+// 閲囪喘璁㈠崟鍙樻洿鏃讹紝鑷姩濉厖閲囪喘璁㈠崟鍙�
+handlePurchaseOrderChange(orderId) {
+ if (!orderId) {
+ this.formData.purchaseOrderCode = '';
+ return;
+ }
+ const order = this.purchaseOrderList.find(o => o.id === orderId);
+ if (order) {
+ this.formData.purchaseOrderCode = order.no;
+ }
+}
+```
+
+### 4. API 鏂规硶
+
+```js
+import request from '@/utils/request';
+
+// 鏍规嵁渚涘簲鍟嗚幏鍙栭噰璐鍗曞垪琛紙宸插鏍哥姸鎬侊級
+export function getPurchaseOrderListBySupplier(supplierId) {
+ return request({
+ url: `/rpc-api/erp/purchase-order/list-by-supplier?supplierId=${supplierId}`,
+ method: 'get'
+ });
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 閲囪喘璁㈠崟涓嬫媺妗嗗彧鍦ㄩ�夋嫨渚涘簲鍟嗗悗鎵嶅姞杞�
+- 鍙姞杞藉凡瀹℃牳鐘舵�佺殑閲囪喘璁㈠崟
+- 鍚庣浼氳嚜鍔ㄦ牴鎹� `purchaseOrderId` 濉厖 `purchaseOrderCode`锛屽墠绔棤闇�鎵嬪姩浼犻��
+- 鍘熸湁鐨� `purchaseOrderCode` 瀛楁淇濈暀鐢ㄤ簬鏄剧ず锛屼絾涓嶅缓璁墜鍔ㄨ緭鍏�
+
+## 鏁版嵁搴撳彉鏇�
+
+闇�瑕佹墽琛� SQL 鑴氭湰锛歚sql/mysql/mes_wm_arrival_notice_purchase_order.sql`
+
+```sql
+ALTER TABLE mes_wm_arrival_notice ADD COLUMN purchase_order_id BIGINT COMMENT '閲囪喘璁㈠崟缂栧彿' AFTER name;
+```
+
+## 娉ㄦ剰锛歮es_wm_item_receipt 琛ㄩ棶棰�
+
+`mes_wm_item_receipt` 琛ㄥ湪鏁版嵁搴撲腑鏄� **鐗╂枡閫�搴撳崟**锛堝叧鑱旂敓浜у伐鍗曪級锛屼絾浠g爜涓敞閲婂啓鐨勬槸"閲囪喘鍏ュ簱鍗�"銆�
+杩欐槸涓�涓唬鐮佷笌鏁版嵁搴撲笉涓�鑷寸殑闂锛岄渶瑕佸悗缁崟鐙慨澶嶃�傛湰娆′慨鏀逛笉褰卞搷璇ヨ〃銆�
\ No newline at end of file
diff --git a/docs/wm_module_analysis.md b/docs/wm_module_analysis.md
new file mode 100644
index 0000000..1712472
--- /dev/null
+++ b/docs/wm_module_analysis.md
@@ -0,0 +1,357 @@
+# 浠撳偍鐗╂祦妯″潡涓氬姟鍒嗘瀽鎶ュ憡
+
+## 涓�銆佸彂鐜扮殑闂涓庢紡娲�
+
+### 1. 骞跺彂瀹夊叏闂 馃敶 楂橀闄�
+
+**闂鎻忚堪**锛�
+搴撳瓨鏇存柊鎿嶄綔 `updateQuantity` 浣跨敤浜嗘暟鎹簱灞傞潰鐨� CAS锛圕ompare And Set锛夐槻璐熷簱瀛橈紝浣嗙己涔忓垎甯冨紡閿佷繚鎶ゃ��
+
+**椋庨櫓鍦烘櫙**锛�
+```
+鍦烘櫙锛氬悓涓�搴撳瓨璁板綍琚袱涓嚭搴撳崟鍚屾椂鎿嶄綔
+鏃堕棿绾匡細
+T1: 绾跨▼A璇诲彇搴撳瓨 quantity=100
+T2: 绾跨▼B璇诲彇搴撳瓨 quantity=100
+T3: 绾跨▼A鎵ц鍑哄簱50锛宷uantity=50
+T4: 绾跨▼B鎵ц鍑哄簱60锛宷uantity=-10锛圕AS浼氭嫤鎴級
+```
+
+**褰撳墠瀹炵幇**锛圡esWmMaterialStockMapper.java:78-86锛夛細
+```java
+default int updateQuantity(Long id, BigDecimal count, boolean checkFlag) {
+ LambdaUpdateWrapper<MesWmMaterialStockDO> updateWrapper = new LambdaUpdateWrapper<>()
+ .eq(MesWmMaterialStockDO::getId, id)
+ .setSql("quantity = quantity + " + count);
+ if (checkFlag && count.compareTo(BigDecimal.ZERO) < 0) {
+ updateWrapper.ge(MesWmMaterialStockDO::getQuantity, count.abs()); // CAS 闃茶礋搴撳瓨
+ }
+ return update(null, updateWrapper);
+}
+```
+
+**闂**锛�
+- CAS鍙互闃叉璐熷簱瀛橈紝浣嗘棤娉曢槻姝㈣秴鍗栵紙涓や釜绾跨▼鍚屾椂妫�鏌ュ簱瀛樺厖瓒筹紝鐒跺悗鍚屾椂鎵e噺锛�
+- 缂哄皯涔愯閿佺増鏈彿鏈哄埗
+
+**寤鸿淇**锛�
+```java
+// 鏂规1锛氭坊鍔犱箰瑙傞攣鐗堟湰鍙峰瓧娈�
+ALTER TABLE mes_wm_material_stock ADD COLUMN version INT DEFAULT 0;
+
+// 鏂规2锛氫娇鐢� Redis 鍒嗗竷寮忛攣锛堟帹鑽愰珮骞跺彂鍦烘櫙锛�
+String lockKey = "stock:" + itemId + ":" + areaId;
+RLock lock = redissonClient.getLock(lockKey);
+try {
+ lock.lock();
+ // 鎵ц搴撳瓨鏇存柊
+} finally {
+ lock.unlock();
+}
+```
+
+---
+
+### 2. 搴撳瓨鍞竴閿璁¢棶棰� 馃煛 涓闄�
+
+**闂鎻忚堪**锛�
+鏂囨。涓畾涔夊敮涓�閿负 `(itemId, warehouseId, locationId, areaId, batchId)`锛屼絾瀹為檯浠g爜涓� `vendorId` 涔熷弬涓庝簡缁勫悎鏌ヨ銆�
+
+**椋庨櫓**锛�
+- 鍚屼竴鐗╂枡鍚屼竴搴撲綅锛屼笉鍚屼緵搴斿晢鐨勫叆搴撲細鍒涘缓涓ゆ潯搴撳瓨璁板綍
+- 鍙兘瀵艰嚧搴撳瓨璁板綍鑶ㄨ儉锛屾煡璇㈡晥鐜囦笅闄�
+
+**浠g爜璇佹嵁**锛圡esWmMaterialStockServiceImpl.java:141-146锛夛細
+```java
+MesWmMaterialStockDO existStock = materialStockMapper.selectByCompositeKey(
+ itemId, warehouseId, locationId, areaId, batchId);
+// 娉ㄦ剰锛歷endorId 鏈弬涓庡敮涓�閿煡璇�
+```
+
+**寤鸿**锛�
+- 鏄庣‘涓氬姟闇�姹傦細鏄惁闇�瑕佹寜渚涘簲鍟嗗尯鍒嗗簱瀛橈紵
+- 濡傛灉闇�瑕侊紝寤鸿鍦ㄥ敮涓�閿腑澧炲姞 vendorId
+- 濡傛灉涓嶉渶瑕侊紝鍏ュ簱鏃朵笉搴斿垱寤烘柊鐨勫簱瀛樿褰�
+
+---
+
+### 2. 鍗曟嵁鍙栨秷鍚庣殑搴撳瓨澶勭悊 鉁� 宸茬‘璁よ璁℃纭�
+
+**璁捐鍐崇瓥**锛�
+- 宸插畬鎴愮姸鎬侊紙FINISHED锛夌殑鍗曟嵁**涓嶅厑璁稿彇娑�**
+- 鍙兘鍦ㄨ崏绋裤�佸緟瀹℃壒銆佸緟鎵ц绛夋湭瀹屾垚鐘舵�佸彇娑�
+- 濡傞渶鍥為��宸插畬鎴愮殑搴撳瓨鎿嶄綔锛屽簲鍒涘缓瀵瑰簲鐨勫啿閿�鍗曟嵁锛堝閫�璐у崟锛�
+
+**浠g爜璇佹嵁**锛�
+```java
+// MesWmItemReceiptServiceImpl.java
+@Override
+public void cancelItemReceipt(Long id) {
+ MesWmItemReceiptDO receipt = validateItemReceiptExists(id);
+ // 宸插畬鎴愬拰宸插彇娑堜笉鍏佽鍙栨秷
+ if (ObjectUtils.equalsAny(receipt.getStatus(),
+ MesWmItemReceiptStatusEnum.FINISHED.getStatus(),
+ MesWmItemReceiptStatusEnum.CANCELED.getStatus())) {
+ throw exception(WM_ITEM_RECEIPT_CANCEL_NOT_ALLOWED);
+ }
+ // ...
+}
+```
+
+**璇勪环**锛氳繖鏄纭殑涓氬姟璁捐锛岄伩鍏嶄簡澶嶆潅鐨勫簱瀛樺啿閿�閫昏緫銆�
+
+---
+
+### 4. 缂哄皯浜嬪姟鍥炴粴鏈哄埗 馃煛 涓闄�
+
+**闂鎻忚堪**锛�
+鎵归噺鍒涘缓浜嬪姟鏃讹紝濡傛灉涓�斿け璐ワ紝宸叉墽琛岀殑浜嬪姟涓嶄細鍥炴粴銆�
+
+**浠g爜璇佹嵁**锛圡esWmTransactionServiceImpl.java:100-107锛夛細
+```java
+@Override
+@Transactional(rollbackFor = Exception.class)
+public void createTransactionList(List<MesWmTransactionSaveReqDTO> reqDTOs) {
+ if (CollUtil.isEmpty(reqDTOs)) {
+ throw exception(WM_TRANSACTION_LIST_EMPTY);
+ }
+ reqDTOs.forEach(this::createTransaction); // 閫愪釜鎵ц锛岄儴鍒嗘垚鍔熶笉浼氬洖婊�
+}
+```
+
+**鍒嗘瀽**锛�
+- 鏂规硶鏈� `@Transactional` 娉ㄨВ锛岀悊璁轰笂鏈変簨鍔′繚鎶�
+- 浣嗗鏋� `createTransaction` 鍐呴儴鎹曡幏寮傚父鏈姏鍑猴紝浼氬鑷撮儴鍒嗘垚鍔�
+
+**寤鸿**锛�
+- 纭繚鎵�鏈夊紓甯搁兘鍚戜笂鎶涘嚭
+- 鑰冭檻娣诲姞浜嬪姟琛ュ伩鏈哄埗
+
+---
+
+### 5. 鐘舵�佹満缂哄け涓ユ牸鏍¢獙 馃煛 涓闄�
+
+**闂鎻忚堪**锛�
+鐘舵�佹祦杞緷璧栨柟娉曞懡鍚嶇害瀹氾紝缂哄皯鏄惧紡鐨勭姸鎬佹満鏍¢獙銆�
+
+**椋庨櫓**锛�
+- 鍙兘鍑虹幇闈炴硶鐘舵�佽烦杞紙濡備粠鑽夌鐩存帴鍒板凡瀹屾垚锛�
+- 鐘舵�佹灇涓惧�煎彲鑳借闅忔剰璁剧疆
+
+**寤鸿**锛�
+```java
+// 娣诲姞鐘舵�佹祦杞牎楠�
+public enum ItemReceiptStateTransition {
+ PREPARE_TO_APPROVING(PREPARE, APPROVING),
+ APPROVING_TO_APPROVED(APPROVING, APPROVED),
+ APPROVED_TO_FINISHED(APPROVED, FINISHED),
+ ANY_TO_CANCELED(ANY, CANCELED);
+
+ public static boolean isValidTransition(Integer from, Integer to) {
+ // 鏍¢獙閫昏緫
+ }
+}
+```
+
+---
+
+### 6. 鐩樼偣宸紓澶勭悊閫昏緫鏈疄鐜� 馃煛 涓闄�
+
+**闂鎻忚堪**锛�
+鏂囨。涓彁鍒�"鐩樼泩+搴撳瓨锛岀洏浜�-搴撳瓨"锛屼絾浠g爜涓湭鎵惧埌宸紓澶勭悊鐨勫叿浣撳疄鐜般��
+
+**椋庨櫓**锛�
+- 鐩樼偣缁撴灉鍙兘鏃犳硶姝g‘褰卞搷搴撳瓨
+- 鐩樼偣娴佺▼涓嶅畬鏁�
+
+---
+
+### 7. 缂哄皯瀹夊叏搴撳瓨棰勮 馃煝 浣庨闄�
+
+**闂鎻忚堪**锛�
+绯荤粺缂哄皯瀹夊叏搴撳瓨涓嬮檺棰勮鍔熻兘銆�
+
+**寤鸿**锛�
+- 鍦ㄧ墿鏂欎富鏁版嵁涓鍔� `safetyStock`锛堝畨鍏ㄥ簱瀛橈級瀛楁
+- 鍑哄簱鏃舵鏌ユ槸鍚︿綆浜庡畨鍏ㄥ簱瀛橈紝瑙﹀彂棰勮
+
+---
+
+### 8. 缂哄皯搴撳瓨鏈夋晥鏈熺鐞� 馃煝 浣庨闄�
+
+**闂鎻忚堪**锛�
+鎵规绠$悊涓病鏈夋湁鏁堟湡瀛楁锛屾棤娉曡繘琛屼复鏈�/杩囨湡棰勮銆�
+
+**寤鸿**锛�
+- 鍦ㄦ壒娆¤〃涓鍔� `expireDate`锛堟湁鏁堟湡锛夊瓧娈�
+- 澧炲姞涓存湡棰勮鍔熻兘
+
+---
+
+## 浜屻�佷笟鍔℃祦绋嬫紡娲�
+
+### 2.1 閲囪喘鍏ュ簱娴佺▼
+
+| 鐜妭 | 褰撳墠瀹炵幇 | 娼滃湪闂 |
+|------|---------|---------|
+| 鎻愪氦 | 鏍¢獙鑷冲皯鏈変竴琛� | 鉁� 姝e父 |
+| 涓婃灦 | 鏍¢獙鏄庣粏鏁伴噺=琛屾暟閲� | 鉁� 姝e父 |
+| 鍏ュ簱 | 鍒涘缓浜嬪姟娴佹按 | 鉁� 姝e父 |
+| 鍙栨秷 | 鍙洿鏂扮姸鎬� | 鈿狅笍 寰呬笂鏋剁姸鎬佸彇娑堝悗锛屽凡涓婃灦鐨勬槑缁嗗浣曞鐞嗭紵 |
+
+### 2.2 閿�鍞嚭搴撴祦绋�
+
+| 鐜妭 | 褰撳墠瀹炵幇 | 娼滃湪闂 |
+|------|---------|---------|
+| 鎻愪氦 | 妫�鏌QC闇�姹� | 鉁� 姝e父 |
+| 鎷h揣 | 鏍¢獙姣忚鏈夋槑缁� | 鈿狅笍 鏈牎楠屽簱瀛樺厖瓒� |
+| 鍑哄簱 | 鍒涘缓浜嬪姟娴佹按 | 鉁� 姝e父 |
+| 鍙栨秷 | 鍙洿鏂扮姸鎬� | 鈿狅笍 宸叉嫞璐х殑搴撳瓨濡備綍澶勭悊锛� |
+
+**寤鸿**锛氭嫞璐ф椂搴斿喕缁撳簱瀛橈紝闃叉琚叾浠栧崟鎹姠鍗犮��
+
+### 2.3 璋冩嫧娴佺▼
+
+| 鐜妭 | 褰撳墠瀹炵幇 | 娼滃湪闂 |
+|------|---------|---------|
+| 鎻愪氦 | 閰嶉�佹ā寮忓喕缁撴簮搴撳瓨 | 鉁� 姝e父 |
+| 涓婃灦 | 鏍¢獙鏄庣粏鏁伴噺 | 鉁� 姝e父 |
+| 鎵ц | 鍒涘缓鍑哄簱+鍏ュ簱浜嬪姟 | 鉁� 姝e父 |
+| 鍙栨秷 | 瑙i櫎鍐荤粨 | 鉁� 姝e父 |
+
+**璇勪环**锛氳皟鎷ㄦ祦绋嬭璁¤緝涓哄畬鍠勩��
+
+---
+
+## 涓夈�佸凡瀹屾垚鐨勪紭鍖栭」
+
+### 1. 搴撳瓨鍞竴閿璁� 鉁� 宸蹭紭鍖�
+
+**浼樺寲鍐呭**锛�
+- `vendorId` 鐜板湪鍙備笌搴撳瓨鍞竴閿�
+- 鍚屼竴鐗╂枡鍚屼竴搴撲綅锛屼笉鍚屼緵搴斿晢鐨勫簱瀛樺垎寮�绠$悊
+- 淇敼浜� `selectByCompositeKey` 鏂规硶锛屽鍔� vendorId 鍙傛暟
+
+**淇敼鏂囦欢**锛�
+- `MesWmMaterialStockMapper.java`
+- `MesWmMaterialStockServiceImpl.java`
+
+### 2. 鐘舵�佹祦杞牎楠� 鉁� 宸蹭紭鍖�
+
+**浼樺寲鍐呭**锛�
+- 鏂板 `WmStatusTransitionUtils` 鐘舵�佹祦杞牎楠屽伐鍏风被
+- 瀹氫箟浜嗗悇涓氬姟鍗曟嵁鐨勫悎娉曠姸鎬佹祦杞鍒�
+- 闃叉闈炴硶鐘舵�佽烦杞�
+
+**鏂板鏂囦欢**锛�
+- `WmStatusTransitionUtils.java`
+
+**浣跨敤绀轰緥**锛�
+```java
+WmStatusTransitionUtils.validateTransition("ITEM_RECEIPT",
+ receipt.getStatus(), MesWmItemReceiptStatusEnum.APPROVING.getStatus(), "閲囪喘鍏ュ簱鍗�");
+```
+
+### 3. 鎷h揣搴撳瓨鏍¢獙 鉁� 宸蹭紭鍖�
+
+**浼樺寲鍐呭**锛�
+- 閿�鍞嚭搴撴嫞璐ф椂鏍¢獙搴撳瓨鍏呰冻
+- 璋冪敤 `materialStockService.validateSelectedStock()` 鏂规硶
+- 鏍¢獙鐗╂枡銆佹壒娆°�佸簱浣嶃�佹暟閲忔槸鍚﹀尮閰�
+
+**淇敼鏂囦欢**锛�
+- `MesWmProductSalesServiceImpl.java` 鐨� `stockProductSales` 鏂规硶
+
+### 4. 鐩樼偣宸紓澶勭悊 鉁� 宸蹭紭鍖�
+
+**浼樺寲鍐呭**锛�
+- 鏂板 `processStockTakingDifference` 鏂规硶
+- 姣旇緝鐩樼偣缁撴灉涓庡簱瀛樺彴璐︾殑宸紓
+- 鐩樼泩锛氬簱瀛樺鍔狅紝鍒涘缓鍏ュ簱浜嬪姟
+- 鐩樹簭锛氬簱瀛樺噺灏戯紝鍒涘缓鍑哄簱浜嬪姟
+
+**淇敼鏂囦欢**锛�
+- `MesWmStockTakingTaskService.java`
+- `MesWmStockTakingTaskServiceImpl.java`
+- `MesWmStockTakingTaskResultService.java`
+- `MesWmStockTakingTaskResultServiceImpl.java`
+- `MesWmStockTakingTaskResultMapper.java`
+- `MesBizTypeConstants.java` (鏂板 WM_STOCK_TAKING 甯搁噺)
+- `ErrorCodeConstants.java` (鏂板 WM_STOCK_TAKING_TASK_NOT_FINISHED 閿欒鐮�)
+
+### 3.1 缂哄皯鍞竴绾︽潫
+
+**闂**锛�
+- `mes_wm_material_stock` 琛ㄥ彲鑳界己灏戝敮涓�绱㈠紩
+- `mes_wm_item_receipt.code` 绛夊崟鎹紪鍙峰簲寤虹珛鍞竴绱㈠紩
+
+**寤鸿**锛�
+```sql
+ALTER TABLE mes_wm_material_stock
+ADD UNIQUE INDEX uk_stock (item_id, warehouse_id, location_id, area_id, IFNULL(batch_id, 0));
+
+ALTER TABLE mes_wm_item_receipt ADD UNIQUE INDEX uk_code (code);
+ALTER TABLE mes_wm_product_sales ADD UNIQUE INDEX uk_code (code);
+ALTER TABLE mes_wm_transfer ADD UNIQUE INDEX uk_code (code);
+```
+
+### 3.2 缂哄皯绾ц仈鍒犻櫎鎺у埗
+
+**闂**锛�
+- 鍒犻櫎浠撳簱鏃讹紝鏈鏌ユ槸鍚﹀瓨鍦ㄥ簱瀛樿褰�
+- 鍒犻櫎鐗╂枡鏃讹紝鏈鏌ユ槸鍚﹀瓨鍦ㄥ簱瀛樿褰�
+
+**寤鸿**锛�
+- 鍦ㄥ垹闄ゅ墠澧炲姞寮曠敤妫�鏌�
+- 鎴栦娇鐢ㄨ蒋鍒犻櫎锛屼繚鐣欏巻鍙叉暟鎹�
+
+---
+
+## 鍥涖�佹敼杩涘缓璁眹鎬�
+
+### 楂樹紭鍏堢骇 馃敶
+
+| 搴忓彿 | 闂 | 瑙e喅鏂规 |
+|------|------|---------|
+| 1 | 骞跺彂瀹夊叏 | 澧炲姞涔愯閿佺増鏈彿鎴栧垎甯冨紡閿� |
+| 2 | 宸插畬鎴愬崟鎹彇娑� | 鏄庣‘涓氬姟瑙勫垯锛岀姝㈡垨鍒涘缓鍐查攢浜嬪姟 |
+
+### 涓紭鍏堢骇 馃煛
+
+| 搴忓彿 | 闂 | 瑙e喅鏂规 |
+|------|------|---------|
+| 3 | 鍞竴閿笉鏄庣‘ | 鏄庣‘ vendorId 鏄惁鍙備笌鍞竴閿� |
+| 4 | 鐘舵�佹満鏍¢獙缂哄け | 澧炲姞鐘舵�佹祦杞牎楠� |
+| 5 | 鐩樼偣宸紓澶勭悊 | 琛ュ厖宸紓澶勭悊閫昏緫 |
+| 6 | 鎷h揣搴撳瓨鏍¢獙 | 鎷h揣鏃舵牎楠屽苟鍐荤粨搴撳瓨 |
+
+### 浣庝紭鍏堢骇 馃煝
+
+| 搴忓彿 | 闂 | 瑙e喅鏂规 |
+|------|------|---------|
+| 7 | 瀹夊叏搴撳瓨棰勮 | 澧炲姞瀹夊叏搴撳瓨瀛楁鍜岄璀﹀姛鑳� |
+| 8 | 鏈夋晥鏈熺鐞� | 鎵规澧炲姞鏈夋晥鏈熷瓧娈� |
+
+---
+
+## 浜斻�佹灦鏋勪紭鍖栧缓璁�
+
+### 5.1 澧炲姞搴撳瓨棰勫崰鏈哄埗
+
+```
+閿�鍞嚭搴撳崟鍒涘缓 鈫� 棰勫崰搴撳瓨锛堥鐣欓噺锛�
+ 鈫�
+ 鎵ц鍑哄簱 鈫� 鎵e噺棰勭暀閲� + 鎵e噺瀹炵墿搴撳瓨
+ 鈫�
+ 鍙栨秷 鈫� 閲婃斁棰勭暀閲�
+```
+
+### 5.2 澧炲姞搴撳瓨鎿嶄綔鏃ュ織
+
+- 璁板綍姣忔搴撳瓨鍙樻洿鐨勬搷浣滀汉銆佹搷浣滄椂闂淬�佹搷浣滃師鍥�
+- 渚夸簬闂杩芥函鍜屽璁�
+
+### 5.3 澧炲姞搴撳瓨蹇収鍔熻兘
+
+- 姣忔棩瀹氭椂鐢熸垚搴撳瓨蹇収
+- 鏀寔鍘嗗彶搴撳瓨鏌ヨ鍜屽姣斿垎鏋�
\ No newline at end of file
diff --git a/docs/wm_module_architecture.md b/docs/wm_module_architecture.md
new file mode 100644
index 0000000..5282eb5
--- /dev/null
+++ b/docs/wm_module_architecture.md
@@ -0,0 +1,440 @@
+# 浠撳偍鐗╂祦妯″潡(WM)鏋舵瀯鏂囨。
+
+## 涓�銆佹ā鍧楁杩�
+
+浠撳偍鐗╂祦妯″潡(WM)鏄疢ES绯荤粺涓殑鏍稿績妯″潡锛岃礋璐g鐞嗙墿鏂欏簱瀛樸�佸嚭鍏ュ簱涓氬姟銆佷粨搴撹缃瓑鍔熻兘銆�
+
+### 1.1 鏍稿績璁捐鐞嗗康
+
+- **鍏淮搴撳瓨妯″瀷**锛氱墿鏂� + 鎵规 + 浠撳簱 + 搴撳尯 + 搴撲綅 + 渚涘簲鍟�
+- **浜嬪姟椹卞姩**锛氭墍鏈夊簱瀛樺彉鍔ㄩ�氳繃浜嬪姟娴佹按璁板綍锛屼繚璇佹暟鎹彲杩芥函
+- **鐘舵�佹満绠$悊**锛氬崟鎹姸鎬佹祦杞弗鏍兼帶鍒讹紝淇濊瘉涓氬姟瀹屾暣鎬�
+
+---
+
+## 浜屻�佹牳蹇冩暟鎹ā鍨�
+
+### 2.1 搴撳瓨鍙拌处锛坢es_wm_material_stock锛�
+
+搴撳瓨鐜版湁閲忚〃锛岃褰曟瘡涓墿鏂欏湪姣忎釜搴撲綅鐨勫疄鏃跺簱瀛樸��
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� mes_wm_material_stock 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鐗╂枡缁村害锛歩temId(鐗╂枡) + batchId(鎵规) + batchCode(鎵规鍙�) 鈹�
+鈹� 浣嶇疆缁村害锛歸arehouseId(浠撳簱) + locationId(搴撳尯) + areaId(搴撲綅)鈹�
+鈹� 涓氬姟缁村害锛歷endorId(渚涘簲鍟�) + receiptTime(鍏ュ簱鏃堕棿) 鈹�
+鈹� 鏁伴噺鐘舵�侊細quantity(鏁伴噺) + frozen(鏄惁鍐荤粨) 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**鍞竴閿�**锛歚(itemId, warehouseId, locationId, areaId, batchId)`
+
+### 2.2 搴撳瓨浜嬪姟娴佹按锛坢es_wm_transaction锛�
+
+璁板綍姣忎竴绗斿簱瀛樺鍑忎簨浠讹紝绯荤粺鑷姩鐢熸垚锛屽彧璇绘煡璇€��
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� mes_wm_transaction 鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 涓氬姟褰掑睘锛歵ype(浜嬪姟绫诲瀷) + bizType(涓氬姟绫诲瀷) 鈹�
+鈹� bizId(涓氬姟鍗旾D) + bizCode(涓氬姟鍗曞彿) + bizLineId(琛孖D)鈹�
+鈹� 鐗╂枡淇℃伅锛歩temId + quantity(鍙樺姩鏁伴噺锛屾璐�) + batchId 鈹�
+鈹� 浣嶇疆淇℃伅锛歸arehouseId + locationId + areaId 鈹�
+鈹� 鏃堕棿淇℃伅锛歵ransactionTime + receiptTime 鈹�
+鈹� 鍏宠仈淇℃伅锛歮aterialStockId + relatedTransactionId 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+**quantity 瑙勫垯**锛�
+- 姝f暟 = 鍏ュ簱锛堝簱瀛樺鍔狅級
+- 璐熸暟 = 鍑哄簱锛堝簱瀛樺噺灏戯級
+
+### 2.3 浠撳簱灞傜骇缁撴瀯
+
+```
+浠撳簱(Warehouse)
+ 鈹斺攢鈹� 搴撳尯(Location)
+ 鈹斺攢鈹� 搴撲綅(Area)
+```
+
+---
+
+## 涓夈�佷笟鍔″崟鎹垎绫�
+
+### 3.1 鍏ュ簱绫诲崟鎹�
+
+| 鍗曟嵁 | 璇存槑 | 鐘舵�佹祦杞� | 搴撳瓨褰卞搷 |
+|------|------|---------|---------|
+| 閲囪喘鍏ュ簱鍗� | 閲囪喘鐗╂枡鍏ュ簱 | 鑽夌鈫掑緟涓婃灦鈫掑緟鍏ュ簱鈫掑凡瀹屾垚 | +搴撳瓨 |
+| 鍒拌揣閫氱煡鍗� | 閲囪喘鍒拌揣鐧昏 | 鑽夌鈫掑緟鍏ュ簱鈫掑凡瀹屾垚 | 鏃狅紙鐢熸垚閲囪喘鍏ュ簱鍗曪級 |
+| 鐢熶骇鍏ュ簱鍗� | 鐢熶骇浜у搧鍏ュ簱 | 鑽夌鈫掑緟瀹℃壒鈫掑緟鎵ц鈫掑凡瀹屾垚 | +搴撳瓨 |
+| 鏉傞」鍏ュ簱鍗� | 鍏朵粬鍘熷洜鍏ュ簱 | 鑽夌鈫掑緟瀹℃壒鈫掑凡瀹屾垚 | +搴撳瓨 |
+| 澶栧崗鍏ュ簱鍗� | 澶栧崗鍔犲伐鍏ュ簱 | 鑽夌鈫掑緟瀹℃壒鈫掑緟鎵ц鈫掑凡瀹屾垚 | +搴撳瓨 |
+| 閿�鍞��璐у崟 | 瀹㈡埛閫�璐у叆搴� | 鑽夌鈫掑緟瀹℃壒鈫掑緟鎵ц鈫掑凡瀹屾垚 | +搴撳瓨 |
+| 鐢熶骇閫�鏂欏崟 | 鐢熶骇閫�鍥炵墿鏂� | 鑽夌鈫掑緟瀹℃壒鈫掑緟鎵ц鈫掑凡瀹屾垚 | +搴撳瓨 |
+
+### 3.2 鍑哄簱绫诲崟鎹�
+
+| 鍗曟嵁 | 璇存槑 | 鐘舵�佹祦杞� | 搴撳瓨褰卞搷 |
+|------|------|---------|---------|
+| 閿�鍞嚭搴撳崟 | 閿�鍞骇鍝佸嚭搴� | 鑽夌鈫掑緟妫�娴�/寰呮嫞璐р啋寰呭~鍐欒繍鍗曗啋寰呭嚭搴撯啋宸插畬鎴� | -搴撳瓨 |
+| 鐢熶骇棰嗘枡鍗� | 鐢熶骇棰嗙敤鐗╂枡 | 鑽夌鈫掑緟瀹℃壒鈫掑緟鎵ц鈫掑凡瀹屾垚 | -搴撳瓨 |
+| 鏉傞」鍑哄簱鍗� | 鍏朵粬鍘熷洜鍑哄簱 | 鑽夌鈫掑緟瀹℃壒鈫掑凡瀹屾垚 | -搴撳瓨 |
+| 澶栧崗鍙戞枡鍗� | 澶栧崗鍔犲伐鍙戞枡 | 鑽夌鈫掑緟瀹℃壒鈫掑緟鎵ц鈫掑凡瀹屾垚 | -搴撳瓨 |
+| 閲囪喘閫�璐у崟 | 閫�鍥炰緵搴斿晢 | 鑽夌鈫掑緟瀹℃壒鈫掑緟鎵ц鈫掑凡瀹屾垚 | -搴撳瓨 |
+
+### 3.3 璋冩嫧绫诲崟鎹�
+
+| 鍗曟嵁 | 璇存槑 | 鐘舵�佹祦杞� | 搴撳瓨褰卞搷 |
+|------|------|---------|---------|
+| 杞Щ璋冩嫧鍗� | 搴撲綅闂寸Щ搴� | 鑽夌鈫掑緟纭鈫掑緟涓婃灦鈫掑緟鎵ц鈫掑凡瀹屾垚 | 鍘熷簱浣�-搴撳瓨锛岀洰鏍囧簱浣�+搴撳瓨 |
+
+### 3.4 鐩樼偣绫诲崟鎹�
+
+| 鍗曟嵁 | 璇存槑 | 鐘舵�佹祦杞� | 搴撳瓨褰卞搷 |
+|------|------|---------|---------|
+| 鐩樼偣璁″垝 | 鍒跺畾鐩樼偣浠诲姟 | 鑽夌鈫掑凡鍙戝竷鈫掑凡瀹屾垚 | 鏃� |
+| 鐩樼偣浠诲姟 | 鎵ц鐩樼偣 | 寰呮墽琛屸啋鐩樼偣涓啋宸插畬鎴� | 鏃� |
+| 鐩樼偣缁撴灉 | 鐩樼偣宸紓澶勭悊 | 鑽夌鈫掑緟瀹℃壒鈫掑凡瀹屾垚 | 鐩樼泩+搴撳瓨锛岀洏浜�-搴撳瓨 |
+
+---
+
+## 鍥涖�佸崟鎹姸鎬佹祦杞浘
+
+### 4.1 閲囪喘鍏ュ簱鍗曪紙閲囪喘鍏ュ簱锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� submit 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� stock 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� finish 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鑽夌 鈹� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 寰呬笂鏋� 鈹� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 寰呭叆搴� 鈹� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 宸插畬鎴� 鈹�
+鈹�(PREPARE)鈹� 鈹�(APPROVING)鈹� 鈹�(APPROVED)鈹� 鈹�(FINISHED)鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹� 鈹�
+ 鈹� 鈹� 鈹� 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ cancel鈹�
+ 鈫�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 宸插彇娑� 鈹�
+ 鈹�(CANCELED)鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 4.2 閿�鍞嚭搴撳崟锛堥攢鍞嚭搴擄級
+
+```
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鑽夌 鈹�
+ 鈹� (PREPARE) 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� submit
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹�
+ 鈫� (闇�瑕丱QC) 鈹� 鈫� (涓嶉渶瑕丱QC)
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 寰呮娴� 鈹� 鈹� 鈹� 寰呮嫞璐� 鈹�
+ 鈹�(CONFIRMED)鈹� 鈹� 鈹�(APPROVING)鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹� 鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�
+ 鈹� OQC瀹屾垚 鈹� 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹�
+ 鈹� 鈹� stock
+ 鈫� 鈫�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 寰呮嫞璐� 鈹傗攢鈹�鈹�鈫掆攤寰呭~鍐欒繍鍗� 鈹�
+ 鈹�(APPROVING)鈹� 鈹� (SHIPPING)鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�
+ 鈹� shipping
+ 鈫�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 寰呮墽琛屽嚭搴撯攤
+ 鈹�(APPROVED) 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�
+ 鈹� finish
+ 鈫�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 宸插畬鎴� 鈹�
+ 鈹�(FINISHED) 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 4.3 杞Щ璋冩嫧鍗�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� submit 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� stock 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� finish 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鑽夌 鈹� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 寰呯‘璁�/ 鈹� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 寰呬笂鏋� 鈹� 鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 寰呮墽琛� 鈹� 鈹�鈹�鈹�鈹�鈹�鈹�鈫� 鈹� 宸插畬鎴� 鈹�
+鈹�(PREPARE)鈹� 鈹� 寰呬笂鏋� 鈹� 鈹�(APPROVING)鈹� 鈹�(APPROVED)鈹� 鈹�(FINISHED)鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹�(UNCONFIRMED/鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� APPROVING) 鈹�
+ 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈫�
+ (閰嶉�佹ā寮忓厛寰呯‘璁�)
+```
+
+---
+
+## 浜斻�佹牳蹇冩湇鍔℃灦鏋�
+
+### 5.1 鏈嶅姟鍒嗗眰
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� Controller 灞� 鈹�
+鈹� (MesWmItemReceiptController, MesWmProductSalesController) 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈫�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� Service 灞� 鈹�
+鈹� 涓诲崟Service锛氫笟鍔¢�昏緫銆佺姸鎬佹祦杞� 鈹�
+鈹� 琛孲ervice锛氬崟鎹绠$悊 鈹�
+鈹� 鏄庣粏Service锛氬崟鎹槑缁嗙鐞� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈫�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鏍稿績搴撳瓨鏈嶅姟 鈹�
+鈹� MesWmTransactionService锛氫簨鍔℃湇鍔★紙鍏ュ簱/鍑哄簱鐨勬牳蹇冿級 鈹�
+鈹� MesWmMaterialStockService锛氬簱瀛樺彴璐︽湇鍔� 鈹�
+鈹� MesWmBatchService锛氭壒娆℃湇鍔� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈫�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� Mapper 灞� 鈹�
+鈹� (MesWmTransactionMapper, MesWmMaterialStockMapper) 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 5.2 浜嬪姟鏈嶅姟鏍稿績娴佺▼
+
+```java
+// MesWmTransactionService#createTransaction
+public Long createTransaction(MesWmTransactionSaveReqDTO reqDTO) {
+ // 1. 鑾峰彇鎴栧垱寤哄簱瀛樺彴璐﹁褰�
+ MesWmMaterialStockDO stock = materialStockService.getOrCreateMaterialStock(...);
+
+ // 2. 鏍¢獙搴撳瓨鏁伴噺锛堝嚭搴撴椂锛�
+ if (quantity < 0 && checkFlag) {
+ validateStockQuantity(stock, quantity);
+ }
+
+ // 3. 鏇存柊搴撳瓨鍙拌处鏁伴噺
+ materialStockService.updateMaterialStockQuantity(stock.getId(), quantity);
+
+ // 4. 鎻掑叆浜嬪姟娴佹按璁板綍
+ MesWmTransactionDO transaction = convertToTransaction(reqDTO, stock);
+ transactionMapper.insert(transaction);
+
+ return transaction.getId();
+}
+```
+
+---
+
+## 鍏�佸崟鎹粨鏋勮璁�
+
+姣忕涓氬姟鍗曟嵁閲囩敤**涓夎〃缁撴瀯**锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 涓诲崟琛� 鈹� 1:n 鈹� 琛岃〃 鈹� 1:n 鈹� 鏄庣粏琛� 鈹�
+鈹� (xxxDO) 鈹傗攢鈹�鈹�鈹�鈫掆攤 (xxxLineDO) 鈹傗攢鈹�鈹�鈹�鈫掆攤(xxxDetailDO) 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鈹� 鈹�
+ 鈹� 鈹� 鈹�
+ 鈫� 鈫� 鈫�
+ 鍗曟嵁澶翠俊鎭� 鎸夌墿鏂欏垎缁� 鍏蜂綋搴撲綅鏄庣粏
+ - 鍗曞彿 - 鐗╂枡ID - 搴撲綅ID
+ - 鐘舵�� - 璁″垝鏁伴噺 - 瀹為檯鏁伴噺
+ - 渚涘簲鍟�/瀹㈡埛 - 鎵规淇℃伅 - 鎵规淇℃伅
+```
+
+**绀轰緥锛氶噰璐叆搴撳崟**
+
+```
+mes_wm_item_receipt锛堜富鍗曪級
+鈹溾攢鈹� mes_wm_item_receipt_line锛堣锛�
+鈹� 鈹溾攢鈹� 鐗╂枡A - 璁″垝鍏ュ簱100涓�
+鈹� 鈹斺攢鈹� 鐗╂枡B - 璁″垝鍏ュ簱50涓�
+鈹斺攢鈹� mes_wm_item_receipt_detail锛堟槑缁嗭級
+ 鈹溾攢鈹� 鐗╂枡A - 搴撲綅01 - 60涓�
+ 鈹溾攢鈹� 鐗╂枡A - 搴撲綅02 - 40涓�
+ 鈹斺攢鈹� 鐗╂枡B - 搴撲綅01 - 50涓�
+```
+
+---
+
+## 涓冦�佷笌澶栭儴妯″潡闆嗘垚
+
+### 7.1 ERP闆嗘垚锛堥噰璐叆搴�/閿�鍞嚭搴擄級
+
+```
+ERP閲囪喘鍏ュ簱鍗� 鈹�鈹�瀹℃牳鈹�鈹�鈫� WmsStockApi.purchaseInbound()
+ 鈹�
+ 鈫�
+ 鏌ユ壘浠撳簱鏄犲皠(ERP浠撳簱鈫扢ES浠撳簱)
+ 鈹�
+ 鈫�
+ 鏌ユ壘鐗╂枡鏄犲皠(ERP浜у搧barCode鈫扢DM鐗╂枡barCode)
+ 鈹�
+ 鈫�
+ 鍒涘缓MES搴撳瓨浜嬪姟
+ 鈹�
+ 鈫�
+ MES搴撳瓨澧炲姞
+```
+
+### 7.2 鐢熶骇妯″潡闆嗘垚
+
+```
+鐢熶骇宸ュ崟 鈹�鈹�瀹屽伐姹囨姤鈹�鈹�鈫� 鍒涘缓鐢熶骇鍏ュ簱鍗�
+ 鈹�
+ 鈫�
+ 鐢熶骇鍏ュ簱鍗曞鏍搁�氳繃
+ 鈹�
+ 鈫�
+ MES搴撳瓨澧炲姞锛堜骇鍝佸叆搴擄級
+```
+
+### 7.3 璐ㄦ妯″潡闆嗘垚
+
+```
+閿�鍞嚭搴撳崟 鈹�鈹�鎻愪氦鈹�鈹�鈫� 妫�鏌ユ槸鍚﹂渶瑕丱QC妫�楠�
+ 鈹� 鈹�
+ 鈹� 鈫� (闇�瑕�)
+ 鈹� 鍒涘缓OQC妫�楠屽崟
+ 鈹� 鈹�
+ 鈹� 鈫� OQC閫氳繃
+ 鈹� 閿�鍞嚭搴撳崟鐘舵�佹祦杞�
+ 鈹� 鈹�
+ 鈫� (涓嶉渶瑕�) 鈫�
+ 鐩存帴寰呮嫞璐� 鈫愨攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 鍏�佸叧閿笟鍔¤鍒�
+
+### 8.1 搴撳瓨鏍¢獙瑙勫垯
+
+- 鍑哄簱鏃跺繀椤绘牎楠屽簱瀛樻槸鍚﹀厖瓒�
+- 鏀寔鍐荤粨搴撳瓨锛堣皟鎷ㄩ厤閫佹ā寮忎笅鍐荤粨婧愬簱瀛橈級
+- 搴撲綅娣锋斁瑙勫垯鏍¢獙锛堝彲閰嶇疆鏄惁鍏佽鐗╂枡/鎵规娣锋斁锛�
+
+### 8.2 鎵规绠$悊
+
+- 鍏ュ簱鏃惰嚜鍔ㄧ敓鎴愭垨鎸囧畾鎵规鍙�
+- 鎵规淇℃伅璺熼殢搴撳瓨娴佽浆
+- 鏀寔鎵规杩芥函
+
+### 8.3 鏉$爜/SN鐮佺鐞�
+
+- 鍏ュ簱鏃惰嚜鍔ㄧ敓鎴愭潯鐮佽褰�
+- SN鐮佹敮鎸佹壒閲忕敓鎴愶紙鎸夋壒娆UID绠$悊锛�
+- 鏉$爜涓庡簱瀛樿褰曞叧鑱�
+
+---
+
+## 涔濄�佹暟鎹簱琛ㄦ竻鍗�
+
+### 9.1 鍩虹璁剧疆
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| mes_wm_warehouse | 浠撳簱 |
+| mes_wm_warehouse_location | 搴撳尯 |
+| mes_wm_warehouse_area | 搴撲綅 |
+| mes_wm_batch | 鎵规 |
+| mes_wm_barcode | 鏉$爜 |
+| mes_wm_barcode_config | 鏉$爜閰嶇疆 |
+| mes_wm_sn | SN鐮� |
+
+### 9.2 搴撳瓨鏍稿績
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| mes_wm_material_stock | 搴撳瓨鍙拌处锛堢幇鏈夐噺锛� |
+| mes_wm_transaction | 搴撳瓨浜嬪姟娴佹按 |
+
+### 9.3 鍏ュ簱鍗曟嵁
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| mes_wm_arrival_notice | 鍒拌揣閫氱煡鍗� |
+| mes_wm_arrival_notice_line | 鍒拌揣閫氱煡鍗曡 |
+| mes_wm_item_receipt | 閲囪喘鍏ュ簱鍗� |
+| mes_wm_item_receipt_line | 閲囪喘鍏ュ簱鍗曡 |
+| mes_wm_item_receipt_detail | 閲囪喘鍏ュ簱鍗曟槑缁� |
+| mes_wm_product_receipt | 鐢熶骇鍏ュ簱鍗� |
+| mes_wm_misc_receipt | 鏉傞」鍏ュ簱鍗� |
+| mes_wm_outsource_receipt | 澶栧崗鍏ュ簱鍗� |
+| mes_wm_return_sales | 閿�鍞��璐у崟 |
+| mes_wm_return_issue | 鐢熶骇閫�鏂欏崟 |
+
+### 9.4 鍑哄簱鍗曟嵁
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| mes_wm_sales_notice | 鍙戣揣閫氱煡鍗� |
+| mes_wm_product_sales | 閿�鍞嚭搴撳崟 |
+| mes_wm_product_issue | 鐢熶骇棰嗘枡鍗� |
+| mes_wm_misc_issue | 鏉傞」鍑哄簱鍗� |
+| mes_wm_outsource_issue | 澶栧崗鍙戞枡鍗� |
+| mes_wm_return_vendor | 閲囪喘閫�璐у崟 |
+
+### 9.5 璋冩嫧鐩樼偣
+
+| 琛ㄥ悕 | 璇存槑 |
+|------|------|
+| mes_wm_transfer | 杞Щ璋冩嫧鍗� |
+| mes_wm_stock_taking_plan | 鐩樼偣璁″垝 |
+| mes_wm_stock_taking_task | 鐩樼偣浠诲姟 |
+| mes_wm_stock_taking_task_line | 鐩樼偣浠诲姟琛� |
+| mes_wm_stock_taking_task_result | 鐩樼偣缁撴灉 |
+
+---
+
+## 鍗併�佹帴鍙f潈闄愬墠缂�
+
+| 妯″潡 | 鏉冮檺鍓嶇紑 |
+|------|---------|
+| 浠撳簱璁剧疆 | mes:wm-warehouse |
+| 閲囪喘鍏ュ簱 | mes:wm-item-receipt |
+| 閿�鍞嚭搴� | mes:wm-product-sales |
+| 鐢熶骇棰嗘枡 | mes:wm-product-issue |
+| 鐢熶骇鍏ュ簱 | mes:wm-product-receipt |
+| 杞Щ璋冩嫧 | mes:wm-transfer |
+| 搴撳瓨鐜版湁閲� | mes:wm-material-stock |
+| 鐩樼偣 | mes:wm-stock-taking |
+
+---
+
+## 鍗佷竴銆佸叧閿灇涓捐鏄�
+
+### 11.1 閫氱敤鐘舵�佸父閲�
+
+```java
+MesOrderStatusConstants:
+ PREPARE = 0 // 鑽夌
+ APPROVING = 10 // 寰呭鎵�/寰呬笂鏋�/寰呮嫞璐�
+ CONFIRMED = 5 // 寰呯‘璁�/寰呮娴�
+ APPROVED = 20 // 宸插鎵�/寰呮墽琛�
+ FINISHED = 30 // 宸插畬鎴�
+ CANCELLED = 40 // 宸插彇娑�
+```
+
+### 11.2 涓氬姟绫诲瀷甯搁噺
+
+```java
+MesBizTypeConstants:
+ WM_ITEM_RECEIPT_IN = 110 // 閲囪喘鍏ュ簱
+ WM_TRANSFER_OUT = 111 // 璋冩嫧鍑哄簱
+ WM_TRANSFER_IN = 112 // 璋冩嫧鍏ュ簱
+ WM_PRODUCT_SALES = 118 // 閿�鍞嚭搴�
+ WM_ISSUE = 115 // 鐢熶骇棰嗘枡
+ WM_PRODUCT_RECPT = 117 // 鐢熶骇鍏ュ簱
+ ...
+```
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index d8b83af..85eafa5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,10 +28,14 @@
<!-- <module>yudao-module-iot</module>-->
<module>yudao-module-mes</module>
<module>yudao-module-wms</module>
+ <module>yudao-module-wms-api</module>
<module>yudao-module-im</module>
<!-- MDM 涓绘暟鎹鐞嗘ā鍧� -->
<module>yudao-module-mdm-api</module>
<module>yudao-module-mdm</module>
+ <!-- HRM 浜哄姏璧勬簮绠$悊妯″潡 -->
+ <module>yudao-module-hrm-api</module>
+ <module>yudao-module-hrm</module>
<!-- 璇峰弬鑰� https://doc.iocoder.cn/ai/build/ 鏂囨。锛屽畬鎴� AI 妯″潡鐨勫惎鍔紒锛侊紒 -->
<!-- <module>yudao-module-ai</module>-->
</modules>
diff --git a/sql/mysql/erp_mes_warehouse_mapping.sql b/sql/mysql/erp_mes_warehouse_mapping.sql
new file mode 100644
index 0000000..120dfbf
--- /dev/null
+++ b/sql/mysql/erp_mes_warehouse_mapping.sql
@@ -0,0 +1,26 @@
+-- =============================================
+-- ERP浠撳簱涓嶮ES浠撳簱鏄犲皠琛�
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS erp_mes_warehouse_mapping (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭ID',
+ erp_warehouse_id BIGINT NOT NULL COMMENT 'ERP浠撳簱ID',
+ erp_warehouse_name VARCHAR(255) COMMENT 'ERP浠撳簱鍚嶇О锛堝啑浣欙級',
+ mes_warehouse_id BIGINT NOT NULL COMMENT 'MES浠撳簱ID',
+ mes_warehouse_name VARCHAR(255) COMMENT 'MES浠撳簱鍚嶇О锛堝啑浣欙級',
+ status TINYINT DEFAULT 1 COMMENT '鐘舵�侊紙1-鍚敤 0-绂佺敤锛�',
+ creator VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ updater VARCHAR(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ deleted BIT DEFAULT 0 COMMENT '鏄惁鍒犻櫎',
+ UNIQUE KEY uk_erp_warehouse (erp_warehouse_id),
+ INDEX idx_mes_warehouse (mes_warehouse_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ERP浠撳簱涓嶮ES浠撳簱鏄犲皠琛�';
+
+-- =============================================
+-- 璇存槑锛�
+-- 1. 姝よ〃鐢ㄤ簬寤虹珛ERP浠撳簱涓嶮ES浠撳簱鐨勬槧灏勫叧绯�
+-- 2. 涓�涓狤RP浠撳簱鍙兘鏄犲皠鍒颁竴涓狹ES浠撳簱
+-- 3. 鏄犲皠寤虹珛鍚庯紝ERP閲囪喘鍏ュ簱/閿�鍞嚭搴撳鏍告椂浼氳嚜鍔ㄦ洿鏂板搴旂殑MES搴撳瓨
+-- =============================================
\ No newline at end of file
diff --git a/sql/mysql/hrm_attendance_supplement.sql b/sql/mysql/hrm_attendance_supplement.sql
new file mode 100644
index 0000000..ec3a9b9
--- /dev/null
+++ b/sql/mysql/hrm_attendance_supplement.sql
@@ -0,0 +1,157 @@
+-- =============================================
+-- HRM 鑰冨嫟绠$悊琛ュ厖 SQL 鍒濆鍖栬剼鏈�
+-- 鍖呭惈鑺傚亣鏃ラ厤缃�佽�冨嫟缁熻姹囨�昏〃銆佽彍鍗曟潈闄�
+-- =============================================
+
+-- 1. 鑺傚亣鏃ラ厤缃〃
+CREATE TABLE IF NOT EXISTS `hrm_attendance_holiday` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鑺傚亣鏃D',
+ `name` varchar(100) NOT NULL COMMENT '鑺傚亣鏃ュ悕绉�',
+ `date` date NOT NULL COMMENT '鏃ユ湡',
+ `type` tinyint NOT NULL DEFAULT 1 COMMENT '绫诲瀷锛�1-浼戞伅鏃ワ紝2-宸ヤ綔鏃�',
+ `is_legal` tinyint NOT NULL DEFAULT 0 COMMENT '鏄惁娉曞畾鑺傚亣鏃ワ細0-鍚︼紝1-鏄�',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_date` (`date`, `deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鑰冨嫟鑺傚亣鏃ラ厤缃〃';
+
+-- 2. 鑰冨嫟缁熻姹囨�昏〃
+CREATE TABLE IF NOT EXISTS `hrm_attendance_summary` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '姹囨�籌D',
+ `user_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+ `year_month` varchar(7) NOT NULL COMMENT '骞存湀锛屾牸寮忥細yyyy-MM',
+ `work_days` int NOT NULL DEFAULT 0 COMMENT '搴斿嚭鍕ゅぉ鏁�',
+ `actual_days` int NOT NULL DEFAULT 0 COMMENT '瀹為檯鍑哄嫟澶╂暟',
+ `late_count` int NOT NULL DEFAULT 0 COMMENT '杩熷埌娆℃暟',
+ `early_count` int NOT NULL DEFAULT 0 COMMENT '鏃╅��娆℃暟',
+ `absent_count` int NOT NULL DEFAULT 0 COMMENT '缂哄崱娆℃暟',
+ `overtime_hours` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '鍔犵彮鏃堕暱锛堝皬鏃讹級',
+ `leave_hours` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '璇峰亣鏃堕暱锛堝皬鏃讹級',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_user_year_month` (`user_id`, `year_month`, `deleted`),
+ KEY `idx_dept_year_month` (`dept_id`, `year_month`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鑰冨嫟缁熻姹囨�昏〃';
+
+-- =============================================
+-- 3. 鑿滃崟鏉冮檺
+-- =============================================
+
+-- 3.1 鑰冨嫟瑙勫垯鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6013, '鑰冨嫟瑙勫垯', '', 2, 0, 6010, 'rule', 'ep:setting', 'hrm/attendance/rule/index', 'HrmAttendanceRule', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60131, '鑰冨嫟瑙勫垯鏌ヨ', 'hrm:attendance:query', 3, 1, 6013, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60132, '鑰冨嫟瑙勫垯鏂板', 'hrm:attendance:create', 3, 2, 6013, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60133, '鑰冨嫟瑙勫垯淇敼', 'hrm:attendance:update', 3, 3, 6013, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60134, '鑰冨嫟瑙勫垯鍒犻櫎', 'hrm:attendance:delete', 3, 4, 6013, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60135, '鑰冨嫟瑙勫垯瀵煎嚭', 'hrm:attendance:export', 3, 5, 6013, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 3.2 鑺傚亣鏃ラ厤缃彍鍗�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6014, '鑺傚亣鏃ラ厤缃�', '', 2, 3, 6010, 'holiday', 'ep:calendar', 'hrm/attendance/holiday/index', 'HrmAttendanceHoliday', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60141, '鑺傚亣鏃ラ厤缃煡璇�', 'hrm:attendance:query', 3, 1, 6014, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60142, '鑺傚亣鏃ラ厤缃柊澧�', 'hrm:attendance:create', 3, 2, 6014, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60143, '鑺傚亣鏃ラ厤缃慨鏀�', 'hrm:attendance:update', 3, 3, 6014, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60144, '鑺傚亣鏃ラ厤缃垹闄�', 'hrm:attendance:delete', 3, 4, 6014, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60145, '鑺傚亣鏃ラ厤缃鍑�', 'hrm:attendance:export', 3, 5, 6014, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 3.3 鑰冨嫟缁熻姹囨�昏彍鍗�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6015, '鑰冨嫟缁熻', '', 2, 4, 6010, 'summary', 'ep:data-analysis', 'hrm/attendance/summary/index', 'HrmAttendanceSummary', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60151, '鑰冨嫟缁熻鏌ヨ', 'hrm:attendance:query', 3, 1, 6015, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60152, '鑰冨嫟缁熻鐢熸垚', 'hrm:attendance:calculate', 3, 2, 6015, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60153, '鑰冨嫟缁熻瀵煎嚭', 'hrm:attendance:export', 3, 3, 6015, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 3.4 鎵撳崱鏉冮檺
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60114, '鎵撳崱', 'hrm:attendance:clock', 3, 4, 6011, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- =============================================
+-- 4. 瑙掕壊鑿滃崟鍏宠仈
+-- =============================================
+
+-- 缁欒鑹� ID=1锛堣秴绾х鐞嗗憳锛夊垎閰嶆柊澧炵殑鑿滃崟鏉冮檺
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6013, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60131, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60132, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60133, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60134, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60135, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6014, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60141, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60142, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60143, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60144, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60145, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6015, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60151, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60152, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60153, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60114, '1', NOW(), '1', NOW(), b'0');
+
+-- =============================================
+-- 5. BPM 瀹℃壒鍒嗙被閰嶇疆锛堝彲閫夛級
+-- =============================================
+
+-- 琛ュ崱瀹℃壒鍒嗙被
+INSERT INTO `bpm_category` (`id`, `name`, `code`, `description`, `status`, `sort`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (200, '鑰冨嫟寮傚父瀹℃壒', 'hrm_attendance_approve', 'HRM鑰冨嫟寮傚父瀹℃壒鍒嗙被', 0, 50, '1', NOW(), '1', NOW(), b'0');
+
+-- =============================================
+-- 鎵ц瀹屾垚鎻愮ず
+-- =============================================
+SELECT 'HRM 鑰冨嫟绠$悊琛ュ厖 SQL 鍒濆鍖栧畬鎴愶紒' AS result;
\ No newline at end of file
diff --git a/sql/mysql/hrm_deduction_init.sql b/sql/mysql/hrm_deduction_init.sql
new file mode 100644
index 0000000..71cff09
--- /dev/null
+++ b/sql/mysql/hrm_deduction_init.sql
@@ -0,0 +1,180 @@
+-- =============================================
+-- 绀句繚鍏Н閲戜釜绋庢墸闄ゅ姛鑳� SQL
+-- =============================================
+
+-- 1. 绀句繚鍏Н閲戦厤缃〃锛堝叏灞�閰嶇疆锛�
+CREATE TABLE IF NOT EXISTS `hrm_social_security_config` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '閰嶇疆ID',
+ `name` varchar(100) NOT NULL COMMENT '閰嶇疆鍚嶇О',
+ `effective_date` date NOT NULL COMMENT '鐢熸晥鏃ユ湡',
+ -- 鍏昏�佷繚闄�
+ `pension_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.16 COMMENT '鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�',
+ `pension_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.08 COMMENT '鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�',
+ -- 鍖荤枟淇濋櫓
+ `medical_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.095 COMMENT '鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥',
+ `medical_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.02 COMMENT '鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥',
+ -- 澶变笟淇濋櫓
+ `unemployment_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥',
+ `unemployment_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '澶变笟淇濋櫓涓汉缂寸撼姣斾緥',
+ -- 宸ヤ激淇濋櫓锛堜粎浼佷笟缂寸撼锛�
+ `work_injury_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥',
+ -- 鐢熻偛淇濋櫓锛堜粎浼佷笟缂寸撼锛�
+ `maternity_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥',
+ -- 鍏Н閲�
+ `housing_fund_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.12 COMMENT '鍏Н閲戜紒涓氱即绾虫瘮渚�',
+ `housing_fund_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.12 COMMENT '鍏Н閲戜釜浜虹即绾虫瘮渚�',
+ -- 绀句繚鍩烘暟涓婁笅闄�
+ `social_security_base_min` decimal(12,2) NOT NULL COMMENT '绀句繚鍩烘暟涓嬮檺',
+ `social_security_base_max` decimal(12,2) NOT NULL COMMENT '绀句繚鍩烘暟涓婇檺',
+ -- 鍏Н閲戝熀鏁颁笂涓嬮檺
+ `housing_fund_base_min` decimal(12,2) NOT NULL COMMENT '鍏Н閲戝熀鏁颁笅闄�',
+ `housing_fund_base_max` decimal(12,2) NOT NULL COMMENT '鍏Н閲戝熀鏁颁笂闄�',
+ -- 鐘舵��
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_effective_date` (`effective_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM绀句繚鍏Н閲戦厤缃〃';
+
+-- 2. 涓◣绋庣巼閰嶇疆琛紙绱繘绋庣巼锛�
+CREATE TABLE IF NOT EXISTS `hrm_tax_rate_config` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '閰嶇疆ID',
+ `name` varchar(100) NOT NULL COMMENT '閰嶇疆鍚嶇О',
+ `effective_date` date NOT NULL COMMENT '鐢熸晥鏃ユ湡',
+ `level` tinyint NOT NULL COMMENT '绋庣巼绾ф暟锛�1-7',
+ `min_amount` decimal(12,2) NOT NULL COMMENT '搴旂撼绋庢墍寰楅涓嬮檺锛堝惈锛�',
+ `max_amount` decimal(12,2) NOT NULL COMMENT '搴旂撼绋庢墍寰楅涓婇檺锛堜笉鍚級锛�0琛ㄧず鏃犱笂闄�',
+ `tax_rate` decimal(5,4) NOT NULL COMMENT '绋庣巼',
+ `quick_deduction` decimal(12,2) NOT NULL DEFAULT 0 COMMENT '閫熺畻鎵i櫎鏁�',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_effective_date` (`effective_date`),
+ KEY `idx_level` (`level`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM涓◣绋庣巼閰嶇疆琛�';
+
+-- 3. 鍛樺伐绀句繚鍏Н閲戞。妗堣〃
+CREATE TABLE IF NOT EXISTS `hrm_employee_social_security` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '妗fID',
+ `user_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `social_security_base` decimal(12,2) NOT NULL COMMENT '绀句繚缂寸撼鍩烘暟',
+ `housing_fund_base` decimal(12,2) NOT NULL COMMENT '鍏Н閲戠即绾冲熀鏁�',
+ `effective_date` date NOT NULL COMMENT '鐢熸晥鏃ユ湡',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_user_effective` (`user_id`, `effective_date`),
+ KEY `idx_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍛樺伐绀句繚鍏Н閲戞。妗堣〃';
+
+-- =============================================
+-- 鍒濆鍖栨暟鎹�
+-- =============================================
+
+-- 鍒濆鍖栫ぞ淇濆叕绉噾閰嶇疆锛�2024骞存爣鍑嗭紝绀轰緥鏁版嵁锛�
+INSERT INTO `hrm_social_security_config`
+(`name`, `effective_date`, `pension_company_ratio`, `pension_personal_ratio`,
+ `medical_company_ratio`, `medical_personal_ratio`,
+ `unemployment_company_ratio`, `unemployment_personal_ratio`,
+ `work_injury_ratio`, `maternity_ratio`,
+ `housing_fund_company_ratio`, `housing_fund_personal_ratio`,
+ `social_security_base_min`, `social_security_base_max`,
+ `housing_fund_base_min`, `housing_fund_base_max`,
+ `status`, `remark`, `creator`, `updater`)
+VALUES
+('2024骞寸ぞ淇濆叕绉噾鏍囧噯', '2024-01-01',
+ 0.16, 0.08, -- 鍏昏�侊細浼佷笟16%锛屼釜浜�8%
+ 0.095, 0.02, -- 鍖荤枟锛氫紒涓�9.5%锛屼釜浜�2%
+ 0.005, 0.005, -- 澶变笟锛氫紒涓�0.5%锛屼釜浜�0.5%
+ 0.005, 0.005, -- 宸ヤ激0.5%锛岀敓鑲�0.5%锛堜粎浼佷笟锛�
+ 0.12, 0.12, -- 鍏Н閲戯細浼佷笟12%锛屼釜浜�12%
+ 4494.00, 28017.00, -- 绀句繚鍩烘暟锛氫笅闄�4494锛屼笂闄�28017
+ 2420.00, 35091.00, -- 鍏Н閲戝熀鏁帮細涓嬮檺2420锛屼笂闄�35091
+ 0, '2024骞村崡閫氬競绀句繚鍏Н閲戠即绾虫爣鍑�', '1', '1');
+
+-- 鍒濆鍖栦釜绋庣◣鐜囪〃锛�2024骞存爣鍑嗭級
+INSERT INTO `hrm_tax_rate_config`
+(`name`, `effective_date`, `level`, `min_amount`, `max_amount`, `tax_rate`, `quick_deduction`, `status`, `remark`, `creator`, `updater`)
+VALUES
+('2024骞翠釜绋庣◣鐜囪〃', '2024-01-01', 1, 0, 36000, 0.03, 0, 0, '涓嶈秴杩�36000鍏�', '1', '1'),
+('2024骞翠釜绋庣◣鐜囪〃', '2024-01-01', 2, 36000, 144000, 0.10, 2520, 0, '瓒呰繃36000鍏冭嚦144000鍏�', '1', '1'),
+('2024骞翠釜绋庣◣鐜囪〃', '2024-01-01', 3, 144000, 300000, 0.20, 16920, 0, '瓒呰繃144000鍏冭嚦300000鍏�', '1', '1'),
+('2024骞翠釜绋庣◣鐜囪〃', '2024-01-01', 4, 300000, 420000, 0.25, 31920, 0, '瓒呰繃300000鍏冭嚦420000鍏�', '1', '1'),
+('2024骞翠釜绋庣◣鐜囪〃', '2024-01-01', 5, 420000, 660000, 0.30, 52920, 0, '瓒呰繃420000鍏冭嚦660000鍏�', '1', '1'),
+('2024骞翠釜绋庣◣鐜囪〃', '2024-01-01', 6, 660000, 960000, 0.35, 85920, 0, '瓒呰繃660000鍏冭嚦960000鍏�', '1', '1'),
+('2024骞翠釜绋庣◣鐜囪〃', '2024-01-01', 7, 960000, 0, 0.45, 181920, 0, '瓒呰繃960000鍏�', '1', '1');
+
+-- =============================================
+-- 鑿滃崟鏉冮檺
+-- =============================================
+
+-- 绀句繚鍏Н閲戦厤缃彍鍗�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6023, '绀句繚鍏Н閲戦厤缃�', '', 2, 3, 6020, 'social-security', 'ep:setting', 'hrm/salary/social-security/index', 'HrmSocialSecurityConfig', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60231, '绀句繚閰嶇疆鏌ヨ', 'hrm:social-security:query', 3, 1, 6023, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60232, '绀句繚閰嶇疆鏂板', 'hrm:social-security:create', 3, 2, 6023, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60233, '绀句繚閰嶇疆淇敼', 'hrm:social-security:update', 3, 3, 6023, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60234, '绀句繚閰嶇疆鍒犻櫎', 'hrm:social-security:delete', 3, 4, 6023, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 涓◣绋庣巼閰嶇疆鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6024, '涓◣绋庣巼閰嶇疆', '', 2, 4, 6020, 'tax-rate', 'ep:document', 'hrm/salary/tax-rate/index', 'HrmTaxRateConfig', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60241, '涓◣閰嶇疆鏌ヨ', 'hrm:tax-rate:query', 3, 1, 6024, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60242, '涓◣閰嶇疆鏂板', 'hrm:tax-rate:create', 3, 2, 6024, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60243, '涓◣閰嶇疆淇敼', 'hrm:tax-rate:update', 3, 3, 6024, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60244, '涓◣閰嶇疆鍒犻櫎', 'hrm:tax-rate:delete', 3, 4, 6024, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 瑙掕壊鑿滃崟鍏宠仈
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6023, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60231, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60232, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60233, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60234, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6024, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60241, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60242, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60243, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60244, '1', NOW(), '1', NOW(), b'0');
+
+SELECT '绀句繚鍏Н閲戜釜绋庢墸闄ゅ姛鑳� SQL 鍒濆鍖栧畬鎴愶紒' AS result;
diff --git a/sql/mysql/hrm_employee_social_security_update.sql b/sql/mysql/hrm_employee_social_security_update.sql
new file mode 100644
index 0000000..6be0194
--- /dev/null
+++ b/sql/mysql/hrm_employee_social_security_update.sql
@@ -0,0 +1,105 @@
+-- =============================================
+-- 鍛樺伐绀句繚鍏Н閲戞。妗堣〃閲嶆柊璁捐
+-- 涓�涓ぞ淇濇柟妗堝彲浠ョ粦瀹氬涓憳宸ワ紝涓�涓憳宸ョ粦瀹氫竴涓ぞ淇濇柟妗�
+-- =============================================
+
+-- 鍒犻櫎鏃ц〃锛堝鏋滃瓨鍦級
+DROP TABLE IF EXISTS `hrm_employee_social_security`;
+
+-- 鍒涘缓鍛樺伐绀句繚鍏Н閲戞。妗堣〃
+CREATE TABLE IF NOT EXISTS `hrm_employee_social_security` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '妗fID',
+ `user_id` bigint NOT NULL COMMENT '绯荤粺鐢ㄦ埛ID',
+
+ -- 缁戝畾鐨勭ぞ淇濆叕绉噾鏂规ID
+ `social_security_scheme_id` bigint NOT NULL COMMENT '绀句繚鍏Н閲戞柟妗圛D',
+
+ -- 缂寸撼鍩烘暟锛堝憳宸ヤ釜鎬у寲璁剧疆锛�
+ `social_security_base` decimal(12,2) NOT NULL COMMENT '绀句繚缂寸撼鍩烘暟',
+ `housing_fund_base` decimal(12,2) NOT NULL COMMENT '鍏Н閲戠即绾冲熀鏁�',
+
+ -- 鐢熸晥淇℃伅
+ `effective_date` date NOT NULL COMMENT '鐢熸晥鏃ユ湡',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_user_effective` (`user_id`, `effective_date`),
+ KEY `idx_user_id` (`user_id`),
+ KEY `idx_scheme_id` (`social_security_scheme_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍛樺伐绀句繚鍏Н閲戞。妗堣〃';
+
+-- =============================================
+-- 绀句繚鍏Н閲戞柟妗堣〃锛堝師鍏ㄥ眬閰嶇疆琛ㄦ敼涓烘柟妗堣〃锛�
+-- 涓�涓柟妗堝彲浠ヨ澶氫釜鍛樺伐缁戝畾
+-- =============================================
+
+-- 鍒犻櫎鏃ц〃
+DROP TABLE IF EXISTS `hrm_social_security_config`;
+
+-- 鍒涘缓绀句繚鍏Н閲戞柟妗堣〃
+CREATE TABLE IF NOT EXISTS `hrm_social_security_scheme` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鏂规ID',
+ `name` varchar(100) NOT NULL COMMENT '鏂规鍚嶇О',
+ `effective_date` date NOT NULL COMMENT '鐢熸晥鏃ユ湡',
+
+ -- 鍏昏�佷繚闄╂瘮渚�
+ `pension_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.16 COMMENT '鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�',
+ `pension_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.08 COMMENT '鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�',
+
+ -- 鍖荤枟淇濋櫓姣斾緥
+ `medical_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.095 COMMENT '鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥',
+ `medical_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.02 COMMENT '鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥',
+
+ -- 澶变笟淇濋櫓姣斾緥
+ `unemployment_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥',
+ `unemployment_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '澶变笟淇濋櫓涓汉缂寸撼姣斾緥',
+
+ -- 宸ヤ激淇濋櫓姣斾緥锛堜粎浼佷笟缂寸撼锛�
+ `work_injury_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥',
+
+ -- 鐢熻偛淇濋櫓姣斾緥锛堜粎浼佷笟缂寸撼锛�
+ `maternity_ratio` decimal(5,4) NOT NULL DEFAULT 0.005 COMMENT '鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥',
+
+ -- 鍏Н閲戞瘮渚�
+ `housing_fund_company_ratio` decimal(5,4) NOT NULL DEFAULT 0.12 COMMENT '鍏Н閲戜紒涓氱即绾虫瘮渚�',
+ `housing_fund_personal_ratio` decimal(5,4) NOT NULL DEFAULT 0.12 COMMENT '鍏Н閲戜釜浜虹即绾虫瘮渚�',
+
+ -- 鐘舵��
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_effective_date` (`effective_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM绀句繚鍏Н閲戞柟妗堣〃';
+
+-- =============================================
+-- 鍒濆鍖栭粯璁ゆ柟妗�
+-- =============================================
+
+INSERT INTO `hrm_social_security_scheme`
+(`name`, `effective_date`,
+ `pension_company_ratio`, `pension_personal_ratio`,
+ `medical_company_ratio`, `medical_personal_ratio`,
+ `unemployment_company_ratio`, `unemployment_personal_ratio`,
+ `work_injury_ratio`, `maternity_ratio`,
+ `housing_fund_company_ratio`, `housing_fund_personal_ratio`,
+ `status`, `remark`, `creator`, `updater`)
+VALUES
+('2024骞村崡閫氬競鏍囧噯', '2024-01-01',
+ 0.16, 0.08,
+ 0.095, 0.02,
+ 0.005, 0.005,
+ 0.005, 0.005,
+ 0.12, 0.12,
+ 0, '2024骞村崡閫氬競绀句繚鍏Н閲戠即绾虫爣鍑�', '1', '1');
+
+SELECT '鍛樺伐绀句繚鍏Н閲戞。妗堣〃閲嶆柊璁捐瀹屾垚锛�' AS result;
\ No newline at end of file
diff --git a/sql/mysql/hrm_employee_update.sql b/sql/mysql/hrm_employee_update.sql
new file mode 100644
index 0000000..a864f0d
--- /dev/null
+++ b/sql/mysql/hrm_employee_update.sql
@@ -0,0 +1,25 @@
+-- =============================================
+-- HRM 鍛樺伐琛ㄧ粨鏋勫彉鏇� SQL
+-- 1. 鍒犻櫎 user_id 瀛楁
+-- 2. 娣诲姞鎵嬫満鍙峰敮涓�绱㈠紩
+-- 3. 娣诲姞鍩烘湰宸ヨ祫瀛楁
+-- 4. 娣诲姞瑙掕壊ID鍒楄〃瀛楁
+-- =============================================
+
+-- 1. 鍒犻櫎 user_id 鐩稿叧鐨勫敮涓�绱㈠紩
+ALTER TABLE `hrm_employee` DROP INDEX `uk_user_id`;
+
+-- 2. 鍒犻櫎 user_id 瀛楁
+ALTER TABLE `hrm_employee` DROP COLUMN `user_id`;
+
+-- 3. 娣诲姞鎵嬫満鍙峰敮涓�绱㈠紩
+ALTER TABLE `hrm_employee` ADD UNIQUE INDEX `uk_phone` (`phone`);
+
+-- 4. 娣诲姞鍩烘湰宸ヨ祫瀛楁
+ALTER TABLE `hrm_employee` ADD COLUMN `base_salary` decimal(12,2) DEFAULT NULL COMMENT '鍩烘湰宸ヨ祫' AFTER `salary_structure_id`;
+
+-- 5. 娣诲姞瑙掕壊ID鍒楄〃瀛楁锛圝SON鏍煎紡瀛樺偍锛�
+ALTER TABLE `hrm_employee` ADD COLUMN `role_ids` varchar(500) DEFAULT NULL COMMENT '瑙掕壊ID鍒楄〃锛圝SON鏍煎紡锛�' AFTER `base_salary`;
+
+-- 鎵ц瀹屾垚鎻愮ず
+SELECT 'HRM 鍛樺伐琛ㄧ粨鏋勫彉鏇村畬鎴愶紒' AS result;
\ No newline at end of file
diff --git a/sql/mysql/hrm_leave_type_config_init.sql b/sql/mysql/hrm_leave_type_config_init.sql
new file mode 100644
index 0000000..ef486c0
--- /dev/null
+++ b/sql/mysql/hrm_leave_type_config_init.sql
@@ -0,0 +1,26 @@
+-- 璇峰亣绫诲瀷鎵f閰嶇疆琛�
+CREATE TABLE IF NOT EXISTS hrm_leave_type_config (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭',
+ leave_type INT NOT NULL COMMENT '璇峰亣绫诲瀷锛�1-浜嬪亣, 2-鐥呭亣, 3-骞村亣, 4-濠氬亣, 5-浜у亣, 6-涓у亣',
+ name VARCHAR(32) NOT NULL COMMENT '绫诲瀷鍚嶇О',
+ deduction_ratio DECIMAL(5,4) NOT NULL DEFAULT 0.0000 COMMENT '鎵f姣斾緥锛�0-1涔嬮棿锛屽0.5琛ㄧず鎵�50%锛�',
+ remark VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
+ status INT NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤, 1-绂佺敤',
+ create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ creator VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ updater VARCHAR(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ deleted BIT NOT NULL DEFAULT 0 COMMENT '鏄惁鍒犻櫎'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '璇峰亣绫诲瀷鎵f閰嶇疆';
+
+-- 鍒濆鍖栭粯璁ゆ暟鎹�
+INSERT INTO hrm_leave_type_config (leave_type, name, deduction_ratio, remark, status) VALUES
+(1, '浜嬪亣', 1.0000, '鍏ㄩ鎵f', 0),
+(2, '鐥呭亣', 0.5000, '鎸�50%鎵f', 0),
+(3, '骞村亣', 0.0000, '涓嶆墸娆�', 0),
+(4, '濠氬亣', 0.0000, '涓嶆墸娆�', 0),
+(5, '浜у亣', 0.0000, '涓嶆墸娆�', 0),
+(6, '涓у亣', 0.0000, '涓嶆墸娆�', 0);
+
+-- 钖叕鏍哥畻琛ㄦ坊鍔犺鍋囨墸娆惧瓧娈�
+ALTER TABLE hrm_salary_calculation ADD COLUMN leave_deduction DECIMAL(10,2) DEFAULT 0.00 COMMENT '璇峰亣鎵f' AFTER other_deduction;
\ No newline at end of file
diff --git a/sql/mysql/hrm_module_init.sql b/sql/mysql/hrm_module_init.sql
new file mode 100644
index 0000000..2510c7f
--- /dev/null
+++ b/sql/mysql/hrm_module_init.sql
@@ -0,0 +1,594 @@
+-- =============================================
+-- HRM 浜哄姏璧勬簮妯″潡 SQL 鍒濆鍖栬剼鏈�
+-- 鍖呭惈浜轰簨瀹℃壒銆佽�冨嫟绠$悊銆佽柂閰鐞嗙浉鍏宠〃
+-- =============================================
+
+-- =============================================
+-- 1. 鍩虹淇℃伅妯″潡琛�
+-- =============================================
+
+-- 1.1 鍛樺伐淇℃伅琛紙鎵╁睍淇℃伅锛�
+CREATE TABLE IF NOT EXISTS `hrm_employee` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍛樺伐ID',
+ `user_id` bigint NOT NULL COMMENT '鍏宠仈绯荤粺鐢ㄦ埛ID',
+ `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+ `post_id` bigint DEFAULT NULL COMMENT '宀椾綅ID',
+ `employee_no` varchar(32) NOT NULL COMMENT '鍛樺伐缂栧彿',
+ `name` varchar(100) NOT NULL COMMENT '鍛樺伐濮撳悕',
+ `gender` tinyint DEFAULT NULL COMMENT '鎬у埆锛�1-鐢凤紝2-濂�',
+ `birthday` date DEFAULT NULL COMMENT '鍑虹敓鏃ユ湡',
+ `phone` varchar(20) DEFAULT NULL COMMENT '鎵嬫満鍙风爜',
+ `email` varchar(100) DEFAULT NULL COMMENT '閭',
+ `id_card` varchar(20) DEFAULT NULL COMMENT '韬唤璇佸彿',
+ `hire_date` date NOT NULL COMMENT '鍏ヨ亴鏃ユ湡',
+ `regular_date` date DEFAULT NULL COMMENT '杞鏃ユ湡',
+ `leave_date` date DEFAULT NULL COMMENT '绂昏亴鏃ユ湡',
+ `employee_status` tinyint NOT NULL DEFAULT 1 COMMENT '鍛樺伐鐘舵�侊細1-鍦ㄨ亴锛�2-璇曠敤锛�3-绂昏亴',
+ `annual_leave_balance` decimal(4,2) DEFAULT 0 COMMENT '骞村亣浣欓锛堝ぉ锛�',
+ `sick_leave_balance` decimal(4,2) DEFAULT 0 COMMENT '鐥呭亣浣欓锛堝ぉ锛�',
+ `salary_structure_id` bigint DEFAULT NULL COMMENT '钖叕缁撴瀯ID',
+ `bank_name` varchar(100) DEFAULT NULL COMMENT '寮�鎴烽摱琛�',
+ `bank_account` varchar(50) DEFAULT NULL COMMENT '閾惰璐﹀彿',
+ `address` varchar(200) DEFAULT NULL COMMENT '灞呬綇鍦板潃',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_user_id` (`user_id`),
+ UNIQUE KEY `uk_employee_no` (`employee_no`),
+ KEY `idx_dept_id` (`dept_id`),
+ KEY `idx_employee_status` (`employee_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍛樺伐淇℃伅琛�';
+
+-- 1.2 鍛樺伐鏁欒偛缁忓巻琛�
+CREATE TABLE IF NOT EXISTS `hrm_employee_education` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '璁板綍ID',
+ `employee_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `school_name` varchar(100) NOT NULL COMMENT '瀛︽牎鍚嶇О',
+ `major` varchar(100) DEFAULT NULL COMMENT '涓撲笟',
+ `degree` tinyint NOT NULL COMMENT '瀛﹀巻锛�1-楂樹腑锛�2-澶т笓锛�3-鏈锛�4-纭曞+锛�5-鍗氬+',
+ `start_date` date NOT NULL COMMENT '鍏ュ鏃堕棿',
+ `end_date` date NOT NULL COMMENT '姣曚笟鏃堕棿',
+ `certificate_no` varchar(50) DEFAULT NULL COMMENT '璇佷功缂栧彿',
+ `remark` varchar(200) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_employee_id` (`employee_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍛樺伐鏁欒偛缁忓巻琛�';
+
+-- 1.3 鍛樺伐宸ヤ綔缁忓巻琛�
+CREATE TABLE IF NOT EXISTS `hrm_employee_work_history` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '璁板綍ID',
+ `employee_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `company_name` varchar(100) NOT NULL COMMENT '鍏徃鍚嶇О',
+ `position` varchar(100) NOT NULL COMMENT '鑱屼綅',
+ `start_date` date NOT NULL COMMENT '鍏ヨ亴鏃堕棿',
+ `end_date` date DEFAULT NULL COMMENT '绂昏亴鏃堕棿',
+ `work_content` varchar(500) DEFAULT NULL COMMENT '宸ヤ綔鍐呭',
+ `leave_reason` varchar(200) DEFAULT NULL COMMENT '绂昏亴鍘熷洜',
+ `reference_name` varchar(50) DEFAULT NULL COMMENT '璇佹槑浜哄鍚�',
+ `reference_phone` varchar(20) DEFAULT NULL COMMENT '璇佹槑浜虹數璇�',
+ `remark` varchar(200) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_employee_id` (`employee_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍛樺伐宸ヤ綔缁忓巻琛�';
+
+-- 1.4 鍛樺伐绱ф�ヨ仈绯讳汉琛�
+CREATE TABLE IF NOT EXISTS `hrm_employee_emergency_contact` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '璁板綍ID',
+ `employee_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `name` varchar(50) NOT NULL COMMENT '鑱旂郴浜哄鍚�',
+ `relationship` varchar(50) NOT NULL COMMENT '涓庡憳宸ュ叧绯�',
+ `phone` varchar(20) NOT NULL COMMENT '鑱旂郴鐢佃瘽',
+ `address` varchar(200) DEFAULT NULL COMMENT '鑱旂郴鍦板潃',
+ `sort` tinyint DEFAULT 1 COMMENT '鎺掑簭锛堝彲璁剧疆澶氫釜绱ф�ヨ仈绯讳汉锛�',
+ `remark` varchar(200) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_employee_id` (`employee_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍛樺伐绱ф�ヨ仈绯讳汉琛�';
+
+-- =============================================
+-- 2. 浜轰簨瀹℃壒妯″潡琛�
+-- =============================================
+
+-- 2.1 璇峰亣鐢宠琛�
+CREATE TABLE IF NOT EXISTS `hrm_leave_application` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鐢宠ID',
+ `no` varchar(32) NOT NULL COMMENT '鐢宠鍗曞彿',
+ `user_id` bigint NOT NULL COMMENT '鐢宠浜篒D',
+ `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+ `leave_type` tinyint NOT NULL COMMENT '璇峰亣绫诲瀷锛�1-浜嬪亣锛�2-鐥呭亣锛�3-骞村亣锛�4-濠氬亣锛�5-浜у亣锛�6-涓у亣',
+ `start_time` datetime NOT NULL COMMENT '寮�濮嬫椂闂�',
+ `end_time` datetime NOT NULL COMMENT '缁撴潫鏃堕棿',
+ `duration` decimal(4,2) NOT NULL COMMENT '璇峰亣鏃堕暱锛堝ぉ锛�',
+ `reason` varchar(500) NOT NULL COMMENT '璇峰亣鍘熷洜',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
+ `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
+ `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
+ `file_url` varchar(500) DEFAULT NULL COMMENT '闄勪欢鍦板潃',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_user_status` (`user_id`, `status`),
+ KEY `idx_dept_time` (`dept_id`, `start_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM璇峰亣鐢宠琛�';
+
+-- 2.2 璋冨矖鐢宠琛�
+CREATE TABLE IF NOT EXISTS `hrm_transfer_application` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鐢宠ID',
+ `no` varchar(32) NOT NULL COMMENT '鐢宠鍗曞彿',
+ `user_id` bigint NOT NULL COMMENT '鐢宠浜篒D',
+ `current_dept_id` bigint NOT NULL COMMENT '鍘熼儴闂↖D',
+ `current_post_id` bigint NOT NULL COMMENT '鍘熷矖浣岻D',
+ `target_dept_id` bigint NOT NULL COMMENT '鐩爣閮ㄩ棬ID',
+ `target_post_id` bigint NOT NULL COMMENT '鐩爣宀椾綅ID',
+ `transfer_type` tinyint NOT NULL COMMENT '璋冨矖绫诲瀷锛�1-骞宠皟锛�2-鍗囪亴锛�3-闄嶈亴',
+ `effective_date` date NOT NULL COMMENT '鐢熸晥鏃ユ湡',
+ `reason` varchar(500) NOT NULL COMMENT '璋冨矖鍘熷洜',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
+ `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
+ `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
+ `file_url` varchar(500) DEFAULT NULL COMMENT '闄勪欢鍦板潃',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_user_status` (`user_id`, `status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM璋冨矖鐢宠琛�';
+
+-- 2.3 绂昏亴鐢宠琛�
+CREATE TABLE IF NOT EXISTS `hrm_resignation_application` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鐢宠ID',
+ `no` varchar(32) NOT NULL COMMENT '鐢宠鍗曞彿',
+ `user_id` bigint NOT NULL COMMENT '鐢宠浜篒D',
+ `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+ `resignation_type` tinyint NOT NULL COMMENT '绂昏亴绫诲瀷锛�1-涓诲姩绂昏亴锛�2-琚姩绂昏亴锛�3-鍚堝悓鍒版湡',
+ `expected_date` date NOT NULL COMMENT '棰勮绂昏亴鏃ユ湡',
+ `actual_date` date DEFAULT NULL COMMENT '瀹為檯绂昏亴鏃ユ湡',
+ `reason` varchar(500) NOT NULL COMMENT '绂昏亴鍘熷洜',
+ `handover_user_id` bigint DEFAULT NULL COMMENT '浜ゆ帴浜篒D',
+ `handover_status` tinyint DEFAULT 0 COMMENT '浜ゆ帴鐘舵�侊細0-鏈紑濮嬶紝10-杩涜涓紝20-宸插畬鎴�',
+ `handover_remark` varchar(500) DEFAULT NULL COMMENT '浜ゆ帴璇存槑',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
+ `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
+ `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
+ `file_url` varchar(500) DEFAULT NULL COMMENT '闄勪欢鍦板潃',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_user_status` (`user_id`, `status`),
+ KEY `idx_dept_time` (`dept_id`, `expected_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM绂昏亴鐢宠琛�';
+
+-- =============================================
+-- 3. 鑰冨嫟绠$悊妯″潡琛�
+-- =============================================
+
+-- 3.1 鑰冨嫟瑙勫垯閰嶇疆琛�
+CREATE TABLE IF NOT EXISTS `hrm_attendance_rule` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '瑙勫垯ID',
+ `name` varchar(100) NOT NULL COMMENT '瑙勫垯鍚嶇О',
+ `dept_id` bigint DEFAULT NULL COMMENT '閫傜敤閮ㄩ棬ID锛堢┖琛ㄧず鍏ㄥ眬锛�',
+ `work_start_time` time NOT NULL COMMENT '涓婄彮鏃堕棿',
+ `work_end_time` time NOT NULL COMMENT '涓嬬彮鏃堕棿',
+ `late_allow_minutes` tinyint DEFAULT 0 COMMENT '杩熷埌鍏佽鍒嗛挓鏁�',
+ `early_allow_minutes` tinyint DEFAULT 0 COMMENT '鏃╅��鍏佽鍒嗛挓鏁�',
+ `overtime_threshold` decimal(4,2) DEFAULT 1 COMMENT '鍔犵彮璧风畻鏃堕暱锛堝皬鏃讹級',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ KEY `idx_dept_id` (`dept_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鑰冨嫟瑙勫垯閰嶇疆琛�';
+
+-- 3.2 鑰冨嫟鎵撳崱璁板綍琛�
+CREATE TABLE IF NOT EXISTS `hrm_attendance_record` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '璁板綍ID',
+ `user_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+ `date` date NOT NULL COMMENT '鑰冨嫟鏃ユ湡',
+ `clock_in_time` datetime DEFAULT NULL COMMENT '涓婄彮鎵撳崱鏃堕棿',
+ `clock_out_time` datetime DEFAULT NULL COMMENT '涓嬬彮鎵撳崱鏃堕棿',
+ `clock_in_type` tinyint DEFAULT NULL COMMENT '涓婄彮鎵撳崱绫诲瀷锛�1-姝e父锛�2-杩熷埌锛�3-缂哄崱',
+ `clock_out_type` tinyint DEFAULT NULL COMMENT '涓嬬彮鎵撳崱绫诲瀷锛�1-姝e父锛�2-鏃╅��锛�3-缂哄崱',
+ `work_hours` decimal(4,2) DEFAULT NULL COMMENT '宸ヤ綔鏃堕暱锛堝皬鏃讹級',
+ `overtime_hours` decimal(4,2) DEFAULT 0 COMMENT '鍔犵彮鏃堕暱锛堝皬鏃讹級',
+ `location` varchar(200) DEFAULT NULL COMMENT '鎵撳崱鍦扮偣',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `data_source` tinyint DEFAULT 1 COMMENT '鏁版嵁鏉ユ簮锛�1-鎵撳崱锛�2-瀵煎叆',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_user_date` (`user_id`, `date`),
+ KEY `idx_dept_date` (`dept_id`, `date`),
+ KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鑰冨嫟鎵撳崱璁板綍琛�';
+
+-- 3.3 鑰冨嫟寮傚父鐢宠琛�
+CREATE TABLE IF NOT EXISTS `hrm_attendance_exception` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鐢宠ID',
+ `no` varchar(32) NOT NULL COMMENT '鐢宠鍗曞彿',
+ `user_id` bigint NOT NULL COMMENT '鐢宠浜篒D',
+ `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+ `type` tinyint NOT NULL COMMENT '寮傚父绫诲瀷锛�1-琛ュ崱锛�2-鍔犵彮锛�3-鍑哄樊',
+ `exception_date` date NOT NULL COMMENT '寮傚父鏃ユ湡',
+ `exception_time` datetime DEFAULT NULL COMMENT '寮傚父鏃堕棿鐐�',
+ `reason` varchar(500) NOT NULL COMMENT '鐢宠鍘熷洜',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
+ `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
+ `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
+ `file_url` varchar(500) DEFAULT NULL COMMENT '闄勪欢鍦板潃',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_user_status` (`user_id`, `status`),
+ KEY `idx_dept_date` (`dept_id`, `exception_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鑰冨嫟寮傚父鐢宠琛�';
+
+-- =============================================
+-- 3. 钖叕绠$悊妯″潡琛�
+-- =============================================
+
+-- 3.1 钖叕缁撴瀯琛�
+CREATE TABLE IF NOT EXISTS `hrm_salary_structure` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缁撴瀯ID',
+ `name` varchar(100) NOT NULL COMMENT '缁撴瀯鍚嶇О',
+ `base_salary` decimal(12,2) NOT NULL COMMENT '鍩烘湰宸ヨ祫',
+ `performance_ratio` decimal(4,2) DEFAULT 0 COMMENT '缁╂晥宸ヨ祫鍗犳瘮锛�%锛�',
+ `overtime_pay_ratio` decimal(4,2) DEFAULT 1.5 COMMENT '鍔犵彮宸ヨ祫鍊嶇巼',
+ `social_security_base` decimal(12,2) DEFAULT NULL COMMENT '绀句繚鍩烘暟',
+ `housing_fund_base` decimal(12,2) DEFAULT NULL COMMENT '鍏Н閲戝熀鏁�',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM钖叕缁撴瀯琛�';
+
+-- 3.2 鍛樺伐钖叕妗f琛�
+CREATE TABLE IF NOT EXISTS `hrm_employee_salary` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '妗fID',
+ `user_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `salary_structure_id` bigint NOT NULL COMMENT '钖叕缁撴瀯ID',
+ `base_salary` decimal(12,2) NOT NULL COMMENT '鍩烘湰宸ヨ祫',
+ `performance_salary` decimal(12,2) DEFAULT NULL COMMENT '缁╂晥宸ヨ祫',
+ `meal_allowance` decimal(12,2) DEFAULT 0 COMMENT '椁愯ˉ',
+ `transport_allowance` decimal(12,2) DEFAULT 0 COMMENT '浜ら�氳ˉ璐�',
+ `other_allowance` decimal(12,2) DEFAULT 0 COMMENT '鍏朵粬琛ヨ创',
+ `effective_date` date NOT NULL COMMENT '鐢熸晥鏃ユ湡',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_user_effective` (`user_id`, `effective_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍛樺伐钖叕妗f琛�';
+
+-- 3.3 钖叕鏍哥畻璁板綍琛�
+CREATE TABLE IF NOT EXISTS `hrm_salary_calculation` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鏍哥畻ID',
+ `no` varchar(32) NOT NULL COMMENT '鏍哥畻鍗曞彿',
+ `period` varchar(20) NOT NULL COMMENT '鏍哥畻鍛ㄦ湡锛堝2024-01锛�',
+ `user_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+ `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+ `base_salary` decimal(12,2) NOT NULL COMMENT '鍩烘湰宸ヨ祫',
+ `performance_salary` decimal(12,2) DEFAULT NULL COMMENT '缁╂晥宸ヨ祫',
+ `overtime_pay` decimal(12,2) DEFAULT 0 COMMENT '鍔犵彮宸ヨ祫',
+ `meal_allowance` decimal(12,2) DEFAULT 0 COMMENT '椁愯ˉ',
+ `transport_allowance` decimal(12,2) DEFAULT 0 COMMENT '浜ら�氳ˉ璐�',
+ `other_allowance` decimal(12,2) DEFAULT 0 COMMENT '鍏朵粬琛ヨ创',
+ `social_security_deduction` decimal(12,2) DEFAULT NULL COMMENT '绀句繚鎵i櫎',
+ `housing_fund_deduction` decimal(12,2) DEFAULT NULL COMMENT '鍏Н閲戞墸闄�',
+ `tax_deduction` decimal(12,2) DEFAULT NULL COMMENT '涓◣鎵i櫎',
+ `other_deduction` decimal(12,2) DEFAULT 0 COMMENT '鍏朵粬鎵i櫎',
+ `actual_salary` decimal(12,2) NOT NULL COMMENT '瀹炲彂宸ヨ祫',
+ `work_days` tinyint DEFAULT NULL COMMENT '鍑哄嫟澶╂暟',
+ `overtime_hours` decimal(4,2) DEFAULT NULL COMMENT '鍔犵彮鏃堕暱',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-寰呯‘璁わ紝10-宸茬‘璁わ紝20-宸插彂鏀�',
+ `confirm_time` datetime DEFAULT NULL COMMENT '纭鏃堕棿',
+ `confirm_user_id` bigint DEFAULT NULL COMMENT '纭浜篒D',
+ `payment_time` datetime DEFAULT NULL COMMENT '鍙戞斁鏃堕棿',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_user_period` (`user_id`, `period`),
+ KEY `idx_dept_period` (`dept_id`, `period`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM钖叕鏍哥畻璁板綍琛�';
+
+-- 3.4 钖祫鍙戞斁鍙拌处琛�
+CREATE TABLE IF NOT EXISTS `hrm_salary_payment` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鍙戞斁ID',
+ `no` varchar(32) NOT NULL COMMENT '鍙戞斁鍗曞彿',
+ `period` varchar(20) NOT NULL COMMENT '鍙戞斁鍛ㄦ湡',
+ `total_amount` decimal(14,2) NOT NULL COMMENT '鍙戞斁鎬婚噾棰�',
+ `total_count` int NOT NULL COMMENT '鍙戞斁浜烘暟',
+ `payment_method` tinyint NOT NULL COMMENT '鍙戞斁鏂瑰紡锛�1-閾惰杞处锛�2-鐜伴噾',
+ `payment_time` datetime NOT NULL COMMENT '鍙戞斁鏃堕棿',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-寰呭彂鏀撅紝10-宸插彂鏀�',
+ `file_url` varchar(500) DEFAULT NULL COMMENT '閾惰鍥炲崟闄勪欢',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_period` (`period`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM钖祫鍙戞斁鍙拌处琛�';
+
+-- =============================================
+-- 4. 鑿滃崟鏉冮檺
+-- =============================================
+
+-- 4.1 HRM绠$悊鐩綍
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6001, '浜轰簨绠$悊', '', 1, 50, 0, '/hrm', 'ep:user', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.2 鍛樺伐绠$悊鐩綍
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6006, '鍛樺伐绠$悊', '', 2, 0, 6001, 'employee', 'ep:avatar', 'hrm/employee/index', 'HrmEmployee', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60061, '鍛樺伐鏌ヨ', 'hrm:employee:query', 3, 1, 6006, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60062, '鍛樺伐鏂板', 'hrm:employee:create', 3, 2, 6006, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60063, '鍛樺伐淇敼', 'hrm:employee:update', 3, 3, 6006, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60064, '鍛樺伐鍒犻櫎', 'hrm:employee:delete', 3, 4, 6006, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60065, '鍛樺伐瀵煎嚭', 'hrm:employee:export', 3, 5, 6006, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.3 浜轰簨瀹℃壒鐩綍
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6002, '浜轰簨瀹℃壒', '', 1, 1, 6001, 'approval', 'ep:document-checked', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.4 璇峰亣鐢宠鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6003, '璇峰亣鐢宠', '', 2, 1, 6002, 'leave', 'ep:calendar', 'hrm/approval/leave/index', 'HrmLeaveApplication', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60031, '璇峰亣鐢宠鏌ヨ', 'hrm:leave:query', 3, 1, 6003, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60032, '璇峰亣鐢宠鏂板', 'hrm:leave:create', 3, 2, 6003, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60033, '璇峰亣鐢宠淇敼', 'hrm:leave:update', 3, 3, 6003, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60034, '璇峰亣鐢宠鍒犻櫎', 'hrm:leave:delete', 3, 4, 6003, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60035, '璇峰亣鐢宠鎻愪氦', 'hrm:leave:submit', 3, 5, 6003, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.5 璋冨矖鐢宠鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6004, '璋冨矖鐢宠', '', 2, 2, 6002, 'transfer', 'ep:sort', 'hrm/approval/transfer/index', 'HrmTransferApplication', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60041, '璋冨矖鐢宠鏌ヨ', 'hrm:transfer:query', 3, 1, 6004, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60042, '璋冨矖鐢宠鏂板', 'hrm:transfer:create', 3, 2, 6004, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60043, '璋冨矖鐢宠淇敼', 'hrm:transfer:update', 3, 3, 6004, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60044, '璋冨矖鐢宠鍒犻櫎', 'hrm:transfer:delete', 3, 4, 6004, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60045, '璋冨矖鐢宠鎻愪氦', 'hrm:transfer:submit', 3, 5, 6004, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.6 绂昏亴鐢宠鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6005, '绂昏亴鐢宠', '', 2, 3, 6002, 'resignation', 'ep:switch-button', 'hrm/approval/resignation/index', 'HrmResignationApplication', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60051, '绂昏亴鐢宠鏌ヨ', 'hrm:resignation:query', 3, 1, 6005, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60052, '绂昏亴鐢宠鏂板', 'hrm:resignation:create', 3, 2, 6005, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60053, '绂昏亴鐢宠淇敼', 'hrm:resignation:update', 3, 3, 6005, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60054, '绂昏亴鐢宠鍒犻櫎', 'hrm:resignation:delete', 3, 4, 6005, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60055, '绂昏亴鐢宠鎻愪氦', 'hrm:resignation:submit', 3, 5, 6005, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.6 鑰冨嫟绠$悊鐩綍
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6010, '鑰冨嫟绠$悊', '', 1, 2, 6001, 'attendance', 'ep:clock', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.7 鑰冨嫟璁板綍鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6011, '鑰冨嫟璁板綍', '', 2, 1, 6010, 'record', 'ep:document', 'hrm/attendance/record/index', 'HrmAttendanceRecord', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60111, '鑰冨嫟璁板綍鏌ヨ', 'hrm:attendance:query', 3, 1, 6011, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60112, '鑰冨嫟璁板綍瀵煎叆', 'hrm:attendance:import', 3, 2, 6011, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60113, '鑰冨嫟璁板綍瀵煎嚭', 'hrm:attendance:export', 3, 3, 6011, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.8 鑰冨嫟寮傚父鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6012, '鑰冨嫟寮傚父', '', 2, 2, 6010, 'exception', 'ep:warning', 'hrm/attendance/exception/index', 'HrmAttendanceException', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.9 钖叕绠$悊鐩綍
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6020, '钖叕绠$悊', '', 1, 3, 6001, 'salary', 'ep:money', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.10 钖叕鏍哥畻鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6021, '钖叕鏍哥畻', '', 2, 1, 6020, 'calculation', 'ep:coin', 'hrm/salary/calculation/index', 'HrmSalaryCalculation', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60211, '钖叕鏍哥畻鏌ヨ', 'hrm:salary:query', 3, 1, 6021, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60212, '钖叕鏍哥畻鎵ц', 'hrm:salary:calculate', 3, 2, 6021, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60213, '钖叕鏍哥畻纭', 'hrm:salary:confirm', 3, 3, 6021, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (60214, '钖叕鏍哥畻瀵煎嚭', 'hrm:salary:export', 3, 4, 6021, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 4.11 钖祫鍙戞斁鑿滃崟
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (6022, '钖祫鍙戞斁', '', 2, 2, 6020, 'payment', 'ep:wallet', 'hrm/salary/payment/index', 'HrmSalaryPayment', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- =============================================
+-- 5. 瑙掕壊鑿滃崟鍏宠仈
+-- =============================================
+
+-- 缁欒鑹� ID=1锛堣秴绾х鐞嗗憳锛夊垎閰� HRM 鑿滃崟鏉冮檺
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6001, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6006, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60061, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60062, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60063, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60064, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60065, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6002, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6003, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60031, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60032, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60033, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60034, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60035, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6004, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60041, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60042, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60043, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60044, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60045, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6005, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60051, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60052, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60053, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60054, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60055, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6010, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6011, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60111, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60112, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60113, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6012, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6020, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6021, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60211, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60212, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60213, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 60214, '1', NOW(), '1', NOW(), b'0');
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1, 6022, '1', NOW(), '1', NOW(), b'0');
+
+-- =============================================
+-- 鎵ц瀹屾垚鎻愮ず
+-- =============================================
+SELECT 'HRM 浜哄姏璧勬簮妯″潡 SQL 鍒濆鍖栧畬鎴愶紒' AS result;
\ No newline at end of file
diff --git a/sql/mysql/hrm_salary_payment_detail_init.sql b/sql/mysql/hrm_salary_payment_detail_init.sql
new file mode 100644
index 0000000..34dff45
--- /dev/null
+++ b/sql/mysql/hrm_salary_payment_detail_init.sql
@@ -0,0 +1,19 @@
+-- 钖祫鍙戞斁鏄庣粏琛�
+CREATE TABLE IF NOT EXISTS hrm_salary_payment_detail (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭',
+ payment_id BIGINT NOT NULL COMMENT '鍙戞斁鍙拌处ID',
+ calculation_id BIGINT NOT NULL COMMENT '钖叕鏍哥畻ID',
+ user_id BIGINT NOT NULL COMMENT '鍛樺伐ID',
+ dept_id BIGINT DEFAULT NULL COMMENT '閮ㄩ棬ID',
+ actual_salary DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '瀹炲彂宸ヨ祫',
+ status INT NOT NULL DEFAULT 0 COMMENT '鍙戞斁鐘舵�侊細0-寰呭彂鏀撅紝10-宸插彂鏀�',
+ payment_time DATETIME DEFAULT NULL COMMENT '鍙戞斁鏃堕棿',
+ remark VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
+ create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ creator VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓浜�',
+ updater VARCHAR(64) DEFAULT '' COMMENT '鏇存柊浜�',
+ deleted BIT NOT NULL DEFAULT 0 COMMENT '鏄惁鍒犻櫎',
+ INDEX idx_payment_id (payment_id),
+ INDEX idx_calculation_id (calculation_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '钖祫鍙戞斁鏄庣粏';
diff --git a/sql/mysql/mes_wm_arrival_notice_purchase_order.sql b/sql/mysql/mes_wm_arrival_notice_purchase_order.sql
new file mode 100644
index 0000000..d66e8fe
--- /dev/null
+++ b/sql/mysql/mes_wm_arrival_notice_purchase_order.sql
@@ -0,0 +1,11 @@
+-- MES 鍒拌揣閫氱煡鍗曞鍔犻噰璐鍗曠紪鍙峰瓧娈�
+-- 灏嗛噰璐鍗曞彿浠庢墜鍔ㄨ緭鍏ユ敼涓哄叧鑱� ERP 閲囪喘璁㈠崟
+
+-- 娣诲姞閲囪喘璁㈠崟缂栧彿瀛楁
+ALTER TABLE mes_wm_arrival_notice ADD COLUMN purchase_order_id BIGINT COMMENT '閲囪喘璁㈠崟缂栧彿' AFTER name;
+
+-- 鏁版嵁杩佺Щ锛氬皢鍘熸湁鐨勯噰璐鍗曞彿锛坧urchase_order_code锛変笌 ERP 閲囪喘璁㈠崟琛紙erp_purchase_order锛夊叧鑱�
+-- 娉ㄦ剰锛氭杩佺Щ鑴氭湰鍋囪 purchase_order_code 瀛樺偍鐨勬槸 ERP 閲囪喘璁㈠崟鐨� no 瀛楁
+-- UPDATE mes_wm_arrival_notice a
+-- SET a.purchase_order_id = (SELECT p.id FROM erp_purchase_order p WHERE p.no = a.purchase_order_code LIMIT 1)
+-- WHERE a.purchase_order_code IS NOT NULL AND a.purchase_order_code != '';
\ No newline at end of file
diff --git a/sql/mysql/mes_wm_sn_fix.sql b/sql/mysql/mes_wm_sn_fix.sql
new file mode 100644
index 0000000..3e1aee7
--- /dev/null
+++ b/sql/mysql/mes_wm_sn_fix.sql
@@ -0,0 +1,21 @@
+-- MES SN鐮佽〃缁撴瀯淇锛圡ySQL 5.7 鍏煎鐗堬級
+-- 鐩存帴鎵ц浠ヤ笅璇彞锛屽鏋滃瓧娈靛凡瀛樺湪浼氭姤閿欙紝蹇界暐鍗冲彲
+
+-- 1. 娣诲姞 uuid 瀛楁
+ALTER TABLE mes_wm_sn ADD COLUMN uuid VARCHAR(64) COMMENT '鎵规UUID' AFTER id;
+
+-- 2. 娣诲姞 batch_code 瀛楁
+ALTER TABLE mes_wm_sn ADD COLUMN batch_code VARCHAR(100) COMMENT '鎵规鍙�' AFTER item_id;
+
+-- 3. 娣诲姞 work_order_id 瀛楁
+ALTER TABLE mes_wm_sn ADD COLUMN work_order_id BIGINT COMMENT '鐢熶骇宸ュ崟缂栧彿' AFTER batch_code;
+
+-- 4. 娣诲姞绱㈠紩
+ALTER TABLE mes_wm_sn ADD INDEX idx_uuid (uuid);
+ALTER TABLE mes_wm_sn ADD INDEX idx_item_id (item_id);
+
+-- 5. 楠岃瘉
+SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT
+FROM information_schema.columns
+WHERE table_schema = DATABASE() AND table_name = 'mes_wm_sn'
+ORDER BY ORDINAL_POSITION;
\ No newline at end of file
diff --git a/sql/mysql/report_module_init.sql b/sql/mysql/report_module_init.sql
new file mode 100644
index 0000000..f966a9e
--- /dev/null
+++ b/sql/mysql/report_module_init.sql
@@ -0,0 +1,70 @@
+-- =============================================
+-- 鎶ヨ〃妯″潡 SQL 鍒濆鍖栬剼鏈�
+-- 鍖呭惈 GoView 澶у睆璁捐鍣ㄧ浉鍏宠〃
+-- =============================================
+
+-- =============================================
+-- 1. 鏁版嵁搴撹〃缁撴瀯
+-- =============================================
+
+-- 1.1 GoView 椤圭洰琛紙澶у睆璁捐鍣級
+CREATE TABLE IF NOT EXISTS `report_go_view_project` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `name` varchar(100) NOT NULL COMMENT '椤圭洰鍚嶇О',
+ `pic_url` varchar(255) DEFAULT NULL COMMENT '棰勮鍥剧墖 URL',
+ `content` longtext COMMENT '鎶ヨ〃鍐呭锛圝SON 閰嶇疆锛�',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鍙戝竷鐘舵�侊細0-宸插彂甯冿紝1-鏈彂甯�',
+ `remark` varchar(500) DEFAULT NULL COMMENT '椤圭洰澶囨敞',
+ `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='GoView 椤圭洰琛�';
+
+-- =============================================
+-- 2. 鑿滃崟鏉冮檺锛堝鏋滀富 SQL 涓凡鍖呭惈锛屽彲璺宠繃锛�
+-- =============================================
+
+-- 2.1 鎶ヨ〃绠$悊鐩綍
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1281, '鎶ヨ〃绠$悊', '', 1, 40, 0, '/report', 'ep:pie-chart', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 2.2 鎶ヨ〃璁捐鍣紙JimuReport 绉湪鎶ヨ〃锛�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (1282, '鎶ヨ〃璁捐鍣�', '', 2, 1, 1281, 'jimu-report', 'ep:trend-charts', 'report/jmreport/index', 'JimuReport', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 2.3 澶у睆璁捐鍣紙GoView锛�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2153, '澶у睆璁捐鍣�', '', 2, 2, 1281, 'go-view', 'fa:area-chart', 'report/goview/index', 'GoView', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 2.4 浠〃鐩樿璁″櫒锛圝imuBI锛�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5009, '浠〃鐩樿璁″櫒', '', 2, 3, 1281, 'jimu-bi', 'fa:y-combinator', 'report/jmreport/bi', 'JimuBI', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- =============================================
+-- 3. 瑙掕壊鑿滃崟鍏宠仈锛堢粰绠$悊鍛樿鑹插垎閰嶆姤琛ㄦ潈闄愶級
+-- =============================================
+
+-- 娉ㄦ剰锛氬鏋滀富 SQL 涓凡鏈夎繖浜涜褰曪紝鍙兘浼氬啿绐侊紝鍙厛鍒犻櫎鍐嶆彃鍏�
+-- DELETE FROM `system_role_menu` WHERE `menu_id` IN (1281, 1282, 2153, 5009);
+
+-- 3.1 缁欒鑹� ID=2锛堝亣璁句负绠$悊鍛橈級鍒嗛厤鎶ヨ〃鑿滃崟鏉冮檺
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, 1281, '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, 1282, '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, 2153, '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (2, 5009, '1', NOW(), '1', NOW(), b'0');
+
+-- =============================================
+-- 鎵ц瀹屾垚鎻愮ず
+-- =============================================
+SELECT '鎶ヨ〃妯″潡 SQL 鍒濆鍖栧畬鎴愶紒' AS result;
+SELECT COUNT(*) AS go_view_project_count FROM report_go_view_project WHERE deleted = 0;
\ No newline at end of file
diff --git a/sql/mysql/wls_database_init.sql b/sql/mysql/wls_database_init.sql
new file mode 100644
index 0000000..656a7f5
--- /dev/null
+++ b/sql/mysql/wls_database_init.sql
@@ -0,0 +1,76 @@
+-- =============================================
+-- 浠撳偍鐗╂祦妯″潡鏁版嵁搴撳垵濮嬪寲鑴氭湰
+-- 鍖呭惈锛氫粨搴撳鎵归厤缃〃銆佸崟鎹瓸PM瀛楁鎵╁睍銆丅PM鍒嗙被
+-- =============================================
+
+-- 1. 鍒涘缓浠撳簱瀹℃壒閰嶇疆琛�
+CREATE TABLE IF NOT EXISTS wms_warehouse_approval_config (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭ID',
+ warehouse_id BIGINT NOT NULL COMMENT '浠撳簱ID',
+ biz_type VARCHAR(32) NOT NULL COMMENT '涓氬姟绫诲瀷(INBOUND-鍏ュ簱/OUTBOUND-鍑哄簱/TRANSFER-璋冩嫧/CHECK-鐩樼偣)',
+ approval_enabled TINYINT(1) DEFAULT 0 COMMENT '鏄惁鍚敤瀹℃壒(0-鍚� 1-鏄�)',
+ process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key(涓虹┖鍒欎娇鐢ㄥ垎绫婚粯璁�)',
+ category_code VARCHAR(64) DEFAULT 'wms_approve' COMMENT 'BPM鍒嗙被缂栫爜',
+ creator VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ updater VARCHAR(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ deleted BIT DEFAULT 0 COMMENT '鏄惁鍒犻櫎',
+ INDEX idx_warehouse_biz (warehouse_id, biz_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠撳簱瀹℃壒閰嶇疆琛�';
+
+-- 2. 涓哄嚭鍏ュ簱鍗曟嵁澧炲姞BPM瀹℃壒瀛楁
+
+-- 閲囪喘鍏ュ簱鍗�
+ALTER TABLE mes_wm_item_receipt
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 閿�鍞嚭搴撳崟
+ALTER TABLE mes_wm_product_sales
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 鐢熶骇棰嗘枡鍗�
+ALTER TABLE mes_wm_product_issue
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 鐢熶骇鍏ュ簱鍗曪紙浜у搧鏀惰揣锛�
+ALTER TABLE mes_wm_product_receipt
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 杞Щ璋冩嫧鍗�
+ALTER TABLE mes_wm_transfer
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 鐩樼偣鏂规
+ALTER TABLE mes_wm_stock_taking_plan
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 鏉傞」鍏ュ簱鍗�
+ALTER TABLE mes_wm_misc_receipt
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 鏉傞」鍑哄簱鍗�
+ALTER TABLE mes_wm_misc_issue
+ ADD COLUMN IF NOT EXISTS process_instance_id VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹炰緥ID' AFTER remark,
+ ADD COLUMN IF NOT EXISTS process_definition_key VARCHAR(64) DEFAULT NULL COMMENT '娴佺▼瀹氫箟Key' AFTER process_instance_id;
+
+-- 3. 鍒涘缓BPM瀹℃壒鍒嗙被
+INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
+SELECT 120, '浠撳簱瀹℃牳', 'wms_approve', '浠撳簱绠$悊瀹℃壒娴佺▼鍒嗙被', 0, 30, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM bpm_category WHERE code = 'wms_approve');
+
+-- =============================================
+-- 涓氬姟绫诲瀷璇存槑锛�
+-- INBOUND - 鍏ュ簱绫讳笟鍔★紙閲囪喘鍏ュ簱銆佺敓浜у叆搴撱�侀攢鍞��璐х瓑锛�
+-- OUTBOUND - 鍑哄簱绫讳笟鍔★紙閿�鍞嚭搴撱�佺敓浜ч鏂欍�侀噰璐��璐х瓑锛�
+-- TRANSFER - 璋冩嫧涓氬姟锛堝唴閮ㄨ皟鎷ㄣ�佸閮ㄨ皟鎷級
+-- CHECK - 鐩樼偣涓氬姟锛堥潤鎬佺洏鐐广�佸姩鎬佺洏鐐癸級
+-- =============================================
\ No newline at end of file
diff --git a/sql/mysql/wls_menu_init.sql b/sql/mysql/wls_menu_init.sql
new file mode 100644
index 0000000..751744e
--- /dev/null
+++ b/sql/mysql/wls_menu_init.sql
@@ -0,0 +1,35 @@
+-- =============================================
+-- 浠撳偍鐗╂祦鑿滃崟璋冩暣鑴氭湰
+-- 灏哅ES浠撳簱绠$悊(5780)鎻愬彇涓轰竴绾ц彍鍗�"浠撳偍鐗╂祦"
+-- =============================================
+
+-- 1. 灏嗕粨搴撶鐞嗚彍鍗曟彁鍙栦负涓�绾ц彍鍗�"浠撳偍鐗╂祦"
+UPDATE system_menu SET
+ parent_id = 0,
+ name = '浠撳偍鐗╂祦',
+ path = 'wls',
+ icon = 'ep:truck',
+ sort = 280 -- 鏀惧湪ERP(sort=250)涔嬪悗
+WHERE id = 5780;
+
+-- 2. 璋冩暣瀛愯彍鍗曠粍浠惰矾寰勫墠缂�(wm -> wls)
+-- 娉ㄦ剰锛氬墠绔渶瑕佸悓姝ヨ皟鏁寸粍浠惰矾寰勶紝鎴栧湪璺敱閰嶇疆涓坊鍔犲埆鍚�
+UPDATE system_menu SET component = REPLACE(component, 'mes/wm/', 'wls/')
+WHERE parent_id = 5780;
+
+-- 3. 璋冩暣瀛愯彍鍗曠殑瀛愯彍鍗曪紙濡傜洏鐐逛换鍔$瓑涓夌骇鑿滃崟锛�
+UPDATE system_menu SET component = REPLACE(component, 'mes/wm/', 'wls/')
+WHERE parent_id IN (SELECT id FROM system_menu WHERE parent_id = 5780);
+
+-- =============================================
+-- 璇存槑锛�
+-- 1. 鎵ц姝よ剼鏈悗锛屽墠绔粍浠惰矾寰勯渶瑕佸悓姝ヨ皟鏁�
+-- 鎴栧湪 router 涓厤缃矾寰勫埆鍚嶏細mes/wm/* -> wls/*
+-- 2. 鏉冮檺鍓嶇紑鏆備笉淇敼锛屼繚鎸� mes:wm-* 鏍煎紡
+-- 鍚庣画鍙牴鎹渶瑕佺粺涓�璋冩暣涓� wls:*
+-- 3. 濡傞渶鎭㈠锛屾墽琛屼互涓嬪弽鍚戣剼鏈細
+-- =============================================
+
+-- 鍙嶅悜鑴氭湰锛堝闇�鎭㈠锛夛細
+-- UPDATE system_menu SET parent_id = 5100, name = '浠撳簱绠$悊', path = 'wm', icon = 'ep:box', sort = 20 WHERE id = 5780;
+-- UPDATE system_menu SET component = REPLACE(component, 'wls/', 'mes/wm/') WHERE parent_id = 5780 OR parent_id IN (SELECT id FROM system_menu WHERE parent_id = 5780);
\ No newline at end of file
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java
new file mode 100644
index 0000000..a198dd4
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.erp.api.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
+
+import java.util.List;
+
+/**
+ * ERP 閲囪喘璁㈠崟 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface ErpPurchaseOrderApi {
+
+ String PREFIX = "/rpc-api/erp/purchase-order";
+
+ /**
+ * 鑾峰彇閲囪喘璁㈠崟
+ *
+ * @param id 璁㈠崟缂栧彿
+ * @return 閲囪喘璁㈠崟
+ */
+ CommonResult<ErpPurchaseOrderRespDTO> getPurchaseOrder(Long id);
+
+ /**
+ * 鏍规嵁渚涘簲鍟嗙紪鍙疯幏鍙栭噰璐鍗曞垪琛紙宸插鏍哥姸鎬侊級
+ *
+ * @param supplierId 渚涘簲鍟嗙紪鍙�
+ * @return 閲囪喘璁㈠崟鍒楄〃
+ */
+ CommonResult<List<ErpPurchaseOrderRespDTO>> getPurchaseOrderListBySupplierId(Long supplierId);
+
+ /**
+ * 鏍规嵁缂栧彿鍒楄〃鑾峰彇閲囪喘璁㈠崟鍒楄〃
+ *
+ * @param ids 缂栧彿鍒楄〃
+ * @return 閲囪喘璁㈠崟鍒楄〃
+ */
+ CommonResult<List<ErpPurchaseOrderRespDTO>> getPurchaseOrderList(List<Long> ids);
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderRespDTO.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderRespDTO.java
new file mode 100644
index 0000000..12fdbf1
--- /dev/null
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderRespDTO.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.erp.api.purchase.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "RPC 鏈嶅姟 - ERP 閲囪喘璁㈠崟 Response DTO")
+@Data
+public class ErpPurchaseOrderRespDTO {
+
+ @Schema(description = "璁㈠崟缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "閲囪喘璁㈠崟鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "PO001")
+ private String no;
+
+ @Schema(description = "渚涘簲鍟嗙紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long supplierId;
+
+ @Schema(description = "璁㈠崟鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "涓嬪崟鏃堕棿")
+ private LocalDateTime orderTime;
+
+ @Schema(description = "鍚堣鏁伴噺")
+ private BigDecimal totalCount;
+
+ @Schema(description = "鍚堣閲戦锛屽崟浣嶏細鍏�", example = "1000")
+ private BigDecimal totalPrice;
+
+ @Schema(description = "宸插叆搴撴暟閲�")
+ private BigDecimal inCount;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/pom.xml b/yudao-module-erp/pom.xml
index ec7a802..475b7ee 100644
--- a/yudao-module-erp/pom.xml
+++ b/yudao-module-erp/pom.xml
@@ -52,6 +52,13 @@
<version>${revision}</version>
</dependency>
+ <!-- WMS 浠撳偍鐗╂祦 API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-wms-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<!-- Web 鐩稿叧 -->
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
new file mode 100644
index 0000000..778f021
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.erp.api.purchase;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
+import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * ERP 閲囪喘璁㈠崟 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(ErpPurchaseOrderApi.PREFIX)
+@Validated
+public class ErpPurchaseOrderApiImpl implements ErpPurchaseOrderApi {
+
+ @Resource
+ private ErpPurchaseOrderService purchaseOrderService;
+
+ @Override
+ public CommonResult<ErpPurchaseOrderRespDTO> getPurchaseOrder(Long id) {
+ ErpPurchaseOrderDO order = purchaseOrderService.getPurchaseOrder(id);
+ return success(BeanUtils.toBean(order, ErpPurchaseOrderRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<ErpPurchaseOrderRespDTO>> getPurchaseOrderListBySupplierId(Long supplierId) {
+ if (supplierId == null) {
+ return success(Collections.emptyList());
+ }
+ // 鏌ヨ宸插鏍哥姸鎬佺殑閲囪喘璁㈠崟
+ List<ErpPurchaseOrderDO> orders = purchaseOrderService.getPurchaseOrderListBySupplierIdAndStatus(
+ supplierId, ErpAuditStatus.APPROVE.getStatus());
+ return success(BeanUtils.toBean(orders, ErpPurchaseOrderRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<ErpPurchaseOrderRespDTO>> getPurchaseOrderList(List<Long> ids) {
+ if (ids == null || ids.isEmpty()) {
+ return success(Collections.emptyList());
+ }
+ List<ErpPurchaseOrderDO> orders = purchaseOrderService.getPurchaseOrderList(ids);
+ return success(BeanUtils.toBean(orders, ErpPurchaseOrderRespDTO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/ErpMesWarehouseMappingController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/ErpMesWarehouseMappingController.java
new file mode 100644
index 0000000..961568f
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/ErpMesWarehouseMappingController.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.erp.controller.admin.mapping;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingCreateReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingUpdateReqVO;
+import cn.iocoder.yudao.module.erp.convert.mapping.ErpMesWarehouseMappingConvert;
+import cn.iocoder.yudao.module.erp.dal.dataobject.mapping.ErpMesWarehouseMappingDO;
+import cn.iocoder.yudao.module.erp.service.product.mapping.ErpMesWarehouseMappingService;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠 Controller
+ */
+@Tag(name = "绠$悊鍚庡彴 - ERP浠撳簱涓嶮ES浠撳簱鏄犲皠")
+@RestController
+@RequestMapping("/erp/warehouse-mapping")
+@Validated
+public class ErpMesWarehouseMappingController {
+
+ @Resource
+ private ErpMesWarehouseMappingService mappingService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓浠撳簱鏄犲皠")
+ @PreAuthorize("@ss.hasPermission('erp:warehouse:update')")
+ public CommonResult<Long> createMapping(@Valid @RequestBody ErpMesWarehouseMappingCreateReqVO createReqVO) {
+ return success(mappingService.createMapping(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊浠撳簱鏄犲皠")
+ @PreAuthorize("@ss.hasPermission('erp:warehouse:update')")
+ public CommonResult<Boolean> updateMapping(@Valid @RequestBody ErpMesWarehouseMappingUpdateReqVO updateReqVO) {
+ mappingService.updateMapping(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎浠撳簱鏄犲皠")
+ @Parameter(name = "id", description = "缂栧彿", required = true)
+ @PreAuthorize("@ss.hasPermission('erp:warehouse:delete')")
+ public CommonResult<Boolean> deleteMapping(@RequestParam("id") Long id) {
+ mappingService.deleteMapping(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰彇浠撳簱鏄犲皠")
+ @Parameter(name = "id", description = "缂栧彿", required = true)
+ @PreAuthorize("@ss.hasPermission('erp:warehouse:query')")
+ public CommonResult<ErpMesWarehouseMappingDO> getMapping(@RequestParam("id") Long id) {
+ return success(mappingService.getMapping(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰彇浠撳簱鏄犲皠鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('erp:warehouse:query')")
+ public CommonResult<PageResult<ErpMesWarehouseMappingDO>> getMappingPage(@Valid ErpMesWarehouseMappingPageReqVO pageReqVO) {
+ return success(mappingService.getMappingPage(pageReqVO));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingCreateReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingCreateReqVO.java
new file mode 100644
index 0000000..97dfb3e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingCreateReqVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.erp.controller.admin.mapping.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠鍒涘缓 Request VO
+ */
+@Schema(description = "绠$悊鍚庡彴 - ERP浠撳簱涓嶮ES浠撳簱鏄犲皠鍒涘缓 Request VO")
+@Data
+public class ErpMesWarehouseMappingCreateReqVO {
+
+ @Schema(description = "ERP浠撳簱ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "ERP浠撳簱ID涓嶈兘涓虹┖")
+ private Long erpWarehouseId;
+
+ @Schema(description = "ERP浠撳簱鍚嶇О")
+ private String erpWarehouseName;
+
+ @Schema(description = "MES浠撳簱ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "MES浠撳簱ID涓嶈兘涓虹┖")
+ private Long mesWarehouseId;
+
+ @Schema(description = "MES浠撳簱鍚嶇О")
+ private String mesWarehouseName;
+
+ @Schema(description = "鐘舵�侊紙1-鍚敤 0-绂佺敤锛�")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingPageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingPageReqVO.java
new file mode 100644
index 0000000..f9c3f0d
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingPageReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.erp.controller.admin.mapping.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠鍒嗛〉 Request VO
+ */
+@Schema(description = "绠$悊鍚庡彴 - ERP浠撳簱涓嶮ES浠撳簱鏄犲皠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ErpMesWarehouseMappingPageReqVO extends PageParam {
+
+ @Schema(description = "ERP浠撳簱ID")
+ private Long erpWarehouseId;
+
+ @Schema(description = "MES浠撳簱ID")
+ private Long mesWarehouseId;
+
+ @Schema(description = "鐘舵��")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingUpdateReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingUpdateReqVO.java
new file mode 100644
index 0000000..7d7170c
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingUpdateReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.erp.controller.admin.mapping.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠鏇存柊 Request VO
+ */
+@Schema(description = "绠$悊鍚庡彴 - ERP浠撳簱涓嶮ES浠撳簱鏄犲皠鏇存柊 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ErpMesWarehouseMappingUpdateReqVO extends ErpMesWarehouseMappingCreateReqVO {
+
+ @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "缂栧彿涓嶈兘涓虹┖")
+ private Long id;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/mapping/ErpMesWarehouseMappingConvert.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/mapping/ErpMesWarehouseMappingConvert.java
new file mode 100644
index 0000000..594fe16
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/mapping/ErpMesWarehouseMappingConvert.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.erp.convert.mapping;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingCreateReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingUpdateReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.mapping.ErpMesWarehouseMappingDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠 Convert
+ */
+@Mapper
+public interface ErpMesWarehouseMappingConvert {
+
+ ErpMesWarehouseMappingConvert INSTANCE = Mappers.getMapper(ErpMesWarehouseMappingConvert.class);
+
+ ErpMesWarehouseMappingDO convert(ErpMesWarehouseMappingCreateReqVO bean);
+
+ ErpMesWarehouseMappingDO convert(ErpMesWarehouseMappingUpdateReqVO bean);
+
+ default PageResult<ErpMesWarehouseMappingDO> convertPage(PageResult<ErpMesWarehouseMappingDO> page) {
+ return page;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/mapping/ErpMesWarehouseMappingDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/mapping/ErpMesWarehouseMappingDO.java
new file mode 100644
index 0000000..e806d09
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/mapping/ErpMesWarehouseMappingDO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.erp.dal.dataobject.mapping;
+
+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.*;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠 DO
+ */
+@TableName("erp_mes_warehouse_mapping")
+@KeySequence("erp_mes_warehouse_mapping_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErpMesWarehouseMappingDO extends BaseDO {
+
+ /**
+ * 涓婚敭ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * ERP浠撳簱ID
+ */
+ private Long erpWarehouseId;
+
+ /**
+ * ERP浠撳簱鍚嶇О锛堝啑浣欙級
+ */
+ private String erpWarehouseName;
+
+ /**
+ * MES浠撳簱ID
+ */
+ private Long mesWarehouseId;
+
+ /**
+ * MES浠撳簱鍚嶇О锛堝啑浣欙級
+ */
+ private String mesWarehouseName;
+
+ /**
+ * 鐘舵�侊紙1-鍚敤 0-绂佺敤锛�
+ */
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/mapping/ErpMesWarehouseMappingMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/mapping/ErpMesWarehouseMappingMapper.java
new file mode 100644
index 0000000..44c73c6
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/mapping/ErpMesWarehouseMappingMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.erp.dal.mysql.mapping;
+
+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.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingPageReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.mapping.ErpMesWarehouseMappingDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠 Mapper
+ */
+@Mapper
+public interface ErpMesWarehouseMappingMapper extends BaseMapperX<ErpMesWarehouseMappingDO> {
+
+ default ErpMesWarehouseMappingDO selectByErpWarehouseId(Long erpWarehouseId) {
+ return selectOne(new LambdaQueryWrapperX<ErpMesWarehouseMappingDO>()
+ .eq(ErpMesWarehouseMappingDO::getErpWarehouseId, erpWarehouseId)
+ .eq(ErpMesWarehouseMappingDO::getStatus, 1)); // 鍙煡璇㈠惎鐢ㄧ姸鎬�
+ }
+
+ default ErpMesWarehouseMappingDO selectByMesWarehouseId(Long mesWarehouseId) {
+ return selectOne(new LambdaQueryWrapperX<ErpMesWarehouseMappingDO>()
+ .eq(ErpMesWarehouseMappingDO::getMesWarehouseId, mesWarehouseId));
+ }
+
+ default PageResult<ErpMesWarehouseMappingDO> selectPage(ErpMesWarehouseMappingPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<ErpMesWarehouseMappingDO>()
+ .eqIfPresent(ErpMesWarehouseMappingDO::getErpWarehouseId, reqVO.getErpWarehouseId())
+ .eqIfPresent(ErpMesWarehouseMappingDO::getMesWarehouseId, reqVO.getMesWarehouseId())
+ .eqIfPresent(ErpMesWarehouseMappingDO::getStatus, reqVO.getStatus())
+ .orderByDesc(ErpMesWarehouseMappingDO::getId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
index 01f0303..76f979c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
@@ -2,6 +2,7 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
@@ -10,6 +11,7 @@
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
import java.util.Objects;
/**
@@ -72,4 +74,11 @@
return selectOne(ErpPurchaseOrderDO::getNo, no);
}
+ default List<ErpPurchaseOrderDO> selectList(Long supplierId, Integer status) {
+ return selectList(new LambdaQueryWrapperX<ErpPurchaseOrderDO>()
+ .eqIfPresent(ErpPurchaseOrderDO::getSupplierId, supplierId)
+ .eqIfPresent(ErpPurchaseOrderDO::getStatus, status)
+ .orderByDesc(ErpPurchaseOrderDO::getId));
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
index 08d75e0..7294529 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -186,4 +186,8 @@
ErrorCode FINANCE_RECEIPT_NO_EXISTS = new ErrorCode(1_030_602_004, "鐢熸垚鏀舵鍗曞彿澶辫触锛岃閲嶆柊鎻愪氦");
ErrorCode FINANCE_RECEIPT_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_602_005, "鏀舵鍗�({})宸插鏍革紝鏃犳硶淇敼");
+ // ========== ERP 浠撳簱鏄犲皠 1-030-700-000 ==========
+ ErrorCode WAREHOUSE_MAPPING_NOT_EXISTS = new ErrorCode(1_030_700_000, "浠撳簱鏄犲皠涓嶅瓨鍦�");
+ ErrorCode WAREHOUSE_MAPPING_EXISTS = new ErrorCode(1_030_700_001, "璇RP浠撳簱宸插瓨鍦ㄦ槧灏勯厤缃�");
+
}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingService.java
new file mode 100644
index 0000000..b8619ea
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingService.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.erp.service.product.mapping;
+
+/**
+ * ERP 浜у搧涓� MDM 鐗╂枡鏄犲皠鏈嶅姟鎺ュ彛
+ *
+ * 璇存槑锛氭湰椤圭洰鐗╂枡缁熶竴浣跨敤 MDM 涓绘暟鎹鐞嗭紝ERP 浜у搧闇�瑕佷笌 MDM 鐗╂枡寤虹珛鏄犲皠鍏崇郴銆�
+ * 鏄犲皠绛栫暐锛氶�氳繃 ERP 浜у搧鏉$爜(barCode) 涓� MDM 鐗╂枡缂栫爜(code) 鎴栨潯鐮佽繘琛屽叧鑱斻��
+ */
+public interface ErpMdmMappingService {
+
+ /**
+ * 鏍规嵁 ERP 浜у搧 ID 鑾峰彇 MDM 鐗╂枡 ID
+ *
+ * 鏄犲皠绛栫暐锛�
+ * 1. 鏌ヨ ERP 浜у搧鐨� barCode
+ * 2. 閫氳繃 barCode 鍖归厤 MDM 鐗╂枡缂栫爜(code) 鎴栨潯鐮�
+ *
+ * @param erpProductId ERP 浜у搧 ID
+ * @return MDM 鐗╂枡 ID锛屾湭鎵惧埌杩斿洖 null
+ */
+ Long getMdmItemId(Long erpProductId);
+
+ /**
+ * 鏍规嵁 ERP 浜у搧鏉$爜鑾峰彇 MDM 鐗╂枡 ID
+ *
+ * @param barCode ERP 浜у搧鏉$爜
+ * @return MDM 鐗╂枡 ID锛屾湭鎵惧埌杩斿洖 null
+ */
+ Long getMdmItemIdByBarCode(String barCode);
+
+ /**
+ * 鏍规嵁 ERP 浠撳簱 ID 鑾峰彇 MES 浠撳簱 ID
+ *
+ * 鏄犲皠绛栫暐锛氶�氳繃浠撳簱鍚嶇О绮剧‘鍖归厤 MES 浠撳簱
+ *
+ * @param erpWarehouseId ERP 浠撳簱 ID
+ * @return MES 浠撳簱 ID锛屾湭鎵惧埌杩斿洖 null
+ */
+ Long getMesWarehouseId(Long erpWarehouseId);
+
+ /**
+ * 鏍规嵁 ERP 浠撳簱鍚嶇О鑾峰彇 MES 浠撳簱 ID
+ *
+ * @param erpWarehouseName ERP 浠撳簱鍚嶇О
+ * @return MES 浠撳簱 ID锛屾湭鎵惧埌杩斿洖 null
+ */
+ Long getMesWarehouseIdByName(String erpWarehouseName);
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingServiceImpl.java
new file mode 100644
index 0000000..ae56e04
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingServiceImpl.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.erp.service.product.mapping;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.erp.dal.dataobject.mapping.ErpMesWarehouseMappingDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.mapping.ErpMesWarehouseMappingMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
+import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpWarehouseMapper;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * ERP 浜у搧涓� MDM 鐗╂枡鏄犲皠鏈嶅姟瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class ErpMdmMappingServiceImpl implements ErpMdmMappingService {
+
+ @Resource
+ private ErpProductMapper productMapper;
+
+ @Resource
+ private ErpWarehouseMapper warehouseMapper;
+
+ @Resource
+ private ErpMesWarehouseMappingMapper warehouseMappingMapper;
+
+ @Resource
+ private MdmItemApi mdmItemApi;
+
+ @Override
+ public Long getMdmItemId(Long erpProductId) {
+ if (erpProductId == null) {
+ return null;
+ }
+ // 1. 鏌ヨ ERP 浜у搧
+ ErpProductDO product = productMapper.selectById(erpProductId);
+ if (product == null || StrUtil.isEmpty(product.getBarCode())) {
+ log.warn("[getMdmItemId] ERP浜у搧涓嶅瓨鍦ㄦ垨鏉$爜涓虹┖锛宔rpProductId={}", erpProductId);
+ return null;
+ }
+
+ // 2. 閫氳繃鏉$爜鏌ヨ MDM 鐗╂枡
+ return getMdmItemIdByBarCode(product.getBarCode());
+ }
+
+ @Override
+ public Long getMdmItemIdByBarCode(String barCode) {
+ if (StrUtil.isEmpty(barCode)) {
+ return null;
+ }
+
+ // 浼樺厛閫氳繃鏉$爜鏌ヨ
+ MdmItemRespDTO item = mdmItemApi.getItemByBarCode(barCode).getCheckedData();
+ if (item != null) {
+ return item.getId();
+ }
+
+ // 鏉$爜鍖归厤缂栫爜
+ item = mdmItemApi.getItemByCode(barCode).getCheckedData();
+ if (item != null) {
+ return item.getId();
+ }
+
+ log.warn("[getMdmItemIdByBarCode] 鏈壘鍒板搴旂殑MDM鐗╂枡锛宐arCode={}", barCode);
+ return null;
+ }
+
+ @Override
+ public Long getMesWarehouseId(Long erpWarehouseId) {
+ if (erpWarehouseId == null) {
+ return null;
+ }
+
+ // 1. 浼樺厛鏌ヨ鏄犲皠琛�
+ ErpMesWarehouseMappingDO mapping = warehouseMappingMapper.selectByErpWarehouseId(erpWarehouseId);
+ if (mapping != null) {
+ return mapping.getMesWarehouseId();
+ }
+
+ // 2. 鏄犲皠琛ㄦ病鏈夎褰曪紝灏濊瘯閫氳繃鍚嶇О鍖归厤锛堝鐢ㄦ柟妗堬級
+ ErpWarehouseDO erpWarehouse = warehouseMapper.selectById(erpWarehouseId);
+ if (erpWarehouse == null) {
+ log.warn("[getMesWarehouseId] ERP浠撳簱涓嶅瓨鍦紝erpWarehouseId={}", erpWarehouseId);
+ return null;
+ }
+
+ log.warn("[getMesWarehouseId] 鏈壘鍒颁粨搴撴槧灏勯厤缃紝erpWarehouseId={}锛岃閰嶇疆浠撳簱鏄犲皠", erpWarehouseId);
+ return null;
+ }
+
+ @Override
+ public Long getMesWarehouseIdByName(String erpWarehouseName) {
+ // 姝ゆ柟娉曞凡涓嶅啀鎺ㄨ崘浣跨敤锛屽缓璁厤缃槧灏勮〃
+ log.warn("[getMesWarehouseIdByName] 寤鸿閰嶇疆浠撳簱鏄犲皠琛ㄦ浛浠e悕绉板尮閰嶏紝erpWarehouseName={}", erpWarehouseName);
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingService.java
new file mode 100644
index 0000000..c00a629
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingService.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.erp.service.product.mapping;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingCreateReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingUpdateReqVO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.mapping.ErpMesWarehouseMappingDO;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠 Service 鎺ュ彛
+ */
+public interface ErpMesWarehouseMappingService {
+
+ /**
+ * 鍒涘缓浠撳簱鏄犲皠
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createMapping(ErpMesWarehouseMappingCreateReqVO createReqVO);
+
+ /**
+ * 鏇存柊浠撳簱鏄犲皠
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateMapping(ErpMesWarehouseMappingUpdateReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎浠撳簱鏄犲皠
+ *
+ * @param id 缂栧彿
+ */
+ void deleteMapping(Long id);
+
+ /**
+ * 鑾峰彇浠撳簱鏄犲皠
+ *
+ * @param id 缂栧彿
+ * @return 浠撳簱鏄犲皠
+ */
+ ErpMesWarehouseMappingDO getMapping(Long id);
+
+ /**
+ * 鑾峰彇浠撳簱鏄犲皠鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ鍙傛暟
+ * @return 浠撳簱鏄犲皠鍒嗛〉
+ */
+ PageResult<ErpMesWarehouseMappingDO> getMappingPage(ErpMesWarehouseMappingPageReqVO pageReqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingServiceImpl.java
new file mode 100644
index 0000000..2553b82
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingServiceImpl.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.erp.service.product.mapping;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingCreateReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingPageReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.mapping.vo.ErpMesWarehouseMappingUpdateReqVO;
+import cn.iocoder.yudao.module.erp.convert.mapping.ErpMesWarehouseMappingConvert;
+import cn.iocoder.yudao.module.erp.dal.dataobject.mapping.ErpMesWarehouseMappingDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.mapping.ErpMesWarehouseMappingMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
+
+/**
+ * ERP浠撳簱涓嶮ES浠撳簱鏄犲皠 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+public class ErpMesWarehouseMappingServiceImpl implements ErpMesWarehouseMappingService {
+
+ @Resource
+ private ErpMesWarehouseMappingMapper mappingMapper;
+
+ @Override
+ public Long createMapping(ErpMesWarehouseMappingCreateReqVO createReqVO) {
+ // 鏍¢獙ERP浠撳簱鏄惁宸插瓨鍦ㄦ槧灏�
+ ErpMesWarehouseMappingDO existing = mappingMapper.selectByErpWarehouseId(createReqVO.getErpWarehouseId());
+ if (existing != null) {
+ throw exception(WAREHOUSE_MAPPING_EXISTS);
+ }
+
+ ErpMesWarehouseMappingDO mapping = ErpMesWarehouseMappingConvert.INSTANCE.convert(createReqVO);
+ mapping.setStatus(1); // 榛樿鍚敤
+ mappingMapper.insert(mapping);
+ return mapping.getId();
+ }
+
+ @Override
+ public void updateMapping(ErpMesWarehouseMappingUpdateReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ validateMappingExists(updateReqVO.getId());
+
+ ErpMesWarehouseMappingDO mapping = ErpMesWarehouseMappingConvert.INSTANCE.convert(updateReqVO);
+ mappingMapper.updateById(mapping);
+ }
+
+ @Override
+ public void deleteMapping(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateMappingExists(id);
+ mappingMapper.deleteById(id);
+ }
+
+ private void validateMappingExists(Long id) {
+ if (mappingMapper.selectById(id) == null) {
+ throw exception(WAREHOUSE_MAPPING_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public ErpMesWarehouseMappingDO getMapping(Long id) {
+ return mappingMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<ErpMesWarehouseMappingDO> getMappingPage(ErpMesWarehouseMappingPageReqVO pageReqVO) {
+ return mappingMapper.selectPage(pageReqVO);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
index adc2f40..bc53896 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -18,16 +19,21 @@
import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
+import cn.iocoder.yudao.module.erp.service.product.mapping.ErpMdmMappingService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockRecordService;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsPurchaseInboundReqDTO;
+import cn.iocoder.yudao.module.wms.api.stock.WmsStockApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
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.Collection;
import java.util.Collections;
import java.util.List;
@@ -46,6 +52,7 @@
*/
@Service
@Validated
+@Slf4j
public class ErpPurchaseInServiceImpl implements ErpPurchaseInService {
@Resource
@@ -65,6 +72,12 @@
private ErpAccountService accountService;
@Resource
private ErpStockRecordService stockRecordService;
+
+ @Resource
+ private WmsStockApi wmsStockApi;
+
+ @Resource
+ private ErpMdmMappingService mdmMappingService;
@Resource
private AdminUserApi adminUserApi;
@@ -185,6 +198,69 @@
purchaseInItem.getProductId(), purchaseInItem.getWarehouseId(), count,
bizType, purchaseInItem.getInId(), purchaseInItem.getId(), purchaseIn.getNo()));
});
+
+ // 4. 鍚屾 WMS 搴撳瓨锛圡ES 搴撳瓨锛�
+ if (approve) {
+ syncToWmsPurchaseInbound(purchaseIn, purchaseInItems);
+ }
+ }
+
+ /**
+ * 鍚屾閲囪喘鍏ュ簱鍒� WMS 搴撳瓨
+ */
+ private void syncToWmsPurchaseInbound(ErpPurchaseInDO purchaseIn, List<ErpPurchaseInItemDO> purchaseInItems) {
+ if (CollUtil.isEmpty(purchaseInItems)) {
+ return;
+ }
+
+ log.info("[syncToWmsPurchaseInbound] 寮�濮嬪悓姝ラ噰璐叆搴撳埌WMS锛宲urchaseInId={}", purchaseIn.getId());
+
+ // 鏋勫缓鍏ュ簱璇锋眰
+ WmsPurchaseInboundReqDTO reqDTO = new WmsPurchaseInboundReqDTO()
+ .setBizId(purchaseIn.getId())
+ .setBizCode(purchaseIn.getNo())
+ .setReceiptTime(LocalDateTime.now());
+
+ // 杞崲鏄庣粏椤�
+ List<WmsPurchaseInboundReqDTO.Item> items = convertList(purchaseInItems, item -> {
+ // ERP 浜у搧 ID -> MDM 鐗╂枡 ID
+ Long mdmItemId = mdmMappingService.getMdmItemId(item.getProductId());
+ // ERP 浠撳簱 ID -> MES 浠撳簱 ID
+ Long mesWarehouseId = mdmMappingService.getMesWarehouseId(item.getWarehouseId());
+
+ if (mdmItemId == null) {
+ log.warn("[syncToWmsPurchaseInbound] 鏈壘鍒癕DM鐗╂枡鏄犲皠锛宔rpProductId={}", item.getProductId());
+ return null;
+ }
+ if (mesWarehouseId == null) {
+ log.warn("[syncToWmsPurchaseInbound] 鏈壘鍒癕ES浠撳簱鏄犲皠锛宔rpWarehouseId={}", item.getWarehouseId());
+ return null;
+ }
+
+ return new WmsPurchaseInboundReqDTO.Item()
+ .setItemId(mdmItemId)
+ .setWarehouseId(mesWarehouseId)
+ .setQuantity(item.getCount())
+ .setBizLineId(item.getId());
+ });
+
+ // 杩囨护鎺夋槧灏勫け璐ョ殑椤�
+ items = items.stream().filter(java.util.Objects::nonNull).toList();
+ if (CollUtil.isEmpty(items)) {
+ log.warn("[syncToWmsPurchaseInbound] 鏃犳湁鏁堢殑鍏ュ簱鏄庣粏锛岃烦杩嘩MS鍚屾");
+ return;
+ }
+
+ reqDTO.setItems(items);
+
+ try {
+ Long transactionId = wmsStockApi.purchaseInbound(reqDTO);
+ log.info("[syncToWmsPurchaseInbound] 鍚屾鎴愬姛锛宼ransactionId={}", transactionId);
+ } catch (Exception e) {
+ log.error("[syncToWmsPurchaseInbound] 鍚屾澶辫触", e);
+ // 娉ㄦ剰锛氳繖閲屽彲浠ラ�夋嫨鎶涘嚭寮傚父鍥炴粴锛屾垨浠呰褰曟棩蹇�
+ // throw exception(PURCHASE_IN_SYNC_WMS_FAIL);
+ }
}
@Override
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
index bfea076..b709b02 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -107,4 +107,21 @@
*/
List<ErpPurchaseOrderItemDO> getPurchaseOrderItemListByOrderIds(Collection<Long> orderIds);
+ /**
+ * 鏍规嵁渚涘簲鍟嗙紪鍙峰拰鐘舵�佽幏鍙栭噰璐鍗曞垪琛�
+ *
+ * @param supplierId 渚涘簲鍟嗙紪鍙�
+ * @param status 鐘舵��
+ * @return 閲囪喘璁㈠崟鍒楄〃
+ */
+ List<ErpPurchaseOrderDO> getPurchaseOrderListBySupplierIdAndStatus(Long supplierId, Integer status);
+
+ /**
+ * 鏍规嵁缂栧彿鍒楄〃鑾峰彇閲囪喘璁㈠崟鍒楄〃
+ *
+ * @param ids 缂栧彿鍒楄〃
+ * @return 閲囪喘璁㈠崟鍒楄〃
+ */
+ List<ErpPurchaseOrderDO> getPurchaseOrderList(List<Long> ids);
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
index 28bc81b..6f7f42e 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -293,4 +293,20 @@
return purchaseOrderItemMapper.selectListByOrderIds(orderIds);
}
+ @Override
+ public List<ErpPurchaseOrderDO> getPurchaseOrderListBySupplierIdAndStatus(Long supplierId, Integer status) {
+ if (supplierId == null) {
+ return Collections.emptyList();
+ }
+ return purchaseOrderMapper.selectList(supplierId, status);
+ }
+
+ @Override
+ public List<ErpPurchaseOrderDO> getPurchaseOrderList(List<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ return purchaseOrderMapper.selectByIds(ids);
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
index f8af202..af646e1 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java
@@ -18,16 +18,21 @@
import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
+import cn.iocoder.yudao.module.erp.service.product.mapping.ErpMdmMappingService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockRecordService;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsSalesOutboundReqDTO;
+import cn.iocoder.yudao.module.wms.api.stock.WmsStockApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
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.Collection;
import java.util.Collections;
import java.util.List;
@@ -46,6 +51,7 @@
*/
@Service
@Validated
+@Slf4j
public class ErpSaleOutServiceImpl implements ErpSaleOutService {
@Resource
@@ -65,6 +71,12 @@
private ErpAccountService accountService;
@Resource
private ErpStockRecordService stockRecordService;
+
+ @Resource
+ private WmsStockApi wmsStockApi;
+
+ @Resource
+ private ErpMdmMappingService mdmMappingService;
@Resource
private AdminUserApi adminUserApi;
@@ -193,6 +205,67 @@
saleOutItem.getProductId(), saleOutItem.getWarehouseId(), count,
bizType, saleOutItem.getOutId(), saleOutItem.getId(), saleOut.getNo()));
});
+
+ // 4. 鍚屾 WMS 搴撳瓨锛圡ES 搴撳瓨锛�
+ if (approve) {
+ syncToWmsSalesOutbound(saleOut, saleOutItems);
+ }
+ }
+
+ /**
+ * 鍚屾閿�鍞嚭搴撳埌 WMS 搴撳瓨
+ */
+ private void syncToWmsSalesOutbound(ErpSaleOutDO saleOut, List<ErpSaleOutItemDO> saleOutItems) {
+ if (CollUtil.isEmpty(saleOutItems)) {
+ return;
+ }
+
+ log.info("[syncToWmsSalesOutbound] 寮�濮嬪悓姝ラ攢鍞嚭搴撳埌WMS锛宻aleOutId={}", saleOut.getId());
+
+ // 鏋勫缓鍑哄簱璇锋眰
+ WmsSalesOutboundReqDTO reqDTO = new WmsSalesOutboundReqDTO()
+ .setBizId(saleOut.getId())
+ .setBizCode(saleOut.getNo())
+ .setOutboundTime(LocalDateTime.now());
+
+ // 杞崲鏄庣粏椤�
+ List<WmsSalesOutboundReqDTO.Item> items = convertList(saleOutItems, item -> {
+ // ERP 浜у搧 ID -> MDM 鐗╂枡 ID
+ Long mdmItemId = mdmMappingService.getMdmItemId(item.getProductId());
+ // ERP 浠撳簱 ID -> MES 浠撳簱 ID
+ Long mesWarehouseId = mdmMappingService.getMesWarehouseId(item.getWarehouseId());
+
+ if (mdmItemId == null) {
+ log.warn("[syncToWmsSalesOutbound] 鏈壘鍒癕DM鐗╂枡鏄犲皠锛宔rpProductId={}", item.getProductId());
+ return null;
+ }
+ if (mesWarehouseId == null) {
+ log.warn("[syncToWmsSalesOutbound] 鏈壘鍒癕ES浠撳簱鏄犲皠锛宔rpWarehouseId={}", item.getWarehouseId());
+ return null;
+ }
+
+ return new WmsSalesOutboundReqDTO.Item()
+ .setItemId(mdmItemId)
+ .setWarehouseId(mesWarehouseId)
+ .setQuantity(item.getCount())
+ .setBizLineId(item.getId());
+ });
+
+ // 杩囨护鎺夋槧灏勫け璐ョ殑椤�
+ items = items.stream().filter(java.util.Objects::nonNull).toList();
+ if (CollUtil.isEmpty(items)) {
+ log.warn("[syncToWmsSalesOutbound] 鏃犳湁鏁堢殑鍑哄簱鏄庣粏锛岃烦杩嘩MS鍚屾");
+ return;
+ }
+
+ reqDTO.setItems(items);
+
+ try {
+ Long transactionId = wmsStockApi.salesOutbound(reqDTO);
+ log.info("[syncToWmsSalesOutbound] 鍚屾鎴愬姛锛宼ransactionId={}", transactionId);
+ } catch (Exception e) {
+ log.error("[syncToWmsSalesOutbound] 鍚屾澶辫触", e);
+ }
}
@Override
diff --git a/yudao-module-hrm-api/pom.xml b/yudao-module-hrm-api/pom.xml
new file mode 100644
index 0000000..0e7529d
--- /dev/null
+++ b/yudao-module-hrm-api/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-hrm-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ hrm 妯″潡涓嬶紝浜哄姏璧勬簮绠$悊鐨� API 鎺ュ彛瀹氫箟锛屼緵鍏朵粬妯″潡寮曠敤
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-common</artifactId>
+ </dependency>
+
+ <!-- Swagger 娉ㄨВ -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ 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
new file mode 100644
index 0000000..e2b579a
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -0,0 +1,94 @@
+package cn.iocoder.yudao.module.hrm.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * HRM 閿欒鐮佹灇涓剧被
+ * <p>
+ * hrm 绯荤粺锛屼娇鐢� 1-030-000-000 娈�
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 鍛樺伐淇℃伅 1-030-000-000 ==========
+ ErrorCode EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_000_000, "鍛樺伐涓嶅瓨鍦�");
+ ErrorCode EMPLOYEE_NO_EXISTS = new ErrorCode(1_030_000_001, "鍛樺伐缂栧彿宸插瓨鍦�");
+ ErrorCode EMPLOYEE_PHONE_EXISTS = new ErrorCode(1_030_000_002, "鎵嬫満鍙峰凡瀛樺湪锛岃鏇存崲鎵嬫満鍙�");
+ ErrorCode EMPLOYEE_EDUCATION_NOT_EXISTS = new ErrorCode(1_030_000_003, "鏁欒偛缁忓巻涓嶅瓨鍦�");
+ ErrorCode EMPLOYEE_WORK_HISTORY_NOT_EXISTS = new ErrorCode(1_030_000_004, "宸ヤ綔缁忓巻涓嶅瓨鍦�");
+ ErrorCode EMPLOYEE_EMERGENCY_CONTACT_NOT_EXISTS = new ErrorCode(1_030_000_005, "绱ф�ヨ仈绯讳汉涓嶅瓨鍦�");
+
+ // ========== 璇峰亣鐢宠 1-030-001-000 ==========
+ ErrorCode LEAVE_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_001_000, "璇峰亣鐢宠涓嶅瓨鍦�");
+ ErrorCode LEAVE_APPLICATION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_001_001, "璇峰亣鐢宠鏇存柊澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+ ErrorCode LEAVE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_001_002, "璇峰亣鐢宠鎻愪氦澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+ ErrorCode LEAVE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_001_003, "鏇存柊璇峰亣鐢宠瀹℃壒鐘舵�佸け璐ワ紝鍘熷洜锛氫笉鏄鎵逛腑鐘舵��");
+ ErrorCode LEAVE_APPLICATION_NO_EXISTS = new ErrorCode(1_030_001_004, "鐢熸垚璇峰亣鐢宠鍗曞彿閲嶅锛岃閲嶈瘯");
+ ErrorCode LEAVE_APPLICATION_DURATION_EXCEED = new ErrorCode(1_030_001_005, "璇峰亣鏃堕暱瓒呭嚭骞村亣浣欓");
+ ErrorCode LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_001_006, "璇峰亣瀹℃壒娴佺▼涓嶅瓨鍦�");
+
+ // ========== 璋冨矖鐢宠 1-030-002-000 ==========
+ ErrorCode TRANSFER_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_002_000, "璋冨矖鐢宠涓嶅瓨鍦�");
+ ErrorCode TRANSFER_APPLICATION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_002_001, "璋冨矖鐢宠鏇存柊澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+ ErrorCode TRANSFER_APPLICATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_002_002, "璋冨矖鐢宠鎻愪氦澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+ ErrorCode TRANSFER_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_002_003, "鏇存柊璋冨矖鐢宠瀹℃壒鐘舵�佸け璐ワ紝鍘熷洜锛氫笉鏄鎵逛腑鐘舵��");
+ ErrorCode TRANSFER_APPLICATION_NO_EXISTS = new ErrorCode(1_030_002_004, "鐢熸垚璋冨矖鐢宠鍗曞彿閲嶅锛岃閲嶈瘯");
+ ErrorCode TRANSFER_APPLICATION_SAME_DEPT = new ErrorCode(1_030_002_005, "璋冨矖澶辫触锛屽師鍥狅細鐩爣閮ㄩ棬涓庡師閮ㄩ棬鐩稿悓");
+ ErrorCode TRANSFER_APPLICATION_SAME_POST = new ErrorCode(1_030_002_006, "璋冨矖澶辫触锛屽師鍥狅細鐩爣宀椾綅涓庡師宀椾綅鐩稿悓");
+ ErrorCode TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_002_007, "璋冨矖瀹℃壒娴佺▼涓嶅瓨鍦�");
+
+ // ========== 绂昏亴鐢宠 1-030-003-000 ==========
+ ErrorCode RESIGNATION_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_003_000, "绂昏亴鐢宠涓嶅瓨鍦�");
+ ErrorCode RESIGNATION_APPLICATION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_003_001, "绂昏亴鐢宠鏇存柊澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+ ErrorCode RESIGNATION_APPLICATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_003_002, "绂昏亴鐢宠鎻愪氦澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+ ErrorCode RESIGNATION_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_003_003, "鏇存柊绂昏亴鐢宠瀹℃壒鐘舵�佸け璐ワ紝鍘熷洜锛氫笉鏄鎵逛腑鐘舵��");
+ ErrorCode RESIGNATION_APPLICATION_NO_EXISTS = new ErrorCode(1_030_003_004, "鐢熸垚绂昏亴鐢宠鍗曞彿閲嶅锛岃閲嶈瘯");
+ ErrorCode RESIGNATION_APPLICATION_EXISTS = new ErrorCode(1_030_003_005, "璇ュ憳宸ュ凡瀛樺湪绂昏亴鐢宠锛岃鍕块噸澶嶆彁浜�");
+ ErrorCode RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_003_006, "绂昏亴瀹℃壒娴佺▼涓嶅瓨鍦�");
+
+ // ========== 鑰冨嫟绠$悊 1-030-004-000 ==========
+ ErrorCode ATTENDANCE_RECORD_NOT_EXISTS = new ErrorCode(1_030_004_000, "鑰冨嫟璁板綍涓嶅瓨鍦�");
+ ErrorCode ATTENDANCE_RECORD_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_030_004_001, "瀵煎叆鑰冨嫟鏁版嵁涓嶈兘涓虹┖");
+ ErrorCode ATTENDANCE_RECORD_EXISTS = new ErrorCode(1_030_004_002, "璇ュ憳宸ュ綋鏃ヨ�冨嫟璁板綍宸插瓨鍦�");
+ ErrorCode ATTENDANCE_EXCEPTION_NOT_EXISTS = new ErrorCode(1_030_004_003, "鑰冨嫟寮傚父鐢宠涓嶅瓨鍦�");
+ ErrorCode ATTENDANCE_EXCEPTION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_004_004, "鑰冨嫟寮傚父鐢宠鎻愪氦澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+ ErrorCode ATTENDANCE_RULE_NOT_EXISTS = new ErrorCode(1_030_004_005, "鑰冨嫟瑙勫垯涓嶅瓨鍦�");
+ ErrorCode ATTENDANCE_RULE_NAME_EXISTS = new ErrorCode(1_030_004_006, "鑰冨嫟瑙勫垯鍚嶇О宸插瓨鍦�");
+ ErrorCode ATTENDANCE_RULE_DEPT_EXISTS = new ErrorCode(1_030_004_007, "璇ラ儴闂ㄥ凡閰嶇疆鑰冨嫟瑙勫垯");
+ ErrorCode ATTENDANCE_HOLIDAY_NOT_EXISTS = new ErrorCode(1_030_004_008, "鑺傚亣鏃ラ厤缃笉瀛樺湪");
+ ErrorCode ATTENDANCE_HOLIDAY_DATE_EXISTS = new ErrorCode(1_030_004_009, "璇ユ棩鏈熻妭鍋囨棩閰嶇疆宸插瓨鍦�");
+ ErrorCode ATTENDANCE_CLOCK_FAIL_NO_RULE = new ErrorCode(1_030_004_010, "鎵撳崱澶辫触锛屾湭鎵惧埌鑰冨嫟瑙勫垯");
+ ErrorCode ATTENDANCE_CLOCK_FAIL_HOLIDAY = new ErrorCode(1_030_004_011, "鎵撳崱澶辫触锛屽綋鏃ヤ负浼戞伅鏃�");
+ ErrorCode ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY = new ErrorCode(1_030_004_012, "鎵撳崱澶辫触锛屽綋鏃ヤ笉鏄伐浣滄棩");
+ ErrorCode ATTENDANCE_SUMMARY_NOT_EXISTS = new ErrorCode(1_030_004_013, "鑰冨嫟姹囨�昏褰曚笉瀛樺湪");
+ ErrorCode ATTENDANCE_EXCEPTION_UPDATE_AUDIT_STATUS_FAIL = new ErrorCode(1_030_004_014, "鏇存柊鑰冨嫟寮傚父瀹℃壒鐘舵�佸け璐ワ紝鍘熷洜锛氫笉鏄鎵逛腑鐘舵��");
+ ErrorCode ATTENDANCE_EXCEPTION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_004_015, "鑰冨嫟寮傚父瀹℃壒娴佺▼涓嶅瓨鍦�");
+ ErrorCode ATTENDANCE_CLOCK_ALREADY_CLOCKED = new ErrorCode(1_030_004_016, "宸叉墦杩囧崱锛岃鍕块噸澶嶆墦鍗�");
+
+ // ========== 钖叕绠$悊 1-030-005-000 ==========
+ ErrorCode SALARY_STRUCTURE_NOT_EXISTS = new ErrorCode(1_030_005_000, "钖叕缁撴瀯涓嶅瓨鍦�");
+ ErrorCode EMPLOYEE_SALARY_NOT_EXISTS = new ErrorCode(1_030_005_001, "鍛樺伐钖叕妗f涓嶅瓨鍦�");
+ ErrorCode EMPLOYEE_SALARY_EXISTS = new ErrorCode(1_030_005_002, "璇ュ憳宸ヨ柂閰。妗堝凡瀛樺湪");
+ ErrorCode SALARY_CALCULATION_NOT_EXISTS = new ErrorCode(1_030_005_003, "钖叕鏍哥畻璁板綍涓嶅瓨鍦�");
+ ErrorCode SALARY_CALCULATION_EXISTS = new ErrorCode(1_030_005_004, "璇ュ憳宸ュ綋鏈堣柂閰牳绠楄褰曞凡瀛樺湪");
+ ErrorCode SALARY_CALCULATION_CONFIRM_FAIL = new ErrorCode(1_030_005_005, "钖叕鏍哥畻纭澶辫触锛屽師鍥狅細涓嶆槸寰呯‘璁ょ姸鎬�");
+ ErrorCode SALARY_PAYMENT_NOT_EXISTS = new ErrorCode(1_030_005_006, "钖祫鍙戞斁璁板綍涓嶅瓨鍦�");
+ ErrorCode SOCIAL_SECURITY_CONFIG_NOT_EXISTS = new ErrorCode(1_030_005_007, "绀句繚鍏Н閲戦厤缃笉瀛樺湪");
+ ErrorCode TAX_RATE_CONFIG_NOT_EXISTS = new ErrorCode(1_030_005_008, "涓◣绋庣巼閰嶇疆涓嶅瓨鍦�");
+ ErrorCode EMPLOYEE_SOCIAL_SECURITY_NOT_EXISTS = new ErrorCode(1_030_005_009, "鍛樺伐绀句繚鍏Н閲戞。妗堜笉瀛樺湪");
+ ErrorCode SOCIAL_SECURITY_SCHEME_NOT_EXISTS = new ErrorCode(1_030_005_010, "绀句繚鍏Н閲戞柟妗堜笉瀛樺湪");
+ ErrorCode LEAVE_TYPE_CONFIG_NOT_EXISTS = new ErrorCode(1_030_005_011, "璇峰亣绫诲瀷鎵f閰嶇疆涓嶅瓨鍦�");
+ ErrorCode SALARY_PAYMENT_EXISTS = new ErrorCode(1_030_005_012, "璇ュ懆鏈熻柂璧勫彂鏀惧彴璐﹀凡瀛樺湪");
+ ErrorCode SALARY_CALCULATION_NO_CONFIRMED = new ErrorCode(1_030_005_013, "璇ュ懆鏈熸病鏈夊凡纭鐨勮柂閰牳绠楄褰�");
+ ErrorCode SALARY_PAYMENT_ALREADY_PAY = new ErrorCode(1_030_005_014, "钖祫鍙戞斁鍙拌处宸插彂鏀撅紝璇峰嬁閲嶅鎿嶄綔");
+ ErrorCode SALARY_PAYMENT_DETAIL_NO_EXISTS = new ErrorCode(1_030_005_015, "娌℃湁寰呭彂鏀剧殑鏄庣粏璁板綍");
+ ErrorCode SALARY_PAYMENT_DETAIL_NOT_EXISTS = new ErrorCode(1_030_005_016, "钖祫鍙戞斁鏄庣粏涓嶅瓨鍦�");
+ ErrorCode SALARY_PAYMENT_DETAIL_ALREADY_PAY = new ErrorCode(1_030_005_017, "璇ユ槑缁嗗凡鍙戞斁锛岃鍕块噸澶嶆搷浣�");
+ ErrorCode SALARY_CALCULATION_DUPLICATE = new ErrorCode(1_030_005_018, "鍛樺伐銆恵}銆戝湪鍛ㄦ湡銆恵}銆戝瓨鍦ㄩ噸澶嶇殑钖叕鏍哥畻璁板綍锛岃鑱旂郴绠$悊鍛樺鐞�");
+ ErrorCode SALARY_CALCULATION_REVOKE_FAIL = new ErrorCode(1_030_005_019, "鎾ら攢纭澶辫触锛屽師鍥狅細涓嶆槸宸茬‘璁ょ姸鎬�");
+ ErrorCode SALARY_CALCULATION_DELETE_FAIL = new ErrorCode(1_030_005_020, "鍒犻櫎澶辫触锛屽師鍥狅細浠呭緟纭鐘舵�佸彲鍒犻櫎");
+ ErrorCode SALARY_CALCULATION_ALREADY_PAY = new ErrorCode(1_030_005_021, "璇ユ牳绠楄褰曞凡鍙戞斁锛屾棤娉曟挙閿�");
+ ErrorCode SALARY_PAYMENT_REVOKE_FAIL = new ErrorCode(1_030_005_022, "鎾ら攢鍙戞斁澶辫触锛屽師鍥狅細璇ユ槑缁嗘湭鍙戞斁");
+ ErrorCode SALARY_PAYMENT_DELETE_FAIL = new ErrorCode(1_030_005_023, "鍒犻櫎澶辫触锛屽師鍥狅細浠呭緟鍙戞斁鐘舵�佸彲鍒犻櫎锛岃鍏堟挙閿�宸插彂鏀炬槑缁�");
+ ErrorCode SALARY_PAYMENT_HAS_PAID_DETAIL = new ErrorCode(1_030_005_024, "鍒犻櫎澶辫触锛屽師鍥狅細瀛樺湪宸插彂鏀炬槑缁嗭紝璇峰厛鎾ら攢");
+
+}
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceClockTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceClockTypeEnum.java
new file mode 100644
index 0000000..ee77e9f
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceClockTypeEnum.java
@@ -0,0 +1,31 @@
+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 HrmAttendanceClockTypeEnum implements ArrayValuable<Integer> {
+
+ NORMAL(1, "姝e父"),
+ LATE(2, "杩熷埌"),
+ EARLY(3, "鏃╅��"),
+ MISSING(4, "缂哄崱");
+
+ private final Integer type;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmAttendanceClockTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceDataSourceEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceDataSourceEnum.java
new file mode 100644
index 0000000..f0ad13e
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceDataSourceEnum.java
@@ -0,0 +1,30 @@
+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 HrmAttendanceDataSourceEnum implements ArrayValuable<Integer> {
+
+ CLOCK(1, "鎵撳崱"),
+ IMPORT(2, "瀵煎叆"),
+ SUPPLEMENT(3, "琛ュ崱瀹℃壒");
+
+ private final Integer type;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmAttendanceDataSourceEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceExceptionTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceExceptionTypeEnum.java
new file mode 100644
index 0000000..05d25b5
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceExceptionTypeEnum.java
@@ -0,0 +1,30 @@
+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 HrmAttendanceExceptionTypeEnum implements ArrayValuable<Integer> {
+
+ SUPPLEMENT(1, "琛ュ崱"),
+ OVERTIME(2, "鍔犵彮"),
+ BUSINESS_TRIP(3, "鍑哄樊");
+
+ private final Integer type;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmAttendanceExceptionTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceHolidayTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceHolidayTypeEnum.java
new file mode 100644
index 0000000..0daaf83
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceHolidayTypeEnum.java
@@ -0,0 +1,29 @@
+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 HrmAttendanceHolidayTypeEnum implements ArrayValuable<Integer> {
+
+ REST_DAY(1, "浼戞伅鏃�"),
+ WORK_DAY(2, "宸ヤ綔鏃�");
+
+ private final Integer type;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmAttendanceHolidayTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAuditStatusEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAuditStatusEnum.java
new file mode 100644
index 0000000..ac759ee
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAuditStatusEnum.java
@@ -0,0 +1,40 @@
+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;
+
+/**
+ * HRM 鐨勫鎵圭姸鎬�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum HrmAuditStatusEnum implements ArrayValuable<Integer> {
+
+ DRAFT(0, "鑽夌"),
+ PROCESS(10, "瀹℃壒涓�"),
+ APPROVE(20, "瀹℃壒閫氳繃"),
+ REJECT(30, "瀹℃壒椹冲洖"),
+ CANCEL(40, "宸叉挙閿�");
+
+ private final Integer status;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmAuditStatusEnum::getStatus).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static String getNameByStatus(Integer status) {
+ return Arrays.stream(values())
+ .filter(e -> e.getStatus().equals(status))
+ .findFirst()
+ .map(HrmAuditStatusEnum::getName)
+ .orElse("");
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmDegreeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmDegreeEnum.java
new file mode 100644
index 0000000..a14331f
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmDegreeEnum.java
@@ -0,0 +1,32 @@
+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 HrmDegreeEnum implements ArrayValuable<Integer> {
+
+ HIGH_SCHOOL(1, "楂樹腑"),
+ COLLEGE(2, "澶т笓"),
+ BACHELOR(3, "鏈"),
+ MASTER(4, "纭曞+"),
+ DOCTOR(5, "鍗氬+");
+
+ private final Integer degree;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmDegreeEnum::getDegree).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeStatusEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeStatusEnum.java
new file mode 100644
index 0000000..3f06f5e
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeStatusEnum.java
@@ -0,0 +1,30 @@
+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 HrmEmployeeStatusEnum implements ArrayValuable<Integer> {
+
+ ON_JOB(1, "鍦ㄨ亴"),
+ TRIAL(2, "璇曠敤"),
+ LEAVE(3, "绂昏亴");
+
+ private final Integer status;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmEmployeeStatusEnum::getStatus).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmLeaveTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmLeaveTypeEnum.java
new file mode 100644
index 0000000..1918984
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmLeaveTypeEnum.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 HrmLeaveTypeEnum implements ArrayValuable<Integer> {
+
+ PERSONAL(1, "浜嬪亣"),
+ SICK(2, "鐥呭亣"),
+ ANNUAL(3, "骞村亣"),
+ MARRIAGE(4, "濠氬亣"),
+ MATERNITY(5, "浜у亣"),
+ BEREAVEMENT(6, "涓у亣");
+
+ private final Integer type;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmLeaveTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static String getNameByStatus(Integer type) {
+ return Arrays.stream(values())
+ .filter(e -> e.getType().equals(type))
+ .findFirst()
+ .map(HrmLeaveTypeEnum::getName)
+ .orElse("");
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmResignationTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmResignationTypeEnum.java
new file mode 100644
index 0000000..f3af295
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmResignationTypeEnum.java
@@ -0,0 +1,38 @@
+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 HrmResignationTypeEnum implements ArrayValuable<Integer> {
+
+ VOLUNTARY(1, "涓诲姩绂昏亴"),
+ INVOLUNTARY(2, "琚姩绂昏亴"),
+ CONTRACT_EXPIRED(3, "鍚堝悓鍒版湡");
+
+ private final Integer type;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmResignationTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static String getNameByStatus(Integer type) {
+ return Arrays.stream(values())
+ .filter(e -> e.getType().equals(type))
+ .findFirst()
+ .map(HrmResignationTypeEnum::getName)
+ .orElse("");
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmTransferTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmTransferTypeEnum.java
new file mode 100644
index 0000000..78deeda
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmTransferTypeEnum.java
@@ -0,0 +1,38 @@
+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 HrmTransferTypeEnum implements ArrayValuable<Integer> {
+
+ LATERAL(1, "骞宠皟"),
+ PROMOTION(2, "鍗囪亴"),
+ DEMOTION(3, "闄嶈亴");
+
+ private final Integer type;
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmTransferTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static String getNameByStatus(Integer type) {
+ return Arrays.stream(values())
+ .filter(e -> e.getType().equals(type))
+ .findFirst()
+ .map(HrmTransferTypeEnum::getName)
+ .orElse("");
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/pom.xml b/yudao-module-hrm/pom.xml
new file mode 100644
index 0000000..df07d9c
--- /dev/null
+++ b/yudao-module-hrm/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-hrm</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ hrm 妯″潡涓嬶紝浜哄姏璧勬簮绠$悊锛圚uman Resource Management锛夈��
+ 渚嬪璇达細鑰冨嫟绠$悊銆佽柂閰鐞嗐�佷汉浜嬪鎵癸紙璇峰亣銆佽皟宀椼�佺鑱岋級绛�
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-hrm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-system</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-bpm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 涓氬姟缁勪欢 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
+ </dependency>
+
+ <!-- Web 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-security</artifactId>
+ </dependency>
+
+ <!-- DB 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-redis</artifactId>
+ </dependency>
+
+ <!-- 宸ュ叿绫荤浉鍏� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-excel</artifactId>
+ </dependency>
+
+ <!-- Test 娴嬭瘯鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-test</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java
new file mode 100644
index 0000000..f362ce9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java
@@ -0,0 +1,113 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmLeaveApplicationService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.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;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 璇峰亣鐢宠")
+@RestController
+@RequestMapping("/hrm/leave")
+@Validated
+public class HrmLeaveApplicationController {
+
+ @Resource
+ private HrmLeaveApplicationService leaveApplicationService;
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓璇峰亣鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:leave:create')")
+ public CommonResult<Long> createLeaveApplication(@Valid @RequestBody HrmLeaveApplicationSaveReqVO createReqVO) {
+ return success(leaveApplicationService.createLeaveApplication(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊璇峰亣鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:leave:update')")
+ public CommonResult<Boolean> updateLeaveApplication(@Valid @RequestBody HrmLeaveApplicationSaveReqVO updateReqVO) {
+ leaveApplicationService.updateLeaveApplication(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎璇峰亣鐢宠")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:leave:delete')")
+ public CommonResult<Boolean> deleteLeaveApplication(@RequestParam("id") Long id) {
+ leaveApplicationService.deleteLeaveApplication(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱璇峰亣鐢宠璇︽儏")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:leave:query')")
+ public CommonResult<HrmLeaveApplicationRespVO> getLeaveApplication(@RequestParam("id") Long id) {
+ return success(leaveApplicationService.getLeaveApplicationDetail(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱璇峰亣鐢宠鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:leave:query')")
+ public CommonResult<PageResult<HrmLeaveApplicationRespVO>> getLeaveApplicationPage(@Valid HrmLeaveApplicationPageReqVO pageReqVO) {
+ return success(leaveApplicationService.getLeaveApplicationPage(pageReqVO));
+ }
+
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦璇峰亣鐢宠瀹℃壒")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @Parameter(name = "processDefinitionKey", description = "娴佺▼瀹氫箟Key", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:leave:submit')")
+ public CommonResult<Boolean> submitLeaveApplication(@RequestParam("id") Long id,
+ @RequestParam("processDefinitionKey") String processDefinitionKey) {
+ // 鑾峰彇褰撳墠鐢ㄦ埛ID
+ AdminUserRespDTO user = adminUserApi.getUser(1L); // TODO: 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛
+ leaveApplicationService.submitLeaveApplication(id, processDefinitionKey, user.getId());
+ return success(true);
+ }
+
+ @GetMapping("/approve-process-definition-list")
+ @Operation(summary = "鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔夊垪琛�")
+ @PreAuthorize("@ss.hasPermission('hrm:leave:query')")
+ public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
+ return success(leaveApplicationService.getApproveProcessDefinitionList());
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭璇峰亣鐢宠 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:leave:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportLeaveApplicationExcel(@Valid HrmLeaveApplicationPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List<HrmLeaveApplicationRespVO> list = leaveApplicationService.getLeaveApplicationPage(pageReqVO).getList();
+ ExcelUtils.write(response, "璇峰亣鐢宠.xls", "鏁版嵁",
+ HrmLeaveApplicationRespVO.class, list);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java
new file mode 100644
index 0000000..2aa674f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmResignationApplicationService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.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;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 绂昏亴鐢宠")
+@RestController
+@RequestMapping("/hrm/resignation")
+@Validated
+public class HrmResignationApplicationController {
+
+ @Resource
+ private HrmResignationApplicationService resignationApplicationService;
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓绂昏亴鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:create')")
+ public CommonResult<Long> createResignationApplication(@Valid @RequestBody HrmResignationApplicationSaveReqVO createReqVO) {
+ return success(resignationApplicationService.createResignationApplication(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊绂昏亴鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:update')")
+ public CommonResult<Boolean> updateResignationApplication(@Valid @RequestBody HrmResignationApplicationSaveReqVO updateReqVO) {
+ resignationApplicationService.updateResignationApplication(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎绂昏亴鐢宠")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:delete')")
+ public CommonResult<Boolean> deleteResignationApplication(@RequestParam("id") Long id) {
+ resignationApplicationService.deleteResignationApplication(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱绂昏亴鐢宠璇︽儏")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:query')")
+ public CommonResult<HrmResignationApplicationRespVO> getResignationApplication(@RequestParam("id") Long id) {
+ return success(resignationApplicationService.getResignationApplicationDetail(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱绂昏亴鐢宠鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:query')")
+ public CommonResult<PageResult<HrmResignationApplicationRespVO>> getResignationApplicationPage(@Valid HrmResignationApplicationPageReqVO pageReqVO) {
+ return success(resignationApplicationService.getResignationApplicationPage(pageReqVO));
+ }
+
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦绂昏亴鐢宠瀹℃壒")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @Parameter(name = "processDefinitionKey", description = "娴佺▼瀹氫箟Key", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:submit')")
+ public CommonResult<Boolean> submitResignationApplication(@RequestParam("id") Long id,
+ @RequestParam("processDefinitionKey") String processDefinitionKey) {
+ AdminUserRespDTO user = adminUserApi.getUser(1L); // TODO: 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛
+ resignationApplicationService.submitResignationApplication(id, processDefinitionKey, user.getId());
+ return success(true);
+ }
+
+ @GetMapping("/approve-process-definition-list")
+ @Operation(summary = "鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔夊垪琛�")
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:query')")
+ public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
+ return success(resignationApplicationService.getApproveProcessDefinitionList());
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭绂昏亴鐢宠 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:resignation:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportResignationApplicationExcel(@Valid HrmResignationApplicationPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List<HrmResignationApplicationRespVO> list = resignationApplicationService.getResignationApplicationPage(pageReqVO).getList();
+ ExcelUtils.write(response, "绂昏亴鐢宠.xls", "鏁版嵁",
+ HrmResignationApplicationRespVO.class, list);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java
new file mode 100644
index 0000000..a700a2f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmTransferApplicationService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.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;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 璋冨矖鐢宠")
+@RestController
+@RequestMapping("/hrm/transfer")
+@Validated
+public class HrmTransferApplicationController {
+
+ @Resource
+ private HrmTransferApplicationService transferApplicationService;
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓璋冨矖鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:create')")
+ public CommonResult<Long> createTransferApplication(@Valid @RequestBody HrmTransferApplicationSaveReqVO createReqVO) {
+ return success(transferApplicationService.createTransferApplication(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊璋冨矖鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:update')")
+ public CommonResult<Boolean> updateTransferApplication(@Valid @RequestBody HrmTransferApplicationSaveReqVO updateReqVO) {
+ transferApplicationService.updateTransferApplication(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎璋冨矖鐢宠")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:delete')")
+ public CommonResult<Boolean> deleteTransferApplication(@RequestParam("id") Long id) {
+ transferApplicationService.deleteTransferApplication(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱璋冨矖鐢宠璇︽儏")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:query')")
+ public CommonResult<HrmTransferApplicationRespVO> getTransferApplication(@RequestParam("id") Long id) {
+ return success(transferApplicationService.getTransferApplicationDetail(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱璋冨矖鐢宠鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:query')")
+ public CommonResult<PageResult<HrmTransferApplicationRespVO>> getTransferApplicationPage(@Valid HrmTransferApplicationPageReqVO pageReqVO) {
+ return success(transferApplicationService.getTransferApplicationPage(pageReqVO));
+ }
+
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦璋冨矖鐢宠瀹℃壒")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @Parameter(name = "processDefinitionKey", description = "娴佺▼瀹氫箟Key", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:submit')")
+ public CommonResult<Boolean> submitTransferApplication(@RequestParam("id") Long id,
+ @RequestParam("processDefinitionKey") String processDefinitionKey) {
+ AdminUserRespDTO user = adminUserApi.getUser(1L); // TODO: 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛
+ transferApplicationService.submitTransferApplication(id, processDefinitionKey, user.getId());
+ return success(true);
+ }
+
+ @GetMapping("/approve-process-definition-list")
+ @Operation(summary = "鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔夊垪琛�")
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:query')")
+ public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
+ return success(transferApplicationService.getApproveProcessDefinitionList());
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭璋冨矖鐢宠 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:transfer:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportTransferApplicationExcel(@Valid HrmTransferApplicationPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List<HrmTransferApplicationRespVO> list = transferApplicationService.getTransferApplicationPage(pageReqVO).getList();
+ ExcelUtils.write(response, "璋冨矖鐢宠.xls", "鏁版嵁",
+ HrmTransferApplicationRespVO.class, list);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationPageReqVO.java
new file mode 100644
index 0000000..0aa1bd6
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationPageReqVO.java
@@ -0,0 +1,33 @@
+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;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇峰亣鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmLeaveApplicationPageReqVO extends PageParam {
+
+ @Schema(description = "鐢宠鍗曞彿", example = "LA202401001")
+ private String no;
+
+ @Schema(description = "鐢宠浜篒D", example = "1")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "璇峰亣绫诲瀷", example = "1")
+ private Integer leaveType;
+
+ @Schema(description = "瀹℃壒鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private String[] 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/HrmLeaveApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java
new file mode 100644
index 0000000..c69563e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval.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 HrmLeaveApplicationRespVO {
+
+ @Schema(description = "鐢宠ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鐢宠鍗曞彿", example = "LA202401001")
+ 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 = "璇峰亣绫诲瀷", example = "1")
+ private Integer leaveType;
+
+ @Schema(description = "璇峰亣绫诲瀷鍚嶇О", example = "骞村亣")
+ private String leaveTypeName;
+
+ @Schema(description = "寮�濮嬫椂闂�", example = "2024-01-01 09:00:00")
+ private LocalDateTime startTime;
+
+ @Schema(description = "缁撴潫鏃堕棿", example = "2024-01-02 18:00:00")
+ private LocalDateTime endTime;
+
+ @Schema(description = "璇峰亣鏃堕暱锛堝ぉ锛�", example = "1.5")
+ private BigDecimal duration;
+
+ @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 = "https://xxx.com/xxx.pdf")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿", example = "2024-01-01 09:00:00")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java
new file mode 100644
index 0000000..ffc32a1
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java
@@ -0,0 +1,50 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇峰亣鐢宠鏂板/淇敼 Request VO")
+@Data
+public class HrmLeaveApplicationSaveReqVO {
+
+ @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 = "璇峰亣绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "璇峰亣绫诲瀷涓嶈兘涓虹┖")
+ private Integer leaveType;
+
+ @Schema(description = "寮�濮嬫椂闂�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01-01 09:00:00")
+ @NotNull(message = "寮�濮嬫椂闂翠笉鑳戒负绌�")
+ private LocalDateTime startTime;
+
+ @Schema(description = "缁撴潫鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01-02 18:00:00")
+ @NotNull(message = "缁撴潫鏃堕棿涓嶈兘涓虹┖")
+ private LocalDateTime endTime;
+
+ @Schema(description = "璇峰亣鏃堕暱锛堝ぉ锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.5")
+ @NotNull(message = "璇峰亣鏃堕暱涓嶈兘涓虹┖")
+ private BigDecimal duration;
+
+ @Schema(description = "璇峰亣鍘熷洜", requiredMode = Schema.RequiredMode.REQUIRED, example = "涓汉浜嬪姟")
+ @NotEmpty(message = "璇峰亣鍘熷洜涓嶈兘涓虹┖")
+ private String reason;
+
+ @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationPageReqVO.java
new file mode 100644
index 0000000..a0fe8d2
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationPageReqVO.java
@@ -0,0 +1,33 @@
+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;
+
+@Schema(description = "绠$悊鍚庡彴 - 绂昏亴鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmResignationApplicationPageReqVO extends PageParam {
+
+ @Schema(description = "鐢宠鍗曞彿", example = "RA202401001")
+ private String no;
+
+ @Schema(description = "鐢宠浜篒D", example = "1")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "绂昏亴绫诲瀷", example = "1")
+ private Integer resignationType;
+
+ @Schema(description = "瀹℃壒鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private String[] 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/HrmResignationApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java
new file mode 100644
index 0000000..8ee359e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java
@@ -0,0 +1,82 @@
+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 HrmResignationApplicationRespVO {
+
+ @Schema(description = "鐢宠ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鐢宠鍗曞彿", example = "RA202401001")
+ 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 = "绂昏亴绫诲瀷", example = "1")
+ private Integer resignationType;
+
+ @Schema(description = "绂昏亴绫诲瀷鍚嶇О", example = "涓诲姩绂昏亴")
+ private String resignationTypeName;
+
+ @Schema(description = "棰勮绂昏亴鏃ユ湡", example = "2024-02-01")
+ private LocalDate expectedDate;
+
+ @Schema(description = "瀹為檯绂昏亴鏃ユ湡", example = "2024-02-01")
+ private LocalDate actualDate;
+
+ @Schema(description = "绂昏亴鍘熷洜", example = "涓汉鍙戝睍")
+ private String reason;
+
+ @Schema(description = "浜ゆ帴浜篒D", example = "1")
+ private Long handoverUserId;
+
+ @Schema(description = "浜ゆ帴浜哄鍚�", example = "鏉庡洓")
+ private String handoverUserName;
+
+ @Schema(description = "浜ゆ帴鐘舵��", example = "0")
+ private Integer handoverStatus;
+
+ @Schema(description = "浜ゆ帴鐘舵�佸悕绉�", example = "鏈紑濮�")
+ private String handoverStatusName;
+
+ @Schema(description = "浜ゆ帴璇存槑", example = "浜ゆ帴璇存槑")
+ private String handoverRemark;
+
+ @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 = "https://xxx.com/xxx.pdf")
+ private String fileUrl;
+
+ @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/HrmResignationApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java
new file mode 100644
index 0000000..acdafab
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java
@@ -0,0 +1,47 @@
+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 HrmResignationApplicationSaveReqVO {
+
+ @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 = "绂昏亴绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "绂昏亴绫诲瀷涓嶈兘涓虹┖")
+ private Integer resignationType;
+
+ @Schema(description = "棰勮绂昏亴鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-01")
+ @NotNull(message = "棰勮绂昏亴鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate expectedDate;
+
+ @Schema(description = "绂昏亴鍘熷洜", requiredMode = Schema.RequiredMode.REQUIRED, example = "涓汉鍙戝睍")
+ @NotEmpty(message = "绂昏亴鍘熷洜涓嶈兘涓虹┖")
+ private String reason;
+
+ @Schema(description = "浜ゆ帴浜篒D", example = "1")
+ private Long handoverUserId;
+
+ @Schema(description = "浜ゆ帴璇存槑", example = "浜ゆ帴璇存槑")
+ private String handoverRemark;
+
+ @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
+ private String fileUrl;
+
+ @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/approval/vo/HrmTransferApplicationPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationPageReqVO.java
new file mode 100644
index 0000000..049e953
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationPageReqVO.java
@@ -0,0 +1,36 @@
+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;
+
+@Schema(description = "绠$悊鍚庡彴 - 璋冨矖鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmTransferApplicationPageReqVO extends PageParam {
+
+ @Schema(description = "鐢宠鍗曞彿", example = "TA202401001")
+ private String no;
+
+ @Schema(description = "鐢宠浜篒D", example = "1")
+ private Long userId;
+
+ @Schema(description = "鍘熼儴闂↖D", example = "1")
+ private Long currentDeptId;
+
+ @Schema(description = "鐩爣閮ㄩ棬ID", example = "2")
+ private Long targetDeptId;
+
+ @Schema(description = "璋冨矖绫诲瀷", example = "1")
+ private Integer transferType;
+
+ @Schema(description = "瀹℃壒鐘舵��", example = "0")
+ private Integer status;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private String[] 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/HrmTransferApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java
new file mode 100644
index 0000000..1930b30
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java
@@ -0,0 +1,82 @@
+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 HrmTransferApplicationRespVO {
+
+ @Schema(description = "鐢宠ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鐢宠鍗曞彿", example = "TA202401001")
+ private String no;
+
+ @Schema(description = "鐢宠浜篒D", example = "1")
+ private Long userId;
+
+ @Schema(description = "鐢宠浜哄鍚�", example = "寮犱笁")
+ private String userName;
+
+ @Schema(description = "鍘熼儴闂↖D", example = "1")
+ private Long currentDeptId;
+
+ @Schema(description = "鍘熼儴闂ㄥ悕绉�", example = "鐮斿彂閮�")
+ private String currentDeptName;
+
+ @Schema(description = "鍘熷矖浣岻D", example = "1")
+ private Long currentPostId;
+
+ @Schema(description = "鍘熷矖浣嶅悕绉�", example = "Java寮�鍙戝伐绋嬪笀")
+ private String currentPostName;
+
+ @Schema(description = "鐩爣閮ㄩ棬ID", example = "2")
+ private Long targetDeptId;
+
+ @Schema(description = "鐩爣閮ㄩ棬鍚嶇О", example = "浜у搧閮�")
+ private String targetDeptName;
+
+ @Schema(description = "鐩爣宀椾綅ID", example = "2")
+ private Long targetPostId;
+
+ @Schema(description = "鐩爣宀椾綅鍚嶇О", example = "浜у搧缁忕悊")
+ private String targetPostName;
+
+ @Schema(description = "璋冨矖绫诲瀷", example = "1")
+ private Integer transferType;
+
+ @Schema(description = "璋冨矖绫诲瀷鍚嶇О", example = "骞宠皟")
+ private String transferTypeName;
+
+ @Schema(description = "鐢熸晥鏃ユ湡", example = "2024-02-01")
+ private LocalDate effectiveDate;
+
+ @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 = "https://xxx.com/xxx.pdf")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿", example = "2024-01-01 09:00:00")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java
new file mode 100644
index 0000000..5c89ef3
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java
@@ -0,0 +1,52 @@
+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 HrmTransferApplicationSaveReqVO {
+
+ @Schema(description = "鐢宠ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鐢宠浜篒D", example = "1")
+ private Long userId;
+
+ @Schema(description = "鍘熼儴闂↖D", example = "1")
+ private Long currentDeptId;
+
+ @Schema(description = "鍘熷矖浣岻D", example = "1")
+ private Long currentPostId;
+
+ @Schema(description = "鐩爣閮ㄩ棬ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "鐩爣閮ㄩ棬涓嶈兘涓虹┖")
+ private Long targetDeptId;
+
+ @Schema(description = "鐩爣宀椾綅ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "鐩爣宀椾綅涓嶈兘涓虹┖")
+ private Long targetPostId;
+
+ @Schema(description = "璋冨矖绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "璋冨矖绫诲瀷涓嶈兘涓虹┖")
+ private Integer transferType;
+
+ @Schema(description = "鐢熸晥鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-01")
+ @NotNull(message = "鐢熸晥鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate effectiveDate;
+
+ @Schema(description = "璋冨矖鍘熷洜", requiredMode = Schema.RequiredMode.REQUIRED, example = "涓氬姟闇�瑕�")
+ @NotEmpty(message = "璋冨矖鍘熷洜涓嶈兘涓虹┖")
+ private String reason;
+
+ @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java
new file mode 100644
index 0000000..33c9f4a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java
@@ -0,0 +1,154 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance;
+
+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.attendance.vo.HrmAttendanceExceptionPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceExceptionService;
+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 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 java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鑰冨嫟寮傚父鐢宠")
+@RestController
+@RequestMapping("/hrm/attendance/exception")
+@Validated
+public class HrmAttendanceExceptionController {
+
+ @Resource
+ private HrmAttendanceExceptionService attendanceExceptionService;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private DeptApi deptApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鏂板鑰冨嫟寮傚父鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:create')")
+ public CommonResult<Long> createAttendanceException(@Valid @RequestBody HrmAttendanceExceptionSaveReqVO createReqVO) {
+ return success(attendanceExceptionService.createAttendanceException(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鑰冨嫟寮傚父鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:update')")
+ public CommonResult<Boolean> updateAttendanceException(@Valid @RequestBody HrmAttendanceExceptionSaveReqVO updateReqVO) {
+ attendanceExceptionService.updateAttendanceException(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鑰冨嫟寮傚父鐢宠")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:delete')")
+ public CommonResult<Boolean> deleteAttendanceException(@RequestParam("id") Long id) {
+ attendanceExceptionService.deleteAttendanceException(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鑰冨嫟寮傚父鐢宠")
+ @Parameter(name = "id", description = "鐢宠ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<HrmAttendanceExceptionRespVO> getAttendanceException(@RequestParam("id") Long id) {
+ HrmAttendanceExceptionDO attendanceException = attendanceExceptionService.getAttendanceException(id);
+ if (attendanceException == null) {
+ return success(null);
+ }
+ HrmAttendanceExceptionRespVO respVO = BeanUtils.toBean(attendanceException, HrmAttendanceExceptionRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭紙userId 鏄憳宸D锛�
+ if (attendanceException.getUserId() != null) {
+ HrmEmployeeDO employee = employeeService.getEmployee(attendanceException.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ }
+ if (attendanceException.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(attendanceException.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ return success(respVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鑰冨嫟寮傚父鐢宠鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<PageResult<HrmAttendanceExceptionRespVO>> getAttendanceExceptionPage(@Valid HrmAttendanceExceptionPageReqVO pageReqVO) {
+ PageResult<HrmAttendanceExceptionDO> pageResult = attendanceExceptionService.getAttendanceExceptionPage(pageReqVO);
+ List<HrmAttendanceExceptionRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmAttendanceExceptionRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, pageResult.getList());
+ }
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ /**
+ * 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭紙userId 鏄憳宸D锛�
+ */
+ private void fillUserInfo(List<HrmAttendanceExceptionRespVO> respList, List<HrmAttendanceExceptionDO> exceptionList) {
+ // 鎵归噺鑾峰彇鍛樺伐淇℃伅
+ Set<Long> employeeIds = exceptionList.stream()
+ .map(HrmAttendanceExceptionDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
+ for (Long employeeId : employeeIds) {
+ HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+ if (employee != null) {
+ employeeMap.put(employeeId, employee);
+ }
+ }
+ // 鎵归噺鑾峰彇閮ㄩ棬淇℃伅
+ Set<Long> deptIds = exceptionList.stream()
+ .map(HrmAttendanceExceptionDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+
+ // 濉厖淇℃伅
+ for (HrmAttendanceExceptionRespVO respVO : respList) {
+ HrmEmployeeDO employee = employeeMap.get(respVO.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ DeptRespDTO dept = deptMap.get(respVO.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+
+ @PostMapping("/submit")
+ @Operation(summary = "鎻愪氦鑰冨嫟寮傚父鐢宠")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:submit')")
+ public CommonResult<Boolean> submitAttendanceException(
+ @RequestParam("id") Long id,
+ @RequestParam("processDefinitionKey") String processDefinitionKey) {
+ // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID
+ Long userId = cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId();
+ attendanceExceptionService.submitAttendanceException(id, processDefinitionKey, userId);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceHolidayController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceHolidayController.java
new file mode 100644
index 0000000..4c99256
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceHolidayController.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceHolidayPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceHolidayRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceHolidaySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceHolidayDO;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceHolidayService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鑰冨嫟鑺傚亣鏃ラ厤缃�")
+@RestController
+@RequestMapping("/hrm/attendance/holiday")
+@Validated
+public class HrmAttendanceHolidayController {
+
+ @Resource
+ private HrmAttendanceHolidayService attendanceHolidayService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鏂板鑺傚亣鏃ラ厤缃�")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:create')")
+ public CommonResult<Long> createAttendanceHoliday(@Valid @RequestBody HrmAttendanceHolidaySaveReqVO createReqVO) {
+ return success(attendanceHolidayService.createAttendanceHoliday(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鑺傚亣鏃ラ厤缃�")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:update')")
+ public CommonResult<Boolean> updateAttendanceHoliday(@Valid @RequestBody HrmAttendanceHolidaySaveReqVO updateReqVO) {
+ attendanceHolidayService.updateAttendanceHoliday(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鑺傚亣鏃ラ厤缃�")
+ @Parameter(name = "id", description = "鑺傚亣鏃D", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:delete')")
+ public CommonResult<Boolean> deleteAttendanceHoliday(@RequestParam("id") Long id) {
+ attendanceHolidayService.deleteAttendanceHoliday(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鑺傚亣鏃ラ厤缃�")
+ @Parameter(name = "id", description = "鑺傚亣鏃D", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<HrmAttendanceHolidayRespVO> getAttendanceHoliday(@RequestParam("id") Long id) {
+ HrmAttendanceHolidayDO attendanceHoliday = attendanceHolidayService.getAttendanceHoliday(id);
+ return success(BeanUtils.toBean(attendanceHoliday, HrmAttendanceHolidayRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鑺傚亣鏃ラ厤缃垎椤�")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<PageResult<HrmAttendanceHolidayRespVO>> getAttendanceHolidayPage(@Valid HrmAttendanceHolidayPageReqVO pageReqVO) {
+ PageResult<HrmAttendanceHolidayDO> pageResult = attendanceHolidayService.getAttendanceHolidayPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, HrmAttendanceHolidayRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭鑺傚亣鏃ラ厤缃� Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportAttendanceHolidayExcel(@Valid HrmAttendanceHolidayPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List<HrmAttendanceHolidayDO> list = attendanceHolidayService.getAttendanceHolidayPage(pageReqVO).getList();
+ List<HrmAttendanceHolidayRespVO> data = BeanUtils.toBean(list, HrmAttendanceHolidayRespVO.class);
+ ExcelUtils.write(response, "鑺傚亣鏃ラ厤缃�.xls", "鏁版嵁", HrmAttendanceHolidayRespVO.class, data);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
new file mode 100644
index 0000000..e99c45f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
@@ -0,0 +1,198 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceRecordService;
+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 cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.EMPLOYEE_NOT_EXISTS;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鑰冨嫟璁板綍")
+@RestController
+@RequestMapping("/hrm/attendance/record")
+@Validated
+public class HrmAttendanceRecordController {
+
+ @Resource
+ private HrmAttendanceRecordService attendanceRecordService;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/clock-in")
+ @Operation(summary = "涓婄彮鎵撳崱")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:clock')")
+ public CommonResult<HrmAttendanceRecordRespVO> clockIn(@RequestParam("location") String location) {
+ Long loginUserId = getLoginUserId();
+ HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS, "褰撳墠鐢ㄦ埛鏈叧鑱斿憳宸ヤ俊鎭紝鏃犳硶鎵撳崱");
+ }
+ HrmAttendanceRecordDO record = attendanceRecordService.clockIn(employee.getId(), LocalDateTime.now(), location);
+ HrmAttendanceRecordRespVO respVO = BeanUtils.toBean(record, HrmAttendanceRecordRespVO.class);
+ respVO.setUserName(employee.getName());
+ if (record.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(record.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ return success(respVO);
+ }
+
+ @PostMapping("/clock-out")
+ @Operation(summary = "涓嬬彮鎵撳崱")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:clock')")
+ public CommonResult<HrmAttendanceRecordRespVO> clockOut(@RequestParam("location") String location) {
+ Long loginUserId = getLoginUserId();
+ HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS, "褰撳墠鐢ㄦ埛鏈叧鑱斿憳宸ヤ俊鎭紝鏃犳硶鎵撳崱");
+ }
+ HrmAttendanceRecordDO record = attendanceRecordService.clockOut(employee.getId(), LocalDateTime.now(), location);
+ HrmAttendanceRecordRespVO respVO = BeanUtils.toBean(record, HrmAttendanceRecordRespVO.class);
+ respVO.setUserName(employee.getName());
+ if (record.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(record.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ return success(respVO);
+ }
+
+ private Long getLoginUserId() {
+ return cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId();
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鑰冨嫟璁板綍鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<PageResult<HrmAttendanceRecordRespVO>> getAttendanceRecordPage(@Valid HrmAttendanceRecordPageReqVO pageReqVO) {
+ PageResult<HrmAttendanceRecordDO> pageResult = attendanceRecordService.getAttendanceRecordPage(pageReqVO);
+ // 杞崲涓� VO
+ List<HrmAttendanceRecordRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmAttendanceRecordRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, pageResult.getList());
+ }
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ /**
+ * 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ * 娉ㄦ剰锛氳�冨嫟璁板綍鐨� userId 鏄憳宸D锛屼笉鏄郴缁熺敤鎴稩D
+ */
+ private void fillUserInfo(List<HrmAttendanceRecordRespVO> respList, List<HrmAttendanceRecordDO> recordList) {
+ // 鎵归噺鑾峰彇鍛樺伐淇℃伅锛坲serId 鏄憳宸D锛�
+ Set<Long> employeeIds = recordList.stream()
+ .map(HrmAttendanceRecordDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
+ for (Long employeeId : employeeIds) {
+ HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+ if (employee != null) {
+ employeeMap.put(employeeId, employee);
+ }
+ }
+ // 鎵归噺鑾峰彇閮ㄩ棬淇℃伅
+ Set<Long> deptIds = recordList.stream()
+ .map(HrmAttendanceRecordDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+
+ // 濉厖淇℃伅
+ for (HrmAttendanceRecordRespVO respVO : respList) {
+ // 閫氳繃鍛樺伐ID鑾峰彇鍛樺伐淇℃伅锛屽啀鑾峰彇鍛樺伐濮撳悕
+ HrmEmployeeDO employee = employeeMap.get(respVO.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ // 閮ㄩ棬鍚嶇О
+ DeptRespDTO dept = deptMap.get(respVO.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鑰冨嫟璁板綍")
+ @Parameter(name = "id", description = "璁板綍ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<HrmAttendanceRecordRespVO> getAttendanceRecord(@RequestParam("id") Long id) {
+ HrmAttendanceRecordDO attendanceRecord = attendanceRecordService.getAttendanceRecord(id);
+ if (attendanceRecord == null) {
+ return success(null);
+ }
+ HrmAttendanceRecordRespVO respVO = BeanUtils.toBean(attendanceRecord, HrmAttendanceRecordRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭紙userId 鏄憳宸D锛�
+ if (attendanceRecord.getUserId() != null) {
+ HrmEmployeeDO employee = employeeService.getEmployee(attendanceRecord.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ }
+ if (attendanceRecord.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(attendanceRecord.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ return success(respVO);
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭鑰冨嫟璁板綍 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportAttendanceRecordExcel(@Valid HrmAttendanceRecordPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ PageResult<HrmAttendanceRecordDO> pageResult = attendanceRecordService.getAttendanceRecordPage(pageReqVO);
+ List<HrmAttendanceRecordDO> recordList = pageResult.getList();
+ // 杞崲涓� VO
+ List<HrmAttendanceRecordRespVO> list = BeanUtils.toBean(recordList, HrmAttendanceRecordRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, recordList);
+ }
+ ExcelUtils.write(response, "鑰冨嫟璁板綍.xls", "鏁版嵁", HrmAttendanceRecordRespVO.class, list);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRuleController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRuleController.java
new file mode 100644
index 0000000..61c3d9d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRuleController.java
@@ -0,0 +1,132 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRulePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceRuleService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鑰冨嫟瑙勫垯閰嶇疆")
+@RestController
+@RequestMapping("/hrm/attendance/rule")
+@Validated
+public class HrmAttendanceRuleController {
+
+ @Resource
+ private HrmAttendanceRuleService attendanceRuleService;
+ @Resource
+ private DeptApi deptApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鏂板鑰冨嫟瑙勫垯")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:create')")
+ public CommonResult<Long> createAttendanceRule(@Valid @RequestBody HrmAttendanceRuleSaveReqVO createReqVO) {
+ return success(attendanceRuleService.createAttendanceRule(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鑰冨嫟瑙勫垯")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:update')")
+ public CommonResult<Boolean> updateAttendanceRule(@Valid @RequestBody HrmAttendanceRuleSaveReqVO updateReqVO) {
+ attendanceRuleService.updateAttendanceRule(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鑰冨嫟瑙勫垯")
+ @Parameter(name = "id", description = "瑙勫垯ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:delete')")
+ public CommonResult<Boolean> deleteAttendanceRule(@RequestParam("id") Long id) {
+ attendanceRuleService.deleteAttendanceRule(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鑰冨嫟瑙勫垯")
+ @Parameter(name = "id", description = "瑙勫垯ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<HrmAttendanceRuleRespVO> getAttendanceRule(@RequestParam("id") Long id) {
+ HrmAttendanceRuleDO attendanceRule = attendanceRuleService.getAttendanceRule(id);
+ if (attendanceRule == null) {
+ return success(null);
+ }
+ HrmAttendanceRuleRespVO respVO = BeanUtils.toBean(attendanceRule, HrmAttendanceRuleRespVO.class);
+ // 濉厖閮ㄩ棬鍚嶇О
+ if (attendanceRule.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(attendanceRule.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ return success(respVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鑰冨嫟瑙勫垯鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<PageResult<HrmAttendanceRuleRespVO>> getAttendanceRulePage(@Valid HrmAttendanceRulePageReqVO pageReqVO) {
+ PageResult<HrmAttendanceRuleDO> pageResult = attendanceRuleService.getAttendanceRulePage(pageReqVO);
+ List<HrmAttendanceRuleRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmAttendanceRuleRespVO.class);
+ // 濉厖閮ㄩ棬鍚嶇О
+ if (CollUtil.isNotEmpty(list)) {
+ Set<Long> deptIds = pageResult.getList().stream()
+ .map(HrmAttendanceRuleDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+ for (HrmAttendanceRuleRespVO respVO : list) {
+ DeptRespDTO dept = deptMap.get(respVO.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰緱鑰冨嫟瑙勫垯鍒楄〃")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<List<HrmAttendanceRuleRespVO>> getAttendanceRuleList() {
+ List<HrmAttendanceRuleDO> list = attendanceRuleService.getAttendanceRuleList();
+ return success(BeanUtils.toBean(list, HrmAttendanceRuleRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭鑰冨嫟瑙勫垯 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportAttendanceRuleExcel(@Valid HrmAttendanceRulePageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ PageResult<HrmAttendanceRuleDO> pageResult = attendanceRuleService.getAttendanceRulePage(pageReqVO);
+ List<HrmAttendanceRuleRespVO> data = BeanUtils.toBean(pageResult.getList(), HrmAttendanceRuleRespVO.class);
+ ExcelUtils.write(response, "鑰冨嫟瑙勫垯.xls", "鏁版嵁", HrmAttendanceRuleRespVO.class, data);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceSummaryController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceSummaryController.java
new file mode 100644
index 0000000..a684c8a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceSummaryController.java
@@ -0,0 +1,155 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceSummaryRespVO;
+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.service.attendance.HrmAttendanceSummaryService;
+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 io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鑰冨嫟缁熻姹囨��")
+@RestController
+@RequestMapping("/hrm/attendance/summary")
+@Validated
+public class HrmAttendanceSummaryController {
+
+ @Resource
+ private HrmAttendanceSummaryService attendanceSummaryService;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private DeptApi deptApi;
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鑰冨嫟缁熻姹囨�诲垎椤�")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<PageResult<HrmAttendanceSummaryRespVO>> getAttendanceSummaryPage(@Valid HrmAttendanceSummaryPageReqVO pageReqVO) {
+ PageResult<HrmAttendanceSummaryDO> pageResult = attendanceSummaryService.getAttendanceSummaryPage(pageReqVO);
+ List<HrmAttendanceSummaryRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmAttendanceSummaryRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, pageResult.getList());
+ }
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ /**
+ * 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ */
+ private void fillUserInfo(List<HrmAttendanceSummaryRespVO> respList, List<HrmAttendanceSummaryDO> summaryList) {
+ // 鎵归噺鑾峰彇鍛樺伐淇℃伅
+ Set<Long> employeeIds = summaryList.stream()
+ .map(HrmAttendanceSummaryDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
+ for (Long employeeId : employeeIds) {
+ HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+ if (employee != null) {
+ employeeMap.put(employeeId, employee);
+ }
+ }
+ // 鎵归噺鑾峰彇閮ㄩ棬淇℃伅
+ Set<Long> deptIds = summaryList.stream()
+ .map(HrmAttendanceSummaryDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+
+ // 濉厖淇℃伅
+ for (HrmAttendanceSummaryRespVO respVO : respList) {
+ HrmEmployeeDO employee = employeeMap.get(respVO.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ DeptRespDTO dept = deptMap.get(respVO.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鍛樺伐鏈堝害鑰冨嫟姹囨��")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
+ public CommonResult<HrmAttendanceSummaryRespVO> getMonthlySummary(
+ @RequestParam("userId") Long userId,
+ @RequestParam("yearMonth") String yearMonth) {
+ HrmAttendanceSummaryDO summary = attendanceSummaryService.getMonthlySummary(userId, yearMonth);
+ if (summary == null) {
+ return success(null);
+ }
+ HrmAttendanceSummaryRespVO respVO = BeanUtils.toBean(summary, HrmAttendanceSummaryRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ if (summary.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(summary.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ return success(respVO);
+ }
+
+ @PostMapping("/generate")
+ @Operation(summary = "鐢熸垚鏈堝害鑰冨嫟姹囨��")
+ @Parameter(name = "yearMonth", description = "骞存湀锛屾牸寮忥細yyyy-MM", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:calculate')")
+ public CommonResult<Boolean> generateMonthlySummary(@RequestParam("yearMonth") String yearMonth,
+ @RequestParam(value = "deptId", required = false) Long deptId,
+ @RequestParam(value = "userId", required = false) Long userId) {
+ if (userId != null) {
+ attendanceSummaryService.generateMonthlySummary(userId, yearMonth);
+ } else if (deptId != null) {
+ attendanceSummaryService.generateDeptMonthlySummary(deptId, yearMonth);
+ } else {
+ attendanceSummaryService.generateAllMonthlySummary(yearMonth);
+ }
+ return success(true);
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭鑰冨嫟缁熻姹囨�� Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:attendance:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportAttendanceSummaryExcel(@Valid HrmAttendanceSummaryPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ PageResult<HrmAttendanceSummaryDO> pageResult = attendanceSummaryService.getAttendanceSummaryPage(pageReqVO);
+ List<HrmAttendanceSummaryRespVO> data = BeanUtils.toBean(pageResult.getList(), HrmAttendanceSummaryRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(data)) {
+ fillUserInfo(data, pageResult.getList());
+ }
+ ExcelUtils.write(response, "鑰冨嫟缁熻姹囨��.xls", "鏁版嵁", HrmAttendanceSummaryRespVO.class, data);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionPageReqVO.java
new file mode 100644
index 0000000..db5db32
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionPageReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.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 HrmAttendanceExceptionPageReqVO extends PageParam {
+
+ @Schema(description = "鐢宠鍗曞彿", example = "KQ202401010001")
+ private String no;
+
+ @Schema(description = "鐢宠浜篒D", example = "1")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "寮傚父绫诲瀷", example = "1")
+ private Integer type;
+
+ @Schema(description = "鐘舵��", 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/attendance/vo/HrmAttendanceExceptionRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java
new file mode 100644
index 0000000..61f903d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟寮傚父鐢宠 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmAttendanceExceptionRespVO {
+
+ @Schema(description = "鐢宠ID")
+ @ExcelProperty("鐢宠ID")
+ private Long id;
+
+ @Schema(description = "鐢宠鍗曞彿")
+ @ExcelProperty("鐢宠鍗曞彿")
+ private String no;
+
+ @Schema(description = "鐢宠浜篒D")
+ private Long userId;
+
+ @Schema(description = "鐢宠浜哄鍚�")
+ @ExcelProperty("鐢宠浜哄鍚�")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬ID")
+ private Long deptId;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "寮傚父绫诲瀷")
+ @ExcelProperty(value = "寮傚父绫诲瀷", converter = DictConvert.class)
+ @DictFormat("hrm_attendance_exception_type")
+ private Integer type;
+
+ @Schema(description = "寮傚父鏃ユ湡")
+ @ExcelProperty("寮傚父鏃ユ湡")
+ private LocalDate exceptionDate;
+
+ @Schema(description = "寮傚父鏃堕棿鐐�")
+ @ExcelProperty("寮傚父鏃堕棿鐐�")
+ private LocalDateTime exceptionTime;
+
+ @Schema(description = "鐢宠鍘熷洜")
+ @ExcelProperty("鐢宠鍘熷洜")
+ private String reason;
+
+ @Schema(description = "鐘舵��")
+ @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+ @DictFormat("hrm_audit_status")
+ private Integer status;
+
+ @Schema(description = "BPM娴佺▼瀹炰緥ID")
+ private String processInstanceId;
+
+ @Schema(description = "瀹℃壒瀹屾垚鏃堕棿")
+ @ExcelProperty("瀹℃壒瀹屾垚鏃堕棿")
+ private LocalDateTime approveTime;
+
+ @Schema(description = "闄勪欢鍦板潃")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/attendance/vo/HrmAttendanceExceptionSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java
new file mode 100644
index 0000000..7bc5dda
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.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;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟寮傚父鐢宠鏂板/淇敼 Request VO")
+@Data
+public class HrmAttendanceExceptionSaveReqVO {
+
+ @Schema(description = "鐢宠ID", example = "1")
+ private Long id;
+
+ @Schema(description = "寮傚父绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "寮傚父绫诲瀷涓嶈兘涓虹┖")
+ private Integer type;
+
+ @Schema(description = "寮傚父鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01-15")
+ @NotNull(message = "寮傚父鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate exceptionDate;
+
+ @Schema(description = "寮傚父鏃堕棿鐐�", example = "2024-01-15 09:00:00")
+ private LocalDateTime exceptionTime;
+
+ @Schema(description = "鐢宠鍘熷洜", requiredMode = Schema.RequiredMode.REQUIRED, example = "蹇樿鎵撳崱")
+ @NotEmpty(message = "鐢宠鍘熷洜涓嶈兘涓虹┖")
+ private String reason;
+
+ @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.jpg")
+ private String fileUrl;
+
+ @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/attendance/vo/HrmAttendanceHolidayPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayPageReqVO.java
new file mode 100644
index 0000000..c4a43f3
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayPageReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟鑺傚亣鏃ュ垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmAttendanceHolidayPageReqVO extends PageParam {
+
+ @Schema(description = "鑺傚亣鏃ュ悕绉�", example = "鏄ヨ妭")
+ private String name;
+
+ @Schema(description = "绫诲瀷锛�1-浼戞伅鏃ワ紝2-宸ヤ綔鏃�", example = "1")
+ private Integer type;
+
+ @Schema(description = "寮�濮嬫棩鏈�", example = "2024-01-01")
+ private LocalDate startDate;
+
+ @Schema(description = "缁撴潫鏃ユ湡", example = "2024-12-31")
+ private LocalDate endDate;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java
new file mode 100644
index 0000000..9a53176
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟鑺傚亣鏃� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmAttendanceHolidayRespVO {
+
+ @Schema(description = "鑺傚亣鏃D")
+ @ExcelProperty("鑺傚亣鏃D")
+ private Long id;
+
+ @Schema(description = "鑺傚亣鏃ュ悕绉�")
+ @ExcelProperty("鑺傚亣鏃ュ悕绉�")
+ private String name;
+
+ @Schema(description = "鏃ユ湡")
+ @ExcelProperty("鏃ユ湡")
+ private LocalDate date;
+
+ @Schema(description = "绫诲瀷锛�1-浼戞伅鏃ワ紝2-宸ヤ綔鏃�")
+ @ExcelProperty(value = "绫诲瀷", converter = DictConvert.class)
+ @DictFormat("hrm_holiday_type")
+ private Integer type;
+
+ @Schema(description = "鏄惁娉曞畾鑺傚亣鏃ワ細0-鍚︼紝1-鏄�")
+ @ExcelProperty(value = "鏄惁娉曞畾鑺傚亣鏃�", converter = DictConvert.class)
+ @DictFormat("hrm_is_legal_holiday")
+ private Integer isLegal;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/attendance/vo/HrmAttendanceHolidaySaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java
new file mode 100644
index 0000000..8f2fcb6
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟鑺傚亣鏃ユ柊澧�/淇敼 Request VO")
+@Data
+public class HrmAttendanceHolidaySaveReqVO {
+
+ @Schema(description = "鑺傚亣鏃D锛堜慨鏀规椂蹇呭~锛�", example = "1")
+ private Long id;
+
+ @Schema(description = "鑺傚亣鏃ュ悕绉�", requiredMode = Schema.RequiredMode.REQUIRED, example = "鏄ヨ妭")
+ @NotBlank(message = "鑺傚亣鏃ュ悕绉颁笉鑳戒负绌�")
+ private String name;
+
+ @Schema(description = "鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-10")
+ @NotNull(message = "鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate date;
+
+ @Schema(description = "绫诲瀷锛�1-浼戞伅鏃ワ紝2-宸ヤ綔鏃�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "绫诲瀷涓嶈兘涓虹┖")
+ private Integer type;
+
+ @Schema(description = "鏄惁娉曞畾鑺傚亣鏃ワ細0-鍚︼紝1-鏄�", example = "1")
+ private Integer isLegal;
+
+ @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/attendance/vo/HrmAttendanceRecordPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordPageReqVO.java
new file mode 100644
index 0000000..68078d2
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordPageReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟璁板綍鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmAttendanceRecordPageReqVO extends PageParam {
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "寮�濮嬫棩鏈�", example = "2024-01-01")
+ private LocalDate startDate;
+
+ @Schema(description = "缁撴潫鏃ユ湡", example = "2024-01-31")
+ private LocalDate endDate;
+
+ @Schema(description = "涓婄彮鎵撳崱绫诲瀷", example = "1")
+ private Integer clockInType;
+
+ @Schema(description = "涓嬬彮鎵撳崱绫诲瀷", example = "1")
+ private Integer clockOutType;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java
new file mode 100644
index 0000000..67215a6
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟璁板綍 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmAttendanceRecordRespVO {
+
+ @Schema(description = "璁板綍ID")
+ @ExcelProperty("璁板綍ID")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long userId;
+
+ @Schema(description = "鍛樺伐濮撳悕")
+ @ExcelProperty("鍛樺伐濮撳悕")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬ID")
+ private Long deptId;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "鑰冨嫟鏃ユ湡")
+ @ExcelProperty("鑰冨嫟鏃ユ湡")
+ private LocalDate date;
+
+ @Schema(description = "涓婄彮鎵撳崱鏃堕棿")
+ @ExcelProperty("涓婄彮鎵撳崱鏃堕棿")
+ private LocalDateTime clockInTime;
+
+ @Schema(description = "涓嬬彮鎵撳崱鏃堕棿")
+ @ExcelProperty("涓嬬彮鎵撳崱鏃堕棿")
+ private LocalDateTime clockOutTime;
+
+ @Schema(description = "涓婄彮鎵撳崱绫诲瀷")
+ @ExcelProperty(value = "涓婄彮鎵撳崱绫诲瀷", converter = DictConvert.class)
+ @DictFormat("hrm_clock_type")
+ private Integer clockInType;
+
+ @Schema(description = "涓嬬彮鎵撳崱绫诲瀷")
+ @ExcelProperty(value = "涓嬬彮鎵撳崱绫诲瀷", converter = DictConvert.class)
+ @DictFormat("hrm_clock_type")
+ private Integer clockOutType;
+
+ @Schema(description = "宸ヤ綔鏃堕暱锛堝皬鏃讹級")
+ @ExcelProperty("宸ヤ綔鏃堕暱")
+ private BigDecimal workHours;
+
+ @Schema(description = "鍔犵彮鏃堕暱锛堝皬鏃讹級")
+ @ExcelProperty("鍔犵彮鏃堕暱")
+ private BigDecimal overtimeHours;
+
+ @Schema(description = "鎵撳崱鍦扮偣")
+ @ExcelProperty("鎵撳崱鍦扮偣")
+ private String location;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鏁版嵁鏉ユ簮")
+ @ExcelProperty(value = "鏁版嵁鏉ユ簮", converter = DictConvert.class)
+ @DictFormat("hrm_data_source")
+ private Integer dataSource;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/attendance/vo/HrmAttendanceRulePageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRulePageReqVO.java
new file mode 100644
index 0000000..1080fb2
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRulePageReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟瑙勫垯鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmAttendanceRulePageReqVO extends PageParam {
+
+ @Schema(description = "瑙勫垯鍚嶇О", example = "鏍囧噯鑰冨嫟瑙勫垯")
+ private String name;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @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/attendance/vo/HrmAttendanceRuleRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleRespVO.java
new file mode 100644
index 0000000..360c315
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleRespVO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟瑙勫垯 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmAttendanceRuleRespVO {
+
+ @Schema(description = "瑙勫垯ID")
+ @ExcelProperty("瑙勫垯ID")
+ private Long id;
+
+ @Schema(description = "瑙勫垯鍚嶇О")
+ @ExcelProperty("瑙勫垯鍚嶇О")
+ private String name;
+
+ @Schema(description = "閫傜敤閮ㄩ棬ID")
+ private Long deptId;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "涓婄彮鏃堕棿")
+ @ExcelProperty("涓婄彮鏃堕棿")
+ private LocalTime workStartTime;
+
+ @Schema(description = "涓嬬彮鏃堕棿")
+ @ExcelProperty("涓嬬彮鏃堕棿")
+ private LocalTime workEndTime;
+
+ @Schema(description = "杩熷埌鍏佽鍒嗛挓鏁�")
+ @ExcelProperty("杩熷埌鍏佽鍒嗛挓鏁�")
+ private Integer lateAllowMinutes;
+
+ @Schema(description = "鏃╅��鍏佽鍒嗛挓鏁�")
+ @ExcelProperty("鏃╅��鍏佽鍒嗛挓鏁�")
+ private Integer earlyAllowMinutes;
+
+ @Schema(description = "鍔犵彮璧风畻鏃堕暱锛堝皬鏃讹級")
+ @ExcelProperty("鍔犵彮璧风畻鏃堕暱")
+ private BigDecimal overtimeThreshold;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤")
+ @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+ @DictFormat("hrm_rule_status")
+ private Integer status;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/attendance/vo/HrmAttendanceRuleSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleSaveReqVO.java
new file mode 100644
index 0000000..8c244a8
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleSaveReqVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.time.LocalTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟瑙勫垯鏂板/淇敼 Request VO")
+@Data
+public class HrmAttendanceRuleSaveReqVO {
+
+ @Schema(description = "瑙勫垯ID锛堜慨鏀规椂蹇呭~锛�", example = "1")
+ private Long id;
+
+ @Schema(description = "瑙勫垯鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鏍囧噯鑰冨嫟瑙勫垯")
+ @NotBlank(message = "瑙勫垯鍚嶇О涓嶈兘涓虹┖")
+ private String name;
+
+ @Schema(description = "閫傜敤閮ㄩ棬ID锛堢┖琛ㄧず鍏ㄥ眬锛�", example = "1")
+ private Long deptId;
+
+ @Schema(description = "涓婄彮鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED, example = "09:00:00")
+ @NotNull(message = "涓婄彮鏃堕棿涓嶈兘涓虹┖")
+ private LocalTime workStartTime;
+
+ @Schema(description = "涓嬬彮鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED, example = "18:00:00")
+ @NotNull(message = "涓嬬彮鏃堕棿涓嶈兘涓虹┖")
+ private LocalTime workEndTime;
+
+ @Schema(description = "杩熷埌鍏佽鍒嗛挓鏁�", example = "10")
+ private Integer lateAllowMinutes;
+
+ @Schema(description = "鏃╅��鍏佽鍒嗛挓鏁�", example = "10")
+ private Integer earlyAllowMinutes;
+
+ @Schema(description = "鍔犵彮璧风畻鏃堕暱锛堝皬鏃讹級", example = "1")
+ private BigDecimal overtimeThreshold;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ 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/attendance/vo/HrmAttendanceSummaryPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java
new file mode 100644
index 0000000..2218ef9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.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 HrmAttendanceSummaryPageReqVO extends PageParam {
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "骞存湀锛屾牸寮忥細yyyy-MM", example = "2024-01")
+ private String yearMonth;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryRespVO.java
new file mode 100644
index 0000000..dd421f8
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryRespVO.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟缁熻姹囨�� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmAttendanceSummaryRespVO {
+
+ @Schema(description = "姹囨�籌D")
+ @ExcelProperty("姹囨�籌D")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long userId;
+
+ @Schema(description = "鍛樺伐濮撳悕")
+ @ExcelProperty("鍛樺伐濮撳悕")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬ID")
+ private Long deptId;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "骞存湀")
+ @ExcelProperty("骞存湀")
+ private String yearMonth;
+
+ @Schema(description = "搴斿嚭鍕ゅぉ鏁�")
+ @ExcelProperty("搴斿嚭鍕ゅぉ鏁�")
+ private Integer workDays;
+
+ @Schema(description = "瀹為檯鍑哄嫟澶╂暟")
+ @ExcelProperty("瀹為檯鍑哄嫟澶╂暟")
+ private Integer actualDays;
+
+ @Schema(description = "杩熷埌娆℃暟")
+ @ExcelProperty("杩熷埌娆℃暟")
+ private Integer lateCount;
+
+ @Schema(description = "鏃╅��娆℃暟")
+ @ExcelProperty("鏃╅��娆℃暟")
+ private Integer earlyCount;
+
+ @Schema(description = "缂哄崱娆℃暟")
+ @ExcelProperty("缂哄崱娆℃暟")
+ private Integer absentCount;
+
+ @Schema(description = "鍔犵彮鏃堕暱锛堝皬鏃讹級")
+ @ExcelProperty("鍔犵彮鏃堕暱")
+ private BigDecimal overtimeHours;
+
+ @Schema(description = "璇峰亣鏃堕暱锛堝皬鏃讹級")
+ @ExcelProperty("璇峰亣鏃堕暱")
+ private BigDecimal leaveHours;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/employee/HrmEmployeeController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
new file mode 100644
index 0000000..827ca30
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
@@ -0,0 +1,187 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeRespVO;
+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.service.employee.HrmEmployeeService;
+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.permission.RoleApi;
+import cn.iocoder.yudao.module.system.api.permission.dto.RoleRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍛樺伐")
+@RestController
+@RequestMapping("/hrm/employee")
+@Validated
+public class HrmEmployeeController {
+
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private PostApi postApi;
+ @Resource
+ private RoleApi roleApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓鍛樺伐")
+ @PreAuthorize("@ss.hasPermission('hrm:employee:create')")
+ public CommonResult<Long> createEmployee(@Valid @RequestBody HrmEmployeeSaveReqVO createReqVO) {
+ return success(employeeService.createEmployee(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鍛樺伐")
+ @PreAuthorize("@ss.hasPermission('hrm:employee:update')")
+ public CommonResult<Boolean> updateEmployee(@Valid @RequestBody HrmEmployeeSaveReqVO updateReqVO) {
+ employeeService.updateEmployee(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鍛樺伐")
+ @Parameter(name = "id", description = "鍛樺伐ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:delete')")
+ public CommonResult<Boolean> deleteEmployee(@RequestParam("id") Long id) {
+ employeeService.deleteEmployee(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鍛樺伐璇︽儏")
+ @Parameter(name = "id", description = "鍛樺伐ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:query')")
+ public CommonResult<HrmEmployeeRespVO> getEmployee(@RequestParam("id") Long id) {
+ return success(employeeService.getEmployeeDetail(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鍛樺伐鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:employee:query')")
+ public CommonResult<PageResult<HrmEmployeeRespVO>> getEmployeePage(@Valid HrmEmployeePageReqVO pageReqVO) {
+ PageResult<HrmEmployeeDO> pageResult = employeeService.getEmployeePage(pageReqVO);
+ // 杞崲涓� VO
+ List<HrmEmployeeRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmEmployeeRespVO.class);
+ // 濉厖閮ㄩ棬銆佸矖浣嶃�佽鑹插悕绉�
+ if (CollUtil.isNotEmpty(list)) {
+ fillEmployeeInfo(list, pageResult.getList());
+ }
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ /**
+ * 濉厖鍛樺伐淇℃伅锛堥儴闂ㄣ�佸矖浣嶃�佽鑹插悕绉帮級
+ */
+ private void fillEmployeeInfo(List<HrmEmployeeRespVO> respList, List<HrmEmployeeDO> employeeList) {
+ // 1. 鎵归噺鑾峰彇閮ㄩ棬淇℃伅
+ Set<Long> deptIds = employeeList.stream()
+ .map(HrmEmployeeDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+
+ // 2. 鎵归噺鑾峰彇宀椾綅淇℃伅
+ Set<Long> postIds = employeeList.stream()
+ .map(HrmEmployeeDO::getPostId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, PostRespDTO> postMap = CollUtil.isEmpty(postIds) ? Collections.emptyMap() : postApi.getPostMap(postIds);
+
+ // 3. 鏀堕泦鎵�鏈夎鑹睮D
+ Set<Long> allRoleIds = new HashSet<>();
+ for (HrmEmployeeDO employee : employeeList) {
+ if (StrUtil.isNotBlank(employee.getRoleIds())) {
+ List<Long> roleIds = JSONUtil.toList(employee.getRoleIds(), Long.class);
+ allRoleIds.addAll(roleIds);
+ }
+ }
+ Map<Long, RoleRespDTO> roleMap = CollUtil.isEmpty(allRoleIds) ? Collections.emptyMap() :
+ roleApi.getRoleList(allRoleIds).stream()
+ .collect(Collectors.toMap(RoleRespDTO::getId, r -> r));
+
+ // 4. 濉厖淇℃伅
+ for (int i = 0; i < respList.size(); i++) {
+ HrmEmployeeRespVO respVO = respList.get(i);
+ HrmEmployeeDO employee = employeeList.get(i);
+
+ // 濉厖閮ㄩ棬鍚嶇О
+ if (employee.getDeptId() != null && deptMap.containsKey(employee.getDeptId())) {
+ respVO.setDeptName(deptMap.get(employee.getDeptId()).getName());
+ }
+
+ // 濉厖宀椾綅鍚嶇О
+ if (employee.getPostId() != null && postMap.containsKey(employee.getPostId())) {
+ respVO.setPostName(postMap.get(employee.getPostId()).getName());
+ }
+
+ // 瑙f瀽 roleIds 骞跺~鍏呰鑹插悕绉�
+ if (StrUtil.isNotBlank(employee.getRoleIds())) {
+ List<Long> roleIds = JSONUtil.toList(employee.getRoleIds(), Long.class);
+ respVO.setRoleIds(roleIds);
+ if (CollUtil.isNotEmpty(roleIds)) {
+ List<String> roleNames = roleIds.stream()
+ .filter(roleMap::containsKey)
+ .map(id -> roleMap.get(id).getName())
+ .collect(Collectors.toList());
+ respVO.setRoleNames(String.join(",", roleNames));
+ }
+ }
+ }
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "鑾峰緱鍛樺伐绮剧畝鍒楄〃", description = "鐢ㄤ簬涓嬫媺閫夋嫨")
+ public CommonResult<List<HrmEmployeeSimpleRespVO>> getEmployeeSimpleList(
+ @RequestParam(value = "employeeStatus", required = false) Integer employeeStatus) {
+ return success(employeeService.getEmployeeSimpleList(employeeStatus));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭鍛樺伐 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:employee:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportEmployeeExcel(@Valid HrmEmployeePageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ PageResult<HrmEmployeeDO> pageResult = employeeService.getEmployeePage(pageReqVO);
+ List<HrmEmployeeDO> employeeList = pageResult.getList();
+ // 杞崲涓� VO
+ List<HrmEmployeeRespVO> list = BeanUtils.toBean(employeeList, HrmEmployeeRespVO.class);
+ // 濉厖閮ㄩ棬銆佸矖浣嶃�佽鑹插悕绉�
+ if (CollUtil.isNotEmpty(list)) {
+ fillEmployeeInfo(list, employeeList);
+ }
+ ExcelUtils.write(response, "鍛樺伐淇℃伅.xls", "鏁版嵁", HrmEmployeeRespVO.class, list);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEducationController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEducationController.java
new file mode 100644
index 0000000..e00253b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEducationController.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee;
+
+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.employee.vo.HrmEmployeeEducationRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEducationDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeEducationService;
+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 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/employee/education")
+@Validated
+public class HrmEmployeeEducationController {
+
+ @Resource
+ private HrmEmployeeEducationService educationService;
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰彇鏁欒偛缁忓巻鍒楄〃")
+ @Parameter(name = "employeeId", description = "鍛樺伐ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:query')")
+ public CommonResult<List<HrmEmployeeEducationRespVO>> getEducationList(
+ @RequestParam("employeeId") Long employeeId) {
+ List<HrmEmployeeEducationDO> list = educationService.getEducationListByEmployeeId(employeeId);
+ return success(BeanUtils.toBean(list, HrmEmployeeEducationRespVO.class));
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鏁欒偛缁忓巻")
+ @Parameter(name = "id", description = "鏁欒偛缁忓巻ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:delete')")
+ public CommonResult<Boolean> deleteEducation(@RequestParam("id") Long id) {
+ educationService.deleteEducation(id);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEmergencyContactController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEmergencyContactController.java
new file mode 100644
index 0000000..d651db0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEmergencyContactController.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee;
+
+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.employee.vo.HrmEmployeeEmergencyContactRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEmergencyContactDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeEmergencyContactService;
+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 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/employee/emergency-contact")
+@Validated
+public class HrmEmployeeEmergencyContactController {
+
+ @Resource
+ private HrmEmployeeEmergencyContactService emergencyContactService;
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰彇绱ф�ヨ仈绯讳汉鍒楄〃")
+ @Parameter(name = "employeeId", description = "鍛樺伐ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:query')")
+ public CommonResult<List<HrmEmployeeEmergencyContactRespVO>> getEmergencyContactList(
+ @RequestParam("employeeId") Long employeeId) {
+ List<HrmEmployeeEmergencyContactDO> list = emergencyContactService.getEmergencyContactListByEmployeeId(employeeId);
+ return success(BeanUtils.toBean(list, HrmEmployeeEmergencyContactRespVO.class));
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎绱ф�ヨ仈绯讳汉")
+ @Parameter(name = "id", description = "绱ф�ヨ仈绯讳汉ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:delete')")
+ public CommonResult<Boolean> deleteEmergencyContact(@RequestParam("id") Long id) {
+ emergencyContactService.deleteEmergencyContact(id);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeWorkHistoryController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeWorkHistoryController.java
new file mode 100644
index 0000000..ee3adff
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeWorkHistoryController.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee;
+
+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.employee.vo.HrmEmployeeWorkHistoryRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeWorkHistoryDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeWorkHistoryService;
+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 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/employee/work-history")
+@Validated
+public class HrmEmployeeWorkHistoryController {
+
+ @Resource
+ private HrmEmployeeWorkHistoryService workHistoryService;
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰彇宸ヤ綔缁忓巻鍒楄〃")
+ @Parameter(name = "employeeId", description = "鍛樺伐ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:query')")
+ public CommonResult<List<HrmEmployeeWorkHistoryRespVO>> getWorkHistoryList(
+ @RequestParam("employeeId") Long employeeId) {
+ List<HrmEmployeeWorkHistoryDO> list = workHistoryService.getWorkHistoryListByEmployeeId(employeeId);
+ return success(BeanUtils.toBean(list, HrmEmployeeWorkHistoryRespVO.class));
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎宸ヤ綔缁忓巻")
+ @Parameter(name = "id", description = "宸ヤ綔缁忓巻ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:delete')")
+ public CommonResult<Boolean> deleteWorkHistory(@RequestParam("id") Long id) {
+ workHistoryService.deleteWorkHistory(id);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationRespVO.java
new file mode 100644
index 0000000..58dbd1d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationRespVO.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐鏁欒偛缁忓巻 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmEmployeeEducationRespVO {
+
+ @Schema(description = "璁板綍ID")
+ @ExcelProperty("璁板綍ID")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long employeeId;
+
+ @Schema(description = "瀛︽牎鍚嶇О")
+ @ExcelProperty("瀛︽牎鍚嶇О")
+ private String schoolName;
+
+ @Schema(description = "涓撲笟")
+ @ExcelProperty("涓撲笟")
+ private String major;
+
+ @Schema(description = "瀛﹀巻")
+ @ExcelProperty(value = "瀛﹀巻", converter = DictConvert.class)
+ @DictFormat("hrm_degree")
+ private Integer degree;
+
+ @Schema(description = "鍏ュ鏃堕棿")
+ @ExcelProperty("鍏ュ鏃堕棿")
+ private LocalDate startDate;
+
+ @Schema(description = "姣曚笟鏃堕棿")
+ @ExcelProperty("姣曚笟鏃堕棿")
+ private LocalDate endDate;
+
+ @Schema(description = "璇佷功缂栧彿")
+ @ExcelProperty("璇佷功缂栧彿")
+ private String certificateNo;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/employee/vo/HrmEmployeeEducationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationSaveReqVO.java
new file mode 100644
index 0000000..f5b1cd4
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationSaveReqVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.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 HrmEmployeeEducationSaveReqVO {
+
+ @Schema(description = "璁板綍ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long employeeId;
+
+ @Schema(description = "瀛︽牎鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍗椾含澶у")
+ @NotEmpty(message = "瀛︽牎鍚嶇О涓嶈兘涓虹┖")
+ private String schoolName;
+
+ @Schema(description = "涓撲笟", example = "璁$畻鏈虹瀛︿笌鎶�鏈�")
+ private String major;
+
+ @Schema(description = "瀛﹀巻", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ @NotNull(message = "瀛﹀巻涓嶈兘涓虹┖")
+ private Integer degree;
+
+ @Schema(description = "鍏ュ鏃堕棿", example = "2010-09-01")
+ private LocalDate startDate;
+
+ @Schema(description = "姣曚笟鏃堕棿", example = "2014-06-30")
+ private LocalDate endDate;
+
+ @Schema(description = "璇佷功缂栧彿", example = "10284201412345678")
+ private String certificateNo;
+
+ @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/employee/vo/HrmEmployeeEmergencyContactRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactRespVO.java
new file mode 100644
index 0000000..b40d29d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactRespVO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐绱ф�ヨ仈绯讳汉 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmEmployeeEmergencyContactRespVO {
+
+ @Schema(description = "璁板綍ID")
+ @ExcelProperty("璁板綍ID")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long employeeId;
+
+ @Schema(description = "鑱旂郴浜哄鍚�")
+ @ExcelProperty("鑱旂郴浜哄鍚�")
+ private String name;
+
+ @Schema(description = "涓庡憳宸ュ叧绯�")
+ @ExcelProperty("涓庡憳宸ュ叧绯�")
+ private String relationship;
+
+ @Schema(description = "鑱旂郴鐢佃瘽")
+ @ExcelProperty("鑱旂郴鐢佃瘽")
+ private String phone;
+
+ @Schema(description = "鑱旂郴鍦板潃")
+ @ExcelProperty("鑱旂郴鍦板潃")
+ private String address;
+
+ @Schema(description = "鎺掑簭")
+ @ExcelProperty("鎺掑簭")
+ private Integer sort;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/employee/vo/HrmEmployeeEmergencyContactSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactSaveReqVO.java
new file mode 100644
index 0000000..400c469
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactSaveReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐绱ф�ヨ仈绯讳汉鏂板/淇敼 Request VO")
+@Data
+public class HrmEmployeeEmergencyContactSaveReqVO {
+
+ @Schema(description = "璁板綍ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long employeeId;
+
+ @Schema(description = "鑱旂郴浜哄鍚�", requiredMode = Schema.RequiredMode.REQUIRED, example = "鐜嬩簲")
+ @NotEmpty(message = "鑱旂郴浜哄鍚嶄笉鑳戒负绌�")
+ private String name;
+
+ @Schema(description = "涓庡憳宸ュ叧绯�", requiredMode = Schema.RequiredMode.REQUIRED, example = "鐖朵翰")
+ @NotEmpty(message = "涓庡憳宸ュ叧绯讳笉鑳戒负绌�")
+ private String relationship;
+
+ @Schema(description = "鑱旂郴鐢佃瘽", requiredMode = Schema.RequiredMode.REQUIRED, example = "13700137000")
+ @NotEmpty(message = "鑱旂郴鐢佃瘽涓嶈兘涓虹┖")
+ private String phone;
+
+ @Schema(description = "鑱旂郴鍦板潃", example = "姹熻嫃鐪佸崡浜競")
+ private String address;
+
+ @Schema(description = "鎺掑簭", example = "1")
+ private Integer sort;
+
+ @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/employee/vo/HrmEmployeePageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java
new file mode 100644
index 0000000..391bea8
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.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 HrmEmployeePageReqVO extends PageParam {
+
+ @Schema(description = "鍛樺伐缂栧彿", example = "EMP001")
+ private String employeeNo;
+
+ @Schema(description = "鍛樺伐濮撳悕", example = "寮犱笁")
+ private String name;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "鍛樺伐鐘舵��", example = "1")
+ private Integer employeeStatus;
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..29d9d9f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
@@ -0,0 +1,126 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import 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 = "绠$悊鍚庡彴 - 鍛樺伐 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmEmployeeRespVO {
+
+ @Schema(description = "鍛樺伐ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("鍛樺伐ID")
+ private Long id;
+
+ @Schema(description = "鍛樺伐缂栧彿")
+ @ExcelProperty("鍛樺伐缂栧彿")
+ private String employeeNo;
+
+ @Schema(description = "閮ㄩ棬ID")
+ private Long deptId;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "宀椾綅ID")
+ private Long postId;
+
+ @Schema(description = "宀椾綅鍚嶇О")
+ @ExcelProperty("宀椾綅鍚嶇О")
+ private String postName;
+
+ @Schema(description = "鍛樺伐濮撳悕")
+ @ExcelProperty("鍛樺伐濮撳悕")
+ private String name;
+
+ @Schema(description = "鎬у埆")
+ @ExcelProperty(value = "鎬у埆", converter = DictConvert.class)
+ @DictFormat("system_user_sex")
+ private Integer gender;
+
+ @Schema(description = "鍑虹敓鏃ユ湡")
+ @ExcelProperty("鍑虹敓鏃ユ湡")
+ private LocalDate birthday;
+
+ @Schema(description = "鎵嬫満鍙风爜")
+ @ExcelProperty("鎵嬫満鍙风爜")
+ private String phone;
+
+ @Schema(description = "閭")
+ @ExcelProperty("閭")
+ private String email;
+
+ @Schema(description = "韬唤璇佸彿")
+ @ExcelProperty("韬唤璇佸彿")
+ private String idCard;
+
+ @Schema(description = "鍏ヨ亴鏃ユ湡")
+ @ExcelProperty("鍏ヨ亴鏃ユ湡")
+ private LocalDate hireDate;
+
+ @Schema(description = "杞鏃ユ湡")
+ @ExcelProperty("杞鏃ユ湡")
+ private LocalDate regularDate;
+
+ @Schema(description = "绂昏亴鏃ユ湡")
+ @ExcelProperty("绂昏亴鏃ユ湡")
+ private LocalDate leaveDate;
+
+ @Schema(description = "鍛樺伐鐘舵��")
+ @ExcelProperty(value = "鍛樺伐鐘舵��", converter = DictConvert.class)
+ @DictFormat("hrm_employee_status")
+ private Integer employeeStatus;
+
+ @Schema(description = "骞村亣浣欓锛堝ぉ锛�")
+ @ExcelProperty("骞村亣浣欓")
+ private BigDecimal annualLeaveBalance;
+
+ @Schema(description = "鐥呭亣浣欓锛堝ぉ锛�")
+ @ExcelProperty("鐥呭亣浣欓")
+ private BigDecimal sickLeaveBalance;
+
+ @Schema(description = "钖叕缁撴瀯ID")
+ private Long salaryStructureId;
+
+ @Schema(description = "鍩烘湰宸ヨ祫")
+ @ExcelProperty("鍩烘湰宸ヨ祫")
+ private BigDecimal baseSalary;
+
+ @Schema(description = "瑙掕壊ID鍒楄〃")
+ private List<Long> roleIds;
+
+ @Schema(description = "瑙掕壊鍚嶇О鍒楄〃")
+ @ExcelProperty("瑙掕壊")
+ private String roleNames;
+
+ @Schema(description = "寮�鎴烽摱琛�")
+ @ExcelProperty("寮�鎴烽摱琛�")
+ private String bankName;
+
+ @Schema(description = "閾惰璐﹀彿")
+ @ExcelProperty("閾惰璐﹀彿")
+ private String bankAccount;
+
+ @Schema(description = "灞呬綇鍦板潃")
+ @ExcelProperty("灞呬綇鍦板潃")
+ private String address;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/employee/vo/HrmEmployeeSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
new file mode 100644
index 0000000..243900d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐鏂板/淇敼 Request VO")
+@Data
+public class HrmEmployeeSaveReqVO {
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long id;
+
+ @Schema(description = "閮ㄩ棬ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "閮ㄩ棬涓嶈兘涓虹┖")
+ private Long deptId;
+
+ @Schema(description = "宀椾綅ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "宀椾綅涓嶈兘涓虹┖")
+ private Long postId;
+
+ @Schema(description = "鍛樺伐濮撳悕", requiredMode = Schema.RequiredMode.REQUIRED, example = "寮犱笁")
+ @NotEmpty(message = "鍛樺伐濮撳悕涓嶈兘涓虹┖")
+ private String name;
+
+ @Schema(description = "鎬у埆", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "鎬у埆涓嶈兘涓虹┖")
+ private Integer gender;
+
+ @Schema(description = "鍑虹敓鏃ユ湡", example = "1990-01-15")
+ private LocalDate birthday;
+
+ @Schema(description = "鎵嬫満鍙风爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000")
+ @NotEmpty(message = "鎵嬫満鍙风爜涓嶈兘涓虹┖")
+ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "鎵嬫満鍙风爜鏍煎紡涓嶆纭�")
+ private String phone;
+
+ @Schema(description = "閭", example = "zhangsan@example.com")
+ @Email(message = "閭鏍煎紡涓嶆纭�")
+ private String email;
+
+ @Schema(description = "韬唤璇佸彿", example = "320102199001150011")
+ @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$",
+ message = "韬唤璇佸彿鏍煎紡涓嶆纭�")
+ private String idCard;
+
+ @Schema(description = "鍏ヨ亴鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2020-03-01")
+ @NotNull(message = "鍏ヨ亴鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate hireDate;
+
+ @Schema(description = "杞鏃ユ湡", example = "2020-06-01")
+ private LocalDate regularDate;
+
+ @Schema(description = "鍛樺伐鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "鍛樺伐鐘舵�佷笉鑳戒负绌�")
+ private Integer employeeStatus;
+
+ @Schema(description = "骞村亣浣欓锛堝ぉ锛�", example = "5")
+ private BigDecimal annualLeaveBalance;
+
+ @Schema(description = "鐥呭亣浣欓锛堝ぉ锛�", example = "10")
+ private BigDecimal sickLeaveBalance;
+
+ @Schema(description = "钖叕缁撴瀯ID", example = "1")
+ private Long salaryStructureId;
+
+ @Schema(description = "鍩烘湰宸ヨ祫", example = "10000.00")
+ private BigDecimal baseSalary;
+
+ @Schema(description = "瑙掕壊ID鍒楄〃", example = "[1, 2]")
+ private List<Long> roleIds;
+
+ @Schema(description = "寮�鎴烽摱琛�", example = "涓浗宸ュ晢閾惰")
+ private String bankName;
+
+ @Schema(description = "閾惰璐﹀彿", example = "6222021234567890123")
+ private String bankAccount;
+
+ @Schema(description = "灞呬綇鍦板潃", example = "姹熻嫃鐪佸崡浜競")
+ private String address;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+ // ========== 瀛愯〃鏁版嵁 ==========
+
+ @Schema(description = "鏁欒偛缁忓巻鍒楄〃")
+ @Valid
+ private List<HrmEmployeeEducationSaveReqVO> educationList;
+
+ @Schema(description = "宸ヤ綔缁忓巻鍒楄〃")
+ @Valid
+ private List<HrmEmployeeWorkHistorySaveReqVO> workHistoryList;
+
+ @Schema(description = "绱ф�ヨ仈绯讳汉鍒楄〃")
+ @Valid
+ private List<HrmEmployeeEmergencyContactSaveReqVO> emergencyContactList;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSimpleRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSimpleRespVO.java
new file mode 100644
index 0000000..2bf625f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSimpleRespVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐绠�鍗曚俊鎭� Response VO")
+@Data
+public class HrmEmployeeSimpleRespVO {
+
+ @Schema(description = "鍛樺伐ID")
+ private Long id;
+
+ @Schema(description = "鍛樺伐缂栧彿")
+ private String employeeNo;
+
+ @Schema(description = "鍛樺伐濮撳悕")
+ private String name;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "鍛樺伐鐘舵��")
+ private Integer employeeStatus;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistoryRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistoryRespVO.java
new file mode 100644
index 0000000..6a16105
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistoryRespVO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐宸ヤ綔缁忓巻 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmEmployeeWorkHistoryRespVO {
+
+ @Schema(description = "璁板綍ID")
+ @ExcelProperty("璁板綍ID")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long employeeId;
+
+ @Schema(description = "鍏徃鍚嶇О")
+ @ExcelProperty("鍏徃鍚嶇О")
+ private String companyName;
+
+ @Schema(description = "鑱屼綅")
+ @ExcelProperty("鑱屼綅")
+ private String position;
+
+ @Schema(description = "鍏ヨ亴鏃堕棿")
+ @ExcelProperty("鍏ヨ亴鏃堕棿")
+ private LocalDate startDate;
+
+ @Schema(description = "绂昏亴鏃堕棿")
+ @ExcelProperty("绂昏亴鏃堕棿")
+ private LocalDate endDate;
+
+ @Schema(description = "宸ヤ綔鍐呭")
+ @ExcelProperty("宸ヤ綔鍐呭")
+ private String workContent;
+
+ @Schema(description = "绂昏亴鍘熷洜")
+ @ExcelProperty("绂昏亴鍘熷洜")
+ private String leaveReason;
+
+ @Schema(description = "璇佹槑浜哄鍚�")
+ @ExcelProperty("璇佹槑浜哄鍚�")
+ private String referenceName;
+
+ @Schema(description = "璇佹槑浜虹數璇�")
+ @ExcelProperty("璇佹槑浜虹數璇�")
+ private String referencePhone;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/employee/vo/HrmEmployeeWorkHistorySaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistorySaveReqVO.java
new file mode 100644
index 0000000..b622589
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistorySaveReqVO.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.employee.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 HrmEmployeeWorkHistorySaveReqVO {
+
+ @Schema(description = "璁板綍ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long employeeId;
+
+ @Schema(description = "鍏徃鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX绉戞妧鏈夐檺鍏徃")
+ @NotEmpty(message = "鍏徃鍚嶇О涓嶈兘涓虹┖")
+ private String companyName;
+
+ @Schema(description = "鑱屼綅", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java寮�鍙戝伐绋嬪笀")
+ @NotEmpty(message = "鑱屼綅涓嶈兘涓虹┖")
+ private String position;
+
+ @Schema(description = "鍏ヨ亴鏃堕棿", example = "2015-03-01")
+ private LocalDate startDate;
+
+ @Schema(description = "绂昏亴鏃堕棿", example = "2020-02-28")
+ private LocalDate endDate;
+
+ @Schema(description = "宸ヤ綔鍐呭", example = "璐熻矗鍚庣绯荤粺寮�鍙�")
+ private String workContent;
+
+ @Schema(description = "绂昏亴鍘熷洜", example = "涓汉鍙戝睍")
+ private String leaveReason;
+
+ @Schema(description = "璇佹槑浜哄鍚�", example = "鏉庡洓")
+ private String referenceName;
+
+ @Schema(description = "璇佹槑浜虹數璇�", example = "13900139000")
+ private String referencePhone;
+
+ @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/HrmEmployeeSocialSecurityController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java
new file mode 100644
index 0000000..db54d53
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java
@@ -0,0 +1,220 @@
+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.HrmEmployeeSocialSecurityPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmEmployeeSocialSecurityRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmEmployeeSocialSecuritySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmEmployeeSocialSecurityService;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSocialSecuritySchemeService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+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 java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍛樺伐绀句繚鍏Н閲戞。妗�")
+@RestController
+@RequestMapping("/hrm/salary/employee-social-security")
+@Validated
+public class HrmEmployeeSocialSecurityController {
+
+ @Resource
+ private HrmEmployeeSocialSecurityService employeeSocialSecurityService;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private HrmSocialSecuritySchemeService schemeService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓鍛樺伐绀句繚鍏Н閲戞。妗�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:create')")
+ public CommonResult<Long> createEmployeeSocialSecurity(@Valid @RequestBody HrmEmployeeSocialSecuritySaveReqVO createReqVO) {
+ HrmEmployeeSocialSecurityDO socialSecurity = BeanUtils.toBean(createReqVO, HrmEmployeeSocialSecurityDO.class);
+ return success(employeeSocialSecurityService.createEmployeeSocialSecurity(socialSecurity));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鍛樺伐绀句繚鍏Н閲戞。妗�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:update')")
+ public CommonResult<Boolean> updateEmployeeSocialSecurity(@Valid @RequestBody HrmEmployeeSocialSecuritySaveReqVO updateReqVO) {
+ HrmEmployeeSocialSecurityDO socialSecurity = BeanUtils.toBean(updateReqVO, HrmEmployeeSocialSecurityDO.class);
+ employeeSocialSecurityService.updateEmployeeSocialSecurity(socialSecurity);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鍛樺伐绀句繚鍏Н閲戞。妗�")
+ @Parameter(name = "id", description = "妗fID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:delete')")
+ public CommonResult<Boolean> deleteEmployeeSocialSecurity(@RequestParam("id") Long id) {
+ employeeSocialSecurityService.deleteEmployeeSocialSecurity(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗�")
+ @Parameter(name = "id", description = "妗fID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<HrmEmployeeSocialSecurityRespVO> getEmployeeSocialSecurity(@RequestParam("id") Long id) {
+ HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityService.getEmployeeSocialSecurity(id);
+ if (socialSecurity == null) {
+ return success(null);
+ }
+ HrmEmployeeSocialSecurityRespVO respVO = BeanUtils.toBean(socialSecurity, HrmEmployeeSocialSecurityRespVO.class);
+ // 濉厖鍛樺伐鍜屾柟妗堜俊鎭�
+ fillEmployeeInfo(respVO, socialSecurity.getUserId());
+ fillSchemeInfo(respVO, socialSecurity.getSocialSecuritySchemeId());
+ return success(respVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堝垎椤�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<PageResult<HrmEmployeeSocialSecurityRespVO>> getEmployeeSocialSecurityPage(
+ @Valid HrmEmployeeSocialSecurityPageReqVO pageReqVO) {
+ PageResult<HrmEmployeeSocialSecurityDO> pageResult = employeeSocialSecurityService.getEmployeeSocialSecurityPage(pageReqVO);
+ List<HrmEmployeeSocialSecurityDO> recordList = pageResult.getList();
+ if (CollUtil.isEmpty(recordList)) {
+ return success(new PageResult<>(Collections.emptyList(), pageResult.getTotal()));
+ }
+ // userId 鏄憳宸D锛屾壒閲忚幏鍙栧憳宸ヤ俊鎭�
+ Set<Long> employeeIds = recordList.stream()
+ .map(HrmEmployeeSocialSecurityDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
+ Set<Long> deptIds = new HashSet<>();
+ for (Long employeeId : employeeIds) {
+ HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+ if (employee != null) {
+ employeeMap.put(employeeId, employee);
+ if (employee.getDeptId() != null) {
+ deptIds.add(employee.getDeptId());
+ }
+ }
+ }
+ // 鎵归噺鑾峰彇閮ㄩ棬淇℃伅
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+ // 鎵归噺鑾峰彇鏂规淇℃伅
+ Set<Long> schemeIds = recordList.stream()
+ .map(HrmEmployeeSocialSecurityDO::getSocialSecuritySchemeId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, HrmSocialSecuritySchemeDO> schemeMap = new HashMap<>();
+ for (Long schemeId : schemeIds) {
+ HrmSocialSecuritySchemeDO scheme = schemeService.getScheme(schemeId);
+ if (scheme != null) {
+ schemeMap.put(schemeId, scheme);
+ }
+ }
+ // 杞崲骞跺~鍏呬俊鎭�
+ List<HrmEmployeeSocialSecurityRespVO> list = recordList.stream()
+ .map(socialSecurity -> {
+ HrmEmployeeSocialSecurityRespVO respVO = BeanUtils.toBean(socialSecurity, HrmEmployeeSocialSecurityRespVO.class);
+ // 濉厖鍛樺伐濮撳悕鍜岄儴闂ㄥ悕绉帮紙userId 鏄憳宸D锛�
+ HrmEmployeeDO employee = employeeMap.get(socialSecurity.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ if (employee.getDeptId() != null) {
+ DeptRespDTO dept = deptMap.get(employee.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+ // 濉厖鏂规鍚嶇О
+ if (socialSecurity.getSocialSecuritySchemeId() != null) {
+ HrmSocialSecuritySchemeDO scheme = schemeMap.get(socialSecurity.getSocialSecuritySchemeId());
+ if (scheme != null) {
+ respVO.setSchemeName(scheme.getName());
+ }
+ }
+ return respVO;
+ })
+ .toList();
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ @GetMapping("/list-by-user")
+ @Operation(summary = "鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堝巻鍙插垪琛�")
+ @Parameter(name = "userId", description = "绯荤粺鐢ㄦ埛ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<List<HrmEmployeeSocialSecurityRespVO>> getEmployeeSocialSecurityList(
+ @RequestParam("userId") Long userId) {
+ List<HrmEmployeeSocialSecurityDO> list = employeeSocialSecurityService.getEmployeeSocialSecurityList(userId);
+ List<HrmEmployeeSocialSecurityRespVO> result = list.stream()
+ .map(socialSecurity -> {
+ HrmEmployeeSocialSecurityRespVO respVO = BeanUtils.toBean(socialSecurity, HrmEmployeeSocialSecurityRespVO.class);
+ fillSchemeInfo(respVO, socialSecurity.getSocialSecuritySchemeId());
+ return respVO;
+ })
+ .toList();
+ return success(result);
+ }
+
+ @GetMapping("/get-latest")
+ @Operation(summary = "鑾峰彇鍛樺伐鏈�鏂扮敓鏁堢殑绀句繚鍏Н閲戞。妗�")
+ @Parameter(name = "userId", description = "绯荤粺鐢ㄦ埛ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<HrmEmployeeSocialSecurityRespVO> getLatestEmployeeSocialSecurity(
+ @RequestParam("userId") Long userId) {
+ HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityService.getLatestEmployeeSocialSecurity(userId);
+ if (socialSecurity == null) {
+ return success(null);
+ }
+ HrmEmployeeSocialSecurityRespVO respVO = BeanUtils.toBean(socialSecurity, HrmEmployeeSocialSecurityRespVO.class);
+ // 濉厖鍛樺伐鍜屾柟妗堜俊鎭�
+ fillEmployeeInfo(respVO, socialSecurity.getUserId());
+ fillSchemeInfo(respVO, socialSecurity.getSocialSecuritySchemeId());
+ return success(respVO);
+ }
+
+ /**
+ * 濉厖鍛樺伐淇℃伅锛堥�氳繃鍛樺伐ID锛�
+ */
+ private void fillEmployeeInfo(HrmEmployeeSocialSecurityRespVO respVO, Long userId) {
+ // userId 鏄憳宸D锛岄�氳繃鍛樺伐琛ㄨ幏鍙栧鍚嶅拰閮ㄩ棬
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ if (employee.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(employee.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+ }
+
+ /**
+ * 濉厖鏂规淇℃伅
+ */
+ private void fillSchemeInfo(HrmEmployeeSocialSecurityRespVO respVO, Long schemeId) {
+ if (schemeId != null) {
+ HrmSocialSecuritySchemeDO scheme = schemeService.getScheme(schemeId);
+ if (scheme != null) {
+ respVO.setSchemeName(scheme.getName());
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java
new file mode 100644
index 0000000..bc1898b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+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.salary.vo.HrmLeaveTypeConfigRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmLeaveTypeConfigSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmLeaveTypeConfigService;
+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 璇峰亣绫诲瀷鎵f閰嶇疆")
+@RestController
+@RequestMapping("/hrm/leave-type-config")
+@Validated
+public class HrmLeaveTypeConfigController {
+
+ @Resource
+ private HrmLeaveTypeConfigService leaveTypeConfigService;
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰彇鎵�鏈夎鍋囩被鍨嬮厤缃垪琛�")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<List<HrmLeaveTypeConfigRespVO>> getLeaveTypeConfigList() {
+ List<HrmLeaveTypeConfigDO> list = leaveTypeConfigService.getLeaveTypeConfigList();
+ return success(BeanUtils.toBean(list, HrmLeaveTypeConfigRespVO.class));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰彇璇峰亣绫诲瀷閰嶇疆")
+ @Parameter(name = "leaveType", description = "璇峰亣绫诲瀷", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<HrmLeaveTypeConfigRespVO> getLeaveTypeConfig(@RequestParam("leaveType") Integer leaveType) {
+ HrmLeaveTypeConfigDO config = leaveTypeConfigService.getLeaveTypeConfig(leaveType);
+ return success(BeanUtils.toBean(config, HrmLeaveTypeConfigRespVO.class));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊璇峰亣绫诲瀷閰嶇疆")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:update')")
+ public CommonResult<Boolean> updateLeaveTypeConfig(@Valid @RequestBody HrmLeaveTypeConfigSaveReqVO updateReqVO) {
+ HrmLeaveTypeConfigDO config = BeanUtils.toBean(updateReqVO, HrmLeaveTypeConfigDO.class);
+ leaveTypeConfigService.updateLeaveTypeConfig(config);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java
new file mode 100644
index 0000000..b90e2bc
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java
@@ -0,0 +1,218 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryCalculationDO;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryCalculationService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 钖叕鏍哥畻")
+@RestController
+@RequestMapping("/hrm/salary/calculation")
+@Validated
+public class HrmSalaryCalculationController {
+
+ @Resource
+ private HrmSalaryCalculationService salaryCalculationService;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private DeptApi deptApi;
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱钖叕鏍哥畻鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<PageResult<HrmSalaryCalculationRespVO>> getSalaryCalculationPage(@Valid HrmSalaryCalculationPageReqVO pageReqVO) {
+ PageResult<HrmSalaryCalculationDO> pageResult = salaryCalculationService.getSalaryCalculationPage(pageReqVO);
+ // 杞崲涓� VO
+ List<HrmSalaryCalculationRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmSalaryCalculationRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, pageResult.getList());
+ }
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ /**
+ * 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭紙userId 鏄憳宸D锛�
+ */
+ private void fillUserInfo(List<HrmSalaryCalculationRespVO> respList, List<HrmSalaryCalculationDO> calculationList) {
+ // userId 鏄憳宸D锛屾壒閲忚幏鍙栧憳宸ヤ俊鎭�
+ Set<Long> employeeIds = calculationList.stream()
+ .map(HrmSalaryCalculationDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
+ for (Long employeeId : employeeIds) {
+ HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+ if (employee != null) {
+ employeeMap.put(employeeId, employee);
+ }
+ }
+
+ // 鎵归噺鑾峰彇閮ㄩ棬淇℃伅
+ Set<Long> deptIds = calculationList.stream()
+ .map(HrmSalaryCalculationDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+
+ // 濉厖淇℃伅
+ for (HrmSalaryCalculationRespVO respVO : respList) {
+ HrmEmployeeDO employee = employeeMap.get(respVO.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ DeptRespDTO dept = deptMap.get(respVO.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱钖叕鏍哥畻")
+ @Parameter(name = "id", description = "鏍哥畻ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<HrmSalaryCalculationRespVO> getSalaryCalculation(@RequestParam("id") Long id) {
+ HrmSalaryCalculationDO salaryCalculation = salaryCalculationService.getSalaryCalculation(id);
+ if (salaryCalculation == null) {
+ return success(null);
+ }
+ HrmSalaryCalculationRespVO respVO = BeanUtils.toBean(salaryCalculation, HrmSalaryCalculationRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭紙userId 鏄憳宸D锛�
+ if (salaryCalculation.getUserId() != null) {
+ HrmEmployeeDO employee = employeeService.getEmployee(salaryCalculation.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ }
+ if (salaryCalculation.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(salaryCalculation.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ return success(respVO);
+ }
+
+ @PostMapping("/calculate")
+ @Operation(summary = "鎵ц钖叕鏍哥畻")
+ @Parameter(name = "period", description = "鏍哥畻鍛ㄦ湡", required = true, example = "2024-01")
+ @Parameter(name = "deptId", description = "閮ㄩ棬ID锛屼负绌哄垯鏍哥畻鎵�鏈夐儴闂�", example = "1")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:calculate')")
+ public CommonResult<Boolean> calculateSalary(@RequestParam("period") String period,
+ @RequestParam(value = "deptId", required = false) Long deptId) {
+ salaryCalculationService.calculateSalary(period, deptId);
+ return success(true);
+ }
+
+ @PostMapping("/confirm")
+ @Operation(summary = "鎵归噺纭钖叕鏍哥畻")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:confirm')")
+ public CommonResult<Boolean> confirmSalaryCalculation(@RequestBody Map<String, List<Long>> request) {
+ List<Long> ids = request.get("ids");
+ salaryCalculationService.confirmSalaryCalculation(ids);
+ return success(true);
+ }
+
+ @PostMapping("/revoke")
+ @Operation(summary = "鎵归噺鎾ら攢钖叕鏍哥畻纭")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:confirm')")
+ public CommonResult<Boolean> revokeSalaryCalculation(@RequestBody Map<String, List<Long>> request) {
+ List<Long> ids = request.get("ids");
+ salaryCalculationService.revokeSalaryCalculation(ids);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎钖叕鏍哥畻璁板綍")
+ @Parameter(name = "id", description = "鏍哥畻ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:salary:delete')")
+ public CommonResult<Boolean> deleteSalaryCalculation(@RequestParam("id") Long id) {
+ salaryCalculationService.deleteSalaryCalculation(id);
+ return success(true);
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭钖叕鏍哥畻 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportSalaryCalculationExcel(@Valid HrmSalaryCalculationPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ PageResult<HrmSalaryCalculationDO> pageResult = salaryCalculationService.getSalaryCalculationPage(pageReqVO);
+ List<HrmSalaryCalculationDO> calculationList = pageResult.getList();
+ // 杞崲涓� VO
+ List<HrmSalaryCalculationRespVO> list = BeanUtils.toBean(calculationList, HrmSalaryCalculationRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, calculationList);
+ }
+ ExcelUtils.write(response, "钖叕鏍哥畻.xls", "鏁版嵁", HrmSalaryCalculationRespVO.class, list);
+ }
+
+ @GetMapping("/preview")
+ @Operation(summary = "棰勮閮ㄩ棬涓嬫墍鏈夊憳宸ョ殑钖祫鏍哥畻淇℃伅")
+ @Parameter(name = "period", description = "鏍哥畻鍛ㄦ湡", required = true, example = "2024-01")
+ @Parameter(name = "deptId", description = "閮ㄩ棬ID锛屼负绌哄垯鏌ヨ鍏ㄩ儴", example = "1")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<List<HrmSalaryCalculationRespVO>> previewSalaryCalculation(
+ @RequestParam("period") String period,
+ @RequestParam(value = "deptId", required = false) Long deptId) {
+ // 鏌ヨ宸茶绠楀ソ鐨勮柂閰牳绠楄褰�
+ List<HrmSalaryCalculationDO> list = salaryCalculationService.previewSalaryCalculation(period, deptId);
+ // 杞崲涓� VO
+ List<HrmSalaryCalculationRespVO> respList = BeanUtils.toBean(list, HrmSalaryCalculationRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(respList)) {
+ fillUserInfo(respList, list);
+ }
+ return success(respList);
+ }
+
+ @PostMapping("/save")
+ @Operation(summary = "鎵归噺淇濆瓨钖叕鏍哥畻璁板綍")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:calculate')")
+ public CommonResult<Boolean> saveSalaryCalculation(@Valid @RequestBody HrmSalaryCalculationSaveReqVO saveReqVO) {
+ // 杞崲涓� DO 鍒楄〃
+ List<HrmSalaryCalculationDO> calculationList = saveReqVO.getList().stream()
+ .map(item -> {
+ HrmSalaryCalculationDO calculation = BeanUtils.toBean(item, HrmSalaryCalculationDO.class);
+ calculation.setPeriod(saveReqVO.getPeriod());
+ return calculation;
+ })
+ .collect(Collectors.toList());
+ // 鎵归噺淇濆瓨
+ salaryCalculationService.saveSalaryCalculationList(calculationList);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java
new file mode 100644
index 0000000..817f4f0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java
@@ -0,0 +1,169 @@
+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.HrmSalaryPaymentDetailPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryPaymentDetailRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryPaymentPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryPaymentRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+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.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryPaymentService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+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 java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 钖祫鍙戞斁")
+@RestController
+@RequestMapping("/hrm/salary/payment")
+@Validated
+public class HrmSalaryPaymentController {
+
+ @Resource
+ private HrmSalaryPaymentService salaryPaymentService;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private DeptApi deptApi;
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱钖祫鍙戞斁鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<PageResult<HrmSalaryPaymentRespVO>> getSalaryPaymentPage(@Valid HrmSalaryPaymentPageReqVO pageReqVO) {
+ PageResult<HrmSalaryPaymentDO> pageResult = salaryPaymentService.getSalaryPaymentPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, HrmSalaryPaymentRespVO.class));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱钖祫鍙戞斁")
+ @Parameter(name = "id", description = "鍙戞斁ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<HrmSalaryPaymentRespVO> getSalaryPayment(@RequestParam("id") Long id) {
+ HrmSalaryPaymentDO salaryPayment = salaryPaymentService.getSalaryPayment(id);
+ return success(BeanUtils.toBean(salaryPayment, HrmSalaryPaymentRespVO.class));
+ }
+
+ @PostMapping("/generate")
+ @Operation(summary = "鐢熸垚钖祫鍙戞斁鍙拌处")
+ @Parameter(name = "period", description = "鏍哥畻鍛ㄦ湡", required = true, example = "2024-01")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:generate')")
+ public CommonResult<Long> generatePayment(@RequestParam("period") String period) {
+ return success(salaryPaymentService.generatePayment(period));
+ }
+
+ @PostMapping("/pay")
+ @Operation(summary = "鍙戞斁钖祫锛堟暣涓彴璐︼級")
+ @Parameter(name = "id", description = "鍙戞斁鍙拌处ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:salary:pay')")
+ public CommonResult<Boolean> paySalary(@RequestParam("id") Long id) {
+ salaryPaymentService.paySalary(id);
+ return success(true);
+ }
+
+ @PostMapping("/pay-detail")
+ @Operation(summary = "鎵归噺鍙戞斁钖祫鏄庣粏")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:pay')")
+ public CommonResult<Boolean> paySalaryDetail(@RequestBody Map<String, List<Long>> request) {
+ List<Long> detailIds = request.get("detailIds");
+ salaryPaymentService.paySalaryDetail(detailIds);
+ return success(true);
+ }
+
+ @PostMapping("/revoke-detail")
+ @Operation(summary = "鎵归噺鎾ら攢钖祫鍙戞斁鏄庣粏")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:pay')")
+ public CommonResult<Boolean> revokeSalaryPaymentDetail(@RequestBody Map<String, List<Long>> request) {
+ List<Long> detailIds = request.get("detailIds");
+ salaryPaymentService.revokeSalaryPaymentDetail(detailIds);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎钖祫鍙戞斁鍙拌处")
+ @Parameter(name = "id", description = "鍙戞斁鍙拌处ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:salary:delete')")
+ public CommonResult<Boolean> deleteSalaryPayment(@RequestParam("id") Long id) {
+ salaryPaymentService.deleteSalaryPayment(id);
+ return success(true);
+ }
+
+ @GetMapping("/detail/page")
+ @Operation(summary = "鑾峰緱钖祫鍙戞斁鏄庣粏鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<PageResult<HrmSalaryPaymentDetailRespVO>> getSalaryPaymentDetailPage(@Valid HrmSalaryPaymentDetailPageReqVO pageReqVO) {
+ PageResult<HrmSalaryPaymentDetailDO> pageResult = salaryPaymentService.getSalaryPaymentDetailPage(pageReqVO);
+ List<HrmSalaryPaymentDetailRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmSalaryPaymentDetailRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, pageResult.getList());
+ }
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ @GetMapping("/detail/list")
+ @Operation(summary = "鑾峰緱钖祫鍙戞斁鏄庣粏鍒楄〃")
+ @Parameter(name = "paymentId", description = "鍙戞斁鍙拌处ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+ public CommonResult<List<HrmSalaryPaymentDetailRespVO>> getSalaryPaymentDetailList(@RequestParam("paymentId") Long paymentId) {
+ List<HrmSalaryPaymentDetailDO> detailList = salaryPaymentService.getSalaryPaymentDetailListByPaymentId(paymentId);
+ List<HrmSalaryPaymentDetailRespVO> list = BeanUtils.toBean(detailList, HrmSalaryPaymentDetailRespVO.class);
+ // 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ if (CollUtil.isNotEmpty(list)) {
+ fillUserInfo(list, detailList);
+ }
+ return success(list);
+ }
+
+ /**
+ * 濉厖鍛樺伐鍜岄儴闂ㄤ俊鎭�
+ */
+ private void fillUserInfo(List<HrmSalaryPaymentDetailRespVO> respList, List<HrmSalaryPaymentDetailDO> detailList) {
+ // 鎵归噺鑾峰彇鍛樺伐淇℃伅
+ Set<Long> employeeIds = detailList.stream()
+ .map(HrmSalaryPaymentDetailDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
+ for (Long employeeId : employeeIds) {
+ HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
+ if (employee != null) {
+ employeeMap.put(employeeId, employee);
+ }
+ }
+ // 鎵归噺鑾峰彇閮ㄩ棬淇℃伅
+ Set<Long> deptIds = detailList.stream()
+ .map(HrmSalaryPaymentDetailDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+
+ // 濉厖淇℃伅
+ for (HrmSalaryPaymentDetailRespVO respVO : respList) {
+ HrmEmployeeDO employee = employeeMap.get(respVO.getUserId());
+ if (employee != null) {
+ respVO.setUserName(employee.getName());
+ }
+ DeptRespDTO dept = deptMap.get(respVO.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecurityConfigController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecurityConfigController.java
new file mode 100644
index 0000000..a01acf0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecurityConfigController.java
@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+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.salary.vo.HrmSocialSecurityConfigRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSocialSecurityConfigSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecurityConfigDO;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSocialSecurityConfigService;
+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.time.LocalDate;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 绀句繚鍏Н閲戦厤缃�")
+@RestController
+@RequestMapping("/hrm/salary/social-security-config")
+@Validated
+public class HrmSocialSecurityConfigController {
+
+ @Resource
+ private HrmSocialSecurityConfigService socialSecurityConfigService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓绀句繚鍏Н閲戦厤缃�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:create')")
+ public CommonResult<Long> createSocialSecurityConfig(@Valid @RequestBody HrmSocialSecurityConfigSaveReqVO createReqVO) {
+ HrmSocialSecurityConfigDO config = BeanUtils.toBean(createReqVO, HrmSocialSecurityConfigDO.class);
+ return success(socialSecurityConfigService.createSocialSecurityConfig(config));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊绀句繚鍏Н閲戦厤缃�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:update')")
+ public CommonResult<Boolean> updateSocialSecurityConfig(@Valid @RequestBody HrmSocialSecurityConfigSaveReqVO updateReqVO) {
+ HrmSocialSecurityConfigDO config = BeanUtils.toBean(updateReqVO, HrmSocialSecurityConfigDO.class);
+ socialSecurityConfigService.updateSocialSecurityConfig(config);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎绀句繚鍏Н閲戦厤缃�")
+ @Parameter(name = "id", description = "閰嶇疆ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:delete')")
+ public CommonResult<Boolean> deleteSocialSecurityConfig(@RequestParam("id") Long id) {
+ socialSecurityConfigService.deleteSocialSecurityConfig(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰彇绀句繚鍏Н閲戦厤缃�")
+ @Parameter(name = "id", description = "閰嶇疆ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<HrmSocialSecurityConfigRespVO> getSocialSecurityConfig(@RequestParam("id") Long id) {
+ HrmSocialSecurityConfigDO config = socialSecurityConfigService.getSocialSecurityConfig(id);
+ return success(BeanUtils.toBean(config, HrmSocialSecurityConfigRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰彇绀句繚鍏Н閲戦厤缃垪琛�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<List<HrmSocialSecurityConfigRespVO>> getSocialSecurityConfigList() {
+ List<HrmSocialSecurityConfigDO> list = socialSecurityConfigService.getSocialSecurityConfigList();
+ return success(BeanUtils.toBean(list, HrmSocialSecurityConfigRespVO.class));
+ }
+
+ @GetMapping("/get-effective")
+ @Operation(summary = "鑾峰彇鐢熸晥鐨勭ぞ淇濆叕绉噾閰嶇疆")
+ @Parameter(name = "effectiveDate", description = "鐢熸晥鏃ユ湡", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<HrmSocialSecurityConfigRespVO> getEffectiveSocialSecurityConfig(
+ @RequestParam("effectiveDate") LocalDate effectiveDate) {
+ HrmSocialSecurityConfigDO config = socialSecurityConfigService.getEffectiveSocialSecurityConfig(effectiveDate);
+ return success(BeanUtils.toBean(config, HrmSocialSecurityConfigRespVO.class));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java
new file mode 100644
index 0000000..caf71d4
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+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.salary.vo.HrmSocialSecuritySchemeRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSocialSecuritySchemeSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSocialSecuritySchemeService;
+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/social-security-scheme")
+@Validated
+public class HrmSocialSecuritySchemeController {
+
+ @Resource
+ private HrmSocialSecuritySchemeService schemeService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓绀句繚鍏Н閲戞柟妗�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:create')")
+ public CommonResult<Long> createScheme(@Valid @RequestBody HrmSocialSecuritySchemeSaveReqVO createReqVO) {
+ HrmSocialSecuritySchemeDO scheme = BeanUtils.toBean(createReqVO, HrmSocialSecuritySchemeDO.class);
+ return success(schemeService.createScheme(scheme));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊绀句繚鍏Н閲戞柟妗�")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:update')")
+ public CommonResult<Boolean> updateScheme(@Valid @RequestBody HrmSocialSecuritySchemeSaveReqVO updateReqVO) {
+ HrmSocialSecuritySchemeDO scheme = BeanUtils.toBean(updateReqVO, HrmSocialSecuritySchemeDO.class);
+ schemeService.updateScheme(scheme);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎绀句繚鍏Н閲戞柟妗�")
+ @Parameter(name = "id", description = "鏂规ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:delete')")
+ public CommonResult<Boolean> deleteScheme(@RequestParam("id") Long id) {
+ schemeService.deleteScheme(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰彇绀句繚鍏Н閲戞柟妗�")
+ @Parameter(name = "id", description = "鏂规ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<HrmSocialSecuritySchemeRespVO> getScheme(@RequestParam("id") Long id) {
+ HrmSocialSecuritySchemeDO scheme = schemeService.getScheme(id);
+ return success(BeanUtils.toBean(scheme, HrmSocialSecuritySchemeRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰彇绀句繚鍏Н閲戞柟妗堝垪琛�")
+ @Parameter(name = "status", description = "鐘舵��")
+ @PreAuthorize("@ss.hasPermission('hrm:social-security:query')")
+ public CommonResult<List<HrmSocialSecuritySchemeRespVO>> getSchemeList(
+ @RequestParam(value = "status", required = false) Integer status) {
+ List<HrmSocialSecuritySchemeDO> list = schemeService.getSchemeList(status);
+ return success(BeanUtils.toBean(list, HrmSocialSecuritySchemeRespVO.class));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmTaxRateConfigController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmTaxRateConfigController.java
new file mode 100644
index 0000000..2df35be
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmTaxRateConfigController.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.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmTaxRateConfigRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmTaxRateConfigSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmTaxRateConfigDO;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmTaxRateConfigService;
+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.time.LocalDate;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 涓◣绋庣巼閰嶇疆")
+@RestController
+@RequestMapping("/hrm/salary/tax-rate-config")
+@Validated
+public class HrmTaxRateConfigController {
+
+ @Resource
+ private HrmTaxRateConfigService taxRateConfigService;
+
+ @PostMapping("/create-list")
+ @Operation(summary = "鎵归噺鍒涘缓涓◣绋庣巼閰嶇疆锛堜竴涓◣鐜囪〃鍖呭惈7涓。浣嶏級")
+ @PreAuthorize("@ss.hasPermission('hrm:tax-rate:create')")
+ public CommonResult<Boolean> createTaxRateConfigList(@Valid @RequestBody List<HrmTaxRateConfigSaveReqVO> createReqVOList) {
+ List<HrmTaxRateConfigDO> configList = BeanUtils.toBean(createReqVOList, HrmTaxRateConfigDO.class);
+ taxRateConfigService.createTaxRateConfigList(configList);
+ return success(true);
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊涓◣绋庣巼閰嶇疆")
+ @PreAuthorize("@ss.hasPermission('hrm:tax-rate:update')")
+ public CommonResult<Boolean> updateTaxRateConfig(@Valid @RequestBody HrmTaxRateConfigSaveReqVO updateReqVO) {
+ HrmTaxRateConfigDO config = BeanUtils.toBean(updateReqVO, HrmTaxRateConfigDO.class);
+ taxRateConfigService.updateTaxRateConfig(config);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎涓◣绋庣巼閰嶇疆")
+ @Parameter(name = "id", description = "閰嶇疆ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:tax-rate:delete')")
+ public CommonResult<Boolean> deleteTaxRateConfig(@RequestParam("id") Long id) {
+ taxRateConfigService.deleteTaxRateConfig(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰彇涓◣绋庣巼閰嶇疆")
+ @Parameter(name = "id", description = "閰嶇疆ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:tax-rate:query')")
+ public CommonResult<HrmTaxRateConfigRespVO> getTaxRateConfig(@RequestParam("id") Long id) {
+ HrmTaxRateConfigDO config = taxRateConfigService.getTaxRateConfig(id);
+ return success(BeanUtils.toBean(config, HrmTaxRateConfigRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰彇涓◣绋庣巼閰嶇疆鍒楄〃")
+ @PreAuthorize("@ss.hasPermission('hrm:tax-rate:query')")
+ public CommonResult<List<HrmTaxRateConfigRespVO>> getTaxRateConfigList() {
+ List<HrmTaxRateConfigDO> list = taxRateConfigService.getTaxRateConfigList();
+ return success(BeanUtils.toBean(list, HrmTaxRateConfigRespVO.class));
+ }
+
+ @GetMapping("/get-effective")
+ @Operation(summary = "鑾峰彇鐢熸晥鐨勪釜绋庣◣鐜囬厤缃垪琛紙鎸夌骇鏁版帓搴忥級")
+ @Parameter(name = "effectiveDate", description = "鐢熸晥鏃ユ湡", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:tax-rate:query')")
+ public CommonResult<List<HrmTaxRateConfigRespVO>> getEffectiveTaxRateConfigList(
+ @RequestParam("effectiveDate") LocalDate effectiveDate) {
+ List<HrmTaxRateConfigDO> list = taxRateConfigService.getEffectiveTaxRateConfigList(effectiveDate);
+ return success(BeanUtils.toBean(list, HrmTaxRateConfigRespVO.class));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityPageReqVO.java
new file mode 100644
index 0000000..e6dc647
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityPageReqVO.java
@@ -0,0 +1,30 @@
+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 java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐绀句繚鍏Н閲戞。妗堝垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class HrmEmployeeSocialSecurityPageReqVO extends PageParam {
+
+ @Schema(description = "鍛樺伐ID", example = "100")
+ private Long userId;
+
+ @Schema(description = "鍛樺伐濮撳悕", example = "寮犱笁")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬ID", example = "104")
+ private Long deptId;
+
+ @Schema(description = "鐢熸晥鏃ユ湡")
+ private LocalDate effectiveDate;
+
+ @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/HrmEmployeeSocialSecurityRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityRespVO.java
new file mode 100644
index 0000000..69e7e6e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityRespVO.java
@@ -0,0 +1,52 @@
+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 = "绠$悊鍚庡彴 - 鍛樺伐绀句繚鍏Н閲戞。妗� Response VO")
+@Data
+public class HrmEmployeeSocialSecurityRespVO {
+
+ @Schema(description = "妗fID", example = "1")
+ private Long id;
+
+ @Schema(description = "绯荤粺鐢ㄦ埛ID锛堝叧鑱� system_users.id锛�", example = "100")
+ private Long userId;
+
+ @Schema(description = "鍛樺伐濮撳悕", example = "寮犱笁")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О", example = "鐮斿彂閮�")
+ private String deptName;
+
+ @Schema(description = "绀句繚鍏Н閲戞柟妗圛D", example = "1")
+ private Long socialSecuritySchemeId;
+
+ @Schema(description = "绀句繚鍏Н閲戞柟妗堝悕绉�", example = "2024骞村崡閫氬競鏍囧噯")
+ private String schemeName;
+
+ // ========== 缂寸撼鍩烘暟锛堝憳宸ヤ釜鎬у寲璁剧疆锛� ==========
+ @Schema(description = "绀句繚缂寸撼鍩烘暟", example = "10000")
+ private BigDecimal socialSecurityBase;
+
+ @Schema(description = "鍏Н閲戠即绾冲熀鏁�", example = "10000")
+ private BigDecimal housingFundBase;
+
+ // ========== 鐢熸晥淇℃伅 ==========
+ @Schema(description = "鐢熸晥鏃ユ湡")
+ private LocalDate effectiveDate;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "2026骞磋皟鏁寸ぞ淇濆熀鏁�")
+ 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/HrmEmployeeSocialSecuritySaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecuritySaveReqVO.java
new file mode 100644
index 0000000..9cc8c47
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecuritySaveReqVO.java
@@ -0,0 +1,45 @@
+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 = "绠$悊鍚庡彴 - 鍛樺伐绀句繚鍏Н閲戞。妗堟柊澧�/淇敼 Request VO")
+@Data
+public class HrmEmployeeSocialSecuritySaveReqVO {
+
+ @Schema(description = "妗fID", example = "1")
+ private Long id;
+
+ @Schema(description = "绯荤粺鐢ㄦ埛ID锛堝叧鑱� system_users.id锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "绯荤粺鐢ㄦ埛ID涓嶈兘涓虹┖")
+ private Long userId;
+
+ @Schema(description = "绀句繚鍏Н閲戞柟妗圛D", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "绀句繚鍏Н閲戞柟妗堜笉鑳戒负绌�")
+ private Long socialSecuritySchemeId;
+
+ // ========== 缂寸撼鍩烘暟锛堝憳宸ヤ釜鎬у寲璁剧疆锛� ==========
+ @Schema(description = "绀句繚缂寸撼鍩烘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
+ @NotNull(message = "绀句繚缂寸撼鍩烘暟涓嶈兘涓虹┖")
+ private BigDecimal socialSecurityBase;
+
+ @Schema(description = "鍏Н閲戠即绾冲熀鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
+ @NotNull(message = "鍏Н閲戠即绾冲熀鏁颁笉鑳戒负绌�")
+ private BigDecimal housingFundBase;
+
+ // ========== 鐢熸晥淇℃伅 ==========
+ @Schema(description = "鐢熸晥鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐢熸晥鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate effectiveDate;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "2026骞磋皟鏁寸ぞ淇濆熀鏁�")
+ 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/HrmLeaveTypeConfigRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigRespVO.java
new file mode 100644
index 0000000..70afdc8
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigRespVO.java
@@ -0,0 +1,30 @@
+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;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇峰亣绫诲瀷鎵f閰嶇疆 Response VO")
+@Data
+public class HrmLeaveTypeConfigRespVO {
+
+ @Schema(description = "涓婚敭")
+ private Long id;
+
+ @Schema(description = "璇峰亣绫诲瀷锛�1-浜嬪亣, 2-鐥呭亣, 3-骞村亣, 4-濠氬亣, 5-浜у亣, 6-涓у亣")
+ private Integer leaveType;
+
+ @Schema(description = "绫诲瀷鍚嶇О")
+ private String name;
+
+ @Schema(description = "鎵f姣斾緥锛�0-1涔嬮棿锛�")
+ private BigDecimal deductionRatio;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤, 1-绂佺敤")
+ 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/HrmLeaveTypeConfigSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigSaveReqVO.java
new file mode 100644
index 0000000..e4d561d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigSaveReqVO.java
@@ -0,0 +1,27 @@
+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;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇峰亣绫诲瀷鎵f閰嶇疆鏇存柊 Request VO")
+@Data
+public class HrmLeaveTypeConfigSaveReqVO {
+
+ @Schema(description = "涓婚敭", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+ private Long id;
+
+ @Schema(description = "鎵f姣斾緥锛�0-1涔嬮棿锛�", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鎵f姣斾緥涓嶈兘涓虹┖")
+ private BigDecimal deductionRatio;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤, 1-绂佺敤")
+ 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/HrmSalaryCalculationPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationPageReqVO.java
new file mode 100644
index 0000000..3b53c13
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationPageReqVO.java
@@ -0,0 +1,30 @@
+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 HrmSalaryCalculationPageReqVO extends PageParam {
+
+ @Schema(description = "鏍哥畻鍗曞彿", example = "SC202401001")
+ private String no;
+
+ @Schema(description = "鏍哥畻鍛ㄦ湡", example = "2024-01")
+ private String period;
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "鐘舵��", 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/HrmSalaryCalculationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java
new file mode 100644
index 0000000..945b210
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java
@@ -0,0 +1,121 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖叕鏍哥畻 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmSalaryCalculationRespVO {
+
+ @Schema(description = "鏍哥畻ID")
+ @ExcelProperty("鏍哥畻ID")
+ private Long id;
+
+ @Schema(description = "鏍哥畻鍗曞彿")
+ @ExcelProperty("鏍哥畻鍗曞彿")
+ private String no;
+
+ @Schema(description = "鏍哥畻鍛ㄦ湡")
+ @ExcelProperty("鏍哥畻鍛ㄦ湡")
+ private String period;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long userId;
+
+ @Schema(description = "鍛樺伐濮撳悕")
+ @ExcelProperty("鍛樺伐濮撳悕")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬ID")
+ private Long deptId;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "鍩烘湰宸ヨ祫")
+ @ExcelProperty("鍩烘湰宸ヨ祫")
+ private BigDecimal baseSalary;
+
+ @Schema(description = "缁╂晥宸ヨ祫")
+ @ExcelProperty("缁╂晥宸ヨ祫")
+ private BigDecimal performanceSalary;
+
+ @Schema(description = "鍔犵彮宸ヨ祫")
+ @ExcelProperty("鍔犵彮宸ヨ祫")
+ private BigDecimal overtimePay;
+
+ @Schema(description = "椁愯ˉ")
+ @ExcelProperty("椁愯ˉ")
+ private BigDecimal mealAllowance;
+
+ @Schema(description = "浜ら�氳ˉ璐�")
+ @ExcelProperty("浜ら�氳ˉ璐�")
+ private BigDecimal transportAllowance;
+
+ @Schema(description = "鍏朵粬琛ヨ创")
+ @ExcelProperty("鍏朵粬琛ヨ创")
+ private BigDecimal otherAllowance;
+
+ @Schema(description = "绀句繚鎵i櫎")
+ @ExcelProperty("绀句繚鎵i櫎")
+ private BigDecimal socialSecurityDeduction;
+
+ @Schema(description = "鍏Н閲戞墸闄�")
+ @ExcelProperty("鍏Н閲戞墸闄�")
+ private BigDecimal housingFundDeduction;
+
+ @Schema(description = "涓◣鎵i櫎")
+ @ExcelProperty("涓◣鎵i櫎")
+ private BigDecimal taxDeduction;
+
+ @Schema(description = "鍏朵粬鎵i櫎")
+ @ExcelProperty("鍏朵粬鎵i櫎")
+ private BigDecimal otherDeduction;
+
+ @Schema(description = "璇峰亣鎵f")
+ @ExcelProperty("璇峰亣鎵f")
+ private BigDecimal leaveDeduction;
+
+ @Schema(description = "瀹炲彂宸ヨ祫")
+ @ExcelProperty("瀹炲彂宸ヨ祫")
+ private BigDecimal actualSalary;
+
+ @Schema(description = "鍑哄嫟澶╂暟")
+ @ExcelProperty("鍑哄嫟澶╂暟")
+ private Integer workDays;
+
+ @Schema(description = "鍔犵彮鏃堕暱")
+ @ExcelProperty("鍔犵彮鏃堕暱")
+ private BigDecimal overtimeHours;
+
+ @Schema(description = "鐘舵��")
+ @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+ @DictFormat("hrm_salary_calculation_status")
+ private Integer status;
+
+ @Schema(description = "纭鏃堕棿")
+ @ExcelProperty("纭鏃堕棿")
+ private LocalDateTime confirmTime;
+
+ @Schema(description = "鍙戞斁鏃堕棿")
+ @ExcelProperty("鍙戞斁鏃堕棿")
+ private LocalDateTime paymentTime;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/HrmSalaryCalculationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationSaveReqVO.java
new file mode 100644
index 0000000..550c6c9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationSaveReqVO.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖叕鏍哥畻淇濆瓨 Request VO")
+@Data
+public class HrmSalaryCalculationSaveReqVO {
+
+ @Schema(description = "鏍哥畻鍛ㄦ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01")
+ @NotNull(message = "鏍哥畻鍛ㄦ湡涓嶈兘涓虹┖")
+ private String period;
+
+ @Schema(description = "钖叕鏍哥畻鍒楄〃", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "钖叕鏍哥畻鍒楄〃涓嶈兘涓虹┖")
+ @Valid
+ private List<CalculationItem> list;
+
+ @Schema(description = "钖叕鏍哥畻椤�")
+ @Data
+ public static class CalculationItem {
+
+ @Schema(description = "鏍哥畻ID锛堟柊澧炴椂涓嶄紶锛屼慨鏀规椂蹇呬紶锛�")
+ private Long id;
+
+ @Schema(description = "绯荤粺鐢ㄦ埛ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鍛樺伐涓嶈兘涓虹┖")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "閮ㄩ棬涓嶈兘涓虹┖")
+ private Long deptId;
+
+ @Schema(description = "鍩烘湰宸ヨ祫", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鍩烘湰宸ヨ祫涓嶈兘涓虹┖")
+ private BigDecimal baseSalary;
+
+ @Schema(description = "缁╂晥宸ヨ祫")
+ private BigDecimal performanceSalary;
+
+ @Schema(description = "鍔犵彮宸ヨ祫")
+ private BigDecimal overtimePay;
+
+ @Schema(description = "椁愯ˉ")
+ private BigDecimal mealAllowance;
+
+ @Schema(description = "浜ら�氳ˉ璐�")
+ private BigDecimal transportAllowance;
+
+ @Schema(description = "鍏朵粬琛ヨ创")
+ private BigDecimal otherAllowance;
+
+ @Schema(description = "绀句繚鎵i櫎")
+ private BigDecimal socialSecurityDeduction;
+
+ @Schema(description = "鍏Н閲戞墸闄�")
+ private BigDecimal housingFundDeduction;
+
+ @Schema(description = "涓◣鎵i櫎")
+ private BigDecimal taxDeduction;
+
+ @Schema(description = "鍏朵粬鎵i櫎")
+ private BigDecimal otherDeduction;
+
+ @Schema(description = "瀹炲彂宸ヨ祫", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "瀹炲彂宸ヨ祫涓嶈兘涓虹┖")
+ private BigDecimal actualSalary;
+
+ @Schema(description = "鍑哄嫟澶╂暟")
+ private Integer workDays;
+
+ @Schema(description = "鍔犵彮鏃堕暱")
+ private BigDecimal overtimeHours;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailPageReqVO.java
new file mode 100644
index 0000000..c75507f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailPageReqVO.java
@@ -0,0 +1,27 @@
+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 HrmSalaryPaymentDetailPageReqVO extends PageParam {
+
+ @Schema(description = "鍙戞斁鍙拌处ID", example = "1")
+ private Long paymentId;
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long userId;
+
+ @Schema(description = "閮ㄩ棬ID", example = "103")
+ private Long deptId;
+
+ @Schema(description = "鍙戞斁鐘舵��", 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/HrmSalaryPaymentDetailRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailRespVO.java
new file mode 100644
index 0000000..a64e8bd
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailRespVO.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖祫鍙戞斁鏄庣粏 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmSalaryPaymentDetailRespVO {
+
+ @Schema(description = "涓婚敭")
+ private Long id;
+
+ @Schema(description = "鍙戞斁鍙拌处ID")
+ private Long paymentId;
+
+ @Schema(description = "钖叕鏍哥畻ID")
+ private Long calculationId;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long userId;
+
+ @Schema(description = "鍛樺伐濮撳悕")
+ @ExcelProperty("鍛樺伐濮撳悕")
+ private String userName;
+
+ @Schema(description = "閮ㄩ棬ID")
+ private Long deptId;
+
+ @Schema(description = "閮ㄩ棬鍚嶇О")
+ @ExcelProperty("閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ @Schema(description = "瀹炲彂宸ヨ祫")
+ @ExcelProperty("瀹炲彂宸ヨ祫")
+ private BigDecimal actualSalary;
+
+ @Schema(description = "鍙戞斁鐘舵��")
+ @ExcelProperty(value = "鍙戞斁鐘舵��", converter = DictConvert.class)
+ @DictFormat("hrm_payment_detail_status")
+ private Integer status;
+
+ @Schema(description = "鍙戞斁鏃堕棿")
+ @ExcelProperty("鍙戞斁鏃堕棿")
+ private LocalDateTime paymentTime;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ 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/HrmSalaryPaymentPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentPageReqVO.java
new file mode 100644
index 0000000..3099885
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentPageReqVO.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 = "绠$悊鍚庡彴 - 钖祫鍙戞斁鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmSalaryPaymentPageReqVO extends PageParam {
+
+ @Schema(description = "鍙戞斁鍗曞彿", example = "SF202401001")
+ private String no;
+
+ @Schema(description = "鍙戞斁鍛ㄦ湡", example = "2024-01")
+ private String period;
+
+ @Schema(description = "鐘舵��", 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/HrmSalaryPaymentRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java
new file mode 100644
index 0000000..4fc9536
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖祫鍙戞斁 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmSalaryPaymentRespVO {
+
+ @Schema(description = "鍙戞斁ID")
+ @ExcelProperty("鍙戞斁ID")
+ private Long id;
+
+ @Schema(description = "鍙戞斁鍗曞彿")
+ @ExcelProperty("鍙戞斁鍗曞彿")
+ private String no;
+
+ @Schema(description = "鍙戞斁鍛ㄦ湡")
+ @ExcelProperty("鍙戞斁鍛ㄦ湡")
+ private String period;
+
+ @Schema(description = "鍙戞斁鎬婚噾棰�")
+ @ExcelProperty("鍙戞斁鎬婚噾棰�")
+ private BigDecimal totalAmount;
+
+ @Schema(description = "鍙戞斁浜烘暟")
+ @ExcelProperty("鍙戞斁浜烘暟")
+ private Integer totalCount;
+
+ @Schema(description = "鍙戞斁鏂瑰紡")
+ @ExcelProperty(value = "鍙戞斁鏂瑰紡", converter = DictConvert.class)
+ @DictFormat("hrm_payment_method")
+ private Integer paymentMethod;
+
+ @Schema(description = "鍙戞斁鏃堕棿")
+ @ExcelProperty("鍙戞斁鏃堕棿")
+ private LocalDateTime paymentTime;
+
+ @Schema(description = "鐘舵��")
+ @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+ @DictFormat("hrm_payment_status")
+ private Integer status;
+
+ @Schema(description = "閾惰鍥炲崟闄勪欢")
+ private String fileUrl;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ 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/HrmSocialSecurityConfigRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigRespVO.java
new file mode 100644
index 0000000..18b4e27
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigRespVO.java
@@ -0,0 +1,81 @@
+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 = "绠$悊鍚庡彴 - 绀句繚鍏Н閲戦厤缃� Response VO")
+@Data
+public class HrmSocialSecurityConfigRespVO {
+
+ @Schema(description = "閰嶇疆ID", example = "1")
+ private Long id;
+
+ @Schema(description = "閰嶇疆鍚嶇О", example = "2024骞寸ぞ淇濆叕绉噾鏍囧噯")
+ private String name;
+
+ @Schema(description = "鐢熸晥鏃ユ湡")
+ private LocalDate effectiveDate;
+
+ // ========== 鍏昏�佷繚闄� ==========
+ @Schema(description = "鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�", example = "0.16")
+ private BigDecimal pensionCompanyRatio;
+
+ @Schema(description = "鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�", example = "0.08")
+ private BigDecimal pensionPersonalRatio;
+
+ // ========== 鍖荤枟淇濋櫓 ==========
+ @Schema(description = "鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.095")
+ private BigDecimal medicalCompanyRatio;
+
+ @Schema(description = "鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥", example = "0.02")
+ private BigDecimal medicalPersonalRatio;
+
+ // ========== 澶变笟淇濋櫓 ==========
+ @Schema(description = "澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal unemploymentCompanyRatio;
+
+ @Schema(description = "澶变笟淇濋櫓涓汉缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal unemploymentPersonalRatio;
+
+ // ========== 宸ヤ激淇濋櫓 ==========
+ @Schema(description = "宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal workInjuryRatio;
+
+ // ========== 鐢熻偛淇濋櫓 ==========
+ @Schema(description = "鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal maternityRatio;
+
+ // ========== 鍏Н閲� ==========
+ @Schema(description = "鍏Н閲戜紒涓氱即绾虫瘮渚�", example = "0.12")
+ private BigDecimal housingFundCompanyRatio;
+
+ @Schema(description = "鍏Н閲戜釜浜虹即绾虫瘮渚�", example = "0.12")
+ private BigDecimal housingFundPersonalRatio;
+
+ // ========== 鍩烘暟涓婁笅闄� ==========
+ @Schema(description = "绀句繚鍩烘暟涓嬮檺", example = "4494")
+ private BigDecimal socialSecurityBaseMin;
+
+ @Schema(description = "绀句繚鍩烘暟涓婇檺", example = "28017")
+ private BigDecimal socialSecurityBaseMax;
+
+ @Schema(description = "鍏Н閲戝熀鏁颁笅闄�", example = "2420")
+ private BigDecimal housingFundBaseMin;
+
+ @Schema(description = "鍏Н閲戝熀鏁颁笂闄�", example = "35091")
+ private BigDecimal housingFundBaseMax;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "2024骞村崡閫氬競绀句繚鍏Н閲戠即绾虫爣鍑�")
+ 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/HrmSocialSecurityConfigSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigSaveReqVO.java
new file mode 100644
index 0000000..7490bc5
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigSaveReqVO.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 绀句繚鍏Н閲戦厤缃柊澧�/淇敼 Request VO")
+@Data
+public class HrmSocialSecurityConfigSaveReqVO {
+
+ @Schema(description = "閰嶇疆ID", example = "1")
+ private Long id;
+
+ @Schema(description = "閰嶇疆鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024骞寸ぞ淇濆叕绉噾鏍囧噯")
+ @NotBlank(message = "閰嶇疆鍚嶇О涓嶈兘涓虹┖")
+ private String name;
+
+ @Schema(description = "鐢熸晥鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐢熸晥鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate effectiveDate;
+
+ // ========== 鍏昏�佷繚闄� ==========
+ @Schema(description = "鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.16")
+ @NotNull(message = "鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚嬩笉鑳戒负绌�")
+ private BigDecimal pensionCompanyRatio;
+
+ @Schema(description = "鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.08")
+ @NotNull(message = "鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚嬩笉鑳戒负绌�")
+ private BigDecimal pensionPersonalRatio;
+
+ // ========== 鍖荤枟淇濋櫓 ==========
+ @Schema(description = "鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.095")
+ @NotNull(message = "鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥涓嶈兘涓虹┖")
+ private BigDecimal medicalCompanyRatio;
+
+ @Schema(description = "鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.02")
+ @NotNull(message = "鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥涓嶈兘涓虹┖")
+ private BigDecimal medicalPersonalRatio;
+
+ // ========== 澶变笟淇濋櫓 ==========
+ @Schema(description = "澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.005")
+ @NotNull(message = "澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥涓嶈兘涓虹┖")
+ private BigDecimal unemploymentCompanyRatio;
+
+ @Schema(description = "澶变笟淇濋櫓涓汉缂寸撼姣斾緥", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.005")
+ @NotNull(message = "澶变笟淇濋櫓涓汉缂寸撼姣斾緥涓嶈兘涓虹┖")
+ private BigDecimal unemploymentPersonalRatio;
+
+ // ========== 宸ヤ激淇濋櫓 ==========
+ @Schema(description = "宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.005")
+ @NotNull(message = "宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥涓嶈兘涓虹┖")
+ private BigDecimal workInjuryRatio;
+
+ // ========== 鐢熻偛淇濋櫓 ==========
+ @Schema(description = "鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.005")
+ @NotNull(message = "鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥涓嶈兘涓虹┖")
+ private BigDecimal maternityRatio;
+
+ // ========== 鍏Н閲� ==========
+ @Schema(description = "鍏Н閲戜紒涓氱即绾虫瘮渚�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.12")
+ @NotNull(message = "鍏Н閲戜紒涓氱即绾虫瘮渚嬩笉鑳戒负绌�")
+ private BigDecimal housingFundCompanyRatio;
+
+ @Schema(description = "鍏Н閲戜釜浜虹即绾虫瘮渚�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.12")
+ @NotNull(message = "鍏Н閲戜釜浜虹即绾虫瘮渚嬩笉鑳戒负绌�")
+ private BigDecimal housingFundPersonalRatio;
+
+ // ========== 鍩烘暟涓婁笅闄� ==========
+ @Schema(description = "绀句繚鍩烘暟涓嬮檺", requiredMode = Schema.RequiredMode.REQUIRED, example = "4494")
+ @NotNull(message = "绀句繚鍩烘暟涓嬮檺涓嶈兘涓虹┖")
+ private BigDecimal socialSecurityBaseMin;
+
+ @Schema(description = "绀句繚鍩烘暟涓婇檺", requiredMode = Schema.RequiredMode.REQUIRED, example = "28017")
+ @NotNull(message = "绀句繚鍩烘暟涓婇檺涓嶈兘涓虹┖")
+ private BigDecimal socialSecurityBaseMax;
+
+ @Schema(description = "鍏Н閲戝熀鏁颁笅闄�", requiredMode = Schema.RequiredMode.REQUIRED, example = "2420")
+ @NotNull(message = "鍏Н閲戝熀鏁颁笅闄愪笉鑳戒负绌�")
+ private BigDecimal housingFundBaseMin;
+
+ @Schema(description = "鍏Н閲戝熀鏁颁笂闄�", requiredMode = Schema.RequiredMode.REQUIRED, example = "35091")
+ @NotNull(message = "鍏Н閲戝熀鏁颁笂闄愪笉鑳戒负绌�")
+ private BigDecimal housingFundBaseMax;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "2024骞村崡閫氬競绀句繚鍏Н閲戠即绾虫爣鍑�")
+ 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/HrmSocialSecuritySchemeRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java
new file mode 100644
index 0000000..9c2462d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java
@@ -0,0 +1,68 @@
+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 = "绠$悊鍚庡彴 - 绀句繚鍏Н閲戞柟妗� Response VO")
+@Data
+public class HrmSocialSecuritySchemeRespVO {
+
+ @Schema(description = "鏂规ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鏂规鍚嶇О", example = "2024骞村崡閫氬競鏍囧噯")
+ private String name;
+
+ @Schema(description = "鐢熸晥鏃ユ湡")
+ private LocalDate effectiveDate;
+
+ // ========== 鍏昏�佷繚闄╂瘮渚� ==========
+ @Schema(description = "鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�", example = "0.16")
+ private BigDecimal pensionCompanyRatio;
+
+ @Schema(description = "鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�", example = "0.08")
+ private BigDecimal pensionPersonalRatio;
+
+ // ========== 鍖荤枟淇濋櫓姣斾緥 ==========
+ @Schema(description = "鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.095")
+ private BigDecimal medicalCompanyRatio;
+
+ @Schema(description = "鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥", example = "0.02")
+ private BigDecimal medicalPersonalRatio;
+
+ // ========== 澶变笟淇濋櫓姣斾緥 ==========
+ @Schema(description = "澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal unemploymentCompanyRatio;
+
+ @Schema(description = "澶变笟淇濋櫓涓汉缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal unemploymentPersonalRatio;
+
+ // ========== 宸ヤ激淇濋櫓姣斾緥 ==========
+ @Schema(description = "宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal workInjuryRatio;
+
+ // ========== 鐢熻偛淇濋櫓姣斾緥 ==========
+ @Schema(description = "鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal maternityRatio;
+
+ // ========== 鍏Н閲戞瘮渚� ==========
+ @Schema(description = "鍏Н閲戜紒涓氱即绾虫瘮渚�", example = "0.12")
+ private BigDecimal housingFundCompanyRatio;
+
+ @Schema(description = "鍏Н閲戜釜浜虹即绾虫瘮渚�", example = "0.12")
+ private BigDecimal housingFundPersonalRatio;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "2024骞村崡閫氬競绀句繚鍏Н閲戠即绾虫爣鍑�")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java
new file mode 100644
index 0000000..08099ab
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 绀句繚鍏Н閲戞柟妗堟柊澧�/淇敼 Request VO")
+@Data
+public class HrmSocialSecuritySchemeSaveReqVO {
+
+ @Schema(description = "鏂规ID", example = "1")
+ private Long id;
+
+ @Schema(description = "鏂规鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024骞村崡閫氬競鏍囧噯")
+ @NotBlank(message = "鏂规鍚嶇О涓嶈兘涓虹┖")
+ private String name;
+
+ @Schema(description = "鐢熸晥鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐢熸晥鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate effectiveDate;
+
+ // ========== 鍏昏�佷繚闄╂瘮渚� ==========
+ @Schema(description = "鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�", example = "0.16")
+ private BigDecimal pensionCompanyRatio;
+
+ @Schema(description = "鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�", example = "0.08")
+ private BigDecimal pensionPersonalRatio;
+
+ // ========== 鍖荤枟淇濋櫓姣斾緥 ==========
+ @Schema(description = "鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.095")
+ private BigDecimal medicalCompanyRatio;
+
+ @Schema(description = "鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥", example = "0.02")
+ private BigDecimal medicalPersonalRatio;
+
+ // ========== 澶变笟淇濋櫓姣斾緥 ==========
+ @Schema(description = "澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal unemploymentCompanyRatio;
+
+ @Schema(description = "澶变笟淇濋櫓涓汉缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal unemploymentPersonalRatio;
+
+ // ========== 宸ヤ激淇濋櫓姣斾緥 ==========
+ @Schema(description = "宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal workInjuryRatio;
+
+ // ========== 鐢熻偛淇濋櫓姣斾緥 ==========
+ @Schema(description = "鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥", example = "0.005")
+ private BigDecimal maternityRatio;
+
+ // ========== 鍏Н閲戞瘮渚� ==========
+ @Schema(description = "鍏Н閲戜紒涓氱即绾虫瘮渚�", example = "0.12")
+ private BigDecimal housingFundCompanyRatio;
+
+ @Schema(description = "鍏Н閲戜釜浜虹即绾虫瘮渚�", example = "0.12")
+ private BigDecimal housingFundPersonalRatio;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "2024骞村崡閫氬競绀句繚鍏Н閲戠即绾虫爣鍑�")
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigRespVO.java
new file mode 100644
index 0000000..51508fc
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigRespVO.java
@@ -0,0 +1,47 @@
+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 = "绠$悊鍚庡彴 - 涓◣绋庣巼閰嶇疆 Response VO")
+@Data
+public class HrmTaxRateConfigRespVO {
+
+ @Schema(description = "閰嶇疆ID", example = "1")
+ private Long id;
+
+ @Schema(description = "閰嶇疆鍚嶇О", example = "2024骞翠釜绋庣◣鐜囪〃")
+ private String name;
+
+ @Schema(description = "鐢熸晥鏃ユ湡")
+ private LocalDate effectiveDate;
+
+ @Schema(description = "绋庣巼绾ф暟锛�1-7", example = "1")
+ private Integer level;
+
+ @Schema(description = "搴旂撼绋庢墍寰楅涓嬮檺锛堝惈锛�", example = "0")
+ private BigDecimal minAmount;
+
+ @Schema(description = "搴旂撼绋庢墍寰楅涓婇檺锛堜笉鍚級锛�0琛ㄧず鏃犱笂闄�", example = "36000")
+ private BigDecimal maxAmount;
+
+ @Schema(description = "绋庣巼", example = "0.03")
+ private BigDecimal taxRate;
+
+ @Schema(description = "閫熺畻鎵i櫎鏁�", example = "0")
+ private BigDecimal quickDeduction;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "涓嶈秴杩�36000鍏�")
+ 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/HrmTaxRateConfigSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigSaveReqVO.java
new file mode 100644
index 0000000..75759b0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigSaveReqVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 涓◣绋庣巼閰嶇疆鏂板/淇敼 Request VO")
+@Data
+public class HrmTaxRateConfigSaveReqVO {
+
+ @Schema(description = "閰嶇疆ID", example = "1")
+ private Long id;
+
+ @Schema(description = "閰嶇疆鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024骞翠釜绋庣◣鐜囪〃")
+ @NotBlank(message = "閰嶇疆鍚嶇О涓嶈兘涓虹┖")
+ private String name;
+
+ @Schema(description = "鐢熸晥鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐢熸晥鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate effectiveDate;
+
+ @Schema(description = "绋庣巼绾ф暟锛�1-7", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "绋庣巼绾ф暟涓嶈兘涓虹┖")
+ private Integer level;
+
+ @Schema(description = "搴旂撼绋庢墍寰楅涓嬮檺锛堝惈锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "搴旂撼绋庢墍寰楅涓嬮檺涓嶈兘涓虹┖")
+ private BigDecimal minAmount;
+
+ @Schema(description = "搴旂撼绋庢墍寰楅涓婇檺锛堜笉鍚級锛�0琛ㄧず鏃犱笂闄�", requiredMode = Schema.RequiredMode.REQUIRED, example = "36000")
+ @NotNull(message = "搴旂撼绋庢墍寰楅涓婇檺涓嶈兘涓虹┖")
+ private BigDecimal maxAmount;
+
+ @Schema(description = "绋庣巼", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.03")
+ @NotNull(message = "绋庣巼涓嶈兘涓虹┖")
+ private BigDecimal taxRate;
+
+ @Schema(description = "閫熺畻鎵i櫎鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "閫熺畻鎵i櫎鏁颁笉鑳戒负绌�")
+ private BigDecimal quickDeduction;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "涓嶈秴杩�36000鍏�")
+ 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/approval/HrmLeaveApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java
new file mode 100644
index 0000000..fb6d477
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java
@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.approval;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmLeaveTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 璇峰亣鐢宠 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_leave_application")
+@KeySequence("hrm_leave_application_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmLeaveApplicationDO extends BaseDO {
+
+ /**
+ * 鐢宠ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐢宠鍗曞彿
+ */
+ private String no;
+
+ /**
+ * 鐢宠浜篒D
+ *
+ * 鍏宠仈 AdminUserDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ *
+ * 鍏宠仈 DeptDO#getId()
+ */
+ private Long deptId;
+
+ /**
+ * 璇峰亣绫诲瀷
+ *
+ * 鏋氫妇 {@link HrmLeaveTypeEnum}
+ */
+ private Integer leaveType;
+
+ /**
+ * 寮�濮嬫椂闂�
+ */
+ private LocalDateTime startTime;
+
+ /**
+ * 缁撴潫鏃堕棿
+ */
+ private LocalDateTime endTime;
+
+ /**
+ * 璇峰亣鏃堕暱锛堝ぉ锛�
+ */
+ private BigDecimal duration;
+
+ /**
+ * 璇峰亣鍘熷洜
+ */
+ private String reason;
+
+ /**
+ * 瀹℃壒鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * BPM 娴佺▼瀹炰緥ID
+ */
+ private String processInstanceId;
+
+ /**
+ * 瀹℃壒瀹屾垚鏃堕棿
+ */
+ private LocalDateTime approveTime;
+
+ /**
+ * 闄勪欢鍦板潃
+ */
+ private String fileUrl;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java
new file mode 100644
index 0000000..a2be939
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java
@@ -0,0 +1,120 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.approval;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmResignationTypeEnum;
+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_resignation_application")
+@KeySequence("hrm_resignation_application_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmResignationApplicationDO extends BaseDO {
+
+ /**
+ * 鐢宠ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐢宠鍗曞彿
+ */
+ private String no;
+
+ /**
+ * 鐢宠浜篒D
+ *
+ * 鍏宠仈 AdminUserDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ *
+ * 鍏宠仈 DeptDO#getId()
+ */
+ private Long deptId;
+
+ /**
+ * 绂昏亴绫诲瀷
+ *
+ * 鏋氫妇 {@link HrmResignationTypeEnum}
+ */
+ private Integer resignationType;
+
+ /**
+ * 棰勮绂昏亴鏃ユ湡
+ */
+ private LocalDate expectedDate;
+
+ /**
+ * 瀹為檯绂昏亴鏃ユ湡
+ */
+ private LocalDate actualDate;
+
+ /**
+ * 绂昏亴鍘熷洜
+ */
+ private String reason;
+
+ /**
+ * 浜ゆ帴浜篒D
+ *
+ * 鍏宠仈 AdminUserDO#getId()
+ */
+ private Long handoverUserId;
+
+ /**
+ * 浜ゆ帴鐘舵��
+ *
+ * 0-鏈紑濮嬶紝10-杩涜涓紝20-宸插畬鎴�
+ */
+ private Integer handoverStatus;
+
+ /**
+ * 浜ゆ帴璇存槑
+ */
+ private String handoverRemark;
+
+ /**
+ * 瀹℃壒鐘舵��
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * BPM 娴佺▼瀹炰緥ID
+ */
+ private String processInstanceId;
+
+ /**
+ * 瀹℃壒瀹屾垚鏃堕棿
+ */
+ private java.time.LocalDateTime approveTime;
+
+ /**
+ * 闄勪欢鍦板潃
+ */
+ private String fileUrl;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java
new file mode 100644
index 0000000..7314a86
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java
@@ -0,0 +1,117 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.approval;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmTransferTypeEnum;
+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_transfer_application")
+@KeySequence("hrm_transfer_application_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmTransferApplicationDO extends BaseDO {
+
+ /**
+ * 鐢宠ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐢宠鍗曞彿
+ */
+ private String no;
+
+ /**
+ * 鐢宠浜篒D
+ *
+ * 鍏宠仈 AdminUserDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 鍘熼儴闂↖D
+ *
+ * 鍏宠仈 DeptDO#getId()
+ */
+ private Long currentDeptId;
+
+ /**
+ * 鍘熷矖浣岻D
+ *
+ * 鍏宠仈 PostDO#getId()
+ */
+ private Long currentPostId;
+
+ /**
+ * 鐩爣閮ㄩ棬ID
+ *
+ * 鍏宠仈 DeptDO#getId()
+ */
+ private Long targetDeptId;
+
+ /**
+ * 鐩爣宀椾綅ID
+ *
+ * 鍏宠仈 PostDO#getId()
+ */
+ private Long targetPostId;
+
+ /**
+ * 璋冨矖绫诲瀷
+ *
+ * 鏋氫妇 {@link HrmTransferTypeEnum}
+ */
+ private Integer transferType;
+
+ /**
+ * 鐢熸晥鏃ユ湡
+ */
+ private LocalDate effectiveDate;
+
+ /**
+ * 璋冨矖鍘熷洜
+ */
+ 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 fileUrl;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java
new file mode 100644
index 0000000..8e9a4e9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.attendance;
+
+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;
+import java.time.LocalDateTime;
+
+/**
+ * 鑰冨嫟寮傚父鐢宠 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_attendance_exception")
+@KeySequence("hrm_attendance_exception_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmAttendanceExceptionDO extends BaseDO {
+
+ /**
+ * 鐢宠ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐢宠鍗曞彿
+ */
+ private String no;
+
+ /**
+ * 鐢宠浜篒D锛堝憳宸D锛�
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 寮傚父绫诲瀷锛�1-琛ュ崱锛�2-鍔犵彮锛�3-鍑哄樊
+ */
+ private Integer type;
+
+ /**
+ * 寮傚父鏃ユ湡
+ */
+ private LocalDate exceptionDate;
+
+ /**
+ * 寮傚父鏃堕棿鐐�
+ */
+ private LocalDateTime exceptionTime;
+
+ /**
+ * 鐢宠鍘熷洜
+ */
+ private String reason;
+
+ /**
+ * 鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢
+ */
+ private Integer status;
+
+ /**
+ * BPM娴佺▼瀹炰緥ID
+ */
+ private String processInstanceId;
+
+ /**
+ * 瀹℃壒瀹屾垚鏃堕棿
+ */
+ private LocalDateTime approveTime;
+
+ /**
+ * 闄勪欢鍦板潃
+ */
+ private String fileUrl;
+
+ /**
+ * 澶囨敞
+ */
+ 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/attendance/HrmAttendanceHolidayDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java
new file mode 100644
index 0000000..2786964
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.attendance;
+
+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_attendance_holiday")
+@KeySequence("hrm_attendance_holiday_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmAttendanceHolidayDO extends BaseDO {
+
+ /**
+ * 鑺傚亣鏃D
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鑺傚亣鏃ュ悕绉�
+ */
+ private String name;
+
+ /**
+ * 鏃ユ湡
+ */
+ private LocalDate date;
+
+ /**
+ * 绫诲瀷锛�1-浼戞伅鏃ワ紝2-宸ヤ綔鏃�
+ *
+ * 鏋氫妇 {@link cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum}
+ */
+ private Integer type;
+
+ /**
+ * 鏄惁娉曞畾鑺傚亣鏃ワ細0-鍚︼紝1-鏄�
+ */
+ private Integer isLegal;
+
+ /**
+ * 澶囨敞
+ */
+ 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/attendance/HrmAttendanceRecordDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRecordDO.java
new file mode 100644
index 0000000..7b28a1d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRecordDO.java
@@ -0,0 +1,96 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.attendance;
+
+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;
+import java.time.LocalDateTime;
+
+/**
+ * 鑰冨嫟鎵撳崱璁板綍 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_attendance_record")
+@KeySequence("hrm_attendance_record_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmAttendanceRecordDO extends BaseDO {
+
+ /**
+ * 璁板綍ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鑰冨嫟鏃ユ湡
+ */
+ private LocalDate date;
+
+ /**
+ * 涓婄彮鎵撳崱鏃堕棿
+ */
+ private LocalDateTime clockInTime;
+
+ /**
+ * 涓嬬彮鎵撳崱鏃堕棿
+ */
+ private LocalDateTime clockOutTime;
+
+ /**
+ * 涓婄彮鎵撳崱绫诲瀷锛�1-姝e父锛�2-杩熷埌锛�3-缂哄崱
+ */
+ private Integer clockInType;
+
+ /**
+ * 涓嬬彮鎵撳崱绫诲瀷锛�1-姝e父锛�2-鏃╅��锛�3-缂哄崱
+ */
+ private Integer clockOutType;
+
+ /**
+ * 宸ヤ綔鏃堕暱锛堝皬鏃讹級
+ */
+ private BigDecimal workHours;
+
+ /**
+ * 鍔犵彮鏃堕暱锛堝皬鏃讹級
+ */
+ private BigDecimal overtimeHours;
+
+ /**
+ * 鎵撳崱鍦扮偣
+ */
+ private String location;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鏁版嵁鏉ユ簮锛�1-鎵撳崱锛�2-瀵煎叆
+ */
+ private Integer dataSource;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRuleDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRuleDO.java
new file mode 100644
index 0000000..3ebebc2
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRuleDO.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.attendance;
+
+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;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+/**
+ * 鑰冨嫟瑙勫垯閰嶇疆 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_attendance_rule")
+@KeySequence("hrm_attendance_rule_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmAttendanceRuleDO extends BaseDO {
+
+ /**
+ * 瑙勫垯ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 瑙勫垯鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 閫傜敤閮ㄩ棬ID锛堢┖琛ㄧず鍏ㄥ眬锛�
+ */
+ private Long deptId;
+
+ /**
+ * 涓婄彮鏃堕棿
+ */
+ private LocalTime workStartTime;
+
+ /**
+ * 涓嬬彮鏃堕棿
+ */
+ private LocalTime workEndTime;
+
+ /**
+ * 杩熷埌鍏佽鍒嗛挓鏁�
+ */
+ private Integer lateAllowMinutes;
+
+ /**
+ * 鏃╅��鍏佽鍒嗛挓鏁�
+ */
+ private Integer earlyAllowMinutes;
+
+ /**
+ * 鍔犵彮璧风畻鏃堕暱锛堝皬鏃讹級
+ */
+ private BigDecimal overtimeThreshold;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ 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/attendance/HrmAttendanceSummaryDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceSummaryDO.java
new file mode 100644
index 0000000..370deaf
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceSummaryDO.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.attendance;
+
+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_attendance_summary")
+@KeySequence("hrm_attendance_summary_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmAttendanceSummaryDO extends BaseDO {
+
+ /**
+ * 姹囨�籌D
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 骞存湀锛屾牸寮忥細yyyy-MM
+ */
+ private String yearMonth;
+
+ /**
+ * 搴斿嚭鍕ゅぉ鏁�
+ */
+ private Integer workDays;
+
+ /**
+ * 瀹為檯鍑哄嫟澶╂暟
+ */
+ private Integer actualDays;
+
+ /**
+ * 杩熷埌娆℃暟
+ */
+ private Integer lateCount;
+
+ /**
+ * 鏃╅��娆℃暟
+ */
+ private Integer earlyCount;
+
+ /**
+ * 缂哄崱娆℃暟
+ */
+ private Integer absentCount;
+
+ /**
+ * 鍔犵彮鏃堕暱锛堝皬鏃讹級
+ */
+ private BigDecimal overtimeHours;
+
+ /**
+ * 璇峰亣鏃堕暱锛堝皬鏃讹級
+ */
+ private BigDecimal leaveHours;
+
+ /**
+ * 澶囨敞
+ */
+ 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
new file mode 100644
index 0000000..e7d77ab
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
@@ -0,0 +1,164 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.employee;
+
+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;
+
+/**
+ * 鍛樺伐淇℃伅 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_employee")
+@KeySequence("hrm_employee_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmEmployeeDO extends BaseDO {
+
+ /**
+ * 鍛樺伐ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍏宠仈绯荤粺鐢ㄦ埛ID
+ *
+ * 鍏宠仈 AdminUserDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ *
+ * 鍏宠仈 DeptDO#getId()
+ */
+ private Long deptId;
+
+ /**
+ * 宀椾綅ID
+ *
+ * 鍏宠仈 PostDO#getId()
+ */
+ private Long postId;
+
+ /**
+ * 鍛樺伐缂栧彿
+ */
+ private String employeeNo;
+
+ /**
+ * 鍛樺伐濮撳悕
+ */
+ private String name;
+
+ /**
+ * 鎬у埆
+ *
+ * 1-鐢凤紝2-濂�
+ */
+ private Integer gender;
+
+ /**
+ * 鍑虹敓鏃ユ湡
+ */
+ private LocalDate birthday;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phone;
+
+ /**
+ * 閭
+ */
+ private String email;
+
+ /**
+ * 韬唤璇佸彿
+ */
+ private String idCard;
+
+ /**
+ * 鍏ヨ亴鏃ユ湡
+ */
+ private LocalDate hireDate;
+
+ /**
+ * 杞鏃ユ湡
+ */
+ private LocalDate regularDate;
+
+ /**
+ * 绂昏亴鏃ユ湡
+ */
+ private LocalDate leaveDate;
+
+ /**
+ * 鍛樺伐鐘舵��
+ *
+ * 1-鍦ㄨ亴锛�2-璇曠敤锛�3-绂昏亴
+ */
+ private Integer employeeStatus;
+
+ /**
+ * 骞村亣浣欓锛堝ぉ锛�
+ */
+ private BigDecimal annualLeaveBalance;
+
+ /**
+ * 鐥呭亣浣欓锛堝ぉ锛�
+ */
+ private BigDecimal sickLeaveBalance;
+
+ /**
+ * 钖叕缁撴瀯ID
+ *
+ * 鍏宠仈 HrmSalaryStructureDO#getId()
+ */
+ private Long salaryStructureId;
+
+ /**
+ * 鍩烘湰宸ヨ祫
+ *
+ * 鐢ㄤ簬钖叕鏍哥畻
+ */
+ private BigDecimal baseSalary;
+
+ /**
+ * 瑙掕壊ID鍒楄〃
+ *
+ * 鐢ㄤ簬鍒嗛厤绯荤粺鐢ㄦ埛瑙掕壊锛孞SON 鏍煎紡瀛樺偍
+ */
+ private String roleIds;
+
+ /**
+ * 寮�鎴烽摱琛�
+ */
+ private String bankName;
+
+ /**
+ * 閾惰璐﹀彿
+ */
+ private String bankAccount;
+
+ /**
+ * 灞呬綇鍦板潃
+ */
+ private String address;
+
+ /**
+ * 澶囨敞
+ */
+ 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/HrmEmployeeEducationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEducationDO.java
new file mode 100644
index 0000000..67c7b80
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEducationDO.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.employee;
+
+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_employee_education")
+@KeySequence("hrm_employee_education_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmEmployeeEducationDO extends BaseDO {
+
+ /**
+ * 璁板綍ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long employeeId;
+
+ /**
+ * 瀛︽牎鍚嶇О
+ */
+ private String schoolName;
+
+ /**
+ * 涓撲笟
+ */
+ private String major;
+
+ /**
+ * 瀛﹀巻
+ *
+ * 1-楂樹腑锛�2-澶т笓锛�3-鏈锛�4-纭曞+锛�5-鍗氬+
+ */
+ private Integer degree;
+
+ /**
+ * 鍏ュ鏃堕棿
+ */
+ private LocalDate startDate;
+
+ /**
+ * 姣曚笟鏃堕棿
+ */
+ private LocalDate endDate;
+
+ /**
+ * 璇佷功缂栧彿
+ */
+ private String certificateNo;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEmergencyContactDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEmergencyContactDO.java
new file mode 100644
index 0000000..b43c922
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEmergencyContactDO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.employee;
+
+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.*;
+
+/**
+ * 鍛樺伐绱ф�ヨ仈绯讳汉 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_employee_emergency_contact")
+@KeySequence("hrm_employee_emergency_contact_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmEmployeeEmergencyContactDO extends BaseDO {
+
+ /**
+ * 璁板綍ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long employeeId;
+
+ /**
+ * 鑱旂郴浜哄鍚�
+ */
+ private String name;
+
+ /**
+ * 涓庡憳宸ュ叧绯�
+ */
+ private String relationship;
+
+ /**
+ * 鑱旂郴鐢佃瘽
+ */
+ private String phone;
+
+ /**
+ * 鑱旂郴鍦板潃
+ */
+ private String address;
+
+ /**
+ * 鎺掑簭锛堝彲璁剧疆澶氫釜绱ф�ヨ仈绯讳汉锛�
+ */
+ private Integer sort;
+
+ /**
+ * 澶囨敞
+ */
+ 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/HrmEmployeeWorkHistoryDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeWorkHistoryDO.java
new file mode 100644
index 0000000..81eea70
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeWorkHistoryDO.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.employee;
+
+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_employee_work_history")
+@KeySequence("hrm_employee_work_history_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmEmployeeWorkHistoryDO extends BaseDO {
+
+ /**
+ * 璁板綍ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long employeeId;
+
+ /**
+ * 鍏徃鍚嶇О
+ */
+ private String companyName;
+
+ /**
+ * 鑱屼綅
+ */
+ private String position;
+
+ /**
+ * 鍏ヨ亴鏃堕棿
+ */
+ private LocalDate startDate;
+
+ /**
+ * 绂昏亴鏃堕棿
+ */
+ private LocalDate endDate;
+
+ /**
+ * 宸ヤ綔鍐呭
+ */
+ private String workContent;
+
+ /**
+ * 绂昏亴鍘熷洜
+ */
+ private String leaveReason;
+
+ /**
+ * 璇佹槑浜哄鍚�
+ */
+ private String referenceName;
+
+ /**
+ * 璇佹槑浜虹數璇�
+ */
+ private String referencePhone;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSalaryDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSalaryDO.java
new file mode 100644
index 0000000..8c29c4a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSalaryDO.java
@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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;
+
+/**
+ * 鍛樺伐钖叕妗f DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_employee_salary")
+@KeySequence("hrm_employee_salary_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmEmployeeSalaryDO extends BaseDO {
+
+ /**
+ * 妗fID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 钖叕缁撴瀯ID
+ */
+ private Long salaryStructureId;
+
+ /**
+ * 鍩烘湰宸ヨ祫
+ */
+ private BigDecimal baseSalary;
+
+ /**
+ * 缁╂晥宸ヨ祫
+ */
+ private BigDecimal performanceSalary;
+
+ /**
+ * 椁愯ˉ
+ */
+ private BigDecimal mealAllowance;
+
+ /**
+ * 浜ら�氳ˉ璐�
+ */
+ private BigDecimal transportAllowance;
+
+ /**
+ * 鍏朵粬琛ヨ创
+ */
+ private BigDecimal otherAllowance;
+
+ /**
+ * 鐢熸晥鏃ユ湡
+ */
+ private LocalDate effectiveDate;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ 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/salary/HrmEmployeeSocialSecurityDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSocialSecurityDO.java
new file mode 100644
index 0000000..2d7163d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSocialSecurityDO.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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;
+
+/**
+ * 鍛樺伐绀句繚鍏Н閲戞。妗� DO
+ *
+ * 鍛樺伐缁戝畾绀句繚鍏Н閲戞柟妗堬紝骞惰缃釜浜哄寲鐨勭即绾冲熀鏁�
+ * 涓�涓ぞ淇濇柟妗堝彲浠ョ粦瀹氬涓憳宸ワ紝涓�涓憳宸ュ彲浠ユ湁澶氫釜绀句繚妗f锛堟寜鐢熸晥鏃ユ湡锛屽彇鏈�鏂扮敓鏁堢殑锛�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_employee_social_security")
+@KeySequence("hrm_employee_social_security_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmEmployeeSocialSecurityDO extends BaseDO {
+
+ /**
+ * 妗fID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 绀句繚鍏Н閲戞柟妗圛D
+ *
+ * 鍏宠仈 HrmSocialSecuritySchemeDO#getId()
+ */
+ private Long socialSecuritySchemeId;
+
+ // ========== 缂寸撼鍩烘暟锛堝憳宸ヤ釜鎬у寲璁剧疆锛� ==========
+ /**
+ * 绀句繚缂寸撼鍩烘暟
+ */
+ private BigDecimal socialSecurityBase;
+
+ /**
+ * 鍏Н閲戠即绾冲熀鏁�
+ */
+ private BigDecimal housingFundBase;
+
+ // ========== 鐢熸晥淇℃伅 ==========
+ /**
+ * 鐢熸晥鏃ユ湡
+ */
+ private LocalDate effectiveDate;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ 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/salary/HrmLeaveTypeConfigDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmLeaveTypeConfigDO.java
new file mode 100644
index 0000000..f33f68c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmLeaveTypeConfigDO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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;
+
+/**
+ * 璇峰亣绫诲瀷鎵f閰嶇疆 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_leave_type_config")
+@KeySequence("hrm_leave_type_config_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmLeaveTypeConfigDO extends BaseDO {
+
+ /**
+ * 涓婚敭
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 璇峰亣绫诲瀷锛�1-浜嬪亣, 2-鐥呭亣, 3-骞村亣, 4-濠氬亣, 5-浜у亣, 6-涓у亣
+ */
+ private Integer leaveType;
+
+ /**
+ * 绫诲瀷鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鎵f姣斾緥锛�0-1涔嬮棿锛屽0.5琛ㄧず鎵�50%锛�
+ */
+ private BigDecimal deductionRatio;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+ /**
+ * 鐘舵�侊細0-鍚敤, 1-绂佺敤
+ */
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java
new file mode 100644
index 0000000..6a10e56
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java
@@ -0,0 +1,150 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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.LocalDateTime;
+
+/**
+ * 钖叕鏍哥畻璁板綍 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_salary_calculation")
+@KeySequence("hrm_salary_calculation_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmSalaryCalculationDO extends BaseDO {
+
+ /**
+ * 鏍哥畻ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鏍哥畻鍗曞彿
+ */
+ private String no;
+
+ /**
+ * 鏍哥畻鍛ㄦ湡锛堝2024-01锛�
+ */
+ private String period;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 鍩烘湰宸ヨ祫
+ */
+ private BigDecimal baseSalary;
+
+ /**
+ * 缁╂晥宸ヨ祫
+ */
+ private BigDecimal performanceSalary;
+
+ /**
+ * 鍔犵彮宸ヨ祫
+ */
+ private BigDecimal overtimePay;
+
+ /**
+ * 椁愯ˉ
+ */
+ private BigDecimal mealAllowance;
+
+ /**
+ * 浜ら�氳ˉ璐�
+ */
+ private BigDecimal transportAllowance;
+
+ /**
+ * 鍏朵粬琛ヨ创
+ */
+ private BigDecimal otherAllowance;
+
+ /**
+ * 绀句繚鎵i櫎
+ */
+ private BigDecimal socialSecurityDeduction;
+
+ /**
+ * 鍏Н閲戞墸闄�
+ */
+ private BigDecimal housingFundDeduction;
+
+ /**
+ * 涓◣鎵i櫎
+ */
+ private BigDecimal taxDeduction;
+
+ /**
+ * 鍏朵粬鎵i櫎
+ */
+ private BigDecimal otherDeduction;
+
+ /**
+ * 璇峰亣鎵f
+ */
+ private BigDecimal leaveDeduction;
+
+ /**
+ * 瀹炲彂宸ヨ祫
+ */
+ private BigDecimal actualSalary;
+
+ /**
+ * 鍑哄嫟澶╂暟
+ */
+ private Integer workDays;
+
+ /**
+ * 鍔犵彮鏃堕暱
+ */
+ private BigDecimal overtimeHours;
+
+ /**
+ * 鐘舵�侊細0-寰呯‘璁わ紝10-宸茬‘璁わ紝20-宸插彂鏀�
+ */
+ private Integer status;
+
+ /**
+ * 纭鏃堕棿
+ */
+ private LocalDateTime confirmTime;
+
+ /**
+ * 纭浜篒D
+ */
+ private Long confirmUserId;
+
+ /**
+ * 鍙戞斁鏃堕棿
+ */
+ private LocalDateTime paymentTime;
+
+ /**
+ * 澶囨敞
+ */
+ 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/salary/HrmSalaryPaymentDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java
new file mode 100644
index 0000000..ae7af88
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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.LocalDateTime;
+
+/**
+ * 钖祫鍙戞斁鍙拌处 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_salary_payment")
+@KeySequence("hrm_salary_payment_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmSalaryPaymentDO extends BaseDO {
+
+ /**
+ * 鍙戞斁ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍙戞斁鍗曞彿
+ */
+ private String no;
+
+ /**
+ * 鍙戞斁鍛ㄦ湡
+ */
+ private String period;
+
+ /**
+ * 鍙戞斁鎬婚噾棰�
+ */
+ private BigDecimal totalAmount;
+
+ /**
+ * 鍙戞斁浜烘暟
+ */
+ private Integer totalCount;
+
+ /**
+ * 鍙戞斁鏂瑰紡锛�1-閾惰杞处锛�2-鐜伴噾
+ */
+ private Integer paymentMethod;
+
+ /**
+ * 鍙戞斁鏃堕棿
+ */
+ private LocalDateTime paymentTime;
+
+ /**
+ * 鐘舵�侊細0-寰呭彂鏀撅紝10-宸插彂鏀�
+ */
+ private Integer status;
+
+ /**
+ * 閾惰鍥炲崟闄勪欢
+ */
+ private String fileUrl;
+
+ /**
+ * 澶囨敞
+ */
+ 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/salary/HrmSalaryPaymentDetailDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDetailDO.java
new file mode 100644
index 0000000..9d547d8
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDetailDO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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.LocalDateTime;
+
+/**
+ * 钖祫鍙戞斁鏄庣粏 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_salary_payment_detail")
+@KeySequence("hrm_salary_payment_detail_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmSalaryPaymentDetailDO extends BaseDO {
+
+ /**
+ * 涓婚敭
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍙戞斁鍙拌处ID
+ */
+ private Long paymentId;
+
+ /**
+ * 钖叕鏍哥畻ID
+ */
+ private Long calculationId;
+
+ /**
+ * 鍛樺伐ID
+ */
+ private Long userId;
+
+ /**
+ * 閮ㄩ棬ID
+ */
+ private Long deptId;
+
+ /**
+ * 瀹炲彂宸ヨ祫
+ */
+ private BigDecimal actualSalary;
+
+ /**
+ * 鍙戞斁鐘舵�侊細0-寰呭彂鏀撅紝10-宸插彂鏀�
+ */
+ private Integer status;
+
+ /**
+ * 鍙戞斁鏃堕棿
+ */
+ private LocalDateTime paymentTime;
+
+ /**
+ * 澶囨敞
+ */
+ 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/salary/HrmSalaryStructureDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java
new file mode 100644
index 0000000..d1d8f57
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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_salary_structure")
+@KeySequence("hrm_salary_structure_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmSalaryStructureDO extends BaseDO {
+
+ /**
+ * 缁撴瀯ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 缁撴瀯鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鍩烘湰宸ヨ祫
+ */
+ private BigDecimal baseSalary;
+
+ /**
+ * 缁╂晥宸ヨ祫鍗犳瘮锛�%锛�
+ */
+ private BigDecimal performanceRatio;
+
+ /**
+ * 鍔犵彮宸ヨ祫鍊嶇巼
+ */
+ private BigDecimal overtimePayRatio;
+
+ /**
+ * 绀句繚鍩烘暟
+ */
+ private BigDecimal socialSecurityBase;
+
+ /**
+ * 鍏Н閲戝熀鏁�
+ */
+ private BigDecimal housingFundBase;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ 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/salary/HrmSocialSecurityConfigDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecurityConfigDO.java
new file mode 100644
index 0000000..a9f7c7b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecurityConfigDO.java
@@ -0,0 +1,123 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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;
+
+/**
+ * 绀句繚鍏Н閲戦厤缃� DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_social_security_config")
+@KeySequence("hrm_social_security_config_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmSocialSecurityConfigDO extends BaseDO {
+
+ /**
+ * 閰嶇疆ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 閰嶇疆鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鐢熸晥鏃ユ湡
+ */
+ private LocalDate effectiveDate;
+
+ // ========== 鍏昏�佷繚闄� ==========
+ /**
+ * 鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�
+ */
+ private BigDecimal pensionCompanyRatio;
+ /**
+ * 鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�
+ */
+ private BigDecimal pensionPersonalRatio;
+
+ // ========== 鍖荤枟淇濋櫓 ==========
+ /**
+ * 鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal medicalCompanyRatio;
+ /**
+ * 鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥
+ */
+ private BigDecimal medicalPersonalRatio;
+
+ // ========== 澶变笟淇濋櫓 ==========
+ /**
+ * 澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal unemploymentCompanyRatio;
+ /**
+ * 澶变笟淇濋櫓涓汉缂寸撼姣斾緥
+ */
+ private BigDecimal unemploymentPersonalRatio;
+
+ // ========== 宸ヤ激淇濋櫓 ==========
+ /**
+ * 宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal workInjuryRatio;
+
+ // ========== 鐢熻偛淇濋櫓 ==========
+ /**
+ * 鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal maternityRatio;
+
+ // ========== 鍏Н閲� ==========
+ /**
+ * 鍏Н閲戜紒涓氱即绾虫瘮渚�
+ */
+ private BigDecimal housingFundCompanyRatio;
+ /**
+ * 鍏Н閲戜釜浜虹即绾虫瘮渚�
+ */
+ private BigDecimal housingFundPersonalRatio;
+
+ // ========== 鍩烘暟涓婁笅闄� ==========
+ /**
+ * 绀句繚鍩烘暟涓嬮檺
+ */
+ private BigDecimal socialSecurityBaseMin;
+ /**
+ * 绀句繚鍩烘暟涓婇檺
+ */
+ private BigDecimal socialSecurityBaseMax;
+ /**
+ * 鍏Н閲戝熀鏁颁笅闄�
+ */
+ private BigDecimal housingFundBaseMin;
+ /**
+ * 鍏Н閲戝熀鏁颁笂闄�
+ */
+ private BigDecimal housingFundBaseMax;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java
new file mode 100644
index 0000000..c07e633
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java
@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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;
+
+/**
+ * 绀句繚鍏Н閲戞柟妗� DO
+ *
+ * 涓�涓柟妗堝彲浠ヨ澶氫釜鍛樺伐缁戝畾
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_social_security_scheme")
+@KeySequence("hrm_social_security_scheme_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmSocialSecuritySchemeDO extends BaseDO {
+
+ /**
+ * 鏂规ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鏂规鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鐢熸晥鏃ユ湡
+ */
+ private LocalDate effectiveDate;
+
+ // ========== 鍏昏�佷繚闄╂瘮渚� ==========
+ /**
+ * 鍏昏�佷繚闄╀紒涓氱即绾虫瘮渚�
+ */
+ private BigDecimal pensionCompanyRatio;
+ /**
+ * 鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�
+ */
+ private BigDecimal pensionPersonalRatio;
+
+ // ========== 鍖荤枟淇濋櫓姣斾緥 ==========
+ /**
+ * 鍖荤枟淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal medicalCompanyRatio;
+ /**
+ * 鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥
+ */
+ private BigDecimal medicalPersonalRatio;
+
+ // ========== 澶变笟淇濋櫓姣斾緥 ==========
+ /**
+ * 澶变笟淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal unemploymentCompanyRatio;
+ /**
+ * 澶变笟淇濋櫓涓汉缂寸撼姣斾緥
+ */
+ private BigDecimal unemploymentPersonalRatio;
+
+ // ========== 宸ヤ激淇濋櫓姣斾緥锛堜粎浼佷笟缂寸撼锛� ==========
+ /**
+ * 宸ヤ激淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal workInjuryRatio;
+
+ // ========== 鐢熻偛淇濋櫓姣斾緥锛堜粎浼佷笟缂寸撼锛� ==========
+ /**
+ * 鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥
+ */
+ private BigDecimal maternityRatio;
+
+ // ========== 鍏Н閲戞瘮渚� ==========
+ /**
+ * 鍏Н閲戜紒涓氱即绾虫瘮渚�
+ */
+ private BigDecimal housingFundCompanyRatio;
+ /**
+ * 鍏Н閲戜釜浜虹即绾虫瘮渚�
+ */
+ private BigDecimal housingFundPersonalRatio;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmTaxRateConfigDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmTaxRateConfigDO.java
new file mode 100644
index 0000000..7c4ec48
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmTaxRateConfigDO.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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;
+
+/**
+ * 涓◣绋庣巼閰嶇疆 DO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@TableName("hrm_tax_rate_config")
+@KeySequence("hrm_tax_rate_config_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmTaxRateConfigDO extends BaseDO {
+
+ /**
+ * 閰嶇疆ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 閰嶇疆鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 鐢熸晥鏃ユ湡
+ */
+ private LocalDate effectiveDate;
+
+ /**
+ * 绋庣巼绾ф暟锛�1-7
+ */
+ private Integer level;
+
+ /**
+ * 搴旂撼绋庢墍寰楅涓嬮檺锛堝惈锛�
+ */
+ private BigDecimal minAmount;
+
+ /**
+ * 搴旂撼绋庢墍寰楅涓婇檺锛堜笉鍚級锛�0琛ㄧず鏃犱笂闄�
+ */
+ private BigDecimal maxAmount;
+
+ /**
+ * 绋庣巼
+ */
+ private BigDecimal taxRate;
+
+ /**
+ * 閫熺畻鎵i櫎鏁�
+ */
+ private BigDecimal quickDeduction;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java
new file mode 100644
index 0000000..da80141
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java
@@ -0,0 +1,57 @@
+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.HrmLeaveApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 璇峰亣鐢宠 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmLeaveApplicationMapper extends BaseMapperX<HrmLeaveApplicationDO> {
+
+ default PageResult<HrmLeaveApplicationDO> selectPage(HrmLeaveApplicationPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmLeaveApplicationDO>()
+ .likeIfPresent(HrmLeaveApplicationDO::getNo, reqVO.getNo())
+ .eqIfPresent(HrmLeaveApplicationDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmLeaveApplicationDO::getDeptId, reqVO.getDeptId())
+ .eqIfPresent(HrmLeaveApplicationDO::getLeaveType, reqVO.getLeaveType())
+ .eqIfPresent(HrmLeaveApplicationDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(HrmLeaveApplicationDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(HrmLeaveApplicationDO::getId));
+ }
+
+ default HrmLeaveApplicationDO selectByNo(String no) {
+ return selectOne(HrmLeaveApplicationDO::getNo, no);
+ }
+
+ default HrmLeaveApplicationDO selectByProcessInstanceId(String processInstanceId) {
+ return selectOne(HrmLeaveApplicationDO::getProcessInstanceId, processInstanceId);
+ }
+
+ /**
+ * 鏌ヨ鍛樺伐鍦ㄦ寚瀹氭椂闂磋寖鍥村唴宸插鎵归�氳繃鐨勮鍋囪褰�
+ *
+ * @param userId 鍛樺伐ID锛堢郴缁熺敤鎴稩D锛�
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 璇峰亣璁板綍鍒楄〃
+ */
+ default List<HrmLeaveApplicationDO> selectApprovedListByUserIdAndTimeRange(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
+ return selectList(new LambdaQueryWrapperX<HrmLeaveApplicationDO>()
+ .eq(HrmLeaveApplicationDO::getUserId, userId)
+ .eq(HrmLeaveApplicationDO::getStatus, HrmAuditStatusEnum.APPROVE.getStatus())
+ .ge(HrmLeaveApplicationDO::getStartTime, startTime)
+ .le(HrmLeaveApplicationDO::getEndTime, endTime));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmResignationApplicationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmResignationApplicationMapper.java
new file mode 100644
index 0000000..82f2bdf
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmResignationApplicationMapper.java
@@ -0,0 +1,43 @@
+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.HrmResignationApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 绂昏亴鐢宠 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmResignationApplicationMapper extends BaseMapperX<HrmResignationApplicationDO> {
+
+ default PageResult<HrmResignationApplicationDO> selectPage(HrmResignationApplicationPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmResignationApplicationDO>()
+ .likeIfPresent(HrmResignationApplicationDO::getNo, reqVO.getNo())
+ .eqIfPresent(HrmResignationApplicationDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmResignationApplicationDO::getDeptId, reqVO.getDeptId())
+ .eqIfPresent(HrmResignationApplicationDO::getResignationType, reqVO.getResignationType())
+ .eqIfPresent(HrmResignationApplicationDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(HrmResignationApplicationDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(HrmResignationApplicationDO::getId));
+ }
+
+ default HrmResignationApplicationDO selectByNo(String no) {
+ return selectOne(HrmResignationApplicationDO::getNo, no);
+ }
+
+ default HrmResignationApplicationDO selectByProcessInstanceId(String processInstanceId) {
+ return selectOne(HrmResignationApplicationDO::getProcessInstanceId, processInstanceId);
+ }
+
+ default HrmResignationApplicationDO selectByUserIdAndStatusNot(Long userId, Integer status) {
+ return selectOne(new LambdaQueryWrapperX<HrmResignationApplicationDO>()
+ .eq(HrmResignationApplicationDO::getUserId, userId)
+ .ne(HrmResignationApplicationDO::getStatus, status));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmTransferApplicationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmTransferApplicationMapper.java
new file mode 100644
index 0000000..daa6fc8
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmTransferApplicationMapper.java
@@ -0,0 +1,38 @@
+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.HrmTransferApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmTransferApplicationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 璋冨矖鐢宠 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmTransferApplicationMapper extends BaseMapperX<HrmTransferApplicationDO> {
+
+ default PageResult<HrmTransferApplicationDO> selectPage(HrmTransferApplicationPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmTransferApplicationDO>()
+ .likeIfPresent(HrmTransferApplicationDO::getNo, reqVO.getNo())
+ .eqIfPresent(HrmTransferApplicationDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmTransferApplicationDO::getCurrentDeptId, reqVO.getCurrentDeptId())
+ .eqIfPresent(HrmTransferApplicationDO::getTargetDeptId, reqVO.getTargetDeptId())
+ .eqIfPresent(HrmTransferApplicationDO::getTransferType, reqVO.getTransferType())
+ .eqIfPresent(HrmTransferApplicationDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(HrmTransferApplicationDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(HrmTransferApplicationDO::getId));
+ }
+
+ default HrmTransferApplicationDO selectByNo(String no) {
+ return selectOne(HrmTransferApplicationDO::getNo, no);
+ }
+
+ default HrmTransferApplicationDO selectByProcessInstanceId(String processInstanceId) {
+ return selectOne(HrmTransferApplicationDO::getProcessInstanceId, processInstanceId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceExceptionMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceExceptionMapper.java
new file mode 100644
index 0000000..e629afc
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceExceptionMapper.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.attendance;
+
+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.attendance.vo.HrmAttendanceExceptionPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 鑰冨嫟寮傚父鐢宠 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmAttendanceExceptionMapper extends BaseMapperX<HrmAttendanceExceptionDO> {
+
+ default PageResult<HrmAttendanceExceptionDO> selectPage(HrmAttendanceExceptionPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmAttendanceExceptionDO>()
+ .likeIfPresent(HrmAttendanceExceptionDO::getNo, reqVO.getNo())
+ .eqIfPresent(HrmAttendanceExceptionDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmAttendanceExceptionDO::getDeptId, reqVO.getDeptId())
+ .eqIfPresent(HrmAttendanceExceptionDO::getType, reqVO.getType())
+ .eqIfPresent(HrmAttendanceExceptionDO::getStatus, reqVO.getStatus())
+ .orderByDesc(HrmAttendanceExceptionDO::getId));
+ }
+
+ default HrmAttendanceExceptionDO selectByNo(String no) {
+ return selectOne(HrmAttendanceExceptionDO::getNo, no);
+ }
+
+ default HrmAttendanceExceptionDO selectByProcessInstanceId(String processInstanceId) {
+ return selectOne(HrmAttendanceExceptionDO::getProcessInstanceId, processInstanceId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java
new file mode 100644
index 0000000..44c03af
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.attendance;
+
+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.attendance.HrmAttendanceHolidayDO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceHolidayPageReqVO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 鑰冨嫟鑺傚亣鏃ラ厤缃� Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmAttendanceHolidayMapper extends BaseMapperX<HrmAttendanceHolidayDO> {
+
+ default PageResult<HrmAttendanceHolidayDO> selectPage(HrmAttendanceHolidayPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
+ .likeIfPresent(HrmAttendanceHolidayDO::getName, reqVO.getName())
+ .eqIfPresent(HrmAttendanceHolidayDO::getType, reqVO.getType())
+ .betweenIfPresent(HrmAttendanceHolidayDO::getDate, reqVO.getStartDate(), reqVO.getEndDate())
+ .orderByDesc(HrmAttendanceHolidayDO::getDate));
+ }
+
+ default HrmAttendanceHolidayDO selectByDate(LocalDate date) {
+ return selectOne(HrmAttendanceHolidayDO::getDate, date);
+ }
+
+ default List<HrmAttendanceHolidayDO> selectListByDateBetween(LocalDate startDate, LocalDate endDate) {
+ return selectList(new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
+ .ge(HrmAttendanceHolidayDO::getDate, startDate)
+ .le(HrmAttendanceHolidayDO::getDate, endDate)
+ .orderByAsc(HrmAttendanceHolidayDO::getDate));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
new file mode 100644
index 0000000..292540d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.attendance;
+
+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.attendance.vo.HrmAttendanceRecordPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 鑰冨嫟鎵撳崱璁板綍 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmAttendanceRecordMapper extends BaseMapperX<HrmAttendanceRecordDO> {
+
+ default PageResult<HrmAttendanceRecordDO> selectPage(HrmAttendanceRecordPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmAttendanceRecordDO>()
+ .eqIfPresent(HrmAttendanceRecordDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmAttendanceRecordDO::getDeptId, reqVO.getDeptId())
+ .geIfPresent(HrmAttendanceRecordDO::getDate, reqVO.getStartDate())
+ .leIfPresent(HrmAttendanceRecordDO::getDate, reqVO.getEndDate())
+ .eqIfPresent(HrmAttendanceRecordDO::getClockInType, reqVO.getClockInType())
+ .eqIfPresent(HrmAttendanceRecordDO::getClockOutType, reqVO.getClockOutType())
+ .orderByDesc(HrmAttendanceRecordDO::getDate));
+ }
+
+ default HrmAttendanceRecordDO selectByUserIdAndDate(Long userId, LocalDate date) {
+ return selectOne(new LambdaQueryWrapperX<HrmAttendanceRecordDO>()
+ .eq(HrmAttendanceRecordDO::getUserId, userId)
+ .eq(HrmAttendanceRecordDO::getDate, date));
+ }
+
+ default List<HrmAttendanceRecordDO> selectListByUserIdAndDateBetween(Long userId, LocalDate startDate, LocalDate endDate) {
+ return selectList(new LambdaQueryWrapperX<HrmAttendanceRecordDO>()
+ .eq(HrmAttendanceRecordDO::getUserId, userId)
+ .ge(HrmAttendanceRecordDO::getDate, startDate)
+ .le(HrmAttendanceRecordDO::getDate, endDate)
+ .orderByAsc(HrmAttendanceRecordDO::getDate));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRuleMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRuleMapper.java
new file mode 100644
index 0000000..eeeb06b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRuleMapper.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.attendance;
+
+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.attendance.vo.HrmAttendanceRulePageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鑰冨嫟瑙勫垯閰嶇疆 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmAttendanceRuleMapper extends BaseMapperX<HrmAttendanceRuleDO> {
+
+ default PageResult<HrmAttendanceRuleDO> selectPage(HrmAttendanceRulePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmAttendanceRuleDO>()
+ .likeIfPresent(HrmAttendanceRuleDO::getName, reqVO.getName())
+ .eqIfPresent(HrmAttendanceRuleDO::getDeptId, reqVO.getDeptId())
+ .eqIfPresent(HrmAttendanceRuleDO::getStatus, reqVO.getStatus())
+ .orderByDesc(HrmAttendanceRuleDO::getId));
+ }
+
+ default List<HrmAttendanceRuleDO> selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmAttendanceRuleDO>()
+ .eqIfPresent(HrmAttendanceRuleDO::getStatus, status)
+ .orderByAsc(HrmAttendanceRuleDO::getName));
+ }
+
+ default List<HrmAttendanceRuleDO> selectListByDeptId(Long deptId) {
+ return selectList(new LambdaQueryWrapperX<HrmAttendanceRuleDO>()
+ .eq(HrmAttendanceRuleDO::getDeptId, deptId)
+ .orderByAsc(HrmAttendanceRuleDO::getName));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceSummaryMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceSummaryMapper.java
new file mode 100644
index 0000000..1a655a4
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceSummaryMapper.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.attendance;
+
+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.attendance.HrmAttendanceSummaryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鑰冨嫟缁熻姹囨�� Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmAttendanceSummaryMapper extends BaseMapperX<HrmAttendanceSummaryDO> {
+
+ default HrmAttendanceSummaryDO selectByUserIdAndYearMonth(Long userId, String yearMonth) {
+ return selectOne(new LambdaQueryWrapperX<HrmAttendanceSummaryDO>()
+ .eq(HrmAttendanceSummaryDO::getUserId, userId)
+ .eq(HrmAttendanceSummaryDO::getYearMonth, yearMonth));
+ }
+
+ default List<HrmAttendanceSummaryDO> selectListByDeptIdAndYearMonth(Long deptId, String yearMonth) {
+ return selectList(new LambdaQueryWrapperX<HrmAttendanceSummaryDO>()
+ .eq(HrmAttendanceSummaryDO::getDeptId, deptId)
+ .eq(HrmAttendanceSummaryDO::getYearMonth, yearMonth)
+ .orderByAsc(HrmAttendanceSummaryDO::getUserId));
+ }
+
+ default List<HrmAttendanceSummaryDO> selectListByYearMonth(String yearMonth) {
+ return selectList(new LambdaQueryWrapperX<HrmAttendanceSummaryDO>()
+ .eq(HrmAttendanceSummaryDO::getYearMonth, yearMonth)
+ .orderByAsc(HrmAttendanceSummaryDO::getDeptId)
+ .orderByAsc(HrmAttendanceSummaryDO::getUserId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java
new file mode 100644
index 0000000..35aea9b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.employee;
+
+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.employee.HrmEmployeeEducationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐鏁欒偛缁忓巻 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmEmployeeEducationMapper extends BaseMapperX<HrmEmployeeEducationDO> {
+
+ default List<HrmEmployeeEducationDO> selectByEmployeeId(Long employeeId) {
+ return selectList(HrmEmployeeEducationDO::getEmployeeId, employeeId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java
new file mode 100644
index 0000000..8a592ba
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.employee;
+
+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.employee.HrmEmployeeEmergencyContactDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐绱ф�ヨ仈绯讳汉 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmEmployeeEmergencyContactMapper extends BaseMapperX<HrmEmployeeEmergencyContactDO> {
+
+ default List<HrmEmployeeEmergencyContactDO> selectByEmployeeId(Long employeeId) {
+ return selectList(new LambdaQueryWrapperX<HrmEmployeeEmergencyContactDO>()
+ .eq(HrmEmployeeEmergencyContactDO::getEmployeeId, employeeId)
+ .orderByAsc(HrmEmployeeEmergencyContactDO::getSort));
+ }
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..e247e90
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.employee;
+
+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.employee.vo.HrmEmployeePageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐淇℃伅 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmEmployeeMapper extends BaseMapperX<HrmEmployeeDO> {
+
+ default HrmEmployeeDO selectByPhone(String phone) {
+ return selectOne(HrmEmployeeDO::getPhone, phone);
+ }
+
+ default HrmEmployeeDO selectByEmployeeNo(String employeeNo) {
+ return selectOne(HrmEmployeeDO::getEmployeeNo, employeeNo);
+ }
+
+ default HrmEmployeeDO selectByUserId(Long userId) {
+ return selectOne(HrmEmployeeDO::getUserId, userId);
+ }
+
+ default List<HrmEmployeeDO> selectByDeptId(Long deptId) {
+ return selectList(HrmEmployeeDO::getDeptId, deptId);
+ }
+
+ default List<HrmEmployeeDO> selectByEmployeeStatus(Integer employeeStatus) {
+ return selectList(HrmEmployeeDO::getEmployeeStatus, employeeStatus);
+ }
+
+ default PageResult<HrmEmployeeDO> selectPage(HrmEmployeePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmEmployeeDO>()
+ .likeIfPresent(HrmEmployeeDO::getEmployeeNo, reqVO.getEmployeeNo())
+ .likeIfPresent(HrmEmployeeDO::getName, reqVO.getName())
+ .eqIfPresent(HrmEmployeeDO::getDeptId, reqVO.getDeptId())
+ .eqIfPresent(HrmEmployeeDO::getEmployeeStatus, reqVO.getEmployeeStatus())
+ .orderByDesc(HrmEmployeeDO::getId));
+ }
+
+ default List<HrmEmployeeDO> selectListByStatus(Integer employeeStatus) {
+ return selectList(new LambdaQueryWrapperX<HrmEmployeeDO>()
+ .eqIfPresent(HrmEmployeeDO::getEmployeeStatus, employeeStatus)
+ .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/employee/HrmEmployeeWorkHistoryMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java
new file mode 100644
index 0000000..465e9c9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.employee;
+
+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.employee.HrmEmployeeWorkHistoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐宸ヤ綔缁忓巻 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmEmployeeWorkHistoryMapper extends BaseMapperX<HrmEmployeeWorkHistoryDO> {
+
+ default List<HrmEmployeeWorkHistoryDO> selectByEmployeeId(Long employeeId) {
+ return selectList(HrmEmployeeWorkHistoryDO::getEmployeeId, employeeId);
+ }
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..cd43c13
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
+
+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.salary.HrmEmployeeSalaryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 鍛樺伐钖叕妗f Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmEmployeeSalaryMapper extends BaseMapperX<HrmEmployeeSalaryDO> {
+
+ default HrmEmployeeSalaryDO selectByUserIdAndEffectiveDate(Long userId, LocalDate effectiveDate) {
+ return selectOne(new LambdaQueryWrapperX<HrmEmployeeSalaryDO>()
+ .eq(HrmEmployeeSalaryDO::getUserId, userId)
+ .eq(HrmEmployeeSalaryDO::getEffectiveDate, effectiveDate));
+ }
+
+ default HrmEmployeeSalaryDO selectLatestByUserId(Long userId) {
+ return selectOne(new LambdaQueryWrapperX<HrmEmployeeSalaryDO>()
+ .eq(HrmEmployeeSalaryDO::getUserId, userId)
+ .eq(HrmEmployeeSalaryDO::getStatus, 0)
+ .orderByDesc(HrmEmployeeSalaryDO::getEffectiveDate)
+ .last("LIMIT 1"));
+ }
+
+ default List<HrmEmployeeSalaryDO> selectListByUserId(Long userId) {
+ return selectList(new LambdaQueryWrapperX<HrmEmployeeSalaryDO>()
+ .eq(HrmEmployeeSalaryDO::getUserId, userId)
+ .orderByDesc(HrmEmployeeSalaryDO::getEffectiveDate));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSocialSecurityMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSocialSecurityMapper.java
new file mode 100644
index 0000000..aa0cb56
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSocialSecurityMapper.java
@@ -0,0 +1,43 @@
+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.HrmEmployeeSocialSecurityPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 鍛樺伐绀句繚鍏Н閲戞。妗� Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmEmployeeSocialSecurityMapper extends BaseMapperX<HrmEmployeeSocialSecurityDO> {
+
+ default HrmEmployeeSocialSecurityDO selectLatestByUserId(Long userId) {
+ return selectOne(new LambdaQueryWrapperX<HrmEmployeeSocialSecurityDO>()
+ .eq(HrmEmployeeSocialSecurityDO::getUserId, userId)
+ .eq(HrmEmployeeSocialSecurityDO::getStatus, 0)
+ .orderByDesc(HrmEmployeeSocialSecurityDO::getEffectiveDate)
+ .last("LIMIT 1"));
+ }
+
+ default List<HrmEmployeeSocialSecurityDO> selectListByUserId(Long userId) {
+ return selectList(new LambdaQueryWrapperX<HrmEmployeeSocialSecurityDO>()
+ .eq(HrmEmployeeSocialSecurityDO::getUserId, userId)
+ .orderByDesc(HrmEmployeeSocialSecurityDO::getEffectiveDate));
+ }
+
+ default PageResult<HrmEmployeeSocialSecurityDO> selectPage(HrmEmployeeSocialSecurityPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmEmployeeSocialSecurityDO>()
+ .eqIfPresent(HrmEmployeeSocialSecurityDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmEmployeeSocialSecurityDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(HrmEmployeeSocialSecurityDO::getEffectiveDate, reqVO.getEffectiveDate())
+ .orderByDesc(HrmEmployeeSocialSecurityDO::getId));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmLeaveTypeConfigMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmLeaveTypeConfigMapper.java
new file mode 100644
index 0000000..c5182d7
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmLeaveTypeConfigMapper.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
+
+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.salary.HrmLeaveTypeConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 璇峰亣绫诲瀷鎵f閰嶇疆 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmLeaveTypeConfigMapper extends BaseMapperX<HrmLeaveTypeConfigDO> {
+
+ default List<HrmLeaveTypeConfigDO> selectList() {
+ return selectList(new LambdaQueryWrapperX<HrmLeaveTypeConfigDO>()
+ .orderByAsc(HrmLeaveTypeConfigDO::getLeaveType));
+ }
+
+ default HrmLeaveTypeConfigDO selectByLeaveType(Integer leaveType) {
+ return selectOne(HrmLeaveTypeConfigDO::getLeaveType, leaveType);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryCalculationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryCalculationMapper.java
new file mode 100644
index 0000000..549ed48
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryCalculationMapper.java
@@ -0,0 +1,60 @@
+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.HrmSalaryCalculationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryCalculationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 钖叕鏍哥畻璁板綍 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmSalaryCalculationMapper extends BaseMapperX<HrmSalaryCalculationDO> {
+
+ default PageResult<HrmSalaryCalculationDO> selectPage(HrmSalaryCalculationPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmSalaryCalculationDO>()
+ .likeIfPresent(HrmSalaryCalculationDO::getNo, reqVO.getNo())
+ .eqIfPresent(HrmSalaryCalculationDO::getPeriod, reqVO.getPeriod())
+ .eqIfPresent(HrmSalaryCalculationDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmSalaryCalculationDO::getDeptId, reqVO.getDeptId())
+ .eqIfPresent(HrmSalaryCalculationDO::getStatus, reqVO.getStatus())
+ .orderByDesc(HrmSalaryCalculationDO::getId));
+ }
+
+ default HrmSalaryCalculationDO selectByNo(String no) {
+ return selectOne(HrmSalaryCalculationDO::getNo, no);
+ }
+
+ default List<HrmSalaryCalculationDO> selectListByUserIdAndPeriod(Long userId, String period) {
+ return selectList(new LambdaQueryWrapperX<HrmSalaryCalculationDO>()
+ .eq(HrmSalaryCalculationDO::getUserId, userId)
+ .eq(HrmSalaryCalculationDO::getPeriod, period));
+ }
+
+ default List<HrmSalaryCalculationDO> selectListByPeriod(String period) {
+ return selectList(new LambdaQueryWrapperX<HrmSalaryCalculationDO>()
+ .eq(HrmSalaryCalculationDO::getPeriod, period)
+ .orderByAsc(HrmSalaryCalculationDO::getUserId));
+ }
+
+ default List<HrmSalaryCalculationDO> selectListByPeriodAndDept(String period, Long deptId) {
+ return selectList(new LambdaQueryWrapperX<HrmSalaryCalculationDO>()
+ .eq(HrmSalaryCalculationDO::getPeriod, period)
+ .eqIfPresent(HrmSalaryCalculationDO::getDeptId, deptId)
+ .orderByAsc(HrmSalaryCalculationDO::getUserId));
+ }
+
+ default List<HrmSalaryCalculationDO> selectListByPeriodAndStatus(String period, Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmSalaryCalculationDO>()
+ .eq(HrmSalaryCalculationDO::getPeriod, period)
+ .eq(HrmSalaryCalculationDO::getStatus, status)
+ .orderByAsc(HrmSalaryCalculationDO::getUserId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentDetailMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentDetailMapper.java
new file mode 100644
index 0000000..94b23f6
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentDetailMapper.java
@@ -0,0 +1,43 @@
+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.HrmSalaryPaymentDetailPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDetailDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 钖祫鍙戞斁鏄庣粏 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmSalaryPaymentDetailMapper extends BaseMapperX<HrmSalaryPaymentDetailDO> {
+
+ default PageResult<HrmSalaryPaymentDetailDO> selectPage(HrmSalaryPaymentDetailPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmSalaryPaymentDetailDO>()
+ .eqIfPresent(HrmSalaryPaymentDetailDO::getPaymentId, reqVO.getPaymentId())
+ .eqIfPresent(HrmSalaryPaymentDetailDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(HrmSalaryPaymentDetailDO::getDeptId, reqVO.getDeptId())
+ .eqIfPresent(HrmSalaryPaymentDetailDO::getStatus, reqVO.getStatus())
+ .orderByAsc(HrmSalaryPaymentDetailDO::getUserId));
+ }
+
+ default List<HrmSalaryPaymentDetailDO> selectListByPaymentId(Long paymentId) {
+ return selectList(HrmSalaryPaymentDetailDO::getPaymentId, paymentId);
+ }
+
+ default List<HrmSalaryPaymentDetailDO> selectListByPaymentIdAndStatus(Long paymentId, Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmSalaryPaymentDetailDO>()
+ .eq(HrmSalaryPaymentDetailDO::getPaymentId, paymentId)
+ .eq(HrmSalaryPaymentDetailDO::getStatus, status));
+ }
+
+ default HrmSalaryPaymentDetailDO selectByCalculationId(Long calculationId) {
+ return selectOne(HrmSalaryPaymentDetailDO::getCalculationId, calculationId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentMapper.java
new file mode 100644
index 0000000..a2b6e27
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentMapper.java
@@ -0,0 +1,37 @@
+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.HrmSalaryPaymentPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 钖祫鍙戞斁鍙拌处 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmSalaryPaymentMapper extends BaseMapperX<HrmSalaryPaymentDO> {
+
+ default PageResult<HrmSalaryPaymentDO> selectPage(HrmSalaryPaymentPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmSalaryPaymentDO>()
+ .likeIfPresent(HrmSalaryPaymentDO::getNo, reqVO.getNo())
+ .eqIfPresent(HrmSalaryPaymentDO::getPeriod, reqVO.getPeriod())
+ .eqIfPresent(HrmSalaryPaymentDO::getStatus, reqVO.getStatus())
+ .orderByDesc(HrmSalaryPaymentDO::getId));
+ }
+
+ default HrmSalaryPaymentDO selectByNo(String no) {
+ return selectOne(HrmSalaryPaymentDO::getNo, no);
+ }
+
+ default HrmSalaryPaymentDO selectByPeriod(String period) {
+ return selectOne(new LambdaQueryWrapperX<HrmSalaryPaymentDO>()
+ .eq(HrmSalaryPaymentDO::getPeriod, period)
+ .orderByDesc(HrmSalaryPaymentDO::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/salary/HrmSalaryStructureMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
new file mode 100644
index 0000000..6952249
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
+
+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.salary.HrmSalaryStructureDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 钖叕缁撴瀯 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmSalaryStructureMapper extends BaseMapperX<HrmSalaryStructureDO> {
+
+ default List<HrmSalaryStructureDO> selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmSalaryStructureDO>()
+ .eqIfPresent(HrmSalaryStructureDO::getStatus, status)
+ .orderByAsc(HrmSalaryStructureDO::getName));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecurityConfigMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecurityConfigMapper.java
new file mode 100644
index 0000000..cc4a143
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecurityConfigMapper.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
+
+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.salary.HrmSocialSecurityConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 绀句繚鍏Н閲戦厤缃� Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmSocialSecurityConfigMapper extends BaseMapperX<HrmSocialSecurityConfigDO> {
+
+ default List<HrmSocialSecurityConfigDO> selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmSocialSecurityConfigDO>()
+ .eqIfPresent(HrmSocialSecurityConfigDO::getStatus, status)
+ .orderByDesc(HrmSocialSecurityConfigDO::getEffectiveDate));
+ }
+
+ default HrmSocialSecurityConfigDO selectLatestByEffectiveDate(LocalDate effectiveDate) {
+ return selectOne(new LambdaQueryWrapperX<HrmSocialSecurityConfigDO>()
+ .eq(HrmSocialSecurityConfigDO::getStatus, 0)
+ .le(HrmSocialSecurityConfigDO::getEffectiveDate, effectiveDate)
+ .orderByDesc(HrmSocialSecurityConfigDO::getEffectiveDate)
+ .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/salary/HrmSocialSecuritySchemeMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeMapper.java
new file mode 100644
index 0000000..171952b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
+
+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.salary.HrmSocialSecuritySchemeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 绀句繚鍏Н閲戞柟妗� Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmSocialSecuritySchemeMapper extends BaseMapperX<HrmSocialSecuritySchemeDO> {
+
+ default List<HrmSocialSecuritySchemeDO> selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmSocialSecuritySchemeDO>()
+ .eqIfPresent(HrmSocialSecuritySchemeDO::getStatus, status)
+ .orderByDesc(HrmSocialSecuritySchemeDO::getEffectiveDate));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmTaxRateConfigMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmTaxRateConfigMapper.java
new file mode 100644
index 0000000..9c19078
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmTaxRateConfigMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
+
+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.salary.HrmTaxRateConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 涓◣绋庣巼閰嶇疆 Mapper
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Mapper
+public interface HrmTaxRateConfigMapper extends BaseMapperX<HrmTaxRateConfigDO> {
+
+ default List<HrmTaxRateConfigDO> selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmTaxRateConfigDO>()
+ .eqIfPresent(HrmTaxRateConfigDO::getStatus, status)
+ .orderByAsc(HrmTaxRateConfigDO::getLevel));
+ }
+
+ default List<HrmTaxRateConfigDO> selectListByEffectiveDate(LocalDate effectiveDate) {
+ return selectList(new LambdaQueryWrapperX<HrmTaxRateConfigDO>()
+ .eq(HrmTaxRateConfigDO::getStatus, 0)
+ .le(HrmTaxRateConfigDO::getEffectiveDate, effectiveDate)
+ .orderByAsc(HrmTaxRateConfigDO::getLevel));
+ }
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..88ad455
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java
@@ -0,0 +1,134 @@
+package cn.iocoder.yudao.module.hrm.dal.redis;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+/**
+ * HRM 缂栧彿鐢熸垚鐨� Redis DAO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Repository
+public class HrmNoRedisDAO {
+
+ /**
+ * 鍛樺伐缂栧彿鍓嶇紑
+ */
+ public static final String EMPLOYEE_NO_PREFIX = "EMP";
+
+ /**
+ * 鑰冨嫟寮傚父鐢宠缂栧彿鍓嶇紑
+ */
+ public static final String ATTENDANCE_EXCEPTION_NO_PREFIX = "KQ";
+
+ /**
+ * 钖叕鏍哥畻缂栧彿鍓嶇紑
+ */
+ public static final String SALARY_CALCULATION_NO_PREFIX = "SC";
+
+ /**
+ * 钖祫鍙戞斁缂栧彿鍓嶇紑
+ */
+ public static final String SALARY_PAYMENT_NO_PREFIX = "SF";
+
+ /**
+ * 璇峰亣鐢宠缂栧彿鍓嶇紑
+ */
+ public static final String LEAVE_APPLICATION_NO_PREFIX = "LA";
+
+ /**
+ * 璋冨矖鐢宠缂栧彿鍓嶇紑
+ */
+ public static final String TRANSFER_APPLICATION_NO_PREFIX = "TA";
+
+ /**
+ * 绂昏亴鐢宠缂栧彿鍓嶇紑
+ */
+ public static final String RESIGNATION_APPLICATION_NO_PREFIX = "RA";
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ /**
+ * 鐢熸垚鍛樺伐缂栧彿锛屾牸寮忥細EMP + yyyyMMdd + 4浣嶈嚜澧�
+ * 渚嬪锛欵MP202401010001
+ *
+ * @return 鍛樺伐缂栧彿
+ */
+ public String generateEmployeeNo() {
+ return generate(EMPLOYEE_NO_PREFIX, 4);
+ }
+
+ /**
+ * 鐢熸垚璇峰亣鐢宠缂栧彿锛屾牸寮忥細LA + yyyyMMdd + 4浣嶈嚜澧�
+ * 渚嬪锛歀A202401010001
+ *
+ * @return 璇峰亣鐢宠缂栧彿
+ */
+ public String generateLeaveApplicationNo() {
+ return generate(LEAVE_APPLICATION_NO_PREFIX, 4);
+ }
+
+ /**
+ * 鐢熸垚璋冨矖鐢宠缂栧彿锛屾牸寮忥細TA + yyyyMMdd + 4浣嶈嚜澧�
+ * 渚嬪锛歍A202401010001
+ *
+ * @return 璋冨矖鐢宠缂栧彿
+ */
+ public String generateTransferApplicationNo() {
+ return generate(TRANSFER_APPLICATION_NO_PREFIX, 4);
+ }
+
+ /**
+ * 鐢熸垚绂昏亴鐢宠缂栧彿锛屾牸寮忥細RA + yyyyMMdd + 4浣嶈嚜澧�
+ * 渚嬪锛歊A202401010001
+ *
+ * @return 绂昏亴鐢宠缂栧彿
+ */
+ public String generateResignationApplicationNo() {
+ return generate(RESIGNATION_APPLICATION_NO_PREFIX, 4);
+ }
+
+ /**
+ * 鐢熸垚钖叕鏍哥畻缂栧彿锛屾牸寮忥細SC + yyyyMMdd + 4浣嶈嚜澧�
+ * 渚嬪锛歋C202401010001
+ *
+ * @return 钖叕鏍哥畻缂栧彿
+ */
+ public String generateSalaryCalculationNo() {
+ return generate(SALARY_CALCULATION_NO_PREFIX, 4);
+ }
+
+ /**
+ * 鐢熸垚钖祫鍙戞斁缂栧彿锛屾牸寮忥細SF + yyyyMMdd + 4浣嶈嚜澧�
+ * 渚嬪锛歋F202401010001
+ *
+ * @return 钖祫鍙戞斁缂栧彿
+ */
+ public String generateSalaryPaymentNo() {
+ return generate(SALARY_PAYMENT_NO_PREFIX, 4);
+ }
+
+ /**
+ * 鐢熸垚缂栧彿
+ *
+ * @param prefix 鍓嶇紑
+ * @param digits 浣嶆暟
+ * @return 缂栧彿
+ */
+ public String generate(String prefix, int digits) {
+ String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
+ String key = HrmRedisKeyConstants.NO + noPrefix;
+ Long no = stringRedisTemplate.opsForValue().increment(key);
+ // 璁剧疆杩囨湡鏃堕棿 1 澶�
+ stringRedisTemplate.expire(key, Duration.ofDays(1L));
+ return noPrefix + String.format("%0" + digits + "d", no);
+ }
+
+}
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
new file mode 100644
index 0000000..d198904
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.hrm.dal.redis;
+
+/**
+ * HRM Redis Key 甯搁噺
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmRedisKeyConstants {
+
+ /**
+ * 缂栧彿鐢熸垚鐨� Key 鍓嶇紑
+ */
+ String NO = "hrm:no:";
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java
new file mode 100644
index 0000000..c7d9f7e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java
@@ -0,0 +1,108 @@
+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.HrmLeaveApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 璇峰亣鐢宠 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmLeaveApplicationService {
+
+ /**
+ * BPM 璇峰亣瀹℃壒娴佺▼鏍囪瘑
+ */
+ String BPM_PROCESS_DEFINITION_KEY = "hrm_leave";
+
+ /**
+ * BPM 璇峰亣瀹℃壒鍒嗙被缂栫爜
+ */
+ String APPROVE_CATEGORY_CODE = "hrm_leave_approve";
+
+ /**
+ * 鍒涘缓璇峰亣鐢宠
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 鐢宠ID
+ */
+ Long createLeaveApplication(@Valid HrmLeaveApplicationSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊璇峰亣鐢宠
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateLeaveApplication(@Valid HrmLeaveApplicationSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎璇峰亣鐢宠
+ *
+ * @param id 鐢宠ID
+ */
+ void deleteLeaveApplication(Long id);
+
+ /**
+ * 鑾峰緱璇峰亣鐢宠
+ *
+ * @param id 鐢宠ID
+ * @return 璇峰亣鐢宠
+ */
+ HrmLeaveApplicationDO getLeaveApplication(Long id);
+
+ /**
+ * 鑾峰緱璇峰亣鐢宠璇︽儏锛堝惈鍏宠仈淇℃伅锛�
+ *
+ * @param id 鐢宠ID
+ * @return 璇峰亣鐢宠璇︽儏
+ */
+ HrmLeaveApplicationRespVO getLeaveApplicationDetail(Long id);
+
+ /**
+ * 鑾峰緱璇峰亣鐢宠鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 璇峰亣鐢宠鍒嗛〉
+ */
+ PageResult<HrmLeaveApplicationRespVO> getLeaveApplicationPage(HrmLeaveApplicationPageReqVO pageReqVO);
+
+ /**
+ * 鎻愪氦璇峰亣鐢宠瀹℃壒
+ *
+ * @param id 鐢宠ID
+ * @param processDefinitionKey 娴佺▼瀹氫箟Key
+ * @param userId 鎻愪氦浜篒D
+ */
+ void submitLeaveApplication(Long id, String processDefinitionKey, Long userId);
+
+ /**
+ * 鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔夊垪琛�
+ *
+ * @return 娴佺▼瀹氫箟鍒楄〃
+ */
+ List<Map<String, Object>> getApproveProcessDefinitionList();
+
+ /**
+ * 鏇存柊璇峰亣鐢宠瀹℃壒鐘舵�侊紙BPM 鍥炶皟锛�
+ *
+ * @param id 鐢宠ID
+ * @param status 瀹℃壒鐘舵��
+ */
+ void updateLeaveApplicationAuditStatus(Long id, Integer status);
+
+ /**
+ * 鏍规嵁娴佺▼瀹炰緥ID鑾峰彇璇峰亣鐢宠
+ *
+ * @param processInstanceId 娴佺▼瀹炰緥ID
+ * @return 璇峰亣鐢宠
+ */
+ HrmLeaveApplicationDO getLeaveApplicationByProcessInstanceId(String processInstanceId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java
new file mode 100644
index 0000000..55ac560
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java
@@ -0,0 +1,324 @@
+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.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.HrmLeaveApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmLeaveTypeEnum;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.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 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 HrmLeaveApplicationServiceImpl implements HrmLeaveApplicationService {
+
+ @Resource
+ private HrmLeaveApplicationMapper leaveApplicationMapper;
+ @Resource
+ private HrmNoRedisDAO noRedisDAO;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+ @Resource
+ private BpmCategoryService bpmCategoryService;
+ @Resource
+ private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createLeaveApplication(HrmLeaveApplicationSaveReqVO createReqVO) {
+ // 1. 鐢熸垚鐢宠鍗曞彿
+ String no = generateUniqueNo();
+ // 2. 璁剧疆鐢宠浜轰俊鎭�
+ Long userId = createReqVO.getUserId();
+ if (userId == null) {
+ userId = getCurrentUserId();
+ }
+ AdminUserRespDTO user = adminUserApi.getUser(userId);
+ if (user == null) {
+ userId = getCurrentUserId();
+ }
+ // 3. 鎻掑叆
+ HrmLeaveApplicationDO application = BeanUtils.toBean(createReqVO, HrmLeaveApplicationDO.class);
+ application.setNo(no);
+ application.setUserId(userId);
+ application.setDeptId(createReqVO.getDeptId() != null ? createReqVO.getDeptId() : user.getDeptId());
+ application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
+ leaveApplicationMapper.insert(application);
+ return application.getId();
+ }
+
+ private Long getCurrentUserId() {
+ // TODO: 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID
+ return 1L;
+ }
+
+ private String generateUniqueNo() {
+ String no = noRedisDAO.generateLeaveApplicationNo();
+ if (leaveApplicationMapper.selectByNo(no) != null) {
+ return generateUniqueNo();
+ }
+ return no;
+ }
+
+ @Override
+ public void updateLeaveApplication(HrmLeaveApplicationSaveReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ HrmLeaveApplicationDO application = validateLeaveApplicationExists(updateReqVO.getId());
+ // 鏍¢獙鐘舵�佷负鑽夌鎴栧鎵逛腑
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
+ && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+ throw exception(LEAVE_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+ }
+ // 鏇存柊
+ HrmLeaveApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmLeaveApplicationDO.class);
+ leaveApplicationMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteLeaveApplication(Long id) {
+ // 鏍¢獙瀛樺湪
+ HrmLeaveApplicationDO application = validateLeaveApplicationExists(id);
+ // 鏍¢獙鐘舵�佷负鑽夌
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(LEAVE_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+ }
+ // 鍒犻櫎
+ leaveApplicationMapper.deleteById(id);
+ }
+
+ private HrmLeaveApplicationDO validateLeaveApplicationExists(Long id) {
+ HrmLeaveApplicationDO application = leaveApplicationMapper.selectById(id);
+ if (application == null) {
+ throw exception(LEAVE_APPLICATION_NOT_EXISTS);
+ }
+ return application;
+ }
+
+ @Override
+ public HrmLeaveApplicationDO getLeaveApplication(Long id) {
+ return leaveApplicationMapper.selectById(id);
+ }
+
+ @Override
+ public HrmLeaveApplicationRespVO getLeaveApplicationDetail(Long id) {
+ HrmLeaveApplicationDO application = getLeaveApplication(id);
+ if (application == null) {
+ return null;
+ }
+ HrmLeaveApplicationRespVO respVO = BeanUtils.toBean(application, HrmLeaveApplicationRespVO.class);
+ // 濉厖鐢ㄦ埛鍚嶇О
+ if (application.getUserId() != null) {
+ AdminUserRespDTO user = adminUserApi.getUser(application.getUserId());
+ if (user != null) {
+ respVO.setUserName(user.getNickname());
+ }
+ }
+ // 濉厖閮ㄩ棬鍚嶇О
+ if (application.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(application.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ // 濉厖璇峰亣绫诲瀷鍚嶇О
+ respVO.setLeaveTypeName(HrmLeaveTypeEnum.getNameByStatus(application.getLeaveType()));
+ // 濉厖瀹℃壒鐘舵�佸悕绉�
+ respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+ return respVO;
+ }
+
+ @Override
+ public PageResult<HrmLeaveApplicationRespVO> getLeaveApplicationPage(HrmLeaveApplicationPageReqVO pageReqVO) {
+ PageResult<HrmLeaveApplicationDO> pageResult = leaveApplicationMapper.selectPage(pageReqVO);
+ if (pageResult.getList().isEmpty()) {
+ return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
+ }
+ // 濉厖鍏宠仈淇℃伅
+ Set<Long> userIds = pageResult.getList().stream()
+ .map(HrmLeaveApplicationDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Set<Long> deptIds = pageResult.getList().stream()
+ .map(HrmLeaveApplicationDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+ List<HrmLeaveApplicationRespVO> list = pageResult.getList().stream().map(app -> {
+ HrmLeaveApplicationRespVO vo = BeanUtils.toBean(app, HrmLeaveApplicationRespVO.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());
+ }
+ vo.setLeaveTypeName(HrmLeaveTypeEnum.getNameByStatus(app.getLeaveType()));
+ vo.setStatusName(HrmAuditStatusEnum.getNameByStatus(app.getStatus()));
+ return vo;
+ }).collect(Collectors.toList());
+ return new PageResult<>(list, pageResult.getTotal());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitLeaveApplication(Long id, String processDefinitionKey, Long userId) {
+ // 1. 鏍¢獙璇峰亣鐢宠鏄惁瀛樺湪
+ HrmLeaveApplicationDO application = validateLeaveApplicationExists(id);
+ if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(LEAVE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+ validateApproveCategoryAndProcessDefinition();
+
+ // 3. 鏍¢獙浼犲叆鐨勬祦绋嬪畾涔� Key 鏄惁灞炰簬璇ュ垎绫讳笖澶勪簬婵�娲荤姸鎬�
+ ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+ if (processDefinition == null) {
+ throw exception(LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+
+ // 4. 鍒涘缓瀹℃壒娴佺▼瀹炰緥
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
+
+ // 5. 鏇存柊璇峰亣鐢宠宸ヤ綔娴佺紪鍙�
+ leaveApplicationMapper.updateById(HrmLeaveApplicationDO.builder()
+ .id(id)
+ .processInstanceId(processInstanceId)
+ .status(HrmAuditStatusEnum.PROCESS.getStatus())
+ .build());
+ }
+
+ /**
+ * 鏍¢獙 BPM 璇峰亣瀹℃壒鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+ */
+ private void validateApproveCategoryAndProcessDefinition() {
+ // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+ List<String> codes = Collections.singletonList(APPROVE_CATEGORY_CODE);
+ Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(codes);
+ if (!categoryMap.containsKey(APPROVE_CATEGORY_CODE)) {
+ throw exception(LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+
+ // 2. 鏍¢獙鍒嗙被涓嬫槸鍚︽湁鍙敤鐨勬祦绋嬪畾涔�
+ List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+ if (CollUtil.isEmpty(definitionInfoList)) {
+ throw exception(LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+
+ // 3. 鏍¢獙鏄惁鏈夋縺娲荤姸鎬佺殑娴佺▼瀹氫箟
+ Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+ List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+ boolean hasActiveDefinition = processDefinitions.stream()
+ .anyMatch(pd -> !pd.isSuspended());
+ if (!hasActiveDefinition) {
+ throw exception(LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public List<Map<String, Object>> getApproveProcessDefinitionList() {
+ // 1. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+ validateApproveCategoryAndProcessDefinition();
+
+ // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+ List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+
+ // 3. 鑾峰彇娴佺▼瀹氫箟璇︽儏
+ Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+ List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
+
+ // 4. 杩囨护锛氬彧淇濈暀婵�娲荤姸鎬侊紝涓旂浉鍚� key 鍙繚鐣欐渶鏂扮増鏈�
+ Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+ for (ProcessDefinition pd : processDefinitions) {
+ if (pd.isSuspended()) {
+ continue;
+ }
+ ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+ if (existing == null || pd.getVersion() > existing.getVersion()) {
+ latestVersionMap.put(pd.getKey(), pd);
+ }
+ }
+
+ // 5. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+ List<Map<String, Object>> result = new ArrayList<>();
+ for (ProcessDefinition pd : latestVersionMap.values()) {
+ Map<String, Object> item = new HashMap<>();
+ item.put("id", pd.getId());
+ item.put("key", pd.getKey());
+ item.put("name", pd.getName());
+ item.put("version", pd.getVersion());
+ result.add(item);
+ }
+ return result;
+ }
+
+ @Override
+ public void updateLeaveApplicationAuditStatus(Long id, Integer status) {
+ // 鏍¢獙瀛樺湪
+ HrmLeaveApplicationDO application = validateLeaveApplicationExists(id);
+ // 鏍¢獙鐘舵�佷负瀹℃壒涓�
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+ log.error("[updateLeaveApplicationAuditStatus][application({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]",
+ id, status);
+ throw exception(LEAVE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+ // 鏇存柊鐘舵��
+ leaveApplicationMapper.updateById(HrmLeaveApplicationDO.builder()
+ .id(id)
+ .status(status)
+ .approveTime(java.time.LocalDateTime.now())
+ .build());
+ }
+
+ @Override
+ public HrmLeaveApplicationDO getLeaveApplicationByProcessInstanceId(String processInstanceId) {
+ return leaveApplicationMapper.selectByProcessInstanceId(processInstanceId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java
new file mode 100644
index 0000000..2440b28
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java
@@ -0,0 +1,108 @@
+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.HrmResignationApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 绂昏亴鐢宠 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmResignationApplicationService {
+
+ /**
+ * BPM 绂昏亴瀹℃壒娴佺▼鏍囪瘑
+ */
+ String BPM_PROCESS_DEFINITION_KEY = "hrm_resignation";
+
+ /**
+ * BPM 绂昏亴瀹℃壒鍒嗙被缂栫爜
+ */
+ String APPROVE_CATEGORY_CODE = "hrm_resignation_approve";
+
+ /**
+ * 鍒涘缓绂昏亴鐢宠
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 鐢宠ID
+ */
+ Long createResignationApplication(@Valid HrmResignationApplicationSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊绂昏亴鐢宠
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateResignationApplication(@Valid HrmResignationApplicationSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎绂昏亴鐢宠
+ *
+ * @param id 鐢宠ID
+ */
+ void deleteResignationApplication(Long id);
+
+ /**
+ * 鑾峰緱绂昏亴鐢宠
+ *
+ * @param id 鐢宠ID
+ * @return 绂昏亴鐢宠
+ */
+ HrmResignationApplicationDO getResignationApplication(Long id);
+
+ /**
+ * 鑾峰緱绂昏亴鐢宠璇︽儏锛堝惈鍏宠仈淇℃伅锛�
+ *
+ * @param id 鐢宠ID
+ * @return 绂昏亴鐢宠璇︽儏
+ */
+ HrmResignationApplicationRespVO getResignationApplicationDetail(Long id);
+
+ /**
+ * 鑾峰緱绂昏亴鐢宠鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 绂昏亴鐢宠鍒嗛〉
+ */
+ PageResult<HrmResignationApplicationRespVO> getResignationApplicationPage(HrmResignationApplicationPageReqVO pageReqVO);
+
+ /**
+ * 鎻愪氦绂昏亴鐢宠瀹℃壒
+ *
+ * @param id 鐢宠ID
+ * @param processDefinitionKey 娴佺▼瀹氫箟Key
+ * @param userId 鎻愪氦浜篒D
+ */
+ void submitResignationApplication(Long id, String processDefinitionKey, Long userId);
+
+ /**
+ * 鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔夊垪琛�
+ *
+ * @return 娴佺▼瀹氫箟鍒楄〃
+ */
+ List<Map<String, Object>> getApproveProcessDefinitionList();
+
+ /**
+ * 鏇存柊绂昏亴鐢宠瀹℃壒鐘舵�侊紙BPM 鍥炶皟锛�
+ *
+ * @param id 鐢宠ID
+ * @param status 瀹℃壒鐘舵��
+ */
+ void updateResignationApplicationAuditStatus(Long id, Integer status);
+
+ /**
+ * 鏍规嵁娴佺▼瀹炰緥ID鑾峰彇绂昏亴鐢宠
+ *
+ * @param processInstanceId 娴佺▼瀹炰緥ID
+ * @return 绂昏亴鐢宠
+ */
+ HrmResignationApplicationDO getResignationApplicationByProcessInstanceId(String processInstanceId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
new file mode 100644
index 0000000..3548495
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
@@ -0,0 +1,314 @@
+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.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.HrmResignationApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmResignationApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmResignationTypeEnum;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.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 HrmResignationApplicationServiceImpl implements HrmResignationApplicationService {
+
+ private static final Integer HANDOVER_STATUS_NOT_STARTED = 0;
+ private static final Integer HANDOVER_STATUS_IN_PROGRESS = 10;
+ private static final Integer HANDOVER_STATUS_COMPLETED = 20;
+
+ @Resource
+ private HrmResignationApplicationMapper resignationApplicationMapper;
+ @Resource
+ private HrmNoRedisDAO noRedisDAO;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+ @Resource
+ private BpmCategoryService bpmCategoryService;
+ @Resource
+ private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createResignationApplication(HrmResignationApplicationSaveReqVO createReqVO) {
+ Long userId = createReqVO.getUserId();
+ if (userId == null) {
+ userId = getCurrentUserId();
+ }
+ HrmResignationApplicationDO existing = resignationApplicationMapper.selectByUserIdAndStatusNot(userId,
+ HrmAuditStatusEnum.CANCEL.getStatus());
+ if (existing != null) {
+ throw exception(RESIGNATION_APPLICATION_EXISTS);
+ }
+ String no = generateUniqueNo();
+ AdminUserRespDTO user = adminUserApi.getUser(userId);
+ if (user == null) {
+ userId = getCurrentUserId();
+ }
+ HrmResignationApplicationDO application = BeanUtils.toBean(createReqVO, HrmResignationApplicationDO.class);
+ application.setNo(no);
+ application.setUserId(userId);
+ application.setDeptId(createReqVO.getDeptId() != null ? createReqVO.getDeptId() : user.getDeptId());
+ application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
+ application.setHandoverStatus(HANDOVER_STATUS_NOT_STARTED);
+ resignationApplicationMapper.insert(application);
+ return application.getId();
+ }
+
+ private Long getCurrentUserId() {
+ return 1L;
+ }
+
+ private String generateUniqueNo() {
+ String no = noRedisDAO.generateResignationApplicationNo();
+ if (resignationApplicationMapper.selectByNo(no) != null) {
+ return generateUniqueNo();
+ }
+ return no;
+ }
+
+ @Override
+ public void updateResignationApplication(HrmResignationApplicationSaveReqVO updateReqVO) {
+ HrmResignationApplicationDO application = validateResignationApplicationExists(updateReqVO.getId());
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(RESIGNATION_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+ }
+ HrmResignationApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmResignationApplicationDO.class);
+ resignationApplicationMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteResignationApplication(Long id) {
+ HrmResignationApplicationDO application = validateResignationApplicationExists(id);
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(RESIGNATION_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+ }
+ resignationApplicationMapper.deleteById(id);
+ }
+
+ private HrmResignationApplicationDO validateResignationApplicationExists(Long id) {
+ HrmResignationApplicationDO application = resignationApplicationMapper.selectById(id);
+ if (application == null) {
+ throw exception(RESIGNATION_APPLICATION_NOT_EXISTS);
+ }
+ return application;
+ }
+
+ @Override
+ public HrmResignationApplicationDO getResignationApplication(Long id) {
+ return resignationApplicationMapper.selectById(id);
+ }
+
+ @Override
+ public HrmResignationApplicationRespVO getResignationApplicationDetail(Long id) {
+ HrmResignationApplicationDO application = getResignationApplication(id);
+ if (application == null) {
+ return null;
+ }
+ HrmResignationApplicationRespVO respVO = BeanUtils.toBean(application, HrmResignationApplicationRespVO.class);
+ fillUserNames(respVO, application);
+ if (application.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(application.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ respVO.setResignationTypeName(HrmResignationTypeEnum.getNameByStatus(application.getResignationType()));
+ respVO.setHandoverStatusName(getHandoverStatusName(application.getHandoverStatus()));
+ respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+ return respVO;
+ }
+
+ private void fillUserNames(HrmResignationApplicationRespVO respVO, HrmResignationApplicationDO application) {
+ Set<Long> userIds = Set.of(application.getUserId(), application.getHandoverUserId())
+ .stream().filter(Objects::nonNull).collect(Collectors.toSet());
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+ if (application.getUserId() != null && userMap.containsKey(application.getUserId())) {
+ respVO.setUserName(userMap.get(application.getUserId()).getNickname());
+ }
+ if (application.getHandoverUserId() != null && userMap.containsKey(application.getHandoverUserId())) {
+ respVO.setHandoverUserName(userMap.get(application.getHandoverUserId()).getNickname());
+ }
+ }
+
+ private String getHandoverStatusName(Integer handoverStatus) {
+ if (handoverStatus == null) {
+ return "";
+ }
+ if (Objects.equals(handoverStatus, HANDOVER_STATUS_NOT_STARTED)) {
+ return "鏈紑濮�";
+ }
+ if (Objects.equals(handoverStatus, HANDOVER_STATUS_IN_PROGRESS)) {
+ return "杩涜涓�";
+ }
+ if (Objects.equals(handoverStatus, HANDOVER_STATUS_COMPLETED)) {
+ return "宸插畬鎴�";
+ }
+ return "";
+ }
+
+ @Override
+ public PageResult<HrmResignationApplicationRespVO> getResignationApplicationPage(HrmResignationApplicationPageReqVO pageReqVO) {
+ PageResult<HrmResignationApplicationDO> pageResult = resignationApplicationMapper.selectPage(pageReqVO);
+ if (pageResult.getList().isEmpty()) {
+ return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
+ }
+ Set<Long> userIds = pageResult.getList().stream()
+ .flatMap(app -> Stream.of(app.getUserId(), app.getHandoverUserId()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+ Set<Long> deptIds = pageResult.getList().stream()
+ .map(HrmResignationApplicationDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+ List<HrmResignationApplicationRespVO> list = pageResult.getList().stream().map(app -> {
+ HrmResignationApplicationRespVO vo = BeanUtils.toBean(app, HrmResignationApplicationRespVO.class);
+ if (app.getUserId() != null && userMap.containsKey(app.getUserId())) {
+ vo.setUserName(userMap.get(app.getUserId()).getNickname());
+ }
+ if (app.getHandoverUserId() != null && userMap.containsKey(app.getHandoverUserId())) {
+ vo.setHandoverUserName(userMap.get(app.getHandoverUserId()).getNickname());
+ }
+ if (app.getDeptId() != null && deptMap.containsKey(app.getDeptId())) {
+ vo.setDeptName(deptMap.get(app.getDeptId()).getName());
+ }
+ vo.setResignationTypeName(HrmResignationTypeEnum.getNameByStatus(app.getResignationType()));
+ vo.setHandoverStatusName(getHandoverStatusName(app.getHandoverStatus()));
+ vo.setStatusName(HrmAuditStatusEnum.getNameByStatus(app.getStatus()));
+ return vo;
+ }).collect(Collectors.toList());
+ return new PageResult<>(list, pageResult.getTotal());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitResignationApplication(Long id, String processDefinitionKey, Long userId) {
+ HrmResignationApplicationDO application = validateResignationApplicationExists(id);
+ if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(RESIGNATION_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
+ }
+ validateApproveCategoryAndProcessDefinition();
+ ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+ if (processDefinition == null) {
+ throw exception(RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
+ resignationApplicationMapper.updateById(HrmResignationApplicationDO.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(RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+ List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+ if (CollUtil.isEmpty(definitionInfoList)) {
+ throw exception(RESIGNATION_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(RESIGNATION_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 updateResignationApplicationAuditStatus(Long id, Integer status) {
+ HrmResignationApplicationDO application = validateResignationApplicationExists(id);
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+ log.error("[updateResignationApplicationAuditStatus][application({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]", id, status);
+ throw exception(RESIGNATION_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+ resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
+ .id(id)
+ .status(status)
+ .approveTime(java.time.LocalDateTime.now())
+ .build());
+ }
+
+ @Override
+ public HrmResignationApplicationDO getResignationApplicationByProcessInstanceId(String processInstanceId) {
+ return resignationApplicationMapper.selectByProcessInstanceId(processInstanceId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java
new file mode 100644
index 0000000..3dedd96
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java
@@ -0,0 +1,108 @@
+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.HrmTransferApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmTransferApplicationDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 璋冨矖鐢宠 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmTransferApplicationService {
+
+ /**
+ * BPM 璋冨矖瀹℃壒娴佺▼鏍囪瘑
+ */
+ String BPM_PROCESS_DEFINITION_KEY = "hrm_transfer";
+
+ /**
+ * BPM 璋冨矖瀹℃壒鍒嗙被缂栫爜
+ */
+ String APPROVE_CATEGORY_CODE = "hrm_transfer_approve";
+
+ /**
+ * 鍒涘缓璋冨矖鐢宠
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 鐢宠ID
+ */
+ Long createTransferApplication(@Valid HrmTransferApplicationSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊璋冨矖鐢宠
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateTransferApplication(@Valid HrmTransferApplicationSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎璋冨矖鐢宠
+ *
+ * @param id 鐢宠ID
+ */
+ void deleteTransferApplication(Long id);
+
+ /**
+ * 鑾峰緱璋冨矖鐢宠
+ *
+ * @param id 鐢宠ID
+ * @return 璋冨矖鐢宠
+ */
+ HrmTransferApplicationDO getTransferApplication(Long id);
+
+ /**
+ * 鑾峰緱璋冨矖鐢宠璇︽儏锛堝惈鍏宠仈淇℃伅锛�
+ *
+ * @param id 鐢宠ID
+ * @return 璋冨矖鐢宠璇︽儏
+ */
+ HrmTransferApplicationRespVO getTransferApplicationDetail(Long id);
+
+ /**
+ * 鑾峰緱璋冨矖鐢宠鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 璋冨矖鐢宠鍒嗛〉
+ */
+ PageResult<HrmTransferApplicationRespVO> getTransferApplicationPage(HrmTransferApplicationPageReqVO pageReqVO);
+
+ /**
+ * 鎻愪氦璋冨矖鐢宠瀹℃壒
+ *
+ * @param id 鐢宠ID
+ * @param processDefinitionKey 娴佺▼瀹氫箟Key
+ * @param userId 鎻愪氦浜篒D
+ */
+ void submitTransferApplication(Long id, String processDefinitionKey, Long userId);
+
+ /**
+ * 鑾峰彇鍙敤鐨勫鎵规祦绋嬪畾涔夊垪琛�
+ *
+ * @return 娴佺▼瀹氫箟鍒楄〃
+ */
+ List<Map<String, Object>> getApproveProcessDefinitionList();
+
+ /**
+ * 鏇存柊璋冨矖鐢宠瀹℃壒鐘舵�侊紙BPM 鍥炶皟锛�
+ *
+ * @param id 鐢宠ID
+ * @param status 瀹℃壒鐘舵��
+ */
+ void updateTransferApplicationAuditStatus(Long id, Integer status);
+
+ /**
+ * 鏍规嵁娴佺▼瀹炰緥ID鑾峰彇璋冨矖鐢宠
+ *
+ * @param processInstanceId 娴佺▼瀹炰緥ID
+ * @return 璋冨矖鐢宠
+ */
+ HrmTransferApplicationDO getTransferApplicationByProcessInstanceId(String processInstanceId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
new file mode 100644
index 0000000..d63e58a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -0,0 +1,331 @@
+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.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.HrmTransferApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmTransferApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmTransferApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmTransferTypeEnum;
+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 HrmTransferApplicationServiceImpl implements HrmTransferApplicationService {
+
+ @Resource
+ private HrmTransferApplicationMapper transferApplicationMapper;
+ @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 createTransferApplication(HrmTransferApplicationSaveReqVO createReqVO) {
+ // 1. 鏍¢獙鐩爣閮ㄩ棬涓庡師閮ㄩ棬涓嶈兘鐩稿悓
+ if (Objects.equals(createReqVO.getTargetDeptId(), createReqVO.getCurrentDeptId())) {
+ throw exception(TRANSFER_APPLICATION_SAME_DEPT);
+ }
+ // 2. 鏍¢獙鐩爣宀椾綅涓庡師宀椾綅涓嶈兘鐩稿悓
+ if (Objects.equals(createReqVO.getTargetPostId(), createReqVO.getCurrentPostId())) {
+ throw exception(TRANSFER_APPLICATION_SAME_POST);
+ }
+ // 3. 鐢熸垚鐢宠鍗曞彿
+ String no = generateUniqueNo();
+ // 4. 璁剧疆鐢宠浜轰俊鎭�
+ Long userId = createReqVO.getUserId();
+ if (userId == null) {
+ userId = getCurrentUserId();
+ }
+ AdminUserRespDTO user = adminUserApi.getUser(userId);
+ if (user == null) {
+ userId = getCurrentUserId();
+ }
+ // 5. 鎻掑叆
+ HrmTransferApplicationDO application = BeanUtils.toBean(createReqVO, HrmTransferApplicationDO.class);
+ application.setNo(no);
+ application.setUserId(userId);
+ application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
+ transferApplicationMapper.insert(application);
+ return application.getId();
+ }
+
+ private Long getCurrentUserId() {
+ return 1L;
+ }
+
+ private String generateUniqueNo() {
+ String no = noRedisDAO.generateTransferApplicationNo();
+ if (transferApplicationMapper.selectByNo(no) != null) {
+ return generateUniqueNo();
+ }
+ return no;
+ }
+
+ @Override
+ public void updateTransferApplication(HrmTransferApplicationSaveReqVO updateReqVO) {
+ HrmTransferApplicationDO application = validateTransferApplicationExists(updateReqVO.getId());
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
+ && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+ throw exception(TRANSFER_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+ }
+ if (Objects.equals(updateReqVO.getTargetDeptId(), updateReqVO.getCurrentDeptId())) {
+ throw exception(TRANSFER_APPLICATION_SAME_DEPT);
+ }
+ if (Objects.equals(updateReqVO.getTargetPostId(), updateReqVO.getCurrentPostId())) {
+ throw exception(TRANSFER_APPLICATION_SAME_POST);
+ }
+ HrmTransferApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmTransferApplicationDO.class);
+ transferApplicationMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteTransferApplication(Long id) {
+ HrmTransferApplicationDO application = validateTransferApplicationExists(id);
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(TRANSFER_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+ }
+ transferApplicationMapper.deleteById(id);
+ }
+
+ private HrmTransferApplicationDO validateTransferApplicationExists(Long id) {
+ HrmTransferApplicationDO application = transferApplicationMapper.selectById(id);
+ if (application == null) {
+ throw exception(TRANSFER_APPLICATION_NOT_EXISTS);
+ }
+ return application;
+ }
+
+ @Override
+ public HrmTransferApplicationDO getTransferApplication(Long id) {
+ return transferApplicationMapper.selectById(id);
+ }
+
+ @Override
+ public HrmTransferApplicationRespVO getTransferApplicationDetail(Long id) {
+ HrmTransferApplicationDO application = getTransferApplication(id);
+ if (application == null) {
+ return null;
+ }
+ HrmTransferApplicationRespVO respVO = BeanUtils.toBean(application, HrmTransferApplicationRespVO.class);
+ if (application.getUserId() != null) {
+ AdminUserRespDTO user = adminUserApi.getUser(application.getUserId());
+ if (user != null) {
+ respVO.setUserName(user.getNickname());
+ }
+ }
+ fillDeptNames(respVO, application);
+ fillPostNames(respVO, application);
+ respVO.setTransferTypeName(HrmTransferTypeEnum.getNameByStatus(application.getTransferType()));
+ respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+ return respVO;
+ }
+
+ private void fillDeptNames(HrmTransferApplicationRespVO respVO, HrmTransferApplicationDO application) {
+ Set<Long> deptIds = Set.of(application.getCurrentDeptId(), application.getTargetDeptId())
+ .stream().filter(Objects::nonNull).collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+ if (application.getCurrentDeptId() != null && deptMap.containsKey(application.getCurrentDeptId())) {
+ respVO.setCurrentDeptName(deptMap.get(application.getCurrentDeptId()).getName());
+ }
+ if (application.getTargetDeptId() != null && deptMap.containsKey(application.getTargetDeptId())) {
+ respVO.setTargetDeptName(deptMap.get(application.getTargetDeptId()).getName());
+ }
+ }
+
+ private void fillPostNames(HrmTransferApplicationRespVO respVO, HrmTransferApplicationDO application) {
+ Set<Long> postIds = Set.of(application.getCurrentPostId(), application.getTargetPostId())
+ .stream().filter(Objects::nonNull).collect(Collectors.toSet());
+ Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
+ if (application.getCurrentPostId() != null && postMap.containsKey(application.getCurrentPostId())) {
+ respVO.setCurrentPostName(postMap.get(application.getCurrentPostId()).getName());
+ }
+ if (application.getTargetPostId() != null && postMap.containsKey(application.getTargetPostId())) {
+ respVO.setTargetPostName(postMap.get(application.getTargetPostId()).getName());
+ }
+ }
+
+ @Override
+ public PageResult<HrmTransferApplicationRespVO> getTransferApplicationPage(HrmTransferApplicationPageReqVO pageReqVO) {
+ PageResult<HrmTransferApplicationDO> pageResult = transferApplicationMapper.selectPage(pageReqVO);
+ if (pageResult.getList().isEmpty()) {
+ return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
+ }
+ Set<Long> userIds = pageResult.getList().stream()
+ .map(HrmTransferApplicationDO::getUserId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+ Set<Long> deptIds = pageResult.getList().stream()
+ .flatMap(app -> Stream.of(app.getCurrentDeptId(), app.getTargetDeptId()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+ Set<Long> postIds = pageResult.getList().stream()
+ .flatMap(app -> Stream.of(app.getCurrentPostId(), app.getTargetPostId()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
+ List<HrmTransferApplicationRespVO> list = pageResult.getList().stream().map(app -> {
+ HrmTransferApplicationRespVO vo = BeanUtils.toBean(app, HrmTransferApplicationRespVO.class);
+ if (app.getUserId() != null && userMap.containsKey(app.getUserId())) {
+ vo.setUserName(userMap.get(app.getUserId()).getNickname());
+ }
+ if (app.getCurrentDeptId() != null && deptMap.containsKey(app.getCurrentDeptId())) {
+ vo.setCurrentDeptName(deptMap.get(app.getCurrentDeptId()).getName());
+ }
+ if (app.getTargetDeptId() != null && deptMap.containsKey(app.getTargetDeptId())) {
+ vo.setTargetDeptName(deptMap.get(app.getTargetDeptId()).getName());
+ }
+ if (app.getCurrentPostId() != null && postMap.containsKey(app.getCurrentPostId())) {
+ vo.setCurrentPostName(postMap.get(app.getCurrentPostId()).getName());
+ }
+ if (app.getTargetPostId() != null && postMap.containsKey(app.getTargetPostId())) {
+ vo.setTargetPostName(postMap.get(app.getTargetPostId()).getName());
+ }
+ vo.setTransferTypeName(HrmTransferTypeEnum.getNameByStatus(app.getTransferType()));
+ vo.setStatusName(HrmAuditStatusEnum.getNameByStatus(app.getStatus()));
+ return vo;
+ }).collect(Collectors.toList());
+ return new PageResult<>(list, pageResult.getTotal());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitTransferApplication(Long id, String processDefinitionKey, Long userId) {
+ HrmTransferApplicationDO application = validateTransferApplicationExists(id);
+ if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+ throw exception(TRANSFER_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
+ }
+ validateApproveCategoryAndProcessDefinition();
+ ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+ if (processDefinition == null) {
+ throw exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
+ transferApplicationMapper.updateById(HrmTransferApplicationDO.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(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+ }
+ List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+ if (CollUtil.isEmpty(definitionInfoList)) {
+ throw exception(TRANSFER_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(TRANSFER_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 updateTransferApplicationAuditStatus(Long id, Integer status) {
+ HrmTransferApplicationDO application = validateTransferApplicationExists(id);
+ if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+ log.error("[updateTransferApplicationAuditStatus][application({}) 涓嶅浜庡鎵逛腑锛屾棤娉曟洿鏂板鎵圭粨鏋�({})]", id, status);
+ throw exception(TRANSFER_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+ }
+ transferApplicationMapper.updateById(HrmTransferApplicationDO.builder()
+ .id(id)
+ .status(status)
+ .approveTime(java.time.LocalDateTime.now())
+ .build());
+ }
+
+ @Override
+ public HrmTransferApplicationDO getTransferApplicationByProcessInstanceId(String processInstanceId) {
+ return transferApplicationMapper.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/HrmLeaveApplicationStatusListener.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmLeaveApplicationStatusListener.java
new file mode 100644
index 0000000..a456b83
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmLeaveApplicationStatusListener.java
@@ -0,0 +1,55 @@
+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.HrmLeaveApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmLeaveApplicationService;
+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;
+
+/**
+ * 璇峰亣鐢宠瀹℃壒鐨勭粨鏋滅洃鍚櫒瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+@Slf4j
+public class HrmLeaveApplicationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private HrmLeaveApplicationService leaveApplicationService;
+ @Resource
+ private HrmLeaveApplicationMapper leaveApplicationMapper;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ璇峰亣鐢宠
+ Long applicationId;
+ try {
+ applicationId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return;
+ }
+
+ HrmLeaveApplicationDO application = leaveApplicationMapper.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());
+ leaveApplicationService.updateLeaveApplicationAuditStatus(applicationId, auditStatus);
+ }
+
+}
\ 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
new file mode 100644
index 0000000..7966bf3
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java
@@ -0,0 +1,55 @@
+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.HrmResignationApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmResignationApplicationMapper;
+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;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 绂昏亴鐢宠瀹℃壒鐨勭粨鏋滅洃鍚櫒瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+@Slf4j
+public class HrmResignationApplicationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private HrmResignationApplicationService resignationApplicationService;
+ @Resource
+ private HrmResignationApplicationMapper resignationApplicationMapper;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ绂昏亴鐢宠
+ Long applicationId;
+ try {
+ applicationId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return;
+ }
+
+ HrmResignationApplicationDO application = resignationApplicationMapper.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());
+ resignationApplicationService.updateResignationApplicationAuditStatus(applicationId, auditStatus);
+ }
+
+}
\ 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
new file mode 100644
index 0000000..e63df1b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java
@@ -0,0 +1,55 @@
+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.HrmTransferApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmTransferApplicationMapper;
+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;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 璋冨矖鐢宠瀹℃壒鐨勭粨鏋滅洃鍚櫒瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+@Slf4j
+public class HrmTransferApplicationStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private HrmTransferApplicationService transferApplicationService;
+ @Resource
+ private HrmTransferApplicationMapper transferApplicationMapper;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ璋冨矖鐢宠
+ Long applicationId;
+ try {
+ applicationId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return;
+ }
+
+ HrmTransferApplicationDO application = transferApplicationMapper.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());
+ transferApplicationService.updateTransferApplicationAuditStatus(applicationId, auditStatus);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionService.java
new file mode 100644
index 0000000..1554c66
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionService.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
+import jakarta.validation.Valid;
+
+/**
+ * 鑰冨嫟寮傚父鐢宠 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmAttendanceExceptionService {
+
+ /**
+ * 鍒涘缓鑰冨嫟寮傚父鐢宠
+ */
+ Long createAttendanceException(@Valid HrmAttendanceExceptionSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鑰冨嫟寮傚父鐢宠
+ */
+ void updateAttendanceException(@Valid HrmAttendanceExceptionSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎鑰冨嫟寮傚父鐢宠
+ */
+ void deleteAttendanceException(Long id);
+
+ /**
+ * 鑾峰緱鑰冨嫟寮傚父鐢宠鍒嗛〉
+ */
+ PageResult<HrmAttendanceExceptionDO> getAttendanceExceptionPage(HrmAttendanceExceptionPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鑰冨嫟寮傚父鐢宠
+ */
+ HrmAttendanceExceptionDO getAttendanceException(Long id);
+
+ /**
+ * 鎻愪氦鑰冨嫟寮傚父鐢宠
+ *
+ * @param id 鐢宠ID
+ * @param processDefinitionKey 娴佺▼瀹氫箟Key
+ * @param userId 鎻愪氦浜篒D
+ */
+ void submitAttendanceException(Long id, String processDefinitionKey, Long userId);
+
+ /**
+ * 鏇存柊鑰冨嫟寮傚父鐢宠瀹℃壒鐘舵��
+ *
+ * @param id 鐢宠ID
+ * @param auditStatus 瀹℃壒鐘舵��
+ */
+ void updateAttendanceExceptionAuditStatus(Long id, Integer auditStatus);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java
new file mode 100644
index 0000000..89cfe32
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java
@@ -0,0 +1,133 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+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.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceExceptionMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+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.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 鑰冨嫟寮傚父鐢宠 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+@Slf4j
+public class HrmAttendanceExceptionServiceImpl implements HrmAttendanceExceptionService {
+
+ @Resource
+ private HrmAttendanceExceptionMapper attendanceExceptionMapper;
+ @Resource
+ private HrmNoRedisDAO noRedisDAO;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+ @Resource
+ private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+ @Override
+ public Long createAttendanceException(HrmAttendanceExceptionSaveReqVO createReqVO) {
+ HrmAttendanceExceptionDO attendanceException = BeanUtils.toBean(createReqVO, HrmAttendanceExceptionDO.class);
+ attendanceException.setNo(generateNo());
+ attendanceException.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
+ attendanceExceptionMapper.insert(attendanceException);
+ return attendanceException.getId();
+ }
+
+ private String generateNo() {
+ return noRedisDAO.generate(HrmNoRedisDAO.ATTENDANCE_EXCEPTION_NO_PREFIX, 4);
+ }
+
+ @Override
+ public void updateAttendanceException(HrmAttendanceExceptionSaveReqVO updateReqVO) {
+ validateAttendanceExceptionExists(updateReqVO.getId());
+ HrmAttendanceExceptionDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceExceptionDO.class);
+ attendanceExceptionMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteAttendanceException(Long id) {
+ validateAttendanceExceptionExists(id);
+ attendanceExceptionMapper.deleteById(id);
+ }
+
+ private HrmAttendanceExceptionDO validateAttendanceExceptionExists(Long id) {
+ HrmAttendanceExceptionDO attendanceException = attendanceExceptionMapper.selectById(id);
+ if (attendanceException == null) {
+ throw exception(ATTENDANCE_EXCEPTION_NOT_EXISTS);
+ }
+ return attendanceException;
+ }
+
+ @Override
+ public PageResult<HrmAttendanceExceptionDO> getAttendanceExceptionPage(HrmAttendanceExceptionPageReqVO pageReqVO) {
+ return attendanceExceptionMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public HrmAttendanceExceptionDO getAttendanceException(Long id) {
+ return attendanceExceptionMapper.selectById(id);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void submitAttendanceException(Long id, String processDefinitionKey, Long userId) {
+ // 1. 鏍¢獙寮傚父鐢宠瀛樺湪涓斾负鑽夌鐘舵��
+ HrmAttendanceExceptionDO exception = validateAttendanceExceptionExists(id);
+ if (!HrmAuditStatusEnum.DRAFT.getStatus().equals(exception.getStatus())) {
+ throw exception(ATTENDANCE_EXCEPTION_SUBMIT_FAIL_NOT_DRAFT);
+ }
+
+ // 2. 鏍¢獙娴佺▼瀹氫箟瀛樺湪涓斾负婵�娲荤姸鎬�
+ if (processDefinitionKey != null) {
+ bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+ }
+
+ // 3. 鍒涘缓瀹℃壒娴佺▼瀹炰緥
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
+ new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(processDefinitionKey)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 4. 鏇存柊寮傚父鐢宠鐘舵��
+ attendanceExceptionMapper.updateById(HrmAttendanceExceptionDO.builder()
+ .id(id)
+ .processInstanceId(processInstanceId)
+ .status(HrmAuditStatusEnum.PROCESS.getStatus())
+ .build());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateAttendanceExceptionAuditStatus(Long id, Integer auditStatus) {
+ // 1. 鏍¢獙寮傚父鐢宠瀛樺湪
+ HrmAttendanceExceptionDO exception = validateAttendanceExceptionExists(id);
+ // 2. 鏍¢獙褰撳墠鐘舵�佷负瀹℃壒涓�
+ if (!HrmAuditStatusEnum.PROCESS.getStatus().equals(exception.getStatus())) {
+ throw exception(ATTENDANCE_EXCEPTION_UPDATE_AUDIT_STATUS_FAIL);
+ }
+ // 3. 鏇存柊鐘舵��
+ attendanceExceptionMapper.updateById(HrmAttendanceExceptionDO.builder()
+ .id(id)
+ .status(auditStatus)
+ .approveTime(LocalDateTime.now())
+ .build());
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java
new file mode 100644
index 0000000..c2fb922
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceHolidayPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceHolidaySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceHolidayDO;
+import jakarta.validation.Valid;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 鑰冨嫟鑺傚亣鏃ラ厤缃� Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmAttendanceHolidayService {
+
+ /**
+ * 鏂板鑺傚亣鏃ラ厤缃�
+ *
+ * @param createReqVO 鏂板淇℃伅
+ * @return 鑺傚亣鏃D
+ */
+ Long createAttendanceHoliday(@Valid HrmAttendanceHolidaySaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鑺傚亣鏃ラ厤缃�
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateAttendanceHoliday(@Valid HrmAttendanceHolidaySaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎鑺傚亣鏃ラ厤缃�
+ *
+ * @param id 鑺傚亣鏃D
+ */
+ void deleteAttendanceHoliday(Long id);
+
+ /**
+ * 鑾峰緱鑺傚亣鏃ラ厤缃�
+ *
+ * @param id 鑺傚亣鏃D
+ * @return 鑺傚亣鏃ラ厤缃�
+ */
+ HrmAttendanceHolidayDO getAttendanceHoliday(Long id);
+
+ /**
+ * 鑾峰緱鑺傚亣鏃ラ厤缃垎椤�
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ鍙傛暟
+ * @return 鑺傚亣鏃ラ厤缃垎椤�
+ */
+ PageResult<HrmAttendanceHolidayDO> getAttendanceHolidayPage(HrmAttendanceHolidayPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鎸囧畾鏃ユ湡鑼冨洿鐨勮妭鍋囨棩鍒楄〃
+ *
+ * @param startDate 寮�濮嬫棩鏈�
+ * @param endDate 缁撴潫鏃ユ湡
+ * @return 鑺傚亣鏃ュ垪琛�
+ */
+ List<HrmAttendanceHolidayDO> getAttendanceHolidayListByDateRange(LocalDate startDate, LocalDate endDate);
+
+ /**
+ * 鍒ゆ柇鏄惁宸ヤ綔鏃�
+ *
+ * @param date 鏃ユ湡
+ * @return true-宸ヤ綔鏃ワ紝false-浼戞伅鏃�
+ */
+ boolean isWorkDay(LocalDate date);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java
new file mode 100644
index 0000000..6e96e27
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java
@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+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.attendance.vo.HrmAttendanceHolidayPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceHolidaySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceHolidayDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceHolidayMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 鑰冨嫟鑺傚亣鏃ラ厤缃� Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmAttendanceHolidayServiceImpl implements HrmAttendanceHolidayService {
+
+ @Resource
+ private HrmAttendanceHolidayMapper attendanceHolidayMapper;
+
+ @Override
+ public Long createAttendanceHoliday(HrmAttendanceHolidaySaveReqVO createReqVO) {
+ // 鏍¢獙鏃ユ湡鏄惁宸插瓨鍦ㄩ厤缃�
+ validateAttendanceHolidayDateExists(createReqVO.getDate());
+ HrmAttendanceHolidayDO attendanceHoliday = BeanUtils.toBean(createReqVO, HrmAttendanceHolidayDO.class);
+ if (attendanceHoliday.getIsLegal() == null) {
+ attendanceHoliday.setIsLegal(0);
+ }
+ attendanceHolidayMapper.insert(attendanceHoliday);
+ return attendanceHoliday.getId();
+ }
+
+ @Override
+ public void updateAttendanceHoliday(HrmAttendanceHolidaySaveReqVO updateReqVO) {
+ validateAttendanceHolidayExists(updateReqVO.getId());
+ // 濡傛灉鏃ユ湡鍙樻洿锛屾牎楠屾柊鏃ユ湡鏄惁宸插瓨鍦ㄩ厤缃�
+ HrmAttendanceHolidayDO oldHoliday = attendanceHolidayMapper.selectById(updateReqVO.getId());
+ if (!oldHoliday.getDate().equals(updateReqVO.getDate())) {
+ validateAttendanceHolidayDateExists(updateReqVO.getDate());
+ }
+ HrmAttendanceHolidayDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceHolidayDO.class);
+ attendanceHolidayMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteAttendanceHoliday(Long id) {
+ validateAttendanceHolidayExists(id);
+ attendanceHolidayMapper.deleteById(id);
+ }
+
+ private void validateAttendanceHolidayExists(Long id) {
+ if (attendanceHolidayMapper.selectById(id) == null) {
+ throw exception(ATTENDANCE_HOLIDAY_NOT_EXISTS);
+ }
+ }
+
+ private void validateAttendanceHolidayDateExists(LocalDate date) {
+ if (attendanceHolidayMapper.selectByDate(date) != null) {
+ throw exception(ATTENDANCE_HOLIDAY_DATE_EXISTS);
+ }
+ }
+
+ @Override
+ public HrmAttendanceHolidayDO getAttendanceHoliday(Long id) {
+ return attendanceHolidayMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<HrmAttendanceHolidayDO> getAttendanceHolidayPage(HrmAttendanceHolidayPageReqVO pageReqVO) {
+ return attendanceHolidayMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<HrmAttendanceHolidayDO> getAttendanceHolidayListByDateRange(LocalDate startDate, LocalDate endDate) {
+ return attendanceHolidayMapper.selectListByDateBetween(startDate, endDate);
+ }
+
+ @Override
+ public boolean isWorkDay(LocalDate date) {
+ // 1. 鏌ヨ鑺傚亣鏃ラ厤缃�
+ HrmAttendanceHolidayDO holiday = attendanceHolidayMapper.selectByDate(date);
+ if (holiday != null) {
+ // 鏈夐厤缃椂锛屾寜閰嶇疆鍒ゆ柇
+ return HrmAttendanceHolidayTypeEnum.WORK_DAY.getType().equals(holiday.getType());
+ }
+ // 2. 鏃犻厤缃椂锛屾寜鍛ㄦ湯鍒ゆ柇
+ DayOfWeek dayOfWeek = date.getDayOfWeek();
+ return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
+ }
+
+}
\ 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
new file mode 100644
index 0000000..8723951
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
+import jakarta.validation.Valid;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 鑰冨嫟璁板綍 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmAttendanceRecordService {
+
+ /**
+ * 鑾峰緱鑰冨嫟璁板綍鍒嗛〉
+ */
+ PageResult<HrmAttendanceRecordDO> getAttendanceRecordPage(HrmAttendanceRecordPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鑰冨嫟璁板綍
+ */
+ HrmAttendanceRecordDO getAttendanceRecord(Long id);
+
+ /**
+ * 鑾峰彇鍛樺伐鎸囧畾鏃ユ湡鑼冨洿鐨勮�冨嫟璁板綍
+ */
+ List<HrmAttendanceRecordDO> getAttendanceRecordListByUserIdAndDateBetween(Long userId, String startDate, String endDate);
+
+ /**
+ * 鑾峰彇鍛樺伐鎸囧畾鏃ユ湡鐨勮�冨嫟璁板綍
+ */
+ HrmAttendanceRecordDO getAttendanceRecordByUserIdAndDate(Long userId, LocalDate date);
+
+ /**
+ * 鎵撳崱锛堜笂鐝墦鍗★級
+ *
+ * @param userId 鍛樺伐ID
+ * @param clockTime 鎵撳崱鏃堕棿
+ * @param location 鎵撳崱鍦扮偣
+ * @return 鑰冨嫟璁板綍
+ */
+ HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location);
+
+ /**
+ * 鎵撳崱锛堜笅鐝墦鍗★級
+ *
+ * @param userId 鍛樺伐ID
+ * @param clockTime 鎵撳崱鏃堕棿
+ * @param location 鎵撳崱鍦扮偣
+ * @return 鑰冨嫟璁板綍
+ */
+ HrmAttendanceRecordDO clockOut(Long userId, LocalDateTime clockTime, String location);
+
+ /**
+ * 琛ュ崱瀹℃壒閫氳繃鍚庢洿鏂拌�冨嫟璁板綍
+ *
+ * @param exception 鑰冨嫟寮傚父鐢宠
+ */
+ void supplementClockIn(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
new file mode 100644
index 0000000..6eab672
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
@@ -0,0 +1,226 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+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.attendance.vo.HrmAttendanceRecordPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRecordMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceDataSourceEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceExceptionTypeEnum;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+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.math.RoundingMode;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 鑰冨嫟璁板綍 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmAttendanceRecordServiceImpl implements HrmAttendanceRecordService {
+
+ @Resource
+ private HrmAttendanceRecordMapper attendanceRecordMapper;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private HrmAttendanceRuleService attendanceRuleService;
+ @Resource
+ private HrmAttendanceHolidayService attendanceHolidayService;
+
+ @Override
+ public PageResult<HrmAttendanceRecordDO> getAttendanceRecordPage(HrmAttendanceRecordPageReqVO pageReqVO) {
+ return attendanceRecordMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public HrmAttendanceRecordDO getAttendanceRecord(Long id) {
+ return attendanceRecordMapper.selectById(id);
+ }
+
+ @Override
+ public List<HrmAttendanceRecordDO> getAttendanceRecordListByUserIdAndDateBetween(Long userId, String startDate, String endDate) {
+ return attendanceRecordMapper.selectListByUserIdAndDateBetween(userId, LocalDate.parse(startDate), LocalDate.parse(endDate));
+ }
+
+ @Override
+ public HrmAttendanceRecordDO getAttendanceRecordByUserIdAndDate(Long userId, LocalDate date) {
+ return attendanceRecordMapper.selectByUserIdAndDate(userId, date);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location) {
+ LocalDate clockDate = clockTime.toLocalDate();
+ // 1. 鏍¢獙鏄惁宸ヤ綔鏃�
+ if (!attendanceHolidayService.isWorkDay(clockDate)) {
+ throw exception(ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY);
+ }
+ // 2. 鑾峰彇鍛樺伐淇℃伅鍜岃�冨嫟瑙勫垯
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS);
+ }
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
+ if (rule == null) {
+ throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
+ }
+ // 3. 鏌ヨ褰撴棩鑰冨嫟璁板綍
+ HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate);
+ if (record != null && record.getClockInTime() != null) {
+ throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
+ }
+ // 4. 鍒ゆ柇鎵撳崱绫诲瀷
+ LocalTime workStartTime = rule.getWorkStartTime();
+ int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
+ LocalTime lateThreshold = workStartTime.plusMinutes(lateAllowMinutes);
+ LocalTime clockTimeOnly = clockTime.toLocalTime();
+ Integer clockInType = clockTimeOnly.isBefore(lateThreshold) || clockTimeOnly.equals(lateThreshold)
+ ? HrmAttendanceClockTypeEnum.NORMAL.getType()
+ : HrmAttendanceClockTypeEnum.LATE.getType();
+ // 5. 鍒涘缓鎴栨洿鏂拌�冨嫟璁板綍
+ if (record == null) {
+ record = HrmAttendanceRecordDO.builder()
+ .userId(userId)
+ .deptId(employee.getDeptId())
+ .date(clockDate)
+ .clockInTime(clockTime)
+ .clockInType(clockInType)
+ .location(location)
+ .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
+ .build();
+ attendanceRecordMapper.insert(record);
+ } else {
+ record.setClockInTime(clockTime);
+ record.setClockInType(clockInType);
+ if (location != null) {
+ record.setLocation(location);
+ }
+ attendanceRecordMapper.updateById(record);
+ }
+ return record;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public HrmAttendanceRecordDO clockOut(Long userId, LocalDateTime clockTime, String location) {
+ LocalDate clockDate = clockTime.toLocalDate();
+ // 1. 鏍¢獙鏄惁宸ヤ綔鏃�
+ if (!attendanceHolidayService.isWorkDay(clockDate)) {
+ throw exception(ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY);
+ }
+ // 2. 鑾峰彇鍛樺伐淇℃伅鍜岃�冨嫟瑙勫垯
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS);
+ }
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
+ if (rule == null) {
+ throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
+ }
+ // 3. 鏌ヨ褰撴棩鑰冨嫟璁板綍
+ HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate);
+ if (record == null) {
+ // 濡傛灉娌℃湁涓婄彮鎵撳崱璁板綍锛屽垱寤轰竴鏉$己鍗$殑璁板綍
+ record = HrmAttendanceRecordDO.builder()
+ .userId(userId)
+ .deptId(employee.getDeptId())
+ .date(clockDate)
+ .clockInType(HrmAttendanceClockTypeEnum.MISSING.getType())
+ .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
+ .build();
+ attendanceRecordMapper.insert(record);
+ }
+ if (record.getClockOutTime() != null) {
+ throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
+ }
+ // 4. 鍒ゆ柇鎵撳崱绫诲瀷
+ LocalTime workEndTime = rule.getWorkEndTime();
+ int earlyAllowMinutes = rule.getEarlyAllowMinutes() != null ? rule.getEarlyAllowMinutes() : 0;
+ LocalTime earlyThreshold = workEndTime.minusMinutes(earlyAllowMinutes);
+ LocalTime clockTimeOnly = clockTime.toLocalTime();
+ Integer clockOutType = clockTimeOnly.isAfter(earlyThreshold) || clockTimeOnly.equals(earlyThreshold)
+ ? HrmAttendanceClockTypeEnum.NORMAL.getType()
+ : HrmAttendanceClockTypeEnum.EARLY.getType();
+ // 5. 鏇存柊鑰冨嫟璁板綍
+ record.setClockOutTime(clockTime);
+ record.setClockOutType(clockOutType);
+ if (location != null) {
+ record.setLocation(location);
+ }
+ // 6. 璁$畻宸ヤ綔鏃堕暱
+ if (record.getClockInTime() != null) {
+ Duration duration = Duration.between(record.getClockInTime(), clockTime);
+ BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
+ .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+ record.setWorkHours(workHours);
+ }
+ attendanceRecordMapper.updateById(record);
+ return record;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void supplementClockIn(HrmAttendanceExceptionDO exception) {
+ // 浠呭鐞嗚ˉ鍗$被鍨�
+ if (!HrmAttendanceExceptionTypeEnum.SUPPLEMENT.getType().equals(exception.getType())) {
+ return;
+ }
+ LocalDate exceptionDate = exception.getExceptionDate();
+ LocalDateTime exceptionTime = exception.getExceptionTime();
+ Long userId = exception.getUserId();
+ // 鏌ヨ鑰冨嫟璁板綍
+ 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())
+ .date(exceptionDate)
+ .dataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType())
+ .build();
+ attendanceRecordMapper.insert(record);
+ }
+ // 鏍规嵁寮傚父鏃堕棿鍒ゆ柇鏄笂鐝繕鏄笅鐝ˉ鍗�
+ LocalTime time = exceptionTime.toLocalTime();
+ if (time.isBefore(LocalTime.of(12, 0))) {
+ // 涓婄彮琛ュ崱
+ record.setClockInTime(exceptionTime);
+ record.setClockInType(HrmAttendanceClockTypeEnum.NORMAL.getType());
+ } else {
+ // 涓嬬彮琛ュ崱
+ record.setClockOutTime(exceptionTime);
+ record.setClockOutType(HrmAttendanceClockTypeEnum.NORMAL.getType());
+ }
+ // 鏇存柊鏁版嵁鏉ユ簮涓鸿ˉ鍗″鎵�
+ record.setDataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType());
+ // 璁$畻宸ヤ綔鏃堕暱
+ if (record.getClockInTime() != null && record.getClockOutTime() != null) {
+ Duration duration = Duration.between(record.getClockInTime(), record.getClockOutTime());
+ BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
+ .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+ record.setWorkHours(workHours);
+ }
+ attendanceRecordMapper.updateById(record);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleService.java
new file mode 100644
index 0000000..0ed1ab2
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleService.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRulePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 鑰冨嫟瑙勫垯閰嶇疆 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmAttendanceRuleService {
+
+ /**
+ * 鏂板鑰冨嫟瑙勫垯
+ *
+ * @param createReqVO 鏂板淇℃伅
+ * @return 瑙勫垯ID
+ */
+ Long createAttendanceRule(@Valid HrmAttendanceRuleSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鑰冨嫟瑙勫垯
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateAttendanceRule(@Valid HrmAttendanceRuleSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎鑰冨嫟瑙勫垯
+ *
+ * @param id 瑙勫垯ID
+ */
+ void deleteAttendanceRule(Long id);
+
+ /**
+ * 鑾峰緱鑰冨嫟瑙勫垯
+ *
+ * @param id 瑙勫垯ID
+ * @return 鑰冨嫟瑙勫垯
+ */
+ HrmAttendanceRuleDO getAttendanceRule(Long id);
+
+ /**
+ * 鑾峰緱鑰冨嫟瑙勫垯鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ鍙傛暟
+ * @return 鑰冨嫟瑙勫垯鍒嗛〉
+ */
+ PageResult<HrmAttendanceRuleDO> getAttendanceRulePage(HrmAttendanceRulePageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鍚敤鐨勮�冨嫟瑙勫垯鍒楄〃
+ *
+ * @return 鑰冨嫟瑙勫垯鍒楄〃
+ */
+ List<HrmAttendanceRuleDO> getAttendanceRuleList();
+
+ /**
+ * 鏍规嵁閮ㄩ棬ID鑾峰彇鑰冨嫟瑙勫垯
+ * 浼樺厛鍖归厤閮ㄩ棬瑙勫垯锛屽叾娆℃煡鎵惧叏灞�瑙勫垯锛坉eptId=null锛�
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @return 鑰冨嫟瑙勫垯
+ */
+ HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java
new file mode 100644
index 0000000..4fdf7ec
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java
@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+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.attendance.vo.HrmAttendanceRulePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRuleMapper;
+import jakarta.annotation.Resource;
+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.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 鑰冨嫟瑙勫垯閰嶇疆 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmAttendanceRuleServiceImpl implements HrmAttendanceRuleService {
+
+ @Resource
+ private HrmAttendanceRuleMapper attendanceRuleMapper;
+
+ @Override
+ public Long createAttendanceRule(HrmAttendanceRuleSaveReqVO createReqVO) {
+ // 鏍¢獙瑙勫垯鍚嶇О鏄惁閲嶅
+ validateAttendanceRuleNameUnique(null, createReqVO.getName());
+ // 鏍¢獙閮ㄩ棬鏄惁宸查厤缃鍒欙紙濡傛灉鎸囧畾浜嗛儴闂級
+ if (createReqVO.getDeptId() != null) {
+ validateAttendanceRuleDeptUnique(null, createReqVO.getDeptId());
+ }
+ HrmAttendanceRuleDO attendanceRule = BeanUtils.toBean(createReqVO, HrmAttendanceRuleDO.class);
+ // 璁剧疆榛樿鍊�
+ if (attendanceRule.getLateAllowMinutes() == null) {
+ attendanceRule.setLateAllowMinutes(0);
+ }
+ if (attendanceRule.getEarlyAllowMinutes() == null) {
+ attendanceRule.setEarlyAllowMinutes(0);
+ }
+ if (attendanceRule.getOvertimeThreshold() == null) {
+ attendanceRule.setOvertimeThreshold(BigDecimal.ONE);
+ }
+ if (attendanceRule.getStatus() == null) {
+ attendanceRule.setStatus(0);
+ }
+ attendanceRuleMapper.insert(attendanceRule);
+ return attendanceRule.getId();
+ }
+
+ @Override
+ public void updateAttendanceRule(HrmAttendanceRuleSaveReqVO updateReqVO) {
+ validateAttendanceRuleExists(updateReqVO.getId());
+ // 鏍¢獙瑙勫垯鍚嶇О鏄惁閲嶅
+ validateAttendanceRuleNameUnique(updateReqVO.getId(), updateReqVO.getName());
+ // 鏍¢獙閮ㄩ棬鏄惁宸查厤缃鍒欙紙濡傛灉鎸囧畾浜嗛儴闂級
+ if (updateReqVO.getDeptId() != null) {
+ validateAttendanceRuleDeptUnique(updateReqVO.getId(), updateReqVO.getDeptId());
+ }
+ HrmAttendanceRuleDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceRuleDO.class);
+ attendanceRuleMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteAttendanceRule(Long id) {
+ validateAttendanceRuleExists(id);
+ attendanceRuleMapper.deleteById(id);
+ }
+
+ private void validateAttendanceRuleExists(Long id) {
+ if (attendanceRuleMapper.selectById(id) == null) {
+ throw exception(ATTENDANCE_RULE_NOT_EXISTS);
+ }
+ }
+
+ private void validateAttendanceRuleNameUnique(Long id, String name) {
+ HrmAttendanceRuleDO rule = attendanceRuleMapper.selectOne(HrmAttendanceRuleDO::getName, name);
+ if (rule == null) {
+ return;
+ }
+ if (id == null) {
+ throw exception(ATTENDANCE_RULE_NAME_EXISTS);
+ }
+ if (!rule.getId().equals(id)) {
+ throw exception(ATTENDANCE_RULE_NAME_EXISTS);
+ }
+ }
+
+ private void validateAttendanceRuleDeptUnique(Long id, Long deptId) {
+ List<HrmAttendanceRuleDO> rules = attendanceRuleMapper.selectListByDeptId(deptId);
+ if (rules.isEmpty()) {
+ return;
+ }
+ for (HrmAttendanceRuleDO rule : rules) {
+ if (id == null || !rule.getId().equals(id)) {
+ throw exception(ATTENDANCE_RULE_DEPT_EXISTS);
+ }
+ }
+ }
+
+ @Override
+ public HrmAttendanceRuleDO getAttendanceRule(Long id) {
+ return attendanceRuleMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<HrmAttendanceRuleDO> getAttendanceRulePage(HrmAttendanceRulePageReqVO pageReqVO) {
+ return attendanceRuleMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<HrmAttendanceRuleDO> getAttendanceRuleList() {
+ return attendanceRuleMapper.selectListByStatus(0);
+ }
+
+ @Override
+ public HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId) {
+ // 1. 鍏堟煡鎵鹃儴闂ㄤ笓灞炶鍒�
+ List<HrmAttendanceRuleDO> deptRules = attendanceRuleMapper.selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<HrmAttendanceRuleDO>()
+ .eq(HrmAttendanceRuleDO::getDeptId, deptId)
+ .eq(HrmAttendanceRuleDO::getStatus, 0));
+ if (!deptRules.isEmpty()) {
+ return deptRules.get(0);
+ }
+ // 2. 鏌ユ壘鍏ㄥ眬瑙勫垯锛坉eptId 涓� null锛�
+ List<HrmAttendanceRuleDO> globalRules = attendanceRuleMapper.selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<HrmAttendanceRuleDO>()
+ .isNull(HrmAttendanceRuleDO::getDeptId)
+ .eq(HrmAttendanceRuleDO::getStatus, 0));
+ if (!globalRules.isEmpty()) {
+ return globalRules.get(0);
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryService.java
new file mode 100644
index 0000000..4e66cb9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryService.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceSummaryDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 鑰冨嫟缁熻姹囨�� Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmAttendanceSummaryService {
+
+ /**
+ * 鐢熸垚鍛樺伐鏈堝害鑰冨嫟姹囨��
+ *
+ * @param userId 鍛樺伐ID
+ * @param yearMonth 骞存湀锛屾牸寮忥細yyyy-MM
+ */
+ void generateMonthlySummary(Long userId, String yearMonth);
+
+ /**
+ * 鎵归噺鐢熸垚閮ㄩ棬鏈堝害姹囨��
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @param yearMonth 骞存湀
+ */
+ void generateDeptMonthlySummary(Long deptId, String yearMonth);
+
+ /**
+ * 鎵归噺鐢熸垚鍏ㄥ叕鍙告湀搴︽眹鎬�
+ *
+ * @param yearMonth 骞存湀
+ */
+ void generateAllMonthlySummary(String yearMonth);
+
+ /**
+ * 鑾峰緱鑰冨嫟缁熻姹囨�诲垎椤�
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ鍙傛暟
+ * @return 鑰冨嫟缁熻姹囨�诲垎椤�
+ */
+ PageResult<HrmAttendanceSummaryDO> getAttendanceSummaryPage(HrmAttendanceSummaryPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鍛樺伐鏈堝害姹囨��
+ *
+ * @param userId 鍛樺伐ID
+ * @param yearMonth 骞存湀
+ * @return 鑰冨嫟缁熻姹囨��
+ */
+ HrmAttendanceSummaryDO getMonthlySummary(Long userId, String yearMonth);
+
+ /**
+ * 鑾峰緱閮ㄩ棬鏈堝害姹囨�诲垪琛�
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @param yearMonth 骞存湀
+ * @return 鑰冨嫟缁熻姹囨�诲垪琛�
+ */
+ List<HrmAttendanceSummaryDO> getDeptMonthlySummaryList(Long deptId, String yearMonth);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
new file mode 100644
index 0000000..7f79a2b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
@@ -0,0 +1,190 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+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.mysql.attendance.HrmAttendanceRecordMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceSummaryMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+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.time.LocalDate;
+import java.time.YearMonth;
+import java.util.List;
+
+/**
+ * 鑰冨嫟缁熻姹囨�� Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+@Slf4j
+public class HrmAttendanceSummaryServiceImpl implements HrmAttendanceSummaryService {
+
+ @Resource
+ private HrmAttendanceSummaryMapper attendanceSummaryMapper;
+ @Resource
+ private HrmAttendanceRecordMapper attendanceRecordMapper;
+ @Resource
+ private HrmEmployeeService employeeService;
+ @Resource
+ private HrmAttendanceHolidayService attendanceHolidayService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void generateMonthlySummary(Long userId, String yearMonth) {
+ // 1. 瑙f瀽骞存湀锛岃幏鍙栬鏈堣捣濮嬫棩鏈�
+ YearMonth ym = YearMonth.parse(yearMonth);
+ LocalDate startDate = ym.atDay(1);
+ LocalDate endDate = ym.atEndOfMonth();
+
+ // 2. 鑾峰彇鍛樺伐淇℃伅
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ if (employee == null) {
+ log.warn("[generateMonthlySummary] 鍛樺伐涓嶅瓨鍦�: {}", userId);
+ return;
+ }
+
+ // 3. 鏌ヨ璇ユ湀鎵�鏈夎�冨嫟璁板綍
+ List<HrmAttendanceRecordDO> records = attendanceRecordMapper.selectListByUserIdAndDateBetween(userId, startDate, endDate);
+
+ // 4. 璁$畻搴斿嚭鍕ゅぉ鏁帮紙宸ヤ綔鏃ユ暟閲忥級
+ int workDays = calculateWorkDays(startDate, endDate);
+
+ // 5. 缁熻鍚勯」鏁版嵁
+ int lateCount = 0;
+ int earlyCount = 0;
+ int absentCount = 0;
+ BigDecimal overtimeHours = BigDecimal.ZERO;
+ int actualDays = 0;
+
+ for (HrmAttendanceRecordDO record : records) {
+ // 缁熻杩熷埌
+ if (HrmAttendanceClockTypeEnum.LATE.getType().equals(record.getClockInType())) {
+ lateCount++;
+ }
+ // 缁熻鏃╅��
+ if (HrmAttendanceClockTypeEnum.EARLY.getType().equals(record.getClockOutType())) {
+ earlyCount++;
+ }
+ // 缁熻缂哄崱
+ if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockInType())) {
+ absentCount++;
+ }
+ if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockOutType())) {
+ absentCount++;
+ }
+ // 绱鍔犵彮鏃堕暱
+ if (record.getOvertimeHours() != null) {
+ overtimeHours = overtimeHours.add(record.getOvertimeHours());
+ }
+ // 缁熻瀹為檯鍑哄嫟澶╂暟锛堟湁姝e父鎵撳崱璁板綍锛�
+ if (record.getClockInTime() != null || record.getClockOutTime() != null) {
+ actualDays++;
+ }
+ }
+
+ // 6. 淇濆瓨鎴栨洿鏂版眹鎬昏褰�
+ HrmAttendanceSummaryDO summary = attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth);
+ if (summary == null) {
+ summary = HrmAttendanceSummaryDO.builder()
+ .userId(userId)
+ .deptId(employee.getDeptId())
+ .yearMonth(yearMonth)
+ .workDays(workDays)
+ .actualDays(actualDays)
+ .lateCount(lateCount)
+ .earlyCount(earlyCount)
+ .absentCount(absentCount)
+ .overtimeHours(overtimeHours)
+ .leaveHours(BigDecimal.ZERO)
+ .build();
+ attendanceSummaryMapper.insert(summary);
+ } else {
+ summary.setWorkDays(workDays);
+ summary.setActualDays(actualDays);
+ summary.setLateCount(lateCount);
+ summary.setEarlyCount(earlyCount);
+ summary.setAbsentCount(absentCount);
+ summary.setOvertimeHours(overtimeHours);
+ attendanceSummaryMapper.updateById(summary);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void generateDeptMonthlySummary(Long deptId, String yearMonth) {
+ // 鑾峰彇閮ㄩ棬鎵�鏈夊湪鑱屽憳宸�
+ List<HrmEmployeeDO> employees = employeeService.getEmployeeList(null);
+ if (CollUtil.isEmpty(employees)) {
+ return;
+ }
+ // 绛涢�夐儴闂ㄥ憳宸�
+ List<HrmEmployeeDO> deptEmployees = employees.stream()
+ .filter(e -> deptId.equals(e.getDeptId()))
+ .toList();
+ for (HrmEmployeeDO employee : deptEmployees) {
+ generateMonthlySummary(employee.getId(), yearMonth);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void generateAllMonthlySummary(String yearMonth) {
+ // 鑾峰彇鎵�鏈夊湪鑱屽憳宸�
+ List<HrmEmployeeDO> employees = employeeService.getEmployeeList(null);
+ if (CollUtil.isEmpty(employees)) {
+ return;
+ }
+ for (HrmEmployeeDO employee : employees) {
+ generateMonthlySummary(employee.getId(), yearMonth);
+ }
+ }
+
+ /**
+ * 璁$畻搴斿嚭鍕ゅぉ鏁帮紙宸ヤ綔鏃ユ暟閲忥級
+ */
+ private int calculateWorkDays(LocalDate startDate, LocalDate endDate) {
+ int workDays = 0;
+ LocalDate current = startDate;
+ while (!current.isAfter(endDate)) {
+ if (attendanceHolidayService.isWorkDay(current)) {
+ workDays++;
+ }
+ current = current.plusDays(1);
+ }
+ return workDays;
+ }
+
+ @Override
+ public PageResult<HrmAttendanceSummaryDO> getAttendanceSummaryPage(HrmAttendanceSummaryPageReqVO pageReqVO) {
+ return attendanceSummaryMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<HrmAttendanceSummaryDO>()
+ .eqIfPresent(HrmAttendanceSummaryDO::getUserId, pageReqVO.getUserId())
+ .eqIfPresent(HrmAttendanceSummaryDO::getDeptId, pageReqVO.getDeptId())
+ .eqIfPresent(HrmAttendanceSummaryDO::getYearMonth, pageReqVO.getYearMonth())
+ .orderByDesc(HrmAttendanceSummaryDO::getYearMonth)
+ .orderByAsc(HrmAttendanceSummaryDO::getDeptId));
+ }
+
+ @Override
+ public HrmAttendanceSummaryDO getMonthlySummary(Long userId, String yearMonth) {
+ return attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth);
+ }
+
+ @Override
+ public List<HrmAttendanceSummaryDO> getDeptMonthlySummaryList(Long deptId, String yearMonth) {
+ return attendanceSummaryMapper.selectListByDeptIdAndYearMonth(deptId, yearMonth);
+ }
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..82ab3b7
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.hrm.service.attendance.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceExceptionMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceExceptionTypeEnum;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceExceptionService;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceRecordService;
+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;
+
+/**
+ * 鑰冨嫟寮傚父鐢宠瀹℃壒鐨勭粨鏋滅洃鍚櫒瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Component
+@Slf4j
+public class HrmAttendanceExceptionStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private HrmAttendanceExceptionService attendanceExceptionService;
+ @Resource
+ private HrmAttendanceExceptionMapper attendanceExceptionMapper;
+ @Resource
+ private HrmAttendanceRecordService attendanceRecordService;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ鑰冨嫟寮傚父鐢宠
+ Long exceptionId;
+ try {
+ exceptionId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return;
+ }
+
+ HrmAttendanceExceptionDO exception = attendanceExceptionMapper.selectById(exceptionId);
+ if (exception == null) {
+ return;
+ }
+
+ // 鍙湁瀹℃壒涓姸鎬佺殑鑰冨嫟寮傚父鐢宠鎵嶅鐞�
+ if (!HrmAuditStatusEnum.PROCESS.getStatus().equals(exception.getStatus())) {
+ log.warn("[onApplicationEvent] 鑰冨嫟寮傚父鐢宠({}) 涓嶅浜庡鎵逛腑鐘舵�侊紝褰撳墠鐘舵��: {}", exceptionId, exception.getStatus());
+ return;
+ }
+
+ log.info("[onApplicationEvent] 鑰冨嫟寮傚父鐢宠({}) 瀹℃壒鐘舵�佹洿鏂�: {}", exceptionId, event.getStatus());
+ // 杞崲瀹℃壒鐘舵��
+ Integer auditStatus = HrmAuditStatusUtils.convertBpmResultToAuditStatus(event.getStatus());
+ // 鏇存柊瀹℃壒鐘舵��
+ attendanceExceptionService.updateAttendanceExceptionAuditStatus(exceptionId, auditStatus);
+
+ // 琛ュ崱瀹℃壒閫氳繃鍚庯紝鏇存柊鑰冨嫟璁板綍
+ if (HrmAttendanceExceptionTypeEnum.SUPPLEMENT.getType().equals(exception.getType())
+ && HrmAuditStatusEnum.APPROVE.getStatus().equals(auditStatus)) {
+ log.info("[onApplicationEvent] 琛ュ崱鐢宠({}) 瀹℃壒閫氳繃锛屽紑濮嬫洿鏂拌�冨嫟璁板綍", exceptionId);
+ attendanceRecordService.supplementClockIn(exception);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationService.java
new file mode 100644
index 0000000..d6c4ffa
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationService.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEducationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEducationDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐鏁欒偛缁忓巻 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmEmployeeEducationService {
+
+ /**
+ * 鍒涘缓鏁欒偛缁忓巻
+ */
+ Long createEducation(@Valid HrmEmployeeEducationSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鏁欒偛缁忓巻
+ */
+ void updateEducation(@Valid HrmEmployeeEducationSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎鏁欒偛缁忓巻
+ */
+ void deleteEducation(Long id);
+
+ /**
+ * 鑾峰緱鏁欒偛缁忓巻鍒楄〃
+ */
+ List<HrmEmployeeEducationDO> getEducationListByEmployeeId(Long employeeId);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationServiceImpl.java
new file mode 100644
index 0000000..1250aaa
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationServiceImpl.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEducationSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEducationDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEducationMapper;
+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_EDUCATION_NOT_EXISTS;
+
+/**
+ * 鍛樺伐鏁欒偛缁忓巻 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmEmployeeEducationServiceImpl implements HrmEmployeeEducationService {
+
+ @Resource
+ private HrmEmployeeEducationMapper educationMapper;
+
+ @Override
+ public Long createEducation(HrmEmployeeEducationSaveReqVO createReqVO) {
+ HrmEmployeeEducationDO education = BeanUtils.toBean(createReqVO, HrmEmployeeEducationDO.class);
+ educationMapper.insert(education);
+ return education.getId();
+ }
+
+ @Override
+ public void updateEducation(HrmEmployeeEducationSaveReqVO updateReqVO) {
+ validateEducationExists(updateReqVO.getId());
+ HrmEmployeeEducationDO updateObj = BeanUtils.toBean(updateReqVO, HrmEmployeeEducationDO.class);
+ educationMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteEducation(Long id) {
+ validateEducationExists(id);
+ educationMapper.deleteById(id);
+ }
+
+ private void validateEducationExists(Long id) {
+ if (educationMapper.selectById(id) == null) {
+ throw exception(EMPLOYEE_EDUCATION_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public List<HrmEmployeeEducationDO> getEducationListByEmployeeId(Long employeeId) {
+ return educationMapper.selectByEmployeeId(employeeId);
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactService.java
new file mode 100644
index 0000000..9cac2db
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactService.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEmergencyContactSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEmergencyContactDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐绱ф�ヨ仈绯讳汉 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmEmployeeEmergencyContactService {
+
+ /**
+ * 鍒涘缓绱ф�ヨ仈绯讳汉
+ */
+ Long createEmergencyContact(@Valid HrmEmployeeEmergencyContactSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊绱ф�ヨ仈绯讳汉
+ */
+ void updateEmergencyContact(@Valid HrmEmployeeEmergencyContactSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎绱ф�ヨ仈绯讳汉
+ */
+ void deleteEmergencyContact(Long id);
+
+ /**
+ * 鑾峰緱绱ф�ヨ仈绯讳汉鍒楄〃
+ */
+ List<HrmEmployeeEmergencyContactDO> getEmergencyContactListByEmployeeId(Long employeeId);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactServiceImpl.java
new file mode 100644
index 0000000..1332bdb
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactServiceImpl.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEmergencyContactSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEmergencyContactDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEmergencyContactMapper;
+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_EMERGENCY_CONTACT_NOT_EXISTS;
+
+/**
+ * 鍛樺伐绱ф�ヨ仈绯讳汉 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmEmployeeEmergencyContactServiceImpl implements HrmEmployeeEmergencyContactService {
+
+ @Resource
+ private HrmEmployeeEmergencyContactMapper emergencyContactMapper;
+
+ @Override
+ public Long createEmergencyContact(HrmEmployeeEmergencyContactSaveReqVO createReqVO) {
+ HrmEmployeeEmergencyContactDO emergencyContact = BeanUtils.toBean(createReqVO, HrmEmployeeEmergencyContactDO.class);
+ emergencyContactMapper.insert(emergencyContact);
+ return emergencyContact.getId();
+ }
+
+ @Override
+ public void updateEmergencyContact(HrmEmployeeEmergencyContactSaveReqVO updateReqVO) {
+ validateEmergencyContactExists(updateReqVO.getId());
+ HrmEmployeeEmergencyContactDO updateObj = BeanUtils.toBean(updateReqVO, HrmEmployeeEmergencyContactDO.class);
+ emergencyContactMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteEmergencyContact(Long id) {
+ validateEmergencyContactExists(id);
+ emergencyContactMapper.deleteById(id);
+ }
+
+ private void validateEmergencyContactExists(Long id) {
+ if (emergencyContactMapper.selectById(id) == null) {
+ throw exception(EMPLOYEE_EMERGENCY_CONTACT_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public List<HrmEmployeeEmergencyContactDO> getEmergencyContactListByEmployeeId(Long employeeId) {
+ return emergencyContactMapper.selectByEmployeeId(employeeId);
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
new file mode 100644
index 0000000..709fc81
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeRespVO;
+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 jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍛樺伐淇℃伅 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmEmployeeService {
+
+ /**
+ * 鍒涘缓鍛樺伐
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @return 鍛樺伐ID
+ */
+ Long createEmployee(@Valid HrmEmployeeSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鍛樺伐
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateEmployee(@Valid HrmEmployeeSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎鍛樺伐
+ *
+ * @param id 鍛樺伐ID
+ */
+ void deleteEmployee(Long id);
+
+ /**
+ * 鑾峰緱鍛樺伐
+ *
+ * @param id 鍛樺伐ID
+ * @return 鍛樺伐
+ */
+ HrmEmployeeDO getEmployee(Long id);
+
+ /**
+ * 鑾峰緱鍛樺伐璇︽儏锛堝惈閮ㄩ棬鍚嶇О銆佸矖浣嶅悕绉帮級
+ *
+ * @param id 鍛樺伐ID
+ * @return 鍛樺伐璇︽儏
+ */
+ HrmEmployeeRespVO getEmployeeDetail(Long id);
+
+ /**
+ * 鏍¢獙鍛樺伐鏄惁瀛樺湪
+ *
+ * @param id 鍛樺伐ID
+ * @return 鍛樺伐
+ */
+ HrmEmployeeDO validateEmployee(Long id);
+
+ /**
+ * 鑾峰緱鍛樺伐鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 鍛樺伐鍒嗛〉
+ */
+ PageResult<HrmEmployeeDO> getEmployeePage(HrmEmployeePageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鍛樺伐鍒楄〃
+ *
+ * @param ids 鍛樺伐ID鍒楄〃
+ * @return 鍛樺伐鍒楄〃
+ */
+ List<HrmEmployeeDO> getEmployeeList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱鎸囧畾鐘舵�佺殑鍛樺伐绠�鍗曞垪琛�
+ *
+ * @param employeeStatus 鍛樺伐鐘舵�侊紝涓� null 鏃舵煡璇㈠叏閮�
+ * @return 鍛樺伐绠�鍗曞垪琛�
+ */
+ List<HrmEmployeeSimpleRespVO> getEmployeeSimpleList(Integer employeeStatus);
+
+ /**
+ * 鏍规嵁绯荤粺鐢ㄦ埛ID鑾峰彇鍛樺伐
+ *
+ * @param userId 绯荤粺鐢ㄦ埛ID
+ * @return 鍛樺伐
+ */
+ HrmEmployeeDO getEmployeeByUserId(Long userId);
+
+}
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
new file mode 100644
index 0000000..72fadf6
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -0,0 +1,275 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.hutool.core.collection.CollUtil;
+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.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.HrmEmployeePageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeRespVO;
+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.controller.admin.employee.vo.HrmEmployeeWorkHistorySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEducationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEmergencyContactDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeWorkHistoryDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEducationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEmergencyContactMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeWorkHistoryMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+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.permission.RoleApi;
+import cn.iocoder.yudao.module.system.api.permission.dto.RoleRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 鍛樺伐淇℃伅 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmEmployeeServiceImpl implements HrmEmployeeService {
+
+ @Resource
+ private HrmEmployeeMapper employeeMapper;
+ @Resource
+ private HrmEmployeeEducationMapper educationMapper;
+ @Resource
+ private HrmEmployeeWorkHistoryMapper workHistoryMapper;
+ @Resource
+ private HrmEmployeeEmergencyContactMapper emergencyContactMapper;
+ @Resource
+ private HrmNoRedisDAO noRedisDAO;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private PostApi postApi;
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private RoleApi roleApi;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createEmployee(HrmEmployeeSaveReqVO createReqVO) {
+ // 1. 鏍¢獙閮ㄩ棬
+ deptApi.validateDeptList(Collections.singleton(createReqVO.getDeptId()));
+ // 2. 鏍¢獙宀椾綅
+ postApi.validPostList(Collections.singleton(createReqVO.getPostId()));
+ // 3. 鏍¢獙鎵嬫満鍙锋槸鍚﹀凡瀛樺湪
+ if (employeeMapper.selectByPhone(createReqVO.getPhone()) != null) {
+ throw exception(EMPLOYEE_PHONE_EXISTS);
+ }
+ // 4. 鍚屾鍒涘缓绯荤粺鐢ㄦ埛
+ AdminUserCreateReqDTO userCreateReqDTO = new AdminUserCreateReqDTO();
+ userCreateReqDTO.setUsername(createReqVO.getPhone());
+ userCreateReqDTO.setNickname(createReqVO.getName());
+ userCreateReqDTO.setDeptId(createReqVO.getDeptId());
+ userCreateReqDTO.setPostIds(Collections.singleton(createReqVO.getPostId()));
+ userCreateReqDTO.setMobile(createReqVO.getPhone());
+ userCreateReqDTO.setPassword(createReqVO.getPhone());
+ userCreateReqDTO.setSex(createReqVO.getGender());
+ userCreateReqDTO.setEmail(createReqVO.getEmail());
+ userCreateReqDTO.setRemark(createReqVO.getRemark());
+ // 璁剧疆瑙掕壊
+ if (CollUtil.isNotEmpty(createReqVO.getRoleIds())) {
+ userCreateReqDTO.setRoleIds(createReqVO.getRoleIds().stream().collect(Collectors.toSet()));
+ }
+ Long userId = adminUserApi.createUser(userCreateReqDTO);
+ // 5. 鐢熸垚鍛樺伐缂栧彿
+ String employeeNo = generateUniqueEmployeeNo();
+ // 6. 鎻掑叆鍛樺伐璁板綍
+ HrmEmployeeDO employee = BeanUtils.toBean(createReqVO, HrmEmployeeDO.class);
+ employee.setEmployeeNo(employeeNo);
+ employee.setUserId(userId); // 鍏宠仈绯荤粺鐢ㄦ埛ID
+ // roleIds 瀛樺偍涓� JSON
+ if (CollUtil.isNotEmpty(createReqVO.getRoleIds())) {
+ employee.setRoleIds(JSONUtil.toJsonStr(createReqVO.getRoleIds()));
+ }
+ employeeMapper.insert(employee);
+ Long employeeId = employee.getId();
+ // 7. 鍚屾娣诲姞鏁欒偛缁忓巻
+ if (CollUtil.isNotEmpty(createReqVO.getEducationList())) {
+ createReqVO.getEducationList().forEach(educationVO -> {
+ HrmEmployeeEducationDO education = BeanUtils.toBean(educationVO, HrmEmployeeEducationDO.class);
+ education.setEmployeeId(employeeId);
+ educationMapper.insert(education);
+ });
+ }
+ // 8. 鍚屾娣诲姞宸ヤ綔缁忓巻
+ if (CollUtil.isNotEmpty(createReqVO.getWorkHistoryList())) {
+ createReqVO.getWorkHistoryList().forEach(workHistoryVO -> {
+ HrmEmployeeWorkHistoryDO workHistory = BeanUtils.toBean(workHistoryVO, HrmEmployeeWorkHistoryDO.class);
+ workHistory.setEmployeeId(employeeId);
+ workHistoryMapper.insert(workHistory);
+ });
+ }
+ // 9. 鍚屾娣诲姞绱ф�ヨ仈绯讳汉
+ if (CollUtil.isNotEmpty(createReqVO.getEmergencyContactList())) {
+ createReqVO.getEmergencyContactList().forEach(emergencyContactVO -> {
+ HrmEmployeeEmergencyContactDO emergencyContact = BeanUtils.toBean(emergencyContactVO, HrmEmployeeEmergencyContactDO.class);
+ emergencyContact.setEmployeeId(employeeId);
+ emergencyContactMapper.insert(emergencyContact);
+ });
+ }
+ return employeeId;
+ }
+
+ private String generateUniqueEmployeeNo() {
+ String employeeNo = noRedisDAO.generateEmployeeNo();
+ if (employeeMapper.selectByEmployeeNo(employeeNo) != null) {
+ return generateUniqueEmployeeNo();
+ }
+ return employeeNo;
+ }
+
+ @Override
+ public void updateEmployee(HrmEmployeeSaveReqVO updateReqVO) {
+ // 鏍¢獙瀛樺湪
+ HrmEmployeeDO employee = validateEmployeeExists(updateReqVO.getId());
+ // 鏍¢獙閮ㄩ棬
+ deptApi.validateDeptList(Collections.singleton(updateReqVO.getDeptId()));
+ // 鏍¢獙宀椾綅
+ postApi.validPostList(Collections.singleton(updateReqVO.getPostId()));
+ // 鏍¢獙鎵嬫満鍙锋槸鍚﹀凡瀛樺湪锛堝鏋滄墜鏈哄彿鏈夊彉鏇达級
+ if (!Objects.equals(employee.getPhone(), updateReqVO.getPhone())
+ && employeeMapper.selectByPhone(updateReqVO.getPhone()) != null) {
+ throw exception(EMPLOYEE_PHONE_EXISTS);
+ }
+ // 鏇存柊
+ HrmEmployeeDO updateObj = BeanUtils.toBean(updateReqVO, HrmEmployeeDO.class);
+ // roleIds 瀛樺偍涓� JSON
+ if (CollUtil.isNotEmpty(updateReqVO.getRoleIds())) {
+ updateObj.setRoleIds(JSONUtil.toJsonStr(updateReqVO.getRoleIds()));
+ }
+ employeeMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteEmployee(Long id) {
+ // 鏍¢獙瀛樺湪
+ validateEmployeeExists(id);
+ // 鍒犻櫎
+ employeeMapper.deleteById(id);
+ }
+
+ private HrmEmployeeDO validateEmployeeExists(Long id) {
+ HrmEmployeeDO employee = employeeMapper.selectById(id);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS);
+ }
+ return employee;
+ }
+
+ @Override
+ public HrmEmployeeDO getEmployee(Long id) {
+ return employeeMapper.selectById(id);
+ }
+
+ @Override
+ public HrmEmployeeRespVO getEmployeeDetail(Long id) {
+ HrmEmployeeDO employee = getEmployee(id);
+ if (employee == null) {
+ return null;
+ }
+ HrmEmployeeRespVO respVO = BeanUtils.toBean(employee, HrmEmployeeRespVO.class);
+ // 瑙f瀽 roleIds JSON
+ if (StrUtil.isNotBlank(employee.getRoleIds())) {
+ respVO.setRoleIds(JSONUtil.toList(employee.getRoleIds(), Long.class));
+ }
+ // 濉厖閮ㄩ棬鍚嶇О
+ if (employee.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(employee.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ // 濉厖宀椾綅鍚嶇О
+ if (employee.getPostId() != null) {
+ Map<Long, PostRespDTO> postMap = postApi.getPostMap(Collections.singleton(employee.getPostId()));
+ PostRespDTO post = postMap.get(employee.getPostId());
+ if (post != null) {
+ respVO.setPostName(post.getName());
+ }
+ }
+ // 濉厖瑙掕壊鍚嶇О
+ if (CollUtil.isNotEmpty(respVO.getRoleIds())) {
+ List<RoleRespDTO> roles = roleApi.getRoleList(respVO.getRoleIds());
+ if (CollUtil.isNotEmpty(roles)) {
+ respVO.setRoleNames(roles.stream().map(RoleRespDTO::getName).collect(Collectors.joining(",")));
+ }
+ }
+ return respVO;
+ }
+
+ @Override
+ public HrmEmployeeDO validateEmployee(Long id) {
+ HrmEmployeeDO employee = employeeMapper.selectById(id);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS);
+ }
+ return employee;
+ }
+
+ @Override
+ public PageResult<HrmEmployeeDO> getEmployeePage(HrmEmployeePageReqVO pageReqVO) {
+ return employeeMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<HrmEmployeeDO> getEmployeeList(Collection<Long> ids) {
+ return employeeMapper.selectByIds(ids);
+ }
+
+ @Override
+ public List<HrmEmployeeSimpleRespVO> getEmployeeSimpleList(Integer employeeStatus) {
+ List<HrmEmployeeDO> list = employeeMapper.selectListByStatus(employeeStatus);
+ // 濉厖閮ㄩ棬鍚嶇О
+ Set<Long> deptIds = list.stream()
+ .map(HrmEmployeeDO::getDeptId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+ return list.stream().map(emp -> {
+ HrmEmployeeSimpleRespVO vo = new HrmEmployeeSimpleRespVO();
+ vo.setId(emp.getId());
+ vo.setEmployeeNo(emp.getEmployeeNo());
+ vo.setName(emp.getName());
+ vo.setEmployeeStatus(emp.getEmployeeStatus());
+ if (emp.getDeptId() != null && deptMap.containsKey(emp.getDeptId())) {
+ vo.setDeptName(deptMap.get(emp.getDeptId()).getName());
+ }
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+ @Override
+ public HrmEmployeeDO getEmployeeByUserId(Long userId) {
+ return employeeMapper.selectByUserId(userId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryService.java
new file mode 100644
index 0000000..101e155
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryService.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeWorkHistorySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeWorkHistoryDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐宸ヤ綔缁忓巻 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmEmployeeWorkHistoryService {
+
+ /**
+ * 鍒涘缓宸ヤ綔缁忓巻
+ */
+ Long createWorkHistory(@Valid HrmEmployeeWorkHistorySaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊宸ヤ綔缁忓巻
+ */
+ void updateWorkHistory(@Valid HrmEmployeeWorkHistorySaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎宸ヤ綔缁忓巻
+ */
+ void deleteWorkHistory(Long id);
+
+ /**
+ * 鑾峰緱宸ヤ綔缁忓巻鍒楄〃
+ */
+ List<HrmEmployeeWorkHistoryDO> getWorkHistoryListByEmployeeId(Long employeeId);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryServiceImpl.java
new file mode 100644
index 0000000..8c07601
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryServiceImpl.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.hrm.service.employee;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeWorkHistorySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeWorkHistoryDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeWorkHistoryMapper;
+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_WORK_HISTORY_NOT_EXISTS;
+
+/**
+ * 鍛樺伐宸ヤ綔缁忓巻 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmEmployeeWorkHistoryServiceImpl implements HrmEmployeeWorkHistoryService {
+
+ @Resource
+ private HrmEmployeeWorkHistoryMapper workHistoryMapper;
+
+ @Override
+ public Long createWorkHistory(HrmEmployeeWorkHistorySaveReqVO createReqVO) {
+ HrmEmployeeWorkHistoryDO workHistory = BeanUtils.toBean(createReqVO, HrmEmployeeWorkHistoryDO.class);
+ workHistoryMapper.insert(workHistory);
+ return workHistory.getId();
+ }
+
+ @Override
+ public void updateWorkHistory(HrmEmployeeWorkHistorySaveReqVO updateReqVO) {
+ validateWorkHistoryExists(updateReqVO.getId());
+ HrmEmployeeWorkHistoryDO updateObj = BeanUtils.toBean(updateReqVO, HrmEmployeeWorkHistoryDO.class);
+ workHistoryMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteWorkHistory(Long id) {
+ validateWorkHistoryExists(id);
+ workHistoryMapper.deleteById(id);
+ }
+
+ private void validateWorkHistoryExists(Long id) {
+ if (workHistoryMapper.selectById(id) == null) {
+ throw exception(EMPLOYEE_WORK_HISTORY_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public List<HrmEmployeeWorkHistoryDO> getWorkHistoryListByEmployeeId(Long employeeId) {
+ return workHistoryMapper.selectByEmployeeId(employeeId);
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionService.java
new file mode 100644
index 0000000..c4f4907
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionService.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecurityConfigDO;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 钖叕鎵i櫎璁$畻 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmDeductionService {
+
+ /**
+ * 璁$畻绀句繚鎵i櫎锛堜釜浜洪儴鍒嗭級
+ *
+ * @param socialSecurityBase 绀句繚鍩烘暟
+ * @param effectiveDate 鐢熸晥鏃ユ湡
+ * @return 绀句繚鎵i櫎閲戦
+ */
+ BigDecimal calculateSocialSecurityDeduction(BigDecimal socialSecurityBase, LocalDate effectiveDate);
+
+ /**
+ * 璁$畻鍏Н閲戞墸闄わ紙涓汉閮ㄥ垎锛�
+ *
+ * @param housingFundBase 鍏Н閲戝熀鏁�
+ * @param effectiveDate 鐢熸晥鏃ユ湡
+ * @return 鍏Н閲戞墸闄ら噾棰�
+ */
+ BigDecimal calculateHousingFundDeduction(BigDecimal housingFundBase, LocalDate effectiveDate);
+
+ /**
+ * 璁$畻涓◣鎵i櫎
+ *
+ * @param taxableIncome 搴旂撼绋庢墍寰楅锛堟湀鏀跺叆 - 绀句繚鍏Н閲� - 涓撻」鎵i櫎 - 5000璧峰緛鐐癸級
+ * @param effectiveDate 鐢熸晥鏃ユ湡
+ * @return 涓◣鎵i櫎閲戦
+ */
+ BigDecimal calculateTaxDeduction(BigDecimal taxableIncome, LocalDate effectiveDate);
+
+ /**
+ * 鑾峰彇鏈�鏂扮殑绀句繚鍏Н閲戦厤缃�
+ *
+ * @param effectiveDate 鐢熸晥鏃ユ湡
+ * @return 绀句繚鍏Н閲戦厤缃�
+ */
+ HrmSocialSecurityConfigDO getLatestSocialSecurityConfig(LocalDate effectiveDate);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionServiceImpl.java
new file mode 100644
index 0000000..06cd7ae
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionServiceImpl.java
@@ -0,0 +1,154 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecurityConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmTaxRateConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecurityConfigMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmTaxRateConfigMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 钖叕鎵i櫎璁$畻 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmDeductionServiceImpl implements HrmDeductionService {
+
+ /**
+ * 涓◣璧峰緛鐐癸紙姣忔湀5000鍏冿級
+ */
+ private static final BigDecimal TAX_THRESHOLD = new BigDecimal("5000");
+
+ /**
+ * 榛樿缂寸撼姣斾緥锛堝綋鍛樺伐妗f涓病鏈夐厤缃椂浣跨敤锛�
+ */
+ private static final BigDecimal DEFAULT_PENSION_PERSONAL_RATIO = new BigDecimal("0.08");
+ private static final BigDecimal DEFAULT_MEDICAL_PERSONAL_RATIO = new BigDecimal("0.02");
+ 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");
+
+ @Resource
+ private HrmSocialSecurityConfigMapper socialSecurityConfigMapper;
+ @Resource
+ private HrmTaxRateConfigMapper taxRateConfigMapper;
+
+ @Override
+ public BigDecimal calculateSocialSecurityDeduction(BigDecimal socialSecurityBase, LocalDate effectiveDate) {
+ // 姝ゆ柟娉曚繚鐣欎綔涓哄鐢紝浣嗕笉鎺ㄨ崘浣跨敤
+ // 鎺ㄨ崘浣跨敤 calculateSocialSecurityDeductionByConfig 鏂规硶
+ if (socialSecurityBase == null || socialSecurityBase.compareTo(BigDecimal.ZERO) <= 0) {
+ return BigDecimal.ZERO;
+ }
+ // 浣跨敤榛樿姣斾緥璁$畻
+ BigDecimal pension = socialSecurityBase.multiply(DEFAULT_PENSION_PERSONAL_RATIO);
+ BigDecimal medical = socialSecurityBase.multiply(DEFAULT_MEDICAL_PERSONAL_RATIO);
+ BigDecimal unemployment = socialSecurityBase.multiply(DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO);
+ return pension.add(medical).add(unemployment).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ @Override
+ public BigDecimal calculateHousingFundDeduction(BigDecimal housingFundBase, LocalDate effectiveDate) {
+ // 姝ゆ柟娉曚繚鐣欎綔涓哄鐢紝浣嗕笉鎺ㄨ崘浣跨敤
+ if (housingFundBase == null || housingFundBase.compareTo(BigDecimal.ZERO) <= 0) {
+ return BigDecimal.ZERO;
+ }
+ // 浣跨敤榛樿姣斾緥璁$畻
+ return housingFundBase.multiply(DEFAULT_HOUSING_FUND_PERSONAL_RATIO).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 鏍规嵁鍛樺伐绀句繚妗f閰嶇疆璁$畻绀句繚鎵i櫎锛堜釜浜洪儴鍒嗭級
+ *
+ * @param socialSecurityBase 绀句繚鍩烘暟
+ * @param pensionPersonalRatio 鍏昏�佷繚闄╀釜浜虹即绾虫瘮渚�
+ * @param medicalPersonalRatio 鍖荤枟淇濋櫓涓汉缂寸撼姣斾緥
+ * @param unemploymentPersonalRatio 澶变笟淇濋櫓涓汉缂寸撼姣斾緥
+ * @return 绀句繚鎵i櫎閲戦
+ */
+ public BigDecimal calculateSocialSecurityDeductionByConfig(
+ BigDecimal socialSecurityBase,
+ BigDecimal pensionPersonalRatio,
+ BigDecimal medicalPersonalRatio,
+ BigDecimal unemploymentPersonalRatio) {
+ if (socialSecurityBase == null || socialSecurityBase.compareTo(BigDecimal.ZERO) <= 0) {
+ return BigDecimal.ZERO;
+ }
+
+ // 浣跨敤浼犲叆鐨勬瘮渚嬶紝濡傛灉涓虹┖鍒欎娇鐢ㄩ粯璁ゅ��
+ BigDecimal pensionRatio = pensionPersonalRatio != null ? pensionPersonalRatio : DEFAULT_PENSION_PERSONAL_RATIO;
+ BigDecimal medicalRatio = medicalPersonalRatio != null ? medicalPersonalRatio : DEFAULT_MEDICAL_PERSONAL_RATIO;
+ BigDecimal unemploymentRatio = unemploymentPersonalRatio != null ? unemploymentPersonalRatio : DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO;
+
+ // 璁$畻涓汉缂寸撼閮ㄥ垎锛氬吇鑰� + 鍖荤枟 + 澶变笟
+ BigDecimal pension = socialSecurityBase.multiply(pensionRatio);
+ BigDecimal medical = socialSecurityBase.multiply(medicalRatio);
+ BigDecimal unemployment = socialSecurityBase.multiply(unemploymentRatio);
+
+ return pension.add(medical).add(unemployment).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 鏍规嵁鍛樺伐绀句繚妗f閰嶇疆璁$畻鍏Н閲戞墸闄わ紙涓汉閮ㄥ垎锛�
+ *
+ * @param housingFundBase 鍏Н閲戝熀鏁�
+ * @param housingFundPersonalRatio 鍏Н閲戜釜浜虹即绾虫瘮渚�
+ * @return 鍏Н閲戞墸闄ら噾棰�
+ */
+ public BigDecimal calculateHousingFundDeductionByConfig(
+ BigDecimal housingFundBase,
+ BigDecimal housingFundPersonalRatio) {
+ if (housingFundBase == null || housingFundBase.compareTo(BigDecimal.ZERO) <= 0) {
+ return BigDecimal.ZERO;
+ }
+
+ // 浣跨敤浼犲叆鐨勬瘮渚嬶紝濡傛灉涓虹┖鍒欎娇鐢ㄩ粯璁ゅ��
+ BigDecimal ratio = housingFundPersonalRatio != null ? housingFundPersonalRatio : DEFAULT_HOUSING_FUND_PERSONAL_RATIO;
+
+ return housingFundBase.multiply(ratio).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ @Override
+ public BigDecimal calculateTaxDeduction(BigDecimal taxableIncome, LocalDate effectiveDate) {
+ if (taxableIncome == null || taxableIncome.compareTo(BigDecimal.ZERO) <= 0) {
+ return BigDecimal.ZERO;
+ }
+
+ List<HrmTaxRateConfigDO> taxRates = taxRateConfigMapper.selectListByEffectiveDate(effectiveDate);
+ if (taxRates == null || taxRates.isEmpty()) {
+ return BigDecimal.ZERO;
+ }
+
+ // 鏌ユ壘瀵瑰簲鐨勭◣鐜囨。浣�
+ for (HrmTaxRateConfigDO taxRate : taxRates) {
+ BigDecimal minAmount = taxRate.getMinAmount();
+ BigDecimal maxAmount = taxRate.getMaxAmount();
+
+ // 鍒ゆ柇鏄惁鍦ㄨ妗d綅鑼冨洿鍐咃紙maxAmount涓�0琛ㄧず鏃犱笂闄愶級
+ boolean inRange = taxableIncome.compareTo(minAmount) >= 0
+ && (maxAmount.compareTo(BigDecimal.ZERO) == 0 || taxableIncome.compareTo(maxAmount) < 0);
+
+ if (inRange) {
+ // 璁$畻涓◣ = 搴旂撼绋庢墍寰楅 * 绋庣巼 - 閫熺畻鎵i櫎鏁�
+ return taxableIncome.multiply(taxRate.getTaxRate())
+ .subtract(taxRate.getQuickDeduction())
+ .setScale(2, RoundingMode.HALF_UP);
+ }
+ }
+
+ return BigDecimal.ZERO;
+ }
+
+ @Override
+ public HrmSocialSecurityConfigDO getLatestSocialSecurityConfig(LocalDate effectiveDate) {
+ return socialSecurityConfigMapper.selectLatestByEffectiveDate(effectiveDate);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityService.java
new file mode 100644
index 0000000..ec403b5
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityService.java
@@ -0,0 +1,56 @@
+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.HrmEmployeeSocialSecurityPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐绀句繚鍏Н閲戞。妗� Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmEmployeeSocialSecurityService {
+
+ /**
+ * 鍒涘缓鍛樺伐绀句繚鍏Н閲戞。妗�
+ */
+ Long createEmployeeSocialSecurity(HrmEmployeeSocialSecurityDO socialSecurity);
+
+ /**
+ * 鏇存柊鍛樺伐绀句繚鍏Н閲戞。妗�
+ */
+ void updateEmployeeSocialSecurity(HrmEmployeeSocialSecurityDO socialSecurity);
+
+ /**
+ * 鍒犻櫎鍛樺伐绀句繚鍏Н閲戞。妗�
+ */
+ void deleteEmployeeSocialSecurity(Long id);
+
+ /**
+ * 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗�
+ */
+ HrmEmployeeSocialSecurityDO getEmployeeSocialSecurity(Long id);
+
+ /**
+ * 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堝垎椤�
+ */
+ PageResult<HrmEmployeeSocialSecurityDO> getEmployeeSocialSecurityPage(HrmEmployeeSocialSecurityPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰彇鍛樺伐鏈�鏂扮敓鏁堢殑绀句繚鍏Н閲戞。妗�
+ */
+ HrmEmployeeSocialSecurityDO getLatestEmployeeSocialSecurity(Long userId);
+
+ /**
+ * 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堝巻鍙插垪琛�
+ */
+ List<HrmEmployeeSocialSecurityDO> getEmployeeSocialSecurityList(Long userId);
+
+ /**
+ * 鎵归噺鍒涘缓鍛樺伐绀句繚鍏Н閲戞。妗堬紙鏍规嵁鍛樺伐鍒楄〃锛�
+ */
+ void batchCreateEmployeeSocialSecurity(List<HrmEmployeeSocialSecurityDO> socialSecurityList);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityServiceImpl.java
new file mode 100644
index 0000000..a2cf5d0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityServiceImpl.java
@@ -0,0 +1,81 @@
+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.HrmEmployeeSocialSecurityPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSocialSecurityMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+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_SOCIAL_SECURITY_NOT_EXISTS;
+
+/**
+ * 鍛樺伐绀句繚鍏Н閲戞。妗� Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmEmployeeSocialSecurityServiceImpl implements HrmEmployeeSocialSecurityService {
+
+ @Resource
+ private HrmEmployeeSocialSecurityMapper employeeSocialSecurityMapper;
+
+ @Override
+ public Long createEmployeeSocialSecurity(HrmEmployeeSocialSecurityDO socialSecurity) {
+ employeeSocialSecurityMapper.insert(socialSecurity);
+ return socialSecurity.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void batchCreateEmployeeSocialSecurity(List<HrmEmployeeSocialSecurityDO> socialSecurityList) {
+ for (HrmEmployeeSocialSecurityDO socialSecurity : socialSecurityList) {
+ employeeSocialSecurityMapper.insert(socialSecurity);
+ }
+ }
+
+ @Override
+ public void updateEmployeeSocialSecurity(HrmEmployeeSocialSecurityDO socialSecurity) {
+ validateEmployeeSocialSecurityExists(socialSecurity.getId());
+ employeeSocialSecurityMapper.updateById(socialSecurity);
+ }
+
+ @Override
+ public void deleteEmployeeSocialSecurity(Long id) {
+ validateEmployeeSocialSecurityExists(id);
+ employeeSocialSecurityMapper.deleteById(id);
+ }
+
+ @Override
+ public HrmEmployeeSocialSecurityDO getEmployeeSocialSecurity(Long id) {
+ return employeeSocialSecurityMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<HrmEmployeeSocialSecurityDO> getEmployeeSocialSecurityPage(HrmEmployeeSocialSecurityPageReqVO pageReqVO) {
+ return employeeSocialSecurityMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public HrmEmployeeSocialSecurityDO getLatestEmployeeSocialSecurity(Long userId) {
+ return employeeSocialSecurityMapper.selectLatestByUserId(userId);
+ }
+
+ @Override
+ public List<HrmEmployeeSocialSecurityDO> getEmployeeSocialSecurityList(Long userId) {
+ return employeeSocialSecurityMapper.selectListByUserId(userId);
+ }
+
+ private void validateEmployeeSocialSecurityExists(Long id) {
+ if (employeeSocialSecurityMapper.selectById(id) == null) {
+ throw exception(EMPLOYEE_SOCIAL_SECURITY_NOT_EXISTS);
+ }
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java
new file mode 100644
index 0000000..efcd3cf
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO;
+
+import java.util.List;
+
+/**
+ * 璇峰亣绫诲瀷鎵f閰嶇疆 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmLeaveTypeConfigService {
+
+ /**
+ * 鑾峰彇鎵�鏈夎鍋囩被鍨嬮厤缃垪琛�
+ */
+ List<HrmLeaveTypeConfigDO> getLeaveTypeConfigList();
+
+ /**
+ * 鑾峰彇璇峰亣绫诲瀷閰嶇疆
+ */
+ HrmLeaveTypeConfigDO getLeaveTypeConfig(Integer leaveType);
+
+ /**
+ * 鏇存柊璇峰亣绫诲瀷閰嶇疆
+ */
+ void updateLeaveTypeConfig(HrmLeaveTypeConfigDO config);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java
new file mode 100644
index 0000000..d806d44
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmLeaveTypeConfigMapper;
+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.LEAVE_TYPE_CONFIG_NOT_EXISTS;
+
+/**
+ * 璇峰亣绫诲瀷鎵f閰嶇疆 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmLeaveTypeConfigServiceImpl implements HrmLeaveTypeConfigService {
+
+ @Resource
+ private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
+
+ @Override
+ public List<HrmLeaveTypeConfigDO> getLeaveTypeConfigList() {
+ return leaveTypeConfigMapper.selectList();
+ }
+
+ @Override
+ public HrmLeaveTypeConfigDO getLeaveTypeConfig(Integer leaveType) {
+ return leaveTypeConfigMapper.selectByLeaveType(leaveType);
+ }
+
+ @Override
+ public void updateLeaveTypeConfig(HrmLeaveTypeConfigDO config) {
+ validateLeaveTypeConfigExists(config.getId());
+ leaveTypeConfigMapper.updateById(config);
+ }
+
+ private void validateLeaveTypeConfigExists(Long id) {
+ if (leaveTypeConfigMapper.selectById(id) == null) {
+ throw exception(LEAVE_TYPE_CONFIG_NOT_EXISTS);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java
new file mode 100644
index 0000000..d06d0c2
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java
@@ -0,0 +1,84 @@
+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.HrmSalaryCalculationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryCalculationDO;
+
+import java.util.List;
+
+/**
+ * 钖叕鏍哥畻 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmSalaryCalculationService {
+
+ /**
+ * 鑾峰緱钖叕鏍哥畻鍒嗛〉
+ */
+ PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱钖叕鏍哥畻
+ */
+ HrmSalaryCalculationDO getSalaryCalculation(Long id);
+
+ /**
+ * 鎵ц钖叕鏍哥畻锛堟寜鍛ㄦ湡锛�
+ *
+ * @param period 鏍哥畻鍛ㄦ湡锛屾牸寮忓 2024-01
+ */
+ void calculateSalary(String period);
+
+ /**
+ * 鎵ц钖叕鏍哥畻锛堟寜鍛ㄦ湡鍜岄儴闂級
+ *
+ * @param period 鏍哥畻鍛ㄦ湡锛屾牸寮忓 2024-01
+ * @param deptId 閮ㄩ棬ID锛屼负绌哄垯鏍哥畻鎵�鏈夐儴闂�
+ */
+ void calculateSalary(String period, Long deptId);
+
+ /**
+ * 鎵归噺纭钖叕鏍哥畻
+ *
+ * @param ids 鏍哥畻ID鍒楄〃
+ */
+ void confirmSalaryCalculation(List<Long> ids);
+
+ /**
+ * 鎾ら攢钖叕鏍哥畻纭
+ * 灏嗗凡纭鐨勬牳绠楄褰曠姸鎬佹仮澶嶄负寰呯‘璁わ紝鍚屾椂鍒犻櫎鍏宠仈鐨勫彂鏀炬槑缁�
+ *
+ * @param ids 鏍哥畻ID鍒楄〃
+ */
+ void revokeSalaryCalculation(List<Long> ids);
+
+ /**
+ * 鍒犻櫎钖叕鏍哥畻璁板綍锛堜粎寰呯‘璁ょ姸鎬佸彲鍒犻櫎锛�
+ *
+ * @param id 鏍哥畻ID
+ */
+ void deleteSalaryCalculation(Long id);
+
+ /**
+ * 鑾峰緱鎸囧畾鍛ㄦ湡鐨勮柂閰牳绠楀垪琛�
+ */
+ List<HrmSalaryCalculationDO> getSalaryCalculationListByPeriod(String period);
+
+ /**
+ * 棰勮閮ㄩ棬涓嬫墍鏈夊憳宸ョ殑钖祫鏍哥畻淇℃伅锛堝凡璁$畻濂界殑锛�
+ *
+ * @param period 鏍哥畻鍛ㄦ湡锛屾牸寮忓 2024-01
+ * @param deptId 閮ㄩ棬ID
+ * @return 钖叕鏍哥畻鍒楄〃
+ */
+ List<HrmSalaryCalculationDO> previewSalaryCalculation(String period, Long deptId);
+
+ /**
+ * 鎵归噺淇濆瓨钖叕鏍哥畻璁板綍
+ *
+ * @param calculationList 钖叕鏍哥畻鍒楄〃
+ */
+ void saveSalaryCalculationList(List<HrmSalaryCalculationDO> calculationList);
+
+}
\ 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
new file mode 100644
index 0000000..ffea59e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -0,0 +1,580 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+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.salary.vo.HrmSalaryCalculationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
+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.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.HrmSocialSecuritySchemeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+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.HrmSocialSecuritySchemeMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+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.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+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.enums.ErrorCodeConstants.*;
+
+/**
+ * 閰牳绠� Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmSalaryCalculationServiceImpl implements HrmSalaryCalculationService {
+
+ /**
+ * 涓◣璧峰緛鐐癸紙姣忔湀5000鍏冿級
+ */
+ private static final BigDecimal TAX_THRESHOLD = new BigDecimal("5000");
+
+ /**
+ * 榛樿缂寸撼姣斾緥锛堝綋鍛樺伐绀句繚妗f涓病鏈夐厤缃垨鏂规涓病鏈夐厤缃椂浣跨敤锛�
+ */
+ private static final BigDecimal DEFAULT_PENSION_PERSONAL_RATIO = new BigDecimal("0.08");
+ private static final BigDecimal DEFAULT_MEDICAL_PERSONAL_RATIO = new BigDecimal("0.02");
+ 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");
+
+ @Resource
+ private HrmSalaryCalculationMapper salaryCalculationMapper;
+ @Resource
+ private HrmEmployeeSalaryMapper employeeSalaryMapper;
+ @Resource
+ private HrmEmployeeMapper employeeMapper;
+ @Resource
+ private HrmEmployeeSocialSecurityMapper employeeSocialSecurityMapper;
+ @Resource
+ private HrmSocialSecuritySchemeMapper socialSecuritySchemeMapper;
+ @Resource
+ private HrmNoRedisDAO noRedisDAO;
+ @Resource
+ private DeptApi deptApi;
+ @Resource
+ private HrmDeductionService deductionService;
+ @Resource
+ private HrmLeaveApplicationMapper leaveApplicationMapper;
+ @Resource
+ private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
+ @Resource
+ private HrmSalaryPaymentMapper salaryPaymentMapper;
+ @Resource
+ private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+
+ @Override
+ public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
+ return salaryCalculationMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public HrmSalaryCalculationDO getSalaryCalculation(Long id) {
+ return salaryCalculationMapper.selectById(id);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void calculateSalary(String period) {
+ calculateSalary(period, null);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void calculateSalary(String period, Long deptId) {
+ // 瑙f瀽鏍哥畻鍛ㄦ湡鐨勬棩鏈�
+ LocalDate effectiveDate = parsePeriodToDate(period);
+
+ // 1. 鑾峰彇鎵�鏈夊湪鑱屽憳宸ワ紙鍛樺伐鐘舵�侊細1-鍦ㄨ亴锛�2-璇曠敤锛�
+ List<HrmEmployeeDO> employees = employeeMapper.selectListByStatus(null);
+ // 杩囨护鍦ㄨ亴鍜岃瘯鐢ㄦ湡鍛樺伐
+ employees = employees.stream()
+ .filter(e -> e.getEmployeeStatus() != null
+ && (e.getEmployeeStatus() == 1 || e.getEmployeeStatus() == 2))
+ .filter(e -> deptId == null || deptId.equals(e.getDeptId()))
+ .toList();
+
+ // 2. 閬嶅巻鍛樺伐杩涜钖叕鏍哥畻
+ for (HrmEmployeeDO employee : employees) {
+ // employeeId 鏄憳宸D锛岀敤浜庡叧鑱旇柂閰。妗堛�佺ぞ淇濇。妗堛�佹牳绠楄褰�
+ Long employeeId = employee.getId();
+ // 2.1 妫�鏌ヨ鍛樺伐褰撴湀鏄惁宸插瓨鍦ㄦ牳绠楄褰曪紙鎸夊憳宸D鏌ヨ锛�
+ List<HrmSalaryCalculationDO> existCalculations = salaryCalculationMapper.selectListByUserIdAndPeriod(employeeId, period);
+ if (CollUtil.isNotEmpty(existCalculations)) {
+ // 濡傛灉瀛樺湪澶氭潯璁板綍锛屾彁绀哄紓甯告暟鎹�
+ if (existCalculations.size() > 1) {
+ throw exception(SALARY_CALCULATION_DUPLICATE, employee.getName(), period);
+ }
+ continue; // 宸插瓨鍦ㄥ垯璺宠繃
+ }
+
+ // 2.2 鑾峰彇鍛樺伐钖叕妗f锛堟寜鍛樺伐ID鏌ヨ锛�
+ HrmEmployeeSalaryDO employeeSalary = employeeSalaryMapper.selectLatestByUserId(employeeId);
+ if (employeeSalary == null) {
+ // 濡傛灉娌℃湁钖叕妗f锛屼娇鐢ㄥ憳宸ヨ〃涓殑鍩烘湰宸ヨ祫鍒涘缓榛樿鏁版嵁
+ employeeSalary = buildDefaultEmployeeSalary(employeeId, employee);
+ }
+
+ // 2.3 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堬紙鎸夊憳宸D鏌ヨ锛屽彇鏈�鏂扮敓鏁堢殑锛�
+ HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
+ // 2.4 濡傛灉鏈夌ぞ淇濇。妗堬紝鑾峰彇缁戝畾鐨勬柟妗�
+ HrmSocialSecuritySchemeDO scheme = null;
+ if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
+ scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
+ }
+
+ // 2.5 鏋勫缓鏍哥畻璁板綍锛堝寘鍚墸闄よ绠楋紝閫氳繃鏂规鑾峰彇姣斾緥锛�
+ HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
+
+ // 2.6 鐢熸垚鏍哥畻鍗曞彿
+ String no = noRedisDAO.generateSalaryCalculationNo();
+ while (salaryCalculationMapper.selectByNo(no) != null) {
+ no = noRedisDAO.generateSalaryCalculationNo();
+ }
+ calculation.setNo(no);
+
+ // 2.7 淇濆瓨鏍哥畻璁板綍
+ salaryCalculationMapper.insert(calculation);
+ }
+ }
+
+ /**
+ * 瑙f瀽鏍哥畻鍛ㄦ湡涓烘棩鏈燂紙濡� 2024-01 -> 2024-01-01锛�
+ */
+ private LocalDate parsePeriodToDate(String period) {
+ String[] parts = period.split("-");
+ int year = Integer.parseInt(parts[0]);
+ int month = Integer.parseInt(parts[1]);
+ return LocalDate.of(year, month, 1);
+ }
+
+ /**
+ * 鏋勫缓榛樿鐨勫憳宸ヨ柂閰。妗堬紙鍩轰簬鍛樺伐琛ㄧ殑鍩烘湰宸ヨ祫锛�
+ */
+ private HrmEmployeeSalaryDO buildDefaultEmployeeSalary(Long employeeId, HrmEmployeeDO employee) {
+ return HrmEmployeeSalaryDO.builder()
+ .userId(employeeId) // 浣跨敤鍛樺伐ID
+ .salaryStructureId(employee.getSalaryStructureId())
+ .baseSalary(employee.getBaseSalary() != null ? employee.getBaseSalary() : BigDecimal.ZERO)
+ .performanceSalary(BigDecimal.ZERO)
+ .mealAllowance(BigDecimal.ZERO)
+ .transportAllowance(BigDecimal.ZERO)
+ .otherAllowance(BigDecimal.ZERO)
+ .status(0)
+ .build();
+ }
+
+ /**
+ * 鏋勫缓钖叕鏍哥畻璁板綍锛堝寘鍚ぞ淇濄�佸叕绉噾銆佷釜绋庛�佽鍋囨墸闄よ绠楋級
+ * 閫氳繃鍛樺伐绀句繚妗f缁戝畾鐨勬柟妗堣幏鍙栫即绾虫瘮渚�
+ */
+ private HrmSalaryCalculationDO buildSalaryCalculation(String period, Long employeeId, HrmEmployeeDO employee,
+ HrmEmployeeSalaryDO employeeSalary,
+ HrmEmployeeSocialSecurityDO socialSecurity,
+ HrmSocialSecuritySchemeDO scheme,
+ LocalDate effectiveDate) {
+ // 璁$畻搴斿彂钖祫
+ BigDecimal baseSalary = employeeSalary.getBaseSalary() != null ? employeeSalary.getBaseSalary() : BigDecimal.ZERO;
+ BigDecimal performanceSalary = employeeSalary.getPerformanceSalary() != null ? employeeSalary.getPerformanceSalary() : BigDecimal.ZERO;
+ 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;
+ BigDecimal overtimePay = BigDecimal.ZERO;
+
+ // 搴斿彂宸ヨ祫鎬婚
+ BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
+ .add(transportAllowance).add(otherAllowance).add(overtimePay);
+
+ // 鑾峰彇绀句繚鍩烘暟锛堝憳宸ユ。妗堜腑鐨勪釜鎬у寲鍩烘暟锛�
+ BigDecimal socialSecurityBase = (socialSecurity != null && socialSecurity.getSocialSecurityBase() != null)
+ ? socialSecurity.getSocialSecurityBase() : baseSalary;
+
+ // 鑾峰彇鍏Н閲戝熀鏁帮紙鍛樺伐妗f涓殑涓�у寲鍩烘暟锛�
+ BigDecimal housingFundBase = (socialSecurity != null && socialSecurity.getHousingFundBase() != null)
+ ? socialSecurity.getHousingFundBase() : baseSalary;
+
+ // 浠庢柟妗堜腑鑾峰彇缂寸撼姣斾緥锛堜紭鍏堜娇鐢ㄦ柟妗堜腑鐨勬瘮渚嬶紝鍚﹀垯浣跨敤榛樿姣斾緥锛�
+ BigDecimal pensionPersonalRatio = (scheme != null && scheme.getPensionPersonalRatio() != null)
+ ? scheme.getPensionPersonalRatio() : DEFAULT_PENSION_PERSONAL_RATIO;
+ BigDecimal medicalPersonalRatio = (scheme != null && scheme.getMedicalPersonalRatio() != null)
+ ? scheme.getMedicalPersonalRatio() : DEFAULT_MEDICAL_PERSONAL_RATIO;
+ BigDecimal unemploymentPersonalRatio = (scheme != null && scheme.getUnemploymentPersonalRatio() != null)
+ ? scheme.getUnemploymentPersonalRatio() : DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO;
+ BigDecimal housingFundPersonalRatio = (scheme != null && scheme.getHousingFundPersonalRatio() != null)
+ ? scheme.getHousingFundPersonalRatio() : DEFAULT_HOUSING_FUND_PERSONAL_RATIO;
+
+ // 绀句繚鎵i櫎 = 鍩烘暟 脳 (鍏昏�佷釜浜烘瘮渚� + 鍖荤枟涓汉姣斾緥 + 澶变笟涓汉姣斾緥)
+ BigDecimal socialSecurityDeduction = socialSecurityBase
+ .multiply(pensionPersonalRatio)
+ .add(socialSecurityBase.multiply(medicalPersonalRatio))
+ .add(socialSecurityBase.multiply(unemploymentPersonalRatio))
+ .setScale(2, RoundingMode.HALF_UP);
+
+ // 鍏Н閲戞墸闄� = 鍩烘暟 脳 鍏Н閲戜釜浜烘瘮渚�
+ BigDecimal housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio).setScale(2, RoundingMode.HALF_UP);
+
+ // 璁$畻璇峰亣鎵f
+ BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary);
+
+ // 璁$畻涓◣鎵i櫎
+ // 搴旂撼绋庢墍寰楅 = 搴斿彂宸ヨ祫 - 绀句繚鎵i櫎 - 鍏Н閲戞墸闄� - 璧峰緛鐐�(5000)
+ BigDecimal taxableIncome = totalIncome
+ .subtract(socialSecurityDeduction)
+ .subtract(housingFundDeduction)
+ .subtract(TAX_THRESHOLD);
+ // 濡傛灉搴旂撼绋庢墍寰楅灏忎簬0锛屽垯涓嶇即绾充釜绋�
+ if (taxableIncome.compareTo(BigDecimal.ZERO) < 0) {
+ taxableIncome = BigDecimal.ZERO;
+ }
+ BigDecimal taxDeduction = deductionService.calculateTaxDeduction(taxableIncome, effectiveDate);
+
+ BigDecimal otherDeduction = BigDecimal.ZERO;
+
+ // 璁$畻瀹炲彂宸ヨ祫 = 搴斿彂 - 鎵i櫎
+ BigDecimal totalDeduction = socialSecurityDeduction.add(housingFundDeduction)
+ .add(taxDeduction).add(otherDeduction).add(leaveDeduction);
+ BigDecimal actualSalary = totalIncome.subtract(totalDeduction);
+
+ return HrmSalaryCalculationDO.builder()
+ .period(period)
+ .userId(employeeId) // 浣跨敤鍛樺伐ID
+ .deptId(employee.getDeptId())
+ .baseSalary(baseSalary)
+ .performanceSalary(performanceSalary)
+ .overtimePay(overtimePay)
+ .mealAllowance(mealAllowance)
+ .transportAllowance(transportAllowance)
+ .otherAllowance(otherAllowance)
+ .socialSecurityDeduction(socialSecurityDeduction)
+ .housingFundDeduction(housingFundDeduction)
+ .taxDeduction(taxDeduction)
+ .otherDeduction(otherDeduction)
+ .leaveDeduction(leaveDeduction)
+ .actualSalary(actualSalary)
+ .workDays(22) // 榛樿宸ヤ綔鏃�
+ .overtimeHours(BigDecimal.ZERO)
+ .status(0) // 寰呯‘璁�
+ .build();
+ }
+
+ /**
+ * 璁$畻璇峰亣鎵f
+ *
+ * @param employee 鍛樺伐淇℃伅
+ * @param period 鏍哥畻鍛ㄦ湡锛堝2026-02锛�
+ * @param baseSalary 鍩烘湰宸ヨ祫
+ * @return 璇峰亣鎵f閲戦
+ */
+ private BigDecimal calculateLeaveDeduction(HrmEmployeeDO employee, String period, BigDecimal baseSalary) {
+ // 瑙f瀽鍛ㄦ湡鑾峰彇鏈堜唤寮�濮嬪拰缁撴潫鏃堕棿
+ YearMonth yearMonth = YearMonth.parse(period);
+ LocalDateTime monthStart = yearMonth.atDay(1).atStartOfDay();
+ LocalDateTime monthEnd = yearMonth.atEndOfMonth().atTime(23, 59, 59);
+
+ // 鏌ヨ鍛樺伐鍦ㄨ鏈堝凡瀹℃壒閫氳繃鐨勮鍋囪褰�
+ // 娉ㄦ剰锛氳鍋囩敵璇风殑 userId 鏄郴缁熺敤鎴稩D锛岄渶瑕侀�氳繃鍛樺伐琛ㄨ幏鍙�
+ Long systemUserId = employee.getUserId();
+ if (systemUserId == null) {
+ return BigDecimal.ZERO;
+ }
+ List<HrmLeaveApplicationDO> leaveApplications = leaveApplicationMapper
+ .selectApprovedListByUserIdAndTimeRange(systemUserId, monthStart, monthEnd);
+
+ if (leaveApplications.isEmpty()) {
+ return BigDecimal.ZERO;
+ }
+
+ // 鑾峰彇鎵�鏈夎鍋囩被鍨嬮厤缃�
+ List<HrmLeaveTypeConfigDO> configList = leaveTypeConfigMapper.selectList();
+ Map<Integer, BigDecimal> configMap = configList.stream()
+ .filter(c -> c.getStatus() == 0) // 鍙彇鍚敤鐨勯厤缃�
+ .collect(Collectors.toMap(HrmLeaveTypeConfigDO::getLeaveType, HrmLeaveTypeConfigDO::getDeductionRatio));
+
+ // 璁$畻鏃ュ伐璧勶紙鍩烘湰宸ヨ祫 / 21.75锛屾寜鍥藉瑙勫畾鐨勫伐浣滄棩璁$畻锛�
+ BigDecimal dailySalary = baseSalary.divide(new BigDecimal("21.75"), 2, RoundingMode.HALF_UP);
+
+ // 绱璇峰亣鎵f
+ BigDecimal totalLeaveDeduction = BigDecimal.ZERO;
+ for (HrmLeaveApplicationDO application : leaveApplications) {
+ Integer leaveType = application.getLeaveType();
+ BigDecimal duration = application.getDuration() != null ? application.getDuration() : BigDecimal.ZERO;
+ BigDecimal deductionRatio = configMap.getOrDefault(leaveType, BigDecimal.ZERO);
+
+ // 璇峰亣鎵f = 鏃ュ伐璧� 脳 璇峰亣澶╂暟 脳 鎵f姣斾緥
+ BigDecimal deduction = dailySalary.multiply(duration).multiply(deductionRatio)
+ .setScale(2, RoundingMode.HALF_UP);
+ totalLeaveDeduction = totalLeaveDeduction.add(deduction);
+ }
+
+ return totalLeaveDeduction;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void confirmSalaryCalculation(List<Long> ids) {
+ if (ids == null || ids.isEmpty()) {
+ return;
+ }
+ LocalDateTime confirmTime = LocalDateTime.now();
+ String period = null;
+ for (Long id : ids) {
+ HrmSalaryCalculationDO salaryCalculation = salaryCalculationMapper.selectById(id);
+ if (salaryCalculation == null) {
+ throw exception(SALARY_CALCULATION_NOT_EXISTS);
+ }
+ if (salaryCalculation.getStatus() != 0) {
+ throw exception(SALARY_CALCULATION_CONFIRM_FAIL);
+ }
+ salaryCalculation.setStatus(10); // 宸茬‘璁�
+ salaryCalculation.setConfirmTime(confirmTime);
+ salaryCalculationMapper.updateById(salaryCalculation);
+ // 璁板綍鍛ㄦ湡
+ period = salaryCalculation.getPeriod();
+ }
+
+ // 妫�鏌ヨ鍛ㄦ湡鏄惁鎵�鏈夋牳绠楄褰曢兘宸茬‘璁わ紝鑷姩鐢熸垚鍙戞斁鍙拌处
+ if (period != null) {
+ autoGeneratePaymentIfAllConfirmed(period);
+ }
+ }
+
+ /**
+ * 妫�鏌ヨ鍛ㄦ湡鏄惁鎵�鏈夋牳绠楄褰曢兘宸茬‘璁わ紝濡傛灉鍏ㄩ儴纭鍒欒嚜鍔ㄧ敓鎴愬彂鏀惧彴璐�
+ */
+ private void autoGeneratePaymentIfAllConfirmed(String period) {
+ // 鏌ヨ璇ュ懆鏈熸墍鏈夋牳绠楄褰�
+ List<HrmSalaryCalculationDO> allCalculations = salaryCalculationMapper.selectListByPeriod(period);
+ if (CollUtil.isEmpty(allCalculations)) {
+ return;
+ }
+ // 妫�鏌ユ槸鍚﹀叏閮ㄥ凡纭锛堢姸鎬�=10锛�
+ boolean allConfirmed = allCalculations.stream()
+ .allMatch(c -> c.getStatus() == 10);
+ if (!allConfirmed) {
+ return;
+ }
+ // 妫�鏌ユ槸鍚﹀凡瀛樺湪鍙戞斁鍙拌处
+ HrmSalaryPaymentDO existPayment = salaryPaymentMapper.selectByPeriod(period);
+ if (existPayment != null) {
+ return;
+ }
+ // 鑷姩鐢熸垚鍙戞斁鍙拌处
+ generatePaymentInternal(period, allCalculations);
+ }
+
+ private HrmSalaryCalculationDO validateSalaryCalculationExists(Long id) {
+ HrmSalaryCalculationDO salaryCalculation = salaryCalculationMapper.selectById(id);
+ if (salaryCalculation == null) {
+ throw exception(SALARY_CALCULATION_NOT_EXISTS);
+ }
+ return salaryCalculation;
+ }
+
+ @Override
+ public List<HrmSalaryCalculationDO> getSalaryCalculationListByPeriod(String period) {
+ return salaryCalculationMapper.selectListByPeriod(period);
+ }
+
+ @Override
+ public List<HrmSalaryCalculationDO> previewSalaryCalculation(String period, Long deptId) {
+ // 瑙f瀽鏍哥畻鍛ㄦ湡鐨勬棩鏈�
+ LocalDate effectiveDate = parsePeriodToDate(period);
+
+ // 1. 鑾峰彇鎵�鏈夊湪鑱屽憳宸ワ紙鍛樺伐鐘舵�侊細1-鍦ㄨ亴锛�2-璇曠敤锛�
+ List<HrmEmployeeDO> employees = employeeMapper.selectListByStatus(null);
+ // 杩囨护鍦ㄨ亴鍜岃瘯鐢ㄦ湡鍛樺伐
+ employees = employees.stream()
+ .filter(e -> e.getEmployeeStatus() != null
+ && (e.getEmployeeStatus() == 1 || e.getEmployeeStatus() == 2))
+ .filter(e -> deptId == null || deptId.equals(e.getDeptId()))
+ .toList();
+
+ // 2. 閬嶅巻鍛樺伐瀹炴椂璁$畻钖叕
+ List<HrmSalaryCalculationDO> resultList = new java.util.ArrayList<>();
+ for (HrmEmployeeDO employee : employees) {
+ Long employeeId = employee.getId();
+
+ // 2.1 鑾峰彇鍛樺伐钖叕妗f锛堟寜鍛樺伐ID鏌ヨ锛�
+ HrmEmployeeSalaryDO employeeSalary = employeeSalaryMapper.selectLatestByUserId(employeeId);
+ if (employeeSalary == null) {
+ // 濡傛灉娌℃湁钖叕妗f锛屼娇鐢ㄥ憳宸ヨ〃涓殑鍩烘湰宸ヨ祫鍒涘缓榛樿鏁版嵁
+ employeeSalary = buildDefaultEmployeeSalary(employeeId, employee);
+ }
+
+ // 2.2 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堬紙鎸夊憳宸D鏌ヨ锛屽彇鏈�鏂扮敓鏁堢殑锛�
+ HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
+ // 2.3 濡傛灉鏈夌ぞ淇濇。妗堬紝鑾峰彇缁戝畾鐨勬柟妗�
+ HrmSocialSecuritySchemeDO scheme = null;
+ if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
+ scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
+ }
+
+ // 2.4 鏋勫缓鏍哥畻璁板綍锛堝疄鏃惰绠楋紝涓嶅瓨鍌級
+ HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
+ resultList.add(calculation);
+ }
+ return resultList;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void saveSalaryCalculationList(List<HrmSalaryCalculationDO> calculationList) {
+ for (HrmSalaryCalculationDO calculation : calculationList) {
+ if (calculation.getId() == null) {
+ // 鏂板璁板綍锛岀敓鎴愭牳绠楀崟鍙�
+ String no = noRedisDAO.generateSalaryCalculationNo();
+ while (salaryCalculationMapper.selectByNo(no) != null) {
+ no = noRedisDAO.generateSalaryCalculationNo();
+ }
+ calculation.setNo(no);
+ calculation.setStatus(0); // 寰呯‘璁�
+ salaryCalculationMapper.insert(calculation);
+ } else {
+ // 鏇存柊宸叉湁璁板綍
+ salaryCalculationMapper.updateById(calculation);
+ }
+ }
+ }
+
+ /**
+ * 鍐呴儴鐢熸垚鍙戞斁鍙拌处鏂规硶锛堜緵鑷姩鐢熸垚璋冪敤锛�
+ */
+ private void generatePaymentInternal(String period, List<HrmSalaryCalculationDO> calculationList) {
+ // 鐢熸垚鍙戞斁鍙拌处
+ String no = generatePaymentNo();
+ BigDecimal totalAmount = calculationList.stream()
+ .map(c -> c.getActualSalary() != null ? c.getActualSalary() : BigDecimal.ZERO)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ Integer totalCount = calculationList.size();
+
+ HrmSalaryPaymentDO payment = HrmSalaryPaymentDO.builder()
+ .no(no)
+ .period(period)
+ .totalAmount(totalAmount)
+ .totalCount(totalCount)
+ .status(0) // 寰呭彂鏀�
+ .build();
+ salaryPaymentMapper.insert(payment);
+
+ // 鐢熸垚鍙戞斁鏄庣粏锛堜笉鏇存柊鏍哥畻鐘舵�侊級
+ for (HrmSalaryCalculationDO calculation : calculationList) {
+ HrmSalaryPaymentDetailDO detail = HrmSalaryPaymentDetailDO.builder()
+ .paymentId(payment.getId())
+ .calculationId(calculation.getId())
+ .userId(calculation.getUserId())
+ .deptId(calculation.getDeptId())
+ .actualSalary(calculation.getActualSalary())
+ .status(0) // 寰呭彂鏀�
+ .build();
+ salaryPaymentDetailMapper.insert(detail);
+ }
+ }
+
+ private String generatePaymentNo() {
+ String no = noRedisDAO.generateSalaryPaymentNo();
+ while (salaryPaymentMapper.selectByNo(no) != null) {
+ no = noRedisDAO.generateSalaryPaymentNo();
+ }
+ return no;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void revokeSalaryCalculation(List<Long> ids) {
+ if (ids == null || ids.isEmpty()) {
+ return;
+ }
+ for (Long id : ids) {
+ HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(id);
+ if (calculation == null) {
+ throw exception(SALARY_CALCULATION_NOT_EXISTS);
+ }
+ // 鏍¢獙鐘舵�佸繀椤绘槸宸茬‘璁�(10)
+ if (calculation.getStatus() != 10) {
+ throw exception(SALARY_CALCULATION_REVOKE_FAIL);
+ }
+ // 妫�鏌ユ槸鍚﹀凡鍙戞斁锛堢姸鎬�=20锛�
+ if (calculation.getStatus() == 20) {
+ throw exception(SALARY_CALCULATION_ALREADY_PAY);
+ }
+ // 鍒犻櫎鍏宠仈鐨勫彂鏀炬槑缁�
+ HrmSalaryPaymentDetailDO detail = salaryPaymentDetailMapper.selectByCalculationId(id);
+ if (detail != null) {
+ // 濡傛灉鏄庣粏宸插彂鏀撅紝涓嶅厑璁告挙閿�
+ if (detail.getStatus() == 10) {
+ throw exception(SALARY_CALCULATION_ALREADY_PAY);
+ }
+ salaryPaymentDetailMapper.deleteById(detail.getId());
+ // 鏇存柊鍙戞斁鍙拌处鐨勬�婚噾棰濆拰鎬讳汉鏁�
+ updatePaymentStatistics(detail.getPaymentId());
+ }
+ // 鎭㈠鏍哥畻鐘舵�佷负寰呯‘璁�
+ calculation.setStatus(0);
+ calculation.setConfirmTime(null);
+ salaryCalculationMapper.updateById(calculation);
+ }
+ }
+
+ /**
+ * 鏇存柊鍙戞斁鍙拌处鐨勭粺璁′俊鎭�
+ */
+ private void updatePaymentStatistics(Long paymentId) {
+ HrmSalaryPaymentDO payment = salaryPaymentMapper.selectById(paymentId);
+ if (payment == null) {
+ return;
+ }
+ // 閲嶆柊璁$畻鎬婚噾棰濆拰鎬讳汉鏁�
+ List<HrmSalaryPaymentDetailDO> details = salaryPaymentDetailMapper.selectListByPaymentId(paymentId);
+ if (CollUtil.isEmpty(details)) {
+ // 濡傛灉娌℃湁鏄庣粏浜嗭紝鍒犻櫎鍙戞斁鍙拌处
+ salaryPaymentMapper.deleteById(paymentId);
+ return;
+ }
+ BigDecimal totalAmount = details.stream()
+ .map(d -> d.getActualSalary() != null ? d.getActualSalary() : BigDecimal.ZERO)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ payment.setTotalAmount(totalAmount);
+ payment.setTotalCount(details.size());
+ salaryPaymentMapper.updateById(payment);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteSalaryCalculation(Long id) {
+ HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(id);
+ if (calculation == null) {
+ throw exception(SALARY_CALCULATION_NOT_EXISTS);
+ }
+ // 浠呭緟纭鐘舵�佸彲鍒犻櫎
+ if (calculation.getStatus() != 0) {
+ throw exception(SALARY_CALCULATION_DELETE_FAIL);
+ }
+ salaryCalculationMapper.deleteById(id);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentService.java
new file mode 100644
index 0000000..9732b9f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentService.java
@@ -0,0 +1,76 @@
+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.HrmSalaryPaymentDetailPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryPaymentPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDetailDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDO;
+
+import java.util.List;
+
+/**
+ * 钖祫鍙戞斁 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmSalaryPaymentService {
+
+ /**
+ * 鑾峰緱钖祫鍙戞斁鍒嗛〉
+ */
+ PageResult<HrmSalaryPaymentDO> getSalaryPaymentPage(HrmSalaryPaymentPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱钖祫鍙戞斁
+ */
+ HrmSalaryPaymentDO getSalaryPayment(Long id);
+
+ /**
+ * 鐢熸垚钖祫鍙戞斁鍙拌处
+ * 灏嗗凡纭鐨勮柂閰牳绠楄褰曡浆鍏ュ彂鏀惧彴璐�
+ *
+ * @param period 鏍哥畻鍛ㄦ湡
+ * @return 鍙戞斁鍙拌处ID
+ */
+ Long generatePayment(String period);
+
+ /**
+ * 鍙戞斁钖祫
+ *
+ * @param id 鍙戞斁鍙拌处ID
+ */
+ void paySalary(Long id);
+
+ /**
+ * 鎵归噺鍙戞斁钖祫锛堟寚瀹氭槑缁嗭級
+ *
+ * @param detailIds 鍙戞斁鏄庣粏ID鍒楄〃
+ */
+ void paySalaryDetail(List<Long> detailIds);
+
+ /**
+ * 鎾ら攢钖祫鍙戞斁
+ * 灏嗗凡鍙戞斁鐨勬槑缁嗘仮澶嶄负寰呭彂鏀撅紝鍚屾椂灏嗗叧鑱旂殑鏍哥畻璁板綍鎭㈠涓哄凡纭鐘舵��
+ *
+ * @param detailIds 鍙戞斁鏄庣粏ID鍒楄〃
+ */
+ void revokeSalaryPaymentDetail(List<Long> detailIds);
+
+ /**
+ * 鍒犻櫎钖祫鍙戞斁鍙拌处锛堜粎寰呭彂鏀剧姸鎬佸彲鍒犻櫎锛岄渶鍏堟挙閿�鎵�鏈夋槑缁嗭級
+ *
+ * @param id 鍙戞斁鍙拌处ID
+ */
+ void deleteSalaryPayment(Long id);
+
+ /**
+ * 鑾峰緱钖祫鍙戞斁鏄庣粏鍒嗛〉
+ */
+ PageResult<HrmSalaryPaymentDetailDO> getSalaryPaymentDetailPage(HrmSalaryPaymentDetailPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱钖祫鍙戞斁鏄庣粏鍒楄〃锛堟寜鍙戞斁鍙拌处ID锛�
+ */
+ List<HrmSalaryPaymentDetailDO> getSalaryPaymentDetailListByPaymentId(Long paymentId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java
new file mode 100644
index 0000000..264a15c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java
@@ -0,0 +1,287 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryPaymentDetailPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryPaymentPageReqVO;
+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.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.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.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 钖祫鍙戞斁 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmSalaryPaymentServiceImpl implements HrmSalaryPaymentService {
+
+ @Resource
+ private HrmSalaryPaymentMapper salaryPaymentMapper;
+ @Resource
+ private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+ @Resource
+ private HrmSalaryCalculationMapper salaryCalculationMapper;
+ @Resource
+ private HrmNoRedisDAO noRedisDAO;
+
+ @Override
+ public PageResult<HrmSalaryPaymentDO> getSalaryPaymentPage(HrmSalaryPaymentPageReqVO pageReqVO) {
+ return salaryPaymentMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public HrmSalaryPaymentDO getSalaryPayment(Long id) {
+ return salaryPaymentMapper.selectById(id);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long generatePayment(String period) {
+ // 1. 妫�鏌ユ槸鍚﹀凡瀛樺湪璇ュ懆鏈熺殑鍙戞斁鍙拌处
+ HrmSalaryPaymentDO existPayment = salaryPaymentMapper.selectByPeriod(period);
+ if (existPayment != null) {
+ throw exception(SALARY_PAYMENT_EXISTS);
+ }
+
+ // 2. 鏌ヨ璇ュ懆鏈熷凡纭鐨勮柂閰牳绠楄褰曪紙鐘舵��=10锛�
+ List<HrmSalaryCalculationDO> calculationList = salaryCalculationMapper.selectListByPeriodAndStatus(period, 10);
+ if (CollUtil.isEmpty(calculationList)) {
+ throw exception(SALARY_CALCULATION_NO_CONFIRMED);
+ }
+
+ // 3. 鐢熸垚鍙戞斁鍙拌处
+ String no = generatePaymentNo();
+ BigDecimal totalAmount = calculationList.stream()
+ .map(c -> c.getActualSalary() != null ? c.getActualSalary() : BigDecimal.ZERO)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ Integer totalCount = calculationList.size();
+
+ HrmSalaryPaymentDO payment = HrmSalaryPaymentDO.builder()
+ .no(no)
+ .period(period)
+ .totalAmount(totalAmount)
+ .totalCount(totalCount)
+ .status(0) // 寰呭彂鏀�
+ .build();
+ salaryPaymentMapper.insert(payment);
+
+ // 4. 鐢熸垚鍙戞斁鏄庣粏锛堜笉鏇存柊鏍哥畻鐘舵�侊級
+ for (HrmSalaryCalculationDO calculation : calculationList) {
+ HrmSalaryPaymentDetailDO detail = HrmSalaryPaymentDetailDO.builder()
+ .paymentId(payment.getId())
+ .calculationId(calculation.getId())
+ .userId(calculation.getUserId())
+ .deptId(calculation.getDeptId())
+ .actualSalary(calculation.getActualSalary())
+ .status(0) // 寰呭彂鏀�
+ .build();
+ salaryPaymentDetailMapper.insert(detail);
+ }
+
+ return payment.getId();
+ }
+
+ private String generatePaymentNo() {
+ String no = noRedisDAO.generateSalaryPaymentNo();
+ while (salaryPaymentMapper.selectByNo(no) != null) {
+ no = noRedisDAO.generateSalaryPaymentNo();
+ }
+ return no;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void paySalary(Long id) {
+ // 1. 鏍¢獙鍙戞斁鍙拌处瀛樺湪
+ HrmSalaryPaymentDO payment = validateSalaryPaymentExists(id);
+ if (payment.getStatus() != 0) {
+ throw exception(SALARY_PAYMENT_ALREADY_PAY);
+ }
+
+ // 2. 鏌ヨ寰呭彂鏀剧殑鏄庣粏
+ List<HrmSalaryPaymentDetailDO> detailList = salaryPaymentDetailMapper.selectListByPaymentIdAndStatus(id, 0);
+ if (CollUtil.isEmpty(detailList)) {
+ throw exception(SALARY_PAYMENT_DETAIL_NO_EXISTS);
+ }
+
+ // 3. 鎵归噺鍙戞斁鏄庣粏锛屽苟鏇存柊鏍哥畻鐘舵��
+ LocalDateTime paymentTime = LocalDateTime.now();
+ for (HrmSalaryPaymentDetailDO detail : detailList) {
+ detail.setStatus(10); // 宸插彂鏀�
+ detail.setPaymentTime(paymentTime);
+ salaryPaymentDetailMapper.updateById(detail);
+
+ // 鏇存柊钖叕鏍哥畻鐘舵�佷负宸插彂鏀撅紙20锛�
+ HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(detail.getCalculationId());
+ if (calculation != null) {
+ calculation.setStatus(20);
+ calculation.setPaymentTime(paymentTime);
+ salaryCalculationMapper.updateById(calculation);
+ }
+ }
+
+ // 4. 鏇存柊鍙戞斁鍙拌处鐘舵��
+ payment.setStatus(10); // 宸插彂鏀�
+ payment.setPaymentTime(paymentTime);
+ salaryPaymentMapper.updateById(payment);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void paySalaryDetail(List<Long> detailIds) {
+ if (CollUtil.isEmpty(detailIds)) {
+ return;
+ }
+ LocalDateTime paymentTime = LocalDateTime.now();
+ Long paymentId = null;
+ for (Long detailId : detailIds) {
+ HrmSalaryPaymentDetailDO detail = salaryPaymentDetailMapper.selectById(detailId);
+ if (detail == null) {
+ throw exception(SALARY_PAYMENT_DETAIL_NOT_EXISTS);
+ }
+ if (detail.getStatus() != 0) {
+ throw exception(SALARY_PAYMENT_DETAIL_ALREADY_PAY);
+ }
+ detail.setStatus(10); // 宸插彂鏀�
+ detail.setPaymentTime(paymentTime);
+ salaryPaymentDetailMapper.updateById(detail);
+ paymentId = detail.getPaymentId();
+
+ // 鏇存柊钖叕鏍哥畻鐘舵�佷负宸插彂鏀撅紙20锛�
+ HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(detail.getCalculationId());
+ if (calculation != null) {
+ calculation.setStatus(20);
+ calculation.setPaymentTime(paymentTime);
+ salaryCalculationMapper.updateById(calculation);
+ }
+ }
+
+ // 妫�鏌ヨ鍙拌处涓嬫槸鍚︽墍鏈夋槑缁嗛兘宸插彂鏀撅紝濡傛灉鏄垯鏇存柊鍙拌处鐘舵��
+ if (paymentId != null) {
+ updatePaymentStatusIfAllPaid(paymentId);
+ }
+ }
+
+ /**
+ * 妫�鏌ヨ鍙拌处涓嬫槸鍚︽墍鏈夋槑缁嗛兘宸插彂鏀撅紝濡傛灉鏄垯鏇存柊鍙拌处鐘舵�佷负宸插彂鏀�
+ */
+ private void updatePaymentStatusIfAllPaid(Long paymentId) {
+ // 鏌ヨ璇ュ彴璐︿笅鎵�鏈夋槑缁�
+ List<HrmSalaryPaymentDetailDO> allDetails = salaryPaymentDetailMapper.selectListByPaymentId(paymentId);
+ if (CollUtil.isEmpty(allDetails)) {
+ return;
+ }
+ // 妫�鏌ユ槸鍚﹀叏閮ㄥ凡鍙戞斁
+ boolean allPaid = allDetails.stream().allMatch(d -> d.getStatus() == 10);
+ if (!allPaid) {
+ return;
+ }
+ // 鏇存柊鍙拌处鐘舵�佷负宸插彂鏀�
+ HrmSalaryPaymentDO payment = salaryPaymentMapper.selectById(paymentId);
+ if (payment != null && payment.getStatus() == 0) {
+ payment.setStatus(10);
+ payment.setPaymentTime(LocalDateTime.now());
+ salaryPaymentMapper.updateById(payment);
+ }
+ }
+
+ private HrmSalaryPaymentDO validateSalaryPaymentExists(Long id) {
+ HrmSalaryPaymentDO payment = salaryPaymentMapper.selectById(id);
+ if (payment == null) {
+ throw exception(SALARY_PAYMENT_NOT_EXISTS);
+ }
+ return payment;
+ }
+
+ @Override
+ public PageResult<HrmSalaryPaymentDetailDO> getSalaryPaymentDetailPage(HrmSalaryPaymentDetailPageReqVO pageReqVO) {
+ return salaryPaymentDetailMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<HrmSalaryPaymentDetailDO> getSalaryPaymentDetailListByPaymentId(Long paymentId) {
+ return salaryPaymentDetailMapper.selectListByPaymentId(paymentId);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void revokeSalaryPaymentDetail(List<Long> detailIds) {
+ if (CollUtil.isEmpty(detailIds)) {
+ return;
+ }
+ Long paymentId = null;
+ for (Long detailId : detailIds) {
+ HrmSalaryPaymentDetailDO detail = salaryPaymentDetailMapper.selectById(detailId);
+ if (detail == null) {
+ throw exception(SALARY_PAYMENT_DETAIL_NOT_EXISTS);
+ }
+ // 鏍¢獙鐘舵�佸繀椤绘槸宸插彂鏀�(10)
+ if (detail.getStatus() != 10) {
+ throw exception(SALARY_PAYMENT_REVOKE_FAIL);
+ }
+ // 鎭㈠鏄庣粏鐘舵�佷负寰呭彂鏀�
+ detail.setStatus(0);
+ detail.setPaymentTime(null);
+ salaryPaymentDetailMapper.updateById(detail);
+ paymentId = detail.getPaymentId();
+
+ // 鎭㈠钖叕鏍哥畻鐘舵�佷负宸茬‘璁�(10)
+ HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(detail.getCalculationId());
+ if (calculation != null) {
+ calculation.setStatus(10);
+ calculation.setPaymentTime(null);
+ salaryCalculationMapper.updateById(calculation);
+ }
+ }
+
+ // 鏇存柊鍙戞斁鍙拌处鐘舵�佷负寰呭彂鏀撅紙濡傛灉鏈夋槑缁嗚鎾ら攢锛�
+ if (paymentId != null) {
+ HrmSalaryPaymentDO payment = salaryPaymentMapper.selectById(paymentId);
+ if (payment != null && payment.getStatus() == 10) {
+ payment.setStatus(0);
+ payment.setPaymentTime(null);
+ salaryPaymentMapper.updateById(payment);
+ }
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteSalaryPayment(Long id) {
+ HrmSalaryPaymentDO payment = validateSalaryPaymentExists(id);
+ // 浠呭緟鍙戞斁鐘舵�佸彲鍒犻櫎
+ if (payment.getStatus() != 0) {
+ throw exception(SALARY_PAYMENT_DELETE_FAIL);
+ }
+ // 妫�鏌ユ槸鍚﹀瓨鍦ㄥ凡鍙戞斁鏄庣粏
+ List<HrmSalaryPaymentDetailDO> details = salaryPaymentDetailMapper.selectListByPaymentId(id);
+ boolean hasPaidDetail = details.stream().anyMatch(d -> d.getStatus() == 10);
+ if (hasPaidDetail) {
+ throw exception(SALARY_PAYMENT_HAS_PAID_DETAIL);
+ }
+ // 鍒犻櫎鎵�鏈夋槑缁�
+ for (HrmSalaryPaymentDetailDO detail : details) {
+ salaryPaymentDetailMapper.deleteById(detail.getId());
+ }
+ // 鍒犻櫎鍙戞斁鍙拌处
+ salaryPaymentMapper.deleteById(id);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigService.java
new file mode 100644
index 0000000..88cc4c0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigService.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecurityConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmTaxRateConfigDO;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 绀句繚鍏Н閲戦厤缃� Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmSocialSecurityConfigService {
+
+ /**
+ * 鍒涘缓绀句繚鍏Н閲戦厤缃�
+ */
+ Long createSocialSecurityConfig(HrmSocialSecurityConfigDO config);
+
+ /**
+ * 鏇存柊绀句繚鍏Н閲戦厤缃�
+ */
+ void updateSocialSecurityConfig(HrmSocialSecurityConfigDO config);
+
+ /**
+ * 鍒犻櫎绀句繚鍏Н閲戦厤缃�
+ */
+ void deleteSocialSecurityConfig(Long id);
+
+ /**
+ * 鑾峰彇绀句繚鍏Н閲戦厤缃�
+ */
+ HrmSocialSecurityConfigDO getSocialSecurityConfig(Long id);
+
+ /**
+ * 鑾峰彇绀句繚鍏Н閲戦厤缃垪琛�
+ */
+ List<HrmSocialSecurityConfigDO> getSocialSecurityConfigList();
+
+ /**
+ * 鑾峰彇鐢熸晥鐨勭ぞ淇濆叕绉噾閰嶇疆
+ */
+ HrmSocialSecurityConfigDO getEffectiveSocialSecurityConfig(LocalDate effectiveDate);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigServiceImpl.java
new file mode 100644
index 0000000..757b416
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigServiceImpl.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecurityConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecurityConfigMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SOCIAL_SECURITY_CONFIG_NOT_EXISTS;
+
+/**
+ * 绀句繚鍏Н閲戦厤缃� Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmSocialSecurityConfigServiceImpl implements HrmSocialSecurityConfigService {
+
+ @Resource
+ private HrmSocialSecurityConfigMapper socialSecurityConfigMapper;
+
+ @Override
+ public Long createSocialSecurityConfig(HrmSocialSecurityConfigDO config) {
+ socialSecurityConfigMapper.insert(config);
+ return config.getId();
+ }
+
+ @Override
+ public void updateSocialSecurityConfig(HrmSocialSecurityConfigDO config) {
+ validateSocialSecurityConfigExists(config.getId());
+ socialSecurityConfigMapper.updateById(config);
+ }
+
+ @Override
+ public void deleteSocialSecurityConfig(Long id) {
+ validateSocialSecurityConfigExists(id);
+ socialSecurityConfigMapper.deleteById(id);
+ }
+
+ @Override
+ public HrmSocialSecurityConfigDO getSocialSecurityConfig(Long id) {
+ return socialSecurityConfigMapper.selectById(id);
+ }
+
+ @Override
+ public List<HrmSocialSecurityConfigDO> getSocialSecurityConfigList() {
+ return socialSecurityConfigMapper.selectListByStatus(null);
+ }
+
+ @Override
+ public HrmSocialSecurityConfigDO getEffectiveSocialSecurityConfig(LocalDate effectiveDate) {
+ return socialSecurityConfigMapper.selectLatestByEffectiveDate(effectiveDate);
+ }
+
+ private void validateSocialSecurityConfigExists(Long id) {
+ if (socialSecurityConfigMapper.selectById(id) == null) {
+ throw exception(SOCIAL_SECURITY_CONFIG_NOT_EXISTS);
+ }
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java
new file mode 100644
index 0000000..579e364
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
+
+import java.util.List;
+
+/**
+ * 绀句繚鍏Н閲戞柟妗� Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmSocialSecuritySchemeService {
+
+ /**
+ * 鍒涘缓绀句繚鍏Н閲戞柟妗�
+ *
+ * @param scheme 鏂规淇℃伅
+ * @return 鏂规ID
+ */
+ Long createScheme(HrmSocialSecuritySchemeDO scheme);
+
+ /**
+ * 鏇存柊绀句繚鍏Н閲戞柟妗�
+ *
+ * @param scheme 鏂规淇℃伅
+ */
+ void updateScheme(HrmSocialSecuritySchemeDO scheme);
+
+ /**
+ * 鍒犻櫎绀句繚鍏Н閲戞柟妗�
+ *
+ * @param id 鏂规ID
+ */
+ void deleteScheme(Long id);
+
+ /**
+ * 鑾峰彇绀句繚鍏Н閲戞柟妗�
+ *
+ * @param id 鏂规ID
+ * @return 鏂规淇℃伅
+ */
+ HrmSocialSecuritySchemeDO getScheme(Long id);
+
+ /**
+ * 鑾峰彇绀句繚鍏Н閲戞柟妗堝垪琛�
+ *
+ * @param status 鐘舵�侊紙鍙�夛級
+ * @return 鏂规鍒楄〃
+ */
+ List<HrmSocialSecuritySchemeDO> getSchemeList(Integer status);
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java
new file mode 100644
index 0000000..59eef61
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper;
+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.SOCIAL_SECURITY_SCHEME_NOT_EXISTS;
+
+/**
+ * 绀句繚鍏Н閲戞柟妗� Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmSocialSecuritySchemeServiceImpl implements HrmSocialSecuritySchemeService {
+
+ @Resource
+ private HrmSocialSecuritySchemeMapper schemeMapper;
+
+ @Override
+ public Long createScheme(HrmSocialSecuritySchemeDO scheme) {
+ schemeMapper.insert(scheme);
+ return scheme.getId();
+ }
+
+ @Override
+ public void updateScheme(HrmSocialSecuritySchemeDO scheme) {
+ validateSchemeExists(scheme.getId());
+ schemeMapper.updateById(scheme);
+ }
+
+ @Override
+ public void deleteScheme(Long id) {
+ validateSchemeExists(id);
+ schemeMapper.deleteById(id);
+ }
+
+ @Override
+ public HrmSocialSecuritySchemeDO getScheme(Long id) {
+ return schemeMapper.selectById(id);
+ }
+
+ @Override
+ public List<HrmSocialSecuritySchemeDO> getSchemeList(Integer status) {
+ return schemeMapper.selectListByStatus(status);
+ }
+
+ private void validateSchemeExists(Long id) {
+ if (schemeMapper.selectById(id) == null) {
+ throw exception(SOCIAL_SECURITY_SCHEME_NOT_EXISTS);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigService.java
new file mode 100644
index 0000000..55677db
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigService.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmTaxRateConfigDO;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 涓◣绋庣巼閰嶇疆 Service 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface HrmTaxRateConfigService {
+
+ /**
+ * 鎵归噺鍒涘缓涓◣绋庣巼閰嶇疆锛堜竴涓◣鐜囪〃鍖呭惈7涓。浣嶏級
+ */
+ void createTaxRateConfigList(List<HrmTaxRateConfigDO> configList);
+
+ /**
+ * 鏇存柊涓◣绋庣巼閰嶇疆
+ */
+ void updateTaxRateConfig(HrmTaxRateConfigDO config);
+
+ /**
+ * 鍒犻櫎涓◣绋庣巼閰嶇疆
+ */
+ void deleteTaxRateConfig(Long id);
+
+ /**
+ * 鑾峰彇涓◣绋庣巼閰嶇疆
+ */
+ HrmTaxRateConfigDO getTaxRateConfig(Long id);
+
+ /**
+ * 鑾峰彇涓◣绋庣巼閰嶇疆鍒楄〃
+ */
+ List<HrmTaxRateConfigDO> getTaxRateConfigList();
+
+ /**
+ * 鑾峰彇鐢熸晥鐨勪釜绋庣◣鐜囬厤缃垪琛紙鎸夌骇鏁版帓搴忥級
+ */
+ List<HrmTaxRateConfigDO> getEffectiveTaxRateConfigList(LocalDate effectiveDate);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigServiceImpl.java
new file mode 100644
index 0000000..9787e8d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigServiceImpl.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmTaxRateConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmTaxRateConfigMapper;
+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.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.TAX_RATE_CONFIG_NOT_EXISTS;
+
+/**
+ * 涓◣绋庣巼閰嶇疆 Service 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class HrmTaxRateConfigServiceImpl implements HrmTaxRateConfigService {
+
+ @Resource
+ private HrmTaxRateConfigMapper taxRateConfigMapper;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void createTaxRateConfigList(List<HrmTaxRateConfigDO> configList) {
+ for (HrmTaxRateConfigDO config : configList) {
+ taxRateConfigMapper.insert(config);
+ }
+ }
+
+ @Override
+ public void updateTaxRateConfig(HrmTaxRateConfigDO config) {
+ validateTaxRateConfigExists(config.getId());
+ taxRateConfigMapper.updateById(config);
+ }
+
+ @Override
+ public void deleteTaxRateConfig(Long id) {
+ validateTaxRateConfigExists(id);
+ taxRateConfigMapper.deleteById(id);
+ }
+
+ @Override
+ public HrmTaxRateConfigDO getTaxRateConfig(Long id) {
+ return taxRateConfigMapper.selectById(id);
+ }
+
+ @Override
+ public List<HrmTaxRateConfigDO> getTaxRateConfigList() {
+ return taxRateConfigMapper.selectListByStatus(null);
+ }
+
+ @Override
+ public List<HrmTaxRateConfigDO> getEffectiveTaxRateConfigList(LocalDate effectiveDate) {
+ return taxRateConfigMapper.selectListByEffectiveDate(effectiveDate);
+ }
+
+ private void validateTaxRateConfigExists(Long id) {
+ if (taxRateConfigMapper.selectById(id) == null) {
+ throw exception(TAX_RATE_CONFIG_NOT_EXISTS);
+ }
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java
new file mode 100644
index 0000000..022ced2
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.util;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+
+/**
+ * HRM 娴佺▼宸ュ叿绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public class HrmAuditStatusUtils {
+
+ /**
+ * BPM 瀹℃壒缁撴灉杞崲
+ *
+ * @param bpmResult BPM 瀹℃壒缁撴灉
+ */
+ public static Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+ Integer auditStatus = BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult) ? HrmAuditStatusEnum.APPROVE.getStatus()
+ : BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult) ? HrmAuditStatusEnum.REJECT.getStatus()
+ : BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult) ? HrmAuditStatusEnum.CANCEL.getStatus() : null;
+ Assert.notNull(auditStatus, "BPM 瀹℃壒缁撴灉({}) 杞崲澶辫触", bpmResult);
+ return auditStatus;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/pom.xml b/yudao-module-mes/pom.xml
index cb3d2de..ac82f3e 100644
--- a/yudao-module-mes/pom.xml
+++ b/yudao-module-mes/pom.xml
@@ -31,6 +31,27 @@
<version>${revision}</version>
</dependency>
+ <!-- WMS 浠撳偍鐗╂祦 API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-wms-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- BPM 宸ヤ綔娴� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-bpm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- ERP 浼佷笟璧勬簮绠$悊 API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-erp-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- 涓氬姟缁勪欢 -->
<!-- Web 鐩稿叧 -->
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsStockApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsStockApiImpl.java
new file mode 100644
index 0000000..9e1b19a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsStockApiImpl.java
@@ -0,0 +1,238 @@
+package cn.iocoder.yudao.module.mes.api.wms;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
+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.MesWmWarehouseService;
+import cn.iocoder.yudao.module.wms.api.dto.WmsPurchaseInboundReqDTO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsSalesOutboundReqDTO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsStockDetailRespDTO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsStockQueryReqDTO;
+import cn.iocoder.yudao.module.wms.api.stock.WmsStockApi;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+/**
+ * WMS 搴撳瓨 API 瀹炵幇绫�
+ *
+ * 鍦� MES 妯″潡涓疄鐜帮紝鍐呴儴璋冪敤 MesWmTransactionService
+ */
+@Service
+@Validated
+@Slf4j
+public class WmsStockApiImpl implements WmsStockApi {
+
+ @Resource
+ private MesWmTransactionService transactionService;
+
+ @Resource
+ private MesWmMaterialStockService materialStockService;
+
+ @Resource
+ private MesWmMaterialStockMapper materialStockMapper;
+
+ @Resource
+ private MesMdItemService itemService;
+
+ @Resource
+ private MesWmWarehouseService warehouseService;
+
+ @Override
+ public Long purchaseInbound(WmsPurchaseInboundReqDTO reqDTO) {
+ log.info("[purchaseInbound] 閲囪喘鍏ュ簱锛宐izId={}, bizCode={}", reqDTO.getBizId(), reqDTO.getBizCode());
+ List<Long> transactionIds = CollUtil.newArrayList();
+ LocalDateTime receiptTime = reqDTO.getReceiptTime() != null ? reqDTO.getReceiptTime() : LocalDateTime.now();
+
+ for (WmsPurchaseInboundReqDTO.Item item : reqDTO.getItems()) {
+ MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
+ .setType(MesWmTransactionTypeEnum.IN.getType())
+ .setBizType(MesBizTypeConstants.WM_ITEM_RECEIPT_IN)
+ .setBizId(reqDTO.getBizId())
+ .setBizCode(reqDTO.getBizCode())
+ .setBizLineId(item.getBizLineId())
+ .setItemId(item.getItemId())
+ .setWarehouseId(item.getWarehouseId())
+ .setLocationId(item.getLocationId())
+ .setAreaId(item.getAreaId())
+ .setBatchId(item.getBatchId())
+ .setBatchCode(item.getBatchCode())
+ .setVendorId(reqDTO.getVendorId())
+ .setQuantity(item.getQuantity())
+ .setReceiptTime(receiptTime)
+ .setCheckFlag(true);
+ transactionIds.add(transactionService.createTransaction(transaction));
+ }
+ return transactionIds.get(0);
+ }
+
+ @Override
+ public Long purchaseReturnOutbound(WmsPurchaseInboundReqDTO reqDTO) {
+ log.info("[purchaseReturnOutbound] 閲囪喘閫�璐у嚭搴擄紝bizId={}, bizCode={}", reqDTO.getBizId(), reqDTO.getBizCode());
+ List<Long> transactionIds = CollUtil.newArrayList();
+ LocalDateTime receiptTime = reqDTO.getReceiptTime() != null ? reqDTO.getReceiptTime() : LocalDateTime.now();
+
+ for (WmsPurchaseInboundReqDTO.Item item : reqDTO.getItems()) {
+ MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
+ .setType(MesWmTransactionTypeEnum.OUT.getType())
+ .setBizType(MesBizTypeConstants.WM_RETURN_VENDOR)
+ .setBizId(reqDTO.getBizId())
+ .setBizCode(reqDTO.getBizCode())
+ .setBizLineId(item.getBizLineId())
+ .setItemId(item.getItemId())
+ .setWarehouseId(item.getWarehouseId())
+ .setLocationId(item.getLocationId())
+ .setAreaId(item.getAreaId())
+ .setBatchId(item.getBatchId())
+ .setBatchCode(item.getBatchCode())
+ .setVendorId(reqDTO.getVendorId())
+ .setQuantity(item.getQuantity().negate())
+ .setReceiptTime(receiptTime)
+ .setCheckFlag(true);
+ transactionIds.add(transactionService.createTransaction(transaction));
+ }
+ return transactionIds.get(0);
+ }
+
+ @Override
+ public Long salesOutbound(WmsSalesOutboundReqDTO reqDTO) {
+ log.info("[salesOutbound] 閿�鍞嚭搴擄紝bizId={}, bizCode={}", reqDTO.getBizId(), reqDTO.getBizCode());
+ List<Long> transactionIds = CollUtil.newArrayList();
+ LocalDateTime outboundTime = reqDTO.getOutboundTime() != null ? reqDTO.getOutboundTime() : LocalDateTime.now();
+
+ for (WmsSalesOutboundReqDTO.Item item : reqDTO.getItems()) {
+ MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
+ .setType(MesWmTransactionTypeEnum.OUT.getType())
+ .setBizType(MesBizTypeConstants.WM_PRODUCT_SALES)
+ .setBizId(reqDTO.getBizId())
+ .setBizCode(reqDTO.getBizCode())
+ .setBizLineId(item.getBizLineId())
+ .setItemId(item.getItemId())
+ .setWarehouseId(item.getWarehouseId())
+ .setLocationId(item.getLocationId())
+ .setAreaId(item.getAreaId())
+ .setBatchId(item.getBatchId())
+ .setBatchCode(item.getBatchCode())
+ .setQuantity(item.getQuantity().negate())
+ .setReceiptTime(outboundTime)
+ .setCheckFlag(true);
+ transactionIds.add(transactionService.createTransaction(transaction));
+ }
+ return transactionIds.get(0);
+ }
+
+ @Override
+ public Long salesReturnInbound(WmsSalesOutboundReqDTO reqDTO) {
+ log.info("[salesReturnInbound] 閿�鍞��璐у叆搴擄紝bizId={}, bizCode={}", reqDTO.getBizId(), reqDTO.getBizCode());
+ List<Long> transactionIds = CollUtil.newArrayList();
+ LocalDateTime outboundTime = reqDTO.getOutboundTime() != null ? reqDTO.getOutboundTime() : LocalDateTime.now();
+
+ for (WmsSalesOutboundReqDTO.Item item : reqDTO.getItems()) {
+ MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
+ .setType(MesWmTransactionTypeEnum.IN.getType())
+ .setBizType(MesBizTypeConstants.WM_RETURN_SALES)
+ .setBizId(reqDTO.getBizId())
+ .setBizCode(reqDTO.getBizCode())
+ .setBizLineId(item.getBizLineId())
+ .setItemId(item.getItemId())
+ .setWarehouseId(item.getWarehouseId())
+ .setLocationId(item.getLocationId())
+ .setAreaId(item.getAreaId())
+ .setBatchId(item.getBatchId())
+ .setBatchCode(item.getBatchCode())
+ .setQuantity(item.getQuantity())
+ .setReceiptTime(outboundTime)
+ .setCheckFlag(true);
+ transactionIds.add(transactionService.createTransaction(transaction));
+ }
+ return transactionIds.get(0);
+ }
+
+ @Override
+ public BigDecimal getStockQuantity(Long itemId, Long warehouseId) {
+ MesWmMaterialStockListReqVO reqVO = new MesWmMaterialStockListReqVO();
+ reqVO.setItemId(itemId);
+ reqVO.setWarehouseId(warehouseId);
+ List<MesWmMaterialStockDO> stockList = materialStockMapper.selectList(reqVO);
+ if (CollUtil.isEmpty(stockList)) {
+ return BigDecimal.ZERO;
+ }
+ return stockList.stream()
+ .map(MesWmMaterialStockDO::getQuantity)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ @Override
+ public WmsStockDetailRespDTO getStockDetail(WmsStockQueryReqDTO reqDTO) {
+ MesWmMaterialStockListReqVO reqVO = new MesWmMaterialStockListReqVO();
+ reqVO.setItemId(reqDTO.getItemId());
+ reqVO.setWarehouseId(reqDTO.getWarehouseId());
+ reqVO.setLocationId(reqDTO.getLocationId());
+ reqVO.setAreaId(reqDTO.getAreaId());
+ reqVO.setBatchId(reqDTO.getBatchId());
+ reqVO.setVendorId(reqDTO.getVendorId());
+ List<MesWmMaterialStockDO> stockList = materialStockMapper.selectList(reqVO);
+ if (CollUtil.isEmpty(stockList)) {
+ return null;
+ }
+ return convertToStockDetailRespDTO(stockList.get(0));
+ }
+
+ @Override
+ public List<WmsStockDetailRespDTO> getStockDetailList(WmsStockQueryReqDTO reqDTO) {
+ MesWmMaterialStockListReqVO reqVO = new MesWmMaterialStockListReqVO();
+ reqVO.setItemId(reqDTO.getItemId());
+ reqVO.setWarehouseId(reqDTO.getWarehouseId());
+ reqVO.setLocationId(reqDTO.getLocationId());
+ reqVO.setAreaId(reqDTO.getAreaId());
+ reqVO.setBatchId(reqDTO.getBatchId());
+ reqVO.setVendorId(reqDTO.getVendorId());
+ List<MesWmMaterialStockDO> stockList = materialStockMapper.selectList(reqVO);
+ if (CollUtil.isEmpty(stockList)) {
+ return Collections.emptyList();
+ }
+ return convertList(stockList, this::convertToStockDetailRespDTO);
+ }
+
+ /**
+ * 杞崲搴撳瓨DO涓哄搷搴擠TO
+ */
+ private WmsStockDetailRespDTO convertToStockDetailRespDTO(MesWmMaterialStockDO stock) {
+ WmsStockDetailRespDTO respDTO = BeanUtils.toBean(stock, WmsStockDetailRespDTO.class);
+
+ if (stock.getItemId() != null) {
+ MesMdItemDO item = itemService.getItem(stock.getItemId());
+ if (item != null) {
+ respDTO.setItemCode(item.getCode());
+ respDTO.setItemName(item.getName());
+ }
+ }
+
+ if (stock.getWarehouseId() != null) {
+ MesWmWarehouseDO warehouse = warehouseService.getWarehouse(stock.getWarehouseId());
+ if (warehouse != null) {
+ respDTO.setWarehouseName(warehouse.getName());
+ }
+ }
+
+ return respDTO;
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsTransactionApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsTransactionApiImpl.java
new file mode 100644
index 0000000..629bd9e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsTransactionApiImpl.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.mes.api.wms;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+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.wms.api.transaction.WmsTransactionApi;
+import cn.iocoder.yudao.module.wms.api.transaction.WmsTransactionCreateReqDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨浜嬪姟 API 瀹炵幇绫�
+ *
+ * 鍦� MES 妯″潡涓疄鐜帮紝鍐呴儴璋冪敤 MesWmTransactionService
+ */
+@Service
+@Validated
+@Slf4j
+public class WmsTransactionApiImpl implements WmsTransactionApi {
+
+ @Resource
+ private MesWmTransactionService transactionService;
+
+ @Override
+ public Long createTransaction(WmsTransactionCreateReqDTO reqDTO) {
+ log.info("[createTransaction] 鍒涘缓搴撳瓨浜嬪姟锛宼ype={}, bizType={}, itemId={}",
+ reqDTO.getType(), reqDTO.getBizType(), reqDTO.getItemId());
+ MesWmTransactionSaveReqDTO mesTransaction = convertToMesTransaction(reqDTO);
+ return transactionService.createTransaction(mesTransaction);
+ }
+
+ @Override
+ public void createTransactionBatch(List<WmsTransactionCreateReqDTO> reqDTOs) {
+ if (CollUtil.isEmpty(reqDTOs)) {
+ return;
+ }
+ log.info("[createTransactionBatch] 鎵归噺鍒涘缓搴撳瓨浜嬪姟锛宑ount={}", reqDTOs.size());
+ List<MesWmTransactionSaveReqDTO> mesTransactions = reqDTOs.stream()
+ .map(this::convertToMesTransaction)
+ .toList();
+ transactionService.createTransactionList(mesTransactions);
+ }
+
+ /**
+ * 杞崲WMS浜嬪姟DTO涓篗ES浜嬪姟DTO
+ */
+ private MesWmTransactionSaveReqDTO convertToMesTransaction(WmsTransactionCreateReqDTO reqDTO) {
+ return BeanUtils.toBean(reqDTO, MesWmTransactionSaveReqDTO.class);
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java
index 10d1eab..b264eb2 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java
@@ -24,8 +24,11 @@
@ExcelProperty("閫氱煡鍗曞悕绉�")
private String name;
- @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "PO20260101")
- @ExcelProperty("閲囪喘璁㈠崟缂栧彿")
+ @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "1")
+ private Long purchaseOrderId;
+
+ @Schema(description = "閲囪喘璁㈠崟鍙�", example = "PO20260101")
+ @ExcelProperty("閲囪喘璁㈠崟鍙�")
private String purchaseOrderCode;
@Schema(description = "渚涘簲鍟嗙紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeSaveReqVO.java
index 32f8b4c..1a494d5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeSaveReqVO.java
@@ -22,7 +22,10 @@
@NotEmpty(message = "閫氱煡鍗曞悕绉颁笉鑳戒负绌�")
private String name;
- @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "PO20260101")
+ @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "1")
+ private Long purchaseOrderId;
+
+ @Schema(description = "閲囪喘璁㈠崟鍙�", example = "PO20260101")
private String purchaseOrderCode;
@Schema(description = "渚涘簲鍟嗙紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java
index cf8d062..2e7dc10 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java
@@ -27,6 +27,9 @@
@Schema(description = "鎵规鍙�")
private String batchCode;
+ @Schema(description = "渚涘簲鍟嗙紪鍙�")
+ private Long vendorId;
+
@Schema(description = "寮�濮嬫椂闂达紙鍔ㄦ�佺洏鐐圭敤锛�")
private LocalDateTime startTime;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wms/approval/WmsApprovalController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wms/approval/WmsApprovalController.java
new file mode 100644
index 0000000..de2d289
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wms/approval/WmsApprovalController.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.mes.controller.admin.wms.approval;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wms.approval.WmsWarehouseApprovalConfigDO;
+import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
+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;
+
+/**
+ * 浠撳簱瀹℃壒閰嶇疆 Controller
+ */
+@Tag(name = "绠$悊鍚庡彴 - 浠撳簱瀹℃壒閰嶇疆")
+@RestController
+@RequestMapping("/mes/wms/approval")
+@Validated
+public class WmsApprovalController {
+
+ @Resource
+ private WmsApprovalService approvalService;
+
+ @GetMapping("/config/get")
+ @Operation(summary = "鑾峰彇浠撳簱瀹℃壒閰嶇疆")
+ @Parameter(name = "warehouseId", description = "浠撳簱ID", required = true)
+ @Parameter(name = "bizType", description = "涓氬姟绫诲瀷", required = true)
+ @PreAuthorize("@ss.hasPermission('mes:wm-warehouse:query')")
+ public CommonResult<WmsWarehouseApprovalConfigDO> getApprovalConfig(
+ @RequestParam("warehouseId") Long warehouseId,
+ @RequestParam("bizType") String bizType) {
+ return success(approvalService.getApprovalConfig(warehouseId, bizType));
+ }
+
+ @GetMapping("/config/list")
+ @Operation(summary = "鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃�")
+ @Parameter(name = "warehouseId", description = "浠撳簱ID", required = true)
+ @PreAuthorize("@ss.hasPermission('mes:wm-warehouse:query')")
+ public CommonResult<List<WmsWarehouseApprovalConfigDO>> getApprovalConfigList(
+ @RequestParam("warehouseId") Long warehouseId) {
+ return success(approvalService.getApprovalConfigList(warehouseId));
+ }
+
+ @PostMapping("/config/save")
+ @Operation(summary = "淇濆瓨瀹℃壒閰嶇疆")
+ @PreAuthorize("@ss.hasPermission('mes:wm-warehouse:update')")
+ public CommonResult<Boolean> saveApprovalConfig(@Valid @RequestBody WmsWarehouseApprovalConfigDO config) {
+ approvalService.saveApprovalConfig(config);
+ return success(true);
+ }
+
+ @GetMapping("/process-list")
+ @Operation(summary = "鑾峰彇鍙敤鐨勫鎵规祦绋嬪垪琛�")
+ @Parameter(name = "warehouseId", description = "浠撳簱ID", required = true)
+ @Parameter(name = "bizType", description = "涓氬姟绫诲瀷", required = true)
+ @PreAuthorize("@ss.hasPermission('mes:wm-warehouse:query')")
+ public CommonResult<List<BpmProcessDefinitionInfoDO>> getAvailableProcessList(
+ @RequestParam("warehouseId") Long warehouseId,
+ @RequestParam("bizType") String bizType) {
+ return success(approvalService.getAvailableProcessList(warehouseId, bizType));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeDO.java
index a3defc0..b6bfa1d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeDO.java
@@ -39,6 +39,14 @@
private String name;
/**
* 閲囪喘璁㈠崟缂栧彿
+ *
+ * 鍏宠仈 ERP {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO#getId()}
+ */
+ private Long purchaseOrderId;
+ /**
+ * 閲囪喘璁㈠崟鍙�
+ *
+ * 鍐椾綑鑷� ERP 閲囪喘璁㈠崟鐨� no 瀛楁
*/
private String purchaseOrderCode;
/**
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java
index 818579b..41d1171 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java
@@ -95,4 +95,16 @@
*/
private String remark;
+ // ========== BPM 瀹℃壒鐩稿叧瀛楁 ==========
+
+ /**
+ * 娴佺▼瀹炰緥ID
+ */
+ private String processInstanceId;
+
+ /**
+ * 娴佺▼瀹氫箟Key
+ */
+ private String processDefinitionKey;
+
}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transfer/MesWmTransferDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transfer/MesWmTransferDO.java
index 64ed37a..f56ced4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transfer/MesWmTransferDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transfer/MesWmTransferDO.java
@@ -94,4 +94,16 @@
*/
private String remark;
+ // ========== BPM 瀹℃壒鐩稿叧瀛楁 ==========
+
+ /**
+ * 娴佺▼瀹炰緥ID
+ */
+ private String processInstanceId;
+
+ /**
+ * 娴佺▼瀹氫箟Key
+ */
+ private String processDefinitionKey;
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wms/approval/WmsWarehouseApprovalConfigDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wms/approval/WmsWarehouseApprovalConfigDO.java
new file mode 100644
index 0000000..7f081e9
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wms/approval/WmsWarehouseApprovalConfigDO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.wms.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.*;
+
+/**
+ * 浠撳簱瀹℃壒閰嶇疆 DO
+ */
+@TableName("wms_warehouse_approval_config")
+@KeySequence("wms_warehouse_approval_config_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WmsWarehouseApprovalConfigDO extends BaseDO {
+
+ /**
+ * 涓婚敭ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 浠撳簱ID
+ */
+ private Long warehouseId;
+
+ /**
+ * 涓氬姟绫诲瀷
+ * INBOUND - 鍏ュ簱绫讳笟鍔�
+ * OUTBOUND - 鍑哄簱绫讳笟鍔�
+ * TRANSFER - 璋冩嫧涓氬姟
+ * CHECK - 鐩樼偣涓氬姟
+ */
+ private String bizType;
+
+ /**
+ * 鏄惁鍚敤瀹℃壒
+ */
+ private Boolean approvalEnabled;
+
+ /**
+ * 娴佺▼瀹氫箟Key
+ */
+ private String processDefinitionKey;
+
+ /**
+ * BPM鍒嗙被缂栫爜
+ */
+ private String categoryCode;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
index 2810c05..a22847d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -85,8 +85,14 @@
return update(null, updateWrapper);
}
+ /**
+ * 鎸夌粍鍚堥敭鏌ヨ搴撳瓨璁板綍
+ *
+ * 鍞竴閿細(itemId, warehouseId, locationId, areaId, batchId, vendorId)
+ * 鍚屼竴鐗╂枡鍚屼竴搴撲綅锛屼笉鍚屼緵搴斿晢/鎵规鐨勫簱瀛樺垎寮�绠$悊
+ */
default MesWmMaterialStockDO selectByCompositeKey(Long itemId, Long warehouseId, Long locationId,
- Long areaId, Long batchId) {
+ Long areaId, Long batchId, Long vendorId) {
LambdaQueryWrapperX<MesWmMaterialStockDO> wrapper = new LambdaQueryWrapperX<MesWmMaterialStockDO>()
.eqIfPresent(MesWmMaterialStockDO::getItemId, itemId)
.eqIfPresent(MesWmMaterialStockDO::getWarehouseId, warehouseId)
@@ -98,6 +104,12 @@
} else {
wrapper.isNull(MesWmMaterialStockDO::getBatchId);
}
+ // vendorId=null 鏃剁簿纭尮閰� is null
+ if (vendorId != null) {
+ wrapper.eq(MesWmMaterialStockDO::getVendorId, vendorId);
+ } else {
+ wrapper.isNull(MesWmMaterialStockDO::getVendorId);
+ }
return selectOne(wrapper);
}
@@ -108,6 +120,7 @@
.eqIfPresent(MesWmMaterialStockDO::getAreaId, reqVO.getAreaId())
.eqIfPresent(MesWmMaterialStockDO::getItemId, reqVO.getItemId())
.eqIfPresent(MesWmMaterialStockDO::getBatchId, reqVO.getBatchId())
+ .eqIfPresent(MesWmMaterialStockDO::getVendorId, reqVO.getVendorId())
.likeIfPresent(MesWmMaterialStockDO::getBatchCode, reqVO.getBatchCode())
.geIfPresent(MesWmMaterialStockDO::getUpdateTime, reqVO.getStartTime())
.leIfPresent(MesWmMaterialStockDO::getUpdateTime, reqVO.getEndTime())
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskResultMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskResultMapper.java
index e9d03c4..05b7f43 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskResultMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskResultMapper.java
@@ -7,6 +7,8 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskResultDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
/**
* MES 鐩樼偣缁撴灉 Mapper
*
@@ -29,4 +31,8 @@
delete(MesWmStockTakingTaskResultDO::getTaskId, taskId);
}
+ default List<MesWmStockTakingTaskResultDO> selectListByTaskId(Long taskId) {
+ return selectList(MesWmStockTakingTaskResultDO::getTaskId, taskId);
+ }
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wms/approval/WmsWarehouseApprovalConfigMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wms/approval/WmsWarehouseApprovalConfigMapper.java
new file mode 100644
index 0000000..3854b19
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wms/approval/WmsWarehouseApprovalConfigMapper.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.wms.approval;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wms.approval.WmsWarehouseApprovalConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 浠撳簱瀹℃壒閰嶇疆 Mapper
+ */
+@Mapper
+public interface WmsWarehouseApprovalConfigMapper extends BaseMapperX<WmsWarehouseApprovalConfigDO> {
+
+ default WmsWarehouseApprovalConfigDO selectByWarehouseIdAndBizType(Long warehouseId, String bizType) {
+ return selectOne(new LambdaQueryWrapperX<WmsWarehouseApprovalConfigDO>()
+ .eq(WmsWarehouseApprovalConfigDO::getWarehouseId, warehouseId)
+ .eq(WmsWarehouseApprovalConfigDO::getBizType, bizType));
+ }
+
+ default List<WmsWarehouseApprovalConfigDO> selectListByWarehouseId(Long warehouseId) {
+ return selectList(WmsWarehouseApprovalConfigDO::getWarehouseId, warehouseId);
+ }
+
+}
\ No newline at end of file
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 e7a9d3d..eb3a23e 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
@@ -459,6 +459,7 @@
ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_705_004, "鏄庣粏涓婃灦鎬绘暟涓庤鍏ュ簱鏁伴噺涓嶅尮閰�");
ErrorCode WM_ITEM_RECEIPT_STATUS_ERROR = new ErrorCode(1_040_705_005, "鍏ュ簱鍗曠姸鎬佷笉姝g‘");
ErrorCode WM_ITEM_RECEIPT_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_705_006, "宸插畬鎴愭垨宸插彇娑堢殑鍏ュ簱鍗曚笉鍏佽鍙栨秷");
+ ErrorCode WM_PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_040_705_007, "閲囪喘璁㈠崟涓嶅瓨鍦�");
ErrorCode WM_ITEM_RECEIPT_LINE_NOT_EXISTS = new ErrorCode(1_040_705_100, "閲囪喘鍏ュ簱鍗曡涓嶅瓨鍦�");
ErrorCode WM_ITEM_RECEIPT_LINE_ARRIVAL_NOTICE_LINE_REQUIRED = new ErrorCode(1_040_705_101,
"鍏ュ簱鍗曞叧鑱斾簡鍒拌揣閫氱煡鍗曪紝蹇呴』閫夋嫨鍒拌揣閫氱煡鍗曡");
@@ -617,6 +618,7 @@
ErrorCode WM_STOCK_TAKING_TASK_DYNAMIC_TIME_REQUIRED = new ErrorCode(1_040_714_118, "鍔ㄦ�佺洏鐐瑰繀椤婚�夋嫨寮�濮嬫椂闂村拰缁撴潫鏃堕棿");
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, "鍙湁宸插畬鎴愮姸鎬佺殑鐩樼偣浠诲姟鎵嶈兘澶勭悊宸紓");
// ========== MES 浠撳簱绠$悊-閿�鍞嚭搴撳崟锛�1-040-714-000锛� ==========
ErrorCode WM_PRODUCT_SALES_NOT_EXISTS = new ErrorCode(1_040_714_000, "閿�鍞嚭搴撳崟涓嶅瓨鍦�");
@@ -736,4 +738,12 @@
ErrorCode PD_PROCESS_PARAM_TEMPLATE_NOT_EXISTS = new ErrorCode(1_040_805_000, "宸ヨ壓鍙傛暟妯℃澘涓嶅瓨鍦�");
ErrorCode PD_PROCESS_PARAM_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_040_805_001, "宸ヨ壓鍙傛暟妯℃澘缂栫爜宸插瓨鍦�");
+ // ========== MES 浠撳偍鐗╂祦-瀹℃壒閰嶇疆锛�1-040-900-000锛� ==========
+ ErrorCode WMS_APPROVAL_CONFIG_NOT_EXISTS = new ErrorCode(1_040_900_000, "浠撳簱瀹℃壒閰嶇疆涓嶅瓨鍦�");
+ ErrorCode WMS_APPROVAL_ORDER_NOT_EXISTS = new ErrorCode(1_040_900_001, "瀹℃壒鍗曟嵁涓嶅瓨鍦�");
+ ErrorCode WMS_APPROVAL_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_040_900_002, "瀹℃壒娴佺▼瀹氫箟涓嶅瓨鍦�");
+ ErrorCode WMS_APPROVAL_ORDER_TYPE_NOT_SUPPORTED = new ErrorCode(1_040_900_003, "涓嶆敮鎸佺殑鍗曟嵁绫诲瀷");
+ ErrorCode WMS_APPROVAL_STATUS_INVALID = new ErrorCode(1_040_900_004, "鍗曟嵁鐘舵�佷笉姝g‘锛屾棤娉曟彁浜ゅ鎵�");
+ ErrorCode WMS_APPROVAL_ALREADY_SUBMITTED = new ErrorCode(1_040_900_005, "鍗曟嵁宸叉彁浜ゅ鎵癸紝璇峰嬁閲嶅鎻愪氦");
+
}
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 021c297..b09a030 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
@@ -27,24 +27,26 @@
public static final int WM_AREA = 104; // 搴撲綅锛歁esWmStorageAreaDO
public static final int WM_PACKAGE = 105; // 瑁呯鍗曪細MesWmPackageDO
public static final int WM_STOCK = 106; // 搴撳瓨锛歁esWmMaterialStockDO
+ public static final int WM_STOCK_TAKING = 107; // 鐩樼偣璋冩暣
public static final int WM_BATCH = 107; // 鎵规锛歁esWmBatchDO
public static final int WM_TRANSACTION = 108; // 搴撳瓨浜嬪姟娴佹按锛歁esWmTransactionDO
public static final int WM_SN = 109; // SN 鐮侊細MesWmSnDO
public static final int WM_ITEM_RECEIPT_IN = 110; // 閲囪喘鍏ュ簱锛歁esWmItemReceiptDO
- public static final int WM_TRANSFER_OUT = 111; // 璋冩嫧鍑哄簱锛歁esWmTransferDO
- public static final int WM_TRANSFER_IN = 112; // 璋冩嫧鍏ュ簱锛歁esWmTransferDO
- public static final int WM_MISC_ISSUE = 113; // 鍏朵粬鍑哄簱锛歁esWmMiscIssueDO
- public static final int WM_MISC_RECPT = 114; // 鍏朵粬鍏ュ簱锛歁esWmMiscReceiptDO
- public static final int WM_ISSUE = 115; // 鐢熶骇棰嗘枡锛歁esWmProductIssueDO
- public static final int WM_RETURN_ISSUE = 116; // 鐢熶骇閫�鏂欙細MesWmReturnIssueDO
- public static final int WM_PRODUCT_RECPT = 117; // 鐢熶骇鍏ュ簱锛歁esWmProductReceiptDO
- public static final int WM_PRODUCT_SALES = 118; // 閿�鍞嚭搴擄細MesWmProductSalesDO
- public static final int WM_RETURN_SALES = 119; // 閿�鍞��璐у叆搴擄細MesWmReturnSalesDO
- public static final int WM_OUTSOURCE_ISSUE = 120; // 澶栧崗鍙戞枡锛歁esWmOutsourceIssueDO
- public static final int WM_OUTSOURCE_RECPT = 121; // 澶栧崗鍏ュ簱锛歁esWmOutsourceReceiptDO
- public static final int WM_ITEM_CONSUME = 122; // 鐢熶骇娑堣�楋細MesWmItemConsumeDO
- public static final int WM_PRODUCT_PRODUCE = 123; // 浜у搧浜у嚭锛歁esWmProductProduceDO
- public static final int WM_RETURN_VENDOR = 124; // 渚涘簲鍟嗛��璐у嚭搴擄細MesWmReturnVendorDO
+ public static final int WM_ITEM_RECEIPT_CANCEL = 111; // 閲囪喘鍏ュ簱鍙栨秷锛堢孩瀛楀啿閿�锛�
+ public static final int WM_TRANSFER_OUT = 112; // 璋冩嫧鍑哄簱锛歁esWmTransferDO
+ public static final int WM_TRANSFER_IN = 113; // 璋冩嫧鍏ュ簱锛歁esWmTransferDO
+ public static final int WM_MISC_ISSUE = 114; // 鍏朵粬鍑哄簱锛歁esWmMiscIssueDO
+ public static final int WM_MISC_RECPT = 115; // 鍏朵粬鍏ュ簱锛歁esWmMiscReceiptDO
+ public static final int WM_ISSUE = 116; // 鐢熶骇棰嗘枡锛歁esWmProductIssueDO
+ public static final int WM_RETURN_ISSUE = 117; // 鐢熶骇閫�鏂欙細MesWmReturnIssueDO
+ public static final int WM_PRODUCT_RECPT = 118; // 鐢熶骇鍏ュ簱锛歁esWmProductReceiptDO
+ public static final int WM_PRODUCT_SALES = 119; // 閿�鍞嚭搴擄細MesWmProductSalesDO
+ public static final int WM_RETURN_SALES = 120; // 閿�鍞��璐у叆搴擄細MesWmReturnSalesDO
+ public static final int WM_OUTSOURCE_ISSUE = 121; // 澶栧崗鍙戞枡锛歁esWmOutsourceIssueDO
+ public static final int WM_OUTSOURCE_RECPT = 122; // 澶栧崗鍏ュ簱锛歁esWmOutsourceReceiptDO
+ public static final int WM_ITEM_CONSUME = 123; // 鐢熶骇娑堣�楋細MesWmItemConsumeDO
+ public static final int WM_PRODUCT_PRODUCE = 124; // 浜у搧浜у嚭锛歁esWmProductProduceDO
+ public static final int WM_RETURN_VENDOR = 125; // 渚涘簲鍟嗛��璐у嚭搴擄細MesWmReturnVendorDO
// ========== CAL 鎺掔彮妯″潡 [200, 300) ==========
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/WmStatusTransitionUtils.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/WmStatusTransitionUtils.java
new file mode 100644
index 0000000..92c1de9
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/WmStatusTransitionUtils.java
@@ -0,0 +1,185 @@
+package cn.iocoder.yudao.module.mes.enums.wm;
+
+import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants;
+
+import java.util.*;
+
+/**
+ * 鍗曟嵁鐘舵�佹祦杞牎楠屽伐鍏�
+ *
+ * 瀹氫箟鍚勪笟鍔″崟鎹殑鍚堟硶鐘舵�佹祦杞紝闃叉闈炴硶鐘舵�佽烦杞�
+ */
+public class WmStatusTransitionUtils {
+
+ /**
+ * 閫氱敤鐘舵�佸父閲忥紙渚夸簬闃呰锛�
+ */
+ private static final int PREPARE = MesOrderStatusConstants.PREPARE; // 鑽夌
+ private static final int APPROVING = MesOrderStatusConstants.APPROVING; // 寰呭鎵�/寰呬笂鏋�/寰呮嫞璐�
+ private static final int CONFIRMED = MesOrderStatusConstants.CONFIRMED; // 寰呯‘璁�/寰呮娴�
+ private static final int APPROVED = MesOrderStatusConstants.APPROVED; // 宸插鎵�/寰呮墽琛�
+ private static final int FINISHED = MesOrderStatusConstants.FINISHED; // 宸插畬鎴�
+ private static final int CANCELLED = MesOrderStatusConstants.CANCELLED; // 宸插彇娑�
+ private static final int SHIPPING = 10; // 寰呭~鍐欒繍鍗曪紙閿�鍞嚭搴撲笓鐢級
+
+ /**
+ * 鐘舵�佹祦杞鍒欏畾涔�
+ * Key: 褰撳墠鐘舵��, Value: 鍏佽娴佽浆鍒扮殑鐘舵�侀泦鍚�
+ */
+ private static final Map<String, Map<Integer, Set<Integer>>> TRANSITION_RULES = new HashMap<>();
+
+ static {
+ // 閲囪喘鍏ュ簱鍗曠姸鎬佹祦杞�
+ registerTransition("ITEM_RECEIPT",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("ITEM_RECEIPT",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("ITEM_RECEIPT",
+ APPROVED, FINISHED, CANCELLED);
+ // 宸插畬鎴愬拰宸插彇娑堜笉鍏佽娴佽浆
+
+ // 閿�鍞嚭搴撳崟鐘舵�佹祦杞�
+ registerTransition("PRODUCT_SALES",
+ PREPARE, CONFIRMED, APPROVING, CANCELLED); // 鎻愪氦鏃跺彲鑳藉幓寰呮娴嬫垨寰呮嫞璐�
+ registerTransition("PRODUCT_SALES",
+ CONFIRMED, APPROVING, CANCELLED); // OQC閫氳繃鍚庡幓寰呮嫞璐�
+ registerTransition("PRODUCT_SALES",
+ APPROVING, SHIPPING, CANCELLED); // 鎷h揣鍚庡幓寰呭~鍐欒繍鍗�
+ registerTransition("PRODUCT_SALES",
+ SHIPPING, APPROVED, CANCELLED); // 濉啓杩愬崟鍚庡幓寰呮墽琛屽嚭搴�
+ registerTransition("PRODUCT_SALES",
+ APPROVED, FINISHED, CANCELLED); // 鍑哄簱鍚庡幓宸插畬鎴�
+
+ // 杞Щ璋冩嫧鍗曠姸鎬佹祦杞�
+ registerTransition("TRANSFER",
+ PREPARE, CONFIRMED, APPROVING, CANCELLED); // 鎻愪氦鏃跺彲鑳藉幓寰呯‘璁ゆ垨寰呬笂鏋�
+ registerTransition("TRANSFER",
+ CONFIRMED, APPROVING, CANCELLED); // 纭鍚庡幓寰呬笂鏋�
+ registerTransition("TRANSFER",
+ APPROVING, APPROVED, CANCELLED); // 涓婃灦鍚庡幓寰呮墽琛�
+ registerTransition("TRANSFER",
+ APPROVED, FINISHED, CANCELLED); // 鎵ц鍚庡幓宸插畬鎴�
+
+ // 鐢熶骇棰嗘枡鍗曠姸鎬佹祦杞�
+ registerTransition("PRODUCT_ISSUE",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("PRODUCT_ISSUE",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("PRODUCT_ISSUE",
+ APPROVED, FINISHED, CANCELLED);
+
+ // 鐢熶骇鍏ュ簱鍗曠姸鎬佹祦杞�
+ registerTransition("PRODUCT_RECEIPT",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("PRODUCT_RECEIPT",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("PRODUCT_RECEIPT",
+ APPROVED, FINISHED, CANCELLED);
+
+ // 鏉傞」鍏ュ簱鍗曠姸鎬佹祦杞�
+ registerTransition("MISC_RECEIPT",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("MISC_RECEIPT",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("MISC_RECEIPT",
+ APPROVED, FINISHED, CANCELLED);
+
+ // 鏉傞」鍑哄簱鍗曠姸鎬佹祦杞�
+ registerTransition("MISC_ISSUE",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("MISC_ISSUE",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("MISC_ISSUE",
+ APPROVED, FINISHED, CANCELLED);
+
+ // 渚涘簲鍟嗛��璐у崟鐘舵�佹祦杞�
+ registerTransition("RETURN_VENDOR",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("RETURN_VENDOR",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("RETURN_VENDOR",
+ APPROVED, FINISHED, CANCELLED);
+
+ // 閿�鍞��璐у崟鐘舵�佹祦杞�
+ registerTransition("RETURN_SALES",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("RETURN_SALES",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("RETURN_SALES",
+ APPROVED, FINISHED, CANCELLED);
+
+ // 澶栧崗鍙戞枡鍗曠姸鎬佹祦杞�
+ registerTransition("OUTSOURCE_ISSUE",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("OUTSOURCE_ISSUE",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("OUTSOURCE_ISSUE",
+ APPROVED, FINISHED, CANCELLED);
+
+ // 澶栧崗鍏ュ簱鍗曠姸鎬佹祦杞�
+ registerTransition("OUTSOURCE_RECEIPT",
+ PREPARE, APPROVING, CANCELLED);
+ registerTransition("OUTSOURCE_RECEIPT",
+ APPROVING, APPROVED, CANCELLED);
+ registerTransition("OUTSOURCE_RECEIPT",
+ APPROVED, FINISHED, CANCELLED);
+ }
+
+ /**
+ * 娉ㄥ唽鐘舵�佹祦杞鍒�
+ */
+ private static void registerTransition(String bizType, int fromStatus, int... toStatuses) {
+ TRANSITION_RULES
+ .computeIfAbsent(bizType, k -> new HashMap<>())
+ .computeIfAbsent(fromStatus, k -> new HashSet<>())
+ .addAll(Arrays.stream(toStatuses).boxed().toList());
+ }
+
+ /**
+ * 鏍¢獙鐘舵�佹祦杞槸鍚﹀悎娉�
+ *
+ * @param bizType 涓氬姟绫诲瀷锛堝 ITEM_RECEIPT, PRODUCT_SALES锛�
+ * @param fromStatus 褰撳墠鐘舵��
+ * @param toStatus 鐩爣鐘舵��
+ * @return 鏄惁鍏佽娴佽浆
+ */
+ public static boolean isValidTransition(String bizType, int fromStatus, int toStatus) {
+ Map<Integer, Set<Integer>> bizRules = TRANSITION_RULES.get(bizType);
+ if (bizRules == null) {
+ return false;
+ }
+ Set<Integer> allowedToStatuses = bizRules.get(fromStatus);
+ return allowedToStatuses != null && allowedToStatuses.contains(toStatus);
+ }
+
+ /**
+ * 鏍¢獙鐘舵�佹祦杞紝闈炴硶鍒欐姏鍑哄紓甯�
+ *
+ * @param bizType 涓氬姟绫诲瀷
+ * @param fromStatus 褰撳墠鐘舵��
+ * @param toStatus 鐩爣鐘舵��
+ * @param documentName 鍗曟嵁鍚嶇О锛堢敤浜庨敊璇彁绀猴級
+ */
+ public static void validateTransition(String bizType, int fromStatus, int toStatus, String documentName) {
+ if (!isValidTransition(bizType, fromStatus, toStatus)) {
+ throw new IllegalStateException(String.format(
+ "%s鐘舵�佹祦杞潪娉曪細涓嶅厑璁镐粠鐘舵�乕%d]娴佽浆鍒扮姸鎬乕%d]", documentName, fromStatus, toStatus));
+ }
+ }
+
+ /**
+ * 鑾峰彇鍗曟嵁鍏佽鐨勪笅涓�姝ョ姸鎬佸垪琛�
+ *
+ * @param bizType 涓氬姟绫诲瀷
+ * @param fromStatus 褰撳墠鐘舵��
+ * @return 鍏佽娴佽浆鐨勭姸鎬佸垪琛�
+ */
+ public static Set<Integer> getAllowedNextStatuses(String bizType, int fromStatus) {
+ Map<Integer, Set<Integer>> bizRules = TRANSITION_RULES.get(bizType);
+ if (bizRules == null) {
+ return Collections.emptySet();
+ }
+ return bizRules.getOrDefault(fromStatus, Collections.emptySet());
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
index 3b5a79e..32ea507 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -11,6 +11,8 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -41,6 +43,9 @@
@Resource
private cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService vendorService;
+ @Resource
+ private ErpPurchaseOrderApi purchaseOrderApi;
+
@Override
public Long createArrivalNotice(MesWmArrivalNoticeSaveReqVO createReqVO) {
// 鏍¢獙鏁版嵁
@@ -70,6 +75,15 @@
validateCodeUnique(reqVO.getId(), reqVO.getCode());
// 鏍¢獙渚涘簲鍟嗗瓨鍦ㄤ笖鍚敤
vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
+ // 鏍¢獙閲囪喘璁㈠崟瀛樺湪锛屽苟鑷姩濉厖閲囪喘璁㈠崟鍙�
+ if (reqVO.getPurchaseOrderId() != null) {
+ ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId()).getCheckedData();
+ if (purchaseOrder == null) {
+ throw exception(WM_PURCHASE_ORDER_NOT_EXISTS);
+ }
+ // 鑷姩濉厖閲囪喘璁㈠崟鍙�
+ reqVO.setPurchaseOrderCode(purchaseOrder.getNo());
+ }
}
@Override
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
index 50405b6..638bae4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -148,9 +148,9 @@
@Override
public MesWmMaterialStockDO getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime) {
- // 1. 鏌ユ壘宸叉湁搴撳瓨璁板綍
+ // 1. 鏌ユ壘宸叉湁搴撳瓨璁板綍锛坴endorId 鍙備笌鍞竴閿級
MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKey(
- itemId, warehouseId, locationId, areaId, batchId);
+ itemId, warehouseId, locationId, areaId, batchId, vendorId);
if (stock != null) {
return stock;
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java
index 3f24a8e..514860c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java
@@ -124,5 +124,13 @@
*/
MesWmProductSalesDO validateProductSalesExistsAndDraft(Long id);
+ /**
+ * 鏇存柊閿�鍞嚭搴撳崟鐘舵��
+ *
+ * @param id 缂栧彿
+ * @param status 鐘舵��
+ */
+ void updateStatus(Long id, Integer status);
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
index d45aa41..a1cedf7 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -20,6 +20,7 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmSalesNoticeStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService;
+import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -57,6 +58,8 @@
private MesWmSalesNoticeService salesNoticeService;
@Resource
private MesWmTransactionService wmTransactionService;
+ @Resource
+ private MesWmMaterialStockService materialStockService;
@Override
public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -167,12 +170,25 @@
if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.APPROVING.getStatus(), sales.getStatus())) {
throw exception(WM_PRODUCT_SALES_CANNOT_PICK);
}
- // 鏍¢獙姣忚鑷冲皯鏈夋嫞璐ф槑缁�
+ // 鏍¢獙姣忚鑷冲皯鏈夋嫞璐ф槑缁嗭紝骞舵牎楠屽簱瀛樺厖瓒�
List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(id);
for (MesWmProductSalesLineDO line : lines) {
List<MesWmProductSalesDetailDO> details = productSalesDetailService.getProductSalesDetailListByLineId(line.getId());
if (CollUtil.isEmpty(details)) {
throw exception(WM_PRODUCT_SALES_DETAILS_EMPTY);
+ }
+ // 鏍¢獙搴撳瓨鍏呰冻
+ for (MesWmProductSalesDetailDO detail : details) {
+ materialStockService.validateSelectedStock(
+ detail.getMaterialStockId(),
+ detail.getItemId(),
+ detail.getBatchId(),
+ detail.getBatchCode(),
+ detail.getWarehouseId(),
+ detail.getLocationId(),
+ detail.getAreaId(),
+ detail.getQuantity()
+ );
}
}
@@ -306,4 +322,9 @@
}
}
+ @Override
+ public void updateStatus(Long id, Integer status) {
+ productSalesMapper.updateById(new MesWmProductSalesDO().setId(id).setStatus(status));
+ }
+
}
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 6c2ebc1..8d1bea0 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
@@ -5,6 +5,8 @@
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.MesWmStockTakingTaskResultDO;
+import java.util.List;
+
/**
* MES 鐩樼偣缁撴灉 Service 鎺ュ彛
*
@@ -57,4 +59,12 @@
*/
void deleteStockTakingTaskResultByTaskId(Long taskId);
+ /**
+ * 鏍规嵁浠诲姟缂栧彿鑾峰彇鐩樼偣缁撴灉鍒楄〃
+ *
+ * @param taskId 浠诲姟缂栧彿
+ * @return 鐩樼偣缁撴灉鍒楄〃
+ */
+ List<MesWmStockTakingTaskResultDO> getStockTakingTaskResultListByTaskId(Long taskId);
+
}
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 2315426..fe08687 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
@@ -14,6 +14,7 @@
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.WM_STOCK_TAKING_TASK_LINE_ALREADY_TAKEN;
@@ -126,6 +127,11 @@
stockTakingTaskResultMapper.deleteByTaskId(taskId);
}
+ @Override
+ public List<MesWmStockTakingTaskResultDO> getStockTakingTaskResultListByTaskId(Long taskId) {
+ return stockTakingTaskResultMapper.selectListByTaskId(taskId);
+ }
+
/**
* 鏍¢獙鐩樼偣缁撴灉鏄惁瀛樺湪
*
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 f5202e4..3051683 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
@@ -96,4 +96,15 @@
*/
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 efaf298..e3b7737 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
@@ -8,10 +8,15 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskSaveReqVO;
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.mysql.wm.stocktaking.task.MesWmStockTakingTaskMapper;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
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.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 jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
@@ -19,6 +24,7 @@
import org.springframework.transaction.annotation.Transactional;
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;
@@ -47,6 +53,9 @@
@Lazy
@Resource
private MesWmStockTakingTaskResultService stockTakingTaskResultService;
+
+ @Resource
+ private MesWmTransactionService wmTransactionService;
@Resource
private AdminUserApi adminUserApi;
@@ -220,4 +229,63 @@
}
}
+ @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);
+ if (CollUtil.isEmpty(results)) {
+ return; // 鏃犵洏鐐圭粨鏋滐紝鏃犻渶澶勭悊
+ }
+
+ // 3. 閬嶅巻缁撴灉锛屽鐞嗗樊寮�
+ for (MesWmStockTakingTaskResultDO result : results) {
+ BigDecimal bookQuantity = result.getQuantity(); // 璐﹂潰鏁伴噺
+ BigDecimal takingQuantity = result.getTakingQuantity(); // 鐩樼偣鏁伴噺
+
+ if (takingQuantity == null || bookQuantity == null) {
+ continue;
+ }
+
+ // 璁$畻宸紓锛氱洏鐐规暟閲� - 璐﹂潰鏁伴噺
+ BigDecimal difference = takingQuantity.subtract(bookQuantity);
+
+ if (difference.compareTo(BigDecimal.ZERO) == 0) {
+ continue; // 鏃犲樊寮傦紝璺宠繃
+ }
+
+ // 4. 鍒涘缓搴撳瓨璋冩暣浜嬪姟
+ MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO()
+ .setItemId(result.getItemId())
+ .setBatchId(result.getBatchId())
+ .setBatchCode(result.getBatchCode())
+ .setWarehouseId(result.getWarehouseId())
+ .setLocationId(result.getLocationId())
+ .setAreaId(result.getAreaId())
+ .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);
+ }
+
+ wmTransactionService.createTransaction(transaction);
+ }
+ }
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferService.java
index 801962d..5a70894 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferService.java
@@ -66,4 +66,12 @@
*/
MesWmTransferDO validateTransferEditable(Long id);
+ /**
+ * 鏇存柊杞Щ鍗曠姸鎬�
+ *
+ * @param id 缂栧彿
+ * @param status 鐘舵��
+ */
+ void updateStatus(Long id, Integer status);
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java
index 8c5e304..9e40829 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java
@@ -322,4 +322,9 @@
materialStockService.updateMaterialStockFrozen(materialStockIds, frozen);
}
+ @Override
+ public void updateStatus(Long id, Integer status) {
+ transferMapper.updateById(new MesWmTransferDO().setId(id).setStatus(status));
+ }
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalService.java
new file mode 100644
index 0000000..948d7fd
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalService.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.mes.service.wms.approval;
+
+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.mes.dal.dataobject.wms.approval.WmsWarehouseApprovalConfigDO;
+
+import java.util.List;
+
+/**
+ * WMS 瀹℃壒鏈嶅姟鎺ュ彛
+ */
+public interface WmsApprovalService {
+
+ /**
+ * 鑾峰彇浠撳簱瀹℃壒閰嶇疆
+ *
+ * @param warehouseId 浠撳簱ID
+ * @param bizType 涓氬姟绫诲瀷
+ * @return 瀹℃壒閰嶇疆
+ */
+ WmsWarehouseApprovalConfigDO getApprovalConfig(Long warehouseId, String bizType);
+
+ /**
+ * 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃�
+ *
+ * @param warehouseId 浠撳簱ID
+ * @return 瀹℃壒閰嶇疆鍒楄〃
+ */
+ List<WmsWarehouseApprovalConfigDO> getApprovalConfigList(Long warehouseId);
+
+ /**
+ * 淇濆瓨瀹℃壒閰嶇疆
+ *
+ * @param config 瀹℃壒閰嶇疆
+ */
+ void saveApprovalConfig(WmsWarehouseApprovalConfigDO config);
+
+ /**
+ * 妫�鏌ユ槸鍚﹂渶瑕佸鎵�
+ *
+ * @param warehouseId 浠撳簱ID
+ * @param bizType 涓氬姟绫诲瀷
+ * @return 鏄惁闇�瑕佸鎵�
+ */
+ boolean needApproval(Long warehouseId, String bizType);
+
+ /**
+ * 鎻愪氦瀹℃壒
+ *
+ * @param orderId 鍗曟嵁ID
+ * @param orderType 鍗曟嵁绫诲瀷(INBOUND/OUTBOUND/TRANSFER/CHECK)
+ * @param processDefinitionKey 娴佺▼瀹氫箟Key(鍙�夛紝涓虹┖鍒欎娇鐢ㄩ厤缃殑榛樿娴佺▼)
+ * @param userId 鐢ㄦ埛ID
+ * @return 娴佺▼瀹炰緥ID
+ */
+ String submitApproval(Long orderId, String orderType, String processDefinitionKey, Long userId);
+
+ /**
+ * 鑾峰彇鍙敤鐨勫鎵规祦绋嬪垪琛�
+ *
+ * @param warehouseId 浠撳簱ID
+ * @param bizType 涓氬姟绫诲瀷
+ * @return 娴佺▼瀹氫箟鍒楄〃
+ */
+ List<BpmProcessDefinitionInfoDO> getAvailableProcessList(Long warehouseId, String bizType);
+
+ /**
+ * 鏇存柊瀹℃壒鐘舵��
+ *
+ * @param orderId 鍗曟嵁ID
+ * @param orderType 鍗曟嵁绫诲瀷
+ * @param bpmStatus BPM瀹℃壒鐘舵��
+ */
+ void updateAuditStatus(Long orderId, String orderType, Integer bpmStatus);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalServiceImpl.java
new file mode 100644
index 0000000..f484d98
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalServiceImpl.java
@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.module.mes.service.wms.approval;
+
+import cn.hutool.core.util.StrUtil;
+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.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wms.approval.WmsWarehouseApprovalConfigDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wms.approval.WmsWarehouseApprovalConfigMapper;
+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.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+
+/**
+ * WMS 瀹℃壒鏈嶅姟瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class WmsApprovalServiceImpl implements WmsApprovalService {
+
+ /**
+ * 榛樿BPM鍒嗙被缂栫爜
+ */
+ private static final String DEFAULT_CATEGORY_CODE = "wms_approve";
+
+ @Resource
+ private WmsWarehouseApprovalConfigMapper approvalConfigMapper;
+
+ @Resource
+ private BpmCategoryService bpmCategoryService;
+
+ @Resource
+ private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+
+ @Override
+ public WmsWarehouseApprovalConfigDO getApprovalConfig(Long warehouseId, String bizType) {
+ return approvalConfigMapper.selectByWarehouseIdAndBizType(warehouseId, bizType);
+ }
+
+ @Override
+ public List<WmsWarehouseApprovalConfigDO> getApprovalConfigList(Long warehouseId) {
+ return approvalConfigMapper.selectListByWarehouseId(warehouseId);
+ }
+
+ @Override
+ public void saveApprovalConfig(WmsWarehouseApprovalConfigDO config) {
+ // 妫�鏌ユ槸鍚﹀凡瀛樺湪
+ WmsWarehouseApprovalConfigDO existing = getApprovalConfig(config.getWarehouseId(), config.getBizType());
+ if (existing == null) {
+ approvalConfigMapper.insert(config);
+ } else {
+ config.setId(existing.getId());
+ approvalConfigMapper.updateById(config);
+ }
+ }
+
+ @Override
+ public boolean needApproval(Long warehouseId, String bizType) {
+ WmsWarehouseApprovalConfigDO config = getApprovalConfig(warehouseId, bizType);
+ return config != null && Boolean.TRUE.equals(config.getApprovalEnabled());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String submitApproval(Long orderId, String orderType, String processDefinitionKey, Long userId) {
+ log.info("[submitApproval] 鎻愪氦瀹℃壒锛宱rderId={}, orderType={}", orderId, orderType);
+
+ // 娉ㄦ剰锛氭鏂规硶闇�瑕佹牴鎹疄闄呬笟鍔″満鏅疄鐜�
+ // 1. 鑾峰彇鍗曟嵁淇℃伅鍜屼粨搴揑D锛堜粨搴揑D鍦ㄦ槑缁嗚涓紝闇�鍏堟煡璇㈡槑缁嗭級
+ // 2. 鑾峰彇瀹℃壒閰嶇疆
+ // 3. 鍒涘缓娴佺▼瀹炰緥
+ // 4. 鏇存柊鍗曟嵁鐨勬祦绋嬪疄渚婭D鍜岀姸鎬�
+
+ // 褰撳墠涓虹畝鍖栧疄鐜帮紝瀹為檯浣跨敤鏃堕渶瑕佽ˉ鍏呭畬鏁撮�昏緫
+ throw exception(WMS_APPROVAL_ORDER_TYPE_NOT_SUPPORTED);
+ }
+
+ @Override
+ public List<BpmProcessDefinitionInfoDO> getAvailableProcessList(Long warehouseId, String bizType) {
+ WmsWarehouseApprovalConfigDO config = getApprovalConfig(warehouseId, bizType);
+ String categoryCode = config != null && StrUtil.isNotBlank(config.getCategoryCode())
+ ? config.getCategoryCode() : DEFAULT_CATEGORY_CODE;
+
+ // 鏌ヨ鍒嗙被涓嬬殑娴佺▼瀹氫箟
+ List<BpmProcessDefinitionInfoDO> definitions = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(categoryCode);
+ if (definitions == null) {
+ return Collections.emptyList();
+ }
+ return definitions;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateAuditStatus(Long orderId, String orderType, Integer bpmStatus) {
+ log.info("[updateAuditStatus] 鏇存柊瀹℃壒鐘舵�侊紝orderId={}, orderType={}, bpmStatus={}", orderId, orderType, bpmStatus);
+
+ // 灏咮PM鐘舵�佽浆鎹负涓氬姟鐘舵��
+ Integer businessStatus = convertBpmResultToBusinessStatus(bpmStatus, orderType);
+ if (businessStatus == null) {
+ log.warn("[updateAuditStatus] 鏈煡鐨凚PM鐘舵�侊紝bpmStatus={}", bpmStatus);
+ return;
+ }
+
+ // 娉ㄦ剰锛氭鏂规硶闇�瑕佹牴鎹疄闄呬笟鍔″満鏅疄鐜�
+ // 褰撳墠涓虹畝鍖栧疄鐜帮紝瀹為檯浣跨敤鏃堕渶瑕佽ˉ鍏呭畬鏁撮�昏緫
+ throw exception(WMS_APPROVAL_ORDER_TYPE_NOT_SUPPORTED);
+ }
+
+ /**
+ * 灏咮PM瀹℃壒缁撴灉杞崲涓轰笟鍔$姸鎬�
+ */
+ private Integer convertBpmResultToBusinessStatus(Integer bpmStatus, String orderType) {
+ // 鏍规嵁BPM瀹℃壒缁撴灉杩斿洖瀵瑰簲鐘舵��
+ // APPROVE -> 宸插鎵圭姸鎬�
+ // REJECT -> 鑽夌鐘舵�侊紙璁╃敤鎴峰彲浠ラ噸鏂版彁浜わ級
+ // CANCEL -> 宸插彇娑堢姸鎬�
+ if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmStatus)) {
+ return 20; // APPROVED 鐘舵��
+ } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmStatus)) {
+ return 0; // PREPARE 鐘舵��
+ } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmStatus)) {
+ return 40; // CANCELED 鐘舵��
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java
new file mode 100644
index 0000000..99ea2af
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.mes.service.wms.approval.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * WMS 閲囪喘鍏ュ簱鍗曞鎵圭姸鎬佺洃鍚櫒
+ *
+ * 鐩戝惉BPM瀹℃壒缁撴灉锛屾洿鏂板叆搴撳崟鐘舵��
+ */
+@Component
+@Slf4j
+public class WmsInboundStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private WmsApprovalService approvalService;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ鍏ュ簱鍗�
+ Long orderId;
+ try {
+ orderId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+ }
+
+ log.info("[onApplicationEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
+ event.getBusinessKey(), event.getStatus());
+
+ // 璋冪敤瀹℃壒鏈嶅姟鏇存柊鐘舵��
+ try {
+ approvalService.updateAuditStatus(orderId, "INBOUND", event.getStatus());
+ } catch (Exception e) {
+ log.error("[onApplicationEvent] 鏇存柊鍏ュ簱鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java
new file mode 100644
index 0000000..dae7b5e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.mes.service.wms.approval.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * WMS 閿�鍞嚭搴撳崟瀹℃壒鐘舵�佺洃鍚櫒
+ *
+ * 鐩戝惉BPM瀹℃壒缁撴灉锛屾洿鏂板嚭搴撳崟鐘舵��
+ */
+@Component
+@Slf4j
+public class WmsOutboundStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private WmsApprovalService approvalService;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ鍑哄簱鍗�
+ Long orderId;
+ try {
+ orderId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+ }
+
+ log.info("[onApplicationEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
+ event.getBusinessKey(), event.getStatus());
+
+ // 璋冪敤瀹℃壒鏈嶅姟鏇存柊鐘舵��
+ try {
+ approvalService.updateAuditStatus(orderId, "OUTBOUND", event.getStatus());
+ } catch (Exception e) {
+ log.error("[onApplicationEvent] 鏇存柊鍑哄簱鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java
new file mode 100644
index 0000000..fffea2c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.mes.service.wms.approval.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * WMS 璋冩嫧鍗曞鎵圭姸鎬佺洃鍚櫒
+ *
+ * 鐩戝惉BPM瀹℃壒缁撴灉锛屾洿鏂拌皟鎷ㄥ崟鐘舵��
+ */
+@Component
+@Slf4j
+public class WmsTransferStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+ @Resource
+ private WmsApprovalService approvalService;
+
+ @Override
+ public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+ // 閫氳繃 businessKey 鏌ヨ璋冩嫧鍗�
+ Long orderId;
+ try {
+ orderId = Long.parseLong(event.getBusinessKey());
+ } catch (NumberFormatException e) {
+ return; // businessKey 涓嶆槸鏁板瓧锛屽拷鐣�
+ }
+
+ log.info("[onApplicationEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宐usinessKey={}, status={}",
+ event.getBusinessKey(), event.getStatus());
+
+ // 璋冪敤瀹℃壒鏈嶅姟鏇存柊鐘舵��
+ try {
+ approvalService.updateAuditStatus(orderId, "TRANSFER", event.getStatus());
+ } catch (Exception e) {
+ log.error("[onApplicationEvent] 鏇存柊璋冩嫧鍗曞鎵圭姸鎬佸け璐ワ紝orderId={}", orderId, e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java
index 0092b27..ca84dca 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.api.user;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import java.util.Collection;
@@ -16,6 +17,14 @@
public interface AdminUserApi {
/**
+ * 鍒涘缓鐢ㄦ埛
+ *
+ * @param createReqDTO 鐢ㄦ埛鍒涘缓淇℃伅
+ * @return 鐢ㄦ埛缂栧彿
+ */
+ Long createUser(AdminUserCreateReqDTO createReqDTO);
+
+ /**
* 閫氳繃鐢ㄦ埛 ID 鏌ヨ鐢ㄦ埛
*
* @param id 鐢ㄦ埛ID
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java
index 8bf1e19..e5157ea 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java
@@ -5,7 +5,9 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
@@ -34,6 +36,23 @@
private DeptService deptService;
@Override
+ public Long createUser(AdminUserCreateReqDTO createReqDTO) {
+ UserSaveReqVO createReqVO = new UserSaveReqVO();
+ createReqVO.setUsername(createReqDTO.getUsername());
+ createReqVO.setNickname(createReqDTO.getNickname());
+ createReqVO.setDeptId(createReqDTO.getDeptId());
+ createReqVO.setPostIds(createReqDTO.getPostIds());
+ createReqVO.setRoleIds(createReqDTO.getRoleIds());
+ createReqVO.setMobile(createReqDTO.getMobile());
+ createReqVO.setPassword(createReqDTO.getPassword());
+ createReqVO.setSex(createReqDTO.getSex());
+ createReqVO.setEmail(createReqDTO.getEmail());
+ createReqVO.setAvatar(createReqDTO.getAvatar());
+ createReqVO.setRemark(createReqDTO.getRemark());
+ return userService.createUser(createReqVO);
+ }
+
+ @Override
@DataPermission(enable = false) // 蹇界暐鏁版嵁鏉冮檺锛岄伩鍏嶅洜涓鸿繃婊わ紝瀵艰嚧鏃犳硶鏌ヨ鐢ㄦ埛銆傜被浼硷細https://github.com/YunaiV/ruoyi-vue-pro/issues/1051
public AdminUserRespDTO getUser(Long id) {
AdminUserDO user = userService.getUser(id);
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserCreateReqDTO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserCreateReqDTO.java
new file mode 100644
index 0000000..2474d99
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserCreateReqDTO.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.system.api.user.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.Set;
+
+/**
+ * Admin 鐢ㄦ埛鍒涘缓 Request DTO
+ */
+@Schema(description = "绠$悊鍚庡彴 - 鐢ㄦ埛鍒涘缓 Request DTO")
+@Data
+public class AdminUserCreateReqDTO {
+
+ @Schema(description = "鐢ㄦ埛璐﹀彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "zhangsan")
+ @NotEmpty(message = "鐢ㄦ埛璐﹀彿涓嶈兘涓虹┖")
+ private String username;
+
+ @Schema(description = "鐢ㄦ埛鏄电О", requiredMode = Schema.RequiredMode.REQUIRED, example = "寮犱笁")
+ @NotEmpty(message = "鐢ㄦ埛鏄电О涓嶈兘涓虹┖")
+ private String nickname;
+
+ @Schema(description = "閮ㄩ棬缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "閮ㄩ棬缂栧彿涓嶈兘涓虹┖")
+ private Long deptId;
+
+ @Schema(description = "宀椾綅缂栧彿鏁扮粍", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1]")
+ @NotNull(message = "宀椾綅缂栧彿涓嶈兘涓虹┖")
+ private Set<Long> postIds;
+
+ @Schema(description = "瑙掕壊缂栧彿鏁扮粍", example = "[1, 2]")
+ private Set<Long> roleIds;
+
+ @Schema(description = "鎵嬫満鍙风爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
+ @NotEmpty(message = "鎵嬫満鍙风爜涓嶈兘涓虹┖")
+ private String mobile;
+
+ @Schema(description = "瀵嗙爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
+ @NotEmpty(message = "瀵嗙爜涓嶈兘涓虹┖")
+ private String password;
+
+ @Schema(description = "鐢ㄦ埛鎬у埆", example = "1")
+ private Integer sex;
+
+ @Schema(description = "鐢ㄦ埛閭", example = "zhangsan@example.com")
+ private String email;
+
+ @Schema(description = "鐢ㄦ埛澶村儚", example = "https://www.iocoder.cn/xxx.png")
+ private String avatar;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java
index f196e65..f645a82 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java
@@ -45,6 +45,9 @@
@DiffLogField(name = "宀椾綅", function = PostParseFunction.NAME)
private Set<Long> postIds;
+ @Schema(description = "瑙掕壊缂栧彿鏁扮粍", example = "[1, 2]")
+ private Set<Long> roleIds;
+
@Schema(description = "鐢ㄦ埛閭", example = "yudao@iocoder.cn")
@Email(message = "閭鏍煎紡涓嶆纭�")
@Size(max = 50, message = "閭闀垮害涓嶈兘瓒呰繃 50 涓瓧绗�")
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
index c2840b8..9f46412 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
@@ -117,6 +117,10 @@
userPostMapper.insertBatch(convertList(user.getPostIds(),
postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
}
+ // 2.3 鍒嗛厤鐢ㄦ埛瑙掕壊
+ if (CollUtil.isNotEmpty(createReqVO.getRoleIds())) {
+ permissionService.assignUserRole(user.getId(), createReqVO.getRoleIds());
+ }
// 3. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
LogRecordContext.putVariable("user", user);
diff --git a/yudao-module-wms-api/pom.xml b/yudao-module-wms-api/pom.xml
new file mode 100644
index 0000000..2c00621
--- /dev/null
+++ b/yudao-module-wms-api/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-wms-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ wms 妯″潡涓嬶紝浠撳偍鐗╂祦鐨� API 鎺ュ彛瀹氫箟锛屼緵鍏朵粬妯″潡寮曠敤
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-common</artifactId>
+ </dependency>
+
+ <!-- Swagger 娉ㄨВ -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsPurchaseInboundReqDTO.java b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsPurchaseInboundReqDTO.java
new file mode 100644
index 0000000..bf35d46
--- /dev/null
+++ b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsPurchaseInboundReqDTO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.wms.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 閲囪喘鍏ュ簱璇锋眰 DTO
+ */
+@Schema(description = "閲囪喘鍏ュ簱璇锋眰 DTO")
+@Data
+@Accessors(chain = true)
+public class WmsPurchaseInboundReqDTO {
+
+ @Schema(description = "涓氬姟ID(閲囪喘鍏ュ簱鍗旾D)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "涓氬姟ID涓嶈兘涓虹┖")
+ private Long bizId;
+
+ @Schema(description = "涓氬姟鍗曞彿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "涓氬姟鍗曞彿涓嶈兘涓虹┖")
+ private String bizCode;
+
+ @Schema(description = "渚涘簲鍟咺D")
+ private Long vendorId;
+
+ @Schema(description = "鍏ュ簱鏃堕棿")
+ private LocalDateTime receiptTime;
+
+ @Schema(description = "鍏ュ簱鏄庣粏", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "鍏ュ簱鏄庣粏涓嶈兘涓虹┖")
+ private List<Item> items;
+
+ @Schema(description = "鍏ュ簱鏄庣粏椤�")
+ @Data
+ @Accessors(chain = true)
+ public static class Item {
+
+ @Schema(description = "鐗╂枡ID(MES鐗╂枡)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐗╂枡ID涓嶈兘涓虹┖")
+ private Long itemId;
+
+ @Schema(description = "浠撳簱ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "浠撳簱ID涓嶈兘涓虹┖")
+ private Long warehouseId;
+
+ @Schema(description = "搴撳尯ID")
+ private Long locationId;
+
+ @Schema(description = "搴撲綅ID")
+ private Long areaId;
+
+ @Schema(description = "鎵规ID")
+ private Long batchId;
+
+ @Schema(description = "鎵规缂栫爜")
+ private String batchCode;
+
+ @Schema(description = "鍏ュ簱鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鍏ュ簱鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal quantity;
+
+ @Schema(description = "鏄庣粏琛孖D")
+ private Long bizLineId;
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsSalesOutboundReqDTO.java b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsSalesOutboundReqDTO.java
new file mode 100644
index 0000000..ed7cbcb
--- /dev/null
+++ b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsSalesOutboundReqDTO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.wms.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 閿�鍞嚭搴撹姹� DTO
+ */
+@Schema(description = "閿�鍞嚭搴撹姹� DTO")
+@Data
+@Accessors(chain = true)
+public class WmsSalesOutboundReqDTO {
+
+ @Schema(description = "涓氬姟ID(閿�鍞嚭搴撳崟ID)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "涓氬姟ID涓嶈兘涓虹┖")
+ private Long bizId;
+
+ @Schema(description = "涓氬姟鍗曞彿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "涓氬姟鍗曞彿涓嶈兘涓虹┖")
+ private String bizCode;
+
+ @Schema(description = "瀹㈡埛ID")
+ private Long clientId;
+
+ @Schema(description = "鍑哄簱鏃堕棿")
+ private LocalDateTime outboundTime;
+
+ @Schema(description = "鍑哄簱鏄庣粏", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "鍑哄簱鏄庣粏涓嶈兘涓虹┖")
+ private List<Item> items;
+
+ @Schema(description = "鍑哄簱鏄庣粏椤�")
+ @Data
+ @Accessors(chain = true)
+ public static class Item {
+
+ @Schema(description = "鐗╂枡ID(MES鐗╂枡)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐗╂枡ID涓嶈兘涓虹┖")
+ private Long itemId;
+
+ @Schema(description = "浠撳簱ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "浠撳簱ID涓嶈兘涓虹┖")
+ private Long warehouseId;
+
+ @Schema(description = "搴撳尯ID")
+ private Long locationId;
+
+ @Schema(description = "搴撲綅ID")
+ private Long areaId;
+
+ @Schema(description = "鎵规ID")
+ private Long batchId;
+
+ @Schema(description = "鎵规缂栫爜")
+ private String batchCode;
+
+ @Schema(description = "鍑哄簱鏁伴噺(璐熸暟)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鍑哄簱鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal quantity;
+
+ @Schema(description = "鏄庣粏琛孖D")
+ private Long bizLineId;
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockDetailRespDTO.java b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockDetailRespDTO.java
new file mode 100644
index 0000000..e2b61e8
--- /dev/null
+++ b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockDetailRespDTO.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.wms.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 搴撳瓨鏄庣粏鍝嶅簲 DTO
+ */
+@Schema(description = "搴撳瓨鏄庣粏鍝嶅簲 DTO")
+@Data
+@Accessors(chain = true)
+public class WmsStockDetailRespDTO {
+
+ @Schema(description = "搴撳瓨ID")
+ private Long id;
+
+ @Schema(description = "鐗╂枡ID")
+ private Long itemId;
+
+ @Schema(description = "鐗╂枡缂栫爜")
+ private String itemCode;
+
+ @Schema(description = "鐗╂枡鍚嶇О")
+ private String itemName;
+
+ @Schema(description = "浠撳簱ID")
+ private Long warehouseId;
+
+ @Schema(description = "浠撳簱鍚嶇О")
+ private String warehouseName;
+
+ @Schema(description = "搴撳尯ID")
+ private Long locationId;
+
+ @Schema(description = "搴撳尯鍚嶇О")
+ private String locationName;
+
+ @Schema(description = "搴撲綅ID")
+ private Long areaId;
+
+ @Schema(description = "搴撲綅鍚嶇О")
+ private String areaName;
+
+ @Schema(description = "鎵规ID")
+ private Long batchId;
+
+ @Schema(description = "鎵规缂栫爜")
+ private String batchCode;
+
+ @Schema(description = "渚涘簲鍟咺D")
+ private Long vendorId;
+
+ @Schema(description = "渚涘簲鍟嗗悕绉�")
+ private String vendorName;
+
+ @Schema(description = "搴撳瓨鏁伴噺")
+ private BigDecimal quantity;
+
+ @Schema(description = "鍏ュ簱鏃堕棿")
+ private LocalDateTime receiptTime;
+
+ @Schema(description = "鏄惁鍐荤粨")
+ private Boolean frozen;
+}
\ No newline at end of file
diff --git a/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockQueryReqDTO.java b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockQueryReqDTO.java
new file mode 100644
index 0000000..35851b7
--- /dev/null
+++ b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockQueryReqDTO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.wms.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+/**
+ * 搴撳瓨鏌ヨ璇锋眰 DTO
+ */
+@Schema(description = "搴撳瓨鏌ヨ璇锋眰 DTO")
+@Data
+@Accessors(chain = true)
+public class WmsStockQueryReqDTO {
+
+ @Schema(description = "鐗╂枡ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐗╂枡ID涓嶈兘涓虹┖")
+ private Long itemId;
+
+ @Schema(description = "浠撳簱ID")
+ private Long warehouseId;
+
+ @Schema(description = "搴撳尯ID")
+ private Long locationId;
+
+ @Schema(description = "搴撲綅ID")
+ private Long areaId;
+
+ @Schema(description = "鎵规ID")
+ private Long batchId;
+
+ @Schema(description = "渚涘簲鍟咺D")
+ private Long vendorId;
+}
\ No newline at end of file
diff --git a/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java
new file mode 100644
index 0000000..80f2bad
--- /dev/null
+++ b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.wms.api.stock;
+
+import cn.iocoder.yudao.module.wms.api.dto.WmsPurchaseInboundReqDTO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsSalesOutboundReqDTO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsStockDetailRespDTO;
+import cn.iocoder.yudao.module.wms.api.dto.WmsStockQueryReqDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import jakarta.validation.Valid;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨 API 鎺ュ彛
+ *
+ * 鎻愪緵璺ㄦā鍧楀簱瀛樻搷浣滆兘鍔涳紝鍖呮嫭锛�
+ * - 閲囪喘鍏ュ簱/閲囪喘閫�璐у嚭搴擄紙ERP閲囪喘妯″潡璋冪敤锛�
+ * - 閿�鍞嚭搴�/閿�鍞��璐у叆搴擄紙ERP閿�鍞ā鍧楄皟鐢級
+ * - 搴撳瓨鏌ヨ锛堥�氱敤鏌ヨ鎺ュ彛锛�
+ */
+@Tag(name = "WMS 搴撳瓨 API")
+public interface WmsStockApi {
+
+ // ========== 閲囪喘鐩稿叧 ==========
+
+ @Operation(summary = "閲囪喘鍏ュ簱")
+ Long purchaseInbound(@Valid WmsPurchaseInboundReqDTO reqDTO);
+
+ @Operation(summary = "閲囪喘閫�璐у嚭搴�")
+ Long purchaseReturnOutbound(@Valid WmsPurchaseInboundReqDTO reqDTO);
+
+ // ========== 閿�鍞浉鍏� ==========
+
+ @Operation(summary = "閿�鍞嚭搴�")
+ Long salesOutbound(@Valid WmsSalesOutboundReqDTO reqDTO);
+
+ @Operation(summary = "閿�鍞��璐у叆搴�")
+ Long salesReturnInbound(@Valid WmsSalesOutboundReqDTO reqDTO);
+
+ // ========== 搴撳瓨鏌ヨ ==========
+
+ @Operation(summary = "鏌ヨ搴撳瓨鏁伴噺")
+ @Parameter(name = "itemId", description = "鐗╂枡ID", required = true)
+ @Parameter(name = "warehouseId", description = "浠撳簱ID", required = true)
+ BigDecimal getStockQuantity(Long itemId, Long warehouseId);
+
+ @Operation(summary = "鏌ヨ搴撳瓨鏄庣粏")
+ WmsStockDetailRespDTO getStockDetail(@Valid WmsStockQueryReqDTO reqDTO);
+
+ @Operation(summary = "鏌ヨ搴撳瓨鏄庣粏鍒楄〃")
+ List<WmsStockDetailRespDTO> getStockDetailList(@Valid WmsStockQueryReqDTO reqDTO);
+}
\ No newline at end of file
diff --git a/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionApi.java b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionApi.java
new file mode 100644
index 0000000..457e9af
--- /dev/null
+++ b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionApi.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.wms.api.transaction;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import jakarta.validation.Valid;
+import java.util.List;
+
+/**
+ * WMS 搴撳瓨浜嬪姟 API 鎺ュ彛
+ *
+ * 鎻愪緵璺ㄦā鍧楀簱瀛樹簨鍔℃搷浣滆兘鍔涳紝鏀寔锛�
+ * - 鍗曟潯浜嬪姟鍒涘缓
+ * - 鎵归噺浜嬪姟鍒涘缓
+ */
+@Tag(name = "WMS 搴撳瓨浜嬪姟 API")
+public interface WmsTransactionApi {
+
+ @Operation(summary = "鍒涘缓搴撳瓨浜嬪姟")
+ Long createTransaction(@Valid WmsTransactionCreateReqDTO reqDTO);
+
+ @Operation(summary = "鎵归噺鍒涘缓搴撳瓨浜嬪姟")
+ void createTransactionBatch(@Valid List<WmsTransactionCreateReqDTO> reqDTOs);
+}
\ No newline at end of file
diff --git a/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionCreateReqDTO.java b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionCreateReqDTO.java
new file mode 100644
index 0000000..fd10ce9
--- /dev/null
+++ b/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionCreateReqDTO.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.wms.api.transaction;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 搴撳瓨浜嬪姟鍒涘缓璇锋眰 DTO
+ */
+@Schema(description = "搴撳瓨浜嬪姟鍒涘缓璇锋眰 DTO")
+@Data
+@Accessors(chain = true)
+public class WmsTransactionCreateReqDTO {
+
+ @Schema(description = "浜嬪姟绫诲瀷(1-鍏ュ簱/2-鍑哄簱/3-璋冩嫧绉诲嚭/4-璋冩嫧绉诲叆/5-鐩樼泩/6-鐩樹簭)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "浜嬪姟绫诲瀷涓嶈兘涓虹┖")
+ private Integer type;
+
+ @Schema(description = "涓氬姟绫诲瀷(110-閲囪喘鍏ュ簱/118-閿�鍞嚭搴撶瓑)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "涓氬姟绫诲瀷涓嶈兘涓虹┖")
+ private Integer bizType;
+
+ @Schema(description = "涓氬姟ID")
+ private Long bizId;
+
+ @Schema(description = "涓氬姟鍗曞彿")
+ private String bizCode;
+
+ @Schema(description = "涓氬姟鏄庣粏琛孖D")
+ private Long bizLineId;
+
+ @Schema(description = "鐗╂枡ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐗╂枡ID涓嶈兘涓虹┖")
+ private Long itemId;
+
+ @Schema(description = "浠撳簱ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "浠撳簱ID涓嶈兘涓虹┖")
+ private Long warehouseId;
+
+ @Schema(description = "搴撳尯ID")
+ private Long locationId;
+
+ @Schema(description = "搴撲綅ID")
+ private Long areaId;
+
+ @Schema(description = "鎵规ID")
+ private Long batchId;
+
+ @Schema(description = "鎵规缂栫爜")
+ private String batchCode;
+
+ @Schema(description = "渚涘簲鍟咺D")
+ private Long vendorId;
+
+ @Schema(description = "浜嬪姟鏁伴噺(鍏ュ簱姝f暟/鍑哄簱璐熸暟)", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "浜嬪姟鏁伴噺涓嶈兘涓虹┖")
+ private BigDecimal quantity;
+
+ @Schema(description = "浜嬪姟鏃堕棿")
+ private LocalDateTime transactionTime;
+
+ @Schema(description = "鍏ュ簱鏃堕棿")
+ private LocalDateTime receiptTime;
+
+ @Schema(description = "鍏宠仈浜嬪姟ID")
+ private Long relatedTransactionId;
+
+ @Schema(description = "鏄惁鏍¢獙搴撳瓨鍏呰冻(榛樿true锛屽嚭搴撴椂鏍¢獙)")
+ private Boolean checkFlag;
+}
\ No newline at end of file
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index 52b4bfe..c422fd5 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -39,7 +39,7 @@
<version>${revision}</version>
</dependency>
- <!-- 鏁版嵁鎶ヨ〃銆傞粯璁ゆ敞閲婏紝淇濊瘉缂栬瘧閫熷害 -->
+ <!-- 鏁版嵁鎶ヨ〃銆傞粯璁ゆ敞閲婏紝绉湪鎶ヨ〃鏆備笉鏀寔 Spring Boot 4 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.boot</groupId>-->
<!-- <artifactId>yudao-module-report</artifactId>-->
@@ -143,6 +143,13 @@
<version>${revision}</version>
</dependency>
+ <!-- HRM 浜哄姏璧勬簮绠$悊妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-hrm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- spring boot 閰嶇疆鎵�闇�渚濊禆 -->
<dependency>
<groupId>org.springframework.boot</groupId>
--
Gitblit v1.9.3