云
昨天 ac2e2b5af3726ee64dc37c3ebf26989a0346f22a
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -15,6 +15,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationMachineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationWorkerDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
@@ -29,6 +30,7 @@
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationMachineService;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationWorkerService;
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO;
@@ -55,6 +57,7 @@
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_SCHEDULE_INVALID;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_ALREADY_FINISHED;
@@ -68,6 +71,8 @@
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskPauseRecordMapper;
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPauseReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskResumeReqVO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
/**
 * MES 生产任务 Service 实现类
@@ -77,6 +82,10 @@
@Service
@Validated
public class MesProTaskServiceImpl implements MesProTaskService {
    @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;
@@ -120,13 +129,21 @@
    private MesDvMachineryService machineryService;
    @Resource
    private MesCalTeamService teamService;
    @Resource
    private MesMdWorkstationWorkerService workstationWorkerService;
    @Resource
    private AdminUserApi adminUserApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createTask(MesProTaskSaveReqVO createReqVO) {
        scheduleGuard.lock();
        // 1. 校验关联数据存在
        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
        workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
        var station = workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
        if (java.util.Objects.equals(workOrder.getType(), cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum.SELF.getType()) && !java.util.Objects.equals(station.getProcessId(), createReqVO.getProcessId())) {
            throw exception(PRO_TASK_SCHEDULE_INVALID, "工作站与工序不匹配");
        }
        routeService.validateRouteExists(createReqVO.getRouteId());
        processService.validateProcessExistsAndEnable(createReqVO.getProcessId());
        fillTeamInfo(createReqVO);
@@ -141,23 +158,32 @@
        MesMdItemDO item = itemService.validateItemExists(itemId);
        // 3.1 构建任务 DO:自动填充客户信息
        List<Long> assigneeUserIds = createReqVO.getAssigneeUserIds();
        if (CollUtil.isEmpty(assigneeUserIds)) {
            // 未指定报工人时默认取工作站配置的人员,避免一键排产生成的任务无人可报
            assigneeUserIds = getWorkstationWorkerUserIds(createReqVO.getWorkstationId());
        }
        MesProTaskDO task = BeanUtils.toBean(createReqVO, MesProTaskDO.class)
                .setItemId(itemId) // 确保使用计算后的 itemId
                .setName(buildTaskName(item, createReqVO.getQuantity()))
                .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_TASK_CODE.getCode()))
                .setAssigneeUserIds(joinUserIds(assigneeUserIds))
                .setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
        if (task.getClientId() == null && workOrder.getClientId() != null) {
            task.setClientId(workOrder.getClientId());
        }
        // 2.2 插入
        scheduleGuard.validate(workOrder, task, null);
        freezeTaskParamConfig(task);
        taskMapper.insert(task);
        synchronizeScheduledQuantity(workOrder, task.getRouteId());
        return task.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateTask(MesProTaskSaveReqVO updateReqVO) {
        scheduleGuard.lock();
        MesProTaskDO task = taskMapper.selectOne(new LambdaQueryWrapperX<MesProTaskDO>()
                .eq(MesProTaskDO::getId, updateReqVO.getId()).last("FOR UPDATE"));
        if (task == null) throw exception(PRO_TASK_NOT_EXISTS);
@@ -183,14 +209,47 @@
        }
        BigDecimal quantity = updateReqVO.getQuantity() != null ? updateReqVO.getQuantity() : task.getQuantity();
        MesProTaskDO candidate = BeanUtils.toBean(task, MesProTaskDO.class)
                .setWorkOrderId(updateReqVO.getWorkOrderId() != null ? updateReqVO.getWorkOrderId() : task.getWorkOrderId())
                .setRouteId(updateReqVO.getRouteId() != null ? updateReqVO.getRouteId() : task.getRouteId())
                .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.validateWorkstationExists(candidate.getWorkstationId());
        if (java.util.Objects.equals(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()).getType(), cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum.SELF.getType()) && !java.util.Objects.equals(station.getProcessId(), candidate.getProcessId())) {
            throw exception(PRO_TASK_SCHEDULE_INVALID, "工作站与工序不匹配");
        }
        scheduleGuard.validate(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()), candidate, task.getId());
        // 2. 更新
        MesProTaskDO updateObj = BeanUtils.toBean(updateReqVO, MesProTaskDO.class)
                .setName(buildTaskName(item, quantity));
        // 报工人为 null 表示不修改;空集合表示清空限制(所有人可报工)
        if (updateReqVO.getAssigneeUserIds() != null) {
            updateObj.setAssigneeUserIds(joinUserIds(updateReqVO.getAssigneeUserIds()));
        }
        if (updateReqVO.getProcessId() != null && !updateReqVO.getProcessId().equals(task.getProcessId())) {
            if (feedbackMapper.selectCountByTaskId(task.getId()) > 0) throw exception(PRO_TASK_HAS_FEEDBACK);
            freezeTaskParamConfig(updateObj);
        }
        taskMapper.updateById(updateObj);
        synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()), candidate.getRouteId());
        if (!java.util.Objects.equals(task.getWorkOrderId(), candidate.getWorkOrderId())) synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId());
    }
    private void synchronizeScheduledQuantity(MesProWorkOrderDO order, Long routeId) {
        if (!java.util.Objects.equals(order.getType(), cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum.SELF.getType()) || order.getQuantity() == null || routeId == null) return;
        List<MesProRouteProcessDO> required = buildRouteProcessList(routeId, order.getId());
        List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(order.getId());
        BigDecimal scheduled = required.isEmpty() ? BigDecimal.ZERO : 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));
    }
    /** 从工序和模板复制定义,后续模板维护不改变已排产任务。 */
