refactor: 单据审核改为审批配置状态机(新增:audit接口/审核人字段/DB变更脚本)
已删除20个文件
已添加18个文件
已修改134个文件
7526 ■■■■■ 文件已修改
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/CrmCancellationController.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationAuditReqVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationSaveReqVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractAuditReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/CrmInvoiceController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceAuditReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceRespVO.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/CrmRefundController.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundAuditReqVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundSaveReqVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/cancellation/CrmCancellationDO.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/invoice/CrmInvoiceDO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/refund/CrmRefundDO.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/cancellation/CrmCancellationMapper.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/refund/CrmRefundMapper.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationService.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/listener/CrmCancellationStatusListener.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractStatusListener.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceService.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/listener/CrmInvoiceStatusListener.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundService.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/listener/CrmRefundStatusListener.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentAuditReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestAuditReqVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentDO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentService.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/listener/ErpFinancePaymentStatusListener.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmRegularizeApplicationController.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationAuditReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationAuditReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationAuditReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationAuditReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionAuditReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseAuditReqVO.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmRegularizeApplicationDO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmRegularizeApplicationMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmResignationApplicationMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmTransferApplicationMapper.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceExceptionMapper.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationService.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationServiceImpl.java 174 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java 170 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java 162 ●●●● 补丁 | 查看 | 原始文档 | 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/HrmRegularizeApplicationStatusListener.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionService.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java 190 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/listener/HrmPerformanceAppraiseStatusListener.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectAuditReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/ProductTechConfirmController.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmAuditReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmRespVO.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmSaveReqVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptAuditReqVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/MesWmItemReceiptAuditReqVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/MesWmItemReceiptRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/MesWmMiscIssueController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/MesWmMiscIssueAuditReqVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/MesWmMiscIssueRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/vo/MesWmMiscReceiptAuditReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/vo/MesWmMiscReceiptRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorAuditReqVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskAuditReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/techconfirm/ProductTechConfirmDO.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptDO.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/itemreceipt/MesWmItemReceiptDO.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/miscissue/MesWmMiscIssueDO.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/miscreceipt/MesWmMiscReceiptDO.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/task/MesWmStockTakingTaskDO.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/project/MesPdProjectMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/techconfirm/ProductTechConfirmMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptMapper.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/miscissue/MesWmMiscIssueMapper.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/miscreceipt/MesWmMiscReceiptMapper.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/returnvendor/MesWmReturnVendorMapper.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskMapper.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdTechConfirmStatusEnum.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscIssueStatusEnum.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscReceiptStatusEnum.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockTakingTaskStatusEnum.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/listener/MesPdProjectStatusListener.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmService.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptService.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptServiceImpl.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/listener/MesWmEquipmentReceiptStatusListener.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java 139 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/listener/MesWmItemReceiptStatusListener.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueService.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/listener/MesWmMiscIssueStatusListener.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptService.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptServiceImpl.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/listener/MesWmMiscReceiptStatusListener.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorService.java 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/listener/MesWmReturnVendorStatusListener.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java 160 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/listener/MesWmStockTakingTaskResultStatusListener.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/CrmCancellationController.java
@@ -21,12 +21,11 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - CRM é”€æˆ·ç”³è¯·")
@RestController
@@ -52,28 +51,28 @@
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审批销户申请(通过/驳回)")
    @PreAuthorize("@ss.hasPermission('crm:cancellation:update')")
    public CommonResult<Boolean> auditCancellation(@Valid @RequestBody CrmCancellationAuditReqVO auditReqVO) {
        cancellationService.auditCancellation(auditReqVO);
        return success(true);
    }
    @PutMapping("/submit")
    @Operation(summary = "提交销户申请审批")
    @Operation(summary = "提交销户申请审批(草稿/审核不通过→审批中),审批人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('crm:cancellation:update')")
    public CommonResult<Boolean> submitCancellation(@RequestParam("id") Long id,
                                                    @RequestParam("processDefinitionKey") String processDefinitionKey) {
        cancellationService.submitCancellation(id, processDefinitionKey, getLoginUserId());
    public CommonResult<Boolean> submitCancellation(@RequestParam("id") Long id) {
        cancellationService.submitCancellation(id);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获得销户申请审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核销户申请(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('crm:cancellation:audit')")
    public CommonResult<Boolean> auditCancellation(@Valid @RequestBody CrmCancellationAuditReqVO auditReqVO) {
        cancellationService.auditCancellation(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得销户申请的审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('crm:cancellation:query')")
    public CommonResult<List<Map<String, Object>>> getCancellationApproveProcessDefinitionList() {
        return success(cancellationService.getCancellationApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(cancellationService.getCancellationApproverUserIds());
    }
    @DeleteMapping("/delete")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationAuditReqVO.java
@@ -4,19 +4,27 @@
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - CRM é”€æˆ·ç”³è¯·å®¡æ‰¹ Request VO")
/**
 * CRM é”€æˆ·ç”³è¯·å®¡æ ¸ Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - CRM é”€æˆ·ç”³è¯·å®¡æ ¸ Request VO")
@Data
public class CrmCancellationAuditReqVO {
    @Schema(description = "销户申请编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @Schema(description = "销户申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "销户申请编号不能为空")
    private Long id;
    @Schema(description = "审批结果:20=通过 / 30=驳回", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "审批结果不能为空")
    private Integer auditStatus;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审批意见")
    private String remark;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "请先结清欠费")
    private String reviewRemark;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationRespVO.java
@@ -81,8 +81,17 @@
    @DictFormat(CRM_AUDIT_STATUS)
    private Integer auditStatus;
    @Schema(description = "流程实例ID")
    private String processInstanceId;
    @Schema(description = "审核人用户编号")
    private Long reviewerId;
    @Schema(description = "审核人姓名")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)")
    private String reviewRemark;
    @Schema(description = "备注")
    @ExcelProperty("备注")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/cancellation/vo/CrmCancellationSaveReqVO.java
@@ -62,8 +62,17 @@
    @Schema(description = "审批状态(0草稿/10待审批/20通过/30驳回)", example = "0")
    private Integer auditStatus;
    @Schema(description = "流程实例ID")
    private String processInstanceId;
    @Schema(description = "审核人用户编号")
    private Long reviewerId;
    @Schema(description = "审核人姓名")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)")
    private String reviewRemark;
    @Schema(description = "备注")
    private String remark;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -10,6 +10,7 @@
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractAuditReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
@@ -48,6 +49,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -189,19 +191,27 @@
    }
    @PutMapping("/submit")
    @Operation(summary = "提交合同审批")
    @Operation(summary = "提交合同审批(未提交/审核不通过→审批中),审批人由审批配置统一指定")
    @Parameter(name = "id", description = "合同编号", required = true)
    @PreAuthorize("@ss.hasPermission('crm:contract:update')")
    public CommonResult<Boolean> submitContract(@RequestParam("id") Long id,
                                                  @RequestParam("processDefinitionKey") String processDefinitionKey) {
        contractService.submitContract(id, processDefinitionKey, getLoginUserId());
    public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
        contractService.submitContract(id);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获取合同审批流程列表")
    @PutMapping("/audit")
    @Operation(summary = "审核合同(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('crm:contract:audit')")
    public CommonResult<Boolean> auditContract(@Valid @RequestBody CrmContractAuditReqVO auditReqVO) {
        contractService.auditContract(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得合同的审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('crm:contract:query')")
    public CommonResult<List<Map<String, Object>>> getContractApproveProcessList() {
        return success(contractService.getContractApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(contractService.getContractApproverUserIds());
    }
    private List<CrmContractRespVO> buildContractDetailList(List<CrmContractDO> contractList) {
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * CRM åˆåŒå®¡æ ¸ Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - CRM åˆåŒå®¡æ ¸ Request VO")
@Data
public class CrmContractAuditReqVO {
    @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "合同编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "金额需与客户再次核对")
    private String reviewRemark;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
@@ -52,9 +52,6 @@
    @ExcelProperty(value = "所属部门", index = 16)
    private String ownerUserDeptName;
    @Schema(description = "工作流编号", example = "1043")
    private String processInstanceId;
    @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer auditStatus;
@@ -62,6 +59,18 @@
    @ExcelProperty(value = "合同状态", index = 20)
    private String auditStatusName;
    @Schema(description = "审核人用户编号", example = "1024")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(不通过时即不通过原因)", example = "金额需与客户再次核对")
    private String reviewRemark;
    @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty(value = "下单时间", index = 6)
    private LocalDateTime orderDate;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/CrmInvoiceController.java
@@ -10,6 +10,7 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceAuditReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaveReqVO;
@@ -43,6 +44,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -187,13 +189,29 @@
    }
    @PutMapping("/submit")
    @Operation(summary = "提交开票审批")
    @Operation(summary = "提交开票审批(草稿/审核不通过→审批中),审批人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('crm:invoice:update')")
    public CommonResult<Boolean> submitInvoice(@RequestParam("id") Long id) {
        invoiceService.submitInvoice(id, getLoginUserId());
        invoiceService.submitInvoice(id);
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审核开票(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('crm:invoice:audit')")
    public CommonResult<Boolean> auditInvoice(@Valid @RequestBody CrmInvoiceAuditReqVO auditReqVO) {
        invoiceService.auditInvoice(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得开票的审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('crm:invoice:query')")
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(invoiceService.getInvoiceApproverUserIds());
    }
    @GetMapping("/audit-count")
    @Operation(summary = "获得待审核开票数量")
    @PreAuthorize("@ss.hasPermission('crm:invoice:query')")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * CRM å¼€ç¥¨å®¡æ ¸ Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - CRM å¼€ç¥¨å®¡æ ¸ Request VO")
@Data
public class CrmInvoiceAuditReqVO {
    @Schema(description = "开票编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "开票编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "发票抬头有误")
    private String reviewRemark;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/invoice/vo/invoice/CrmInvoiceRespVO.java
@@ -69,15 +69,23 @@
    @ExcelProperty("负责人部门")
    private String ownerUserDeptName;
    @Schema(description = "工作流编号", example = "1043")
    @ExcelProperty("工作流编号")
    private String processInstanceId;
    @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    @ExcelProperty(value = "审批状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.CRM_AUDIT_STATUS)
    private Integer auditStatus;
    @Schema(description = "审核人用户编号")
    private Long reviewerId;
    @Schema(description = "审核人姓名")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)")
    private String reviewRemark;
    @Schema(description = "备注", example = "备注")
    @ExcelProperty("备注")
    private String remark;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/CrmRefundController.java
@@ -21,12 +21,11 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - CRM é€€è´¹å•")
@RestController
@@ -52,28 +51,28 @@
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审批退费单(通过/驳回)")
    @PreAuthorize("@ss.hasPermission('crm:refund:update')")
    public CommonResult<Boolean> auditRefund(@Valid @RequestBody CrmRefundAuditReqVO auditReqVO) {
        refundService.auditRefund(auditReqVO);
        return success(true);
    }
    @PutMapping("/submit")
    @Operation(summary = "提交退费单审批")
    @Operation(summary = "提交退费单审批(草稿/审核不通过→审批中),审批人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('crm:refund:update')")
    public CommonResult<Boolean> submitRefund(@RequestParam("id") Long id,
                                              @RequestParam("processDefinitionKey") String processDefinitionKey) {
        refundService.submitRefund(id, processDefinitionKey, getLoginUserId());
    public CommonResult<Boolean> submitRefund(@RequestParam("id") Long id) {
        refundService.submitRefund(id);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获得退费单审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核退费单(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('crm:refund:audit')")
    public CommonResult<Boolean> auditRefund(@Valid @RequestBody CrmRefundAuditReqVO auditReqVO) {
        refundService.auditRefund(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得退费单的审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('crm:refund:query')")
    public CommonResult<List<Map<String, Object>>> getRefundApproveProcessDefinitionList() {
        return success(refundService.getRefundApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(refundService.getRefundApproverUserIds());
    }
    @DeleteMapping("/delete")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundAuditReqVO.java
@@ -4,19 +4,27 @@
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - CRM é€€è´¹å•审批 Request VO")
/**
 * CRM é€€è´¹å•审核 Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - CRM é€€è´¹å•审核 Request VO")
@Data
public class CrmRefundAuditReqVO {
    @Schema(description = "退费单编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @Schema(description = "退费单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "退费单编号不能为空")
    private Long id;
    @Schema(description = "审批结果:20=通过 / 30=驳回", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "审批结果不能为空")
    private Integer auditStatus;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审批意见")
    private String remark;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "金额需与客户再次核对")
    private String reviewRemark;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundRespVO.java
@@ -72,8 +72,17 @@
    @DictFormat(CRM_AUDIT_STATUS)
    private Integer auditStatus;
    @Schema(description = "流程实例ID")
    private String processInstanceId;
    @Schema(description = "审核人用户编号")
    private Long reviewerId;
    @Schema(description = "审核人姓名")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)")
    private String reviewRemark;
    @Schema(description = "备注")
    @ExcelProperty("备注")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/refund/vo/CrmRefundSaveReqVO.java
@@ -56,8 +56,17 @@
    @Schema(description = "审批状态(0草稿/10待审批/20通过/30驳回)", example = "0")
    private Integer auditStatus;
    @Schema(description = "流程实例ID")
    private String processInstanceId;
    @Schema(description = "审核人用户编号")
    private Long reviewerId;
    @Schema(description = "审核人姓名")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)")
    private String reviewRemark;
    @Schema(description = "备注")
    private String remark;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/cancellation/CrmCancellationDO.java
@@ -45,10 +45,24 @@
    private String applyUserName;
    private LocalDateTime applyTime;
    /**
     * å®¡æ‰¹çŠ¶æ€ï¼š0草稿/10待审批/20通过/30驳回
     * å®¡æ‰¹çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum}
     */
    private Integer auditStatus;
    private String processInstanceId;
    /**
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  cancellation_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private Long reviewerId;
    /** å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户) */
    private String reviewerName;
    /** å®¡æ ¸æ—¶é—´ */
    private LocalDateTime reviewTime;
    /** å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因) */
    private String reviewRemark;
    private String remark;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
@@ -67,17 +67,33 @@
    private Long ownerUserId;
    /**
     * å·¥ä½œæµç¼–号
     *
     * å…³è” ProcessInstance çš„ id å±žæ€§
     */
    private String processInstanceId;
    /**
     * å®¡æ‰¹çŠ¶æ€
     *
     * ç”±ä¸šåŠ¡æµç¨‹é©±åŠ¨ï¼Œä¸æŽ¥å—å‰ç«¯ç›´æŽ¥èµ‹å€¼ï¼šåˆ›å»ºæ—¶å›ºå®šä¸ºæœªæäº¤ï¼Œæäº¤å®¡æ‰¹åŽç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œ
     * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡ç”± {@link cn.iocoder.yudao.module.crm.service.contract.CrmContractService#auditContract} æŽ¨è¿›ã€‚
     *
     * æžšä¸¾ {@link CrmAuditStatusEnum}
     */
    private Integer auditStatus;
    /**
     * å®¡æ ¸äººç”¨æˆ·ç¼–号(提交审批时由提交人从系统用户中选择)
     *
     * åªæœ‰è¯¥ç”¨æˆ·èƒ½åœ¨åˆ—表页看到「审核」按钮并执行审核操作。
     * å…³è” AdminUserDO çš„ id å­—段
     */
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(提交审批时冗余记录,避免列表展示时再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * ä¸‹å•日期
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/invoice/CrmInvoiceDO.java
@@ -82,16 +82,23 @@
    private String remark;
    /**
     * å·¥ä½œæµç¼–号
     *
     * å…³è” ProcessInstance çš„ id å±žæ€§
     */
    private String processInstanceId;
    /**
     * å®¡æ‰¹çŠ¶æ€
     *
     * æžšä¸¾ {@link CrmAuditStatusEnum}
     */
    private Integer auditStatus;
    /**
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  crm_invoice_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private Long reviewerId;
    /** å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户) */
    private String reviewerName;
    /** å®¡æ ¸æ—¶é—´ */
    private LocalDateTime reviewTime;
    /** å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因) */
    private String reviewRemark;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/refund/CrmRefundDO.java
@@ -42,10 +42,24 @@
    private String applyUserName;
    private LocalDateTime applyTime;
    /**
     * å®¡æ‰¹çŠ¶æ€ï¼š0草稿/10待审批/20通过/30驳回
     * å®¡æ‰¹çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum}
     */
    private Integer auditStatus;
    private String processInstanceId;
    /**
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  refund_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private Long reviewerId;
    /** å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户) */
    private String reviewerName;
    /** å®¡æ ¸æ—¶é—´ */
    private LocalDateTime reviewTime;
    /** å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因) */
    private String reviewRemark;
    private String remark;
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/cancellation/CrmCancellationMapper.java
@@ -5,8 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.cancellation.CrmCancellationDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
@@ -16,6 +18,49 @@
        return selectPage(reqVO, buildQuery(reqVO));
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ä¹‹æ‰€ä»¥ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤çš„字段更新策略会忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人、审核时间、审核意见真正清空。
     *
     * @param id     é”€æˆ·ç”³è¯·ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<CrmCancellationDO>()
                .eq(CrmCancellationDO::getId, id)
                .set(CrmCancellationDO::getAuditStatus, status)
                .set(CrmCancellationDO::getReviewerId, null)
                .set(CrmCancellationDO::getReviewerName, null)
                .set(CrmCancellationDO::getReviewTime, null)
                .set(CrmCancellationDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           é”€æˆ·ç”³è¯·ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditCancellation(Long id, Integer status, Long reviewerId,
                                  String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<CrmCancellationDO>()
                .eq(CrmCancellationDO::getId, id)
                .set(CrmCancellationDO::getAuditStatus, status)
                .set(CrmCancellationDO::getReviewerId, reviewerId)
                .set(CrmCancellationDO::getReviewerName, reviewerName)
                .set(CrmCancellationDO::getReviewTime, LocalDateTime.now())
                .set(CrmCancellationDO::getReviewRemark, reviewRemark));
    }
    default List<CrmCancellationDO> selectList(CrmCancellationPageReqVO reqVO) {
        return selectList(buildQuery(reqVO));
    }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
@@ -13,6 +13,7 @@
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
@@ -31,6 +32,49 @@
        return selectOne(CrmContractDO::getNo, no);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ä¹‹æ‰€ä»¥ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤çš„字段更新策略会忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人、审核时间、审核意见真正清空。
     *
     * @param id     åˆåŒç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<CrmContractDO>()
                .eq(CrmContractDO::getId, id)
                .set(CrmContractDO::getAuditStatus, status)
                .set(CrmContractDO::getReviewerId, null)
                .set(CrmContractDO::getReviewerName, null)
                .set(CrmContractDO::getReviewTime, null)
                .set(CrmContractDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           åˆåŒç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditContract(Long id, Integer status, Long reviewerId,
                              String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<CrmContractDO>()
                .eq(CrmContractDO::getId, id)
                .set(CrmContractDO::getAuditStatus, status)
                .set(CrmContractDO::getReviewerId, reviewerId)
                .set(CrmContractDO::getReviewerName, reviewerName)
                .set(CrmContractDO::getReviewTime, LocalDateTime.now())
                .set(CrmContractDO::getReviewRemark, reviewRemark));
    }
    default PageResult<CrmContractDO> selectPageByCustomerId(CrmContractPageReqVO pageReqVO) {
        return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmContractDO>()
                .eq(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/invoice/CrmInvoiceMapper.java
@@ -12,9 +12,11 @@
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -34,6 +36,49 @@
        return selectOne(CrmInvoiceDO::getNo, no);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ä¹‹æ‰€ä»¥ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤çš„字段更新策略会忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人、审核时间、审核意见真正清空。
     *
     * @param id     å¼€ç¥¨ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<CrmInvoiceDO>()
                .eq(CrmInvoiceDO::getId, id)
                .set(CrmInvoiceDO::getAuditStatus, status)
                .set(CrmInvoiceDO::getReviewerId, null)
                .set(CrmInvoiceDO::getReviewerName, null)
                .set(CrmInvoiceDO::getReviewTime, null)
                .set(CrmInvoiceDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           å¼€ç¥¨ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditInvoice(Long id, Integer status, Long reviewerId,
                             String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<CrmInvoiceDO>()
                .eq(CrmInvoiceDO::getId, id)
                .set(CrmInvoiceDO::getAuditStatus, status)
                .set(CrmInvoiceDO::getReviewerId, reviewerId)
                .set(CrmInvoiceDO::getReviewerName, reviewerName)
                .set(CrmInvoiceDO::getReviewTime, LocalDateTime.now())
                .set(CrmInvoiceDO::getReviewRemark, reviewRemark));
    }
    default PageResult<CrmInvoiceDO> selectPageByCustomerId(CrmInvoicePageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<CrmInvoiceDO>()
                .eq(CrmInvoiceDO::getCustomerId, reqVO.getCustomerId()) // å¿…须传递
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/refund/CrmRefundMapper.java
@@ -5,8 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.refund.CrmRefundDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
@@ -16,6 +18,49 @@
        return selectPage(reqVO, buildQuery(reqVO));
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ä¹‹æ‰€ä»¥ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤çš„字段更新策略会忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人、审核时间、审核意见真正清空。
     *
     * @param id     é€€è´¹å•编号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<CrmRefundDO>()
                .eq(CrmRefundDO::getId, id)
                .set(CrmRefundDO::getAuditStatus, status)
                .set(CrmRefundDO::getReviewerId, null)
                .set(CrmRefundDO::getReviewerName, null)
                .set(CrmRefundDO::getReviewTime, null)
                .set(CrmRefundDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           é€€è´¹å•编号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditRefund(Long id, Integer status, Long reviewerId,
                            String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<CrmRefundDO>()
                .eq(CrmRefundDO::getId, id)
                .set(CrmRefundDO::getAuditStatus, status)
                .set(CrmRefundDO::getReviewerId, reviewerId)
                .set(CrmRefundDO::getReviewerName, reviewerName)
                .set(CrmRefundDO::getReviewTime, LocalDateTime.now())
                .set(CrmRefundDO::getReviewRemark, reviewRemark));
    }
    default List<CrmRefundDO> selectList(CrmRefundPageReqVO reqVO) {
        return selectList(buildQuery(reqVO));
    }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/CrmCancellationService.java
@@ -1,14 +1,13 @@
package cn.iocoder.yudao.module.crm.service.cancellation;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationAuditReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.cancellation.vo.CrmCancellationSaveReqVO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface CrmCancellationService {
@@ -24,12 +23,34 @@
    List<CrmCancellationRespVO> getCancellationList(CrmCancellationPageReqVO reqVO);
    void auditCancellation(@Valid CrmCancellationAuditReqVO auditReqVO);
    /**
     * æäº¤é”€æˆ·ç”³è¯·å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  cancellation_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审批人时抛出业务异常。
     *
     * @param id é”€æˆ·ç”³è¯·ç¼–号
     */
    void submitCancellation(Long id);
    void submitCancellation(Long id, String processDefinitionKey, Long userId);
    /**
     * èŽ·å¾—é”€æˆ·ç”³è¯·å®¡æ‰¹çš„å®¡æ‰¹äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getCancellationApproverUserIds();
    void updateCancellationAuditStatus(Long id, Integer bpmResult);
    List<Map<String, Object>> getCancellationApproveProcessDefinitionList();
    /**
     * å®¡æ ¸é”€æˆ·ç”³è¯·ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * ä»…业务编码 cancellation_approve é…ç½®çš„审批人可操作(多人时或签);审核不通过时必须填写不通过原因。
     *
     * @param id           é”€æˆ·ç”³è¯·ç¼–号
     * @param pass         æ˜¯å¦é€šè¿‡ï¼štrue=审核通过 / false=审核不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å³ä¸é€šè¿‡åŽŸå› ï¼Œå¿…å¡«ï¼‰
     */
    void auditCancellation(Long id, Boolean pass, String reviewRemark);
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/cancellation/listener/CrmCancellationStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
@@ -14,6 +14,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -65,28 +66,34 @@
    void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
    /**
     * å‘起合同审批流程
     * æäº¤åˆåŒå®¡æ‰¹ï¼ˆæœªæäº¤ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  contract_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审批人时抛出业务异常。
     *
     * @param id                    åˆåŒç¼–号
     * @param processDefinitionKey  æµç¨‹å®šä¹‰ Key
     * @param userId                ç”¨æˆ·ç¼–号
     */
    void submitContract(Long id, String processDefinitionKey, Long userId);
    void submitContract(Long id);
    /**
     * èŽ·å–åˆåŒå®¡æ‰¹æµç¨‹åˆ—è¡¨
     * èŽ·å¾—åˆåŒå®¡æ‰¹çš„å®¡æ‰¹äººç¼–å·é›†åˆ
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    List<Map<String, Object>> getContractApproveProcessDefinitionList();
    Set<Long> getContractApproverUserIds();
    /**
     * æ›´æ–°åˆåŒæµç¨‹å®¡æ‰¹ç»“æžœ
     * å®¡æ ¸åˆåŒï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * ä»…业务编码 contract_approve é…ç½®çš„审批人可操作(多人时或签);审核不通过时必须填写不通过原因。
     *
     * @param id        åˆåŒç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @param pass         æ˜¯å¦é€šè¿‡ï¼štrue=审核通过 / false=审核不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å³ä¸é€šè¿‡åŽŸå› ï¼Œå¿…å¡«ï¼‰
     */
    void updateContractAuditStatus(Long id, Integer bpmResult);
    void auditContract(Long id, Boolean pass, String reviewRemark);
    /**
     * èŽ·å¾—åˆåŒ
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -4,15 +4,11 @@
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
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;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
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.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
@@ -42,7 +38,9 @@
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
@@ -63,13 +61,11 @@
import java.util.Map;
import java.util.Set;
import org.flowable.engine.repository.ProcessDefinition;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
 * CRM åˆåŒ Service å®žçŽ°ç±»
@@ -82,14 +78,11 @@
public class CrmContractServiceImpl implements CrmContractService {
    /**
     * BPM åˆåŒå®¡æ‰¹æµç¨‹æ ‡è¯†
     * CRM åˆåŒå®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    public static final String BPM_PROCESS_DEFINITION_KEY = "crm-contract-audit";
    /**
     * BPM åˆåŒå®¡æ‰¹åˆ†ç±»ç¼–码
     */
    private static final String CONTRACT_APPROVE_CATEGORY_CODE = "contract_approve";
    private static final String CONTRACT_APPROVE_BIZ_TYPE = "contract_approve";
    @Resource
    private CrmContractMapper contractMapper;
@@ -117,11 +110,7 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private ErpSaleOrderApi saleOrderApi;
    @Resource
@@ -300,6 +289,20 @@
        return contract;
    }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
@@ -338,114 +341,53 @@
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
            success = CRM_CONTRACT_SUBMIT_SUCCESS)
    public void submitContract(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒåˆåŒæ˜¯å¦åœ¨å®¡æ‰¹
    public void submitContract(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆæœªæäº¤ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        CrmContractDO contract = validateContractExists(id);
        if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
            throw exception(CONTRACT_SUBMIT_FAIL_NOT_DRAFT);
        if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())
                && ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(CONTRACT_SUBMIT_FAIL_NOT_EDITABLE);
        }
        // 2. æ ¡éªŒåˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validateContractApproveCategoryAndProcessDefinition();
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(CONTRACT_APPROVE_BIZ_TYPE);
        // 3. æ ¡éªŒä¼ å…¥çš„æµç¨‹å®šä¹‰ Key æ˜¯å¦å±žäºŽè¯¥åˆ†ç±»ä¸”处于激活状态
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        if (processDefinition == null) {
            throw exception(CONTRACT_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        // 3. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        contractMapper.submitAndResetAuditInfo(id, CrmAuditStatusEnum.PROCESS.getStatus());
        // 4. åˆ›å»ºåˆåŒå®¡æ‰¹æµç¨‹å®žä¾‹
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
        // 5. æ›´æ–°åˆåŒå·¥ä½œæµç¼–号
        contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId)
                .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
        // 6. è®°å½•日志
        // 4. è®°å½•日志
        LogRecordContext.putVariable("contractName", contract.getName());
    }
    /**
     * æ ¡éªŒ BPM åˆåŒå®¡æ‰¹åˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
     */
    private void validateContractApproveCategoryAndProcessDefinition() {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        List<String> codes = Collections.singletonList(CONTRACT_APPROVE_CATEGORY_CODE);
        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(codes);
        if (!categoryMap.containsKey(CONTRACT_APPROVE_CATEGORY_CODE)) {
            throw exception(CONTRACT_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        // 2. æ ¡éªŒåˆ†ç±»ä¸‹æ˜¯å¦æœ‰å¯ç”¨çš„æµç¨‹å®šä¹‰
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(CONTRACT_APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            throw exception(CONTRACT_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(CONTRACT_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
    @Override
    public Set<Long> getContractApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(CONTRACT_APPROVE_BIZ_TYPE);
    }
    @Override
    public List<Map<String, Object>> getContractApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validateContractApproveCategoryAndProcessDefinition();
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(CONTRACT_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 updateContractAuditStatus(Long id, Integer bpmResult) {
        // 1.1 æ ¡éªŒåˆåŒæ˜¯å¦å­˜åœ¨
    @Transactional(rollbackFor = Exception.class)
    public void auditContract(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        CrmContractDO contract = validateContractExists(id);
        // 1.2 åªæœ‰å®¡æ‰¹ä¸­ï¼Œå¯ä»¥æ›´æ–°å®¡æ‰¹ç»“æžœ
        // 2. æ ¡éªŒå¤„于审批中状态
        if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
            log.error("[updateContractAuditStatus][contract({}) ä¸å¤„于审批中,无法更新审批结果({})]",
                    contract.getId(), bpmResult);
            throw exception(CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
            throw exception(CONTRACT_AUDIT_FAIL_NOT_PROCESS);
        }
        // 2. æ›´æ–°åˆåŒå®¡æ‰¹ç»“æžœ
        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
        contractMapper.updateById(new CrmContractDO().setId(id).setAuditStatus(auditStatus));
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(CONTRACT_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(CONTRACT_AUDIT_REJECT_REASON_REQUIRED);
            }
            contractMapper.auditContract(id, CrmAuditStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为审核通过
        contractMapper.auditContract(id, CrmAuditStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    // ======================= æŸ¥è¯¢ç›¸å…³ =======================
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceService.java
@@ -10,6 +10,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -36,14 +37,6 @@
    void updateInvoice(@Valid CrmInvoiceSaveReqVO updateReqVO);
    /**
     * æ›´æ–°å¼€ç¥¨æµç¨‹å®¡æ‰¹ç»“æžœ
     *
     * @param id        å¼€ç¥¨ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updateInvoiceAuditStatus(Long id, Integer bpmResult);
    /**
     * åˆ é™¤å¼€ç¥¨
     *
     * @param id ç¼–号
@@ -51,12 +44,34 @@
    void deleteInvoice(Long id);
    /**
     * å‘起开票审批流程
     * æäº¤å¼€ç¥¨å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  crm_invoice_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审批人时抛出业务异常。
     *
     * @param id     å¼€ç¥¨ç¼–号
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitInvoice(Long id, Long userId);
    void submitInvoice(Long id);
    /**
     * èŽ·å¾—å¼€ç¥¨å®¡æ‰¹çš„å®¡æ‰¹äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getInvoiceApproverUserIds();
    /**
     * å®¡æ ¸å¼€ç¥¨ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * ä»…业务编码 crm_invoice_approve é…ç½®çš„审批人可操作(多人时或签);审核不通过时必须填写不通过原因。
     *
     * @param id           å¼€ç¥¨ç¼–号
     * @param pass         æ˜¯å¦é€šè¿‡ï¼štrue=审核通过 / false=审核不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å³ä¸é€šè¿‡åŽŸå› ï¼Œå¿…å¡«ï¼‰
     */
    void auditInvoice(Long id, Boolean pass, String reviewRemark);
    /**
     * èŽ·å¾—å¼€ç¥¨
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java
@@ -5,12 +5,11 @@
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
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.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
@@ -24,8 +23,10 @@
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
@@ -39,11 +40,12 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
/**
 * CRM å¼€ç¥¨ Service å®žçŽ°ç±»
@@ -56,9 +58,11 @@
public class CrmInvoiceServiceImpl implements CrmInvoiceService {
    /**
     * BPM å¼€ç¥¨å®¡æ‰¹æµç¨‹æ ‡è¯†
     * CRM å¼€ç¥¨å®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    public static final String BPM_PROCESS_DEFINITION_KEY = "crm-invoice-audit";
    private static final String INVOICE_APPROVE_BIZ_TYPE = "crm_invoice_approve";
    /**
     * å‘票附件的业务记录类型,对应 {@link cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum#CRM_INVOICE}
@@ -79,7 +83,7 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
@@ -182,22 +186,6 @@
    }
    @Override
    public void updateInvoiceAuditStatus(Long id, Integer bpmResult) {
        // 1.1 æ ¡éªŒå­˜åœ¨
        CrmInvoiceDO invoice = validateInvoiceExists(id);
        // 1.2 åªæœ‰å®¡æ‰¹ä¸­ï¼Œå¯ä»¥æ›´æ–°å®¡æ‰¹ç»“æžœ
        if (ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
            log.error("[updateInvoiceAuditStatus][invoice({}) ä¸å¤„于审批中,无法更新审批结果({})]",
                    invoice.getId(), bpmResult);
            throw exception(INVOICE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        // 2. æ›´æ–°å¼€ç¥¨å®¡æ‰¹çŠ¶æ€
        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
        invoiceMapper.updateById(new CrmInvoiceDO().setId(id).setAuditStatus(auditStatus));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
            success = CRM_INVOICE_DELETE_SUCCESS)
@@ -225,29 +213,74 @@
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
            success = CRM_INVOICE_SUBMIT_SUCCESS)
    public void submitInvoice(Long id, Long userId) {
        // 1. æ ¡éªŒå¼€ç¥¨æ˜¯å¦åœ¨å®¡æ‰¹
    public void submitInvoice(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆæœªæäº¤ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        CrmInvoiceDO invoice = validateInvoiceExists(id);
        if (ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
            throw exception(INVOICE_SUBMIT_FAIL_NOT_DRAFT);
        if (ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())
                && ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(INVOICE_SUBMIT_FAIL_NOT_EDITABLE);
        }
        // 1.1 é‡æ–°æ ¡éªŒå¯å¼€ç¥¨é‡‘额,防止累计开票超过合同金额(草稿期后可能有其他单进入审批中)
        // 2. é‡æ–°æ ¡éªŒå¯å¼€ç¥¨é‡‘额,防止累计开票超过合同金额(草稿期后可能有其他单进入审批中)
        validateInvoicePriceExceedsLimit(new CrmInvoiceSaveReqVO()
                .setId(invoice.getId()).setContractId(invoice.getContractId()).setPrice(invoice.getPrice()));
        // 2. åˆ›å»ºå¼€ç¥¨å®¡æ‰¹æµç¨‹å®žä¾‹
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id)));
        // 3. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(INVOICE_APPROVE_BIZ_TYPE);
        // 3. æ›´æ–°å¼€ç¥¨å·¥ä½œæµç¼–号
        invoiceMapper.updateById(new CrmInvoiceDO().setId(id).setProcessInstanceId(processInstanceId)
                .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
        // 4. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        invoiceMapper.submitAndResetAuditInfo(id, CrmAuditStatusEnum.PROCESS.getStatus());
        // 4. è®°å½•日志
        // 5. è®°å½•日志
        LogRecordContext.putVariable("invoiceNo", invoice.getNo());
    }
    @Override
    public Set<Long> getInvoiceApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(INVOICE_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditInvoice(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        CrmInvoiceDO invoice = validateInvoiceExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(INVOICE_AUDIT_FAIL_NOT_PROCESS);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(INVOICE_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(INVOICE_AUDIT_REJECT_REASON_REQUIRED);
            }
            invoiceMapper.auditInvoice(id, CrmAuditStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为审核通过
        //    ï¼ˆå¼€ç¥¨æ— é¢å¤–后置业务逻辑,审批通过后即可用于创建回款 / å…³è”开票选择)
        invoiceMapper.auditInvoice(id, CrmAuditStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    private CrmInvoiceDO validateInvoiceExists(Long id) {
        CrmInvoiceDO invoice = invoiceMapper.selectById(id);
        if (invoice == null) {
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/listener/CrmInvoiceStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/CrmRefundService.java
@@ -1,14 +1,13 @@
package cn.iocoder.yudao.module.crm.service.refund;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundAuditReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.refund.vo.CrmRefundSaveReqVO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface CrmRefundService {
@@ -24,12 +23,34 @@
    List<CrmRefundRespVO> getRefundList(CrmRefundPageReqVO reqVO);
    void auditRefund(@Valid CrmRefundAuditReqVO auditReqVO);
    /**
     * æäº¤é€€è´¹å•审批(草稿 / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  refund_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审批人时抛出业务异常。
     *
     * @param id é€€è´¹å•编号
     */
    void submitRefund(Long id);
    void submitRefund(Long id, String processDefinitionKey, Long userId);
    /**
     * èŽ·å¾—é€€è´¹å•å®¡æ‰¹çš„å®¡æ‰¹äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getRefundApproverUserIds();
    void updateRefundAuditStatus(Long id, Integer bpmResult);
    List<Map<String, Object>> getRefundApproveProcessDefinitionList();
    /**
     * å®¡æ ¸é€€è´¹å•(审批中 â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * ä»…业务编码 refund_approve é…ç½®çš„审批人可操作(多人时或签);审核不通过时必须填写不通过原因。
     *
     * @param id           é€€è´¹å•编号
     * @param pass         æ˜¯å¦é€šè¿‡ï¼štrue=审核通过 / false=审核不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å³ä¸é€šè¿‡åŽŸå› ï¼Œå¿…å¡«ï¼‰
     */
    void auditRefund(Long id, Boolean pass, String reviewRemark);
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/refund/listener/CrmRefundStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java
@@ -9,6 +9,7 @@
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentSaveReqVO;
@@ -38,12 +39,12 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP ä»˜æ¬¾å•")
@RestController
@@ -80,21 +81,29 @@
        return success(true);
    }
    @PostMapping("/submit")
    @Operation(summary = "提交付款单审批")
    @PutMapping("/submit")
    @Operation(summary = "提交付款单审批(未审核 â†’ å®¡æ‰¹ä¸­ï¼‰ï¼Œå®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®š")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('erp:finance-payment:update')")
    public CommonResult<Boolean> submitFinancePayment(@RequestParam("id") Long id,
                                                      @RequestParam("processDefinitionKey") String processDefinitionKey) {
        Long userId = getLoginUserId();
        financePaymentService.submitFinancePayment(id, processDefinitionKey, userId);
    public CommonResult<Boolean> submitFinancePayment(@RequestParam("id") Long id) {
        financePaymentService.submitFinancePayment(id);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获取付款单审批流程列表")
    @PutMapping("/audit")
    @Operation(summary = "审核付款单(审批中 â†’ å®¡æ ¸é€šè¿‡/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('erp:finance-payment:audit')")
    public CommonResult<Boolean> auditFinancePayment(@Valid @RequestBody ErpFinancePaymentAuditReqVO auditReqVO) {
        financePaymentService.auditFinancePayment(auditReqVO.getId(), auditReqVO.getPass(),
                auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得付款单的审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('erp:finance-payment:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessList() {
        return success(financePaymentService.getFinancePaymentApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(financePaymentService.getFinancePaymentApproverUserIds());
    }
    @DeleteMapping("/delete")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ERP ä»˜æ¬¾å•审核 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ERP ä»˜æ¬¾å•审核 Request VO")
@Data
public class ErpFinancePaymentAuditReqVO {
    @Schema(description = "付款单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "付款单编号不能为空")
    private Long id;
    @Schema(description = "是否审核通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "审核结果不能为空")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时为可选;审核不通过时为必填的原因)", example = "同意付款")
    private String reviewRemark;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java
@@ -26,9 +26,24 @@
    @Schema(description = "付款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Integer status;
    @Schema(description = "工作流编号", example = "3cc7f8e6")
    @Schema(description = "工作流编号(已废弃,轻量级审批不再使用)", example = "3cc7f8e6")
    @Deprecated
    private String processInstanceId;
    // ========== å®¡æ‰¹ä¿¡æ¯ï¼ˆè½»é‡çº§çŠ¶æ€æœºå®¡æ‰¹ï¼‰ ==========
    @Schema(description = "审核人编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人昵称", example = "超级管理员")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核不通过时为驳回原因)", example = "同意付款")
    private String reviewRemark;
    @Schema(description = "付款时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime paymentTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -8,6 +8,7 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
@@ -38,12 +39,12 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP é‡‡è´­ç”³è¯·")
@RestController
@@ -134,20 +135,28 @@
    }
    @PutMapping("/submit")
    @Operation(summary = "提交采购申请审批")
    @Operation(summary = "提交采购申请审批(未提交/审核不通过 â†’ å®¡æ‰¹ä¸­ï¼‰ï¼Œå®¡æ‰¹äººç”±å®¡æ‰¹é…ç½®ç»Ÿä¸€æŒ‡å®š")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:submit')")
    public CommonResult<Boolean> submitPurchaseRequest(@RequestParam("id") Long id,
                                                       @RequestParam("processDefinitionKey") String processDefinitionKey) {
        Long userId = getLoginUserId();
        purchaseRequestService.submitPurchaseRequest(id, processDefinitionKey, userId);
    public CommonResult<Boolean> submitPurchaseRequest(@RequestParam("id") Long id) {
        purchaseRequestService.submitPurchaseRequest(id);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获取采购申请审批流程列表")
    @PutMapping("/audit")
    @Operation(summary = "审核采购申请(审批中 â†’ å®¡æ ¸é€šè¿‡/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:audit')")
    public CommonResult<Boolean> auditPurchaseRequest(@Valid @RequestBody ErpPurchaseRequestAuditReqVO auditReqVO) {
        purchaseRequestService.auditPurchaseRequest(auditReqVO.getId(), auditReqVO.getPass(),
                auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得采购申请的审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessList() {
        return success(purchaseRequestService.getPurchaseRequestApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(purchaseRequestService.getPurchaseRequestApproverUserIds());
    }
    @PostMapping("/generate-order")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ERP é‡‡è´­ç”³è¯·å®¡æ ¸ Request VO
 */
@Schema(description = "管理后台 - ERP é‡‡è´­ç”³è¯·å®¡æ ¸ Request VO")
@Data
public class ErpPurchaseRequestAuditReqVO {
    @Schema(description = "采购申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "采购申请编号不能为空")
    private Long id;
    @Schema(description = "是否审核通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "审核结果不能为空")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时为可选;审核不通过时为必填的原因)", example = "同意采购")
    private String reviewRemark;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -29,9 +29,27 @@
    @ExcelProperty(value = "状态", converter = cn.iocoder.yudao.module.erp.convert.ErpPurchaseRequestStatusConvert.class)
    private Integer status;
    @Schema(description = "流程实例编号", example = "1024")
    @Schema(description = "流程实例编号(已废弃,轻量级审批不再使用)", example = "1024")
    @Deprecated
    private String processInstanceId;
    // ========== å®¡æ‰¹ä¿¡æ¯ï¼ˆè½»é‡çº§çŠ¶æ€æœºå®¡æ‰¹ï¼‰ ==========
    @Schema(description = "审核人编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人昵称", example = "超级管理员")
    @ExcelProperty("审核人")
    private String reviewerName;
    @Schema(description = "审核时间")
    @ExcelProperty("审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核不通过时为驳回原因)", example = "同意采购")
    @ExcelProperty("审核意见")
    private String reviewRemark;
    @Schema(description = "申请人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @ExcelProperty("申请人编号")
    private Long requestUserId;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentDO.java
@@ -44,9 +44,33 @@
    /**
     * å·¥ä½œæµç¼–号
     *
     * å…³è” BPM æµç¨‹å®žä¾‹çš„ id
     * å·²åºŸå¼ƒï¼šä»˜æ¬¾å•改为轻量级状态机审批后不再写入,仅保留历史数据。
     */
    @Deprecated
    private String processInstanceId;
    /**
     * å®¡æ ¸äººç¼–号(系统用户)
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  erp_finance_payment_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private Long reviewerId;
    /**
     * å®¡æ ¸äººæ˜µç§°ï¼ˆå†—余展示用)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,作为驳回原因)
     */
    private String reviewRemark;
    /**
     * ä»˜æ¬¾æ—¶é—´
     */
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
@@ -111,10 +111,36 @@
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     *
     * å·²åºŸå¼ƒï¼šé‡‡è´­ç”³è¯·æ”¹ä¸ºè½»é‡çº§çŠ¶æ€æœºå®¡æ‰¹åŽä¸å†å†™å…¥ï¼Œä»…ä¿ç•™åŽ†å²æ•°æ®ã€‚
     */
    @Deprecated
    private String processInstanceId;
    /**
     * å®¡æ ¸äººç¼–号(系统用户)
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  purchase_request_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private Long reviewerId;
    /**
     * å®¡æ ¸äººæ˜µç§°ï¼ˆå†—余展示用)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,作为驳回原因)
     */
    private String reviewRemark;
    /**
     * ç”Ÿæˆçš„采购订单编号
     */
    private Long orderId;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java
@@ -14,6 +14,7 @@
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -84,4 +85,34 @@
        return convertMap(result, obj -> (Long) obj.get("invoice_id"), obj -> (BigDecimal) obj.get("total_payment_price"));
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<ErpFinancePaymentDO>()
                .eq(ErpFinancePaymentDO::getId, id)
                .set(ErpFinancePaymentDO::getStatus, status)
                .set(ErpFinancePaymentDO::getReviewerId, null)
                .set(ErpFinancePaymentDO::getReviewerName, null)
                .set(ErpFinancePaymentDO::getReviewTime, null)
                .set(ErpFinancePaymentDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼ˆé€šè¿‡ / ä¸é€šè¿‡ï¼‰ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     */
    default int auditFinancePayment(Long id, Integer status, Long reviewerId,
                                    String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<ErpFinancePaymentDO>()
                .eq(ErpFinancePaymentDO::getId, id)
                .set(ErpFinancePaymentDO::getStatus, status)
                .set(ErpFinancePaymentDO::getReviewerId, reviewerId)
                .set(ErpFinancePaymentDO::getReviewerName, reviewerName)
                .set(ErpFinancePaymentDO::getReviewTime, LocalDateTime.now())
                .set(ErpFinancePaymentDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java
@@ -5,8 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -42,4 +44,34 @@
                .orderByDesc(ErpPurchaseRequestDO::getId));
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<ErpPurchaseRequestDO>()
                .eq(ErpPurchaseRequestDO::getId, id)
                .set(ErpPurchaseRequestDO::getStatus, status)
                .set(ErpPurchaseRequestDO::getReviewerId, null)
                .set(ErpPurchaseRequestDO::getReviewerName, null)
                .set(ErpPurchaseRequestDO::getReviewTime, null)
                .set(ErpPurchaseRequestDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼ˆé€šè¿‡ / ä¸é€šè¿‡ï¼‰ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     */
    default int auditPurchaseRequest(Long id, Integer status, Long reviewerId,
                                     String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<ErpPurchaseRequestDO>()
                .eq(ErpPurchaseRequestDO::getId, id)
                .set(ErpPurchaseRequestDO::getStatus, status)
                .set(ErpPurchaseRequestDO::getReviewerId, reviewerId)
                .set(ErpPurchaseRequestDO::getReviewerName, reviewerName)
                .set(ErpPurchaseRequestDO::getReviewTime, LocalDateTime.now())
                .set(ErpPurchaseRequestDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -51,14 +51,14 @@
    ErrorCode PURCHASE_REQUEST_NOT_EXISTS = new ErrorCode(1_030_104_000, "采购申请单不存在");
    ErrorCode PURCHASE_REQUEST_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_001, "采购申请单({})不是草稿状态,无法删除");
    ErrorCode PURCHASE_REQUEST_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_002, "采购申请单({})不是草稿状态,无法修改");
    ErrorCode PURCHASE_REQUEST_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_104_003, "提交失败,只有草稿状态的申请单才能提交");
    ErrorCode PURCHASE_REQUEST_NO_EXISTS = new ErrorCode(1_030_104_004, "生成申请单号失败,请重新提交");
    ErrorCode PURCHASE_REQUEST_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_104_005, "更新审批状态失败,只有审批中的申请单才能更新");
    ErrorCode PURCHASE_REQUEST_NOT_APPROVE = new ErrorCode(1_030_104_006, "采购申请单未审核通过,无法操作");
    ErrorCode PURCHASE_REQUEST_ORDER_EXISTS = new ErrorCode(1_030_104_007, "采购申请单已生成采购订单,请勿重复操作");
    ErrorCode PURCHASE_REQUEST_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_104_008, "采购申请审批分类不存在,请先在BPM模块创建采购申请审批流程分类");
    ErrorCode PURCHASE_REQUEST_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_104_009, "采购申请审批分类下没有可用的流程模型,请先创建并发布流程");
    ErrorCode PURCHASE_REQUEST_ITEM_PRICE_EMPTY = new ErrorCode(1_030_104_021, "采购申请明细({})金额信息不完整,无法生成采购订单");
    ErrorCode PURCHASE_REQUEST_SUBMIT_FAIL_NOT_ALLOWED = new ErrorCode(1_030_104_022, "提交失败,只有草稿或审核不通过状态的采购申请才能提交");
    ErrorCode PURCHASE_REQUEST_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_104_023, "审核失败,只有审批中状态的采购申请才能审核");
    ErrorCode PURCHASE_REQUEST_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_104_024, "审核不通过时必须填写不通过原因");
    ErrorCode PURCHASE_REQUEST_ITEM_EMPTY = new ErrorCode(1_030_104_025, "采购申请单没有申请明细,无法提交审批");
    // ========== ERP é‡‡è´­è®¡åˆ’(1-030-104-010) ==========
    ErrorCode PURCHASE_PLAN_NOT_EXISTS = new ErrorCode(1_030_104_010, "采购计划不存在");
@@ -216,11 +216,11 @@
    ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_ATTACHMENT = new ErrorCode(1_030_601_008, "创建付款单失败,原因:关联的来票未上传发票附件");
    ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_SUPPLIER_MISMATCH = new ErrorCode(1_030_601_009, "创建付款单失败,原因:关联的来票与所选供应商不匹配");
    ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_PRICE_EXCEEDS = new ErrorCode(1_030_601_010, "创建付款单失败,原因:付款金额超出该来票剩余可付金额,目前可付:{} å…ƒ");
    ErrorCode FINANCE_PAYMENT_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_601_011, "提交审批失败,只有未审核状态的付款单才能提交");
    ErrorCode FINANCE_PAYMENT_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_601_012, "付款单审批分类不存在,请先在BPM模块创建采购审核流程分类");
    ErrorCode FINANCE_PAYMENT_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_601_013, "付款单审批分类下没有可用的流程模型,请先创建并发布流程");
    ErrorCode FINANCE_PAYMENT_UPDATE_FAIL_APPROVING = new ErrorCode(1_030_601_014, "付款单({})审批中,无法修改");
    ErrorCode FINANCE_PAYMENT_DELETE_FAIL_APPROVING = new ErrorCode(1_030_601_015, "付款单({})审批中,无法删除");
    ErrorCode FINANCE_PAYMENT_SUBMIT_FAIL_NOT_ALLOWED = new ErrorCode(1_030_601_016, "提交失败,只有未审核状态的付款单才能提交");
    ErrorCode FINANCE_PAYMENT_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_601_017, "审核失败,只有审批中状态的付款单才能审核");
    ErrorCode FINANCE_PAYMENT_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_601_018, "审核不通过时必须填写不通过原因");
    // ========== ERP æ”¶æ¬¾å• 1-030-602-000 ==========
    ErrorCode FINANCE_RECEIPT_NOT_EXISTS = new ErrorCode(1_030_602_000, "收款单不存在");
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentService.java
@@ -11,6 +11,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * ERP ä»˜æ¬¾å• Service æŽ¥å£
@@ -37,26 +38,30 @@
    /**
     * æäº¤ä»˜æ¬¾å•审批
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰çš„编号
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitFinancePayment(Long id, String processDefinitionKey, Long userId);
    /**
     * èŽ·å¾—ä»˜æ¬¾å•å®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     */
    List<Map<String, Object>> getFinancePaymentApproveProcessDefinitionList();
    /**
     * æ›´æ–°ä»˜æ¬¾å•的审批状态(由 BPM æµç¨‹å®žä¾‹çš„回调触发)
     * ä»…【未审核】(含审核不通过退回)状态可提交;审批人由「系统管理 - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œ
     * æäº¤åŽè¿›å…¥ã€å®¡æ‰¹ä¸­ã€‘,并清空上一轮审核结果。
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updateFinancePaymentAuditStatus(Long id, Integer bpmResult);
    void submitFinancePayment(Long id);
    /**
     * èŽ·å–ä»˜æ¬¾å•çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼ˆæˆ–ç­¾ï¼šä»»ä¸€äººå®¡æ ¸å³å¯ï¼‰
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号
     */
    Set<Long> getFinancePaymentApproverUserIds();
    /**
     * å®¡æ ¸ä»˜æ¬¾å•(通过 / ä¸é€šè¿‡ï¼‰
     *
     * ä»…配置内的审批人可执行;审核不通过时必须填写原因,退回后可修改并重新提交。
     *
     * @param id ç¼–号
     * @param pass æ˜¯å¦å®¡æ ¸é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§
     */
    void auditFinancePayment(Long id, Boolean pass, String reviewRemark);
    /**
     * åˆ é™¤ä»˜æ¬¾å•
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
@@ -2,15 +2,9 @@
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.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.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO;
@@ -23,11 +17,12 @@
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceServiceImpl;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
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;
@@ -37,6 +32,7 @@
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
/**
@@ -50,9 +46,9 @@
public class ErpFinancePaymentServiceImpl implements ErpFinancePaymentService {
    /**
     * BPM ä»˜æ¬¾å•审批分类编码
     * é€šç”¨å®¡æ‰¹é…ç½®çš„业务类型编码(对应 system_approval_config.biz_type)
     */
    private static final String BPM_PROCESS_DEFINITION_CATEGORY_CODE = "erp_finance_payment_approve";
    private static final String FINANCE_PAYMENT_APPROVE_BIZ_TYPE = "erp_finance_payment_approve";
    @Resource
    private ErpFinancePaymentMapper financePaymentMapper;
@@ -76,11 +72,7 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -176,106 +168,62 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitFinancePayment(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒå­˜åœ¨ + æœªå®¡æ ¸çŠ¶æ€
    public void submitFinancePayment(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆæœªå®¡æ ¸ï¼šå«å®¡æ ¸ä¸é€šè¿‡é€€å›žåŽé‡æ–°æäº¤ï¼‰
        ErpFinancePaymentDO payment = validateFinancePaymentExists(id);
        if (!ErpAuditStatus.PROCESS.getStatus().equals(payment.getStatus())) {
            throw exception(FINANCE_PAYMENT_SUBMIT_FAIL_STATUS);
            throw exception(FINANCE_PAYMENT_SUBMIT_FAIL_NOT_ALLOWED);
        }
        // 2. åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°ä»˜æ¬¾å•状态为审批中
        financePaymentMapper.updateById(new ErpFinancePaymentDO()
                .setId(id)
                .setStatus(ErpAuditStatus.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId));
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(FINANCE_PAYMENT_APPROVE_BIZ_TYPE);
        // 3. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœ
        financePaymentMapper.submitAndResetAuditInfo(id, ErpAuditStatus.APPROVING.getStatus());
    }
    @Override
    public List<Map<String, Object>> getFinancePaymentApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validateFinancePaymentApproveCategoryAndProcessDefinition();
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(BPM_PROCESS_DEFINITION_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. éåŽ†èŽ·å–æµç¨‹å®šä¹‰è¯¦æƒ…ï¼Œè¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    public Set<Long> getFinancePaymentApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(FINANCE_PAYMENT_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFinancePaymentAuditStatus(Long id, Integer bpmResult) {
    public void auditFinancePayment(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        ErpFinancePaymentDO payment = validateFinancePaymentExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!ErpAuditStatus.APPROVING.getStatus().equals(payment.getStatus())) {
            log.warn("[updateFinancePaymentAuditStatus] ä»˜æ¬¾å•({}) ä¸å¤„于审批中状态", id);
            throw exception(FINANCE_PAYMENT_AUDIT_FAIL_NOT_PROCESS);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(FINANCE_PAYMENT_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回财务人员修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(FINANCE_PAYMENT_AUDIT_REJECT_REASON_REQUIRED);
            }
            financePaymentMapper.auditFinancePayment(id, ErpAuditStatus.PROCESS.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToAuditStatus(bpmResult);
        if (newStatus != null) {
            financePaymentMapper.updateById(new ErpFinancePaymentDO()
                    .setId(id).setStatus(newStatus));
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为已审核
        financePaymentMapper.auditFinancePayment(id, ErpAuditStatus.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    /**
     * æ ¡éªŒä»˜æ¬¾å•审批分类和流程定义是否存在
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private void validateFinancePaymentApproveCategoryAndProcessDefinition() {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(BPM_PROCESS_DEFINITION_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(FINANCE_PAYMENT_BPM_CATEGORY_NOT_EXISTS);
        }
    }
    /**
     * è½¬æ¢ BPM å®¡æ‰¹ç»“果为审核状态
     */
    private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.APPROVE.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.PROCESS.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.CANCEL.getStatus();
        }
    private String getUserNickname(Long userId) {
        if (userId == null) {
        return null;
    }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    private List<ErpFinancePaymentItemDO> validateFinancePaymentItems(
            Long supplierId,
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/listener/ErpFinancePaymentStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java
@@ -9,7 +9,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * ERP é‡‡è´­ç”³è¯· Service æŽ¥å£
@@ -73,26 +73,30 @@
    /**
     * æäº¤é‡‡è´­ç”³è¯·å®¡æ‰¹
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId);
    /**
     * èŽ·å–é‡‡è´­ç”³è¯·å®¡æ‰¹æµç¨‹åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     */
    List<Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList();
    /**
     * æ›´æ–°é‡‡è´­ç”³è¯·å®¡æ‰¹ç»“果(BPM å›žè°ƒï¼‰
     * ä»…【未提交】或【审核不通过】状态可提交;审批人由「系统管理 - å®¡æ‰¹é…ç½®ã€ç»Ÿä¸€æŒ‡å®šï¼Œ
     * æäº¤åŽè¿›å…¥ã€å®¡æ‰¹ä¸­ã€‘,并清空上一轮审核结果。
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult);
    void submitPurchaseRequest(Long id);
    /**
     * èŽ·å–é‡‡è´­ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼ˆæˆ–ç­¾ï¼šä»»ä¸€äººå®¡æ ¸å³å¯ï¼‰
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号
     */
    Set<Long> getPurchaseRequestApproverUserIds();
    /**
     * å®¡æ ¸é‡‡è´­ç”³è¯·ï¼ˆé€šè¿‡ / ä¸é€šè¿‡ï¼‰
     *
     * ä»…配置内的审批人可执行;审核不通过时必须填写原因,退回后可修改并重新提交。
     *
     * @param id ç¼–号
     * @param pass æ˜¯å¦å®¡æ ¸é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§
     */
    void auditPurchaseRequest(Long id, Boolean pass, String reviewRemark);
    /**
     * ç”Ÿæˆé‡‡è´­è®¢å•
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -1,16 +1,10 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.hutool.core.collection.CollUtil;
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;
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.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
@@ -24,13 +18,13 @@
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
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;
@@ -40,6 +34,7 @@
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
/**
@@ -51,9 +46,9 @@
public class ErpPurchaseRequestServiceImpl implements ErpPurchaseRequestService {
    /**
     * BPM é‡‡è´­ç”³è¯·å®¡æ‰¹åˆ†ç±»ç¼–码
     * é€šç”¨å®¡æ‰¹é…ç½®çš„业务类型编码(对应 system_approval_config.biz_type)
     */
    private static final String PURCHASE_REQUEST_APPROVE_CATEGORY_CODE = "purchase_request_approve";
    private static final String PURCHASE_REQUEST_APPROVE_BIZ_TYPE = "purchase_request_approve";
    @Resource
    private ErpPurchaseRequestMapper purchaseRequestMapper;
@@ -72,11 +67,7 @@
    private ErpSupplierService supplierService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private AdminUserApi adminUserApi;
@@ -245,90 +236,77 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestCanModify(id);
    public void submitPurchaseRequest(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆæœªæäº¤ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
        if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())
                && !ErpPurchaseRequestStatusEnum.REJECT.getStatus().equals(purchaseRequest.getStatus())) {
            throw exception(PURCHASE_REQUEST_SUBMIT_FAIL_NOT_ALLOWED);
        }
        // 2. æ ¡éªŒæ˜¯å¦æœ‰æ˜Žç»†
        List<ErpPurchaseRequestItemDO> items = purchaseRequestItemMapper.selectListByRequestId(id);
        if (CollUtil.isEmpty(items)) {
            throw exception(PURCHASE_REQUEST_NOT_EXISTS); // TODO: æ·»åŠ æ˜Žç»†ä¸ºç©ºçš„é”™è¯¯ç 
            throw exception(PURCHASE_REQUEST_ITEM_EMPTY);
        }
        // 3. åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 4. æ›´æ–°é‡‡è´­ç”³è¯·çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­
        purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                .setId(id)
                .setStatus(ErpPurchaseRequestStatusEnum.PROCESS.getStatus())
                .setProcessInstanceId(processInstanceId));
        // 3. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(PURCHASE_REQUEST_APPROVE_BIZ_TYPE);
        // 4. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœ
        purchaseRequestMapper.submitAndResetAuditInfo(id, ErpPurchaseRequestStatusEnum.PROCESS.getStatus());
    }
    @Override
    public List<Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validatePurchaseRequestApproveCategoryAndProcessDefinition();
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. éåŽ†èŽ·å–æµç¨‹å®šä¹‰è¯¦æƒ…ï¼Œè¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    public Set<Long> getPurchaseRequestApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(PURCHASE_REQUEST_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult) {
    public void auditPurchaseRequest(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!ErpPurchaseRequestStatusEnum.PROCESS.getStatus().equals(purchaseRequest.getStatus())) {
            log.warn("[updatePurchaseRequestAuditStatus] é‡‡è´­ç”³è¯·({}) ä¸å¤„于审批中状态", id);
            throw exception(PURCHASE_REQUEST_AUDIT_FAIL_NOT_PROCESS);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(PURCHASE_REQUEST_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回申请人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(PURCHASE_REQUEST_AUDIT_REJECT_REASON_REQUIRED);
            }
            purchaseRequestMapper.auditPurchaseRequest(id, ErpPurchaseRequestStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToStatus(bpmResult);
        if (newStatus != null) {
            purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                    .setId(id).setStatus(newStatus));
        }
        // 3. å®¡æ‰¹é€šè¿‡ä¸”有供应商,自动生成采购订单
        if (ErpPurchaseRequestStatusEnum.APPROVE.getStatus().equals(newStatus)
                && purchaseRequest.getSupplierId() != null
                && purchaseRequest.getOrderId() == null) {
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见
        purchaseRequestMapper.auditPurchaseRequest(id, ErpPurchaseRequestStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
        // 6. å®¡æ ¸é€šè¿‡ä¸”有供应商、未生成订单时,自动生成采购订单
        if (purchaseRequest.getSupplierId() != null && purchaseRequest.getOrderId() == null) {
            try {
            Long orderId = doGeneratePurchaseOrder(purchaseRequest);
            log.info("[updatePurchaseRequestAuditStatus] é‡‡è´­ç”³è¯·({}) è‡ªåŠ¨ç”Ÿæˆé‡‡è´­è®¢å•({})", id, orderId);
                log.info("[auditPurchaseRequest] é‡‡è´­ç”³è¯·({}) è‡ªåŠ¨ç”Ÿæˆé‡‡è´­è®¢å•({})", id, orderId);
            } catch (Exception e) {
                // ä¸æŠ›å‡ºå¼‚常,避免影响审核结果落库;生成失败可由用户手动触发
                log.error("[auditPurchaseRequest] é‡‡è´­ç”³è¯·({}) è‡ªåŠ¨ç”Ÿæˆé‡‡è´­è®¢å•å¤±è´¥", id, e);
        }
        }
    }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
@@ -421,31 +399,6 @@
                .setOrderNo(orderNo));
        return purchaseOrder.getId();
    }
    /**
     * æ ¡éªŒé‡‡è´­ç”³è¯·å®¡æ‰¹åˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
     */
    private void validatePurchaseRequestApproveCategoryAndProcessDefinition() {
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(PURCHASE_REQUEST_BPM_CATEGORY_NOT_EXISTS);
        }
    }
    /**
     * è½¬æ¢ BPM å®¡æ‰¹ç»“果为状态
     */
    private Integer convertBpmResultToStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return ErpPurchaseRequestStatusEnum.APPROVE.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return ErpPurchaseRequestStatusEnum.REJECT.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return ErpPurchaseRequestStatusEnum.CANCEL.getStatus();
        }
        return null;
    }
    private ErpPurchaseRequestDO validatePurchaseRequestExists(Long id) {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -32,30 +32,32 @@
    // ========== è¯·å‡ç”³è¯· 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_SUBMIT_FAIL_NOT_EDITABLE = 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, "请假审批流程不存在");
    ErrorCode LEAVE_APPLICATION_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_001_006, "只有审批中状态的请假申请才能审核");
    ErrorCode LEAVE_APPLICATION_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_001_007, "审核不通过时必须填写不通过原因");
    // ========== è°ƒå²—申请 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_SUBMIT_FAIL_NOT_EDITABLE = new ErrorCode(1_030_002_002, "只有草稿或审核不通过状态的调岗申请才能提交审批");
    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, "调岗审批流程不存在");
    ErrorCode TRANSFER_APPLICATION_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_002_008, "只有审批中状态的调岗申请才能审核");
    ErrorCode TRANSFER_APPLICATION_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_002_009, "审核不通过时必须填写不通过原因");
    // ========== ç¦»èŒç”³è¯· 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_SUBMIT_FAIL_NOT_EDITABLE = 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, "离职审批流程不存在");
    ErrorCode RESIGNATION_APPLICATION_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_003_006, "只有审批中状态的离职申请才能审核");
    ErrorCode RESIGNATION_APPLICATION_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_003_007, "审核不通过时必须填写不通过原因");
    // ========== ç”¨æˆ·æ•°æ®äº¤æŽ¥ 1-030-007-000 ==========
    ErrorCode USER_HANDOVER_NOT_EXISTS = new ErrorCode(1_030_007_000, "用户交接记录不存在");
@@ -68,7 +70,7 @@
    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_EXCEPTION_SUBMIT_FAIL_NOT_EDITABLE = new ErrorCode(1_030_004_004, "只有草稿或审核不通过状态的考勤异常申请才能提交审批");
    ErrorCode ATTENDANCE_EXCEPTION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_004_017, "考勤异常申请更新失败,原因:不是草稿状态");
    ErrorCode ATTENDANCE_EXCEPTION_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_030_004_018, "考勤异常申请删除失败,原因:不是草稿状态");
    ErrorCode ATTENDANCE_RULE_NOT_EXISTS = new ErrorCode(1_030_004_005, "考勤规则不存在");
@@ -80,17 +82,17 @@
    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, "已打过卡,请勿重复打卡");
    ErrorCode ATTENDANCE_EXCEPTION_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_004_019, "只有审批中状态的考勤异常申请才能审核");
    ErrorCode ATTENDANCE_EXCEPTION_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_004_020, "审核不通过时必须填写不通过原因");
    // ========== ç»©æ•ˆè€ƒæ ¸ 1-030-008-000 ==========
    ErrorCode PERFORMANCE_APPRAISE_NOT_EXISTS = new ErrorCode(1_030_008_000, "绩效考核方案不存在");
    ErrorCode PERFORMANCE_APPRAISE_ITEM_NOT_EXISTS = new ErrorCode(1_030_008_001, "考核指标不存在");
    ErrorCode PERFORMANCE_APPRAISE_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_008_002, "绩效考核方案更新失败,原因:不是草稿状态");
    ErrorCode PERFORMANCE_APPRAISE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_008_003, "绩效考核方案提交失败,原因:不是草稿状态");
    ErrorCode PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_008_004, "更新绩效考核审批状态失败,原因:不是审批中状态");
    ErrorCode PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_008_005, "绩效考核审批流程不存在或未激活,请检查 BPM é…ç½®");
    ErrorCode PERFORMANCE_APPRAISE_SUBMIT_FAIL_NOT_EDITABLE = new ErrorCode(1_030_008_003, "只有草稿或审核不通过状态的绩效考核方案才能提交审批");
    ErrorCode PERFORMANCE_APPRAISE_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_008_004, "只有审批中状态的绩效考核方案才能审核");
    ErrorCode PERFORMANCE_APPRAISE_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_008_005, "审核不通过时必须填写不通过原因");
    ErrorCode PERFORMANCE_APPRAISE_ITEM_EMPTY = new ErrorCode(1_030_008_006, "考核指标不能为空");
    ErrorCode PERFORMANCE_APPRAISE_RESULT_NOT_EXISTS = new ErrorCode(1_030_008_007, "考核结果不存在");
    ErrorCode PERFORMANCE_APPRAISE_RESULT_ITEM_NOT_EXISTS = new ErrorCode(1_030_008_008, "考核指标评分不存在");
@@ -127,11 +129,12 @@
    // ========== è½¬æ­£ç”³è¯· 1-030-006-000 ==========
    ErrorCode REGULARIZE_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_006_000, "转正申请不存在");
    ErrorCode REGULARIZE_APPLICATION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_006_001, "转正申请更新失败,原因:不是草稿状态");
    ErrorCode REGULARIZE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_006_002, "转正申请提交失败,原因:不是草稿状态");
    ErrorCode REGULARIZE_APPLICATION_SUBMIT_FAIL_NOT_EDITABLE = new ErrorCode(1_030_006_002, "只有草稿或审核不通过状态的转正申请才能提交审批");
    ErrorCode REGULARIZE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_006_003, "更新转正申请审批状态失败,原因:不是审批中状态");
    ErrorCode REGULARIZE_APPLICATION_NO_EXISTS = new ErrorCode(1_030_006_004, "生成转正申请单号重复,请重试");
    ErrorCode REGULARIZE_APPLICATION_EXISTS = new ErrorCode(1_030_006_005, "该员工已存在转正申请,请勿重复提交");
    ErrorCode REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_006_006, "转正审批流程不存在");
    ErrorCode REGULARIZE_APPLICATION_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_030_006_006, "只有审批中状态的转正申请才能审核");
    ErrorCode REGULARIZE_APPLICATION_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_030_006_007, "审核不通过时必须填写不通过原因");
    // ========== åŸ¹è®­ç®¡ç† 1-030-009-000 ==========
    ErrorCode TRAINING_NOT_EXISTS = new ErrorCode(1_030_009_000, "培训活动不存在");
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmLeaveApplicationController.java
@@ -5,7 +5,7 @@
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.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationAuditReqVO;
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;
@@ -22,7 +22,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -75,23 +75,28 @@
        return success(leaveApplicationService.getLeaveApplicationPage(pageReqVO));
    }
    @PostMapping("/submit")
    @Operation(summary = "提交请假申请审批")
    @PutMapping("/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) {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        leaveApplicationService.submitLeaveApplication(id, processDefinitionKey, userId);
    @PreAuthorize("@ss.hasPermission('hrm:leave:update')")
    public CommonResult<Boolean> submitLeaveApplication(@RequestParam("id") Long id) {
        leaveApplicationService.submitLeaveApplication(id);
        return success(true);
    }
    @GetMapping("/approve-process-definition-list")
    @Operation(summary = "获取可用的审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('hrm:leave:audit')")
    public CommonResult<Boolean> auditLeaveApplication(@Valid @RequestBody HrmLeaveApplicationAuditReqVO auditReqVO) {
        leaveApplicationService.auditLeaveApplication(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('hrm:leave:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
        return success(leaveApplicationService.getApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(leaveApplicationService.getLeaveApplicationApproverUserIds());
    }
    @GetMapping("/export-excel")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmRegularizeApplicationController.java
@@ -2,7 +2,7 @@
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationAuditReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationSaveReqVO;
@@ -16,8 +16,7 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -69,23 +68,28 @@
        return success(regularizeApplicationService.getRegularizeApplicationPage(pageReqVO));
    }
    @PostMapping("/submit")
    @Operation(summary = "提交转正申请审批")
    @PutMapping("/submit")
    @Operation(summary = "提交审批(草稿/审核不通过→审批中),审批人由审批配置统一指定")
    @Parameter(name = "id", description = "申请ID", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义Key", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:regularize:submit')")
    public CommonResult<Boolean> submitRegularizeApplication(@RequestParam("id") Long id,
                                                             @RequestParam("processDefinitionKey") String processDefinitionKey) {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        regularizeApplicationService.submitRegularizeApplication(id, processDefinitionKey, userId);
    @PreAuthorize("@ss.hasPermission('hrm:regularize:update')")
    public CommonResult<Boolean> submitRegularizeApplication(@RequestParam("id") Long id) {
        regularizeApplicationService.submitRegularizeApplication(id);
        return success(true);
    }
    @GetMapping("/approve-process-definition-list")
    @Operation(summary = "获取可用的审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('hrm:regularize:audit')")
    public CommonResult<Boolean> auditRegularizeApplication(@Valid @RequestBody HrmRegularizeApplicationAuditReqVO auditReqVO) {
        regularizeApplicationService.auditRegularizeApplication(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('hrm:regularize:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
        return success(regularizeApplicationService.getApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(regularizeApplicationService.getRegularizeApplicationApproverUserIds());
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java
@@ -5,14 +5,12 @@
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.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationAuditReqVO;
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.controller.admin.approval.vo.HrmResignationHandoverReqVO;
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;
@@ -25,7 +23,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -38,8 +36,6 @@
    @Resource
    private HrmResignationApplicationService resignationApplicationService;
    @Resource
    private AdminUserApi adminUserApi;
    @PostMapping("/create")
    @Operation(summary = "创建离职申请")
@@ -80,23 +76,28 @@
        return success(resignationApplicationService.getResignationApplicationPage(pageReqVO));
    }
    @PostMapping("/submit")
    @Operation(summary = "提交离职申请审批")
    @PutMapping("/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) {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        resignationApplicationService.submitResignationApplication(id, processDefinitionKey, userId);
    @PreAuthorize("@ss.hasPermission('hrm:resignation:update')")
    public CommonResult<Boolean> submitResignationApplication(@RequestParam("id") Long id) {
        resignationApplicationService.submitResignationApplication(id);
        return success(true);
    }
    @GetMapping("/approve-process-definition-list")
    @Operation(summary = "获取可用的审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('hrm:resignation:audit')")
    public CommonResult<Boolean> auditResignationApplication(@Valid @RequestBody HrmResignationApplicationAuditReqVO auditReqVO) {
        resignationApplicationService.auditResignationApplication(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('hrm:resignation:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
        return success(resignationApplicationService.getApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(resignationApplicationService.getResignationApplicationApproverUserIds());
    }
    @PutMapping("/handover")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmTransferApplicationController.java
@@ -5,7 +5,7 @@
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.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationAuditReqVO;
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;
@@ -24,7 +24,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -79,23 +79,28 @@
        return success(transferApplicationService.getTransferApplicationPage(pageReqVO));
    }
    @PostMapping("/submit")
    @Operation(summary = "提交调岗申请审批")
    @PutMapping("/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) {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        transferApplicationService.submitTransferApplication(id, processDefinitionKey, userId);
    @PreAuthorize("@ss.hasPermission('hrm:transfer:update')")
    public CommonResult<Boolean> submitTransferApplication(@RequestParam("id") Long id) {
        transferApplicationService.submitTransferApplication(id);
        return success(true);
    }
    @GetMapping("/approve-process-definition-list")
    @Operation(summary = "获取可用的审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('hrm:transfer:audit')")
    public CommonResult<Boolean> auditTransferApplication(@Valid @RequestBody HrmTransferApplicationAuditReqVO auditReqVO) {
        transferApplicationService.auditTransferApplication(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('hrm:transfer:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
        return success(transferApplicationService.getApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(transferApplicationService.getTransferApplicationApproverUserIds());
    }
    @GetMapping("/export-excel")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ç®¡ç†åŽå° - è¯·å‡ç”³è¯·å®¡æ ¸ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - è¯·å‡ç”³è¯·å®¡æ ¸ Request VO")
@Data
public class HrmLeaveApplicationAuditReqVO {
    @Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "申请ID不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(通过时选填 / ä¸é€šè¿‡æ—¶å¿…填)", example = "请假事由需补充说明")
    private String reviewRemark;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java
@@ -54,8 +54,17 @@
    @Schema(description = "审批状态名称", example = "草稿")
    private String statusName;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ID", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "审核时间", example = "2024-01-01 10:00:00")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "同意")
    private String reviewRemark;
    @Schema(description = "审批完成时间", example = "2024-01-01 10:00:00")
    private LocalDateTime approveTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ç®¡ç†åŽå° - è½¬æ­£ç”³è¯·å®¡æ ¸ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - è½¬æ­£ç”³è¯·å®¡æ ¸ Request VO")
@Data
public class HrmRegularizeApplicationAuditReqVO {
    @Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "申请ID不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(通过时选填 / ä¸é€šè¿‡æ—¶å¿…填)", example = "转正考核需补充材料")
    private String reviewRemark;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmRegularizeApplicationRespVO.java
@@ -52,8 +52,17 @@
    @Schema(description = "审批状态名称", example = "草稿")
    private String statusName;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ID", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "审核时间", example = "2024-01-01 10:00:00")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "同意")
    private String reviewRemark;
    @Schema(description = "审批完成时间", example = "2024-01-01 10:00:00")
    private LocalDateTime approveTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ç®¡ç†åŽå° - ç¦»èŒç”³è¯·å®¡æ ¸ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç¦»èŒç”³è¯·å®¡æ ¸ Request VO")
@Data
public class HrmResignationApplicationAuditReqVO {
    @Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "申请ID不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(通过时选填 / ä¸é€šè¿‡æ—¶å¿…填)", example = "需先完成工作交接")
    private String reviewRemark;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java
@@ -66,8 +66,17 @@
    @Schema(description = "审批状态名称", example = "草稿")
    private String statusName;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ID", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "审核时间", example = "2024-01-01 10:00:00")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "同意")
    private String reviewRemark;
    @Schema(description = "审批完成时间", example = "2024-01-01 10:00:00")
    private LocalDateTime approveTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ç®¡ç†åŽå° - è°ƒå²—申请审核 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - è°ƒå²—申请审核 Request VO")
@Data
public class HrmTransferApplicationAuditReqVO {
    @Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "申请ID不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(通过时选填 / ä¸é€šè¿‡æ—¶å¿…填)", example = "目标岗位需再次确认")
    private String reviewRemark;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java
@@ -66,8 +66,17 @@
    @Schema(description = "审批状态名称", example = "草稿")
    private String statusName;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ID", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "李四")
    private String reviewerName;
    @Schema(description = "审核时间", example = "2024-01-01 10:00:00")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "同意调岗")
    private String reviewRemark;
    @Schema(description = "审批完成时间", example = "2024-01-01 10:00:00")
    private LocalDateTime approveTime;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java
@@ -4,6 +4,7 @@
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionAuditReqVO;
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;
@@ -143,16 +144,28 @@
        }
    }
    @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);
    @PutMapping("/submit")
    @Operation(summary = "提交审批(草稿/审核不通过→审批中),审批人由审批配置统一指定")
    @Parameter(name = "id", description = "申请ID", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:attendance-exception:submit')")
    public CommonResult<Boolean> submitAttendanceException(@RequestParam("id") Long id) {
        attendanceExceptionService.submitAttendanceException(id);
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审核(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('hrm:attendance-exception:audit')")
    public CommonResult<Boolean> auditAttendanceException(@Valid @RequestBody HrmAttendanceExceptionAuditReqVO auditReqVO) {
        attendanceExceptionService.auditAttendanceException(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('hrm:attendance:query')")
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(attendanceExceptionService.getAttendanceExceptionApproverUserIds());
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ç®¡ç†åŽå° - è€ƒå‹¤å¼‚常申请审核 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - è€ƒå‹¤å¼‚常申请审核 Request VO")
@Data
public class HrmAttendanceExceptionAuditReqVO {
    @Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "申请ID不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(通过时选填 / ä¸é€šè¿‡æ—¶å¿…填)", example = "补卡时间需与门禁记录一致")
    private String reviewRemark;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java
@@ -61,8 +61,17 @@
    @DictFormat("hrm_audit_status")
    private Integer status;
    @Schema(description = "BPM流程实例ID")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "李四")
    private String reviewerName;
    @Schema(description = "审核时间", example = "2024-01-01 10:00:00")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "同意补卡")
    private String reviewRemark;
    @Schema(description = "审批完成时间")
    @ExcelProperty("审批完成时间")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/HrmPerformanceAppraiseController.java
@@ -21,16 +21,13 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ")
@RestController
@@ -112,30 +109,28 @@
    // ========== å®¡æ‰¹ ==========
    @PostMapping("/submit")
    @Operation(summary = "提交绩效考核方案审批")
    @PutMapping("/submit")
    @Operation(summary = "提交审批(草稿/审核不通过→审批中),审批人由审批配置统一指定")
    @Parameter(name = "id", description = "方案编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义Key", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:submit')")
    public CommonResult<Boolean> submitAppraise(@RequestParam("id") Long id,
                                                @RequestParam("processDefinitionKey") String processDefinitionKey) {
        appraiseService.submitAppraise(id, processDefinitionKey, getLoginUserId());
    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:update')")
    public CommonResult<Boolean> submitAppraise(@RequestParam("id") Long id) {
        appraiseService.submitAppraise(id);
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审批绩效考核方案(通过/驳回)")
    @Operation(summary = "审核(审批中→审核通过/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:audit')")
    public CommonResult<Boolean> auditAppraise(@Valid @RequestBody HrmPerformanceAppraiseAuditReqVO auditReqVO) {
        appraiseService.auditAppraise(auditReqVO);
        appraiseService.auditAppraise(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获得绩效考核方案审批流程定义列表")
    @GetMapping("/approver-ids")
    @Operation(summary = "获得审批人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('hrm:performance-appraise:query')")
    public CommonResult<List<Map<String, Object>>> getAppraiseApproveProcessDefinitionList() {
        return success(appraiseService.getAppraiseApproveProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(appraiseService.getAppraiseApproverUserIds());
    }
    // ========== è¯„分/结果 ==========
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseAuditReqVO.java
@@ -5,9 +5,9 @@
import lombok.Data;
/**
 * HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ å®¡æ‰¹ Request VO
 * HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ å®¡æ ¸ Request VO
 */
@Schema(description = "HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ - å®¡æ‰¹ Request VO")
@Schema(description = "HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ - å®¡æ ¸ Request VO")
@Data
public class HrmPerformanceAppraiseAuditReqVO {
@@ -15,11 +15,11 @@
    @NotNull(message = "方案编号不能为空")
    private Long id;
    @Schema(description = "审批结果(20 é€šè¿‡ / 30 é©³å›žï¼‰", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
    @NotNull(message = "审批结果不能为空")
    private Integer auditStatus;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审批备注", example = "同意发布")
    private String remark;
    @Schema(description = "审核意见(通过时选填 / ä¸é€šè¿‡æ—¶å¿…填)", example = "考核指标需补充说明")
    private String reviewRemark;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/performanceappraise/vo/HrmPerformanceAppraiseRespVO.java
@@ -55,17 +55,17 @@
    @Schema(description = "审批状态名称", example = "草稿")
    private String auditStatusName;
    @Schema(description = "审批流程实例编号")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人", example = "1")
    private Long auditUserId;
    @Schema(description = "审核人姓名", example = "李四")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime auditTime;
    private LocalDateTime reviewTime;
    @Schema(description = "审核备注")
    private String auditRemark;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)")
    private String reviewRemark;
    @Schema(description = "备注")
    @ExcelProperty("备注")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java
@@ -85,9 +85,27 @@
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ID
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  hrm_leave_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å®¡æ‰¹å®Œæˆæ—¶é—´
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmRegularizeApplicationDO.java
@@ -80,9 +80,27 @@
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ID
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  hrm_regularize_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private java.time.LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å®¡æ‰¹å®Œæˆæ—¶é—´
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java
@@ -98,9 +98,27 @@
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ID
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  hrm_resignation_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private java.time.LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å®¡æ‰¹å®Œæˆæ—¶é—´
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java
@@ -95,9 +95,27 @@
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ID
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  hrm_transfer_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private java.time.LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å®¡æ‰¹å®Œæˆæ—¶é—´
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java
@@ -73,9 +73,27 @@
    private Integer status;
    /**
     * BPM流程实例ID
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  hrm_attendance_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å®¡æ‰¹å®Œæˆæ—¶é—´
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/performanceappraise/HrmPerformanceAppraiseDO.java
@@ -68,24 +68,27 @@
    private Integer auditStatus;
    /**
     * å®¡æ‰¹æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  performance_appraise_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äºº
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private Long auditUserId;
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime auditTime;
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸å¤‡æ³¨
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String auditRemark;
    private String reviewRemark;
    /**
     * å¤‡æ³¨
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java
@@ -3,6 +3,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 com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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;
@@ -34,8 +35,47 @@
        return selectOne(HrmLeaveApplicationDO::getNo, no);
    }
    default HrmLeaveApplicationDO selectByProcessInstanceId(String processInstanceId) {
        return selectOne(HrmLeaveApplicationDO::getProcessInstanceId, processInstanceId);
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ LambdaUpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     *
     * @param id     ç”³è¯·ID
     * @param status ç›®æ ‡çŠ¶æ€
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<HrmLeaveApplicationDO>()
                .eq(HrmLeaveApplicationDO::getId, id)
                .set(HrmLeaveApplicationDO::getStatus, status)
                .set(HrmLeaveApplicationDO::getReviewerId, null)
                .set(HrmLeaveApplicationDO::getReviewerName, null)
                .set(HrmLeaveApplicationDO::getReviewTime, null)
                .set(HrmLeaveApplicationDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           ç”³è¯·ID
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditLeaveApplication(Long id, Integer status, Long reviewerId,
                                      String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<HrmLeaveApplicationDO>()
                .eq(HrmLeaveApplicationDO::getId, id)
                .set(HrmLeaveApplicationDO::getStatus, status)
                .set(HrmLeaveApplicationDO::getReviewerId, reviewerId)
                .set(HrmLeaveApplicationDO::getReviewerName, reviewerName)
                .set(HrmLeaveApplicationDO::getReviewTime, LocalDateTime.now())
                .set(HrmLeaveApplicationDO::getReviewRemark, reviewRemark));
    }
    /**
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmRegularizeApplicationMapper.java
@@ -5,7 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmRegularizeApplicationDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * è½¬æ­£ç”³è¯· Mapper
@@ -30,8 +33,47 @@
        return selectOne(HrmRegularizeApplicationDO::getNo, no);
    }
    default HrmRegularizeApplicationDO selectByProcessInstanceId(String processInstanceId) {
        return selectOne(HrmRegularizeApplicationDO::getProcessInstanceId, processInstanceId);
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ LambdaUpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     *
     * @param id     ç”³è¯·ID
     * @param status ç›®æ ‡çŠ¶æ€
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<HrmRegularizeApplicationDO>()
                .eq(HrmRegularizeApplicationDO::getId, id)
                .set(HrmRegularizeApplicationDO::getStatus, status)
                .set(HrmRegularizeApplicationDO::getReviewerId, null)
                .set(HrmRegularizeApplicationDO::getReviewerName, null)
                .set(HrmRegularizeApplicationDO::getReviewTime, null)
                .set(HrmRegularizeApplicationDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           ç”³è¯·ID
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditRegularizeApplication(Long id, Integer status, Long reviewerId,
                                           String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<HrmRegularizeApplicationDO>()
                .eq(HrmRegularizeApplicationDO::getId, id)
                .set(HrmRegularizeApplicationDO::getStatus, status)
                .set(HrmRegularizeApplicationDO::getReviewerId, reviewerId)
                .set(HrmRegularizeApplicationDO::getReviewerName, reviewerName)
                .set(HrmRegularizeApplicationDO::getReviewTime, LocalDateTime.now())
                .set(HrmRegularizeApplicationDO::getReviewRemark, reviewRemark));
    }
    default HrmRegularizeApplicationDO selectByUserIdAndStatusNot(Long userId, Integer status) {
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmResignationApplicationMapper.java
@@ -5,7 +5,10 @@
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 com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * ç¦»èŒç”³è¯· Mapper
@@ -30,8 +33,47 @@
        return selectOne(HrmResignationApplicationDO::getNo, no);
    }
    default HrmResignationApplicationDO selectByProcessInstanceId(String processInstanceId) {
        return selectOne(HrmResignationApplicationDO::getProcessInstanceId, processInstanceId);
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ LambdaUpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     *
     * @param id     ç”³è¯·ID
     * @param status ç›®æ ‡çŠ¶æ€
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<HrmResignationApplicationDO>()
                .eq(HrmResignationApplicationDO::getId, id)
                .set(HrmResignationApplicationDO::getStatus, status)
                .set(HrmResignationApplicationDO::getReviewerId, null)
                .set(HrmResignationApplicationDO::getReviewerName, null)
                .set(HrmResignationApplicationDO::getReviewTime, null)
                .set(HrmResignationApplicationDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           ç”³è¯·ID
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditResignationApplication(Long id, Integer status, Long reviewerId,
                                            String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<HrmResignationApplicationDO>()
                .eq(HrmResignationApplicationDO::getId, id)
                .set(HrmResignationApplicationDO::getStatus, status)
                .set(HrmResignationApplicationDO::getReviewerId, reviewerId)
                .set(HrmResignationApplicationDO::getReviewerName, reviewerName)
                .set(HrmResignationApplicationDO::getReviewTime, LocalDateTime.now())
                .set(HrmResignationApplicationDO::getReviewRemark, reviewRemark));
    }
    default HrmResignationApplicationDO selectByUserIdAndStatusNot(Long userId, Integer status) {
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmTransferApplicationMapper.java
@@ -5,7 +5,10 @@
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 com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * è°ƒå²—申请 Mapper
@@ -31,8 +34,48 @@
        return selectOne(HrmTransferApplicationDO::getNo, no);
    }
    default HrmTransferApplicationDO selectByProcessInstanceId(String processInstanceId) {
        return selectOne(HrmTransferApplicationDO::getProcessInstanceId, processInstanceId);
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ LambdaUpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     *
     * @param id     ç”³è¯·ID
     * @param status ç›®æ ‡çŠ¶æ€
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<HrmTransferApplicationDO>()
                .eq(HrmTransferApplicationDO::getId, id)
                .set(HrmTransferApplicationDO::getStatus, status)
                .set(HrmTransferApplicationDO::getReviewerId, null)
                .set(HrmTransferApplicationDO::getReviewerName, null)
                .set(HrmTransferApplicationDO::getReviewTime, null)
                .set(HrmTransferApplicationDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           ç”³è¯·ID
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditTransferApplication(Long id, Integer status, Long reviewerId,
                                         String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<HrmTransferApplicationDO>()
                .eq(HrmTransferApplicationDO::getId, id)
                .set(HrmTransferApplicationDO::getStatus, status)
                .set(HrmTransferApplicationDO::getReviewerId, reviewerId)
                .set(HrmTransferApplicationDO::getReviewerName, reviewerName)
                .set(HrmTransferApplicationDO::getReviewTime, LocalDateTime.now())
                .set(HrmTransferApplicationDO::getReviewRemark, reviewRemark)
                .set(HrmTransferApplicationDO::getApproveTime, LocalDateTime.now()));
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceExceptionMapper.java
@@ -5,7 +5,10 @@
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 com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * è€ƒå‹¤å¼‚常申请 Mapper
@@ -29,8 +32,48 @@
        return selectOne(HrmAttendanceExceptionDO::getNo, no);
    }
    default HrmAttendanceExceptionDO selectByProcessInstanceId(String processInstanceId) {
        return selectOne(HrmAttendanceExceptionDO::getProcessInstanceId, processInstanceId);
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ LambdaUpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     *
     * @param id     ç”³è¯·ID
     * @param status ç›®æ ‡çŠ¶æ€
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<HrmAttendanceExceptionDO>()
                .eq(HrmAttendanceExceptionDO::getId, id)
                .set(HrmAttendanceExceptionDO::getStatus, status)
                .set(HrmAttendanceExceptionDO::getReviewerId, null)
                .set(HrmAttendanceExceptionDO::getReviewerName, null)
                .set(HrmAttendanceExceptionDO::getReviewTime, null)
                .set(HrmAttendanceExceptionDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           ç”³è¯·ID
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditAttendanceException(Long id, Integer status, Long reviewerId,
                                         String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<HrmAttendanceExceptionDO>()
                .eq(HrmAttendanceExceptionDO::getId, id)
                .set(HrmAttendanceExceptionDO::getStatus, status)
                .set(HrmAttendanceExceptionDO::getReviewerId, reviewerId)
                .set(HrmAttendanceExceptionDO::getReviewerName, reviewerName)
                .set(HrmAttendanceExceptionDO::getReviewTime, LocalDateTime.now())
                .set(HrmAttendanceExceptionDO::getReviewRemark, reviewRemark)
                .set(HrmAttendanceExceptionDO::getApproveTime, LocalDateTime.now()));
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/performanceappraise/HrmPerformanceAppraiseMapper.java
@@ -5,7 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo.HrmPerformanceAppraisePageReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ Mapper
@@ -21,6 +24,51 @@
                .orderByDesc(HrmPerformanceAppraiseDO::getId));
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ LambdaUpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人/审核时间/审核意见清空。
     *
     * @param id          æ–¹æ¡ˆç¼–号
     * @param auditStatus ç›®æ ‡å®¡æ‰¹çŠ¶æ€
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer auditStatus) {
        return update(null, new LambdaUpdateWrapper<HrmPerformanceAppraiseDO>()
                .eq(HrmPerformanceAppraiseDO::getId, id)
                .set(HrmPerformanceAppraiseDO::getAuditStatus, auditStatus)
                .set(HrmPerformanceAppraiseDO::getReviewerId, null)
                .set(HrmPerformanceAppraiseDO::getReviewerName, null)
                .set(HrmPerformanceAppraiseDO::getReviewTime, null)
                .set(HrmPerformanceAppraiseDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           æ–¹æ¡ˆç¼–号
     * @param auditStatus  ç›®æ ‡å®¡æ‰¹çŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param status       æ–¹æ¡ˆçŠ¶æ€ï¼ˆå®¡æ ¸é€šè¿‡æ—¶ç½®ä¸ºå·²å‘å¸ƒï¼Œå¦åˆ™ä¿æŒä¸å˜ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditAppraise(Long id, Integer auditStatus, Integer status, Long reviewerId,
                              String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<HrmPerformanceAppraiseDO>()
                .eq(HrmPerformanceAppraiseDO::getId, id)
                .set(HrmPerformanceAppraiseDO::getAuditStatus, auditStatus)
                .set(HrmPerformanceAppraiseDO::getStatus, status)
                .set(HrmPerformanceAppraiseDO::getReviewerId, reviewerId)
                .set(HrmPerformanceAppraiseDO::getReviewerName, reviewerName)
                .set(HrmPerformanceAppraiseDO::getReviewTime, LocalDateTime.now())
                .set(HrmPerformanceAppraiseDO::getReviewRemark, reviewRemark));
    }
    default HrmPerformanceAppraiseDO selectMonthlyCompletedByPeriod(String period) {
        return selectOne(new LambdaQueryWrapperX<HrmPerformanceAppraiseDO>()
                .eq(HrmPerformanceAppraiseDO::getPeriodType, 1)
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationService.java
@@ -7,8 +7,7 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * è¯·å‡ç”³è¯· Service æŽ¥å£
@@ -16,16 +15,6 @@
 * @author è¶…级管理员
 */
public interface HrmLeaveApplicationService {
    /**
     * BPM è¯·å‡å®¡æ‰¹æµç¨‹æ ‡è¯†
     */
    String BPM_PROCESS_DEFINITION_KEY = "hrm_leave";
    /**
     * BPM è¯·å‡å®¡æ‰¹åˆ†ç±»ç¼–码
     */
    String APPROVE_CATEGORY_CODE = "hrm_leave_approve";
    /**
     * åˆ›å»ºè¯·å‡ç”³è¯·
@@ -74,35 +63,28 @@
    PageResult<HrmLeaveApplicationRespVO> getLeaveApplicationPage(HrmLeaveApplicationPageReqVO pageReqVO);
    /**
     * æäº¤è¯·å‡ç”³è¯·å®¡æ‰¹
     * æäº¤è¯·å‡ç”³è¯·å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤æ—¶æ— éœ€é€‰æ‹©ã€‚
     *
     * @param id ç”³è¯·ID
     * @param processDefinitionKey æµç¨‹å®šä¹‰Key
     * @param userId æäº¤äººID
     */
    void submitLeaveApplication(Long id, String processDefinitionKey, Long userId);
    void submitLeaveApplication(Long id);
    /**
     * èŽ·å–å¯ç”¨çš„å®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨
     * èŽ·å¾—è¯·å‡ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›å‰ç«¯åˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’®
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    List<Map<String, Object>> getApproveProcessDefinitionList();
    Set<Long> getLeaveApplicationApproverUserIds();
    /**
     * æ›´æ–°è¯·å‡ç”³è¯·å®¡æ‰¹çŠ¶æ€ï¼ˆBPM å›žè°ƒï¼‰
     * å®¡æ ¸è¯·å‡ç”³è¯·ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * @param id ç”³è¯·ID
     * @param status å®¡æ‰¹çŠ¶æ€
     * @param pass         æ˜¯å¦é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å¿…填)
     */
    void updateLeaveApplicationAuditStatus(Long id, Integer status);
    /**
     * æ ¹æ®æµç¨‹å®žä¾‹ID获取请假申请
     *
     * @param processInstanceId æµç¨‹å®žä¾‹ID
     * @return è¯·å‡ç”³è¯·
     */
    HrmLeaveApplicationDO getLeaveApplicationByProcessInstanceId(String processInstanceId);
    void auditLeaveApplication(Long id, Boolean pass, String reviewRemark);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java
@@ -1,15 +1,9 @@
package cn.iocoder.yudao.module.hrm.service.approval;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO;
@@ -18,6 +12,7 @@
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.approval.ApprovalConfigApi;
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.storage.StorageAttachmentApi;
@@ -25,14 +20,11 @@
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;
@@ -40,7 +32,7 @@
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.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
/**
@@ -53,6 +45,13 @@
@Slf4j
public class HrmLeaveApplicationServiceImpl implements HrmLeaveApplicationService {
    /**
     * è¯·å‡ç”³è¯·å®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String LEAVE_APPROVE_BIZ_TYPE = "hrm_leave_approve";
    @Resource
    private HrmLeaveApplicationMapper leaveApplicationMapper;
    @Resource
@@ -62,13 +61,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -215,121 +210,64 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitLeaveApplication(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒè¯·å‡ç”³è¯·æ˜¯å¦å­˜åœ¨
    public void submitLeaveApplication(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        HrmLeaveApplicationDO application = validateLeaveApplicationExists(id);
        if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
            throw exception(LEAVE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
                && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(LEAVE_APPLICATION_SUBMIT_FAIL_NOT_EDITABLE);
        }
        // 2. æ ¡éªŒåˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validateApproveCategoryAndProcessDefinition();
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(LEAVE_APPROVE_BIZ_TYPE);
        // 3. æ ¡éªŒä¼ å…¥çš„æµç¨‹å®šä¹‰ Key æ˜¯å¦å±žäºŽè¯¥åˆ†ç±»ä¸”处于激活状态
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        if (processDefinition == null) {
            throw exception(LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        // 3. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        leaveApplicationMapper.submitAndResetAuditInfo(id, HrmAuditStatusEnum.PROCESS.getStatus());
        }
        // 4. åˆ›å»ºå®¡æ‰¹æµç¨‹å®žä¾‹
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
    @Override
    public Set<Long> getLeaveApplicationApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(LEAVE_APPROVE_BIZ_TYPE);
    }
        // 5. æ›´æ–°è¯·å‡ç”³è¯·å·¥ä½œæµç¼–号
        leaveApplicationMapper.updateById(HrmLeaveApplicationDO.builder()
                .id(id)
                .processInstanceId(processInstanceId)
                .status(HrmAuditStatusEnum.PROCESS.getStatus())
                .build());
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditLeaveApplication(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        HrmLeaveApplicationDO application = validateLeaveApplicationExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(LEAVE_APPLICATION_AUDIT_FAIL_NOT_PROCESS);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(LEAVE_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(LEAVE_APPLICATION_AUDIT_REJECT_REASON_REQUIRED);
            }
            leaveApplicationMapper.auditLeaveApplication(id, HrmAuditStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为审核通过
        leaveApplicationMapper.auditLeaveApplication(id, HrmAuditStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    /**
     * æ ¡éªŒ BPM è¯·å‡å®¡æ‰¹åˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    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);
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        // 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);
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationService.java
@@ -6,8 +6,7 @@
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationSaveReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmRegularizeApplicationDO;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * è½¬æ­£ç”³è¯· Service æŽ¥å£
@@ -15,11 +14,6 @@
 * @author è¶…级管理员
 */
public interface HrmRegularizeApplicationService {
    /**
     * å®¡æ‰¹åˆ†ç±»ç¼–码:转正审批
     */
    String APPROVE_CATEGORY_CODE = "hrm_regularize_approve";
    /**
     * åˆ›å»ºè½¬æ­£ç”³è¯·
@@ -68,35 +62,30 @@
    PageResult<HrmRegularizeApplicationRespVO> getRegularizeApplicationPage(HrmRegularizeApplicationPageReqVO pageReqVO);
    /**
     * æäº¤è½¬æ­£ç”³è¯·å®¡æ‰¹
     * æäº¤è½¬æ­£ç”³è¯·å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤æ—¶æ— éœ€é€‰æ‹©ã€‚
     *
     * @param id ç”³è¯·ID
     * @param processDefinitionKey æµç¨‹å®šä¹‰Key
     * @param userId å‘起人ID
     */
    void submitRegularizeApplication(Long id, String processDefinitionKey, Long userId);
    void submitRegularizeApplication(Long id);
    /**
     * æ›´æ–°è½¬æ­£ç”³è¯·å®¡æ‰¹çŠ¶æ€
     * èŽ·å¾—è½¬æ­£ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›å‰ç«¯åˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’®
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getRegularizeApplicationApproverUserIds();
    /**
     * å®¡æ ¸è½¬æ­£ç”³è¯·ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * å®¡æ ¸é€šè¿‡æ—¶å›žå†™å‘˜å·¥ä¸»æ¡£ï¼ˆçŠ¶æ€=在职,用工性质=正式,转正日期)。
     *
     * @param id ç”³è¯·ID
     * @param status å®¡æ‰¹çŠ¶æ€
     * @param pass         æ˜¯å¦é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å¿…填)
     */
    void updateRegularizeApplicationAuditStatus(Long id, Integer status);
    /**
     * æ ¹æ®æµç¨‹å®žä¾‹ID获取转正申请
     *
     * @param processInstanceId æµç¨‹å®žä¾‹ID
     * @return ç”³è¯·ä¿¡æ¯
     */
    HrmRegularizeApplicationDO getRegularizeApplicationByProcessInstanceId(String processInstanceId);
    /**
     * èŽ·å–è½¬æ­£å®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     */
    List<Map<String, Object>> getApproveProcessDefinitionList();
    void auditRegularizeApplication(Long id, Boolean pass, String reviewRemark);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmRegularizeApplicationServiceImpl.java
@@ -1,23 +1,20 @@
package cn.iocoder.yudao.module.hrm.service.approval;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmRegularizeApplicationSaveReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmRegularizeApplicationDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmRegularizeApplicationMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmEmploymentTypeEnum;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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;
@@ -26,23 +23,20 @@
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.time.LocalDate;
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.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
/**
@@ -55,6 +49,23 @@
@Slf4j
public class HrmRegularizeApplicationServiceImpl implements HrmRegularizeApplicationService {
    /**
     * è½¬æ­£ç”³è¯·å®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String REGULARIZE_APPROVE_BIZ_TYPE = "hrm_regularize_approve";
    /**
     * å‘˜å·¥çŠ¶æ€ï¼šåœ¨èŒ
     */
    private static final Integer EMPLOYEE_STATUS_ACTIVE = 1;
    /**
     * ç”¨å·¥æ€§è´¨ï¼šæ­£å¼
     */
    private static final Integer EMPLOYMENT_TYPE_FORMAL = 1;
    @Resource
    private HrmRegularizeApplicationMapper regularizeApplicationMapper;
    @Resource
@@ -66,11 +77,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private HrmEmployeeMapper employeeMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -220,88 +229,87 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitRegularizeApplication(Long id, String processDefinitionKey, Long userId) {
    public void submitRegularizeApplication(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        HrmRegularizeApplicationDO application = validateRegularizeApplicationExists(id);
        if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
            throw exception(REGULARIZE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
        }
        validateApproveCategoryAndProcessDefinition();
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        if (processDefinition == null) {
            throw exception(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
        regularizeApplicationMapper.updateById(HrmRegularizeApplicationDO.builder()
                .id(id)
                .processInstanceId(processInstanceId)
                .status(HrmAuditStatusEnum.PROCESS.getStatus())
                .build());
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
                && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(REGULARIZE_APPLICATION_SUBMIT_FAIL_NOT_EDITABLE);
    }
    private void validateApproveCategoryAndProcessDefinition() {
        List<String> codes = Collections.singletonList(APPROVE_CATEGORY_CODE);
        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(codes);
        if (!categoryMap.containsKey(APPROVE_CATEGORY_CODE)) {
            throw exception(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            throw exception(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
        boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended());
        if (!hasActiveDefinition) {
            throw exception(REGULARIZE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(REGULARIZE_APPROVE_BIZ_TYPE);
        // 3. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        regularizeApplicationMapper.submitAndResetAuditInfo(id, HrmAuditStatusEnum.PROCESS.getStatus());
    }
    @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;
    public Set<Long> getRegularizeApplicationApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(REGULARIZE_APPROVE_BIZ_TYPE);
    }
    @Override
    public void updateRegularizeApplicationAuditStatus(Long id, Integer status) {
    @Transactional(rollbackFor = Exception.class)
    public void auditRegularizeApplication(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        HrmRegularizeApplicationDO application = validateRegularizeApplicationExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            log.error("[updateRegularizeApplicationAuditStatus][application({}) ä¸å¤„于审批中,无法更新审批结果({})]", id, status);
            throw exception(REGULARIZE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
            throw exception(REGULARIZE_APPLICATION_AUDIT_FAIL_NOT_PROCESS);
        }
        regularizeApplicationMapper.updateById(HrmRegularizeApplicationDO.builder()
                .id(id)
                .status(status)
                .approveTime(java.time.LocalDateTime.now())
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(REGULARIZE_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(REGULARIZE_APPLICATION_AUDIT_REJECT_REASON_REQUIRED);
            }
            regularizeApplicationMapper.auditRegularizeApplication(id, HrmAuditStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为审核通过
        regularizeApplicationMapper.auditRegularizeApplication(id, HrmAuditStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
        // 6. å®¡æ ¸é€šè¿‡åŽç½®ä¸šåŠ¡é€»è¾‘ï¼šå›žå†™å‘˜å·¥ä¸»æ¡£ï¼ˆçŠ¶æ€=在职,用工性质=正式,转正日期 = æ‹Ÿè½¬æ­£æ—¥æœŸæˆ–当天)
        writeBackEmployee(application);
    }
    /**
     * å®¡æ ¸é€šè¿‡åŽå›žå†™å‘˜å·¥ä¸»æ¡£
     *
     * @param application è½¬æ­£ç”³è¯·
     */
    private void writeBackEmployee(HrmRegularizeApplicationDO application) {
        HrmEmployeeDO employee = employeeMapper.selectByUserId(application.getUserId());
        if (employee == null) {
            log.warn("[writeBackEmployee] è½¬æ­£ç”³è¯·({}) ç”¨æˆ·({}) æœªæ‰¾åˆ°å‘˜å·¥æ¡£æ¡ˆï¼Œè·³è¿‡å›žå†™", application.getId(), application.getUserId());
            return;
        }
        LocalDate regularDate = application.getExpectedDate() != null ? application.getExpectedDate() : LocalDate.now();
        employeeMapper.updateById(HrmEmployeeDO.builder()
                .id(employee.getId())
                .employeeStatus(EMPLOYEE_STATUS_ACTIVE)
                .employmentType(EMPLOYMENT_TYPE_FORMAL)
                .regularDate(regularDate)
                .build());
    }
    @Override
    public HrmRegularizeApplicationDO getRegularizeApplicationByProcessInstanceId(String processInstanceId) {
        return regularizeApplicationMapper.selectByProcessInstanceId(processInstanceId);
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java
@@ -8,8 +8,7 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * ç¦»èŒç”³è¯· Service æŽ¥å£
@@ -17,16 +16,6 @@
 * @author è¶…级管理员
 */
public interface HrmResignationApplicationService {
    /**
     * BPM ç¦»èŒå®¡æ‰¹æµç¨‹æ ‡è¯†
     */
    String BPM_PROCESS_DEFINITION_KEY = "hrm_resignation";
    /**
     * BPM ç¦»èŒå®¡æ‰¹åˆ†ç±»ç¼–码
     */
    String APPROVE_CATEGORY_CODE = "hrm_resignation_approve";
    /**
     * åˆ›å»ºç¦»èŒç”³è¯·
@@ -75,36 +64,31 @@
    PageResult<HrmResignationApplicationRespVO> getResignationApplicationPage(HrmResignationApplicationPageReqVO pageReqVO);
    /**
     * æäº¤ç¦»èŒç”³è¯·å®¡æ‰¹
     * æäº¤ç¦»èŒç”³è¯·å®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤æ—¶æ— éœ€é€‰æ‹©ã€‚
     *
     * @param id ç”³è¯·ID
     * @param processDefinitionKey æµç¨‹å®šä¹‰Key
     * @param userId æäº¤äººID
     */
    void submitResignationApplication(Long id, String processDefinitionKey, Long userId);
    void submitResignationApplication(Long id);
    /**
     * èŽ·å–å¯ç”¨çš„å®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨
     * èŽ·å¾—ç¦»èŒç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›å‰ç«¯åˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’®
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    List<Map<String, Object>> getApproveProcessDefinitionList();
    Set<Long> getResignationApplicationApproverUserIds();
    /**
     * æ›´æ–°ç¦»èŒç”³è¯·å®¡æ‰¹çŠ¶æ€ï¼ˆBPM å›žè°ƒï¼‰
     * å®¡æ ¸ç¦»èŒç”³è¯·ï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * å®¡æ ¸é€šè¿‡æ—¶å›žå†™å‘˜å·¥ä¸»æ¡£ï¼ˆçŠ¶æ€=离职,离职日期)。
     *
     * @param id ç”³è¯·ID
     * @param status å®¡æ‰¹çŠ¶æ€
     * @param pass         æ˜¯å¦é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å¿…填)
     */
    void updateResignationApplicationAuditStatus(Long id, Integer status);
    /**
     * æ ¹æ®æµç¨‹å®žä¾‹ID获取离职申请
     *
     * @param processInstanceId æµç¨‹å®žä¾‹ID
     * @return ç¦»èŒç”³è¯·
     */
    HrmResignationApplicationDO getResignationApplicationByProcessInstanceId(String processInstanceId);
    void auditResignationApplication(Long id, Boolean pass, String reviewRemark);
    /**
     * æ›´æ–°ç¦»èŒç”³è¯·äº¤æŽ¥çŠ¶æ€
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
@@ -1,24 +1,21 @@
package cn.iocoder.yudao.module.hrm.service.approval;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.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.controller.admin.approval.vo.HrmResignationHandoverReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmResignationApplicationMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmResignationTypeEnum;
import cn.iocoder.yudao.module.hrm.service.handover.HrmUserHandoverService;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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.storage.StorageAttachmentApi;
@@ -26,14 +23,12 @@
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.time.LocalDate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -44,7 +39,7 @@
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
/**
@@ -61,6 +56,18 @@
    private static final Integer HANDOVER_STATUS_IN_PROGRESS = 10;
    private static final Integer HANDOVER_STATUS_COMPLETED = 20;
    /**
     * ç¦»èŒç”³è¯·å®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String RESIGNATION_APPROVE_BIZ_TYPE = "hrm_resignation_approve";
    /**
     * å‘˜å·¥çŠ¶æ€ï¼šç¦»èŒ
     */
    private static final Integer EMPLOYEE_STATUS_RESIGNED = 3;
    @Resource
    private HrmResignationApplicationMapper resignationApplicationMapper;
    @Resource
@@ -70,13 +77,11 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private HrmEmployeeMapper employeeMapper;
    @Resource
    private HrmUserHandoverService handoverService;
@@ -243,88 +248,87 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitResignationApplication(Long id, String processDefinitionKey, Long userId) {
    public void submitResignationApplication(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        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());
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
                && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(RESIGNATION_APPLICATION_SUBMIT_FAIL_NOT_EDITABLE);
    }
    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);
        }
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(RESIGNATION_APPROVE_BIZ_TYPE);
        // 3. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        resignationApplicationMapper.submitAndResetAuditInfo(id, HrmAuditStatusEnum.PROCESS.getStatus());
    }
    @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;
    public Set<Long> getResignationApplicationApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(RESIGNATION_APPROVE_BIZ_TYPE);
    }
    @Override
    public void updateResignationApplicationAuditStatus(Long id, Integer status) {
    @Transactional(rollbackFor = Exception.class)
    public void auditResignationApplication(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        HrmResignationApplicationDO application = validateResignationApplicationExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            log.error("[updateResignationApplicationAuditStatus][application({}) ä¸å¤„于审批中,无法更新审批结果({})]", id, status);
            throw exception(RESIGNATION_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
            throw exception(RESIGNATION_APPLICATION_AUDIT_FAIL_NOT_PROCESS);
        }
        resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
                .id(id)
                .status(status)
                .approveTime(java.time.LocalDateTime.now())
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(RESIGNATION_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(RESIGNATION_APPLICATION_AUDIT_REJECT_REASON_REQUIRED);
            }
            resignationApplicationMapper.auditResignationApplication(id, HrmAuditStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为审核通过
        resignationApplicationMapper.auditResignationApplication(id, HrmAuditStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
        // 6. å®¡æ ¸é€šè¿‡åŽç½®ä¸šåŠ¡é€»è¾‘ï¼šå›žå†™å‘˜å·¥ä¸»æ¡£ï¼ˆçŠ¶æ€=离职,离职日期 = å®žé™…离职日期或预计离职日期)
        writeBackEmployee(application);
    }
    /**
     * å®¡æ ¸é€šè¿‡åŽå›žå†™å‘˜å·¥ä¸»æ¡£
     *
     * @param application ç¦»èŒç”³è¯·
     */
    private void writeBackEmployee(HrmResignationApplicationDO application) {
        HrmEmployeeDO employee = employeeMapper.selectByUserId(application.getUserId());
        if (employee == null) {
            log.warn("[writeBackEmployee] ç¦»èŒç”³è¯·({}) ç”¨æˆ·({}) æœªæ‰¾åˆ°å‘˜å·¥æ¡£æ¡ˆï¼Œè·³è¿‡å›žå†™", application.getId(), application.getUserId());
            return;
        }
        LocalDate leaveDate = application.getActualDate() != null
                ? application.getActualDate() : application.getExpectedDate();
        employeeMapper.updateById(HrmEmployeeDO.builder()
                .id(employee.getId())
                .employeeStatus(EMPLOYEE_STATUS_RESIGNED)
                .leaveDate(leaveDate)
                .build());
    }
    @Override
    public HrmResignationApplicationDO getResignationApplicationByProcessInstanceId(String processInstanceId) {
        return resignationApplicationMapper.selectByProcessInstanceId(processInstanceId);
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationService.java
@@ -7,8 +7,7 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmTransferApplicationDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * è°ƒå²—申请 Service æŽ¥å£
@@ -16,16 +15,6 @@
 * @author è¶…级管理员
 */
public interface HrmTransferApplicationService {
    /**
     * BPM è°ƒå²—审批流程标识
     */
    String BPM_PROCESS_DEFINITION_KEY = "hrm_transfer";
    /**
     * BPM è°ƒå²—审批分类编码
     */
    String APPROVE_CATEGORY_CODE = "hrm_transfer_approve";
    /**
     * åˆ›å»ºè°ƒå²—申请
@@ -74,35 +63,30 @@
    PageResult<HrmTransferApplicationRespVO> getTransferApplicationPage(HrmTransferApplicationPageReqVO pageReqVO);
    /**
     * æäº¤è°ƒå²—申请审批
     * æäº¤è°ƒå²—申请审批(草稿 / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤æ—¶æ— éœ€é€‰æ‹©ã€‚
     *
     * @param id ç”³è¯·ID
     * @param processDefinitionKey æµç¨‹å®šä¹‰Key
     * @param userId æäº¤äººID
     */
    void submitTransferApplication(Long id, String processDefinitionKey, Long userId);
    void submitTransferApplication(Long id);
    /**
     * èŽ·å–å¯ç”¨çš„å®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨
     * èŽ·å¾—è°ƒå²—ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›å‰ç«¯åˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’®
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    List<Map<String, Object>> getApproveProcessDefinitionList();
    Set<Long> getTransferApplicationApproverUserIds();
    /**
     * æ›´æ–°è°ƒå²—申请审批状态(BPM å›žè°ƒï¼‰
     * å®¡æ ¸è°ƒå²—申请(审批中 â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * å®¡æ ¸é€šè¿‡æ—¶å›žå†™å‘˜å·¥ä¸»æ¡£ï¼ˆéƒ¨é—¨ = ç›®æ ‡éƒ¨é—¨ï¼Œå²—位 = ç›®æ ‡å²—位)。
     *
     * @param id ç”³è¯·ID
     * @param status å®¡æ‰¹çŠ¶æ€
     * @param pass         æ˜¯å¦é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å¿…填)
     */
    void updateTransferApplicationAuditStatus(Long id, Integer status);
    /**
     * æ ¹æ®æµç¨‹å®žä¾‹ID获取调岗申请
     *
     * @param processInstanceId æµç¨‹å®žä¾‹ID
     * @return è°ƒå²—申请
     */
    HrmTransferApplicationDO getTransferApplicationByProcessInstanceId(String processInstanceId);
    void auditTransferApplication(Long id, Boolean pass, String reviewRemark);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -1,22 +1,19 @@
package cn.iocoder.yudao.module.hrm.service.approval;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.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.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmTransferApplicationMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
import cn.iocoder.yudao.module.hrm.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.approval.ApprovalConfigApi;
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.storage.StorageAttachmentApi;
@@ -26,14 +23,11 @@
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;
@@ -44,7 +38,7 @@
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
/**
@@ -57,6 +51,13 @@
@Slf4j
public class HrmTransferApplicationServiceImpl implements HrmTransferApplicationService {
    /**
     * è°ƒå²—申请审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String TRANSFER_APPROVE_BIZ_TYPE = "hrm_transfer_approve";
    @Resource
    private HrmTransferApplicationMapper transferApplicationMapper;
    @Resource
@@ -68,13 +69,11 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private HrmEmployeeMapper employeeMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -254,88 +253,85 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitTransferApplication(Long id, String processDefinitionKey, Long userId) {
    public void submitTransferApplication(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        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());
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
                && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(TRANSFER_APPLICATION_SUBMIT_FAIL_NOT_EDITABLE);
    }
    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);
        }
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(TRANSFER_APPROVE_BIZ_TYPE);
        // 3. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        transferApplicationMapper.submitAndResetAuditInfo(id, HrmAuditStatusEnum.PROCESS.getStatus());
    }
    @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;
    public Set<Long> getTransferApplicationApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(TRANSFER_APPROVE_BIZ_TYPE);
    }
    @Override
    public void updateTransferApplicationAuditStatus(Long id, Integer status) {
    @Transactional(rollbackFor = Exception.class)
    public void auditTransferApplication(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        HrmTransferApplicationDO application = validateTransferApplicationExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            log.error("[updateTransferApplicationAuditStatus][application({}) ä¸å¤„于审批中,无法更新审批结果({})]", id, status);
            throw exception(TRANSFER_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
            throw exception(TRANSFER_APPLICATION_AUDIT_FAIL_NOT_PROCESS);
        }
        transferApplicationMapper.updateById(HrmTransferApplicationDO.builder()
                .id(id)
                .status(status)
                .approveTime(java.time.LocalDateTime.now())
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(TRANSFER_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(TRANSFER_APPLICATION_AUDIT_REJECT_REASON_REQUIRED);
            }
            transferApplicationMapper.auditTransferApplication(id, HrmAuditStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为审核通过
        transferApplicationMapper.auditTransferApplication(id, HrmAuditStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
        // 6. å®¡æ ¸é€šè¿‡åŽç½®ä¸šåŠ¡é€»è¾‘ï¼šå›žå†™å‘˜å·¥ä¸»æ¡£ï¼ˆéƒ¨é—¨ = ç›®æ ‡éƒ¨é—¨ï¼Œå²—位 = ç›®æ ‡å²—位)
        writeBackEmployee(application);
    }
    /**
     * å®¡æ ¸é€šè¿‡åŽå›žå†™å‘˜å·¥ä¸»æ¡£
     *
     * @param application è°ƒå²—申请
     */
    private void writeBackEmployee(HrmTransferApplicationDO application) {
        HrmEmployeeDO employee = employeeMapper.selectByUserId(application.getUserId());
        if (employee == null) {
            log.warn("[writeBackEmployee] è°ƒå²—申请({}) ç”¨æˆ·({}) æœªæ‰¾åˆ°å‘˜å·¥æ¡£æ¡ˆï¼Œè·³è¿‡å›žå†™", application.getId(), application.getUserId());
            return;
        }
        employeeMapper.updateById(HrmEmployeeDO.builder()
                .id(employee.getId())
                .deptId(application.getTargetDeptId())
                .postId(application.getTargetPostId())
                .build());
    }
    @Override
    public HrmTransferApplicationDO getTransferApplicationByProcessInstanceId(String processInstanceId) {
        return transferApplicationMapper.selectByProcessInstanceId(processInstanceId);
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmLeaveApplicationStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmRegularizeApplicationStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmResignationApplicationStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmTransferApplicationStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionService.java
@@ -6,6 +6,8 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
import jakarta.validation.Valid;
import java.util.Set;
/**
 * è€ƒå‹¤å¼‚常申请 Service æŽ¥å£
 *
@@ -39,20 +41,30 @@
    HrmAttendanceExceptionDO getAttendanceException(Long id);
    /**
     * æäº¤è€ƒå‹¤å¼‚常申请
     * æäº¤è€ƒå‹¤å¼‚常申请审批(草稿 / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤æ—¶æ— éœ€é€‰æ‹©ã€‚
     *
     * @param id ç”³è¯·ID
     * @param processDefinitionKey æµç¨‹å®šä¹‰Key
     * @param userId æäº¤äººID
     */
    void submitAttendanceException(Long id, String processDefinitionKey, Long userId);
    void submitAttendanceException(Long id);
    /**
     * æ›´æ–°è€ƒå‹¤å¼‚常申请审批状态
     * èŽ·å¾—è€ƒå‹¤å¼‚å¸¸ç”³è¯·çš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›å‰ç«¯åˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’®
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getAttendanceExceptionApproverUserIds();
    /**
     * å®¡æ ¸è€ƒå‹¤å¼‚常申请(审批中 â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * å®¡æ ¸é€šè¿‡æ—¶æ‰§è¡ŒåŽç½®ä¸šåŠ¡é€»è¾‘ï¼šè¡¥å¡å›žå¡«ä¸Šä¸‹ç­æ—¶é—´ / åŠ ç­å›žå†™åŠ ç­æ—¶é•¿ã€‚
     *
     * @param id ç”³è¯·ID
     * @param auditStatus å®¡æ‰¹çŠ¶æ€
     * @param pass         æ˜¯å¦é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å¿…填)
     */
    void updateAttendanceExceptionAuditStatus(Long id, Integer auditStatus);
    void auditAttendanceException(Long id, Boolean pass, String reviewRemark);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java
@@ -1,16 +1,18 @@
package cn.iocoder.yudao.module.hrm.service.attendance;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.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.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceExceptionTypeEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
@@ -20,9 +22,10 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
/**
@@ -35,18 +38,27 @@
@Slf4j
public class HrmAttendanceExceptionServiceImpl implements HrmAttendanceExceptionService {
    /**
     * è€ƒå‹¤å¼‚常申请审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String ATTENDANCE_APPROVE_BIZ_TYPE = "hrm_attendance_approve";
    @Resource
    private HrmAttendanceExceptionMapper attendanceExceptionMapper;
    @Resource
    private HrmNoRedisDAO noRedisDAO;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private HrmEmployeeService employeeService;
    @Resource
    private HrmAttendanceRecordService attendanceRecordService;
    @Override
    public Long createAttendanceException(HrmAttendanceExceptionSaveReqVO createReqVO) {
@@ -110,47 +122,73 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitAttendanceException(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒå¼‚常申请存在且为草稿状态
    public void submitAttendanceException(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        HrmAttendanceExceptionDO exception = validateAttendanceExceptionExists(id);
        if (!HrmAuditStatusEnum.DRAFT.getStatus().equals(exception.getStatus())) {
            throw exception(ATTENDANCE_EXCEPTION_SUBMIT_FAIL_NOT_DRAFT);
        if (!HrmAuditStatusEnum.DRAFT.getStatus().equals(exception.getStatus())
                && !HrmAuditStatusEnum.REJECT.getStatus().equals(exception.getStatus())) {
            throw exception(ATTENDANCE_EXCEPTION_SUBMIT_FAIL_NOT_EDITABLE);
        }
        // 2. æ ¡éªŒæµç¨‹å®šä¹‰å­˜åœ¨ä¸”为激活状态
        if (processDefinitionKey != null) {
            bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(ATTENDANCE_APPROVE_BIZ_TYPE);
        // 3. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        attendanceExceptionMapper.submitAndResetAuditInfo(id, HrmAuditStatusEnum.PROCESS.getStatus());
        }
        // 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
    public Set<Long> getAttendanceExceptionApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(ATTENDANCE_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAttendanceExceptionAuditStatus(Long id, Integer auditStatus) {
        // 1. æ ¡éªŒå¼‚常申请存在
    public void auditAttendanceException(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        HrmAttendanceExceptionDO exception = validateAttendanceExceptionExists(id);
        // 2. æ ¡éªŒå½“前状态为审批中
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!HrmAuditStatusEnum.PROCESS.getStatus().equals(exception.getStatus())) {
            throw exception(ATTENDANCE_EXCEPTION_UPDATE_AUDIT_STATUS_FAIL);
            throw exception(ATTENDANCE_EXCEPTION_AUDIT_FAIL_NOT_PROCESS);
        }
        // 3. æ›´æ–°çŠ¶æ€
        attendanceExceptionMapper.updateById(HrmAttendanceExceptionDO.builder()
                .id(id)
                .status(auditStatus)
                .approveTime(LocalDateTime.now())
                .build());
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(ATTENDANCE_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(ATTENDANCE_EXCEPTION_AUDIT_REJECT_REASON_REQUIRED);
            }
            attendanceExceptionMapper.auditAttendanceException(id, HrmAuditStatusEnum.REJECT.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为审核通过
        attendanceExceptionMapper.auditAttendanceException(id, HrmAuditStatusEnum.APPROVE.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
        // 6. å®¡æ ¸é€šè¿‡åŽç½®ä¸šåŠ¡é€»è¾‘ï¼šè¡¥å¡å›žå¡«ä¸Šä¸‹ç­æ—¶é—´ / åŠ ç­å›žå†™åŠ ç­æ—¶é•¿
        if (HrmAttendanceExceptionTypeEnum.SUPPLEMENT.getType().equals(exception.getType())) {
            log.info("[auditAttendanceException] è¡¥å¡ç”³è¯·({}) å®¡æ‰¹é€šè¿‡ï¼Œå¼€å§‹æ›´æ–°è€ƒå‹¤è®°å½•", id);
            attendanceRecordService.supplementClockIn(exception);
        } else if (HrmAttendanceExceptionTypeEnum.OVERTIME.getType().equals(exception.getType())) {
            log.info("[auditAttendanceException] åŠ ç­ç”³è¯·({}) å®¡æ‰¹é€šè¿‡ï¼Œå¼€å§‹å›žå†™åŠ ç­æ—¶é•¿", id);
            attendanceRecordService.recordOvertime(exception);
        }
    }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/listener/HrmAttendanceExceptionStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseService.java
@@ -9,7 +9,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ Service æŽ¥å£
@@ -37,24 +37,31 @@
    void saveAppraiseItems(HrmPerformanceAppraiseItemSaveReqVO saveReqVO);
    /**
     * æäº¤ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹
     * æäº¤ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€æŒ‡å®šï¼Œæäº¤æ—¶æ— éœ€é€‰æ‹©ã€‚
     *
     * @param id æ–¹æ¡ˆç¼–号
     */
    void submitAppraise(Long id, String processDefinitionKey, Long userId);
    void submitAppraise(Long id);
    /**
     * å®¡æ‰¹ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆï¼ˆé€šè¿‡/驳回,manual)
     * èŽ·å¾—ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆçš„å®¡æ‰¹äººç¼–å·é›†åˆï¼Œä¾›å‰ç«¯åˆ¤æ–­æ˜¯å¦å±•ç¤ºå®¡æ ¸æŒ‰é’®
     *
     * @return å®¡æ‰¹äººç”¨æˆ·ç¼–号集合
     */
    void auditAppraise(HrmPerformanceAppraiseAuditReqVO auditReqVO);
    Set<Long> getAppraiseApproverUserIds();
    /**
     * æ›´æ–°ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹çŠ¶æ€ï¼ˆBPM ç›‘听回调)
     * å®¡æ ¸ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆï¼ˆå®¡æ‰¹ä¸­ â†’ å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * å®¡æ ¸é€šè¿‡æ—¶æ–¹æ¡ˆçŠ¶æ€æµè½¬ä¸ºã€Œå·²å‘å¸ƒã€ã€‚
     *
     * @param id           æ–¹æ¡ˆç¼–号
     * @param pass         æ˜¯å¦é€šè¿‡
     * @param reviewRemark å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡æ—¶å¿…填)
     */
    void updateAppraiseAuditStatus(Long id, Integer bpmResult);
    /**
     * èŽ·å¾—ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹æµç¨‹å®šä¹‰åˆ—è¡¨
     */
    List<Map<String, Object>> getAppraiseApproveProcessDefinitionList();
    void auditAppraise(Long id, Boolean pass, String reviewRemark);
    /**
     * ä¿å­˜å‘˜å·¥è€ƒæ ¸ç»“果(按员工逐指标打分,自动计算综合得分与档级)
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/HrmPerformanceAppraiseServiceImpl.java
@@ -7,11 +7,6 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.hrm.controller.admin.performanceappraise.vo.*;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.*;
@@ -19,16 +14,17 @@
import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.*;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -36,7 +32,6 @@
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hrm.util.HrmAuditStatusUtils.convertBpmResultToAuditStatus;
/**
 * HRM ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆ Service å®žçŽ°ç±»
@@ -82,9 +77,11 @@
    private static final Integer EMPLOYEE_STATUS_ACTIVE = 1;
    /**
     * BPM ç»©æ•ˆè€ƒæ ¸å®¡æ‰¹åˆ†ç±»ç¼–码
     * ç»©æ•ˆè€ƒæ ¸æ–¹æ¡ˆå®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审批人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String APPRAISE_APPROVE_CATEGORY_CODE = "performance_appraise_approve";
    private static final String APPRAISE_APPROVE_BIZ_TYPE = "performance_appraise_approve";
    @Resource
    private HrmPerformanceAppraiseMapper appraiseMapper;
@@ -97,11 +94,9 @@
    @Resource
    private HrmEmployeeMapper employeeMapper;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private AdminUserApi adminUserApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -121,8 +116,7 @@
        HrmPerformanceAppraiseDO oldAppraise = validateAppraiseCanModify(updateReqVO.getId());
        HrmPerformanceAppraiseDO updateObj = BeanUtils.toBean(updateReqVO, HrmPerformanceAppraiseDO.class, req -> req
                .setStatus(oldAppraise.getStatus())
                .setAuditStatus(oldAppraise.getAuditStatus())
                .setProcessInstanceId(oldAppraise.getProcessInstanceId()));
                .setAuditStatus(oldAppraise.getAuditStatus()));
        updateObj.setScopeTeamIds(CollUtil.isEmpty(updateReqVO.getScopeTeamIds())
                ? null : JSONUtil.toJsonStr(updateReqVO.getScopeTeamIds()));
        appraiseMapper.updateById(updateObj);
@@ -185,122 +179,72 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitAppraise(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒæ–¹æ¡ˆæ˜¯å¦åœ¨è‰ç¨¿çŠ¶æ€
    public void submitAppraise(Long id) {
        // 1. æ ¡éªŒæ–¹æ¡ˆå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        HrmPerformanceAppraiseDO appraise = appraiseMapper.selectById(id);
        if (appraise == null) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_NOT_EXISTS);
        }
        if (ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_SUBMIT_FAIL_NOT_DRAFT);
        if (ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
                && ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_SUBMIT_FAIL_NOT_EDITABLE);
        }
        if (CollUtil.isEmpty(itemMapper.selectListByAppraiseId(id))) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_ITEM_EMPTY);
        }
        // 2. æ ¡éªŒå®¡æ‰¹åˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨ï¼Œä¸”传入的流程定义处于激活状态
        validateAppraiseApproveCategoryAndProcessDefinition();
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        if (processDefinition == null) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审批人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(APPRAISE_APPROVE_BIZ_TYPE);
        // 3. å®¡æ‰¹çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœï¼Œé¿å…æ®‹ç•™ã€Œå®¡æ ¸ä¸é€šè¿‡ã€çš„åŽŸå› 
        appraiseMapper.submitAndResetAuditInfo(id, HrmAuditStatusEnum.PROCESS.getStatus());
        }
        // 3. åˆ›å»ºå®¡æ‰¹æµç¨‹å®žä¾‹
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 4. æ›´æ–°æ–¹æ¡ˆæµç¨‹ç¼–号与审批状态
        appraiseMapper.updateById(new HrmPerformanceAppraiseDO().setId(id).setProcessInstanceId(processInstanceId)
                .setAuditStatus(HrmAuditStatusEnum.PROCESS.getStatus()));
    @Override
    public Set<Long> getAppraiseApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(APPRAISE_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditAppraise(HrmPerformanceAppraiseAuditReqVO auditReqVO) {
        HrmPerformanceAppraiseDO appraise = validateAppraiseExists(auditReqVO.getId());
    public void auditAppraise(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        HrmPerformanceAppraiseDO appraise = validateAppraiseExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_AUDIT_FAIL_NOT_PROCESS);
        }
        Integer auditStatus = auditReqVO.getAuditStatus();
        if (!Objects.equals(auditStatus, HrmAuditStatusEnum.APPROVE.getStatus())
                && !Objects.equals(auditStatus, HrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审批人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(APPRAISE_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,审批状态置为审核不通过,退回提交人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_AUDIT_REJECT_REASON_REQUIRED);
        }
        HrmPerformanceAppraiseDO update = new HrmPerformanceAppraiseDO()
                .setId(appraise.getId())
                .setAuditStatus(auditStatus)
                .setAuditUserId(getLoginUserId())
                .setAuditTime(LocalDateTime.now());
        if (auditReqVO.getRemark() != null) {
            update.setAuditRemark(auditReqVO.getRemark());
            // æ–¹æ¡ˆçŠ¶æ€ä¿æŒä¸å˜
            appraiseMapper.auditAppraise(id, HrmAuditStatusEnum.REJECT.getStatus(), appraise.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // å®¡æ‰¹é€šè¿‡ -> æ–¹æ¡ˆå‘布
        if (Objects.equals(auditStatus, HrmAuditStatusEnum.APPROVE.getStatus())) {
            update.setStatus(STATUS_PUBLISHED);
        }
        appraiseMapper.updateById(update);
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,审批状态置为审核通过
        // 6. å®¡æ ¸é€šè¿‡åŽç½®ä¸šåŠ¡é€»è¾‘ï¼šæ–¹æ¡ˆçŠ¶æ€æµè½¬ä¸ºã€Œå·²å‘å¸ƒã€
        appraiseMapper.auditAppraise(id, HrmAuditStatusEnum.APPROVE.getStatus(), STATUS_PUBLISHED,
                userId, getUserNickname(userId), reviewRemark);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAppraiseAuditStatus(Long id, Integer bpmResult) {
        // 1.1 æ ¡éªŒæ–¹æ¡ˆæ˜¯å¦å­˜åœ¨
        HrmPerformanceAppraiseDO appraise = appraiseMapper.selectById(id);
        if (appraise == null) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_NOT_EXISTS);
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        // 1.2 åªæœ‰å®¡æ‰¹ä¸­ï¼Œå¯ä»¥æ›´æ–°å®¡æ‰¹ç»“æžœ
        if (ObjUtil.notEqual(appraise.getAuditStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            log.error("[updateAppraiseAuditStatus][appraise({}) ä¸å¤„于审批中,无法更新审批结果({})]", appraise.getId(), bpmResult);
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        // 2. æ›´æ–°æ–¹æ¡ˆå®¡æ‰¹ç»“æžœ
        Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
        HrmPerformanceAppraiseDO update = new HrmPerformanceAppraiseDO().setId(id).setAuditStatus(auditStatus);
        if (Objects.equals(auditStatus, HrmAuditStatusEnum.APPROVE.getStatus())) {
            update.setStatus(STATUS_PUBLISHED);
        }
        appraiseMapper.updateById(update);
    }
    @Override
    public List<Map<String, Object>> getAppraiseApproveProcessDefinitionList() {
        // 1. æ ¡éªŒå®¡æ‰¹åˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validateAppraiseApproveCategoryAndProcessDefinition();
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(APPRAISE_APPROVE_CATEGORY_CODE);
        // 3. èŽ·å–æµç¨‹å®šä¹‰è¯¦æƒ…
        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
        // 4. è¿‡æ»¤ï¼šåªä¿ç•™æ¿€æ´»çŠ¶æ€ï¼Œä¸”ç›¸åŒ key åªä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (ProcessDefinition pd : processDefinitions) {
            if (pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 5. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            item.put("version", pd.getVersion());
            result.add(item);
        }
        return result;
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
@@ -482,32 +426,6 @@
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_UPDATE_FAIL_NOT_DRAFT);
        }
        return appraise;
    }
    /**
     * æ ¡éªŒ BPM ç»©æ•ˆè€ƒæ ¸å®¡æ‰¹åˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
     */
    private void validateAppraiseApproveCategoryAndProcessDefinition() {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(Collections.singletonList(APPRAISE_APPROVE_CATEGORY_CODE));
        if (!categoryMap.containsKey(APPRAISE_APPROVE_CATEGORY_CODE)) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        // 2. æ ¡éªŒåˆ†ç±»ä¸‹æ˜¯å¦æœ‰å¯ç”¨çš„æµç¨‹å®šä¹‰
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(APPRAISE_APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        // 3. æ ¡éªŒæ˜¯å¦æœ‰æ¿€æ´»çŠ¶æ€çš„æµç¨‹å®šä¹‰
        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
        boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended());
        if (!hasActiveDefinition) {
            throw exception(ErrorCodeConstants.PERFORMANCE_APPRAISE_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/performanceappraise/listener/HrmPerformanceAppraiseStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/util/HrmAuditStatusUtils.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java
@@ -8,6 +8,7 @@
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectSaveReqVO;
@@ -29,6 +30,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -125,21 +127,27 @@
    }
    @PutMapping("/submit-approval")
    @Operation(summary = "提交设计项目审批(待审核→审批中)")
    @Operation(summary = "提交设计项目审批(待审核→审批中),审核人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义 Key", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pd-project:update')")
    public CommonResult<Boolean> submitProjectApproval(@RequestParam("id") Long id,
                                                       @RequestParam("processDefinitionKey") String processDefinitionKey) {
        projectService.submitProjectApproval(id, processDefinitionKey);
    public CommonResult<Boolean> submitProjectApproval(@RequestParam("id") Long id) {
        projectService.submitProjectApproval(id);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获得设计项目审批流程列表")
    @PreAuthorize("@ss.hasPermission('mes:pd-project:update')")
    public CommonResult<List<Map<String, Object>>> getProjectApproveProcessDefinitionList() {
        return success(projectService.getProjectApproveProcessDefinitionList());
    @PutMapping("/audit")
    @Operation(summary = "审核设计项目(审批中→待归档/待审核),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:pd-project:audit')")
    public CommonResult<Boolean> auditProject(@Valid @RequestBody MesPdProjectAuditReqVO auditReqVO) {
        projectService.auditProject(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得设计项目的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:pd-project:query')")
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(projectService.getProjectApproverUserIds());
    }
    @PutMapping("/archive")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * è®¾è®¡é¡¹ç›®å®¡æ ¸ Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - MES è®¾è®¡é¡¹ç›®å®¡æ ¸ Request VO")
@Data
public class MesPdProjectAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "设计项目编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "资料需补充完整")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java
@@ -66,8 +66,20 @@
    @ExcelProperty("备注")
    private String remark;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "2")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "李工")
    @ExcelProperty("审核人")
    private String reviewerName;
    @Schema(description = "审核时间")
    @ExcelProperty("审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "资料需补充完整")
    @ExcelProperty("审核意见")
    private String reviewRemark;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("创建时间")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/ProductTechConfirmController.java
@@ -6,6 +6,7 @@
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.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmSaveReqVO;
@@ -22,8 +23,8 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -53,7 +54,7 @@
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除产品技术确认函")
    @Operation(summary = "删除产品技术确认函(仅草稿 / å®¡æ ¸ä¸é€šè¿‡çŠ¶æ€å¯åˆ é™¤ï¼‰")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:delete')")
    public CommonResult<Boolean> deleteTechConfirm(@RequestParam("id") Long id) {
@@ -61,6 +62,41 @@
        return success(true);
    }
    @PutMapping("/submit-approval")
    @Operation(summary = "提交技术确认函审核(草稿/审核不通过→待审核),审核人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:update')")
    public CommonResult<Boolean> submitTechConfirmAudit(@RequestParam("id") Long id) {
        techConfirmService.submitTechConfirmAudit(id);
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审核技术确认函(待审核→已审核/审核不通过),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:audit')")
    public CommonResult<Boolean> auditTechConfirm(@Valid @RequestBody ProductTechConfirmAuditReqVO auditReqVO) {
        techConfirmService.auditTechConfirm(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得技术确认函的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:query')")
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(techConfirmService.getTechConfirmApproverUserIds());
    }
    @PutMapping("/customer-confirm")
    @Operation(summary = "客户确认(已审核→客户已确认)")
    @Parameter(name = "id", description = "编号", required = true)
    @Parameter(name = "customerConfirmUser", description = "客户确认人姓名", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pd-tech-confirm:update')")
    public CommonResult<Boolean> confirmTechConfirmByCustomer(@RequestParam("id") Long id,
                                                              @RequestParam("customerConfirmUser") String customerConfirmUser) {
        techConfirmService.confirmTechConfirmByCustomer(id, customerConfirmUser);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得产品技术确认函")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * äº§å“æŠ€æœ¯ç¡®è®¤å‡½å®¡æ ¸ Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - äº§å“æŠ€æœ¯ç¡®è®¤å‡½å®¡æ ¸ Request VO")
@Data
public class ProductTechConfirmAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "技术确认函编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "参数需与客户再次核对")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmRespVO.java
@@ -57,7 +57,7 @@
    @ExcelProperty("编制时间")
    private LocalDateTime draftTime;
    @Schema(description = "审核人ID", example = "2")
    @Schema(description = "审核人ID(提交审核时由编制人指定)", example = "2")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "李工")
@@ -68,6 +68,10 @@
    @ExcelProperty("审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审批通过时的意见 / é©³å›žæ—¶çš„原因)", example = "参数需与客户再次核对")
    @ExcelProperty("审核意见")
    private String reviewRemark;
    @Schema(description = "客户确认人", example = "王经理")
    @ExcelProperty("客户确认人")
    private String customerConfirmUser;
@@ -76,7 +80,7 @@
    @ExcelProperty("客户确认时间")
    private LocalDateTime customerConfirmTime;
    @Schema(description = "状态(0=草稿,1=待审核,2=已审核,3=客户已确认)", example = "0")
    @Schema(description = "状态(0=草稿,1=待审核,2=已审核,3=客户已确认,4=审核不通过)", example = "0")
    @ExcelProperty(value = "状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.PRODUCT_TECH_CONFIRM_STATUS)
    private Integer status;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/techconfirm/vo/ProductTechConfirmSaveReqVO.java
@@ -3,13 +3,26 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * äº§å“æŠ€æœ¯ç¡®è®¤å‡½æ–°å¢ž/修改 Request VO
 *
 * æ³¨æ„ï¼šæœ¬ VO åªæ‰¿è½½ç”¨æˆ·å¯å¡«å†™çš„业务字段。
 * çŠ¶æ€ã€ç¼–åˆ¶äºº/编制时间、审核人/审核时间、客户确认人/客户确认时间均由业务流程驱动,
 * ä¸æŽ¥å—前端传参,因此不在本 VO ä¸­å®šä¹‰ï¼š
 * <ul>
 *     <li>状态:创建时固定为草稿,由提交审批、BPM å®¡æ‰¹å›žè°ƒã€å®¢æˆ·ç¡®è®¤é€æ­¥æŽ¨è¿›</li>
 *     <li>编制人/编制时间:创建时取当前登录用户与当前时间</li>
 *     <li>审核人/审核时间:BPM å®¡æ‰¹ç»“束时取审批人写入</li>
 *     <li>客户确认人/客户确认时间:客户确认时取当前登录用户与当前时间</li>
 * </ul>
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - äº§å“æŠ€æœ¯ç¡®è®¤å‡½æ–°å¢ž/修改 Request VO")
@Data
public class ProductTechConfirmSaveReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1024")
    @Schema(description = "编号(新增时为空,修改时必填)", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1024")
    private Long id;
    @Schema(description = "确认函编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "TC20260801001")
@@ -32,33 +45,6 @@
    @Schema(description = "模板类型(奥地利/日本/通用)", example = "general")
    private String templateType;
    @Schema(description = "编制人ID", example = "1")
    private Long drafterId;
    @Schema(description = "编制人姓名", example = "胡东")
    private String drafterName;
    @Schema(description = "编制时间")
    private LocalDateTime draftTime;
    @Schema(description = "审核人ID", example = "2")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "李工")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "客户确认人", example = "王经理")
    private String customerConfirmUser;
    @Schema(description = "客户确认时间")
    private LocalDateTime customerConfirmTime;
    @Schema(description = "状态(0=草稿,1=待审核,2=已审核,3=客户已确认)", example = "0")
    private Integer status;
    @Schema(description = "备注", example = "测试备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java
@@ -15,8 +15,7 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -68,19 +67,26 @@
    }
    @PutMapping("/submit-approval")
    @Operation(summary = "提交设备入库单审批")
    @Operation(summary = "提交设备入库单审批(草稿→审批中),审核人由审批配置统一指定")
    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:submit')")
    public CommonResult<Boolean> submitApproval(@RequestParam("id") Long id,
                                                  @RequestParam("processDefinitionKey") String processDefinitionKey) {
        equipmentReceiptService.submitEquipmentReceiptApproval(id, processDefinitionKey);
    public CommonResult<Boolean> submitApproval(@RequestParam("id") Long id) {
        equipmentReceiptService.submitEquipmentReceiptApproval(id);
        return success(true);
    }
    @GetMapping("/approval-process-list")
    @Operation(summary = "获得设备入库审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核设备入库单(审批中→已审批/草稿),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:audit')")
    public CommonResult<Boolean> audit(@Valid @RequestBody MesWmEquipmentReceiptAuditReqVO auditReqVO) {
        equipmentReceiptService.auditEquipmentReceipt(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得设备入库单的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:wm:equipment-receipt:query')")
    public CommonResult<List<Map<String, Object>>> approvalProcessList() {
        return success(equipmentReceiptService.getEquipmentReceiptApprovalProcessDefinitionList());
    public CommonResult<Set<Long>> approverIds() {
        return success(equipmentReceiptService.getEquipmentReceiptApproverUserIds());
    }
    @PutMapping("/finish")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * è®¾å¤‡å…¥åº“单审核 Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 */
@Schema(description = "管理后台 - MES è®¾å¤‡å…¥åº“单审核 Request VO")
@Data
public class MesWmEquipmentReceiptAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "设备入库单编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "数量需再次核对")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/vo/MesWmEquipmentReceiptRespVO.java
@@ -29,8 +29,17 @@
    @Schema(description = "状态", example = "0")
    private Integer status;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "2")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "李工")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "数量需再次核对")
    private String reviewRemark;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java
@@ -8,6 +8,7 @@
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO;
@@ -33,6 +34,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -111,12 +113,27 @@
    }
    @PutMapping("/submit")
    @Operation(summary = "提交采购入库单")
    @Operation(summary = "提交采购入库单审批(草稿→待上架),审核人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:wm-item-receipt:update')")
    public CommonResult<Boolean> submitItemReceipt(@RequestParam("id") Long id) {
        itemReceiptService.submitItemReceipt(id);
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审核采购入库单(待上架→待执行入库/草稿),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:wm-item-receipt:audit')")
    public CommonResult<Boolean> auditItemReceipt(@Valid @RequestBody MesWmItemReceiptAuditReqVO auditReqVO) {
        itemReceiptService.auditItemReceipt(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得采购入库单的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:wm-item-receipt:query')")
    public CommonResult<Set<Long>> approverIds() {
        return success(itemReceiptService.getItemReceiptApproverUserIds());
    }
    @PutMapping("/stock")
@@ -152,17 +169,6 @@
    @PreAuthorize("@ss.hasPermission('mes:wm-item-receipt:create')")
    public CommonResult<Long> generateFromArrivalNotice(@RequestParam("arrivalNoticeId") Long arrivalNoticeId) {
        return success(itemReceiptService.generateItemReceiptFromArrivalNotice(arrivalNoticeId));
    }
    @PutMapping("/submit-approval")
    @Operation(summary = "提交采购入库单审批")
    @Parameter(name = "id", description = "入库单编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义 Key", required = true)
    @PreAuthorize("@ss.hasPermission('mes:wm-item-receipt:update')")
    public CommonResult<Boolean> submitApproval(@RequestParam("id") Long id,
                                                 @RequestParam("processDefinitionKey") String processDefinitionKey) {
        itemReceiptService.submitItemReceiptApproval(id, processDefinitionKey);
        return success(true);
    }
    // ==================== æ‹¼æŽ¥ VO ====================
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/MesWmItemReceiptAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * é‡‡è´­å…¥åº“单审核 Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 */
@Schema(description = "管理后台 - MES é‡‡è´­å…¥åº“单审核 Request VO")
@Data
public class MesWmItemReceiptAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "采购入库单编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "数量需再次核对")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/MesWmItemReceiptRespVO.java
@@ -57,8 +57,17 @@
    @ExcelProperty("状态")
    private Integer status;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "2")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "李工")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(审核通过时的意见 / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)", example = "数量需再次核对")
    private String reviewRemark;
    @Schema(description = "备注", example = "备注")
    @ExcelProperty("备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/MesWmMiscIssueController.java
@@ -7,6 +7,7 @@
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.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssueAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssuePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssueRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssueSaveReqVO;
@@ -25,6 +26,7 @@
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -96,21 +98,27 @@
    }
    @PutMapping("/submit")
    @Operation(summary = "提交杂项出库单审批")
    @Operation(summary = "提交杂项出库单审批(草稿→审批中),审核人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义 Key", required = true)
    @PreAuthorize("@ss.hasPermission('mes:wm-misc-issue:update')")
    public CommonResult<Boolean> submitMiscIssue(@RequestParam("id") Long id,
                                                  @RequestParam("processDefinitionKey") String processDefinitionKey) {
        miscIssueService.submitMiscIssueApproval(id, processDefinitionKey);
    public CommonResult<Boolean> submitMiscIssue(@RequestParam("id") Long id) {
        miscIssueService.submitMiscIssueApproval(id);
        return success(true);
    }
    @GetMapping("/approval-process-list")
    @Operation(summary = "获得杂项出库审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核杂项出库单(审批中→待执行出库/草稿),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:wm-misc-issue:audit')")
    public CommonResult<Boolean> auditMiscIssue(@Valid @RequestBody MesWmMiscIssueAuditReqVO auditReqVO) {
        miscIssueService.auditMiscIssue(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得杂项出库单的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:wm-misc-issue:query')")
    public CommonResult<List<java.util.Map<String, Object>>> getApprovalProcessList() {
        return success(miscIssueService.getMiscIssueApprovalProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(miscIssueService.getMiscIssueApproverUserIds());
    }
    @PutMapping("/finish")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/MesWmMiscIssueAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * æ‚项出库单审核 Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 */
@Schema(description = "管理后台 - æ‚项出库单审核 Request VO")
@Data
public class MesWmMiscIssueAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "杂项出库单编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "出库数量需再次核对")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/MesWmMiscIssueRespVO.java
@@ -35,8 +35,17 @@
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(通过时的意见 / ä¸é€šè¿‡æ—¶çš„原因)", example = "出库数量需再次核对")
    private String reviewRemark;
    @Schema(description = "备注", example = "备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java
@@ -5,6 +5,7 @@
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.mes.controller.admin.wm.miscreceipt.vo.MesWmMiscReceiptAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo.MesWmMiscReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo.MesWmMiscReceiptRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo.MesWmMiscReceiptSaveReqVO;
@@ -22,6 +23,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -77,21 +79,27 @@
    }
    @PutMapping("/submit")
    @Operation(summary = "提交杂项入库单审批")
    @Operation(summary = "提交杂项入库单审批(草稿→审批中),审核人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义 Key", required = true)
    @PreAuthorize("@ss.hasPermission('mes:wm:misc-receipt:submit')")
    public CommonResult<Boolean> submitMiscReceipt(@RequestParam("id") Long id,
                                                    @RequestParam("processDefinitionKey") String processDefinitionKey) {
        miscReceiptService.submitMiscReceiptApproval(id, processDefinitionKey);
    public CommonResult<Boolean> submitMiscReceipt(@RequestParam("id") Long id) {
        miscReceiptService.submitMiscReceiptApproval(id);
        return success(true);
    }
    @GetMapping("/approval-process-list")
    @Operation(summary = "获得杂项入库审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核杂项入库单(审批中→已审批/草稿),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:wm:misc-receipt:audit')")
    public CommonResult<Boolean> auditMiscReceipt(@Valid @RequestBody MesWmMiscReceiptAuditReqVO auditReqVO) {
        miscReceiptService.auditMiscReceipt(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得杂项入库单的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:wm:misc-receipt:query')")
    public CommonResult<List<java.util.Map<String, Object>>> getApprovalProcessList() {
        return success(miscReceiptService.getMiscReceiptApprovalProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(miscReceiptService.getMiscReceiptApproverUserIds());
    }
    @PutMapping("/finish")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/vo/MesWmMiscReceiptAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * æ‚项入库单审核 Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - æ‚项入库单审核 Request VO")
@Data
public class MesWmMiscReceiptAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "杂项入库单编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "入库数量需再次核对")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/vo/MesWmMiscReceiptRespVO.java
@@ -35,8 +35,17 @@
    @Schema(description = "状态", example = "0")
    private Integer status;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(通过时的意见 / ä¸é€šè¿‡æ—¶çš„原因)", example = "入库数量需再次核对")
    private String reviewRemark;
    @Schema(description = "备注", example = "备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java
@@ -8,6 +8,7 @@
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorSaveReqVO;
@@ -29,6 +30,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -103,21 +105,27 @@
    }
    @PutMapping("/submit")
    @Operation(summary = "提交供应商退货单审批")
    @Operation(summary = "提交供应商退货单审批(草稿→待拣货),审核人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义 Key", required = true)
    @PreAuthorize("@ss.hasPermission('mes:wm-return-vendor:update')")
    public CommonResult<Boolean> submitReturnVendor(@RequestParam("id") Long id,
                                                     @RequestParam("processDefinitionKey") String processDefinitionKey) {
        returnVendorService.submitReturnVendorApproval(id, processDefinitionKey);
    public CommonResult<Boolean> submitReturnVendor(@RequestParam("id") Long id) {
        returnVendorService.submitReturnVendorApproval(id);
        return success(true);
    }
    @GetMapping("/approval-process-list")
    @Operation(summary = "获得供应商退货审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核供应商退货单(待拣货→待执行退货/草稿),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:wm-return-vendor:audit')")
    public CommonResult<Boolean> auditReturnVendor(@Valid @RequestBody MesWmReturnVendorAuditReqVO auditReqVO) {
        returnVendorService.auditReturnVendor(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得供应商退货单的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:wm-return-vendor:query')")
    public CommonResult<List<java.util.Map<String, Object>>> getApprovalProcessList() {
        return success(returnVendorService.getReturnVendorApprovalProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(returnVendorService.getReturnVendorApproverUserIds());
    }
    @PutMapping("/stock")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ä¾›åº”商退货单审核 Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 */
@Schema(description = "管理后台 - ä¾›åº”商退货单审核 Request VO")
@Data
public class MesWmReturnVendorAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "供应商退货单编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "退货数量需再次核对")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java
@@ -48,8 +48,17 @@
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    private Integer status;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "审核时间")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(通过时的意见 / ä¸é€šè¿‡æ—¶çš„原因)", example = "退货数量需再次核对")
    private String reviewRemark;
    @Schema(description = "备注", example = "备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
@@ -29,6 +29,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -114,21 +115,27 @@
    }
    @PutMapping("/submit-approval")
    @Operation(summary = "提交盘点结果审批")
    @Operation(summary = "提交盘点结果审批(盘点中→盘点中·结果审批中),审核人由审批配置统一指定")
    @Parameter(name = "id", description = "编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义 Key", required = true)
    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:update')")
    public CommonResult<Boolean> submitStockTakingTaskResultApproval(@RequestParam("id") Long id,
                                                                     @RequestParam("processDefinitionKey") String processDefinitionKey) {
        stockTakingTaskService.submitStockTakingResultApproval(id, processDefinitionKey);
    public CommonResult<Boolean> submitStockTakingTaskResultApproval(@RequestParam("id") Long id) {
        stockTakingTaskService.submitStockTakingResultApproval(id);
        return success(true);
    }
    @GetMapping("/approval-process-list")
    @Operation(summary = "获得盘点结果审批流程定义列表")
    @PutMapping("/audit")
    @Operation(summary = "审核盘点结果(结果审批中→已完成/盘点中),仅审批人本人可操作")
    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:audit')")
    public CommonResult<Boolean> auditStockTakingTaskResult(@Valid @RequestBody MesWmStockTakingTaskAuditReqVO auditReqVO) {
        stockTakingTaskService.auditStockTakingResult(auditReqVO.getId(), auditReqVO.getPass(), auditReqVO.getReviewRemark());
        return success(true);
    }
    @GetMapping("/approver-ids")
    @Operation(summary = "获得盘点结果审批的审核人编号集合,供前端判断是否展示审核按钮")
    @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:query')")
    public CommonResult<List<Map<String, Object>>> getStockTakingTaskResultApprovalProcessDefinitionList() {
        return success(stockTakingTaskService.getStockTakingTaskResultApprovalProcessDefinitionList());
    public CommonResult<Set<Long>> getApproverUserIds() {
        return success(stockTakingTaskService.getStockTakingTaskApproverUserIds());
    }
    @PutMapping("/cancel")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskAuditReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * ç›˜ç‚¹ç»“果审核 Request VO
 *
 * å®¡æ ¸é€šè¿‡ / ä¸é€šè¿‡å…±ç”¨æœ¬ VO,由 pass åŒºåˆ†ï¼š
 * é€šè¿‡æ—¶å®¡æ ¸æ„è§é€‰å¡«ï¼Œä¸é€šè¿‡æ—¶å®¡æ ¸æ„è§ï¼ˆä¸é€šè¿‡åŽŸå› ï¼‰å¿…å¡«ï¼Œç”± Service æ ¡éªŒã€‚
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç›˜ç‚¹ç»“果审核 Request VO")
@Data
public class MesWmStockTakingTaskAuditReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "盘点任务编号不能为空")
    private Long id;
    @Schema(description = "是否通过:true=审核通过 / false=审核不通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "请选择审核结果")
    private Boolean pass;
    @Schema(description = "审核意见(审核通过时选填 / å®¡æ ¸ä¸é€šè¿‡æ—¶å¿…填,即不通过原因)", example = "盘亏差异需附说明")
    private String reviewRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskRespVO.java
@@ -70,8 +70,17 @@
    @ExcelProperty("状态")
    private Integer status;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号,非空表示盘点结果已提交审批", example = "xxx")
    private String processInstanceId;
    @Schema(description = "审核人用户编号", example = "1")
    private Long reviewerId;
    @Schema(description = "审核人姓名", example = "张三")
    private String reviewerName;
    @Schema(description = "结果审批提交时间 / å®¡æ ¸æ—¶é—´", example = "2026-09-16 10:00:00")
    private LocalDateTime reviewTime;
    @Schema(description = "审核意见(通过时的意见 / ä¸é€šè¿‡æ—¶çš„原因)", example = "盘亏差异需附说明")
    private String reviewRemark;
    @Schema(description = "备注", example = "备注")
    @ExcelProperty("备注")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java
@@ -58,13 +58,24 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  mes_pd_project_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * BPM æµç¨‹å®šä¹‰ Key
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String processDefinitionKey;
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * ä¸»è®¾è®¡å¸ˆ
     *
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/techconfirm/ProductTechConfirmDO.java
@@ -73,16 +73,23 @@
    private LocalDateTime draftTime;
    /**
     * å®¡æ ¸äººID
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  pd_tech_confirm_approve),
     * æäº¤å®¡æ ¸æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录的是实际执行审核的用户,审核通过后不再变动。
     */
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示时再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å®¢æˆ·ç¡®è®¤äºº
     */
@@ -92,9 +99,16 @@
     */
    private LocalDateTime customerConfirmTime;
    /**
     * çŠ¶æ€ï¼ˆ0=草稿,1=待审核,2=已审核,3=客户已确认)
     * çŠ¶æ€ï¼ˆ0=草稿,1=待审核,2=已审核,3=客户已确认,4=审核不通过)
     *
     * åªç”±ä¸šåŠ¡æµç¨‹é©±åŠ¨ï¼Œä¸æŽ¥å—å‰ç«¯ç›´æŽ¥èµ‹å€¼ï¼š
     * åˆ›å»ºæ—¶å›ºå®šä¸ºè‰ç¨¿ï¼Œæäº¤å®¡æ ¸åŽç½®ä¸ºå¾…审核,审核通过/不通过由
     * {@link cn.iocoder.yudao.module.mes.service.pd.techconfirm.ProductTechConfirmService#auditTechConfirm} æŽ¨è¿›ï¼Œ
     * å®¢æˆ·ç¡®è®¤ç”±
     * {@link cn.iocoder.yudao.module.mes.service.pd.techconfirm.ProductTechConfirmService#confirmTechConfirmByCustomer} æŽ¨è¿›ã€‚
     *
     * å­—å…¸ {@link DictTypeConstants#PRODUCT_TECH_CONFIRM_STATUS}
     * æžšä¸¾ {@link cn.iocoder.yudao.module.mes.enums.pd.MesPdTechConfirmStatusEnum}
     */
    private Integer status;
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/equipmentreceipt/MesWmEquipmentReceiptDO.java
@@ -49,9 +49,24 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  equipment_receipt_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å¤‡æ³¨
     */
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/itemreceipt/MesWmItemReceiptDO.java
@@ -81,13 +81,24 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ‰¹äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  item_receipt_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * BPM æµç¨‹å®šä¹‰ Key
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String processDefinitionKey;
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å¤‡æ³¨
     */
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/miscissue/MesWmMiscIssueDO.java
@@ -68,9 +68,24 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  misc_issue_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å¤‡æ³¨
     */
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/miscreceipt/MesWmMiscReceiptDO.java
@@ -68,9 +68,24 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  misc_receipt_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å¤‡æ³¨
     */
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java
@@ -70,9 +70,24 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  return_vendor_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * å®¡æ ¸æ—¶é—´
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å¤‡æ³¨
     */
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/stocktaking/task/MesWmStockTakingTaskDO.java
@@ -93,11 +93,32 @@
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     * å®¡æ ¸äººç”¨æˆ·ç¼–号
     *
     * éžç©ºè¡¨ç¤ºç›˜ç‚¹ç»“果已提交 BPM å®¡æ‰¹ï¼Œæ­£åœ¨ç­‰å¾…审批结果
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  stock_taking_result_approve),
     * æäº¤æ—¶ä¸æŒ‡å®šï¼›æ­¤å¤„记录实际执行审核的用户。审核不通过后保留,表示结果审批已被处理过。
     */
    private String processInstanceId;
    private Long reviewerId;
    /**
     * å®¡æ ¸äººå§“名(审核时冗余记录,避免列表展示再查用户)
     */
    private String reviewerName;
    /**
     * ç»“果审批提交时间 / å®¡æ ¸æ—¶é—´
     *
     * æœ¬ä»»åŠ¡çš„çŠ¶æ€ 2(盘点中)同时承载「盘点中」与「盘点结果审批中」两个阶段,
     * æ•…用本字段区分阶段:
     * <ul>
     *     <li>提交结果审批时写入提交时间,审核人与审核意见为空 â€”— ç»“果审批已提交、等待审核;</li>
     *     <li>审核时更新为实际审核时间 â€”— ä¸Ž reviewerId ä¸€åŒéžç©ºï¼Œè¡¨ç¤ºæœ¬è½®ç»“果审批已处理完毕;</li>
     *     <li>两者皆空表示本轮结果审批尚未提交,可继续录入实盘数量。</li>
     * </ul>
     */
    private LocalDateTime reviewTime;
    /**
     * å®¡æ ¸æ„è§ï¼ˆå®¡æ ¸é€šè¿‡æ—¶çš„æ„è§ / å®¡æ ¸ä¸é€šè¿‡æ—¶çš„原因)
     */
    private String reviewRemark;
    /**
     * å¤‡æ³¨
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/project/MesPdProjectMapper.java
@@ -5,7 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.project.MesPdProjectDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
@Mapper
public interface MesPdProjectMapper extends BaseMapperX<MesPdProjectDO> {
@@ -25,4 +28,47 @@
        return selectOne(MesPdProjectDO::getTaskCode, taskCode);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§æ¸…空。
     *
     * @param id     ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<MesPdProjectDO>()
                .eq(MesPdProjectDO::getId, id)
                .set(MesPdProjectDO::getStatus, status)
                .set(MesPdProjectDO::getReviewerId, null)
                .set(MesPdProjectDO::getReviewerName, null)
                .set(MesPdProjectDO::getReviewTime, null)
                .set(MesPdProjectDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå¾…å½’æ¡£ / å¾…审核)
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditProject(Long id, Integer status, Long reviewerId,
                             String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<MesPdProjectDO>()
                .eq(MesPdProjectDO::getId, id)
                .set(MesPdProjectDO::getStatus, status)
                .set(MesPdProjectDO::getReviewerId, reviewerId)
                .set(MesPdProjectDO::getReviewerName, reviewerName)
                .set(MesPdProjectDO::getReviewTime, LocalDateTime.now())
                .set(MesPdProjectDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/techconfirm/ProductTechConfirmMapper.java
@@ -5,7 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
@Mapper
public interface ProductTechConfirmMapper extends BaseMapperX<ProductTechConfirmDO> {
@@ -20,4 +23,47 @@
                .orderByDesc(ProductTechConfirmDO::getId));
    }
    /**
     * æäº¤å®¡æ ¸ï¼šç½®ä¸ºå¾…审核,并清空上一轮审核结果
     *
     * ä¹‹æ‰€ä»¥ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤çš„字段更新策略会忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人、审核时间、审核意见真正清空。
     *
     * @param id     ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå¾…å®¡æ ¸ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<ProductTechConfirmDO>()
                .eq(ProductTechConfirmDO::getId, id)
                .set(ProductTechConfirmDO::getStatus, status)
                .set(ProductTechConfirmDO::getReviewerId, null)
                .set(ProductTechConfirmDO::getReviewerName, null)
                .set(ProductTechConfirmDO::getReviewTime, null)
                .set(ProductTechConfirmDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * reviewRemark å…è®¸ä¸º null(审核通过时意见可选),故同样使用 UpdateWrapper ç›´å†™ã€‚
     *
     * @param id           ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå·²å®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditTechConfirm(Long id, Integer status, Long reviewerId,
                                 String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<ProductTechConfirmDO>()
                .eq(ProductTechConfirmDO::getId, id)
                .set(ProductTechConfirmDO::getStatus, status)
                .set(ProductTechConfirmDO::getReviewerId, reviewerId)
                .set(ProductTechConfirmDO::getReviewerName, reviewerName)
                .set(ProductTechConfirmDO::getReviewTime, LocalDateTime.now())
                .set(ProductTechConfirmDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/equipmentreceipt/MesWmEquipmentReceiptMapper.java
@@ -5,7 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.MesWmEquipmentReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * MES è®¾å¤‡å…¥åº“单 Mapper
@@ -27,4 +30,45 @@
        return selectOne(MesWmEquipmentReceiptDO::getCode, code);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§æ¸…空。
     *
     * @param id     ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<MesWmEquipmentReceiptDO>()
                .eq(MesWmEquipmentReceiptDO::getId, id)
                .set(MesWmEquipmentReceiptDO::getStatus, status)
                .set(MesWmEquipmentReceiptDO::getReviewerId, null)
                .set(MesWmEquipmentReceiptDO::getReviewerName, null)
                .set(MesWmEquipmentReceiptDO::getReviewTime, null)
                .set(MesWmEquipmentReceiptDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * @param id           ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå·²å®¡æ‰¹ / è‰ç¨¿ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditEquipmentReceipt(Long id, Integer status, Long reviewerId,
                                      String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<MesWmEquipmentReceiptDO>()
                .eq(MesWmEquipmentReceiptDO::getId, id)
                .set(MesWmEquipmentReceiptDO::getStatus, status)
                .set(MesWmEquipmentReceiptDO::getReviewerId, reviewerId)
                .set(MesWmEquipmentReceiptDO::getReviewerName, reviewerName)
                .set(MesWmEquipmentReceiptDO::getReviewTime, LocalDateTime.now())
                .set(MesWmEquipmentReceiptDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java
@@ -5,8 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -40,4 +42,45 @@
        return selectOne(MesWmItemReceiptDO::getNoticeId, noticeId);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå¾…上架,并清空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§æ¸…空。
     *
     * @param id     ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå¾…ä¸Šæž¶ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<MesWmItemReceiptDO>()
                .eq(MesWmItemReceiptDO::getId, id)
                .set(MesWmItemReceiptDO::getStatus, status)
                .set(MesWmItemReceiptDO::getReviewerId, null)
                .set(MesWmItemReceiptDO::getReviewerName, null)
                .set(MesWmItemReceiptDO::getReviewTime, null)
                .set(MesWmItemReceiptDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * @param id           ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå¾…æ‰§è¡Œå…¥åº“ / è‰ç¨¿ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditItemReceipt(Long id, Integer status, Long reviewerId,
                                 String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<MesWmItemReceiptDO>()
                .eq(MesWmItemReceiptDO::getId, id)
                .set(MesWmItemReceiptDO::getStatus, status)
                .set(MesWmItemReceiptDO::getReviewerId, reviewerId)
                .set(MesWmItemReceiptDO::getReviewerName, reviewerName)
                .set(MesWmItemReceiptDO::getReviewTime, LocalDateTime.now())
                .set(MesWmItemReceiptDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/miscissue/MesWmMiscIssueMapper.java
@@ -5,8 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssuePageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -35,4 +37,45 @@
        return selectList(MesWmMiscIssueDO::getStatus, status);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§æ¸…空。
     *
     * @param id     ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<MesWmMiscIssueDO>()
                .eq(MesWmMiscIssueDO::getId, id)
                .set(MesWmMiscIssueDO::getStatus, status)
                .set(MesWmMiscIssueDO::getReviewerId, null)
                .set(MesWmMiscIssueDO::getReviewerName, null)
                .set(MesWmMiscIssueDO::getReviewTime, null)
                .set(MesWmMiscIssueDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * @param id           ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå¾…æ‰§è¡Œå‡ºåº“ / è‰ç¨¿ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditMiscIssue(Long id, Integer status, Long reviewerId,
                              String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<MesWmMiscIssueDO>()
                .eq(MesWmMiscIssueDO::getId, id)
                .set(MesWmMiscIssueDO::getStatus, status)
                .set(MesWmMiscIssueDO::getReviewerId, reviewerId)
                .set(MesWmMiscIssueDO::getReviewerName, reviewerName)
                .set(MesWmMiscIssueDO::getReviewTime, LocalDateTime.now())
                .set(MesWmMiscIssueDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/miscreceipt/MesWmMiscReceiptMapper.java
@@ -5,7 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo.MesWmMiscReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscreceipt.MesWmMiscReceiptDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * MES æ‚项入库单 Mapper
@@ -31,4 +34,45 @@
        return selectOne(MesWmMiscReceiptDO::getCode, code);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§æ¸…空。
     *
     * @param id     ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå®¡æ‰¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<MesWmMiscReceiptDO>()
                .eq(MesWmMiscReceiptDO::getId, id)
                .set(MesWmMiscReceiptDO::getStatus, status)
                .set(MesWmMiscReceiptDO::getReviewerId, null)
                .set(MesWmMiscReceiptDO::getReviewerName, null)
                .set(MesWmMiscReceiptDO::getReviewTime, null)
                .set(MesWmMiscReceiptDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * @param id           ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå·²å®¡æ‰¹ / è‰ç¨¿ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditMiscReceipt(Long id, Integer status, Long reviewerId,
                                 String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<MesWmMiscReceiptDO>()
                .eq(MesWmMiscReceiptDO::getId, id)
                .set(MesWmMiscReceiptDO::getStatus, status)
                .set(MesWmMiscReceiptDO::getReviewerId, reviewerId)
                .set(MesWmMiscReceiptDO::getReviewerName, reviewerName)
                .set(MesWmMiscReceiptDO::getReviewTime, LocalDateTime.now())
                .set(MesWmMiscReceiptDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/returnvendor/MesWmReturnVendorMapper.java
@@ -5,7 +5,10 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * MES ä¾›åº”商退货单 Mapper
@@ -30,4 +33,45 @@
        return selectOne(MesWmReturnVendorDO::getCode, code);
    }
    /**
     * æäº¤å®¡æ‰¹ï¼šç½®ä¸ºå¾…拣货,并清空上一轮审核结果
     *
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§æ¸…空。
     *
     * @param id     ç¼–号
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆå¾…æ‹£è´§ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<MesWmReturnVendorDO>()
                .eq(MesWmReturnVendorDO::getId, id)
                .set(MesWmReturnVendorDO::getStatus, status)
                .set(MesWmReturnVendorDO::getReviewerId, null)
                .set(MesWmReturnVendorDO::getReviewerName, null)
                .set(MesWmReturnVendorDO::getReviewTime, null)
                .set(MesWmReturnVendorDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ï¼šå†™å…¥å®¡æ ¸ç»“果、审核人与审核意见
     *
     * @param id           ç¼–号
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå¾…æ‰§è¡Œé€€è´§ / è‰ç¨¿ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditReturnVendor(Long id, Integer status, Long reviewerId,
                                 String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<MesWmReturnVendorDO>()
                .eq(MesWmReturnVendorDO::getId, id)
                .set(MesWmReturnVendorDO::getStatus, status)
                .set(MesWmReturnVendorDO::getReviewerId, reviewerId)
                .set(MesWmReturnVendorDO::getReviewerName, reviewerName)
                .set(MesWmReturnVendorDO::getReviewTime, LocalDateTime.now())
                .set(MesWmReturnVendorDO::getReviewRemark, reviewRemark));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskMapper.java
@@ -8,6 +8,8 @@
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
/**
 * MES ç›˜ç‚¹ä»»åŠ¡ Mapper
 *
@@ -21,16 +23,79 @@
    }
    /**
     * æ¸…空 BPM æµç¨‹å®žä¾‹ç¼–号
     * å¼€å§‹ç›˜ç‚¹ï¼šç½®ä¸ºç›˜ç‚¹ä¸­ï¼Œå¹¶æ¸…空上一轮审核结果
     *
     * ç”¨äºŽç›˜ç‚¹ç»“果审批被驳回/取消后置空,任务回到可重新提交结果审批的状态
     * ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略忽略 null,
     * ç›´æŽ¥ updateById æ— æ³•把审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§çœŸæ­£æ¸…空。
     *
     * @param id     ç›˜ç‚¹ä»»åŠ¡ç¼–å·
     * @param status ç›®æ ‡çŠ¶æ€ï¼ˆç›˜ç‚¹ä¸­ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    default int submitAndResetAuditInfo(Long id, Integer status) {
        return update(null, new LambdaUpdateWrapper<MesWmStockTakingTaskDO>()
                .eq(MesWmStockTakingTaskDO::getId, id)
                .set(MesWmStockTakingTaskDO::getStatus, status)
                .set(MesWmStockTakingTaskDO::getReviewerId, null)
                .set(MesWmStockTakingTaskDO::getReviewerName, null)
                .set(MesWmStockTakingTaskDO::getReviewTime, null)
                .set(MesWmStockTakingTaskDO::getReviewRemark, null));
    }
    /**
     * æäº¤ç»“果审批:写入提交时间并清空上一轮的审核人与审核意见
     *
     * ä»»åŠ¡çŠ¶æ€ä¿æŒã€Œç›˜ç‚¹ä¸­ã€ä¸å˜ï¼ˆè¯¥çŠ¶æ€åŒæ—¶æ‰¿è½½ç›˜ç‚¹ä¸­ä¸Žç»“æžœå®¡æ‰¹ä¸­ä¸¤ä¸ªé˜¶æ®µï¼‰ï¼Œ
     * ä»¥ reviewTime éžç©ºä¸” reviewerId ä¸ºç©ºè¡¨ç¤ºã€Œç»“果审批已提交、等待审核」。
     * ä¸ä¿®æ”¹ status å­—段,故用 UpdateWrapper ç²¾ç¡®å†™å…¥ã€‚
     *
     * @param id ç›˜ç‚¹ä»»åŠ¡ç¼–å·
     * @return å½±å“è¡Œæ•°
     */
    default int markResultApprovalSubmitted(Long id) {
        return update(null, new LambdaUpdateWrapper<MesWmStockTakingTaskDO>()
                .eq(MesWmStockTakingTaskDO::getId, id)
                .set(MesWmStockTakingTaskDO::getReviewTime, LocalDateTime.now())
                .set(MesWmStockTakingTaskDO::getReviewerId, null)
                .set(MesWmStockTakingTaskDO::getReviewerName, null)
                .set(MesWmStockTakingTaskDO::getReviewRemark, null));
    }
    /**
     * å®¡æ ¸ç»“果:写入审核结果、审核人与审核意见
     *
     * @param id           ç›˜ç‚¹ä»»åŠ¡ç¼–å·
     * @param status       ç›®æ ‡çŠ¶æ€ï¼ˆå·²å®Œæˆ / ç›˜ç‚¹ä¸­ï¼‰
     * @param reviewerId   å®¡æ ¸äººç¼–号
     * @param reviewerName å®¡æ ¸äººå§“名
     * @param reviewRemark å®¡æ ¸æ„è§
     * @return å½±å“è¡Œæ•°
     */
    default int auditStockTakingTask(Long id, Integer status, Long reviewerId,
                                     String reviewerName, String reviewRemark) {
        return update(null, new LambdaUpdateWrapper<MesWmStockTakingTaskDO>()
                .eq(MesWmStockTakingTaskDO::getId, id)
                .set(MesWmStockTakingTaskDO::getStatus, status)
                .set(MesWmStockTakingTaskDO::getReviewerId, reviewerId)
                .set(MesWmStockTakingTaskDO::getReviewerName, reviewerName)
                .set(MesWmStockTakingTaskDO::getReviewTime, LocalDateTime.now())
                .set(MesWmStockTakingTaskDO::getReviewRemark, reviewRemark));
    }
    /**
     * æ¸…空审核标记
     *
     * ç”¨äºŽä»»åŠ¡å–æ¶ˆåŽæ¸…é™¤ç»“æžœå®¡æ‰¹ç—•è¿¹
     *
     * @param id ç›˜ç‚¹ä»»åŠ¡ç¼–å·
     */
    default void clearProcessInstanceId(Long id) {
    default void clearAuditInfo(Long id) {
        update(null, new LambdaUpdateWrapper<MesWmStockTakingTaskDO>()
                .eq(MesWmStockTakingTaskDO::getId, id)
                .set(MesWmStockTakingTaskDO::getProcessInstanceId, null));
                .set(MesWmStockTakingTaskDO::getReviewerId, null)
                .set(MesWmStockTakingTaskDO::getReviewerName, null)
                .set(MesWmStockTakingTaskDO::getReviewTime, null)
                .set(MesWmStockTakingTaskDO::getReviewRemark, null));
    }
    default PageResult<MesWmStockTakingTaskDO> selectPage(MesWmStockTakingTaskPageReqVO reqVO) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -523,6 +523,8 @@
    ErrorCode WM_ITEM_RECEIPT_STATUS_ERROR = new ErrorCode(1_040_705_005, "入库单状态不正确");
    ErrorCode WM_ITEM_RECEIPT_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_705_006, "已完成或已取消的入库单不允许取消");
    ErrorCode WM_ITEM_RECEIPT_ALREADY_EXISTS = new ErrorCode(1_040_705_007, "该到货通知单已生成入库单,不能重复生成");
    ErrorCode WM_ITEM_RECEIPT_NOT_APPROVING = new ErrorCode(1_040_705_008, "只有待上架状态的采购入库单才能审核");
    ErrorCode WM_ITEM_RECEIPT_REJECT_REASON_REQUIRED = new ErrorCode(1_040_705_009, "审核不通过时必须填写不通过原因");
    ErrorCode WM_PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_040_705_008, "采购订单不存在");
    ErrorCode WM_PURCHASE_ORDER_NO_ITEM = new ErrorCode(1_040_705_008, "采购订单无明细数据");
    ErrorCode WM_ITEM_RECEIPT_LINE_NOT_EXISTS = new ErrorCode(1_040_705_100, "采购入库单行不存在");
@@ -625,6 +627,8 @@
    ErrorCode WM_RETURN_VENDOR_CODE_DUPLICATE = new ErrorCode(1_040_711_005, "供应商退货单编号已存在");
    ErrorCode WM_RETURN_VENDOR_NO_DETAIL = new ErrorCode(1_040_711_006, "供应商退货单没有拣货明细,无法执行退货");
    ErrorCode WM_RETURN_VENDOR_DETAIL_ITEM_MISMATCH = new ErrorCode(1_040_711_007, "拣货明细的物料与退货单行的物料不一致");
    ErrorCode WM_RETURN_VENDOR_NOT_APPROVING = new ErrorCode(1_040_711_008, "只有待拣货状态的供应商退货单才能审核");
    ErrorCode WM_RETURN_VENDOR_REJECT_REASON_REQUIRED = new ErrorCode(1_040_711_009, "审核不通过时必须填写不通过原因");
    ErrorCode WM_RETURN_VENDOR_LINE_NOT_EXISTS = new ErrorCode(1_040_711_100, "供应商退货单行不存在");
    ErrorCode WM_RETURN_VENDOR_DETAIL_NOT_EXISTS = new ErrorCode(1_040_711_200, "供应商退货单明细不存在");
    ErrorCode WM_RETURN_VENDOR_DETAIL_QUANTITY_INVALID = new ErrorCode(1_040_711_201, "退货明细数量必须大于 0");
@@ -691,6 +695,8 @@
    ErrorCode WM_STOCK_TAKING_TASK_NOT_FINISHED = new ErrorCode(1_040_714_121, "只有已完成状态的盘点任务才能处理差异");
    ErrorCode WM_STOCK_TAKING_TASK_NO_RESULT = new ErrorCode(1_040_714_122, "盘点任务至少需要一条盘点结果才允许提交结果审批");
    ErrorCode WM_STOCK_TAKING_TASK_ALREADY_SUBMIT_APPROVAL = new ErrorCode(1_040_714_123, "盘点结果审批已提交,请勿重复提交");
    ErrorCode WM_STOCK_TAKING_TASK_RESULT_NOT_SUBMITTED = new ErrorCode(1_040_714_124, "只有已提交结果审批的盘点任务才能审核");
    ErrorCode WM_STOCK_TAKING_TASK_REJECT_REASON_REQUIRED = new ErrorCode(1_040_714_125, "审核不通过时必须填写不通过原因");
    // ========== MES ä»“库管理-销售出库单(1-040-714-000) ==========
    ErrorCode WM_PRODUCT_SALES_NOT_EXISTS = new ErrorCode(1_040_714_000, "销售出库单不存在");
@@ -732,6 +738,8 @@
    ErrorCode WM_MISC_ISSUE_NO_LINE = new ErrorCode(1_040_715_003, "杂项出库单至少需要一条行数据");
    ErrorCode WM_MISC_ISSUE_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_715_004, "杂项出库单已完成或已取消,无法取消");
    ErrorCode WM_MISC_ISSUE_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_715_005, "杂项出库单行数量与明细数量不一致");
    ErrorCode WM_MISC_ISSUE_NOT_APPROVING = new ErrorCode(1_040_715_006, "只有审批中状态的杂项出库单才能审核");
    ErrorCode WM_MISC_ISSUE_REJECT_REASON_REQUIRED = new ErrorCode(1_040_715_007, "审核不通过时必须填写不通过原因");
    ErrorCode WM_MISC_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_715_100, "杂项出库单行不存在");
    ErrorCode WM_MISC_ISSUE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_715_200, "杂项出库单明细不存在");
@@ -742,6 +750,8 @@
    ErrorCode WM_MISC_RECEIPT_STATUS_NOT_APPROVED = new ErrorCode(1_040_716_003, "只有已审批状态才允许执行入库");
    ErrorCode WM_MISC_RECEIPT_NO_LINE = new ErrorCode(1_040_716_004, "至少需要一条行项目");
    ErrorCode WM_MISC_RECEIPT_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_716_005, "已完成或已取消的入库单不允许取消");
    ErrorCode WM_MISC_RECEIPT_NOT_APPROVING = new ErrorCode(1_040_716_006, "只有审批中状态的杂项入库单才能审核");
    ErrorCode WM_MISC_RECEIPT_REJECT_REASON_REQUIRED = new ErrorCode(1_040_716_007, "审核不通过时必须填写不通过原因");
    ErrorCode WM_MISC_RECEIPT_LINE_NOT_EXISTS = new ErrorCode(1_040_716_100, "杂项入库单行不存在");
    ErrorCode WM_MISC_RECEIPT_WAREHOUSE_REQUIRED = new ErrorCode(1_040_716_101, "仓库不能为空");
    ErrorCode WM_MISC_RECEIPT_QUANTITY_INVALID = new ErrorCode(1_040_716_102, "入库数量必须大于 0");
@@ -794,6 +804,8 @@
    ErrorCode WM_EQUIP_RECEIPT_LINE_NOT_EXISTS = new ErrorCode(1_040_733_100, "设备入库单行不存在");
    ErrorCode WM_EQUIP_RECEIPT_MACHINERY_NOT_VALID = new ErrorCode(1_040_733_101, "所选设备未关联物料,无法入库");
    ErrorCode WM_EQUIP_RECEIPT_QUANTITY_INVALID = new ErrorCode(1_040_733_102, "入库数量必须大于 0");
    ErrorCode WM_EQUIP_RECEIPT_NOT_APPROVING = new ErrorCode(1_040_733_006, "只有审批中状态的设备入库单才能审核");
    ErrorCode WM_EQUIP_RECEIPT_REJECT_REASON_REQUIRED = new ErrorCode(1_040_733_007, "审核不通过时必须填写不通过原因");
    // ========== MES ä»“库管理-装箱单(1-040-740-000) ==========
    ErrorCode WM_PACKAGE_NOT_EXISTS = new ErrorCode(1_040_740_000, "装箱单不存在");
@@ -817,8 +829,8 @@
    ErrorCode PD_PROJECT_NOT_AUDIT_FOR_ARCHIVE = new ErrorCode(1_040_800_006, "只有待归档状态的设计项目才能归档");
    ErrorCode PD_PROJECT_AUDIT_NOT_FINISHED = new ErrorCode(1_040_800_007, "该项目下存在未审核的资料,请先完成所有资料审核");
    ErrorCode PD_PROJECT_NOT_UPLOAD_FOR_APPROVAL = new ErrorCode(1_040_800_008, "只有待审核状态的设计项目才能提交审批");
    ErrorCode PD_PROJECT_NOT_APPROVING = new ErrorCode(1_040_800_009, "设计项目不处于审批中状态");
    ErrorCode PD_PROJECT_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_040_800_010, "设计项目审批分类不存在,请先配置 BPM å®¡æ‰¹æµç¨‹");
    ErrorCode PD_PROJECT_NOT_APPROVING = new ErrorCode(1_040_800_009, "只有审批中状态的设计项目才能审核");
    ErrorCode PD_PROJECT_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_040_800_010, "审核不通过时必须填写不通过原因");
    // ========== MES äº§å“è®¾è®¡-资料子表(1-040-801-000) ==========
    ErrorCode PD_DOCUMENT_NOT_EXISTS = new ErrorCode(1_040_801_000, "设计资料不存在");
@@ -849,6 +861,11 @@
    // ========== MES äº§å“è®¾è®¡-技术确认函(1-040-808-000) ==========
    ErrorCode PD_TECH_CONFIRM_NOT_EXISTS = new ErrorCode(1_040_808_000, "技术确认函不存在");
    ErrorCode PD_TECH_CONFIRM_NO_DUPLICATE = new ErrorCode(1_040_808_001, "确认函编号已存在");
    ErrorCode PD_TECH_CONFIRM_NOT_EDITABLE = new ErrorCode(1_040_808_002, "只有草稿或审核不通过状态的技术确认函才能修改或删除");
    ErrorCode PD_TECH_CONFIRM_NOT_EDITABLE_FOR_SUBMIT = new ErrorCode(1_040_808_003, "只有草稿或审核不通过状态的技术确认函才能提交审核");
    ErrorCode PD_TECH_CONFIRM_NOT_PENDING_AUDIT = new ErrorCode(1_040_808_004, "只有待审核状态的技术确认函才能审核");
    ErrorCode PD_TECH_CONFIRM_REJECT_REASON_REQUIRED = new ErrorCode(1_040_808_007, "审核不通过时必须填写不通过原因");
    ErrorCode PD_TECH_CONFIRM_NOT_AUDITED_FOR_CUSTOMER_CONFIRM = new ErrorCode(1_040_808_008, "只有已审核状态的技术确认函才能进行客户确认");
    // ========== MES äº§å“è®¾è®¡-产品档案与基础数据(1-040-809-000) ==========
    ErrorCode PD_PRODUCT_NOT_EXISTS = new ErrorCode(1_040_809_000, "产品档案不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java
@@ -39,7 +39,7 @@
    /**
     * å¾…å½’æ¡£
     *
     * å¯¹åº” MesPdProjectService#updateProjectAuditStatus æ–¹æ³•(BPM å®¡æ‰¹é€šè¿‡åŽï¼‰
     * å¯¹åº” MesPdProjectService#auditProject æ–¹æ³•(审核通过后)
     */
    AUDIT(MesPdStatusConstants.AUDIT, "待归档"),
    /**
@@ -51,7 +51,7 @@
    /**
     * å®¡æ‰¹ä¸­
     *
     * å¯¹åº” MesPdProjectService#submitProjectApproval æ–¹æ³•
     * å¯¹åº” MesPdProjectService#submitProjectApproval æ–¹æ³•;审核通过后置为待归档,不通过退回待审核
     */
    APPROVING(MesPdStatusConstants.APPROVING, "审批中");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdTechConfirmStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.mes.enums.pd;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
 * äº§å“è®¾è®¡-技术确认函状态
 *
 * @author è¶…级管理员
 */
@RequiredArgsConstructor
@Getter
public enum MesPdTechConfirmStatusEnum implements ArrayValuable<Integer> {
    DRAFT(0, "草稿"),
    PENDING_AUDIT(1, "待审核"),
    AUDITED(2, "已审核"),
    CUSTOMER_CONFIRMED(3, "客户已确认"),
    REJECTED(4, "审核不通过");
    private final Integer status;
    private final String name;
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesPdTechConfirmStatusEnum::getStatus).toArray(Integer[]::new);
    public static String getNameByStatus(Integer status) {
        if (status == null) {
            return null;
        }
        for (MesPdTechConfirmStatusEnum e : values()) {
            if (e.getStatus().equals(status)) {
                return e.getName();
            }
        }
        return null;
    }
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscIssueStatusEnum.java
@@ -33,7 +33,7 @@
    /**
     * å¾…执行出库
     *
     * å¯¹åº” MesWmMiscIssueService#updateMiscIssueAuditStatus æ–¹æ³•
     * å¯¹åº” MesWmMiscIssueService#auditMiscIssue æ–¹æ³•
     */
    APPROVED(MesOrderStatusConstants.APPROVED, "待执行出库"),
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMiscReceiptStatusEnum.java
@@ -31,7 +31,7 @@
    /**
     * å·²å®¡æ‰¹
     *
     * å¯¹åº” MesWmMiscReceiptService#updateMiscReceiptAuditStatus æ–¹æ³•
     * å¯¹åº” MesWmMiscReceiptService#auditMiscReceipt æ–¹æ³•
     */
    APPROVED(MesOrderStatusConstants.APPROVED, "已审批"),
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockTakingTaskStatusEnum.java
@@ -32,7 +32,7 @@
    /**
     * å·²å®Œæˆ
     *
     * ç›˜ç‚¹ç»“果审批通过后,由结果审批回调置为已完成并自动核销差异
     * ç›˜ç‚¹ç»“果审核通过后,由结果审核置为已完成并自动核销差异
     */
    FINISHED(MesOrderStatusConstants.FINISHED, "已完成"),
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java
@@ -9,6 +9,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * MES è®¾è®¡é¡¹ç›® Service æŽ¥å£
@@ -82,28 +83,34 @@
    /**
     * æäº¤è®¾è®¡é¡¹ç›®å®¡æ‰¹ï¼ˆå¾…审核→审批中)
     * <p>
     * åˆ›å»º BPM æµç¨‹å®žä¾‹ï¼Œå®¡æ‰¹é€šè¿‡åŽç”±ç›‘听器回调更新为待归档
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  mes_pd_project_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。
     *
     * @param id                  ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     */
    void submitProjectApproval(Long id, String processDefinitionKey);
    void submitProjectApproval(Long id);
    /**
     * èŽ·å¾—è®¾è®¡é¡¹ç›®å®¡æ‰¹æµç¨‹åˆ—è¡¨
     * èŽ·å¾—è®¾è®¡é¡¹ç›®å®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * @return æµç¨‹åˆ—表(id/name/version/key)
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    List<Map<String, Object>> getProjectApproveProcessDefinitionList();
    Set<Long> getProjectApproverUserIds();
    /**
     * æ›´æ–°è®¾è®¡é¡¹ç›®å®¡æ‰¹çŠ¶æ€ï¼ˆBPM ç›‘听器回调)
     * å®¡æ ¸è®¾è®¡é¡¹ç›®ï¼ˆå®¡æ‰¹ä¸­â†’å¾…å½’æ¡£ / å¾…审核)
     *
     * ä»…业务编码 mes_pd_project_approve é…ç½®çš„审核人可操作(多人时或签),且仅在审批中状态下允许。
     * å®¡æ ¸é€šè¿‡ç½®ä¸ºå¾…归档;审核不通过必须填写原因,退回待审核状态可修改后重新提交。
     *
     * @param id       ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void updateProjectAuditStatus(Long id, Integer bpmResult);
    void auditProject(Long id, Boolean pass, String reviewRemark);
    /**
     * å½’档设计项目(待归档→已归档)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java
@@ -1,14 +1,7 @@
package cn.iocoder.yudao.module.mes.service.pd.project;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
@@ -22,22 +15,22 @@
import cn.iocoder.yudao.module.mes.enums.pd.MesPdProjectStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.mes.service.pd.document.MesPdDocumentService;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
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.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -55,9 +48,11 @@
public class MesPdProjectServiceImpl implements MesPdProjectService {
    /**
     * BPM äº§å“é¡¹ç›®å®¡æ‰¹åˆ†ç±»ç¼–码
     * è®¾è®¡é¡¹ç›®å®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String BPM_PROCESS_DEFINITION_CATEGORY_CODE = "mes_pd_project_approve";
    private static final String PROJECT_APPROVE_BIZ_TYPE = "mes_pd_project_approve";
    @Resource
    private MesPdProjectMapper projectMapper;
@@ -77,13 +72,10 @@
    private MesMdAutoCodeRecordService autoCodeRecordService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    private AdminUserApi adminUserApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -210,7 +202,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitProjectApproval(Long id, String processDefinitionKey) {
    public void submitProjectApproval(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¾…审核状态
        MesPdProjectDO project = validateProjectExists(id);
        if (!MesPdProjectStatusEnum.UPLOAD.getStatus().equals(project.getStatus())) {
@@ -222,90 +214,56 @@
        if (documentAuditMapper.existsPendingAuditByDocumentIds(documentIds)) {
            throw exception(PD_PROJECT_AUDIT_NOT_FINISHED);
        }
        // 3. åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 4. æ›´æ–°é¡¹ç›®çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­
        projectMapper.updateById(MesPdProjectDO.builder()
                .id(id)
                .status(MesPdProjectStatusEnum.APPROVING.getStatus())
                .processInstanceId(processInstanceId)
                .processDefinitionKey(processDefinitionKey)
                .build());
        // 3. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(PROJECT_APPROVE_BIZ_TYPE);
        // 4. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœ
        projectMapper.submitAndResetAuditInfo(id, MesPdProjectStatusEnum.APPROVING.getStatus());
    }
    @Override
    public List<Map<String, Object>> getProjectApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(BPM_PROCESS_DEFINITION_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(PD_PROJECT_BPM_CATEGORY_NOT_EXISTS);
        }
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(BPM_PROCESS_DEFINITION_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    public Set<Long> getProjectApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(PROJECT_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProjectAuditStatus(Long id, Integer bpmResult) {
    public void auditProject(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesPdProjectDO project = validateProjectExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!MesPdProjectStatusEnum.APPROVING.getStatus().equals(project.getStatus())) {
            log.warn("[updateProjectAuditStatus] è®¾è®¡é¡¹ç›®({}) ä¸å¤„于审批中状态,当前状态:{}", id, project.getStatus());
            throw exception(PD_PROJECT_NOT_APPROVING);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(PROJECT_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回待审核状态,可修改后重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(PD_PROJECT_AUDIT_REJECT_REASON_REQUIRED);
            }
            projectMapper.auditProject(id, MesPdProjectStatusEnum.UPLOAD.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToProjectStatus(bpmResult);
        if (newStatus != null) {
            projectMapper.updateById(MesPdProjectDO.builder()
                    .id(id)
                    .status(newStatus)
                    .build());
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为待归档
        projectMapper.auditProject(id, MesPdProjectStatusEnum.AUDIT.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    /**
     * è½¬æ¢ BPM å®¡æ‰¹ç»“果为设计项目状态
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private Integer convertBpmResultToProjectStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return MesPdProjectStatusEnum.AUDIT.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return MesPdProjectStatusEnum.UPLOAD.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return MesPdProjectStatusEnum.UPLOAD.getStatus();
        }
    private String getUserNickname(Long userId) {
        if (userId == null) {
        return null;
    }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/listener/MesPdProjectStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmService.java
@@ -5,21 +5,109 @@
import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
import java.util.Set;
/**
 * MES äº§å“æŠ€æœ¯ç¡®è®¤å‡½ Service æŽ¥å£
 *
 * çŠ¶æ€æµè½¬å®Œå…¨ç”±ä¸šåŠ¡æµç¨‹é©±åŠ¨ï¼Œä¸æŽ¥å—å‰ç«¯ç›´æŽ¥æŒ‡å®šçŠ¶æ€ï¼š
 * <pre>
 *   0 è‰ç¨¿ --提交审核--> 1 å¾…审核 --审核通过--> 2 å·²å®¡æ ¸ --客户确认--> 3 å®¢æˆ·å·²ç¡®è®¤
 *      ^                    |
 *      |                    +--审核不通过--> 4 å®¡æ ¸ä¸é€šè¿‡
 *      |                                        |
 *      +----------------------------------------+  å®¡æ ¸ä¸é€šè¿‡å¯ä¿®æ”¹åŽé‡æ–°æäº¤
 * </pre>
 * åªæœ‰è‰ç¨¿ä¸Žå®¡æ ¸ä¸é€šè¿‡çŠ¶æ€å…è®¸ä¿®æ”¹ä¸šåŠ¡å­—æ®µï¼Œå¾…å®¡æ ¸ / å·²å®¡æ ¸ / å®¢æˆ·å·²ç¡®è®¤ä¸ºé”å®šæˆ–终态。
 *
 * @author è¶…级管理员
 */
public interface ProductTechConfirmService {
    /**
     * åˆ›å»ºæŠ€æœ¯ç¡®è®¤å‡½
     *
     * çŠ¶æ€å›ºå®šä¸ºè‰ç¨¿ï¼Œç¼–åˆ¶äºº / ç¼–制时间取当前登录用户与当前时间。
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return ç¼–号
     */
    Long createTechConfirm(ProductTechConfirmSaveReqVO createReqVO);
    /**
     * ä¿®æ”¹æŠ€æœ¯ç¡®è®¤å‡½
     *
     * ä»…草稿与审核不通过状态允许修改,其余状态一律拒绝。
     *
     * @param updateReqVO ä¿®æ”¹ä¿¡æ¯
     */
    void updateTechConfirm(ProductTechConfirmSaveReqVO updateReqVO);
    /**
     * åˆ é™¤æŠ€æœ¯ç¡®è®¤å‡½
     *
     * ä»…草稿与审核不通过状态允许删除。
     *
     * @param id ç¼–号
     */
    void deleteTechConfirm(Long id);
    /**
     * èŽ·å¾—æŠ€æœ¯ç¡®è®¤å‡½
     *
     * @param id ç¼–号
     * @return æŠ€æœ¯ç¡®è®¤å‡½
     */
    ProductTechConfirmDO getTechConfirm(Long id);
    /**
     * èŽ·å¾—æŠ€æœ¯ç¡®è®¤å‡½åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
     * @return æŠ€æœ¯ç¡®è®¤å‡½åˆ†é¡µ
     */
    PageResult<ProductTechConfirmDO> getTechConfirmPage(ProductTechConfirmPageReqVO pageReqVO);
    /**
     * æäº¤å®¡æ ¸ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ â†’ å¾…审核)
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  pd_tech_confirm_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。
     *
     * @param id ç¼–号
     */
    void submitTechConfirmAudit(Long id);
    /**
     * èŽ·å¾—æŠ€æœ¯ç¡®è®¤å‡½å®¡æ ¸çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getTechConfirmApproverUserIds();
    /**
     * å®¡æ ¸æŠ€æœ¯ç¡®è®¤å‡½ï¼ˆå¾…审核 â†’ å·²å®¡æ ¸ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
     *
     * ä»…业务编码 pd_tech_confirm_approve é…ç½®çš„审核人可操作(多人时或签),且仅在待审核状态下允许。
     * å®¡æ ¸é€šè¿‡è®°å½•审核人与审核时间;审核不通过必须填写不通过原因,
     * ä½¿å•据退回可修改状态,由编制人修改后重新提交。
     *
     * @param id           ç¼–号
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void auditTechConfirm(Long id, Boolean pass, String reviewRemark);
    /**
     * å®¢æˆ·ç¡®è®¤ï¼ˆå·²å®¡æ ¸ â†’ å®¢æˆ·å·²ç¡®è®¤ï¼‰
     *
     * ç”±ä¸šåŠ¡å‘˜ä»£å®¢æˆ·æ‰§è¡Œï¼Œè®°å½•å®¢æˆ·ç¡®è®¤äººä¸Žå®¢æˆ·ç¡®è®¤æ—¶é—´ã€‚
     *
     * @param id                  ç¼–号
     * @param customerConfirmUser å®¢æˆ·ç¡®è®¤äººå§“名
     */
    void confirmTechConfirmByCustomer(Long id, String customerConfirmUser);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java
@@ -1,56 +1,108 @@
package cn.iocoder.yudao.module.mes.service.pd.techconfirm;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pd.techconfirm.ProductTechConfirmMapper;
import cn.iocoder.yudao.module.mes.enums.pd.MesPdTechConfirmStatusEnum;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_TECH_CONFIRM_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_TECH_CONFIRM_NO_DUPLICATE;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
 * MES äº§å“æŠ€æœ¯ç¡®è®¤å‡½ Service å®žçŽ°ç±»
 *
 * çŠ¶æ€å®Œå…¨ç”±ä¸šåŠ¡æµç¨‹é©±åŠ¨ï¼Œå‰ç«¯ä¸å‚ä¸ŽçŠ¶æ€èµ‹å€¼ã€‚å„çŠ¶æ€çš„å†™å…¥å…¥å£ä¸¥æ ¼åŒºåˆ†ï¼š
 * <ul>
 *     <li>草稿:{@link #createTechConfirm} åˆ›å»ºæ—¶å†™å…¥ï¼›{@link #auditTechConfirm} å®¢æˆ·ç¡®è®¤è¢«é©³å›žæ—¶å›žé€€</li>
 *     <li>待审核:{@link #submitTechConfirmAudit} æäº¤å®¡æ ¸åŽå†™å…¥ï¼ŒåŒæ—¶è®°å½•编制人指定的审核人</li>
 *     <li>已审核:{@link #auditTechConfirm} å®¡æ ¸é€šè¿‡æ—¶å†™å…¥ï¼ŒåŒæ—¶è®°å½•审核时间与审核意见</li>
 *     <li>审核不通过:{@link #auditTechConfirm} å®¡æ ¸ä¸é€šè¿‡æ—¶å†™å…¥ï¼Œä¿ç•™ä¸é€šè¿‡åŽŸå› ä¾›ç¼–åˆ¶äººä¿®æ”¹åŽé‡æ–°æäº¤</li>
 *     <li>客户已确认:{@link #confirmTechConfirmByCustomer} å®¢æˆ·ç¡®è®¤åŽå†™å…¥</li>
 * </ul>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
@Slf4j
public class ProductTechConfirmServiceImpl implements ProductTechConfirmService {
    /**
     * æŠ€æœ¯ç¡®è®¤å‡½å®¡æ‰¹çš„业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String TECH_CONFIRM_APPROVE_BIZ_TYPE = "pd_tech_confirm_approve";
    @Resource
    private ProductTechConfirmMapper techConfirmMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private ApprovalConfigApi approvalConfigApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createTechConfirm(ProductTechConfirmSaveReqVO createReqVO) {
        // æ ¡éªŒç¡®è®¤å‡½ç¼–号唯一
        validateConfirmNoUnique(createReqVO.getConfirmNo(), null);
        // æ’å…¥
        // æ’入:状态固定为草稿,编制人 / ç¼–制时间取当前登录用户与当前时间
        Long userId = getLoginUserId();
        ProductTechConfirmDO techConfirm = BeanUtils.toBean(createReqVO, ProductTechConfirmDO.class);
        techConfirm.setStatus(MesPdTechConfirmStatusEnum.DRAFT.getStatus());
        techConfirm.setDrafterId(userId);
        techConfirm.setDrafterName(getUserNickname(userId));
        techConfirm.setDraftTime(LocalDateTime.now());
        // å®¡æ ¸äººç”±æäº¤å®¡æ ¸æ—¶æŒ‡å®šï¼Œåˆ›å»ºæ—¶æ¸…空,避免前端传参带入
        techConfirm.setReviewerId(null);
        techConfirm.setReviewerName(null);
        techConfirm.setReviewTime(null);
        techConfirm.setReviewRemark(null);
        techConfirm.setCustomerConfirmUser(null);
        techConfirm.setCustomerConfirmTime(null);
        techConfirmMapper.insert(techConfirm);
        return techConfirm.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateTechConfirm(ProductTechConfirmSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨
        validateTechConfirmExists(updateReqVO.getId());
        ProductTechConfirmDO techConfirm = validateTechConfirmExists(updateReqVO.getId());
        // æ ¡éªŒçŠ¶æ€ä¸ºè‰ç¨¿æˆ–å®¡æ ¸ä¸é€šè¿‡
        validateTechConfirmEditable(techConfirm);
        // æ ¡éªŒç¡®è®¤å‡½ç¼–号唯一(排除自身)
        validateConfirmNoUnique(updateReqVO.getConfirmNo(), updateReqVO.getId());
        // æ›´æ–°
        // æ›´æ–°ï¼šçŠ¶æ€ã€ç¼–åˆ¶äººã€å®¡æ ¸äººã€å®¢æˆ·ç¡®è®¤äººç­‰æµç¨‹å­—æ®µä¸€å¾‹ä¸æŽ¥æ”¶å‰ç«¯ä¼ å‚ï¼Œæ­¤å¤„åªè½ä¸šåŠ¡å­—æ®µ
        ProductTechConfirmDO updateObj = BeanUtils.toBean(updateReqVO, ProductTechConfirmDO.class);
        techConfirmMapper.updateById(updateObj);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteTechConfirm(Long id) {
        // æ ¡éªŒå­˜åœ¨
        validateTechConfirmExists(id);
        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
        // æ ¡éªŒçŠ¶æ€ä¸ºè‰ç¨¿æˆ–å®¡æ ¸ä¸é€šè¿‡
        validateTechConfirmEditable(techConfirm);
        // åˆ é™¤
        techConfirmMapper.deleteById(id);
    }
@@ -63,6 +115,85 @@
    @Override
    public PageResult<ProductTechConfirmDO> getTechConfirmPage(ProductTechConfirmPageReqVO pageReqVO) {
        return techConfirmMapper.selectPage(pageReqVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitTechConfirmAudit(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¯æäº¤çŠ¶æ€ï¼ˆè‰ç¨¿ / å®¡æ ¸ä¸é€šè¿‡ï¼‰
        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
        if (!MesPdTechConfirmStatusEnum.DRAFT.getStatus().equals(techConfirm.getStatus())
                && !MesPdTechConfirmStatusEnum.REJECTED.getStatus().equals(techConfirm.getStatus())) {
            throw exception(PD_TECH_CONFIRM_NOT_EDITABLE_FOR_SUBMIT);
        }
        // 2. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(TECH_CONFIRM_APPROVE_BIZ_TYPE);
        // 3. æ›´æ–°çŠ¶æ€ä¸ºå¾…å®¡æ ¸ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœ
        //    ç”¨ UpdateWrapper è€Œéž updateById:MyBatis-Plus é»˜è®¤æ›´æ–°ç­–略会忽略 null,
        //    ç›´æŽ¥ updateById æ— æ³•把审核不通过后残留的审核人 / å®¡æ ¸æ—¶é—´ / å®¡æ ¸æ„è§çœŸæ­£æ¸…空。
        techConfirmMapper.submitAndResetAuditInfo(id, MesPdTechConfirmStatusEnum.PENDING_AUDIT.getStatus());
    }
    @Override
    public Set<Long> getTechConfirmApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(TECH_CONFIRM_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditTechConfirm(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
        // 2. æ ¡éªŒå¤„于待审核状态
        if (!MesPdTechConfirmStatusEnum.PENDING_AUDIT.getStatus().equals(techConfirm.getStatus())) {
            throw exception(PD_TECH_CONFIRM_NOT_PENDING_AUDIT);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(TECH_CONFIRM_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,状态置为审核不通过,退回编制人修改后可重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(PD_TECH_CONFIRM_REJECT_REASON_REQUIRED);
            }
            techConfirmMapper.auditTechConfirm(id, MesPdTechConfirmStatusEnum.REJECTED.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为已审核
        techConfirmMapper.auditTechConfirm(id, MesPdTechConfirmStatusEnum.AUDITED.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void confirmTechConfirmByCustomer(Long id, String customerConfirmUser) {
        // 1. æ ¡éªŒå­˜åœ¨ + å·²å®¡æ ¸çŠ¶æ€
        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
        if (!MesPdTechConfirmStatusEnum.AUDITED.getStatus().equals(techConfirm.getStatus())) {
            throw exception(PD_TECH_CONFIRM_NOT_AUDITED_FOR_CUSTOMER_CONFIRM);
        }
        // 2. æ›´æ–°çŠ¶æ€ä¸ºå®¢æˆ·å·²ç¡®è®¤ï¼Œå¹¶è®°å½•å®¢æˆ·ç¡®è®¤äººä¸Žç¡®è®¤æ—¶é—´
        techConfirmMapper.updateById(ProductTechConfirmDO.builder()
                .id(id)
                .status(MesPdTechConfirmStatusEnum.CUSTOMER_CONFIRMED.getStatus())
                .customerConfirmUser(customerConfirmUser)
                .customerConfirmTime(LocalDateTime.now())
                .build());
    }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    private void validateConfirmNoUnique(String confirmNo, Long excludeId) {
@@ -84,4 +215,19 @@
        return techConfirm;
    }
    /**
     * æ ¡éªŒæŠ€æœ¯ç¡®è®¤å‡½å¤„于可修改状态
     *
     * åªæœ‰è‰ç¨¿ä¸Žå®¡æ ¸ä¸é€šè¿‡å…è®¸ä¿®æ”¹ä¸Žåˆ é™¤ï¼›å¾…审核 / å·²å®¡æ ¸ / å®¢æˆ·å·²ç¡®è®¤ä¸ºé”å®šæˆ–终态。
     * å®¡æ ¸ä¸é€šè¿‡çš„单据需退回给编制人修改后重新提交,因此同样放开。
     *
     * @param techConfirm æŠ€æœ¯ç¡®è®¤å‡½
     */
    private void validateTechConfirmEditable(ProductTechConfirmDO techConfirm) {
        if (!MesPdTechConfirmStatusEnum.DRAFT.getStatus().equals(techConfirm.getStatus())
                && !MesPdTechConfirmStatusEnum.REJECTED.getStatus().equals(techConfirm.getStatus())) {
            throw exception(PD_TECH_CONFIRM_NOT_EDITABLE);
        }
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptService.java
@@ -6,12 +6,9 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface MesWmEquipmentReceiptService {
    String APPROVE_CATEGORY_CODE = "equipment_receipt_approve";
    Long createEquipmentReceipt(@Valid MesWmEquipmentReceiptSaveReqVO createReqVO);
@@ -23,11 +20,36 @@
    PageResult<MesWmEquipmentReceiptDO> getEquipmentReceiptPage(MesWmEquipmentReceiptPageReqVO pageReqVO);
    void submitEquipmentReceiptApproval(Long id, String processDefinitionKey);
    /**
     * æäº¤è®¾å¤‡å…¥åº“单审批(草稿 â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  equipment_receipt_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。
     *
     * @param id ç¼–号
     */
    void submitEquipmentReceiptApproval(Long id);
    void updateEquipmentReceiptAuditStatus(Long id, Integer bpmResult);
    /**
     * èŽ·å¾—è®¾å¤‡å…¥åº“å•å®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getEquipmentReceiptApproverUserIds();
    List<Map<String, Object>> getEquipmentReceiptApprovalProcessDefinitionList();
    /**
     * å®¡æ ¸è®¾å¤‡å…¥åº“单(审批中 â†’ å·²å®¡æ‰¹ / è‰ç¨¿ï¼‰
     *
     * ä»…业务编码 equipment_receipt_approve é…ç½®çš„审核人可操作(多人时或签),且仅在审批中状态下允许。
     * å®¡æ ¸é€šè¿‡ç½®ä¸ºå·²å®¡æ‰¹ï¼ˆå¯æ‰§è¡Œå…¥åº“);审核不通过必须填写原因,退回草稿可修改后重新提交。
     *
     * @param id           ç¼–号
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void auditEquipmentReceipt(Long id, Boolean pass, String reviewRemark);
    void finishEquipmentReceipt(Long id);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/MesWmEquipmentReceiptServiceImpl.java
@@ -2,13 +2,9 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.MesWmEquipmentReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.equipmentreceipt.vo.MesWmEquipmentReceiptSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.equipmentreceipt.MesWmEquipmentReceiptDO;
@@ -25,21 +21,23 @@
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.*;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -49,6 +47,13 @@
@Validated
@Slf4j
public class MesWmEquipmentReceiptServiceImpl implements MesWmEquipmentReceiptService {
    /**
     * è®¾å¤‡å…¥åº“单审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String EQUIPMENT_RECEIPT_APPROVE_BIZ_TYPE = "equipment_receipt_approve";
    @Resource
    private MesWmEquipmentReceiptMapper equipmentReceiptMapper;
@@ -64,9 +69,9 @@
    @Resource
    private MesWmWarehouseAreaService wmAreaService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Override
    public Long createEquipmentReceipt(MesWmEquipmentReceiptSaveReqVO createReqVO) {
@@ -114,95 +119,63 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitEquipmentReceiptApproval(Long id, String processDefinitionKey) {
        // æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
    public void submitEquipmentReceiptApproval(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateEquipmentReceiptExistsAndPrepare(id);
        // æ ¡éªŒè‡³å°‘有一条行
        // 2. æ ¡éªŒè‡³å°‘有一条行
        List<MesWmEquipmentReceiptLineDO> lines = equipmentReceiptLineService.getEquipmentReceiptLineListByReceiptId(id);
        if (CollUtil.isEmpty(lines)) {
            throw exception(WM_EQUIP_RECEIPT_NO_LINE);
        }
        // åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ â†’ å®¡æ‰¹ä¸­ï¼‰
        equipmentReceiptMapper.updateById(new MesWmEquipmentReceiptDO()
                .setId(id)
                .setStatus(MesWmEquipmentReceiptStatusEnum.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId));
        // 3. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(EQUIPMENT_RECEIPT_APPROVE_BIZ_TYPE);
        // 4. çŠ¶æ€ç½®ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶æ¸…ç©ºä¸Šä¸€è½®å®¡æ ¸ç»“æžœ
        equipmentReceiptMapper.submitAndResetAuditInfo(id, MesWmEquipmentReceiptStatusEnum.APPROVING.getStatus());
    }
    @Override
    public List<Map<String, Object>> getEquipmentReceiptApprovalProcessDefinitionList() {
        return getProcessDefinitionListByCategory(APPROVE_CATEGORY_CODE);
    public Set<Long> getEquipmentReceiptApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(EQUIPMENT_RECEIPT_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateEquipmentReceiptAuditStatus(Long id, Integer bpmResult) {
    public void auditEquipmentReceipt(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesWmEquipmentReceiptDO receipt = validateEquipmentReceiptExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!MesWmEquipmentReceiptStatusEnum.APPROVING.getStatus().equals(receipt.getStatus())) {
            log.warn("[updateEquipmentReceiptAuditStatus] è®¾å¤‡å…¥åº“单({}) ä¸å¤„于审批中状态", id);
            throw exception(WM_EQUIP_RECEIPT_NOT_APPROVING);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(EQUIPMENT_RECEIPT_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回草稿可修改后重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(WM_EQUIP_RECEIPT_REJECT_REASON_REQUIRED);
            }
            equipmentReceiptMapper.auditEquipmentReceipt(id, MesWmEquipmentReceiptStatusEnum.PREPARE.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToStatus(bpmResult);
        if (newStatus != null) {
            equipmentReceiptMapper.updateById(new MesWmEquipmentReceiptDO().setId(id).setStatus(newStatus));
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为已审批(可执行入库)
        equipmentReceiptMapper.auditEquipmentReceipt(id, MesWmEquipmentReceiptStatusEnum.APPROVED.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    private Integer convertBpmResultToStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return MesWmEquipmentReceiptStatusEnum.APPROVED.getStatus();
        }
        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return MesWmEquipmentReceiptStatusEnum.PREPARE.getStatus();
        }
        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return MesWmEquipmentReceiptStatusEnum.CANCELED.getStatus();
        }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
        return null;
    }
    private List<Map<String, Object>> getProcessDefinitionListByCategory(String categoryCode) {
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(categoryCode);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    private Long getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof Long) {
            return (Long) authentication.getPrincipal();
        }
        return 1L; // é»˜è®¤ç”¨æˆ·
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/equipmentreceipt/listener/MesWmEquipmentReceiptStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptService.java
@@ -8,6 +8,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -55,7 +56,10 @@
    PageResult<MesWmItemReceiptDO> getItemReceiptPage(MesWmItemReceiptPageReqVO pageReqVO);
    /**
     * æäº¤é‡‡è´­å…¥åº“单(草稿 â†’ å¾…上架)
     * æäº¤é‡‡è´­å…¥åº“单审批(草稿 â†’ å¾…上架)
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  item_receipt_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。
     *
     * @param id ç¼–号
     */
@@ -133,19 +137,24 @@
    Long generateItemReceiptFromArrivalNotice(Long arrivalNoticeId);
    /**
     * æäº¤é‡‡è´­å…¥åº“单审批
     * èŽ·å¾—é‡‡è´­å…¥åº“å•å®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * @param id å…¥åº“单编号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    void submitItemReceiptApproval(Long id, String processDefinitionKey);
    Set<Long> getItemReceiptApproverUserIds();
    /**
     * æ›´æ–°é‡‡è´­å…¥åº“单审批状态(由 BPM Listener å›žè°ƒï¼‰
     * å®¡æ ¸é‡‡è´­å…¥åº“单(待上架 â†’ å¾…执行入库 / è‰ç¨¿ï¼‰
     *
     * ä»…业务编码 item_receipt_approve é…ç½®çš„审核人可操作(多人时或签),且仅在待上架状态下允许。
     * å®¡æ ¸é€šè¿‡ç½®ä¸ºå¾…执行入库;审核不通过必须填写原因,退回草稿可修改后重新提交。
     *
     * @param id å…¥åº“单编号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void updateItemReceiptAuditStatus(Long id, Integer bpmResult);
    void auditItemReceipt(Long id, Boolean pass, String reviewRemark);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
@@ -16,21 +16,20 @@
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemReceiptStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeLineService;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -42,9 +41,11 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -54,6 +55,13 @@
@Validated
@Slf4j
public class MesWmItemReceiptServiceImpl implements MesWmItemReceiptService {
    /**
     * é‡‡è´­å…¥åº“单审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String ITEM_RECEIPT_APPROVE_BIZ_TYPE = "item_receipt_approve";
    @Resource
    private MesWmItemReceiptMapper itemReceiptMapper;
@@ -78,7 +86,10 @@
    private MesWmArrivalNoticeLineService arrivalNoticeLineService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    private AdminUserApi adminUserApi;
    @Resource
    private ApprovalConfigApi approvalConfigApi;
    @Resource
    private PurchaseOrderApi purchaseOrderApi;
@@ -165,9 +176,56 @@
        // æ ¡éªŒç‰©æ–™æ‰¹æ¬¡ç®¡ç†ï¼šå¦‚果物料开启批次管理,则明细的批次号不能为空
        validateBatchManagement(lines);
        // æäº¤ï¼ˆè‰ç¨¿ â†’ å¾…上架)
        itemReceiptMapper.updateById(new MesWmItemReceiptDO()
                .setId(id).setStatus(MesWmItemReceiptStatusEnum.APPROVING.getStatus()));
        // æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(ITEM_RECEIPT_APPROVE_BIZ_TYPE);
        // æäº¤ï¼ˆè‰ç¨¿ â†’ å¾…上架),并清空上一轮审核结果
        itemReceiptMapper.submitAndResetAuditInfo(id, MesWmItemReceiptStatusEnum.APPROVING.getStatus());
    }
    @Override
    public Set<Long> getItemReceiptApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(ITEM_RECEIPT_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditItemReceipt(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesWmItemReceiptDO receipt = validateItemReceiptExists(id);
        // 2. æ ¡éªŒå¤„于待上架状态
        if (!MesWmItemReceiptStatusEnum.APPROVING.getStatus().equals(receipt.getStatus())) {
            throw exception(WM_ITEM_RECEIPT_NOT_APPROVING);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(ITEM_RECEIPT_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回草稿可修改后重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(WM_ITEM_RECEIPT_REJECT_REASON_REQUIRED);
            }
            itemReceiptMapper.auditItemReceipt(id, MesWmItemReceiptStatusEnum.PREPARE.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为待执行入库
        itemReceiptMapper.auditItemReceipt(id, MesWmItemReceiptStatusEnum.APPROVED.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    /**
@@ -443,69 +501,6 @@
        }
        return receipt.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitItemReceiptApproval(Long id, String processDefinitionKey) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateItemReceiptExistsAndDraft(id);
        // 2. åˆ›å»º BPM æµç¨‹å®žä¾‹
        Long userId = getCurrentUserId();
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°å…¥åº“单状态为审批中
        itemReceiptMapper.updateById(new MesWmItemReceiptDO()
                .setId(id)
                .setStatus(MesWmItemReceiptStatusEnum.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateItemReceiptAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesWmItemReceiptDO receipt = validateItemReceiptExists(id);
        if (!MesWmItemReceiptStatusEnum.APPROVING.getStatus().equals(receipt.getStatus())) {
            log.warn("[updateItemReceiptAuditStatus] å…¥åº“单({}) ä¸å¤„于审批中状态", id);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToReceiptStatus(bpmResult);
        if (newStatus != null) {
            itemReceiptMapper.updateById(new MesWmItemReceiptDO()
                    .setId(id).setStatus(newStatus));
        }
    }
    /**
     * è½¬æ¢ BPM å®¡æ‰¹ç»“果为入库单状态
     */
    private Integer convertBpmResultToReceiptStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return MesWmItemReceiptStatusEnum.APPROVED.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return MesWmItemReceiptStatusEnum.PREPARE.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return MesWmItemReceiptStatusEnum.CANCELED.getStatus();
        }
        return null;
    }
    /**
     * èŽ·å–å½“å‰ç”¨æˆ· ID
     */
    private Long getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof Long) {
            return (Long) authentication.getPrincipal();
        }
        return 1L; // é»˜è®¤ç”¨æˆ·
    }
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/listener/MesWmItemReceiptStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueService.java
@@ -6,18 +6,12 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * MES æ‚项出库单 Service æŽ¥å£
 */
public interface MesWmMiscIssueService {
    /**
     * æ‚项出库 BPM å®¡æ‰¹åˆ†ç±»ç¼–码
     */
    String APPROVE_CATEGORY_CODE = "misc_issue_approve";
    /**
     * åˆ›å»ºæ‚项出库单
@@ -58,27 +52,35 @@
    PageResult<MesWmMiscIssueDO> getMiscIssuePage(MesWmMiscIssuePageReqVO pageReqVO);
    /**
     * æäº¤æ‚项出库单审批(草稿 â†’ å®¡æ‰¹ä¸­ï¼‰ï¼Œåˆ›å»º BPM æµç¨‹å®žä¾‹
     * æäº¤æ‚项出库单审批(草稿 â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  misc_issue_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     */
    void submitMiscIssueApproval(Long id, String processDefinitionKey);
    void submitMiscIssueApproval(Long id);
    /**
     * æ›´æ–°æ‚项出库单审批状态(审批中 â†’ å¾…执行出库/草稿/已取消)
     * èŽ·å¾—æ‚é¡¹å‡ºåº“å•å®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getMiscIssueApproverUserIds();
    /**
     * å®¡æ ¸æ‚项出库单(审批中 â†’ å¾…执行出库 / è‰ç¨¿ï¼‰
     *
     * ä»…业务编码 misc_issue_approve é…ç½®çš„审核人可操作(多人时或签),且仅在审批中状态下允许。
     * å®¡æ ¸é€šè¿‡ç½®ä¸ºå¾…执行出库(可执行出库);审核不通过必须填写原因,退回草稿可修改后重新提交。
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void updateMiscIssueAuditStatus(Long id, Integer bpmResult);
    /**
     * èŽ·å–æ‚é¡¹å‡ºåº“å®¡æ‰¹å¯ç”¨çš„ BPM æµç¨‹å®šä¹‰åˆ—表
     *
     * @return æµç¨‹å®šä¹‰åˆ—表(id/key/name)
     */
    List<Map<String, Object>> getMiscIssueApprovalProcessDefinitionList();
    void auditMiscIssue(Long id, Boolean pass, String reviewRemark);
    /**
     * æ‰§è¡Œå‡ºåº“(待执行出库 â†’ å·²å®Œæˆï¼‰ï¼Œæ‰£å‡åº“å­˜
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -15,24 +16,21 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -43,6 +41,13 @@
@Slf4j
public class MesWmMiscIssueServiceImpl implements MesWmMiscIssueService {
    /**
     * æ‚项出库单审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String MISC_ISSUE_APPROVE_BIZ_TYPE = "misc_issue_approve";
    @Resource
    private MesWmMiscIssueMapper miscIssueMapper;
@@ -51,9 +56,9 @@
    @Resource
    private MesWmTransactionService wmTransactionService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Override
    public Long createMiscIssue(MesWmMiscIssueSaveReqVO createReqVO) {
@@ -103,95 +108,63 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitMiscIssueApproval(Long id, String processDefinitionKey) {
        // æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
    public void submitMiscIssueApproval(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateMiscIssueExistsAndDraft(id);
        // æ ¡éªŒè‡³å°‘有一条行
        // 2. æ ¡éªŒè‡³å°‘有一条行
        List<MesWmMiscIssueLineDO> lines = miscIssueLineService.getMiscIssueLineListByIssueId(id);
        if (CollUtil.isEmpty(lines)) {
            throw exception(WM_MISC_ISSUE_NO_LINE);
        }
        // åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ â†’ å®¡æ‰¹ä¸­ï¼‰
        miscIssueMapper.updateById(new MesWmMiscIssueDO()
                .setId(id)
                .setStatus(MesWmMiscIssueStatusEnum.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId));
        // 3. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(MISC_ISSUE_APPROVE_BIZ_TYPE);
        // 4. æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ â†’ å®¡æ‰¹ä¸­ï¼‰ï¼Œå¹¶æ¸…空上一轮审核结果
        miscIssueMapper.submitAndResetAuditInfo(id, MesWmMiscIssueStatusEnum.APPROVING.getStatus());
    }
    @Override
    public List<Map<String, Object>> getMiscIssueApprovalProcessDefinitionList() {
        return getProcessDefinitionListByCategory(APPROVE_CATEGORY_CODE);
    public Set<Long> getMiscIssueApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(MISC_ISSUE_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateMiscIssueAuditStatus(Long id, Integer bpmResult) {
    public void auditMiscIssue(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesWmMiscIssueDO issue = validateMiscIssueExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!MesWmMiscIssueStatusEnum.APPROVING.getStatus().equals(issue.getStatus())) {
            log.warn("[updateMiscIssueAuditStatus] æ‚项出库单({}) ä¸å¤„于审批中状态", id);
            throw exception(WM_MISC_ISSUE_NOT_APPROVING);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(MISC_ISSUE_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回草稿可修改后重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(WM_MISC_ISSUE_REJECT_REASON_REQUIRED);
            }
            miscIssueMapper.auditMiscIssue(id, MesWmMiscIssueStatusEnum.PREPARE.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToStatus(bpmResult);
        if (newStatus != null) {
            miscIssueMapper.updateById(new MesWmMiscIssueDO().setId(id).setStatus(newStatus));
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为待执行出库
        miscIssueMapper.auditMiscIssue(id, MesWmMiscIssueStatusEnum.APPROVED.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    private Integer convertBpmResultToStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return MesWmMiscIssueStatusEnum.APPROVED.getStatus();
        }
        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return MesWmMiscIssueStatusEnum.PREPARE.getStatus();
        }
        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return MesWmMiscIssueStatusEnum.CANCELED.getStatus();
        }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
        return null;
    }
    private List<Map<String, Object>> getProcessDefinitionListByCategory(String categoryCode) {
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(categoryCode);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    private Long getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof Long) {
            return (Long) authentication.getPrincipal();
        }
        return 1L; // é»˜è®¤ç”¨æˆ·
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/listener/MesWmMiscIssueStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptService.java
@@ -6,8 +6,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscreceipt.MesWmMiscReceiptDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * MES æ‚项入库单 Service æŽ¥å£
@@ -15,11 +14,6 @@
 * @author è¶…级管理员
 */
public interface MesWmMiscReceiptService {
    /**
     * æ‚项入库 BPM å®¡æ‰¹åˆ†ç±»ç¼–码
     */
    String APPROVE_CATEGORY_CODE = "misc_receipt_approve";
    /**
     * åˆ›å»ºæ‚项入库单
@@ -60,27 +54,35 @@
    PageResult<MesWmMiscReceiptDO> getMiscReceiptPage(MesWmMiscReceiptPageReqVO pageReqVO);
    /**
     * æäº¤æ‚项入库单审批(草稿 â†’ å®¡æ‰¹ä¸­ï¼‰ï¼Œåˆ›å»º BPM æµç¨‹å®žä¾‹
     * æäº¤æ‚项入库单审批(草稿 â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  misc_receipt_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     */
    void submitMiscReceiptApproval(Long id, String processDefinitionKey);
    void submitMiscReceiptApproval(Long id);
    /**
     * æ›´æ–°æ‚项入库单审批状态(审批中 â†’ å·²å®¡æ‰¹/草稿/已取消)
     * èŽ·å¾—æ‚é¡¹å…¥åº“å•å®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getMiscReceiptApproverUserIds();
    /**
     * å®¡æ ¸æ‚项入库单(审批中 â†’ å·²å®¡æ‰¹ / è‰ç¨¿ï¼‰
     *
     * ä»…业务编码 misc_receipt_approve é…ç½®çš„审核人可操作(多人时或签),且仅在审批中状态下允许。
     * å®¡æ ¸é€šè¿‡ç½®ä¸ºå·²å®¡æ‰¹ï¼ˆå¯æ‰§è¡Œå…¥åº“);审核不通过必须填写原因,退回草稿可修改后重新提交。
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void updateMiscReceiptAuditStatus(Long id, Integer bpmResult);
    /**
     * èŽ·å–æ‚é¡¹å…¥åº“å®¡æ‰¹å¯ç”¨çš„ BPM æµç¨‹å®šä¹‰åˆ—表
     *
     * @return æµç¨‹å®šä¹‰åˆ—表(id/key/name)
     */
    List<Map<String, Object>> getMiscReceiptApprovalProcessDefinitionList();
    void auditMiscReceipt(Long id, Boolean pass, String reviewRemark);
    /**
     * æ‰§è¡Œå…¥åº“(已审批 â†’ å·²å®Œæˆï¼‰ï¼Œæ›´æ–°åº“存台账
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscreceipt.vo.MesWmMiscReceiptPageReqVO;
@@ -14,26 +15,22 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -44,6 +41,13 @@
@Slf4j
public class MesWmMiscReceiptServiceImpl implements MesWmMiscReceiptService {
    /**
     * æ‚项入库单审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String MISC_RECEIPT_APPROVE_BIZ_TYPE = "misc_receipt_approve";
    @Resource
    private MesWmMiscReceiptMapper miscReceiptMapper;
@@ -53,9 +57,9 @@
    @Resource
    private MesWmTransactionService wmTransactionService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Override
    public Long createMiscReceipt(MesWmMiscReceiptSaveReqVO createReqVO) {
@@ -105,95 +109,63 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitMiscReceiptApproval(Long id, String processDefinitionKey) {
        // æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
    public void submitMiscReceiptApproval(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateMiscReceiptExistsAndPrepare(id);
        // æ ¡éªŒè‡³å°‘有一条行
        // 2. æ ¡éªŒè‡³å°‘有一条行
        List<MesWmMiscReceiptLineDO> lines = miscReceiptLineService.getMiscReceiptLineListByReceiptId(id);
        if (CollUtil.isEmpty(lines)) {
            throw exception(WM_MISC_RECEIPT_NO_LINE);
        }
        // åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ â†’ å®¡æ‰¹ä¸­ï¼‰
        miscReceiptMapper.updateById(new MesWmMiscReceiptDO()
                .setId(id)
                .setStatus(MesWmMiscReceiptStatusEnum.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId));
        // 3. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(MISC_RECEIPT_APPROVE_BIZ_TYPE);
        // 4. æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ â†’ å®¡æ‰¹ä¸­ï¼‰ï¼Œå¹¶æ¸…空上一轮审核结果
        miscReceiptMapper.submitAndResetAuditInfo(id, MesWmMiscReceiptStatusEnum.APPROVING.getStatus());
    }
    @Override
    public List<Map<String, Object>> getMiscReceiptApprovalProcessDefinitionList() {
        return getProcessDefinitionListByCategory(APPROVE_CATEGORY_CODE);
    public Set<Long> getMiscReceiptApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(MISC_RECEIPT_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateMiscReceiptAuditStatus(Long id, Integer bpmResult) {
    public void auditMiscReceipt(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesWmMiscReceiptDO receipt = validateMiscReceiptExists(id);
        // 2. æ ¡éªŒå¤„于审批中状态
        if (!MesWmMiscReceiptStatusEnum.APPROVING.getStatus().equals(receipt.getStatus())) {
            log.warn("[updateMiscReceiptAuditStatus] æ‚项入库单({}) ä¸å¤„于审批中状态", id);
            throw exception(WM_MISC_RECEIPT_NOT_APPROVING);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(MISC_RECEIPT_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回草稿可修改后重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(WM_MISC_RECEIPT_REJECT_REASON_REQUIRED);
            }
            miscReceiptMapper.auditMiscReceipt(id, MesWmMiscReceiptStatusEnum.PREPARE.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToStatus(bpmResult);
        if (newStatus != null) {
            miscReceiptMapper.updateById(new MesWmMiscReceiptDO().setId(id).setStatus(newStatus));
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为已审批(可执行入库)
        miscReceiptMapper.auditMiscReceipt(id, MesWmMiscReceiptStatusEnum.APPROVED.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    private Integer convertBpmResultToStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return MesWmMiscReceiptStatusEnum.APPROVED.getStatus();
        }
        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return MesWmMiscReceiptStatusEnum.PREPARE.getStatus();
        }
        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return MesWmMiscReceiptStatusEnum.CANCELED.getStatus();
        }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
        return null;
    }
    private List<Map<String, Object>> getProcessDefinitionListByCategory(String categoryCode) {
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(categoryCode);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    private Long getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof Long) {
            return (Long) authentication.getPrincipal();
        }
        return 1L; // é»˜è®¤ç”¨æˆ·
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/listener/MesWmMiscReceiptStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorService.java
@@ -6,18 +6,12 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * MES ä¾›åº”商退货单 Service æŽ¥å£
 */
public interface MesWmReturnVendorService {
    /**
     * ä¾›åº”商退货 BPM å®¡æ‰¹åˆ†ç±»ç¼–码
     */
    String APPROVE_CATEGORY_CODE = "return_vendor_approve";
    /**
     * åˆ›å»ºä¾›åº”商退货单
@@ -66,27 +60,36 @@
    MesWmReturnVendorDO validateReturnVendorExists(Long id);
    /**
     * æäº¤ä¾›åº”商退货单审批(草稿 -> å®¡æ‰¹ä¸­ï¼‰ï¼Œåˆ›å»º BPM æµç¨‹å®žä¾‹
     * æäº¤ä¾›åº”商退货单审批(草稿 â†’ å¾…拣货)
     *
     * å®¡æ ¸äººç”±ã€Œç³»ç»Ÿç®¡ç† - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®ï¼ˆä¸šåŠ¡ç¼–ç  return_vendor_approve),
     * æäº¤æ—¶æ— éœ€æŒ‡å®šï¼›æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     */
    void submitReturnVendorApproval(Long id, String processDefinitionKey);
    void submitReturnVendorApproval(Long id);
    /**
     * æ›´æ–°ä¾›åº”商退货单审批状态(审批中 -> å¾…执行退货/草稿/已取消)
     * èŽ·å¾—ä¾›åº”å•†é€€è´§å•å®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getReturnVendorApproverUserIds();
    /**
     * å®¡æ ¸ä¾›åº”商退货单(待拣货 â†’ å¾…执行退货 / è‰ç¨¿ï¼‰
     *
     * ä»…业务编码 return_vendor_approve é…ç½®çš„审核人可操作(多人时或签),且仅在待拣货状态下允许。
     * å®¡æ ¸é€šè¿‡ç½®ä¸ºå¾…执行退货(可完成退货并扣减库存);审核不通过必须填写原因,
     * é€€å›žè‰ç¨¿å¯ä¿®æ”¹åŽé‡æ–°æäº¤ã€‚
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void updateReturnVendorAuditStatus(Long id, Integer bpmResult);
    /**
     * èŽ·å–ä¾›åº”å•†é€€è´§å®¡æ‰¹å¯ç”¨çš„ BPM æµç¨‹å®šä¹‰åˆ—表
     *
     * @return æµç¨‹å®šä¹‰åˆ—表(id/key/name)
     */
    List<Map<String, Object>> getReturnVendorApprovalProcessDefinitionList();
    void auditReturnVendor(Long id, Boolean pass, String reviewRemark);
    /**
     * æ‰§è¡Œæ‹£è´§ï¼ˆå¾…拣货 -> å¾…执行退货)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -18,25 +19,22 @@
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
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.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.*;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -46,6 +44,13 @@
@Validated
@Slf4j
public class MesWmReturnVendorServiceImpl implements MesWmReturnVendorService {
    /**
     * ä¾›åº”商退货单审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String RETURN_VENDOR_APPROVE_BIZ_TYPE = "return_vendor_approve";
    @Resource
    private MesWmReturnVendorMapper returnVendorMapper;
@@ -59,9 +64,9 @@
    @Resource
    private MesWmTransactionService wmTransactionService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    private AdminUserApi adminUserApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private ApprovalConfigApi approvalConfigApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -113,95 +118,63 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitReturnVendorApproval(Long id, String processDefinitionKey) {
        // æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
    public void submitReturnVendorApproval(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateReturnVendorExistsAndPrepare(id);
        // æ ¡éªŒè‡³å°‘有一条行
        // 2. æ ¡éªŒè‡³å°‘有一条行
        List<MesWmReturnVendorLineDO> lines = returnVendorLineService.getReturnVendorLineListByReturnId(id);
        if (CollUtil.isEmpty(lines)) {
            throw exception(WM_RETURN_VENDOR_NO_LINE);
        }
        // åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ â†’ å®¡æ‰¹ä¸­ï¼‰
        returnVendorMapper.updateById(new MesWmReturnVendorDO()
                .setId(id)
                .setStatus(MesWmReturnVendorStatusEnum.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId));
        // 3. æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(RETURN_VENDOR_APPROVE_BIZ_TYPE);
        // 4. æäº¤å®¡æ‰¹ï¼ˆè‰ç¨¿ â†’ å¾…拣货),并清空上一轮审核结果
        returnVendorMapper.submitAndResetAuditInfo(id, MesWmReturnVendorStatusEnum.APPROVING.getStatus());
    }
    @Override
    public List<Map<String, Object>> getReturnVendorApprovalProcessDefinitionList() {
        return getProcessDefinitionListByCategory(APPROVE_CATEGORY_CODE);
    public Set<Long> getReturnVendorApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(RETURN_VENDOR_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateReturnVendorAuditStatus(Long id, Integer bpmResult) {
    public void auditReturnVendor(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesWmReturnVendorDO returnVendor = validateReturnVendorExists(id);
        // 2. æ ¡éªŒå¤„于待拣货状态
        if (!MesWmReturnVendorStatusEnum.APPROVING.getStatus().equals(returnVendor.getStatus())) {
            log.warn("[updateReturnVendorAuditStatus] ä¾›åº”商退货单({}) ä¸å¤„于审批中状态", id);
            throw exception(WM_RETURN_VENDOR_NOT_APPROVING);
        }
        // 3. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(RETURN_VENDOR_APPROVE_BIZ_TYPE, userId);
        // 4. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,退回草稿可修改后重新提交
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(WM_RETURN_VENDOR_REJECT_REASON_REQUIRED);
            }
            returnVendorMapper.auditReturnVendor(id, MesWmReturnVendorStatusEnum.PREPARE.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToStatus(bpmResult);
        if (newStatus != null) {
            returnVendorMapper.updateById(new MesWmReturnVendorDO().setId(id).setStatus(newStatus));
        }
        // 5. å®¡æ ¸é€šè¿‡ï¼šè®°å½•审核人与审核意见,状态置为待执行退货(可完成退货并扣减库存)
        returnVendorMapper.auditReturnVendor(id, MesWmReturnVendorStatusEnum.APPROVED.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
    }
    private Integer convertBpmResultToStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return MesWmReturnVendorStatusEnum.APPROVED.getStatus();
        }
        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return MesWmReturnVendorStatusEnum.PREPARE.getStatus();
        }
        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return MesWmReturnVendorStatusEnum.CANCELED.getStatus();
        }
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
        return null;
    }
    private List<Map<String, Object>> getProcessDefinitionListByCategory(String categoryCode) {
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(categoryCode);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    private Long getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof Long) {
            return (Long) authentication.getPrincipal();
        }
        return 1L; // é»˜è®¤ç”¨æˆ·
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/listener/MesWmReturnVendorStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
@@ -6,8 +6,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * MES ç›˜ç‚¹ä»»åŠ¡ Service æŽ¥å£
@@ -15,11 +14,6 @@
 * @author è¶…级管理员
 */
public interface MesWmStockTakingTaskService {
    /**
     * ç›˜ç‚¹ç»“果审批 BPM åˆ†ç±»ç¼–码
     */
    String APPROVE_CATEGORY_CODE = "stock_taking_result_approve";
    /**
     * åˆ›å»ºç›˜ç‚¹ä»»åŠ¡
@@ -54,37 +48,41 @@
    void submitStockTakingTask(Long id);
    /**
     * æäº¤ç›˜ç‚¹ç»“果审批(盘点中 â†’ ç›˜ç‚¹ä¸­ + åˆ›å»º BPM æµç¨‹å®žä¾‹ï¼‰
     * æäº¤ç›˜ç‚¹ç»“果审批(盘点中 â†’ ç›˜ç‚¹ä¸­Â·ç»“果审批中)
     *
     * <p>录入/导入完成后提交结果审批:校验盘点中存在且至少一条盘点结果、且结果审批未提交过,
     * åˆ›å»º BPM æµç¨‹å®žä¾‹å¹¶å›žå†™ {@code processInstanceId}。审批通过后才执行差异核销并置为已完成。
     * <p>录入/导入完成后提交结果审批:校验盘点中存在且至少一条盘点结果、且本轮结果审批未提交过,
     * è®°å½•提交时间后等待审核。审核人由「系统管理 - å®¡æ‰¹é…ç½®ã€æŒ‰ä¸šåŠ¡ç±»åž‹ç»Ÿä¸€é…ç½®
     * ï¼ˆä¸šåŠ¡ç¼–ç  stock_taking_result_approve),提交时无需指定;
     * æœªå¯ç”¨å®¡æ‰¹æˆ–未配置有效审核人时抛出业务异常。审批通过后才执行差异核销并置为已完成。
     *
     * @param id                  ç›˜ç‚¹ä»»åŠ¡ç¼–å·
     * @param processDefinitionKey BPM æµç¨‹å®šä¹‰ Key
     */
    void submitStockTakingResultApproval(Long id, String processDefinitionKey);
    void submitStockTakingResultApproval(Long id);
    /**
     * æ›´æ–°ç›˜ç‚¹ä»»åŠ¡ç»“æžœå®¡æ‰¹çŠ¶æ€ï¼ˆBPM å®¡æ‰¹å›žè°ƒï¼‰
     * èŽ·å¾—ç›˜ç‚¹ç»“æžœå®¡æ‰¹çš„å®¡æ ¸äººç¼–å·é›†åˆ
     *
     * <p>仅盘点中状态处理:
     * ä¾›å‰ç«¯åˆ¤æ–­å½“前登录用户是否应展示「审核」按钮;未配置时返回空集合。
     *
     * @return å®¡æ ¸äººç”¨æˆ·ç¼–号集合
     */
    Set<Long> getStockTakingTaskApproverUserIds();
    /**
     * å®¡æ ¸ç›˜ç‚¹ç»“果(盘点中·结果审批中 â†’ å·²å®Œæˆ / ç›˜ç‚¹ä¸­ï¼‰
     *
     * <p>仅业务编码 stock_taking_result_approve é…ç½®çš„审核人可操作(多人时或签),
     * ä¸”仅在结果审批已提交、尚未审核时允许:
     * <ul>
     *     <li>审批通过:解冻库存,同事务自动核销盘盈/盘亏并置为已完成;</li>
     *     <li>审批驳回:清空流程实例编号,任务保持盘点中,可修改实盘后重新提交;</li>
     *     <li>审批取消:解冻库存并置为已取消。</li>
     *     <li>审核通过:解冻库存,同事务自动核销盘盈/盘亏并置为已完成;</li>
     *     <li>审核不通过:必须填写原因,任务保持盘点中,可修改实盘后重新提交结果审批。</li>
     * </ul>
     *
     * @param id        ç›˜ç‚¹ä»»åŠ¡ç¼–å·
     * @param bpmResult BPM å®¡æ‰¹ç»“果(通过/驳回/取消)
     * @param pass         æ˜¯å¦é€šè¿‡ï¼Œtrue=通过 / false=不通过
     * @param reviewRemark å®¡æ ¸æ„è§ï¼Œå®¡æ ¸ä¸é€šè¿‡æ—¶å¿…å¡«
     */
    void updateStockTakingTaskResultAuditStatus(Long id, Integer bpmResult);
    /**
     * èŽ·å–ç›˜ç‚¹ç»“æžœå®¡æ‰¹å¯ç”¨çš„ BPM æµç¨‹å®šä¹‰åˆ—表
     *
     * @return æµç¨‹å®šä¹‰åˆ—表(id/key/name)
     */
    List<Map<String, Object>> getStockTakingTaskResultApprovalProcessDefinitionList();
    void auditStockTakingResult(Long id, Boolean pass, String reviewRemark);
    /**
     * å–消盘点任务
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskPageReqVO;
@@ -20,15 +21,11 @@
import cn.iocoder.yudao.module.mes.service.wm.stocktaking.plan.MesWmStockTakingPlanService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import 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.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -38,11 +35,14 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -55,13 +55,15 @@
@Slf4j
public class MesWmStockTakingTaskServiceImpl implements MesWmStockTakingTaskService {
    @Resource
    private MesWmStockTakingTaskMapper stockTakingTaskMapper;
    /**
     * ç›˜ç‚¹ç»“果审批的业务类型编码
     *
     * ä¸Ž system_approval_config.biz_type å¯¹åº”,审核人在「系统管理 - å®¡æ‰¹é…ç½®ã€ä¸­ç»´æŠ¤ã€‚
     */
    private static final String STOCK_TAKING_RESULT_APPROVE_BIZ_TYPE = "stock_taking_result_approve";
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private MesWmStockTakingTaskMapper stockTakingTaskMapper;
    @Resource
    private MesWmMaterialStockService materialStockService;
@@ -82,6 +84,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private ApprovalConfigApi approvalConfigApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -186,9 +191,8 @@
            throw exception(WM_STOCK_TAKING_TASK_NO_LINE);
        }
        // 2. æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­
        stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
                .setStatus(MesWmStockTakingTaskStatusEnum.APPROVING.getStatus()));
        // 2. æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸ºç›˜ç‚¹ä¸­ï¼Œå¹¶æ¸…ç©ºå®¡æ ¸æ ‡è®°
        stockTakingTaskMapper.submitAndResetAuditInfo(id, MesWmStockTakingTaskStatusEnum.APPROVING.getStatus());
        // 3. æ ¹æ®å†»ç»“标识,对物资进行冻结
        if (Boolean.TRUE.equals(task.getFrozen())) {
@@ -198,11 +202,11 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitStockTakingResultApproval(Long id, String processDefinitionKey) {
    public void submitStockTakingResultApproval(Long id) {
        // 1.1 æ ¡éªŒä»»åŠ¡å­˜åœ¨ä¸”å¤„äºŽç›˜ç‚¹ä¸­çŠ¶æ€
        MesWmStockTakingTaskDO task = validateStockTakingTaskExistsAndApproving(id);
        // 1.2 æ ¡éªŒç»“果审批未提交过(避免重复提交)
        if (task.getProcessInstanceId() != null) {
        // 1.2 æ ¡éªŒæœ¬è½®ç»“果审批未提交过(避免重复提交)
        if (isResultApprovalPending(task)) {
            throw exception(WM_STOCK_TAKING_TASK_ALREADY_SUBMIT_APPROVAL);
        }
        // 1.3 æ ¡éªŒè‡³å°‘有一条盘点结果
@@ -210,94 +214,80 @@
        if (CollUtil.isEmpty(results)) {
            throw exception(WM_STOCK_TAKING_TASK_NO_RESULT);
        }
        // 1.4 æ ¡éªŒå®¡æ‰¹é…ç½®å·²å¯ç”¨ä¸”配置了有效审核人(未配置时抛出带明确提示的业务异常)
        approvalConfigApi.validateApprovalEnabledAndGetApprovers(STOCK_TAKING_RESULT_APPROVE_BIZ_TYPE);
        // 2. åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(getCurrentUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 3. å›žå†™æµç¨‹å®žä¾‹ç¼–号(任务保持盘点中,等待审批)
        stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO()
                .setId(id).setProcessInstanceId(processInstanceId));
        // 2. è®°å½•提交时间并清空上一轮审核结果(任务保持盘点中,等待审核)
        stockTakingTaskMapper.markResultApprovalSubmitted(id);
    }
    @Override
    public List<Map<String, Object>> getStockTakingTaskResultApprovalProcessDefinitionList() {
        return getProcessDefinitionListByCategory(APPROVE_CATEGORY_CODE);
    public Set<Long> getStockTakingTaskApproverUserIds() {
        return approvalConfigApi.getApproverUserIds(STOCK_TAKING_RESULT_APPROVE_BIZ_TYPE);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStockTakingTaskResultAuditStatus(Long id, Integer bpmResult) {
    public void auditStockTakingResult(Long id, Boolean pass, String reviewRemark) {
        // 1. æ ¡éªŒä»»åŠ¡å­˜åœ¨
        MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id);
        // 2. æ ¡éªŒå¤„于盘点中(含结果审批中)状态
        if (!MesWmStockTakingTaskStatusEnum.APPROVING.getStatus().equals(task.getStatus())) {
            log.warn("[updateStockTakingTaskResultAuditStatus] ç›˜ç‚¹ä»»åŠ¡({}) ä¸å¤„于盘点中状态,忽略审批事件", id);
            throw exception(WM_STOCK_TAKING_TASK_NOT_APPROVING);
        }
        // 3. æ ¡éªŒæœ¬è½®ç»“果审批已提交且尚未审核
        if (!isResultApprovalPending(task)) {
            throw exception(WM_STOCK_TAKING_TASK_RESULT_NOT_SUBMITTED);
        }
        // 4. æ ¡éªŒå½“前登录用户是该业务类型的审核人(或签:任一人均可审核)
        Long userId = getLoginUserId();
        approvalConfigApi.validateApprover(STOCK_TAKING_RESULT_APPROVE_BIZ_TYPE, userId);
        // 5. å®¡æ ¸ä¸é€šè¿‡ï¼šå¿…须填写原因,任务保持盘点中,可修改实盘后重新提交结果审批
        if (!Boolean.TRUE.equals(pass)) {
            if (StrUtil.isBlank(reviewRemark)) {
                throw exception(WM_STOCK_TAKING_TASK_REJECT_REASON_REQUIRED);
            }
            stockTakingTaskMapper.auditStockTakingTask(id, MesWmStockTakingTaskStatusEnum.APPROVING.getStatus(),
                    userId, getUserNickname(userId), reviewRemark);
            return;
        }
        // 2. å®¡æ‰¹é€šè¿‡ï¼šå…ˆç½®ä¸ºå·²å®Œæˆï¼Œå†è§£å†»å¹¶åŒäº‹åŠ¡è‡ªåŠ¨æ ¸é”€å·®å¼‚
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            // 2.1 å…ˆç½®ä¸ºå·²å®Œæˆï¼šä»»åŠ¡çŠ¶æ€å˜æ›´åŽï¼ŒåŽç»­é‡å¤çš„å®¡æ‰¹äº‹ä»¶è¯»å–åˆ° FINISHED å³è¢«å¿½ç•¥ï¼Œé¿å…é‡å¤æ ¸é”€
            stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
                    .setStatus(MesWmStockTakingTaskStatusEnum.FINISHED.getStatus()));
            // 2.2 è§£å†»åº“存:须在差异核销前解冻,否则核销事务会被库存冻结校验拦截
        // 6. å®¡æ ¸é€šè¿‡ï¼šå…ˆç½®ä¸ºå·²å®Œæˆå¹¶è®°å½•审核人与审核意见
        stockTakingTaskMapper.auditStockTakingTask(id, MesWmStockTakingTaskStatusEnum.FINISHED.getStatus(),
                userId, getUserNickname(userId), reviewRemark);
        // 7. è§£å†»åº“存:须在差异核销前解冻,否则核销事务会被库存冻结校验拦截
            if (Boolean.TRUE.equals(task.getFrozen())) {
                updateMaterialStockFrozen(stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id), false);
            }
            // 2.3 è‡ªåŠ¨æ ¸é”€ç›˜ç‚¹å·®å¼‚ï¼šç›˜ç›ˆå…¥åº“ / ç›˜äºå‡ºåº“,同事务内更新库存台账;任一步失败整体回滚,任务回退为盘点中
        // 8. è‡ªåŠ¨æ ¸é”€ç›˜ç‚¹å·®å¼‚ï¼šç›˜ç›ˆå…¥åº“ / ç›˜äºå‡ºåº“,同事务内更新库存台账;任一步失败整体回滚,任务回退为盘点中
            doProcessStockTakingDifference(task);
            return;
        }
        // 3. å®¡æ‰¹é©³å›žï¼šæ¸…空流程实例编号,任务保持盘点中,可修改实盘后重新提交结果审批
        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            stockTakingTaskMapper.clearProcessInstanceId(id);
            return;
        }
        // 4. å®¡æ‰¹å–消:解冻库存并置为已取消
        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            if (Boolean.TRUE.equals(task.getFrozen())) {
                updateMaterialStockFrozen(stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id), false);
            }
            stockTakingTaskMapper.clearProcessInstanceId(id);
            stockTakingTaskMapper.updateById(new MesWmStockTakingTaskDO().setId(id)
                    .setStatus(MesWmStockTakingTaskStatusEnum.CANCELED.getStatus()));
        }
    }
    /**
     * èŽ·å–åˆ†ç±»ä¸‹çš„å¯ç”¨ BPM æµç¨‹å®šä¹‰ï¼ˆè¿‡æ»¤æŒ‚起流程,同 Key ä¿ç•™æœ€æ–°ç‰ˆæœ¬ï¼‰
     * åˆ¤æ–­æœ¬è½®ç›˜ç‚¹ç»“果审批是否已提交且尚未审核
     *
     * @param categoryCode æµç¨‹åˆ†ç±»ç¼–码
     * @return æµç¨‹å®šä¹‰åˆ—表(id/key/name)
     * ä»»åŠ¡çŠ¶æ€ 2(盘点中)同时承载「盘点中」与「盘点结果审批中」两个阶段,
     * ä»¥ reviewTime éžç©ºä¸” reviewerId ä¸ºç©ºä½œä¸ºã€Œå·²æäº¤ã€ç­‰å¾…审核」的判定条件;
     * å®¡æ ¸ä¸é€šè¿‡åŽ reviewerId éžç©ºï¼Œä»»åŠ¡å›žåˆ°å¯é‡æ–°æäº¤ç»“æžœå®¡æ‰¹çš„çŠ¶æ€ã€‚
     *
     * @param task ç›˜ç‚¹ä»»åŠ¡
     * @return æ˜¯å¦å¤„于结果审批中
     */
    private List<Map<String, Object>> getProcessDefinitionListByCategory(String categoryCode) {
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(categoryCode);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
    private boolean isResultApprovalPending(MesWmStockTakingTaskDO task) {
        return task.getReviewerId() == null && task.getReviewTime() != null;
        }
        // è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
    /**
     * èŽ·å–ç”¨æˆ·æ˜µç§°
     *
     * @param userId ç”¨æˆ·ç¼–号
     * @return æ˜µç§°ï¼Œç”¨æˆ·ä¸å­˜åœ¨æ—¶è¿”回 null
     */
    private String getUserNickname(Long userId) {
        if (userId == null) {
            return null;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        return user == null ? null : user.getNickname();
    }
    private Long getCurrentUserId() {
@@ -318,8 +308,8 @@
        }
        task.setStatus(MesWmStockTakingTaskStatusEnum.CANCELED.getStatus());
        stockTakingTaskMapper.updateById(task);
        // å–消后清空流程实例编号,避免残留审批标记影响后续重新开始盘点
        stockTakingTaskMapper.clearProcessInstanceId(id);
        // å–消后清空审核标记,避免残留审批标记影响后续重新开始盘点
        stockTakingTaskMapper.clearAuditInfo(id);
        if (Boolean.TRUE.equals(task.getFrozen())) {
            updateMaterialStockFrozen(stockTakingTaskLineService.getStockTakingTaskLineListByTaskId(id), false);
        }
@@ -384,7 +374,7 @@
    /**
     * è‡ªåŠ¨æ ¸é”€ç›˜ç‚¹å·®å¼‚ï¼šæŒ‰ç›˜ç‚¹ç»“æžœé€è¡Œç”Ÿæˆ IN/OUT åº“存事务更新库存台账
     *
     * <p>仅由 {@link #updateStockTakingTaskResultAuditStatus} çš„审批通过分支在同事务内调用;
     * <p>仅由 {@link #auditStockTakingResult} çš„审核通过分支在同事务内调用;
     * å¤±è´¥æ•´ä½“回滚,任务回退为盘点中状态,避免重复调整。
     *
     * @param task å·²ç½®ä¸ºå·²å®Œæˆã€å·²è§£å†»çš„盘点任务
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/listener/MesWmStockTakingTaskResultStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
@@ -11,9 +11,12 @@
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.*;
import cn.iocoder.yudao.module.system.convert.user.UserConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -33,6 +36,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -49,6 +54,10 @@
    private AdminUserService userService;
    @Resource
    private DeptService deptService;
    @Resource
    private RoleService roleService;
    @Resource
    private PermissionService permissionService;
    @PostMapping("/create")
    @Operation(summary = "新增用户")
@@ -148,6 +157,32 @@
        return success(UserConvert.INSTANCE.convertSimpleList(list, deptMap));
    }
    @GetMapping("/list-by-role-code")
    @Operation(summary = "获得指定角色编码的用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项")
    @Parameter(name = "code", description = "角色编码", required = true, example = "production_submit_work")
    public CommonResult<List<UserSimpleRespVO>> getUserListByRoleCode(@RequestParam("code") String code) {
        // 1. æŸ¥è¯¢è§’色
        RoleDO role = roleService.getRoleByCode(code);
        if (role == null) {
            return success(Collections.emptyList());
        }
        // 2. æŸ¥è¯¢æ‹¥æœ‰è¯¥è§’色的用户编号
        Set<Long> userIds = permissionService.getUserRoleIdListByRoleId(Collections.singletonList(role.getId()));
        if (CollUtil.isEmpty(userIds)) {
            return success(Collections.emptyList());
        }
        // 3. æŸ¥è¯¢ç”¨æˆ·ï¼Œåªä¿ç•™å¼€å¯çŠ¶æ€
        List<AdminUserDO> list = userService.getUserList(userIds).stream()
                .filter(user -> CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()))
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(list)) {
            return success(Collections.emptyList());
        }
        // 4. æ‹¼æŽ¥éƒ¨é—¨æ•°æ®
        Map<Long, DeptDO> deptMap = deptService.getDeptMap(convertList(list, AdminUserDO::getDeptId));
        return success(UserConvert.INSTANCE.convertSimpleList(list, deptMap));
    }
    @GetMapping("/get")
    @Operation(summary = "获得用户详情")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java
@@ -73,6 +73,14 @@
    RoleDO getRoleFromCache(Long id);
    /**
     * æ ¹æ®è§’色编码获得角色
     *
     * @param code è§’色编码
     * @return è§’色
     */
    RoleDO getRoleByCode(String code);
    /**
     * èŽ·å¾—è§’è‰²åˆ—è¡¨
     *
     * @param ids è§’色编号数组
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
@@ -196,6 +196,11 @@
        return roleMapper.selectById(id);
    }
    @Override
    public RoleDO getRoleByCode(String code) {
        return roleMapper.selectByCode(code);
    }
    @Override
    public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) {