liyong
2026-07-10 80ddf4c9c0bcb0f6c31524e0d9f5599c8001e904
Merge branch 'dev_business' of http://114.132.189.42:9002/r/mom-pro2-after into dev_business
已添加231个文件
已修改35个文件
22720 ■■■■■ 文件已修改
.claude/rules/sql-syntax.md 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/settings.local.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/hrm_attendance_frontend.md 604 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/hrm_delete_api.md 409 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/hrm_frontend.md 803 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/leave_deduction_frontend.md 573 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/salary_calculation.md 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/salary_deduction.md 721 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/wls_module_integration.md 730 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/wm_arrival_notice_purchase_order.md 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/wm_module_analysis.md 357 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/wm_module_architecture.md 440 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/erp_mes_warehouse_mapping.sql 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_attendance_supplement.sql 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_deduction_init.sql 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_employee_social_security_update.sql 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_employee_update.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_leave_type_config_init.sql 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_module_init.sql 594 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_salary_payment_detail_init.sql 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_wm_arrival_notice_purchase_order.sql 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_wm_sn_fix.sql 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/report_module_init.sql 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/wls_database_init.sql 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/wls_menu_init.sql 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderRespDTO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/ErpMesWarehouseMappingController.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingCreateReqVO.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingPageReqVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/mapping/vo/ErpMesWarehouseMappingUpdateReqVO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/mapping/ErpMesWarehouseMappingConvert.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/mapping/ErpMesWarehouseMappingDO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/mapping/ErpMesWarehouseMappingMapper.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingService.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMdmMappingServiceImpl.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingService.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/product/mapping/ErpMesWarehouseMappingServiceImpl.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/pom.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceClockTypeEnum.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceDataSourceEnum.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceExceptionTypeEnum.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAttendanceHolidayTypeEnum.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmAuditStatusEnum.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmDegreeEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeStatusEnum.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmLeaveTypeEnum.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmResignationTypeEnum.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmTransferTypeEnum.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/pom.xml 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationPageReqVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationPageReqVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationPageReqVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceHolidayController.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRuleController.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceSummaryController.java 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionPageReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayPageReqVO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordPageReqVO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRulePageReqVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleRespVO.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleSaveReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryRespVO.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEducationController.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeEmergencyContactController.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeWorkHistoryController.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationRespVO.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEducationSaveReqVO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactRespVO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeEmergencyContactSaveReqVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSimpleRespVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistoryRespVO.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeWorkHistorySaveReqVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecurityConfigController.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmTaxRateConfigController.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityPageReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecurityRespVO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmEmployeeSocialSecuritySaveReqVO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigRespVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigSaveReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationPageReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationSaveReqVO.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailPageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentDetailRespVO.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentPageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigRespVO.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecurityConfigSaveReqVO.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigRespVO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmTaxRateConfigSaveReqVO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRecordDO.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRuleDO.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceSummaryDO.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEducationDO.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeEmergencyContactDO.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeWorkHistoryDO.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSalaryDO.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmEmployeeSocialSecurityDO.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmLeaveTypeConfigDO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDetailDO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecurityConfigDO.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmTaxRateConfigDO.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmResignationApplicationMapper.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmTransferApplicationMapper.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceExceptionMapper.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRuleMapper.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceSummaryMapper.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSocialSecurityMapper.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmLeaveTypeConfigMapper.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryCalculationMapper.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentDetailMapper.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryPaymentMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecurityConfigMapper.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmTaxRateConfigMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmRedisKeyConstants.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java 314 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmLeaveApplicationStatusListener.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionService.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleService.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryService.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationService.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEducationServiceImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactService.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeEmergencyContactServiceImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java 275 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryService.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeWorkHistoryServiceImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionService.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmDeductionServiceImpl.java 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityService.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmEmployeeSocialSecurityServiceImpl.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java 580 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentService.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java 287 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigService.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigServiceImpl.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigService.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigServiceImpl.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsStockApiImpl.java 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsTransactionApiImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeSaveReqVO.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wms/approval/WmsApprovalController.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transfer/MesWmTransferDO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wms/approval/WmsWarehouseApprovalConfigDO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskResultMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wms/approval/WmsWarehouseApprovalConfigMapper.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/WmStatusTransitionUtils.java 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalService.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalServiceImpl.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserCreateReqDTO.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/pom.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsPurchaseInboundReqDTO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsSalesOutboundReqDTO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockDetailRespDTO.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockQueryReqDTO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionApi.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionCreateReqDTO.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/pom.xml 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/rules/sql-syntax.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
# SQL è¯­æ³•规范
## è§„则
- MySQL `ALTER TABLE ADD COLUMN` ä¸æ”¯æŒ `IF NOT EXISTS` è¯­æ³•,即使 MySQL 8.0+ ä¹Ÿä¸æ”¯æŒã€‚
- æ·»åŠ å­—æ®µå‰éœ€è¦å…ˆæ£€æŸ¥å­—æ®µæ˜¯å¦å­˜åœ¨ï¼Œæˆ–ç›´æŽ¥æ‰§è¡Œå¹¶å¿½ç•¥"字段已存在"错误。
## æ­£ç¡®ç¤ºä¾‹
```sql
-- æ­£ç¡®ï¼šç›´æŽ¥æ·»åŠ å­—æ®µ
ALTER TABLE mes_wm_sn ADD COLUMN uuid VARCHAR(64) COMMENT '批次UUID' AFTER id;
-- æ­£ç¡®ï¼šå…ˆæ£€æŸ¥å†æ·»åŠ ï¼ˆå…¼å®¹è„šæœ¬ï¼‰
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
-- é”™è¯¯ï¼šADD 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 | âœ… æ”¯æŒï¼ˆMySQL 8.0+) |
| ALTER TABLE ADD COLUMN IF NOT EXISTS | âŒ ä¸æ”¯æŒ |
| ALTER TABLE ADD INDEX IF NOT EXISTS | âœ… æ”¯æŒï¼ˆMySQL 8.0+) |
| ALTER TABLE ADD CONSTRAINT IF NOT EXISTS | âŒ ä¸æ”¯æŒ |
.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 *)",
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 |
**请求参数(create/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 |
**请求参数(create/update):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | å¦ | èŠ‚å‡æ—¥ID(修改时必填) |
| 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 |
**请求参数(generate):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| 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` å‚数。
**原接口:**
```js
POST /hrm/attendance/exception/submit?id=xxx
```
**新接口:**
```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 | æ­£å¸¸ |
| 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. **统计生成**:建议每月结束后手动触发生成统计,或配置定时任务自动生成
docs/hrm_delete_api.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,409 @@
# HRM æ¨¡å—删除接口补全
## æ¦‚è¿°
为 HRM æ¨¡å—中缺失删除接口的 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. åŒæ—¶å°†å…³è”的核算记录恢复为已确认(status=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) --删除--> (删除)
  æ³¨ï¼šéœ€å…ˆæ’¤é”€æ‰€æœ‰å·²å‘放明细才能删除
```
---
## äº”、已有删除接口说明
以下模块已有删除接口,无需前端额外修改:
| æ¨¡å— | åˆ é™¤æŽ¥å£è·¯å¾„ |
|------|-------------|
| å‘˜å·¥ä¸»è¡¨ | /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 |
| å‘˜å·¥ç¤¾ä¿æ¡£æ¡ˆ | /hrm/salary/employee-social-security/delete |
## å…­ã€ä¸æä¾›åˆ é™¤æŽ¥å£è¯´æ˜Ž
以下模块不提供删除接口(审计类数据不允许直接删除):
- è€ƒå‹¤è®°å½•(/hrm/attendance/record)- è€ƒå‹¤æ•°æ®å±žäºŽå®¡è®¡æ•°æ®
## ä¸ƒã€æƒé™è¯´æ˜Ž
| æƒé™æ ‡è¯† | è¯´æ˜Ž |
|----------|------|
| hrm:employee:delete | å‘˜å·¥åŠå­æ¨¡å—删除权限 |
| hrm:salary:confirm | è–ªé…¬æ ¸ç®—确认/撤销权限 |
| hrm:salary:delete | è–ªé…¬æ ¸ç®—/发放删除权限 |
| hrm:salary:pay | è–ªèµ„发放/撤销权限 |
## å…«ã€æ³¨æ„äº‹é¡¹
1. åˆ é™¤æ“ä½œéœ€è¦äºŒæ¬¡ç¡®è®¤ï¼Œé¿å…è¯¯åˆ 
2. è–ªé…¬æ ¸ç®—确认后不可直接删除,需先撤销确认
3. è–ªèµ„发放后不可直接删除台账,需先撤销所有已发放明细
4. æ’¤é”€æ“ä½œä¼šçº§è”更新关联数据(如发放明细、核算状态等)
5. åˆ é™¤æ“ä½œéœ€åˆ·æ–°åˆ—表数据
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 | èŽ·å–ç´§æ€¥è”ç³»äººåˆ—è¡¨ |
**注意:教育经历、工作经历、紧急联系人只在新增员工时同步添加,不提供单独维护接口。**
### 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,    // åŽŸéƒ¨é—¨ID(自动获取当前员工部门)
      currentPostId: undefined,    // åŽŸå²—ä½ID(自动获取当前员工岗位)
      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,     // äº¤æŽ¥äººID
      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": "正常",
        "clockOutType": 1,
        "clockOutTypeName": "正常",
        "workHours": 9.58,
        "overtimeHours": 0.50,
        "location": "公司总部",
        "dataSource": 1,
        "dataSourceName": "打卡"
      }
    ],
    "total": 100
  }
}
```
### 5.5 æ‰“卡类型
| ç±»åž‹å€¼ | ç±»åž‹å | é¢œè‰² |
|--------|--------|------|
| 1 | æ­£å¸¸ | success |
| 2 | è¿Ÿåˆ°/早退 | warning |
| 3 | ç¼ºå¡ | danger |
### 5.6 å¯¼å…¥æ¨¡æ¿
| åˆ—名 | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|
| å‘˜å·¥ç¼–号 | æ˜¯ | |
| å‘˜å·¥å§“名 | æ˜¯ | |
| è€ƒå‹¤æ—¥æœŸ | æ˜¯ | æ ¼å¼ï¼šyyyy-MM-dd |
| ä¸Šç­æ‰“卡时间 | å¦ | æ ¼å¼ï¼šHH:mm:ss |
| ä¸‹ç­æ‰“卡时间 | å¦ | æ ¼å¼ï¼šHH:mm:ss |
| æ‰“卡地点 | å¦ | |
| å¤‡æ³¨ | å¦ | |
---
## å…­ã€è–ªé…¬ç®¡ç†
### 6.1 æ¶‰åŠé¡µé¢
- è–ªé…¬ç»“构配置页 `hrm/salary/structure/index.vue`
- å‘˜å·¥è–ªé…¬æ¡£æ¡ˆé¡µ `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: '薪资发放' }
    }
  ]
}
```
---
## å…«ã€API æŽ¥å£æ–‡ä»¶
```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' })
}
```
---
## ä¹ã€BPM æµç¨‹é›†æˆ
### 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 | å®¡æ‰¹ä¸­ | å·²æäº¤BPM流程 |
| 20 | å®¡æ‰¹é€šè¿‡ | BPM流程审批通过 |
| 30 | å®¡æ‰¹é©³å›ž | BPM流程审批驳回 |
| 40 | å·²æ’¤é”€ | BPM流程被撤销 |
---
## åã€æ³¨æ„äº‹é¡¹
1. **数据权限**:列表查询会自动根据当前用户的部门权限过滤数据
2. **审批流程**:提交申请后,状态变为"审批中",需等待BPM流程完成
3. **年假校验**:请假类型为年假时,需校验年假余额是否充足
4. **考勤导入**:导入时会自动根据考勤规则判断打卡类型
5. **薪酬核算**:核算前需确保当月考勤数据已导入完成
6. **数据追溯**:所有操作都会记录操作日志,支持3个月数据追溯
docs/leave_deduction_frontend.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,573 @@
# è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½® & è–ªé…¬æ ¸ç®—联动 & è–ªèµ„发放流程
## æ¦‚è¿°
薪酬核算模块新增请假扣款功能,并实现薪酬核算到薪资发放的自动流转。
## æ¶‰åŠé¡µé¢
- è–ªé…¬ç®¡ç† -> è–ªé…¬è®¾ç½® -> è¯·å‡æ‰£æ¬¾é…ç½®
- è–ªé…¬ç®¡ç† -> è–ªé…¬æ ¸ç®— -> è–ªé…¬æ ¸ç®—预览
- è–ªé…¬ç®¡ç† -> è–ªèµ„发放 -> è–ªèµ„发放台账
- è–ªé…¬ç®¡ç† -> è–ªèµ„发放 -> è–ªèµ„发放明细
---
## ä¸€ã€è¯·å‡æ‰£æ¬¾é…ç½®
### 1. èŽ·å–è¯·å‡ç±»åž‹é…ç½®åˆ—è¡¨
**请求:**
```
GET /admin-api/hrm/leave-type-config/list
```
**响应:**
```json
{
  "code": 0,
  "data": [
    {
      "id": 1,
      "leaveType": 1,
      "name": "事假",
      "deductionRatio": 1.0000,
      "remark": "全额扣款",
      "status": 0
    },
    {
      "id": 2,
      "leaveType": 2,
      "name": "病假",
      "deductionRatio": 0.5000,
      "remark": "按50%扣款",
      "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 | æ‰£æ¬¾æ¯”例(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%扣款",
  "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 | è¯·å‡æ‰£æ¬¾é‡‘额 |
### 2. æ‰¹é‡ç¡®è®¤è–ªé…¬æ ¸ç®—
**请求:**
```
POST /admin-api/hrm/salary/calculation/confirm
```
**请求体:**
```json
{
  "ids": [1, 2, 3]
}
```
**响应:**
```json
{
  "code": 0,
  "data": true,
  "msg": "操作成功"
}
```
---
## ä¸‰ã€è¯·å‡æ‰£æ¬¾è®¡ç®—规则
### è®¡ç®—公式
```
请假扣款 = æ—¥å·¥èµ„ Ã— è¯·å‡å¤©æ•° Ã— æ‰£æ¬¾æ¯”例
日工资 = åŸºæœ¬å·¥èµ„ / 21.75
```
### æ‰£æ¬¾è§„则
| è¯·å‡ç±»åž‹ | é»˜è®¤æ‰£æ¬¾æ¯”例 | è¯´æ˜Ž |
|----------|-------------|------|
| äº‹å‡ | 100% | å…¨é¢æ‰£æ¬¾ |
| ç—…假 | 50% | æŒ‰æ¯”例扣款 |
| å¹´å‡ | 0% | ä¸æ‰£æ¬¾ |
| å©šå‡ | 0% | ä¸æ‰£æ¬¾ |
| äº§å‡ | 0% | ä¸æ‰£æ¬¾ |
| ä¸§å‡ | 0% | ä¸æ‰£æ¬¾ |
### è®¡ç®—示例
员工张三:
- åŸºæœ¬å·¥èµ„:10000元
- æ—¥å·¥èµ„:10000 / 21.75 = 460元
- è¯·å‡è®°å½•:事假2天,病假1天
计算:
- äº‹å‡æ‰£æ¬¾ï¼š460 Ã— 2 Ã— 100% = 920元
- ç—…假扣款:460 Ã— 1 Ã— 50% = 230元
- æ€»è¯·å‡æ‰£æ¬¾ï¼š920 + 230 = 1150元
---
## å››ã€æ•°æ®åº“变更
执行以下SQL脚本:
```sql
-- è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½®è¡¨
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 '扣款比例(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 '请假类型扣款配置';
-- åˆå§‹åŒ–默认数据
INSERT INTO hrm_leave_type_config (leave_type, name, deduction_ratio, remark, status) VALUES
(1, '事假', 1.0000, '全额扣款', 0),
(2, '病假', 0.5000, '按50%扣款', 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 '请假扣款' AFTER other_deduction;
```
---
## äº”、前端修改点
### 1. è¯·å‡æ‰£æ¬¾é…ç½®é¡µé¢
```html
<!-- é…ç½®åˆ—表表格 -->
<el-table :data="configList">
  <el-table-column prop="name" label="请假类型" />
  <el-table-column prop="deductionRatio" label="扣款比例">
    <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="编辑扣款比例">
  <el-form>
    <el-form-item label="扣款比例">
      <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="请假扣款" />
```
### 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. è¯·å‡æ‰£æ¬¾åªè®¡ç®—已审批通过的请假记录
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. å‘放薪资后不可撤销
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 | ç¤¾ä¿æ‰£é™¤ |
| housingFundDeduction | BigDecimal | å…¬ç§¯é‡‘扣除 |
| taxDeduction | BigDecimal | ä¸ªç¨Žæ‰£é™¤ |
| otherDeduction | BigDecimal | å…¶ä»–扣除 |
| 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. **获取薪酬档案**:通过 `selectLatestByUserId(userId)` èŽ·å–å‘˜å·¥æœ€æ–°ç”Ÿæ•ˆçš„è–ªé…¬æ¡£æ¡ˆ
3. **薪酬档案不存在时**:使用员工表的 `baseSalary` å­—段作为基本工资,其他薪资项目设为 0
4. **计算实发工资**:实发工资 = åŸºæœ¬å·¥èµ„ + ç»©æ•ˆå·¥èµ„ + å„项补贴 - å„项扣除
5. **防止重复核算**:同一员工同一周期只生成一条核算记录
6. **核算状态**:初始状态为待确认(0),确认后变为已确认(10)
## æ³¨æ„äº‹é¡¹
- è–ªé…¬æ ¸ç®—前需要确保员工有薪酬档案或员工表中有基本工资
- ç¤¾ä¿ã€å…¬ç§¯é‡‘、个税扣除目前设为 0,后续需要对接具体计算服务
- æ ¸ç®—周期格式为 YYYY-MM(如 2024-01)
- åŒä¸€å‘˜å·¥åŒä¸€å‘¨æœŸåªèƒ½æ ¸ç®—一次,重复调用会跳过已核算员工
docs/salary_deduction.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,721 @@
# ç¤¾ä¿å…¬ç§¯é‡‘个税扣除功能
## æ¶‰åŠé¡µé¢
- è–ªé…¬ç®¡ç† -> ç¤¾ä¿å…¬ç§¯é‡‘方案
- è–ªé…¬ç®¡ç† -> ä¸ªç¨Žç¨ŽçŽ‡é…ç½®
- è–ªé…¬ç®¡ç† -> è–ªé…¬æ ¸ç®—
- å‘˜å·¥ç®¡ç† -> å‘˜å·¥ç¤¾ä¿å…¬ç§¯é‡‘档案
## æ•°æ®åº“表
| è¡¨å | è¯´æ˜Ž |
|------|------|
| hrm_employee | å‘˜å·¥åŸºæœ¬ä¿¡æ¯è¡¨ |
| hrm_employee_salary | å‘˜å·¥è–ªé…¬æ¡£æ¡ˆè¡¨ |
| hrm_employee_social_security | å‘˜å·¥ç¤¾ä¿å…¬ç§¯é‡‘档案表(绑定方案 + ä¸ªäººåŒ–基数) |
| hrm_social_security_scheme | ç¤¾ä¿å…¬ç§¯é‡‘方案表(比例模板) |
| hrm_tax_rate_config | ä¸ªç¨Žç¨ŽçŽ‡é…ç½®è¡¨ï¼ˆç´¯è¿›ç¨ŽçŽ‡ï¼‰ |
**设计说明:**
- **社保公积金方案**:定义各项缴纳比例,可被多个员工复用
- **员工社保公积金档案**:员工绑定方案,设置个人化的缴纳基数
- ä¸€ä¸ªç¤¾ä¿æ–¹æ¡ˆå¯ä»¥ç»‘定多个员工,一个员工可以有多个社保档案(按生效日期取最新)
## è¡¨å…³è”关系
**重要说明:员工与系统用户通过手机号绑定,userId æ˜¯ç³»ç»Ÿç”¨æˆ·ID,不是员工ID。**
```
┌─────────────────────────────────────────────────────────────────────────┐
│                           è¡¨å…³è”关系图                                    â”‚
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         â”‚
│   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 (员工薪酬档案)                                     â”‚
│   â”œâ”€ userId â†’ å…³è”系统用户ID (system_users.id)                           â”‚
│   â”œâ”€ baseSalary (基本工资)                                               â”‚
│   â”œâ”€ performanceSalary (绩效工资)                                        â”‚
│   â””─ effectiveDate (生效日期)                                            â”‚
│                                                                         â”‚
│   hrm_employee_social_security (员工社保公积金档案)                       â”‚
│   â”œâ”€ userId â†’ å…³è”系统用户ID (system_users.id)                           â”‚
│   â”œâ”€ socialSecuritySchemeId â†’ å…³è”社保公积金方案ID                       â”‚
│   â”œâ”€ 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. è–ªé…¬æ¡£æ¡ˆã€ç¤¾ä¿æ¡£æ¡ˆã€æ ¸ç®—记录中的 `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`(通过档案的方案ID获取) | é»˜è®¤æ¯”例 |
| å…¬ç§¯é‡‘比例 | `hrm_social_security_scheme.housingFundPersonalRatio`(通过档案的方案ID获取) | 12% |
| ä¸ªç¨Žç¨Žçއ | `hrm_tax_rate_config` æŒ‰åº”纳税所得额匹配 | - |
**默认缴纳比例:**
| é™©ç§ | ä¸ªäººæ¯”例 |
|------|---------|
| å…»è€ä¿é™© | 8% |
| åŒ»ç–—保险 | 2% |
| å¤±ä¸šä¿é™© | 0.5% |
| å…¬ç§¯é‡‘ | 12% |
**userId ä½¿ç”¨åœºæ™¯ï¼š**
| åœºæ™¯ | ä½¿ç”¨æ–¹å¼ |
|------|---------|
| åˆ›å»ºè–ªé…¬æ¡£æ¡ˆ | userId = employee.getUserId() |
| åˆ›å»ºç¤¾ä¿æ¡£æ¡ˆ | userId = employee.getUserId() |
| è–ªé…¬æ ¸ç®—记录 | userId = employee.getUserId() |
| æŸ¥è¯¢è–ªé…¬æ¡£æ¡ˆ | selectLatestByUserId(userId) |
| æŸ¥è¯¢ç¤¾ä¿æ¡£æ¡ˆ | 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 | é€Ÿç®—扣除数 |
### å‘˜å·¥ç¤¾ä¿å…¬ç§¯é‡‘档案
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| 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 | æ¡£æ¡ˆID |
| userId | Long | ç³»ç»Ÿç”¨æˆ·ID(关联员工表) |
| userName | String | å‘˜å·¥å§“名(响应字段) |
| deptName | String | éƒ¨é—¨åç§°ï¼ˆå“åº”字段) |
| socialSecuritySchemeId | Long | ç¤¾ä¿å…¬ç§¯é‡‘方案ID |
| 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="速算扣除数" 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>
```
## æ‰£é™¤è®¡ç®—逻辑说明
### ç¤¾ä¿æ‰£é™¤ï¼ˆä¸ªäººéƒ¨åˆ†ï¼‰
每个员工使用其绑定方案中的缴纳比例计算:
```
社保扣除 = ç¤¾ä¿åŸºæ•° Ã— (养老保险个人比例 + åŒ»ç–—保险个人比例 + å¤±ä¸šä¿é™©ä¸ªäººæ¯”例)
```
- åŸºæ•°å–值:员工社保档案的 `socialSecurityBase`
- æ¯”例取值:通过 `socialSecuritySchemeId` èŽ·å–æ–¹æ¡ˆçš„æ¯”ä¾‹
- è‹¥æ–¹æ¡ˆä¸­æ¯”例为空,使用默认值:养老8%、医疗2%、失业0.5%
### å…¬ç§¯é‡‘扣除(个人部分)
每个员工使用其绑定方案中的缴纳比例计算:
```
公积金扣除 = å…¬ç§¯é‡‘基数 Ã— å…¬ç§¯é‡‘个人比例
```
- åŸºæ•°å–值:员工社保档案的 `housingFundBase`
- æ¯”例取值:通过 `socialSecuritySchemeId` èŽ·å–æ–¹æ¡ˆçš„æ¯”ä¾‹
- è‹¥æ–¹æ¡ˆä¸­æ¯”例为空,使用默认值:12%
### ä¸ªç¨Žæ‰£é™¤
```
应纳税所得额 = åº”发工资 - ç¤¾ä¿æ‰£é™¤ - å…¬ç§¯é‡‘扣除 - 5000(起征点)
个税 = åº”纳税所得额 Ã— ç¨Žçއ - é€Ÿç®—扣除数
```
- è‹¥åº”纳税所得额 â‰¤ 0,则个税为 0
- æ ¹æ®7级累进税率表查找对应档位
## ç¤ºä¾‹è®¡ç®—
假设员工:
- åŸºæœ¬å·¥èµ„:10000 å…ƒ
- ç»©æ•ˆå·¥èµ„:2000 å…ƒ
- ç¤¾ä¿åŸºæ•°ï¼š10000 å…ƒ
- å…¬ç§¯é‡‘基数:10000 å…ƒ
按 2024 å¹´æ ‡å‡†è®¡ç®—:
1. **应发工资** = 10000 + 2000 = 12000 å…ƒ
2. **社保扣除** = 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 å…ƒ/月
- å‘˜å·¥ç¤¾ä¿å…¬ç§¯é‡‘档案可在员工管理中维护,支持按生效日期管理历史记录
- è–ªé…¬æ ¸ç®—时会自动计算各项扣除,无需手动干预
docs/wls_module_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,730 @@
# ä»“储物流模块前端文档
## ä¸€ã€åŠŸèƒ½æ¦‚è¿°
将MES模块的仓库管理提取为独立的一级菜单"仓储物流",并实现:
- ERP采购入库审核 â†’ MES库存同步增加
- ERP销售出库审核 â†’ MES库存同步减少
- ä»“库级别审批流程配置(每个仓库可独立启用/禁用审批)
---
## äºŒã€SQL脚本执行顺序
```bash
# 1. èœå•调整(将仓库管理提取为一级菜单)
sql/mysql/wls_menu_init.sql
# 2. æ•°æ®åº“初始化(审批配置表 + å•据BPM字段)
sql/mysql/wls_database_init.sql
# 3. ERP仓库与MES仓库映射表
sql/mysql/erp_mes_warehouse_mapping.sql
```
---
## ä¸‰ã€èœå•结构
### 3.1 æ–°èœå•结构
```
仓储物流(一级菜单,id=5780)
├── ä»“库设置
├── åº“存现有量
├── é‡‡è´­å…¥åº“
├── åˆ°è´§é€šçŸ¥
├── é‡‡è´­é€€è´§
├── ç”Ÿäº§é¢†æ–™
├── ç”Ÿäº§é€€æ–™
├── äº§å“å…¥åº“
├── å‘货通知
├── é”€å”®å‡ºåº“
├── é”€å”®é€€è´§
├── è½¬ç§»è°ƒæ‹¨
├── åº“存盘点
├── è£…箱管理
├── æ¡ç é…ç½®
├── èµ‹èƒ½ç®¡ç†
├── SN码
├── å¤–协发料
├── å¤–协入库
├── æ‚项入库
├── æ‚项出库
├── å®¡æ‰¹é…ç½®ï¼ˆæ–°å¢žï¼‰
└── ä»“库映射(新增,在ERP菜单下)
```
### 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`(放在ERP仓库管理下)
**功能**:
1. é…ç½®ERP仓库与MES仓库的映射关系
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>
```
---
## äº”、API接口
### 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
}
```
---
## å…­ã€API文件创建
### 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产品与MDM物料
通过条码(barCode)关联:
- ERP产品的barCode字段值 = MDM物料的barCode字段值
- ç³»ç»Ÿè‡ªåŠ¨é€šè¿‡barCode匹配,无需额外配置表
### 7.2 ERP仓库与MES仓库
需要在仓库映射页面配置映射关系:
| ERP仓库 | MES仓库 | è¯´æ˜Ž |
|---------|---------|------|
| ä¸»ä»“库(ID=1) | æ€»ä»“(ID=100) | é»˜è®¤æ˜ å°„ |
**注意**:ERP仓库必须配置映射后,ERP采购入库/销售出库审核才能同步MES库存。
---
## å…«ã€ä¸šåŠ¡æµç¨‹
### 8.1 é‡‡è´­å…¥åº“流程(ERP â†’ 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. **映射配置**:ERP仓库必须先配置映射才能同步MES库存
3. **权限前缀**:
   - å®¡æ‰¹é…ç½®ï¼š`mes:wm-warehouse:query`、`mes:wm-warehouse:update`
   - ä»“库映射:`erp:warehouse:query`、`erp:warehouse:update`、`erp:warehouse:delete`
4. **审批流程**:需在BPM管理后台创建"仓库审核(wms_approve)"分类下的流程
5. **物料条码**:MDM物料必须填写barCode,且与ERP产品barCode一致
---
## åã€æµ‹è¯•验证
### 10.1 åŠŸèƒ½æµ‹è¯•
1. **菜单验证**:登录系统,确认"仓储物流"作为一级菜单显示
2. **映射配置**:在ERP仓库管理下,配置ERP仓库与MES仓库映射
3. **采购入库**:
   - ERP创建采购入库单 â†’ å®¡æ ¸
   - æŸ¥è¯¢MES库存现有量 â†’ ç¡®è®¤åº“存增加
4. **销售出库**:
   - ERP创建销售出库单 â†’ å®¡æ ¸
   - æŸ¥è¯¢MES库存现有量 â†’ ç¡®è®¤åº“存减少
5. **审批流程**:
   - é…ç½®ä»“库入库审批 â†’ å¯ç”¨
   - åˆ›å»ºMES入库单 â†’ æå®¡æ‰¹ â†’ å®¡æ‰¹é€šè¿‡ â†’ çŠ¶æ€å˜æ›´
### 10.2 å¼‚常场景
1. æœªé…ç½®ä»“库映射 â†’ ERP审核时报错提示"仓库映射不存在"
2. ç‰©æ–™æ¡ç ä¸åŒ¹é… â†’ ERP审核时报错提示"物料不存在"
3. åº“存不足 â†’ é”€å”®å‡ºåº“时报错提示"库存不足"
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` | æ›´æ–°åˆ°è´§é€šçŸ¥å• |
**请求参数变更(create/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;
```
## æ³¨æ„ï¼šmes_wm_item_receipt è¡¨é—®é¢˜
`mes_wm_item_receipt` è¡¨åœ¨æ•°æ®åº“中是 **物料退库单**(关联生产工单),但代码中注释写的是"采购入库单"。
这是一个代码与数据库不一致的问题,需要后续单独修复。本次修改不影响该表。
docs/wm_module_analysis.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,357 @@
# ä»“储物流模块业务分析报告
## ä¸€ã€å‘现的问题与漏洞
### 1. å¹¶å‘安全问题 ðŸ”´ é«˜é£Žé™©
**问题描述**:
库存更新操作 `updateQuantity` ä½¿ç”¨äº†æ•°æ®åº“层面的 CAS(Compare And Set)防负库存,但缺乏分布式锁保护。
**风险场景**:
```
场景:同一库存记录被两个出库单同时操作
时间线:
T1: çº¿ç¨‹A读取库存 quantity=100
T2: çº¿ç¨‹B读取库存 quantity=100
T3: çº¿ç¨‹A执行出库50,quantity=50
T4: çº¿ç¨‹B执行出库60,quantity=-10(CAS会拦截)
```
**当前实现**(MesWmMaterialStockMapper.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可以防止负库存,但无法防止超卖(两个线程同时检查库存充足,然后同时扣减)
- ç¼ºå°‘乐观锁版本号机制
**建议修复**:
```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)`,但实际代码中 `vendorId` ä¹Ÿå‚与了组合查询。
**风险**:
- åŒä¸€ç‰©æ–™åŒä¸€åº“位,不同供应商的入库会创建两条库存记录
- å¯èƒ½å¯¼è‡´åº“存记录膨胀,查询效率下降
**代码证据**(MesWmMaterialStockServiceImpl.java:141-146):
```java
MesWmMaterialStockDO existStock = materialStockMapper.selectByCompositeKey(
        itemId, warehouseId, locationId, areaId, batchId);
// æ³¨æ„ï¼švendorId æœªå‚与唯一键查询
```
**建议**:
- æ˜Žç¡®ä¸šåŠ¡éœ€æ±‚ï¼šæ˜¯å¦éœ€è¦æŒ‰ä¾›åº”å•†åŒºåˆ†åº“å­˜ï¼Ÿ
- å¦‚果需要,建议在唯一键中增加 vendorId
- å¦‚果不需要,入库时不应创建新的库存记录
---
### 2. å•据取消后的库存处理 âœ… å·²ç¡®è®¤è®¾è®¡æ­£ç¡®
**设计决策**:
- å·²å®ŒæˆçŠ¶æ€ï¼ˆFINISHED)的单据**不允许取消**
- åªèƒ½åœ¨è‰ç¨¿ã€å¾…审批、待执行等未完成状态取消
- å¦‚需回退已完成的库存操作,应创建对应的冲销单据(如退货单)
**代码证据**:
```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. ç¼ºå°‘事务回滚机制 ðŸŸ¡ ä¸­é£Žé™©
**问题描述**:
批量创建事务时,如果中途失败,已执行的事务不会回滚。
**代码证据**(MesWmTransactionServiceImpl.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. ç›˜ç‚¹å·®å¼‚处理逻辑未实现 ðŸŸ¡ ä¸­é£Žé™©
**问题描述**:
文档中提到"盘盈+库存,盘亏-库存",但代码中未找到差异处理的具体实现。
**风险**:
- ç›˜ç‚¹ç»“果可能无法正确影响库存
- ç›˜ç‚¹æµç¨‹ä¸å®Œæ•´
---
### 7. ç¼ºå°‘安全库存预警 ðŸŸ¢ ä½Žé£Žé™©
**问题描述**:
系统缺少安全库存下限预警功能。
**建议**:
- åœ¨ç‰©æ–™ä¸»æ•°æ®ä¸­å¢žåŠ  `safetyStock`(安全库存)字段
- å‡ºåº“时检查是否低于安全库存,触发预警
---
### 8. ç¼ºå°‘库存有效期管理 ðŸŸ¢ ä½Žé£Žé™©
**问题描述**:
批次管理中没有有效期字段,无法进行临期/过期预警。
**建议**:
- åœ¨æ‰¹æ¬¡è¡¨ä¸­å¢žåŠ  `expireDate`(有效期)字段
- å¢žåŠ ä¸´æœŸé¢„è­¦åŠŸèƒ½
---
## äºŒã€ä¸šåŠ¡æµç¨‹æ¼æ´ž
### 2.1 é‡‡è´­å…¥åº“流程
| çŽ¯èŠ‚ | å½“前实现 | æ½œåœ¨é—®é¢˜ |
|------|---------|---------|
| æäº¤ | æ ¡éªŒè‡³å°‘有一行 | âœ… æ­£å¸¸ |
| ä¸Šæž¶ | æ ¡éªŒæ˜Žç»†æ•°é‡=行数量 | âœ… æ­£å¸¸ |
| å…¥åº“ | åˆ›å»ºäº‹åŠ¡æµæ°´ | âœ… æ­£å¸¸ |
| å–消 | åªæ›´æ–°çŠ¶æ€ | âš ï¸ å¾…上架状态取消后,已上架的明细如何处理? |
### 2.2 é”€å”®å‡ºåº“流程
| çŽ¯èŠ‚ | å½“前实现 | æ½œåœ¨é—®é¢˜ |
|------|---------|---------|
| æäº¤ | æ£€æŸ¥OQC需求 | âœ… æ­£å¸¸ |
| æ‹£è´§ | æ ¡éªŒæ¯è¡Œæœ‰æ˜Žç»† | âš ï¸ æœªæ ¡éªŒåº“存充足 |
| å‡ºåº“ | åˆ›å»ºäº‹åŠ¡æµæ°´ | âœ… æ­£å¸¸ |
| å–消 | åªæ›´æ–°çŠ¶æ€ | âš ï¸ å·²æ‹£è´§çš„库存如何处理? |
**建议**:拣货时应冻结库存,防止被其他单据抢占。
### 2.3 è°ƒæ‹¨æµç¨‹
| çŽ¯èŠ‚ | å½“前实现 | æ½œåœ¨é—®é¢˜ |
|------|---------|---------|
| æäº¤ | é…é€æ¨¡å¼å†»ç»“源库存 | âœ… æ­£å¸¸ |
| ä¸Šæž¶ | æ ¡éªŒæ˜Žç»†æ•°é‡ | âœ… æ­£å¸¸ |
| æ‰§è¡Œ | åˆ›å»ºå‡ºåº“+入库事务 | âœ… æ­£å¸¸ |
| å–消 | è§£é™¤å†»ç»“ | âœ… æ­£å¸¸ |
**评价**:调拨流程设计较为完善。
---
## ä¸‰ã€å·²å®Œæˆçš„优化项
### 1. åº“存唯一键设计 âœ… å·²ä¼˜åŒ–
**优化内容**:
- `vendorId` çŽ°åœ¨å‚ä¸Žåº“å­˜å”¯ä¸€é”®
- åŒä¸€ç‰©æ–™åŒä¸€åº“位,不同供应商的库存分开管理
- ä¿®æ”¹äº† `selectByCompositeKey` æ–¹æ³•,增加 vendorId å‚æ•°
**修改文件**:
- `MesWmMaterialStockMapper.java`
- `MesWmMaterialStockServiceImpl.java`
### 2. çŠ¶æ€æµè½¬æ ¡éªŒ âœ… å·²ä¼˜åŒ–
**优化内容**:
- æ–°å¢ž `WmStatusTransitionUtils` çŠ¶æ€æµè½¬æ ¡éªŒå·¥å…·ç±»
- å®šä¹‰äº†å„业务单据的合法状态流转规则
- é˜²æ­¢éžæ³•状态跳转
**新增文件**:
- `WmStatusTransitionUtils.java`
**使用示例**:
```java
WmStatusTransitionUtils.validateTransition("ITEM_RECEIPT",
        receipt.getStatus(), MesWmItemReceiptStatusEnum.APPROVING.getStatus(), "采购入库单");
```
### 3. æ‹£è´§åº“存校验 âœ… å·²ä¼˜åŒ–
**优化内容**:
- é”€å”®å‡ºåº“拣货时校验库存充足
- è°ƒç”¨ `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 ç¼ºå°‘级联删除控制
**问题**:
- åˆ é™¤ä»“库时,未检查是否存在库存记录
- åˆ é™¤ç‰©æ–™æ—¶ï¼Œæœªæ£€æŸ¥æ˜¯å¦å­˜åœ¨åº“存记录
**建议**:
- åœ¨åˆ é™¤å‰å¢žåŠ å¼•ç”¨æ£€æŸ¥
- æˆ–使用软删除,保留历史数据
---
## å››ã€æ”¹è¿›å»ºè®®æ±‡æ€»
### é«˜ä¼˜å…ˆçº§ ðŸ”´
| åºå· | é—®é¢˜ | è§£å†³æ–¹æ¡ˆ |
|------|------|---------|
| 1 | å¹¶å‘安全 | å¢žåŠ ä¹è§‚é”ç‰ˆæœ¬å·æˆ–åˆ†å¸ƒå¼é” |
| 2 | å·²å®Œæˆå•据取消 | æ˜Žç¡®ä¸šåŠ¡è§„åˆ™ï¼Œç¦æ­¢æˆ–åˆ›å»ºå†²é”€äº‹åŠ¡ |
### ä¸­ä¼˜å…ˆçº§ ðŸŸ¡
| åºå· | é—®é¢˜ | è§£å†³æ–¹æ¡ˆ |
|------|------|---------|
| 3 | å”¯ä¸€é”®ä¸æ˜Žç¡® | æ˜Žç¡® vendorId æ˜¯å¦å‚与唯一键 |
| 4 | çŠ¶æ€æœºæ ¡éªŒç¼ºå¤± | å¢žåŠ çŠ¶æ€æµè½¬æ ¡éªŒ |
| 5 | ç›˜ç‚¹å·®å¼‚处理 | è¡¥å……差异处理逻辑 |
| 6 | æ‹£è´§åº“存校验 | æ‹£è´§æ—¶æ ¡éªŒå¹¶å†»ç»“库存 |
### ä½Žä¼˜å…ˆçº§ ðŸŸ¢
| åºå· | é—®é¢˜ | è§£å†³æ–¹æ¡ˆ |
|------|------|---------|
| 7 | å®‰å…¨åº“存预警 | å¢žåŠ å®‰å…¨åº“å­˜å­—æ®µå’Œé¢„è­¦åŠŸèƒ½ |
| 8 | æœ‰æ•ˆæœŸç®¡ç† | æ‰¹æ¬¡å¢žåŠ æœ‰æ•ˆæœŸå­—æ®µ |
---
## äº”、架构优化建议
### 5.1 å¢žåŠ åº“å­˜é¢„å æœºåˆ¶
```
销售出库单创建 â†’ é¢„占库存(预留量)
        â†“
    æ‰§è¡Œå‡ºåº“ â†’ æ‰£å‡é¢„留量 + æ‰£å‡å®žç‰©åº“å­˜
        â†“
    å–消 â†’ é‡Šæ”¾é¢„留量
```
### 5.2 å¢žåŠ åº“å­˜æ“ä½œæ—¥å¿—
- è®°å½•每次库存变更的操作人、操作时间、操作原因
- ä¾¿äºŽé—®é¢˜è¿½æº¯å’Œå®¡è®¡
### 5.3 å¢žåŠ åº“å­˜å¿«ç…§åŠŸèƒ½
- æ¯æ—¥å®šæ—¶ç”Ÿæˆåº“存快照
- æ”¯æŒåŽ†å²åº“å­˜æŸ¥è¯¢å’Œå¯¹æ¯”åˆ†æž
docs/wm_module_architecture.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,440 @@
# ä»“储物流模块(WM)架构文档
## ä¸€ã€æ¨¡å—概述
仓储物流模块(WM)是MES系统中的核心模块,负责管理物料库存、出入库业务、仓库设置等功能。
### 1.1 æ ¸å¿ƒè®¾è®¡ç†å¿µ
- **六维库存模型**:物料 + æ‰¹æ¬¡ + ä»“库 + åº“区 + åº“位 + ä¾›åº”商
- **事务驱动**:所有库存变动通过事务流水记录,保证数据可追溯
- **状态机管理**:单据状态流转严格控制,保证业务完整性
---
## äºŒã€æ ¸å¿ƒæ•°æ®æ¨¡åž‹
### 2.1 åº“存台账(mes_wm_material_stock)
库存现有量表,记录每个物料在每个库位的实时库存。
```
┌─────────────────────────────────────────────────────────────┐
│                     mes_wm_material_stock                    â”‚
├─────────────────────────────────────────────────────────────┤
│  ç‰©æ–™ç»´åº¦ï¼šitemId(物料) + batchId(批次) + batchCode(批次号)  â”‚
│  ä½ç½®ç»´åº¦ï¼šwarehouseId(仓库) + locationId(库区) + areaId(库位)│
│  ä¸šåŠ¡ç»´åº¦ï¼švendorId(供应商) + receiptTime(入库时间)           â”‚
│  æ•°é‡çŠ¶æ€ï¼šquantity(数量) + frozen(是否冻结)                  â”‚
└─────────────────────────────────────────────────────────────┘
```
**唯一键**:`(itemId, warehouseId, locationId, areaId, batchId)`
### 2.2 åº“存事务流水(mes_wm_transaction)
记录每一笔库存增减事件,系统自动生成,只读查询。
```
┌─────────────────────────────────────────────────────────────┐
│                    mes_wm_transaction                        â”‚
├─────────────────────────────────────────────────────────────┤
│  ä¸šåŠ¡å½’å±žï¼štype(事务类型) + bizType(业务类型)                 â”‚
│           bizId(业务单ID) + bizCode(业务单号) + bizLineId(行ID)│
│  ç‰©æ–™ä¿¡æ¯ï¼šitemId + quantity(变动数量,正负) + batchId        â”‚
│  ä½ç½®ä¿¡æ¯ï¼šwarehouseId + locationId + areaId                 â”‚
│  æ—¶é—´ä¿¡æ¯ï¼štransactionTime + receiptTime                     â”‚
│  å…³è”信息:materialStockId + relatedTransactionId            â”‚
└─────────────────────────────────────────────────────────────┘
```
**quantity è§„则**:
- æ­£æ•° = å…¥åº“(库存增加)
- è´Ÿæ•° = å‡ºåº“(库存减少)
### 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
              â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
              â”‚                â”‚                â”‚
              â†“ (需要OQC)      â”‚                â†“ (不需要OQC)
        â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”         â”‚          â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
        â”‚  å¾…检测   â”‚         â”‚          â”‚  å¾…拣货   â”‚
        â”‚(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:业务逻辑、状态流转                              â”‚
│  è¡ŒService:单据行管理                                       â”‚
│  æ˜Žç»†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仓库→MES仓库)
                              â”‚
                              â†“
                        æŸ¥æ‰¾ç‰©æ–™æ˜ å°„(ERP产品barCode→MDM物料barCode)
                              â”‚
                              â†“
                        åˆ›å»ºMES库存事务
                              â”‚
                              â†“
                        MES库存增加
```
### 7.2 ç”Ÿäº§æ¨¡å—集成
```
生产工单 â”€â”€å®Œå·¥æ±‡æŠ¥â”€â”€â†’ åˆ›å»ºç”Ÿäº§å…¥åº“单
                              â”‚
                              â†“
                        ç”Ÿäº§å…¥åº“单审核通过
                              â”‚
                              â†“
                        MES库存增加(产品入库)
```
### 7.3 è´¨æ£€æ¨¡å—集成
```
销售出库单 â”€â”€æäº¤â”€â”€â†’ æ£€æŸ¥æ˜¯å¦éœ€è¦OQC检验
        â”‚                  â”‚
        â”‚                  â†“ (需要)
        â”‚            åˆ›å»ºOQC检验单
        â”‚                  â”‚
        â”‚                  â†“ OQC通过
        â”‚            é”€å”®å‡ºåº“单状态流转
        â”‚                  â”‚
        â†“ (不需要)         â†“
   ç›´æŽ¥å¾…拣货  â†â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”˜
```
---
## å…«ã€å…³é”®ä¸šåŠ¡è§„åˆ™
### 8.1 åº“存校验规则
- å‡ºåº“时必须校验库存是否充足
- æ”¯æŒå†»ç»“库存(调拨配送模式下冻结源库存)
- åº“位混放规则校验(可配置是否允许物料/批次混放)
### 8.2 æ‰¹æ¬¡ç®¡ç†
- å…¥åº“时自动生成或指定批次号
- æ‰¹æ¬¡ä¿¡æ¯è·Ÿéšåº“存流转
- æ”¯æŒæ‰¹æ¬¡è¿½æº¯
### 8.3 æ¡ç /SN码管理
- å…¥åº“时自动生成条码记录
- SN码支持批量生成(按批次UUID管理)
- æ¡ç ä¸Žåº“存记录关联
---
## ä¹ã€æ•°æ®åº“表清单
### 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 | ç›˜ç‚¹ç»“æžœ |
---
## åã€æŽ¥å£æƒé™å‰ç¼€
| æ¨¡å— | æƒé™å‰ç¼€ |
|------|---------|
| ä»“库设置 | 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      // ç”Ÿäº§å…¥åº“
    ...
```
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>
sql/mysql/erp_mes_warehouse_mapping.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
-- =============================================
-- ERP仓库与MES仓库映射表
-- =============================================
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仓库与MES仓库映射表';
-- =============================================
-- è¯´æ˜Žï¼š
-- 1. æ­¤è¡¨ç”¨äºŽå»ºç«‹ERP仓库与MES仓库的映射关系
-- 2. ä¸€ä¸ªERP仓库只能映射到一个MES仓库
-- 3. æ˜ å°„建立后,ERP采购入库/销售出库审核时会自动更新对应的MES库存
-- =============================================
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 '节假日ID',
    `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 '汇总ID',
    `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;
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 '速算扣除数',
    `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 '档案ID',
    `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;
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 '档案ID',
    `user_id` bigint NOT NULL COMMENT '系统用户ID',
    -- ç»‘定的社保公积金方案ID
    `social_security_scheme_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`),
    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;
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列表字段(JSON格式存储)
ALTER TABLE `hrm_employee` ADD COLUMN `role_ids` varchar(500) DEFAULT NULL COMMENT '角色ID列表(JSON格式)' AFTER `base_salary`;
-- æ‰§è¡Œå®Œæˆæç¤º
SELECT 'HRM å‘˜å·¥è¡¨ç»“构变更完成!' AS result;
sql/mysql/hrm_leave_type_config_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
-- è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½®è¡¨
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 '扣款比例(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 '请假类型扣款配置';
-- åˆå§‹åŒ–默认数据
INSERT INTO hrm_leave_type_config (leave_type, name, deduction_ratio, remark, status) VALUES
(1, '事假', 1.0000, '全额扣款', 0),
(2, '病假', 0.5000, '按50%扣款', 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 '请假扣款' AFTER other_deduction;
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 '申请人ID',
    `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 '申请人ID',
    `current_dept_id` bigint NOT NULL COMMENT '原部门ID',
    `current_post_id` bigint NOT NULL COMMENT '原岗位ID',
    `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 '申请人ID',
    `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 '交接人ID',
    `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-正常,2-迟到,3-缺卡',
    `clock_out_type` tinyint DEFAULT NULL COMMENT '下班打卡类型:1-正常,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 '申请人ID',
    `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 å‘˜å·¥è–ªé…¬æ¡£æ¡ˆè¡¨
CREATE TABLE IF NOT EXISTS `hrm_employee_salary` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '档案ID',
    `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员工薪酬档案表';
-- 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 '社保扣除',
    `housing_fund_deduction` decimal(12,2) DEFAULT NULL COMMENT '公积金扣除',
    `tax_deduction` decimal(12,2) DEFAULT NULL COMMENT '个税扣除',
    `other_deduction` decimal(12,2) DEFAULT 0 COMMENT '其他扣除',
    `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 '确认人ID',
    `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;
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 '薪资发放明细';
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;
-- æ•°æ®è¿ç§»ï¼šå°†åŽŸæœ‰çš„é‡‡è´­è®¢å•å·ï¼ˆpurchase_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 != '';
sql/mysql/mes_wm_sn_fix.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
-- MES SN码表结构修复(MySQL 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;
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 '报表内容(JSON é…ç½®ï¼‰',
    `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 ä»ªè¡¨ç›˜è®¾è®¡å™¨ï¼ˆJimuBI)
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;
sql/mysql/wls_database_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
-- =============================================
-- ä»“储物流模块数据库初始化脚本
-- åŒ…含:仓库审批配置表、单据BPM字段扩展、BPM分类
-- =============================================
-- 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    - ç›˜ç‚¹ä¸šåŠ¡ï¼ˆé™æ€ç›˜ç‚¹ã€åŠ¨æ€ç›˜ç‚¹ï¼‰
-- =============================================
sql/mysql/wls_menu_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
-- =============================================
-- ä»“储物流菜单调整脚本
-- å°†MES仓库管理(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);
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);
}
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;
}
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 ç›¸å…³ -->
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));
    }
}
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仓库与MES仓库映射 Controller
 */
@Tag(name = "管理后台 - ERP仓库与MES仓库映射")
@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));
    }
}
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仓库与MES仓库映射创建 Request VO
 */
@Schema(description = "管理后台 - ERP仓库与MES仓库映射创建 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;
}
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仓库与MES仓库映射分页 Request VO
 */
@Schema(description = "管理后台 - ERP仓库与MES仓库映射分页 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;
}
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仓库与MES仓库映射更新 Request VO
 */
@Schema(description = "管理后台 - ERP仓库与MES仓库映射更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ErpMesWarehouseMappingUpdateReqVO extends ErpMesWarehouseMappingCreateReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "编号不能为空")
    private Long id;
}
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仓库与MES仓库映射 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;
    }
}
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仓库与MES仓库映射 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;
}
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仓库与MES仓库映射 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));
    }
}
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));
    }
}
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, "该ERP仓库已存在映射配置");
}
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);
}
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产品不存在或条码为空,erpProductId={}", 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物料,barCode={}", 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] å»ºè®®é…ç½®ä»“库映射表替代名称匹配,erpWarehouseName={}", erpWarehouseName);
        return null;
    }
}
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仓库与MES仓库映射 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);
}
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仓库与MES仓库映射 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);
    }
}
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 åº“存(MES åº“存)
        if (approve) {
            syncToWmsPurchaseInbound(purchaseIn, purchaseInItems);
        }
    }
    /**
     * åŒæ­¥é‡‡è´­å…¥åº“到 WMS åº“å­˜
     */
    private void syncToWmsPurchaseInbound(ErpPurchaseInDO purchaseIn, List<ErpPurchaseInItemDO> purchaseInItems) {
        if (CollUtil.isEmpty(purchaseInItems)) {
            return;
        }
        log.info("[syncToWmsPurchaseInbound] å¼€å§‹åŒæ­¥é‡‡è´­å…¥åº“到WMS,purchaseInId={}", 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] æœªæ‰¾åˆ°MDM物料映射,erpProductId={}", item.getProductId());
                return null;
            }
            if (mesWarehouseId == null) {
                log.warn("[syncToWmsPurchaseInbound] æœªæ‰¾åˆ°MES仓库映射,erpWarehouseId={}", 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] æ— æœ‰æ•ˆçš„入库明细,跳过WMS同步");
            return;
        }
        reqDTO.setItems(items);
        try {
            Long transactionId = wmsStockApi.purchaseInbound(reqDTO);
            log.info("[syncToWmsPurchaseInbound] åŒæ­¥æˆåŠŸï¼ŒtransactionId={}", transactionId);
        } catch (Exception e) {
            log.error("[syncToWmsPurchaseInbound] åŒæ­¥å¤±è´¥", e);
            // æ³¨æ„ï¼šè¿™é‡Œå¯ä»¥é€‰æ‹©æŠ›å‡ºå¼‚常回滚,或仅记录日志
            // throw exception(PURCHASE_IN_SYNC_WMS_FAIL);
        }
    }
    @Override
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);
}
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);
    }
}
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 åº“存(MES åº“存)
        if (approve) {
            syncToWmsSalesOutbound(saleOut, saleOutItems);
        }
    }
    /**
     * åŒæ­¥é”€å”®å‡ºåº“到 WMS åº“å­˜
     */
    private void syncToWmsSalesOutbound(ErpSaleOutDO saleOut, List<ErpSaleOutItemDO> saleOutItems) {
        if (CollUtil.isEmpty(saleOutItems)) {
            return;
        }
        log.info("[syncToWmsSalesOutbound] å¼€å§‹åŒæ­¥é”€å”®å‡ºåº“到WMS,saleOutId={}", 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] æœªæ‰¾åˆ°MDM物料映射,erpProductId={}", item.getProductId());
                return null;
            }
            if (mesWarehouseId == null) {
                log.warn("[syncToWmsSalesOutbound] æœªæ‰¾åˆ°MES仓库映射,erpWarehouseId={}", 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] æ— æœ‰æ•ˆçš„出库明细,跳过WMS同步");
            return;
        }
        reqDTO.setItems(items);
        try {
            Long transactionId = wmsStockApi.salesOutbound(reqDTO);
            log.info("[syncToWmsSalesOutbound] åŒæ­¥æˆåŠŸï¼ŒtransactionId={}", transactionId);
        } catch (Exception e) {
            log.error("[syncToWmsSalesOutbound] åŒæ­¥å¤±è´¥", e);
        }
    }
    @Override
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>
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, "员工薪酬档案不存在");
    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, "请假类型扣款配置不存在");
    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, "删除失败,原因:存在已发放明细,请先撤销");
}
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, "正常"),
    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;
    }
}
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;
    }
}
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;
    }
}
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;
    }
}
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("");
    }
}
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;
    }
}
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;
    }
}
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("");
    }
}
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("");
    }
}
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("");
    }
}
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 æ¨¡å—下,人力资源管理(Human 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>
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);
    }
}
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);
    }
}
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);
    }
}
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 = "申请人ID", 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;
}
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 = "申请人ID", 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;
}
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 = "申请人ID", 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;
}
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 = "申请人ID", 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;
}
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 = "申请人ID", 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 = "交接人ID", 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;
}
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 = "申请人ID", 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 = "交接人ID", 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;
}
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 = "申请人ID", example = "1")
    private Long userId;
    @Schema(description = "原部门ID", 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;
}
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 = "申请人ID", example = "1")
    private Long userId;
    @Schema(description = "申请人姓名", example = "张三")
    private String userName;
    @Schema(description = "原部门ID", example = "1")
    private Long currentDeptId;
    @Schema(description = "原部门名称", example = "研发部")
    private String currentDeptName;
    @Schema(description = "原岗位ID", 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;
}
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 = "申请人ID", example = "1")
    private Long userId;
    @Schema(description = "原部门ID", example = "1")
    private Long currentDeptId;
    @Schema(description = "原岗位ID", 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;
}
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 æ˜¯å‘˜å·¥ID)
        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 æ˜¯å‘˜å·¥ID)
     */
    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);
    }
}
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 = "节假日ID", 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 = "节假日ID", 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);
    }
}
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 æ˜¯å‘˜å·¥ID,不是系统用户ID
     */
    private void fillUserInfo(List<HrmAttendanceRecordRespVO> respList, List<HrmAttendanceRecordDO> recordList) {
        // æ‰¹é‡èŽ·å–å‘˜å·¥ä¿¡æ¯ï¼ˆuserId æ˜¯å‘˜å·¥ID)
        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 æ˜¯å‘˜å·¥ID)
        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);
    }
}
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);
    }
}
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);
    }
}
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 = "申请人ID", 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;
}
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 = "申请人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(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;
}
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;
}
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;
}
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 = "节假日ID")
    @ExcelProperty("节假日ID")
    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;
}
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 = "节假日ID(修改时必填)", 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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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 = "汇总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 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;
}
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. æ”¶é›†æ‰€æœ‰è§’色ID
        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());
            }
            // è§£æž 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);
    }
}
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);
    }
}
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);
    }
}
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);
    }
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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 = "档案ID", 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 = "档案ID", 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 æ˜¯å‘˜å·¥ID,批量获取员工信息
        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 æ˜¯å‘˜å·¥ID)
                    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 æ˜¯å‘˜å·¥ID,通过员工表获取姓名和部门
        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());
            }
        }
    }
}
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 è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½®")
@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);
    }
}
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 æ˜¯å‘˜å·¥ID)
     */
    private void fillUserInfo(List<HrmSalaryCalculationRespVO> respList, List<HrmSalaryCalculationDO> calculationList) {
        // userId æ˜¯å‘˜å·¥ID,批量获取员工信息
        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 æ˜¯å‘˜å·¥ID)
        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);
    }
}
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());
            }
        }
    }
}
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));
    }
}
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));
    }
}
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));
    }
}
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;
}
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 = "档案ID", 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 = "社保公积金方案ID", 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;
}
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 = "档案ID", 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 = "社保公积金方案ID", 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;
}
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 = "管理后台 - è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½® 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 = "扣款比例(0-1之间)")
    private BigDecimal deductionRatio;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "状态:0-启用, 1-禁用")
    private Integer status;
}
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 = "管理后台 - è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½®æ›´æ–° Request VO")
@Data
public class HrmLeaveTypeConfigSaveReqVO {
    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "主键不能为空")
    private Long id;
    @Schema(description = "扣款比例(0-1之间)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "扣款比例不能为空")
    private BigDecimal deductionRatio;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "状态:0-启用, 1-禁用")
    private Integer status;
}
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;
}
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 = "社保扣除")
    @ExcelProperty("社保扣除")
    private BigDecimal socialSecurityDeduction;
    @Schema(description = "公积金扣除")
    @ExcelProperty("公积金扣除")
    private BigDecimal housingFundDeduction;
    @Schema(description = "个税扣除")
    @ExcelProperty("个税扣除")
    private BigDecimal taxDeduction;
    @Schema(description = "其他扣除")
    @ExcelProperty("其他扣除")
    private BigDecimal otherDeduction;
    @Schema(description = "请假扣款")
    @ExcelProperty("请假扣款")
    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;
}
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 = "社保扣除")
        private BigDecimal socialSecurityDeduction;
        @Schema(description = "公积金扣除")
        private BigDecimal housingFundDeduction;
        @Schema(description = "个税扣除")
        private BigDecimal taxDeduction;
        @Schema(description = "其他扣除")
        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;
    }
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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 = "速算扣除数", 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;
}
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 = "速算扣除数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    @NotNull(message = "速算扣除数不能为空")
    private BigDecimal quickDeduction;
    @Schema(description = "状态:0-启用,1-禁用", example = "0")
    private Integer status;
    @Schema(description = "备注", example = "不超过36000元")
    private String remark;
}
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;
    /**
     * ç”³è¯·äººID
     *
     * å…³è” 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;
}
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;
    /**
     * ç”³è¯·äººID
     *
     * å…³è” 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;
    /**
     * äº¤æŽ¥äººID
     *
     * å…³è” 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;
}
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;
    /**
     * ç”³è¯·äººID
     *
     * å…³è” AdminUserDO#getId()
     */
    private Long userId;
    /**
     * åŽŸéƒ¨é—¨ID
     *
     * å…³è” DeptDO#getId()
     */
    private Long currentDeptId;
    /**
     * åŽŸå²—ä½ID
     *
     * å…³è” 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;
}
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;
    /**
     * ç”³è¯·äººID(员工ID)
     *
     * å…³è” 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;
}
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 {
    /**
     * èŠ‚å‡æ—¥ID
     */
    @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;
}
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-正常,2-迟到,3-缺卡
     */
    private Integer clockInType;
    /**
     * ä¸‹ç­æ‰“卡类型:1-正常,2-早退,3-缺卡
     */
    private Integer clockOutType;
    /**
     * å·¥ä½œæ—¶é•¿ï¼ˆå°æ—¶ï¼‰
     */
    private BigDecimal workHours;
    /**
     * åŠ ç­æ—¶é•¿ï¼ˆå°æ—¶ï¼‰
     */
    private BigDecimal overtimeHours;
    /**
     * æ‰“卡地点
     */
    private String location;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * æ•°æ®æ¥æºï¼š1-打卡,2-导入
     */
    private Integer dataSource;
}
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;
}
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 {
    /**
     * æ±‡æ€»ID
     */
    @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;
}
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列表
     *
     * ç”¨äºŽåˆ†é…ç³»ç»Ÿç”¨æˆ·è§’色,JSON æ ¼å¼å­˜å‚¨
     */
    private String roleIds;
    /**
     * å¼€æˆ·é“¶è¡Œ
     */
    private String bankName;
    /**
     * é“¶è¡Œè´¦å·
     */
    private String bankAccount;
    /**
     * å±…住地址
     */
    private String address;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
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;
}
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;
}
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;
}
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;
/**
 * å‘˜å·¥è–ªé…¬æ¡£æ¡ˆ 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 {
    /**
     * æ¡£æ¡ˆID
     */
    @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;
}
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
 *
 * å‘˜å·¥ç»‘定社保公积金方案,并设置个人化的缴纳基数
 * ä¸€ä¸ªç¤¾ä¿æ–¹æ¡ˆå¯ä»¥ç»‘定多个员工,一个员工可以有多个社保档案(按生效日期,取最新生效的)
 *
 * @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 {
    /**
     * æ¡£æ¡ˆID
     */
    @TableId
    private Long id;
    /**
     * å‘˜å·¥ID
     *
     * å…³è” HrmEmployeeDO#getId()
     */
    private Long userId;
    /**
     * ç¤¾ä¿å…¬ç§¯é‡‘方案ID
     *
     * å…³è” HrmSocialSecuritySchemeDO#getId()
     */
    private Long socialSecuritySchemeId;
    // ========== ç¼´çº³åŸºæ•°ï¼ˆå‘˜å·¥ä¸ªæ€§åŒ–设置) ==========
    /**
     * ç¤¾ä¿ç¼´çº³åŸºæ•°
     */
    private BigDecimal socialSecurityBase;
    /**
     * å…¬ç§¯é‡‘缴纳基数
     */
    private BigDecimal housingFundBase;
    // ========== ç”Ÿæ•ˆä¿¡æ¯ ==========
    /**
     * ç”Ÿæ•ˆæ—¥æœŸ
     */
    private LocalDate effectiveDate;
    /**
     * çŠ¶æ€ï¼š0-启用,1-禁用
     */
    private Integer status;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
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;
/**
 * è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½® 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;
    /**
     * æ‰£æ¬¾æ¯”例(0-1之间,如0.5表示扣50%)
     */
    private BigDecimal deductionRatio;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * çŠ¶æ€ï¼š0-启用, 1-禁用
     */
    private Integer status;
}
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;
    /**
     * ç¤¾ä¿æ‰£é™¤
     */
    private BigDecimal socialSecurityDeduction;
    /**
     * å…¬ç§¯é‡‘扣除
     */
    private BigDecimal housingFundDeduction;
    /**
     * ä¸ªç¨Žæ‰£é™¤
     */
    private BigDecimal taxDeduction;
    /**
     * å…¶ä»–扣除
     */
    private BigDecimal otherDeduction;
    /**
     * è¯·å‡æ‰£æ¬¾
     */
    private BigDecimal leaveDeduction;
    /**
     * å®žå‘工资
     */
    private BigDecimal actualSalary;
    /**
     * å‡ºå‹¤å¤©æ•°
     */
    private Integer workDays;
    /**
     * åŠ ç­æ—¶é•¿
     */
    private BigDecimal overtimeHours;
    /**
     * çŠ¶æ€ï¼š0-待确认,10-已确认,20-已发放
     */
    private Integer status;
    /**
     * ç¡®è®¤æ—¶é—´
     */
    private LocalDateTime confirmTime;
    /**
     * ç¡®è®¤äººID
     */
    private Long confirmUserId;
    /**
     * å‘放时间
     */
    private LocalDateTime paymentTime;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
    /**
     * é€Ÿç®—扣除数
     */
    private BigDecimal quickDeduction;
    /**
     * çŠ¶æ€ï¼š0-启用,1-禁用
     */
    private Integer status;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
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(系统用户ID)
     * @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));
    }
}
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));
    }
}
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);
    }
}
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);
    }
}
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));
    }
}
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));
    }
}
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));
    }
}
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));
    }
}
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);
    }
}
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));
    }
}
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));
    }
}
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);
    }
}
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;
/**
 * å‘˜å·¥è–ªé…¬æ¡£æ¡ˆ 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));
    }
}
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));
    }
}
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;
/**
 * è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½® 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);
    }
}
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));
    }
}
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);
    }
}
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"));
    }
}
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));
    }
}
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"));
    }
}
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));
    }
}
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));
    }
}
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位自增
     * ä¾‹å¦‚:EMP202401010001
     *
     * @return å‘˜å·¥ç¼–号
     */
    public String generateEmployeeNo() {
        return generate(EMPLOYEE_NO_PREFIX, 4);
    }
    /**
     * ç”Ÿæˆè¯·å‡ç”³è¯·ç¼–号,格式:LA + yyyyMMdd + 4位自增
     * ä¾‹å¦‚:LA202401010001
     *
     * @return è¯·å‡ç”³è¯·ç¼–号
     */
    public String generateLeaveApplicationNo() {
        return generate(LEAVE_APPLICATION_NO_PREFIX, 4);
    }
    /**
     * ç”Ÿæˆè°ƒå²—申请编号,格式:TA + yyyyMMdd + 4位自增
     * ä¾‹å¦‚:TA202401010001
     *
     * @return è°ƒå²—申请编号
     */
    public String generateTransferApplicationNo() {
        return generate(TRANSFER_APPLICATION_NO_PREFIX, 4);
    }
    /**
     * ç”Ÿæˆç¦»èŒç”³è¯·ç¼–号,格式:RA + yyyyMMdd + 4位自增
     * ä¾‹å¦‚:RA202401010001
     *
     * @return ç¦»èŒç”³è¯·ç¼–号
     */
    public String generateResignationApplicationNo() {
        return generate(RESIGNATION_APPLICATION_NO_PREFIX, 4);
    }
    /**
     * ç”Ÿæˆè–ªé…¬æ ¸ç®—编号,格式:SC + yyyyMMdd + 4位自增
     * ä¾‹å¦‚:SC202401010001
     *
     * @return è–ªé…¬æ ¸ç®—编号
     */
    public String generateSalaryCalculationNo() {
        return generate(SALARY_CALCULATION_NO_PREFIX, 4);
    }
    /**
     * ç”Ÿæˆè–ªèµ„发放编号,格式:SF + yyyyMMdd + 4位自增
     * ä¾‹å¦‚:SF202401010001
     *
     * @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);
    }
}
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:";
}
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 æäº¤äººID
     */
    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);
}
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);
    }
}
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 æäº¤äººID
     */
    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);
}
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);
    }
}
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 æäº¤äººID
     */
    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);
}
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);
    }
}
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);
    }
}
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);
    }
}
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);
    }
}
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 æäº¤äººID
     */
    void submitAttendanceException(Long id, String processDefinitionKey, Long userId);
    /**
     * æ›´æ–°è€ƒå‹¤å¼‚常申请审批状态
     *
     * @param id ç”³è¯·ID
     * @param auditStatus å®¡æ‰¹çŠ¶æ€
     */
    void updateAttendanceExceptionAuditStatus(Long id, Integer auditStatus);
}
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());
    }
}
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 èŠ‚å‡æ—¥ID
     */
    Long createAttendanceHoliday(@Valid HrmAttendanceHolidaySaveReqVO createReqVO);
    /**
     * æ›´æ–°èŠ‚å‡æ—¥é…ç½®
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updateAttendanceHoliday(@Valid HrmAttendanceHolidaySaveReqVO updateReqVO);
    /**
     * åˆ é™¤èŠ‚å‡æ—¥é…ç½®
     *
     * @param id èŠ‚å‡æ—¥ID
     */
    void deleteAttendanceHoliday(Long id);
    /**
     * èŽ·å¾—èŠ‚å‡æ—¥é…ç½®
     *
     * @param id èŠ‚å‡æ—¥ID
     * @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);
}
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;
    }
}
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);
}
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);
    }
}
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获取考勤规则
     * ä¼˜å…ˆåŒ¹é…éƒ¨é—¨è§„则,其次查找全局规则(deptId=null)
     *
     * @param deptId éƒ¨é—¨ID
     * @return è€ƒå‹¤è§„则
     */
    HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId);
}
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. æŸ¥æ‰¾å…¨å±€è§„则(deptId ä¸º 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;
    }
}
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);
}
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. è§£æžå¹´æœˆï¼ŒèŽ·å–è¯¥æœˆèµ·å§‹æ—¥æœŸ
        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());
            }
            // ç»Ÿè®¡å®žé™…出勤天数(有正常打卡记录)
            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);
    }
}
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);
        }
    }
}
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);
}
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);
    }
}
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);
}
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);
    }
}
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);
}
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);
        // è§£æž 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);
    }
}
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);
}
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);
    }
}
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;
/**
 * è–ªé…¬æ‰£é™¤è®¡ç®— Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface HrmDeductionService {
    /**
     * è®¡ç®—社保扣除(个人部分)
     *
     * @param socialSecurityBase ç¤¾ä¿åŸºæ•°
     * @param effectiveDate ç”Ÿæ•ˆæ—¥æœŸ
     * @return ç¤¾ä¿æ‰£é™¤é‡‘额
     */
    BigDecimal calculateSocialSecurityDeduction(BigDecimal socialSecurityBase, LocalDate effectiveDate);
    /**
     * è®¡ç®—公积金扣除(个人部分)
     *
     * @param housingFundBase å…¬ç§¯é‡‘基数
     * @param effectiveDate ç”Ÿæ•ˆæ—¥æœŸ
     * @return å…¬ç§¯é‡‘扣除金额
     */
    BigDecimal calculateHousingFundDeduction(BigDecimal housingFundBase, LocalDate effectiveDate);
    /**
     * è®¡ç®—个税扣除
     *
     * @param taxableIncome åº”纳税所得额(月收入 - ç¤¾ä¿å…¬ç§¯é‡‘ - ä¸“项扣除 - 5000起征点)
     * @param effectiveDate ç”Ÿæ•ˆæ—¥æœŸ
     * @return ä¸ªç¨Žæ‰£é™¤é‡‘额
     */
    BigDecimal calculateTaxDeduction(BigDecimal taxableIncome, LocalDate effectiveDate);
    /**
     * èŽ·å–æœ€æ–°çš„ç¤¾ä¿å…¬ç§¯é‡‘é…ç½®
     *
     * @param effectiveDate ç”Ÿæ•ˆæ—¥æœŸ
     * @return ç¤¾ä¿å…¬ç§¯é‡‘配置
     */
    HrmSocialSecurityConfigDO getLatestSocialSecurityConfig(LocalDate effectiveDate);
}
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;
/**
 * è–ªé…¬æ‰£é™¤è®¡ç®— Service å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
public class HrmDeductionServiceImpl implements HrmDeductionService {
    /**
     * ä¸ªç¨Žèµ·å¾ç‚¹ï¼ˆæ¯æœˆ5000元)
     */
    private static final BigDecimal TAX_THRESHOLD = new BigDecimal("5000");
    /**
     * é»˜è®¤ç¼´çº³æ¯”例(当员工档案中没有配置时使用)
     */
    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);
    }
    /**
     * æ ¹æ®å‘˜å·¥ç¤¾ä¿æ¡£æ¡ˆé…ç½®è®¡ç®—社保扣除(个人部分)
     *
     * @param socialSecurityBase ç¤¾ä¿åŸºæ•°
     * @param pensionPersonalRatio å…»è€ä¿é™©ä¸ªäººç¼´çº³æ¯”例
     * @param medicalPersonalRatio åŒ»ç–—保险个人缴纳比例
     * @param unemploymentPersonalRatio å¤±ä¸šä¿é™©ä¸ªäººç¼´çº³æ¯”例
     * @return ç¤¾ä¿æ‰£é™¤é‡‘额
     */
    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);
    }
    /**
     * æ ¹æ®å‘˜å·¥ç¤¾ä¿æ¡£æ¡ˆé…ç½®è®¡ç®—公积金扣除(个人部分)
     *
     * @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();
            // åˆ¤æ–­æ˜¯å¦åœ¨è¯¥æ¡£ä½èŒƒå›´å†…(maxAmount为0表示无上限)
            boolean inRange = taxableIncome.compareTo(minAmount) >= 0
                    && (maxAmount.compareTo(BigDecimal.ZERO) == 0 || taxableIncome.compareTo(maxAmount) < 0);
            if (inRange) {
                // è®¡ç®—个税 = åº”纳税所得额 * ç¨Žçއ - é€Ÿç®—扣除数
                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);
    }
}
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);
}
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);
        }
    }
}
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;
/**
 * è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½® Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface HrmLeaveTypeConfigService {
    /**
     * èŽ·å–æ‰€æœ‰è¯·å‡ç±»åž‹é…ç½®åˆ—è¡¨
     */
    List<HrmLeaveTypeConfigDO> getLeaveTypeConfigList();
    /**
     * èŽ·å–è¯·å‡ç±»åž‹é…ç½®
     */
    HrmLeaveTypeConfigDO getLeaveTypeConfig(Integer leaveType);
    /**
     * æ›´æ–°è¯·å‡ç±»åž‹é…ç½®
     */
    void updateLeaveTypeConfig(HrmLeaveTypeConfigDO config);
}
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;
/**
 * è¯·å‡ç±»åž‹æ‰£æ¬¾é…ç½® 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);
        }
    }
}
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);
}
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");
    /**
     * é»˜è®¤ç¼´çº³æ¯”例(当员工社保档案中没有配置或方案中没有配置时使用)
     */
    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) {
        // è§£æžæ ¸ç®—周期的日期
        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 æ˜¯å‘˜å·¥ID,用于关联薪酬档案、社保档案、核算记录
            Long employeeId = employee.getId();
            // 2.1 æ£€æŸ¥è¯¥å‘˜å·¥å½“月是否已存在核算记录(按员工ID查询)
            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 èŽ·å–å‘˜å·¥è–ªé…¬æ¡£æ¡ˆï¼ˆæŒ‰å‘˜å·¥ID查询)
            HrmEmployeeSalaryDO employeeSalary = employeeSalaryMapper.selectLatestByUserId(employeeId);
            if (employeeSalary == null) {
                // å¦‚果没有薪酬档案,使用员工表中的基本工资创建默认数据
                employeeSalary = buildDefaultEmployeeSalary(employeeId, employee);
            }
            // 2.3 èŽ·å–å‘˜å·¥ç¤¾ä¿å…¬ç§¯é‡‘æ¡£æ¡ˆï¼ˆæŒ‰å‘˜å·¥ID查询,取最新生效的)
            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);
        }
    }
    /**
     * è§£æžæ ¸ç®—周期为日期(如 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();
    }
    /**
     * æž„建薪酬核算记录(包含社保、公积金、个税、请假扣除计算)
     * é€šè¿‡å‘˜å·¥ç¤¾ä¿æ¡£æ¡ˆç»‘定的方案获取缴纳比例
     */
    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;
        // èŽ·å–å…¬ç§¯é‡‘åŸºæ•°ï¼ˆå‘˜å·¥æ¡£æ¡ˆä¸­çš„ä¸ªæ€§åŒ–åŸºæ•°ï¼‰
        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;
        // ç¤¾ä¿æ‰£é™¤ = åŸºæ•° Ã— (养老个人比例 + åŒ»ç–—个人比例 + å¤±ä¸šä¸ªäººæ¯”例)
        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);
        // è®¡ç®—请假扣款
        BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary);
        // è®¡ç®—个税扣除
        // åº”纳税所得额 = åº”发工资 - ç¤¾ä¿æ‰£é™¤ - å…¬ç§¯é‡‘扣除 - èµ·å¾ç‚¹(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;
        // è®¡ç®—实发工资 = åº”发 - æ‰£é™¤
        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();
    }
    /**
     * è®¡ç®—请假扣款
     *
     * @param employee   å‘˜å·¥ä¿¡æ¯
     * @param period     æ ¸ç®—周期(如2026-02)
     * @param baseSalary åŸºæœ¬å·¥èµ„
     * @return è¯·å‡æ‰£æ¬¾é‡‘额
     */
    private BigDecimal calculateLeaveDeduction(HrmEmployeeDO employee, String period, BigDecimal baseSalary) {
        // è§£æžå‘¨æœŸèŽ·å–æœˆä»½å¼€å§‹å’Œç»“æŸæ—¶é—´
        YearMonth yearMonth = YearMonth.parse(period);
        LocalDateTime monthStart = yearMonth.atDay(1).atStartOfDay();
        LocalDateTime monthEnd = yearMonth.atEndOfMonth().atTime(23, 59, 59);
        // æŸ¥è¯¢å‘˜å·¥åœ¨è¯¥æœˆå·²å®¡æ‰¹é€šè¿‡çš„请假记录
        // æ³¨æ„ï¼šè¯·å‡ç”³è¯·çš„ userId æ˜¯ç³»ç»Ÿç”¨æˆ·ID,需要通过员工表获取
        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);
        // ç´¯è®¡è¯·å‡æ‰£æ¬¾
        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);
            // è¯·å‡æ‰£æ¬¾ = æ—¥å·¥èµ„ Ã— è¯·å‡å¤©æ•° Ã— æ‰£æ¬¾æ¯”例
            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) {
        // è§£æžæ ¸ç®—周期的日期
        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 èŽ·å–å‘˜å·¥è–ªé…¬æ¡£æ¡ˆï¼ˆæŒ‰å‘˜å·¥ID查询)
            HrmEmployeeSalaryDO employeeSalary = employeeSalaryMapper.selectLatestByUserId(employeeId);
            if (employeeSalary == null) {
                // å¦‚果没有薪酬档案,使用员工表中的基本工资创建默认数据
                employeeSalary = buildDefaultEmployeeSalary(employeeId, employee);
            }
            // 2.2 èŽ·å–å‘˜å·¥ç¤¾ä¿å…¬ç§¯é‡‘æ¡£æ¡ˆï¼ˆæŒ‰å‘˜å·¥ID查询,取最新生效的)
            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);
    }
}
在上述文件截断后对比
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentService.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigService.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecurityConfigServiceImpl.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigService.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmTaxRateConfigServiceImpl.java yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java yudao-module-mes/pom.xml yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsStockApiImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/WmsTransactionApiImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeRespVO.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/vo/MesWmArrivalNoticeSaveReqVO.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wms/approval/WmsApprovalController.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeDO.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transfer/MesWmTransferDO.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wms/approval/WmsWarehouseApprovalConfigDO.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskResultMapper.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wms/approval/WmsWarehouseApprovalConfigMapper.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/WmStatusTransitionUtils.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultService.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskResultServiceImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferService.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalService.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/WmsApprovalServiceImpl.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserCreateReqDTO.java yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java yudao-module-wms-api/pom.xml yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsPurchaseInboundReqDTO.java yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsSalesOutboundReqDTO.java yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockDetailRespDTO.java yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/dto/WmsStockQueryReqDTO.java yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/stock/WmsStockApi.java yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionApi.java yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/transaction/WmsTransactionCreateReqDTO.java yudao-server/pom.xml