package cn.iocoder.yudao.module.mes.service.pro.task.schedule; 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; import cn.iocoder.yudao.module.mes.dal.mysql.md.workstation.MesMdWorkstationMachineMapper; import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper; import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.math.BigDecimal; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_SCHEDULE_INVALID; @Service public class MesProScheduleGuardService { @Resource private MesProTaskMapper taskMapper; @Resource private MesMdWorkstationMachineMapper workstationMachineMapper; @Resource private MesDvMachineryMapper machineryMapper; @Resource private MesProScheduleQuantityService quantityService; public void lock() { if (!TransactionSynchronizationManager.isActualTransactionActive()) { throw new IllegalStateException("排产锁必须在事务内获取"); } taskMapper.lockSchedulingResources(); } public Map> machineIdsByStation() { Map> map = new HashMap<>(); for (var relation : workstationMachineMapper.selectList()) { if (relation.getWorkstationId() != null && relation.getMachineryId() != null) { map.computeIfAbsent(relation.getWorkstationId(), id -> new TreeSet<>()).add(relation.getMachineryId()); } } for (var machine : machineryMapper.selectList()) { if (machine.getWorkstationId() != null) { map.computeIfAbsent(machine.getWorkstationId(), id -> new TreeSet<>()).add(machine.getId()); } } return map; } public void validate(MesProWorkOrderDO order, MesProTaskDO candidate, Long exceptTaskId) { if (!Objects.equals(order.getType(), cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum.SELF.getType())) { throw exception(PRO_TASK_SCHEDULE_INVALID, "仅支持自行生产工单排产"); } if (candidate.getQuantity() == null || candidate.getQuantity().signum() <= 0 || order.getQuantity() == null) { throw exception(PRO_TASK_SCHEDULE_INVALID, "排产数量必须大于0"); } List existing = taskMapper.selectListByWorkOrderId(order.getId()); if (existing.stream().anyMatch(task -> !Objects.equals(task.getId(), exceptTaskId) && !Objects.equals(task.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus()) && Objects.equals(task.getProcessId(), candidate.getProcessId()) && (task.getQuantity() == null || task.getQuantity().signum() <= 0))) { throw exception(PRO_TASK_SCHEDULE_INVALID, "当前工序已有任务数量无效,请先修正历史任务数量"); } BigDecimal assigned = existing.stream() .filter(task -> !Objects.equals(task.getId(), exceptTaskId) && !Objects.equals(task.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus()) && Objects.equals(task.getProcessId(), candidate.getProcessId())) .map(task -> task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity()) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal targetQuantity = quantityService.targetQuantity(order, candidate.getRouteId(), candidate.getProcessId()); if (assigned.add(candidate.getQuantity()).compareTo(targetQuantity) > 0) { throw exception(PRO_TASK_SCHEDULE_INVALID, "工单" + order.getCode() + "的当前工序累计排产数量超过工序目标产量" + targetQuantity.stripTrailingZeros().toPlainString()); } var start = candidate.getStartTime(); var end = candidate.getEndTime(); if (start == null && end == null) return; // 允许手工维护待完善的草稿;自动排产会阻止未知资源占用。 if (start == null || end == null || !end.isAfter(start)) { throw exception(PRO_TASK_SCHEDULE_INVALID, "请填写完整的计划起止时间,结束时间必须晚于开始时间"); } if (candidate.getWorkstationId() == null) { throw exception(PRO_TASK_SCHEDULE_INVALID, "有时间计划的任务必须指定工作站"); } var resources = machineIdsByStation(); Set machines = resources.getOrDefault(candidate.getWorkstationId(), Set.of()); for (var task : taskMapper.selectSchedulingTasks()) { if (Objects.equals(task.getId(), exceptTaskId) || MesProTaskStatusEnum.isEndStatus(task.getStatus()) || task.getWorkstationId() == null) continue; var busy = new MesProScheduleEngine.Busy(task.getWorkstationId(), resources.getOrDefault(task.getWorkstationId(), Set.of()), task.getStartTime(), task.getEndTime()); if (!MesProScheduleEngine.sharesResources(candidate.getWorkstationId(), machines, busy)) continue; if (busy.start() == null || busy.end() == null || !busy.end().isAfter(busy.start())) { throw exception(PRO_TASK_SCHEDULE_INVALID, "同一工作站或共享设备存在未完善时间的任务" + task.getCode()); } if (MesProScheduleEngine.overlaps(start, end, busy)) { throw exception(PRO_TASK_SCHEDULE_INVALID, "工作站或共享设备与任务" + task.getCode() + "的计划时间冲突"); } } } }