云
7 小时以前 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
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package cn.iocoder.yudao.module.mes.service.pro.feedback;
 
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.pro.task.schedule.MesProScheduleGuardService;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
 
import java.math.BigDecimal;
import java.time.*;
import java.util.*;
 
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
 
class MesProFeedbackEquipmentServiceTest {
    private final MesProScheduleGuardService guard = mock(MesProScheduleGuardService.class);
    private final MesProTaskMapper tasks = mock(MesProTaskMapper.class);
    private final MesProFeedbackMapper feedbacks = mock(MesProFeedbackMapper.class);
    private final MesDvMachineryMapper machines = mock(MesDvMachineryMapper.class);
    private final MesMdWorkstationService stations = mock(MesMdWorkstationService.class);
    private MesProFeedbackEquipmentService service;
    private MesProTaskDO task;
    private MesDvMachineryDO machine;
    private final LocalDateTime now = LocalDateTime.of(2026,10,15,14,0);
 
    @BeforeEach void setup() {
        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "test"), MesDvMachineryDO.class);
        service = new MesProFeedbackEquipmentService();
        ReflectionTestUtils.setField(service,"guard",guard);
        ReflectionTestUtils.setField(service,"taskMapper",tasks);
        ReflectionTestUtils.setField(service,"feedbackMapper",feedbacks);
        ReflectionTestUtils.setField(service,"machineryMapper",machines);
        ReflectionTestUtils.setField(service,"workstationService",stations);
        ReflectionTestUtils.setField(service,"clock",Clock.fixed(now.atZone(ZoneId.of("Asia/Shanghai")).toInstant(),ZoneId.of("Asia/Shanghai")));
        task=new MesProTaskDO().setId(1L).setWorkOrderId(2L).setWorkstationId(3L).setProcessId(4L).setRouteId(5L)
                .setQuantity(BigDecimal.TEN).setProducedQuantity(BigDecimal.ZERO).setStatus(0);
        machine=new MesDvMachineryDO().setId(10L).setName("冷热罐").setStatus(1);
        when(tasks.selectById(1L)).thenReturn(task);
        when(tasks.selectSchedulingTasks()).thenReturn(List.of(task));
        when(tasks.updateById(any(MesProTaskDO.class))).thenAnswer(call->{MesProTaskDO update=call.getArgument(0);if(update.getStatus()!=null)task.setStatus(update.getStatus());return 1;});
        when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(10L),6L,Set.of(10L)));
        when(stations.getWorkstation(3L)).thenReturn(new MesMdWorkstationDO().setId(3L).setProcessId(4L).setStatus(0));
        when(machines.selectByIds(anyCollection())).thenReturn(List.of(machine));
        when(machines.update(isNull(),any(Wrapper.class))).thenReturn(1);
        when(feedbacks.selectListByTaskId(anyLong())).thenReturn(List.of());
        when(feedbacks.selectListByTaskIds(anyCollection())).thenReturn(List.of());
    }
    private MesProFeedbackDO report() {
        return new MesProFeedbackDO().setId(7L).setTaskId(1L).setWorkOrderId(2L).setWorkstationId(3L)
                .setProcessId(4L).setRouteId(5L).setFeedbackTime(now).setStatus(2);
    }
    private MesProTaskDO other() {
        return new MesProTaskDO().setId(8L).setWorkstationId(6L).setStatus(2)
                .setQuantity(BigDecimal.TEN).setProducedQuantity(BigDecimal.ONE);
    }
    @Test void submitCapturesStoppedEquipmentWithoutStartingIt() {
        var f=report();service.validateSubmission(f);
        assertEquals("10",f.getMachineryIds());
        verify(machines,never()).update(isNull(),any(Wrapper.class));
    }
    @Test void maintenanceBlocksCurrentSubmission() {
        machine.setStatus(3);
        assertThrows(ServiceException.class,()->service.validateSubmission(report()));
    }
    @Test void pastDayReportNeitherUsesCurrentMaintenanceNorChangesEquipment() {
        var f=report().setFeedbackTime(now.minusDays(1));machine.setStatus(3);
        assertDoesNotThrow(()->service.validateSubmission(f));service.afterApproved(f);
        assertNull(f.getMachineryIds());verifyNoInteractions(machines);
    }
    @Test void futureTimeAndPausedTaskAreRejected() {
        assertThrows(ServiceException.class,()->service.validateSubmission(report().setFeedbackTime(now.plusHours(1))));
        task.setStatus(9);assertThrows(ServiceException.class,()->service.validateSubmission(report()));
    }
    @Test void missingStationEquipmentAndChangedTaskAreRejected() {
        when(guard.machineIdsByStation()).thenReturn(Map.of());
        assertThrows(ServiceException.class,()->service.validateSubmission(report()));
        assertThrows(ServiceException.class,()->service.validateSubmission(report().setProcessId(999L)));
    }
    @Test void delayedApprovalDoesNotInvalidateAlreadySubmittedProduction() {
        machine.setStatus(3);
        assertDoesNotThrow(()->service.validateApproval(report().setMachineryIds("10")));
        service.afterApproved(report().setMachineryIds("10"));
        verify(machines,never()).update(isNull(),any(Wrapper.class));
    }
    @Test void partialApprovedProductionStartsEquipment() {
        task.setProducedQuantity(BigDecimal.ONE);
        service.afterApproved(report().setMachineryIds("10"));
        assertEquals(2,task.getStatus());verify(machines).update(isNull(),any(Wrapper.class));
    }
    @Test void finishedQuantityStopsEquipment() {
        task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2);
        service.afterApproved(report().setMachineryIds("10"));
        assertEquals(4, task.getStatus());
        verify(machines).update(isNull(),any(Wrapper.class));
    }
    @Test void historicalCompletedTaskClosesWithoutChangingLiveMachineStatus() {
        task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2);
        service.afterApproved(report().setFeedbackTime(now.minusDays(1)));
        assertEquals(4,task.getStatus());verifyNoInteractions(machines);
    }
    @Test void resumeChecksMaintenanceAndSharedOccupancy() {
        task.setStatus(2).setProducedQuantity(BigDecimal.ONE);machine.setStatus(3);
        assertThrows(ServiceException.class,()->service.validateResume(task));
        machine.setStatus(1);
        when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other()));
        assertThrows(ServiceException.class,()->service.validateResume(task));
    }
    @Test void anotherActiveTaskKeepsSharedEquipmentRunning() {
        task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2);
        when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other()));
        service.afterApproved(report().setMachineryIds("10"));
        verify(machines,never()).update(isNull(),any(Wrapper.class));
    }
    @Test void otherRunningTaskWithZeroReportedQuantityStillBlocksSubmission() {
        when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other().setStatus(1).setProducedQuantity(BigDecimal.ZERO)));
        assertThrows(ServiceException.class,()->service.validateSubmission(report()));
    }
    @Test void submittedReportReservesEquipmentWithoutStartingIt() {
        var pendingTask=other().setStatus(0).setProducedQuantity(BigDecimal.ZERO);
        when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,pendingTask));
        when(feedbacks.selectListByTaskIds(anyCollection())).thenReturn(List.of(
                report().setId(9L).setTaskId(8L).setMachineryIds("10").setStatus(2)));
        assertThrows(ServiceException.class,()->service.validateSubmission(report()));
        task.setProducedQuantity(BigDecimal.TEN);machine.setStatus(2);
        service.afterApproved(report().setMachineryIds("10"));
        verify(machines).update(isNull(),any(Wrapper.class));
    }
    @Test void pausedTaskReleasesButFutureDraftDoesNotOccupyEquipment() {
        task.setStatus(9).setProducedQuantity(BigDecimal.ONE).setFirstFeedbackTime(now);machine.setStatus(2);
        when(tasks.selectSchedulingTasks()).thenReturn(List.of(task,other().setStatus(0).setProducedQuantity(BigDecimal.ZERO)));
        service.afterTaskStateChanged(List.of(task));verify(machines).update(isNull(),any(Wrapper.class));
    }
    @Test void cancelingUnstartedDraftDoesNotStopUntrackedEquipment() {
        machine.setStatus(2);service.afterTaskStateChanged(List.of(task));
        verify(machines,never()).selectByIds(anyCollection());
    }
    @Test void existingSnapshotSurvivesWorkstationRebinding() {
        when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(99L)));
        when(feedbacks.selectListByTaskId(1L)).thenReturn(List.of(report().setMachineryIds("10").setStatus(4)));
        var f=report();service.validateSubmission(f);assertEquals("10",f.getMachineryIds());
    }
    @Test void newTaskCanUseAvailableSubsetButCannotSwapExistingMaintenanceSnapshot() {
        var maintenance=new MesDvMachineryDO().setId(11L).setStatus(3).setName("维护设备");
        when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(10L,11L)));
        when(machines.selectByIds(anyCollection())).thenAnswer(call->{Collection<Long> ids=call.getArgument(0);return List.of(machine,maintenance).stream().filter(m->ids.contains(m.getId())).toList();});
        var f=report();service.validateSubmission(f);assertEquals("10",f.getMachineryIds());
        when(feedbacks.selectListByTaskId(1L)).thenReturn(List.of(report().setMachineryIds("11").setStatus(4)));
        assertThrows(ServiceException.class,()->service.validateSubmission(report()));
    }
    @Test void maintenanceIsNeverOverwrittenWhenTaskStops() {
        task.setStatus(9).setProducedQuantity(BigDecimal.ONE).setFirstFeedbackTime(now);machine.setStatus(3);
        service.afterTaskStateChanged(List.of(task));verify(machines,never()).update(isNull(),any(Wrapper.class));
    }
    @Test void stoppingReboundStationTouchesSnapshotMachinesOnly() {
        task.setStatus(9).setProducedQuantity(BigDecimal.ONE).setFirstFeedbackTime(now);
        when(guard.machineIdsByStation()).thenReturn(Map.of(3L,Set.of(99L)));
        when(feedbacks.selectListByTaskId(1L)).thenReturn(List.of(report().setMachineryIds("10").setStatus(4)));
        service.afterTaskStateChanged(List.of(task));
        verify(machines).selectByIds(eq(Set.of(10L)));
    }
    @Test void concurrentMaintenanceRollsBackStartingProduction() {
        task.setProducedQuantity(BigDecimal.ONE);
        when(machines.update(isNull(),any(Wrapper.class))).thenReturn(0);
        assertThrows(ServiceException.class,()->service.afterApproved(report().setMachineryIds("10")));
    }
    @Test void historicalAutoFinishDoesNotTouchLiveMachinesAndScopeIsReleased() {
        task.setStatus(2).setProducedQuantity(BigDecimal.ONE);
        try (var scope=service.beginApproval(report().setFeedbackTime(now.minusDays(1)))) {
            service.afterTaskStateChanged(List.of(task));
            verifyNoInteractions(machines);
        }
        service.afterTaskStateChanged(List.of(task));
        verify(machines).selectByIds(anyCollection());
    }
}