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<Long, Set<Long>> machineIdsByStation() {
|
Map<Long, Set<Long>> 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<MesProTaskDO> 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<Long> 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() + "的计划时间冲突");
|
}
|
}
|
}
|
}
|