云
4 小时以前 e09f103259fa602382bb5ce2a03361a6397af46a
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package cn.iocoder.yudao.module.mes.service.pro.task.schedule;
 
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationMachineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.*;
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.dal.mysql.pro.task.MesProTaskMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskServiceImpl;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
 
class MesProScheduleGuardTest {
    private static <T> T stub(Class<T> type, InvocationHandler handler) {
        return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, handler));
    }
    private final List<MesProTaskDO> tasks = new ArrayList<>();
    private final MesProWorkOrderDO order = new MesProWorkOrderDO().setId(1L).setType(1).setQuantity(BigDecimal.valueOf(100));
    private MesProTaskMapper mapper() {
        return stub(MesProTaskMapper.class, (p,m,a) -> switch (m.getName()) {
            case "selectListByWorkOrderId" -> tasks.stream().filter(task -> Objects.equals(task.getWorkOrderId(), a[0])).toList();
            case "selectSchedulingTasks" -> tasks.stream().filter(task -> task.getStatus()!=4 && task.getStatus()!=5).toList();
            default -> throw new AssertionError(m.getName());
        });
    }
    private MesProScheduleGuardService guard() {
        var guard = new MesProScheduleGuardService();
        ReflectionTestUtils.setField(guard, "taskMapper", mapper());
        ReflectionTestUtils.setField(guard, "quantityService", new MesProScheduleQuantityService() {
            @Override public BigDecimal targetQuantity(MesProWorkOrderDO order, Long routeId, Long processId) {
                return order.getQuantity();
            }
        });
        ReflectionTestUtils.setField(guard, "workstationMachineMapper", stub(MesMdWorkstationMachineMapper.class, (p,m,a) -> List.of(
                new MesMdWorkstationMachineDO().setWorkstationId(1L).setMachineryId(10L),
                new MesMdWorkstationMachineDO().setWorkstationId(2L).setMachineryId(10L))));
        ReflectionTestUtils.setField(guard, "machineryMapper", stub(MesDvMachineryMapper.class, (p,m,a) -> List.of()));
        return guard;
    }
    private MesProTaskDO candidate() {
        return new MesProTaskDO().setWorkOrderId(1L).setProcessId(1L).setWorkstationId(2L).setQuantity(BigDecimal.TEN)
                .setStartTime(LocalDateTime.of(2026,10,15,9,0)).setEndTime(LocalDateTime.of(2026,10,15,10,0));
    }
 
    @Test void sharedEquipmentConflictIsCaughtForManualTask() {
        tasks.add(candidate().setId(1L).setCode("EXISTING").setWorkOrderId(2L).setWorkstationId(1L).setStatus(0));
        assertThrows(ServiceException.class, () -> guard().validate(order, candidate(), null));
    }
    @Test void boundaryTouchIsAllowed() {
        tasks.add(candidate().setId(1L).setWorkOrderId(2L).setWorkstationId(1L).setStatus(0)
                .setStartTime(LocalDateTime.of(2026,10,15,8,0)).setEndTime(LocalDateTime.of(2026,10,15,9,0)));
        assertDoesNotThrow(() -> guard().validate(order, candidate(), null));
    }
    @Test void perProcessQuantityIncludesFinishedAndIgnoresCanceled() {
        tasks.add(candidate().setId(1L).setQuantity(BigDecimal.valueOf(95)).setStatus(4));
        assertThrows(ServiceException.class, () -> guard().validate(order, candidate(), null));
        tasks.getFirst().setStatus(5);
        assertDoesNotThrow(() -> guard().validate(order, candidate(), null));
    }
    @Test void editExcludesItselfAndIncompleteTimesAreRejected() {
        tasks.add(candidate().setId(1L).setQuantity(BigDecimal.valueOf(100)).setStatus(0));
        assertDoesNotThrow(() -> guard().validate(order, candidate(), 1L));
        assertThrows(ServiceException.class, () -> guard().validate(order, candidate().setEndTime(null), 1L));
    }
    @Test void invalidHistoricalQuantityBlocksExtraScheduling() {
        tasks.add(candidate().setId(1L).setQuantity(null).setStatus(4));
        assertThrows(ServiceException.class, () -> guard().validate(order, candidate(), null));
    }
    @Test void schedulingLockRequiresTransaction() {
        assertThrows(IllegalStateException.class, () -> guard().lock());
    }
    private MesProScheduleGuardService yieldGuard() {
        var guard = guard();
        var quantities = new MesProScheduleQuantityService();
        ReflectionTestUtils.setField(quantities, "workOrderProcessService", stub(MesProWorkOrderProcessService.class, (p,m,a) -> List.of(
                new MesProWorkOrderProcessDO().setProcessId(3L).setSort(30).setYieldRate(new BigDecimal("0.98")),
                new MesProWorkOrderProcessDO().setProcessId(1L).setSort(10).setYieldRate(new BigDecimal("0.98")),
                new MesProWorkOrderProcessDO().setProcessId(2L).setSort(20).setYieldRate(new BigDecimal("0.98")))));
        ReflectionTestUtils.setField(guard, "quantityService", quantities);
        order.setQuantity(new BigDecimal("200"));
        return guard;
    }
    @Test void yieldAdjustedPreviewQuantitiesCanBeWrittenButExcessIsRejected() {
        var guard = yieldGuard();
        for (int i = 0; i < 3; i++) {
            long processId = i + 1;
            BigDecimal quantity = BigDecimal.valueOf(new int[]{209,205,200}[i]);
            assertDoesNotThrow(() -> guard.validate(order, candidate().setProcessId(processId).setQuantity(quantity), null));
            assertThrows(ServiceException.class, () -> guard.validate(order, candidate().setProcessId(processId).setQuantity(quantity.add(BigDecimal.ONE)), null));
        }
    }
    @Test void yieldAdjustedLimitIncludesSplitFinishedTasksAndExcludesCanceledAndEditedTask() {
        var guard = yieldGuard();
        tasks.add(candidate().setId(1L).setQuantity(BigDecimal.valueOf(100)).setStatus(4));
        tasks.add(candidate().setId(2L).setQuantity(BigDecimal.valueOf(209)).setStatus(5));
        assertDoesNotThrow(() -> guard.validate(order, candidate().setQuantity(BigDecimal.valueOf(109)), null));
        assertThrows(ServiceException.class, () -> guard.validate(order, candidate().setQuantity(BigDecimal.valueOf(110)), null));
        assertDoesNotThrow(() -> guard.validate(order, candidate().setQuantity(BigDecimal.valueOf(209)), 1L));
    }
    @Test void aggregateScheduledQuantityUsesMinimumAcrossProcessesAndFallsAfterDeletion() {
        var service = new MesProTaskServiceImpl();
        tasks.add(candidate().setId(1L).setQuantity(BigDecimal.valueOf(100)).setStatus(0));
        tasks.add(candidate().setId(2L).setProcessId(2L).setQuantity(BigDecimal.valueOf(40)).setStatus(0));
        tasks.add(candidate().setId(3L).setProcessId(2L).setQuantity(BigDecimal.valueOf(20)).setStatus(4));
        tasks.add(candidate().setId(4L).setProcessId(2L).setQuantity(BigDecimal.valueOf(30)).setStatus(5));
        var saved = new AtomicReference<MesProWorkOrderDO>();
        ReflectionTestUtils.setField(service, "taskMapper", mapper());
        ReflectionTestUtils.setField(service, "workOrderProcessService", stub(MesProWorkOrderProcessService.class, (p,m,a) -> List.of(
                new MesProWorkOrderProcessDO().setProcessId(1L), new MesProWorkOrderProcessDO().setProcessId(2L))));
        ReflectionTestUtils.setField(service, "scheduleWorkOrderMapper", stub(MesProWorkOrderMapper.class, (p,m,a) -> { saved.set((MesProWorkOrderDO)a[0]); return 1; }));
        ReflectionTestUtils.invokeMethod(service, "synchronizeScheduledQuantity", order, 100L);
        assertEquals(BigDecimal.valueOf(60), saved.get().getQuantityScheduled());
        tasks.removeIf(task -> task.getProcessId()==2L);
        ReflectionTestUtils.invokeMethod(service, "synchronizeScheduledQuantity", order, 100L);
        assertEquals(BigDecimal.ZERO, saved.get().getQuantityScheduled());
    }
}