Jiaqi Xia

feat(suspension): only keep latest valid record for each dimension when calculat…

…ing suspension intervals
...@@ -30,12 +30,14 @@ public class SuspensionServiceImpl implements SuspensionService { ...@@ -30,12 +30,14 @@ public class SuspensionServiceImpl implements SuspensionService {
30 final long now = System.currentTimeMillis(); 30 final long now = System.currentTimeMillis();
31 31
32 // 学校维度:classId == 0 && dinerId == 0,按 clientId 过滤 32 // 学校维度:classId == 0 && dinerId == 0,按 clientId 过滤
33 - List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( 33 + List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecordsRaw = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
34 enterpriseId, 34 enterpriseId,
35 List.of(clientId), 35 List.of(clientId),
36 null, 36 null,
37 List.of(0) 37 List.of(0)
38 - ).stream().filter(r -> r.getEndAt() > now).toList(); 38 + );
39 + List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = schoolLevelRecordsRaw == null ? List.of() :
40 + schoolLevelRecordsRaw.stream().filter(r -> r.getEndAt() > now).toList();
39 SingleDinerMealSuspensionRecordRpcResponse latestSchoolLevelRecord = pickLatest( 41 SingleDinerMealSuspensionRecordRpcResponse latestSchoolLevelRecord = pickLatest(
40 schoolLevelRecords, 42 schoolLevelRecords,
41 record -> record.getClientId() == clientId && record.getClassId() == 0 && record.getDinerId() == 0 43 record -> record.getClientId() == clientId && record.getClassId() == 0 && record.getDinerId() == 0
...@@ -45,12 +47,14 @@ public class SuspensionServiceImpl implements SuspensionService { ...@@ -45,12 +47,14 @@ public class SuspensionServiceImpl implements SuspensionService {
45 } 47 }
46 48
47 // 班级维度:dinerId == 0,按 clientId + classId 过滤 49 // 班级维度:dinerId == 0,按 clientId + classId 过滤
48 - List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( 50 + List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecordsRaw = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
49 enterpriseId, 51 enterpriseId,
50 List.of(clientId), 52 List.of(clientId),
51 List.of(classId), 53 List.of(classId),
52 List.of(0) 54 List.of(0)
53 - ).stream().filter(r -> r.getEndAt() > now).toList(); 55 + );
56 + List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = classLevelRecordsRaw == null ? List.of() :
57 + classLevelRecordsRaw.stream().filter(r -> r.getEndAt() > now).toList();
54 SingleDinerMealSuspensionRecordRpcResponse latestClassLevelRecord = pickLatest( 58 SingleDinerMealSuspensionRecordRpcResponse latestClassLevelRecord = pickLatest(
55 classLevelRecords, 59 classLevelRecords,
56 record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == 0 60 record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == 0
...@@ -60,12 +64,14 @@ public class SuspensionServiceImpl implements SuspensionService { ...@@ -60,12 +64,14 @@ public class SuspensionServiceImpl implements SuspensionService {
60 } 64 }
61 65
62 // 学生维度:三个 id 都不为 0 66 // 学生维度:三个 id 都不为 0
63 - List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( 67 + List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecordsRaw = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
64 enterpriseId, 68 enterpriseId,
65 List.of(clientId), 69 List.of(clientId),
66 List.of(classId), 70 List.of(classId),
67 List.of(dinerId) 71 List.of(dinerId)
68 - ).stream().filter(r -> r.getEndAt() > now).toList(); 72 + );
73 + List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = dinerLevelRecordsRaw == null ? List.of() :
74 + dinerLevelRecordsRaw.stream().filter(r -> r.getEndAt() > now).toList();
69 SingleDinerMealSuspensionRecordRpcResponse latestDinerLevelRecord = pickLatest( 75 SingleDinerMealSuspensionRecordRpcResponse latestDinerLevelRecord = pickLatest(
70 dinerLevelRecords, 76 dinerLevelRecords,
71 record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == dinerId 77 record -> record.getClientId() == clientId && record.getClassId() == classId && record.getDinerId() == dinerId
...@@ -115,12 +121,19 @@ public class SuspensionServiceImpl implements SuspensionService { ...@@ -115,12 +121,19 @@ public class SuspensionServiceImpl implements SuspensionService {
115 List<Integer> dinerIds = diners.stream().map(SingleDinerRpcResponse::getId).toList(); 121 List<Integer> dinerIds = diners.stream().map(SingleDinerRpcResponse::getId).toList();
116 122
117 // 查询三类记录 123 // 查询三类记录
118 - List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( 124 + List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecordsRawBatch = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
119 - enterpriseId, clientIds, null, List.of(0)).stream().filter(r -> r.getEndAt() > now).toList(); 125 + enterpriseId, clientIds, null, List.of(0));
120 - List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( 126 + List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecordsRawBatch = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
121 - enterpriseId, clientIds, classIds, List.of(0)).stream().filter(r -> r.getEndAt() > now).toList(); 127 + enterpriseId, clientIds, classIds, List.of(0));
122 - List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( 128 + List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecordsRawBatch = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
123 - enterpriseId, clientIds, classIds, dinerIds).stream().filter(r -> r.getEndAt() > now).toList(); 129 + enterpriseId, clientIds, classIds, dinerIds);
130 +
131 + List<SingleDinerMealSuspensionRecordRpcResponse> schoolLevelRecords = schoolLevelRecordsRawBatch == null ? List.of() :
132 + schoolLevelRecordsRawBatch.stream().filter(r -> r.getEndAt() > now).toList();
133 + List<SingleDinerMealSuspensionRecordRpcResponse> classLevelRecords = classLevelRecordsRawBatch == null ? List.of() :
134 + classLevelRecordsRawBatch.stream().filter(r -> r.getEndAt() > now).toList();
135 + List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = dinerLevelRecordsRawBatch == null ? List.of() :
136 + dinerLevelRecordsRawBatch.stream().filter(r -> r.getEndAt() > now).toList();
124 137
125 // 学校维度:clientId -> latest 138 // 学校维度:clientId -> latest
126 Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> clientIdToLatestSchool = schoolLevelRecords == null ? Map.of() : 139 Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> clientIdToLatestSchool = schoolLevelRecords == null ? Map.of() :
...@@ -200,7 +213,7 @@ public class SuspensionServiceImpl implements SuspensionService { ...@@ -200,7 +213,7 @@ public class SuspensionServiceImpl implements SuspensionService {
200 public List<SuspensionInterval> getSuspensionIntervalsForDiner(int enterpriseId, int clientId, int classId, int dinerId) { 213 public List<SuspensionInterval> getSuspensionIntervalsForDiner(int enterpriseId, int clientId, int classId, int dinerId) {
201 Map<Integer, List<SuspensionInterval>> map = getSuspensionIntervalsForDiners(enterpriseId, List.of( 214 Map<Integer, List<SuspensionInterval>> map = getSuspensionIntervalsForDiners(enterpriseId, List.of(
202 SingleDinerRpcResponse.newBuilder() 215 SingleDinerRpcResponse.newBuilder()
203 - .setEnterpriseId(clientId) // 注意:此处仅为构造占位,真正需要的是 clientId/classId/dinerId 216 + .setEnterpriseId(enterpriseId)
204 .setClientId(clientId) 217 .setClientId(clientId)
205 .setClassId(classId) 218 .setClassId(classId)
206 .setId(dinerId) 219 .setId(dinerId)
...@@ -227,38 +240,51 @@ public class SuspensionServiceImpl implements SuspensionService { ...@@ -227,38 +240,51 @@ public class SuspensionServiceImpl implements SuspensionService {
227 List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition( 240 List<SingleDinerMealSuspensionRecordRpcResponse> dinerLevelRecords = mealOrderServiceRpcClient.queryDinerMealSuspensionRecordsByCondition(
228 enterpriseId, clientIds, classIds, dinerIds); 241 enterpriseId, clientIds, classIds, dinerIds);
229 242
230 - Map<Integer, List<SingleDinerMealSuspensionRecordRpcResponse>> clientIdToSchool = schoolLevelRecords == null ? Map.of() : 243 + // 仅保留每个维度“最新一条”有效记录(createdAt 最大)
244 + Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> clientIdToLatestSchool = schoolLevelRecords == null ? Map.of() :
231 schoolLevelRecords.stream() 245 schoolLevelRecords.stream()
232 .filter(Objects::nonNull) 246 .filter(Objects::nonNull)
233 .filter(r -> r.getClassId() == 0 && r.getDinerId() == 0) 247 .filter(r -> r.getClassId() == 0 && r.getDinerId() == 0)
234 - .collect(Collectors.groupingBy(SingleDinerMealSuspensionRecordRpcResponse::getClientId)); 248 + .collect(Collectors.toMap(
249 + SingleDinerMealSuspensionRecordRpcResponse::getClientId,
250 + Function.identity(),
251 + (a, b) -> a.getCreatedAt() >= b.getCreatedAt() ? a : b
252 + ));
235 253
236 - Map<String, List<SingleDinerMealSuspensionRecordRpcResponse>> clientClassToRecords = classLevelRecords == null ? Map.of() : 254 + Map<String, SingleDinerMealSuspensionRecordRpcResponse> clientClassToLatest = classLevelRecords == null ? Map.of() :
237 classLevelRecords.stream() 255 classLevelRecords.stream()
238 .filter(Objects::nonNull) 256 .filter(Objects::nonNull)
239 .filter(r -> r.getDinerId() == 0) 257 .filter(r -> r.getDinerId() == 0)
240 - .collect(Collectors.groupingBy(r -> r.getClientId() + "#" + r.getClassId())); 258 + .collect(Collectors.toMap(
259 + r -> r.getClientId() + "#" + r.getClassId(),
260 + Function.identity(),
261 + (a, b) -> a.getCreatedAt() >= b.getCreatedAt() ? a : b
262 + ));
241 263
242 - Map<Integer, List<SingleDinerMealSuspensionRecordRpcResponse>> dinerIdToRecords = dinerLevelRecords == null ? Map.of() : 264 + Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> dinerIdToLatest = dinerLevelRecords == null ? Map.of() :
243 dinerLevelRecords.stream() 265 dinerLevelRecords.stream()
244 .filter(Objects::nonNull) 266 .filter(Objects::nonNull)
245 - .collect(Collectors.groupingBy(SingleDinerMealSuspensionRecordRpcResponse::getDinerId)); 267 + .collect(Collectors.toMap(
268 + SingleDinerMealSuspensionRecordRpcResponse::getDinerId,
269 + Function.identity(),
270 + (a, b) -> a.getCreatedAt() >= b.getCreatedAt() ? a : b
271 + ));
246 272
247 for (SingleDinerRpcResponse diner : diners) { 273 for (SingleDinerRpcResponse diner : diners) {
248 List<SuspensionInterval> intervals = new ArrayList<>(); 274 List<SuspensionInterval> intervals = new ArrayList<>();
249 275
250 - List<SingleDinerMealSuspensionRecordRpcResponse> schoolRecs = clientIdToSchool.getOrDefault(diner.getClientId(), List.of()); 276 + SingleDinerMealSuspensionRecordRpcResponse latestSchool = clientIdToLatestSchool.get(diner.getClientId());
251 - List<SingleDinerMealSuspensionRecordRpcResponse> classRecs = clientClassToRecords.getOrDefault(diner.getClientId() + "#" + diner.getClassId(), List.of()); 277 + SingleDinerMealSuspensionRecordRpcResponse latestClass = clientClassToLatest.get(diner.getClientId() + "#" + diner.getClassId());
252 - List<SingleDinerMealSuspensionRecordRpcResponse> dinerRecs = dinerIdToRecords.getOrDefault(diner.getId(), List.of()); 278 + SingleDinerMealSuspensionRecordRpcResponse latestDiner = dinerIdToLatest.get(diner.getId());
253 279
254 - for (SingleDinerMealSuspensionRecordRpcResponse rec : schoolRecs) { 280 + if (latestSchool != null && latestSchool.getEndAt() > now) {
255 - if (rec.getEndAt() > now) intervals.add(new SuspensionInterval(rec.getStartAt(), rec.getEndAt())); 281 + intervals.add(new SuspensionInterval(latestSchool.getStartAt(), latestSchool.getEndAt()));
256 } 282 }
257 - for (SingleDinerMealSuspensionRecordRpcResponse rec : classRecs) { 283 + if (latestClass != null && latestClass.getEndAt() > now) {
258 - if (rec.getEndAt() > now) intervals.add(new SuspensionInterval(rec.getStartAt(), rec.getEndAt())); 284 + intervals.add(new SuspensionInterval(latestClass.getStartAt(), latestClass.getEndAt()));
259 } 285 }
260 - for (SingleDinerMealSuspensionRecordRpcResponse rec : dinerRecs) { 286 + if (latestDiner != null && latestDiner.getEndAt() > now) {
261 - if (rec.getEndAt() > now) intervals.add(new SuspensionInterval(rec.getStartAt(), rec.getEndAt())); 287 + intervals.add(new SuspensionInterval(latestDiner.getStartAt(), latestDiner.getEndAt()));
262 } 288 }
263 289
264 // 归并并集:合并重叠区间、保留间隙 290 // 归并并集:合并重叠区间、保留间隙
......