|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+package com.water.patrol.service;
|
|
|
2
|
+
|
|
|
3
|
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
|
4
|
+import com.water.patrol.entity.PatrolIssueReport;
|
|
|
5
|
+import com.water.patrol.entity.PatrolWorkOrder;
|
|
|
6
|
+import com.water.patrol.mapper.PatrolIssueReportMapper;
|
|
|
7
|
+import org.junit.jupiter.api.Test;
|
|
|
8
|
+import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
9
|
+import org.mockito.InjectMocks;
|
|
|
10
|
+import org.mockito.Mock;
|
|
|
11
|
+import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
12
|
+
|
|
|
13
|
+import java.time.LocalDateTime;
|
|
|
14
|
+import java.util.List;
|
|
|
15
|
+import java.util.Map;
|
|
|
16
|
+
|
|
|
17
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
18
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
19
|
+import static org.mockito.ArgumentMatchers.eq;
|
|
|
20
|
+import static org.mockito.Mockito.*;
|
|
|
21
|
+
|
|
|
22
|
+/**
|
|
|
23
|
+ * PatrolIssueLinkageService 专项测试(对应 Issue #77,PM 反复要求的巡检/工单专项测试)。
|
|
|
24
|
+ *
|
|
|
25
|
+ * 覆盖:联动创建工单 + workOrderId 回填 + 状态同步 + 分类统计 + 时效分析。
|
|
|
26
|
+ */
|
|
|
27
|
+@ExtendWith(MockitoExtension.class)
|
|
|
28
|
+class PatrolIssueLinkageServiceTest {
|
|
|
29
|
+
|
|
|
30
|
+ @Mock PatrolIssueReportMapper issueMapper;
|
|
|
31
|
+ @Mock PatrolWorkOrderService workOrderService;
|
|
|
32
|
+
|
|
|
33
|
+ @InjectMocks PatrolIssueLinkageService service;
|
|
|
34
|
+
|
|
|
35
|
+ private PatrolIssueReport issue(Long id, String severity, Long workOrderId) {
|
|
|
36
|
+ PatrolIssueReport i = new PatrolIssueReport();
|
|
|
37
|
+ i.setId(id);
|
|
|
38
|
+ i.setTaskId(10L);
|
|
|
39
|
+ i.setIssueType("water_leak");
|
|
|
40
|
+ i.setDescription("管道漏水");
|
|
|
41
|
+ i.setSeverity(severity);
|
|
|
42
|
+ i.setWorkOrderId(workOrderId);
|
|
|
43
|
+ i.setStatus("submitted");
|
|
|
44
|
+ i.setCreatedAt(LocalDateTime.now().minusHours(2));
|
|
|
45
|
+ return i;
|
|
|
46
|
+ }
|
|
|
47
|
+
|
|
|
48
|
+ private PatrolWorkOrder wo(Long id, String status, LocalDateTime resolvedAt) {
|
|
|
49
|
+ PatrolWorkOrder w = new PatrolWorkOrder();
|
|
|
50
|
+ w.setId(id);
|
|
|
51
|
+ w.setOrderNo("PWO-" + id);
|
|
|
52
|
+ w.setStatus(status);
|
|
|
53
|
+ w.setResolvedAt(resolvedAt);
|
|
|
54
|
+ return w;
|
|
|
55
|
+ }
|
|
|
56
|
+
|
|
|
57
|
+ // ---------- 联动创建工单 ----------
|
|
|
58
|
+
|
|
|
59
|
+ @Test
|
|
|
60
|
+ void linkAndCreateWorkOrder_createsOrderAndBackfillsId() {
|
|
|
61
|
+ PatrolIssueReport i = issue(1L, "high", null);
|
|
|
62
|
+ PatrolWorkOrder created = wo(500L, "pending", null);
|
|
|
63
|
+ when(workOrderService.create(eq(10L), eq("water_leak"), eq("管道漏水"), isNull(), eq("high")))
|
|
|
64
|
+ .thenReturn(created);
|
|
|
65
|
+
|
|
|
66
|
+ PatrolWorkOrder result = service.linkAndCreateWorkOrder(i);
|
|
|
67
|
+
|
|
|
68
|
+ assertSame(created, result);
|
|
|
69
|
+ // workOrderId 被回填
|
|
|
70
|
+ assertEquals(500L, i.getWorkOrderId());
|
|
|
71
|
+ // 状态流转为处理中
|
|
|
72
|
+ assertEquals("processing", i.getStatus());
|
|
|
73
|
+ // 工单创建 + 问题更新均被调用
|
|
|
74
|
+ verify(workOrderService).create(any(), any(), any(), any(), any());
|
|
|
75
|
+ verify(issueMapper).updateById(i);
|
|
|
76
|
+ }
|
|
|
77
|
+
|
|
|
78
|
+ @Test
|
|
|
79
|
+ void linkAndCreateWorkOrder_severityMapping_urgentToHigh() {
|
|
|
80
|
+ PatrolIssueReport i = issue(2L, "urgent", null);
|
|
|
81
|
+ when(workOrderService.create(any(), any(), any(), any(), eq("high"))).thenReturn(wo(1L, "pending", null));
|
|
|
82
|
+
|
|
|
83
|
+ service.linkAndCreateWorkOrder(i);
|
|
|
84
|
+ // urgent 映射为 high
|
|
|
85
|
+ verify(workOrderService).create(any(), any(), any(), any(), eq("high"));
|
|
|
86
|
+ }
|
|
|
87
|
+
|
|
|
88
|
+ @Test
|
|
|
89
|
+ void linkAndCreateWorkOrder_severityMapping_lowStaysLow() {
|
|
|
90
|
+ PatrolIssueReport i = issue(3L, "low", null);
|
|
|
91
|
+ when(workOrderService.create(any(), any(), any(), any(), eq("low"))).thenReturn(wo(1L, "pending", null));
|
|
|
92
|
+
|
|
|
93
|
+ service.linkAndCreateWorkOrder(i);
|
|
|
94
|
+ verify(workOrderService).create(any(), any(), any(), any(), eq("low"));
|
|
|
95
|
+ }
|
|
|
96
|
+
|
|
|
97
|
+ @Test
|
|
|
98
|
+ void linkAndCreateWorkOrder_skipsIfAlreadyLinked() {
|
|
|
99
|
+ PatrolIssueReport i = issue(4L, "high", 999L); // 已有工单
|
|
|
100
|
+ when(workOrderService.getDetail(999L)).thenReturn(wo(999L, "assigned", null));
|
|
|
101
|
+
|
|
|
102
|
+ PatrolWorkOrder result = service.linkAndCreateWorkOrder(i);
|
|
|
103
|
+
|
|
|
104
|
+ assertEquals(999L, result.getId());
|
|
|
105
|
+ // 不应再创建新工单
|
|
|
106
|
+ verify(workOrderService, never()).create(any(), any(), any(), any(), any());
|
|
|
107
|
+ verify(issueMapper, never()).updateById(any());
|
|
|
108
|
+ }
|
|
|
109
|
+
|
|
|
110
|
+ @Test
|
|
|
111
|
+ void linkAndCreateWorkOrder_nullIssueThrows() {
|
|
|
112
|
+ assertThrows(IllegalArgumentException.class, () -> service.linkAndCreateWorkOrder(null));
|
|
|
113
|
+ }
|
|
|
114
|
+
|
|
|
115
|
+ // ---------- 状态同步 ----------
|
|
|
116
|
+
|
|
|
117
|
+ @Test
|
|
|
118
|
+ void syncIssueStatus_updatesIssueWhenWorkOrderResolved() {
|
|
|
119
|
+ PatrolIssueReport i = issue(5L, "high", 50L);
|
|
|
120
|
+ i.setStatus("processing");
|
|
|
121
|
+ when(issueMapper.selectOne(any(Wrapper.class))).thenReturn(i);
|
|
|
122
|
+
|
|
|
123
|
+ service.syncIssueStatus(50L, "resolved");
|
|
|
124
|
+
|
|
|
125
|
+ assertEquals("resolved", i.getStatus());
|
|
|
126
|
+ verify(issueMapper).updateById(i);
|
|
|
127
|
+ }
|
|
|
128
|
+
|
|
|
129
|
+ @Test
|
|
|
130
|
+ void syncIssueStatus_noopWhenNoLinkedIssue() {
|
|
|
131
|
+ when(issueMapper.selectOne(any(Wrapper.class))).thenReturn(null);
|
|
|
132
|
+ service.syncIssueStatus(999L, "resolved");
|
|
|
133
|
+ verify(issueMapper, never()).updateById(any());
|
|
|
134
|
+ }
|
|
|
135
|
+
|
|
|
136
|
+ // ---------- 分类统计 ----------
|
|
|
137
|
+
|
|
|
138
|
+ @Test
|
|
|
139
|
+ @SuppressWarnings("unchecked")
|
|
|
140
|
+ void countByType_groupsByIssueType() {
|
|
|
141
|
+ PatrolIssueReport a = issue(1L, "high", null); a.setIssueType("water_leak");
|
|
|
142
|
+ PatrolIssueReport b = issue(2L, "low", null); b.setIssueType("water_leak");
|
|
|
143
|
+ PatrolIssueReport c = issue(3L, "medium", null); c.setIssueType("equipment_failure");
|
|
|
144
|
+ when(issueMapper.selectList(any(Wrapper.class))).thenReturn(List.of(a, b, c));
|
|
|
145
|
+
|
|
|
146
|
+ Map<String, Long> stats = service.countByType();
|
|
|
147
|
+
|
|
|
148
|
+ assertEquals(2L, stats.get("water_leak"));
|
|
|
149
|
+ assertEquals(1L, stats.get("equipment_failure"));
|
|
|
150
|
+ }
|
|
|
151
|
+
|
|
|
152
|
+ // ---------- 时效分析 ----------
|
|
|
153
|
+
|
|
|
154
|
+ @Test
|
|
|
155
|
+ @SuppressWarnings("unchecked")
|
|
|
156
|
+ void processingTimeStats_computesAvgAndMax() {
|
|
|
157
|
+ LocalDateTime base = LocalDateTime.now().minusHours(5);
|
|
|
158
|
+ PatrolIssueReport i1 = issue(1L, "high", 100L);
|
|
|
159
|
+ i1.setCreatedAt(base);
|
|
|
160
|
+ PatrolIssueReport i2 = issue(2L, "low", 200L);
|
|
|
161
|
+ i2.setCreatedAt(base);
|
|
|
162
|
+
|
|
|
163
|
+ when(issueMapper.selectList(any(Wrapper.class))).thenReturn(List.of(i1, i2));
|
|
|
164
|
+ // 工单1:创建后2小时解决;工单2:创建后4小时解决
|
|
|
165
|
+ when(workOrderService.getDetail(100L)).thenReturn(wo(100L, "resolved", base.plusHours(2)));
|
|
|
166
|
+ when(workOrderService.getDetail(200L)).thenReturn(wo(200L, "resolved", base.plusHours(4)));
|
|
|
167
|
+
|
|
|
168
|
+ Map<String, Object> stats = service.processingTimeStats();
|
|
|
169
|
+
|
|
|
170
|
+ assertEquals(2L, stats.get("resolvedCount"));
|
|
|
171
|
+ // 平均 = (2+4)/2 = 3 小时
|
|
|
172
|
+ assertEquals(3.0, (double) stats.get("avgHours"), 0.01);
|
|
|
173
|
+ // 最大 = 4 小时
|
|
|
174
|
+ assertEquals(4.0, (double) stats.get("maxHours"), 0.01);
|
|
|
175
|
+ }
|
|
|
176
|
+
|
|
|
177
|
+ @Test
|
|
|
178
|
+ @SuppressWarnings("unchecked")
|
|
|
179
|
+ void processingTimeStats_skipsUnresolved() {
|
|
|
180
|
+ PatrolIssueReport i = issue(1L, "high", 100L);
|
|
|
181
|
+ when(issueMapper.selectList(any(Wrapper.class))).thenReturn(List.of(i));
|
|
|
182
|
+ // 工单未解决(resolvedAt=null)
|
|
|
183
|
+ when(workOrderService.getDetail(100L)).thenReturn(wo(100L, "in_progress", null));
|
|
|
184
|
+
|
|
|
185
|
+ Map<String, Object> stats = service.processingTimeStats();
|
|
|
186
|
+
|
|
|
187
|
+ assertEquals(0L, stats.get("resolvedCount"));
|
|
|
188
|
+ assertEquals(0.0, (double) stats.get("avgHours"), 0.01);
|
|
|
189
|
+ }
|
|
|
190
|
+}
|