云
12 小时以前 3ff6604c75c887f4056ee03545d204bc9323286c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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() + "的计划时间冲突");
            }
        }
    }
}