@@ -233,6 +292,38 @@
        if (team != null) {
            reqVO.setTeamName(team.getName());
        }
    }
    /**
     * 获得工作站配置岗位对应的员工编号
     */
    private List<Long> getWorkstationWorkerUserIds(Long workstationId) {
        if (workstationId == null) {
            return Collections.emptyList();
        }
        List<MesMdWorkstationWorkerDO> workers = workstationWorkerService
                .getWorkstationWorkerListByWorkstationId(workstationId);
        if (CollUtil.isEmpty(workers)) {
            return Collections.emptyList();
        }
        Set<Long> postIds = convertSet(workers, MesMdWorkstationWorkerDO::getPostId);
        if (CollUtil.isEmpty(postIds)) {
            return Collections.emptyList();
        }
        return convertList(adminUserApi.getUserListByPostIds(postIds), AdminUserRespDTO::getId);
    }
    /**
     * 将报工人用户编号列表拼接为逗号分隔字符串
     *
     * @return null 表示不修改/不限制;空字符串表示清空限制
     */
    private String joinUserIds(List<Long> userIds) {
        if (userIds == null) {
            return null;
        }
        return userIds.stream().filter(java.util.Objects::nonNull).distinct()
                .map(String::valueOf).collect(java.util.stream.Collectors.joining(","));
    }
    /**
@@ -322,15 +413,17 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteTask(Long id) {
        // 1. 校验存在且非终态
        validateTaskNotFinished(id);
        scheduleGuard.lock();
        MesProTaskDO task = validateTaskNotFinished(id);
        // 2. 校验任务下无报工记录,避免删除后报工追溯断链
        if (feedbackMapper.selectCountByTaskId(id) > 0) {
            throw exception(PRO_TASK_HAS_FEEDBACK);
        }
        // 3. 删除
        taskMapper.deleteById(id);
        synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId());
    }
    @Override
@@ -354,8 +447,8 @@
    }
    @Override
    public Map<Long, Long> getPendingFeedbackCountByWorkstation() {
        List<Map<String, Object>> rows = taskMapper.selectPendingFeedbackCountByWorkstation();
    public Map<Long, Long> getPendingFeedbackCountByWorkstation(Long assigneeUserId) {
        List<Map<String, Object>> rows = taskMapper.selectPendingFeedbackCountByWorkstation(assigneeUserId);
        Map<Long, Long> result = new HashMap<>(rows.size());
        for (Map<String, Object> row : rows) {
            Object workstationId = row.get("workstation_id");
@@ -505,6 +598,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Long> autoGenerateTasks(MesProTaskAutoGenerateReqVO reqVO) {
        scheduleGuard.lock();
        // 1. 校验工单已确认
        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
        // 2. 确定工艺路线:优先请求指定 → 工单关联 → 产品关联
@@ -535,6 +629,7 @@
                    .setStatus(MesProTaskStatusEnum.PREPARE.getStatus())
                    .setClientId(workOrder.getClientId())
                    .setColorCode(rp.getColorCode());
            scheduleGuard.validate(workOrder, task, null);
            freezeTaskParamConfig(task);
            taskMapper.insert(task);
            taskIds.add(task.getId());