| | |
| | | public class MesProTaskServiceImpl implements MesProTaskService { |
| | | |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackEquipmentService equipmentService; |
| | | |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.pro.task.schedule.MesProScheduleGuardService scheduleGuard; |
| | | |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper scheduleWorkOrderMapper; |
| | | |
| | | @Resource |
| | | private MesProTaskMapper taskMapper; |
| | | |
| | | @Resource |
| | |
| | | private MesDvMachineryService machineryService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class, isolation = org.springframework.transaction.annotation.Isolation.SERIALIZABLE) |
| | | public Long createTask(MesProTaskSaveReqVO createReqVO) { |
| | | scheduleGuard.lock(); |
| | | // 1. 校验关联数据存在 |
| | | MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId()); |
| | | workstationService.validateWorkstationExists(createReqVO.getWorkstationId()); |
| | | MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(createReqVO.getWorkOrderId()); |
| | | var station = workstationService.validateWorkstationExistsAndEnable(createReqVO.getWorkstationId()); |
| | | if (!java.util.Objects.equals(station.getProcessId(), createReqVO.getProcessId())) { |
| | | throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_SCHEDULE_INVALID, "工作站与工序不匹配"); |
| | | } |
| | | routeService.validateRouteExists(createReqVO.getRouteId()); |
| | | processService.validateProcessExistsAndEnable(createReqVO.getProcessId()); |
| | | |
| | |
| | | if (task.getClientId() == null && workOrder.getClientId() != null) { |
| | | task.setClientId(workOrder.getClientId()); |
| | | } |
| | | scheduleGuard.validate(workOrder, task, null); |
| | | // 2.2 插入 |
| | | taskMapper.insert(task); |
| | | // 2.2.1 参数快照:优先使用模板;无模板时回退到工序主数据绑定的工艺参数 |
| | |
| | | |
| | | // 2.3 保存指定报工人员 |
| | | updateTaskAssignees(task.getId(), createReqVO.getAssigneeUserIds()); |
| | | synchronizeScheduledQuantity(workOrder, task.getRouteId()); |
| | | |
| | | return task.getId(); |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class, isolation = org.springframework.transaction.annotation.Isolation.SERIALIZABLE) |
| | | public void updateTask(MesProTaskSaveReqVO updateReqVO) { |
| | | scheduleGuard.lock(); |
| | | // 1.1 校验存在 |
| | | MesProTaskDO task = validateTaskExists(updateReqVO.getId()); |
| | | MesProTaskDO task = validateTaskNotFinished(updateReqVO.getId()); |
| | | // 1.2 校验关联数据存在 |
| | | if (updateReqVO.getWorkOrderId() != null) { |
| | | workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId()); |
| | |
| | | } |
| | | BigDecimal quantity = updateReqVO.getQuantity() != null ? updateReqVO.getQuantity() : task.getQuantity(); |
| | | |
| | | MesProTaskDO candidate = BeanUtils.toBean(task, MesProTaskDO.class) |
| | | .setWorkOrderId(updateReqVO.getWorkOrderId() != null ? updateReqVO.getWorkOrderId() : task.getWorkOrderId()) |
| | | .setProcessId(updateReqVO.getProcessId() != null ? updateReqVO.getProcessId() : task.getProcessId()) |
| | | .setWorkstationId(updateReqVO.getWorkstationId() != null ? updateReqVO.getWorkstationId() : task.getWorkstationId()) |
| | | .setQuantity(quantity) |
| | | .setStartTime(updateReqVO.getStartTime() != null ? updateReqVO.getStartTime() : task.getStartTime()) |
| | | .setEndTime(updateReqVO.getEndTime() != null ? updateReqVO.getEndTime() : task.getEndTime()); |
| | | var station = workstationService.validateWorkstationExistsAndEnable(candidate.getWorkstationId()); |
| | | if (!java.util.Objects.equals(station.getProcessId(), candidate.getProcessId())) { |
| | | throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_SCHEDULE_INVALID, "工作站与工序不匹配"); |
| | | } |
| | | scheduleGuard.validate(workOrderService.validateWorkOrderConfirmed(candidate.getWorkOrderId()), candidate, task.getId()); |
| | | |
| | | // 2. 更新 |
| | | MesProTaskDO updateObj = BeanUtils.toBean(updateReqVO, MesProTaskDO.class) |
| | | .setName(buildTaskName(item, quantity)); |
| | |
| | | |
| | | // 3. 更新指定报工人员(比对更新) |
| | | updateTaskAssignees(task.getId(), updateReqVO.getAssigneeUserIds()); |
| | | synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()), |
| | | updateReqVO.getRouteId() != null ? updateReqVO.getRouteId() : task.getRouteId()); |
| | | if (!java.util.Objects.equals(task.getWorkOrderId(), candidate.getWorkOrderId())) { |
| | | synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId()); |
| | | } |
| | | } |
| | | |
| | | /** 工单排产量按各工序累计排产量的最小值计算,任务修改/删除后同步,避免无法补排。 */ |
| | | private void synchronizeScheduledQuantity(MesProWorkOrderDO order, Long routeId) { |
| | | List<MesProRouteProcessDO> required = buildRouteProcessList(routeId, order.getId()); |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(order.getId()); |
| | | BigDecimal scheduled = order.getQuantity(); |
| | | for (var process : required) { |
| | | BigDecimal amount = tasks.stream().filter(task -> java.util.Objects.equals(task.getProcessId(), process.getProcessId()) |
| | | && !java.util.Objects.equals(task.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus())) |
| | | .map(task -> task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity()) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | scheduled = scheduled.min(amount); |
| | | } |
| | | scheduleWorkOrderMapper.updateById(new MesProWorkOrderDO().setId(order.getId()).setQuantityScheduled(scheduled)); |
| | | } |
| | | |
| | | private void updateTaskAssignees(Long taskId, List<Long> assigneeUserIds) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteTask(Long id) { |
| | | scheduleGuard.lock(); |
| | | // 1. 校验存在且非终态 |
| | | validateTaskNotFinished(id); |
| | | MesProTaskDO task = validateTaskNotFinished(id); |
| | | // 2. 校验任务下无报工记录,避免删除后报工追溯断链 |
| | | if (feedbackMapper.selectCountByTaskId(id) > 0) { |
| | | throw exception(PRO_TASK_HAS_FEEDBACK); |
| | |
| | | taskMapper.deleteById(id); |
| | | // 4. 删除关联的人员 |
| | | taskAssigneeMapper.deleteByTaskId(id); |
| | | synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void finishTaskByOrderId(Long workOrderId) { |
| | | equipmentService.lock(); |
| | | // 1. 读取工单下所有任务,过滤掉已完成/已取消的 |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | |
| | | if (CollUtil.isNotEmpty(updateList)) { |
| | | taskMapper.updateBatch(updateList); |
| | | } |
| | | equipmentService.afterTaskStateChanged(tasks); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void cancelTaskByOrderId(Long workOrderId) { |
| | | equipmentService.lock(); |
| | | // 1. 读取工单下所有任务,过滤掉已完成/已取消的 |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | |
| | | if (CollUtil.isNotEmpty(updateList)) { |
| | | taskMapper.updateBatch(updateList); |
| | | } |
| | | equipmentService.afterTaskStateChanged(tasks); |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public List<Long> autoGenerateTasks(MesProTaskAutoGenerateReqVO reqVO) { |
| | | scheduleGuard.lock(); |
| | | // 1. 校验工单已确认 |
| | | MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId()); |
| | | // 2. 确定工艺路线:优先请求指定 → 工单关联 → 产品关联 |
| | |
| | | .setParamTemplateId(rp.getParamTemplateId()) |
| | | .setClientId(workOrder.getClientId()) |
| | | .setColorCode(rp.getColorCode()); |
| | | scheduleGuard.validate(workOrder, task, null); |
| | | taskMapper.insert(task); |
| | | // 参数快照:优先使用模板;无模板时回退到工序主数据绑定的工艺参数 |
| | | createTaskParamRecordWithFallback(task); |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void pauseTask(MesProTaskPauseReqVO pauseReqVO) { |
| | | equipmentService.lock(); |
| | | // 1. 校验任务存在 |
| | | MesProTaskDO task = validateTaskExists(pauseReqVO.getId()); |
| | | // 2. 校验状态是否允许停工(如必须是 生产中 或 部分完成) |
| | | if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PREPARE.getStatus())) { |
| | | if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PREPARE.getStatus(), |
| | | MesProTaskStatusEnum.PRODUCING.getStatus(), MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus())) { |
| | | throw exception(PRO_TASK_CANNOT_PAUSE); |
| | | } |
| | | |
| | |
| | | .setPauseTime(LocalDateTime.now()) |
| | | .setPauseUserId(SecurityFrameworkUtils.getLoginUserId()); |
| | | taskPauseRecordMapper.insert(pauseRecord); |
| | | equipmentService.afterTaskStateChanged(List.of(task)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void resumeTask(MesProTaskResumeReqVO resumeReqVO) { |
| | | equipmentService.lock(); |
| | | // 1. 校验任务存在 |
| | | MesProTaskDO task = validateTaskExists(resumeReqVO.getId()); |
| | | // 2. 校验状态必须是已停工 |
| | |
| | | } |
| | | |
| | | // 3. 恢复任务状态 |
| | | Integer resumeStatus = MesProTaskStatusEnum.PREPARE.getStatus(); |
| | | Integer resumeStatus = task.getProducedQuantity() != null && task.getProducedQuantity().signum() > 0 |
| | | ? MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus() : MesProTaskStatusEnum.PREPARE.getStatus(); |
| | | taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(resumeStatus)); |
| | | equipmentService.validateResume(task); |
| | | equipmentService.afterTaskStateChanged(List.of(task)); |
| | | |
| | | // 4. 更新最新的一条停工记录 |
| | | MesProTaskPauseRecordDO pauseRecord = taskPauseRecordMapper.selectLastUnresumedByTaskId(task.getId()); |