gaoluyang
7 天以前 e1590017b716d77f88f075364c55cfbdb480286f
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -76,6 +76,9 @@
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
@@ -135,23 +138,18 @@
        scheduleGuard.lock();
        // 1. 校验关联数据存在
        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());
        workstationService.validateWorkstationExistsAndEnable(createReqVO.getWorkstationId());
        // 2. 自动确定 itemId:优先前端传入 → 工艺路线工序的产出产品 → 工单成品
        // 未配置工序/工艺路线时,任务直接按工单成品产出
        Long itemId = createReqVO.getItemId();
        if (itemId == null) {
        if (itemId == null && createReqVO.getRouteId() != null && createReqVO.getProcessId() != null) {
            MesProRouteProcessDO routeProcess = routeProcessService
                    .getRouteProcessByRouteIdAndProcessId(createReqVO.getRouteId(), createReqVO.getProcessId());
            if (routeProcess != null && routeProcess.getOutputItemId() != null) {
                itemId = Long.valueOf(routeProcess.getOutputItemId().split(",")[0].trim());
            } else {
                itemId = workOrder.getProductId();
            }
            itemId = resolveTaskItemId(routeProcess, workOrder.getProductId());
        }
        if (itemId == null) {
            itemId = workOrder.getProductId();
        }
        MesMdItemDO item = itemService.validateItemExists(itemId);
@@ -205,12 +203,6 @@
        if (updateReqVO.getWorkstationId() != null) {
            workstationService.validateWorkstationExists(updateReqVO.getWorkstationId());
        }
        if (updateReqVO.getRouteId() != null) {
            routeService.validateRouteExists(updateReqVO.getRouteId());
        }
        if (updateReqVO.getProcessId() != null) {
            processService.validateProcessExistsAndEnable(updateReqVO.getProcessId());
        }
        MesMdItemDO item;
        if (updateReqVO.getItemId() != null) {
            item = itemService.validateItemExists(updateReqVO.getItemId());
@@ -226,10 +218,6 @@
                .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. 更新
@@ -250,13 +238,24 @@
    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()))
        BigDecimal orderQuantity = order.getQuantity() == null ? BigDecimal.ZERO : order.getQuantity();
        BigDecimal scheduled;
        if (CollUtil.isEmpty(required)) {
            // 未配置工序/工艺路线:整张工单只有一个生产步骤,已排产量按任务数量合计
            scheduled = tasks.stream()
                    .filter(task -> !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);
                    .reduce(BigDecimal.ZERO, BigDecimal::add)
                    .min(orderQuantity);
        } else {
            scheduled = orderQuantity;
            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));
    }
@@ -341,12 +340,34 @@
            }
            return result;
        }
        // 2. 回退到工艺路线模板工序
        // 2. 回退到工艺路线模板工序;未配置工艺路线时视为单工序生产
        if (routeId == null) {
            return new java.util.ArrayList<>();
        }
        List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByRouteId(routeId);
        if (CollUtil.isEmpty(routeProcesses)) {
            throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_ROUTE_ENABLE_NO_PROCESS);
        }
        return routeProcesses;
    }
    /** 当前产品的任务产出,优先使用工序的产品归属配置。 */
    private Long resolveTaskItemId(MesProRouteProcessDO routeProcess, Long productId) {
        if (routeProcess == null) {
            return productId;
        }
        if (routeProcess.getId() != null) {
            List<Long> outputIds = routeProcessService
                    .getOutputItemIdsByRouteProcessId(routeProcess.getId(), productId);
            if (outputIds != null) {
                return CollUtil.isNotEmpty(outputIds) ? outputIds.get(0) : productId;
            }
        }
        String outputItemId = routeProcess.getOutputItemId();
        if (outputItemId != null && !outputItemId.isBlank()) {
            return Long.valueOf(outputItemId.split(",")[0].trim());
        }
        return productId;
    }
    @Override
@@ -424,7 +445,9 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void finishTaskByOrderId(Long workOrderId) {
        equipmentService.lock();
        // 1. 读取工单下所有任务,过滤掉已完成/已取消的
        List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId);
        LocalDateTime now = LocalDateTime.now();
@@ -436,10 +459,13 @@
        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();
@@ -451,6 +477,7 @@
        if (CollUtil.isNotEmpty(updateList)) {
            taskMapper.updateBatch(updateList);
        }
        equipmentService.afterTaskStateChanged(tasks);
    }
    @Override
@@ -554,7 +581,7 @@
        List<Long> taskIds = new java.util.ArrayList<>();
        for (MesProRouteProcessDO rp : routeProcesses) {
            cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO proc = procMap.get(rp.getProcessId());
            Long itemId = rp.getOutputItemId() != null ? Long.valueOf(rp.getOutputItemId().split(",")[0].trim()) : workOrder.getProductId();
            Long itemId = resolveTaskItemId(rp, workOrder.getProductId());
            cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO item = itemService.validateItemExists(itemId);
            MesProTaskDO task = new MesProTaskDO()
                    .setWorkOrderId(workOrder.getId())
@@ -582,10 +609,12 @@
    @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);
        }
        
@@ -602,11 +631,13 @@
                .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. 校验状态必须是已停工
@@ -615,8 +646,11 @@
        }
        // 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());