Jiaqi Xia

Merge branch 'dev/nongxin-adjust' into 'master'

Dev/nongxin adjust



See merge request !8
...@@ -20,6 +20,12 @@ application { ...@@ -20,6 +20,12 @@ application {
20 '-Dlogging.path=logs'] 20 '-Dlogging.path=logs']
21 } 21 }
22 22
23 +java {
24 + toolchain {
25 + languageVersion = JavaLanguageVersion.of(17)
26 + }
27 +}
28 +
23 ext { 29 ext {
24 grpcVersion = '1.62.2' 30 grpcVersion = '1.62.2'
25 protobufVersion = '3.25.3' 31 protobufVersion = '3.25.3'
......
...@@ -7,6 +7,7 @@ import com.infoloop.tianting.mealorderservice.*; ...@@ -7,6 +7,7 @@ import com.infoloop.tianting.mealorderservice.*;
7 import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient; 7 import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient;
8 import com.infoloop.tianting.service.client.DeliveryRuleServiceRpcClient; 8 import com.infoloop.tianting.service.client.DeliveryRuleServiceRpcClient;
9 import com.infoloop.tianting.service.client.MealOrderServiceRpcClient; 9 import com.infoloop.tianting.service.client.MealOrderServiceRpcClient;
10 +import com.infoloop.tianting.service.SuspensionService;
10 import com.infoloop.tianting.store.WeeklyTaskStore; 11 import com.infoloop.tianting.store.WeeklyTaskStore;
11 import com.infoloop.tianting.utils.DateUtil; 12 import com.infoloop.tianting.utils.DateUtil;
12 import lombok.RequiredArgsConstructor; 13 import lombok.RequiredArgsConstructor;
...@@ -46,6 +47,8 @@ public class AutoCreateMealOrderTask { ...@@ -46,6 +47,8 @@ public class AutoCreateMealOrderTask {
46 47
47 private final WeeklyTaskStore weeklyTaskStore; 48 private final WeeklyTaskStore weeklyTaskStore;
48 49
50 + private final SuspensionService suspensionService;
51 +
49 @Scheduled(cron = "0 0/5 * * * ?") 52 @Scheduled(cron = "0 0/5 * * * ?")
50 public void executeTask() { 53 public void executeTask() {
51 final int enterpriseId = 449; 54 final int enterpriseId = 449;
...@@ -188,6 +191,7 @@ public class AutoCreateMealOrderTask { ...@@ -188,6 +191,7 @@ public class AutoCreateMealOrderTask {
188 // 构建创建请求 191 // 构建创建请求
189 final var creations = notReservedDiners.stream() 192 final var creations = notReservedDiners.stream()
190 .map(diner -> toMealOrderCreation( 193 .map(diner -> toMealOrderCreation(
194 + enterpriseId,
191 diner, 195 diner,
192 clientMap, 196 clientMap,
193 gradeClassMap, 197 gradeClassMap,
...@@ -217,29 +221,32 @@ public class AutoCreateMealOrderTask { ...@@ -217,29 +221,32 @@ public class AutoCreateMealOrderTask {
217 } 221 }
218 } 222 }
219 223
220 - private MealOrderCreation toMealOrderCreation(SingleDinerRpcResponse diner, 224 + private MealOrderCreation toMealOrderCreation(int enterpriseId,
221 - Map<Integer, SingleClientRpcResponse> clientMap, 225 + SingleDinerRpcResponse diner,
222 - Map<Integer, SingleGradeClassRpcResponse> classMap, 226 + Map<Integer, SingleClientRpcResponse> clientIdToClient,
223 - Map<Integer, SingleMealMenuRecordRpcResponse> menuMap, 227 + Map<Integer, SingleGradeClassRpcResponse> classIdToClass,
224 - Map<Integer, SingleMpAccountDinerRefRpcResponse> mpAccountDinerRefMap, 228 + Map<Integer, SingleMealMenuRecordRpcResponse> clientIdToLatestMenu,
229 + Map<Integer, SingleMpAccountDinerRefRpcResponse> dinerIdToAccountRef,
225 ZonedDateTime startOfNextWeek, 230 ZonedDateTime startOfNextWeek,
226 Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> schoolSuspensionMap, 231 Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> schoolSuspensionMap,
227 Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> studentSuspensionMap, 232 Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> studentSuspensionMap,
228 - Map<String, Integer> holidayMap) { 233 + Map<String, Integer> dateStrToHolidayStatus) {
229 - final var menu = menuMap.get(diner.getClientId()); 234 + final SingleMealMenuRecordRpcResponse menu = clientIdToLatestMenu.get(diner.getClientId());
230 - final var client = clientMap.get(diner.getClientId()); 235 + final SingleClientRpcResponse client = clientIdToClient.get(diner.getClientId());
231 - final var gradeClass = classMap.get(diner.getClassId()); 236 + final SingleGradeClassRpcResponse gradeClass = classIdToClass.get(diner.getClassId());
232 - final var mpAccountDinerRef = mpAccountDinerRefMap.get(diner.getId()); 237 + final SingleMpAccountDinerRefRpcResponse mpAccountDinerRef = dinerIdToAccountRef.get(diner.getId());
233 if (menu == null || client == null || gradeClass == null) return null; 238 if (menu == null || client == null || gradeClass == null) return null;
234 239
235 - // 获取停餐信息,优先使用学校级别的 240 + // 计算多段停餐区间(学校/班级/学生维度最新记录的并集,保留间隙)
236 - SingleDinerMealSuspensionRecordRpcResponse suspensionRecord = schoolSuspensionMap.get(diner.getClientId()); 241 + final var intervals = suspensionService.getSuspensionIntervalsForDiner(
237 - if (suspensionRecord == null) { 242 + enterpriseId,
238 - suspensionRecord = studentSuspensionMap.get(diner.getId()); 243 + diner.getClientId(),
239 - } 244 + diner.getClassId(),
245 + diner.getId()
246 + );
240 247
241 - // 构建下周的餐食安排 248 + // 构建下周的餐食安排(基于多段停餐区间)
242 - List<MenuSchedule> meals = buildNextWeekMeals(startOfNextWeek, suspensionRecord, holidayMap); 249 + List<MenuSchedule> meals = buildNextWeekMeals(startOfNextWeek, intervals, dateStrToHolidayStatus);
243 250
244 if (meals.isEmpty()) { 251 if (meals.isEmpty()) {
245 return null; 252 return null;
...@@ -261,8 +268,10 @@ public class AutoCreateMealOrderTask { ...@@ -261,8 +268,10 @@ public class AutoCreateMealOrderTask {
261 .build(); 268 .build();
262 } 269 }
263 270
271 + // 已由并集窗口版本替代
272 +
264 private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart, 273 private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart,
265 - SingleDinerMealSuspensionRecordRpcResponse suspensionRecord, 274 + java.util.List<SuspensionService.SuspensionInterval> intervals,
266 Map<String, Integer> holidayMap) { 275 Map<String, Integer> holidayMap) {
267 return IntStream.range(0, 7) 276 return IntStream.range(0, 7)
268 .mapToObj(i -> { 277 .mapToObj(i -> {
...@@ -272,30 +281,23 @@ public class AutoCreateMealOrderTask { ...@@ -272,30 +281,23 @@ public class AutoCreateMealOrderTask {
272 // 检查是否为节假日 281 // 检查是否为节假日
273 final var status = holidayMap.get(dateStr); 282 final var status = holidayMap.get(dateStr);
274 if (status != null && status != 0 && status != 2) { 283 if (status != null && status != 0 && status != 2) {
275 - // 如果不是工作日,返回null
276 return null; 284 return null;
277 } 285 }
278 286
279 - // 如果有停餐信息,检查当前循环的日期是否在停餐时间范围内 287 + if (intervals != null && !intervals.isEmpty()) {
280 - if (suspensionRecord != null) {
281 final var dateStart = date.with(LocalTime.MIN).toInstant().toEpochMilli(); 288 final var dateStart = date.with(LocalTime.MIN).toInstant().toEpochMilli();
282 final var dateEnd = date.with(LocalTime.MAX).toInstant().toEpochMilli(); 289 final var dateEnd = date.with(LocalTime.MAX).toInstant().toEpochMilli();
283 - final var suspensionStart = suspensionRecord.getStartAt(); 290 + final boolean overlaps = intervals.stream()
284 - final var suspensionEnd = suspensionRecord.getEndAt(); 291 + .anyMatch(interval -> !(dateEnd < interval.startAt || dateStart > interval.endAt));
285 - 292 + if (overlaps) return null;
286 - // 如果停餐时间范围与当前日期有重叠,返回null
287 - if (!(dateEnd < suspensionStart || dateStart > suspensionEnd)) {
288 - return null;
289 - }
290 } 293 }
291 294
292 - // 如果是工作日且不在停餐时间内,返回正常菜单
293 return MenuSchedule.newBuilder() 295 return MenuSchedule.newBuilder()
294 .setDate(date.format(DateUtil.YYYY_MM_DD_LEFT_SYMBOL)) 296 .setDate(date.format(DateUtil.YYYY_MM_DD_LEFT_SYMBOL))
295 .setMenu("A") 297 .setMenu("A")
296 .build(); 298 .build();
297 }) 299 })
298 - .filter(Objects::nonNull) // 过滤掉null值 300 + .filter(Objects::nonNull)
299 .toList(); 301 .toList();
300 } 302 }
301 303
......
...@@ -6,6 +6,7 @@ import lombok.Builder; ...@@ -6,6 +6,7 @@ import lombok.Builder;
6 import lombok.Data; 6 import lombok.Data;
7 7
8 import java.util.Date; 8 import java.util.Date;
9 +import java.util.List;
9 10
10 @Data 11 @Data
11 @Builder 12 @Builder
...@@ -38,6 +39,13 @@ public class DinerMealSuspensionRecordVO { ...@@ -38,6 +39,13 @@ public class DinerMealSuspensionRecordVO {
38 @ApiModelProperty("学号") 39 @ApiModelProperty("学号")
39 private String studentNo; 40 private String studentNo;
40 41
42 + @ApiModelProperty("停餐时间区间列表")
43 + private List<Interval> intervals;
44 +
45 + @Data
46 + @Builder
47 + @ApiModel("停餐时间区间")
48 + public static class Interval {
41 @ApiModelProperty("开始时间") 49 @ApiModelProperty("开始时间")
42 private Date startAt; 50 private Date startAt;
43 51
...@@ -46,4 +54,5 @@ public class DinerMealSuspensionRecordVO { ...@@ -46,4 +54,5 @@ public class DinerMealSuspensionRecordVO {
46 54
47 @ApiModelProperty("备注") 55 @ApiModelProperty("备注")
48 private String comment; 56 private String comment;
57 + }
49 } 58 }
...\ No newline at end of file ...\ No newline at end of file
......
1 +package com.infoloop.tianting.service;
2 +
3 +import com.infoloop.tianting.mealorderservice.SingleDinerMealSuspensionRecordRpcResponse;
4 +import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse;
5 +
6 +import java.util.List;
7 +import java.util.Map;
8 +
9 +/**
10 + * 公共停餐服务:为单个就餐人提供学校/班级/学生三个维度的最新停餐记录集合
11 + */
12 +public interface SuspensionService {
13 +
14 + /**
15 + * 简单时间窗对象
16 + */
17 + class SuspensionWindow {
18 + public final long startAt;
19 + public final long endAt;
20 +
21 + public SuspensionWindow(long startAt, long endAt) {
22 + this.startAt = startAt;
23 + this.endAt = endAt;
24 + }
25 + }
26 +
27 + /**
28 + * 多段时间窗对象(标准闭区间 \[startAt, endAt\]),用于表示并集后可能出现的多段停餐区间
29 + */
30 + class SuspensionInterval {
31 + public final long startAt;
32 + public final long endAt;
33 +
34 + public SuspensionInterval(long startAt, long endAt) {
35 + this.startAt = startAt;
36 + this.endAt = endAt;
37 + }
38 + }
39 +
40 + /**
41 + * 获取就餐人的停餐记录(学校/班级/学生三个维度各取最新一条),用于做并集判断
42 + *
43 + * @param enterpriseId 企业ID
44 + * @param clientId 学校ID
45 + * @param classId 班级ID
46 + * @param dinerId 就餐人ID
47 + * @return 最多包含3条记录(学校级一条、班级级一条、学生级一条),按存在性返回非空集合
48 + */
49 + List<SingleDinerMealSuspensionRecordRpcResponse> getLatestSuspensionsForDiner(int enterpriseId, int clientId, int classId, int dinerId);
50 +
51 + /**
52 + * 获取就餐人的停餐并集时间窗(取上述三维度最新记录的并集:min(startAt), max(endAt))。
53 + * 若不存在任何停餐记录则返回 null。
54 + */
55 + SuspensionWindow getSuspensionUnionWindowForDiner(int enterpriseId, int clientId, int classId, int dinerId);
56 +
57 + /**
58 + * 为多个订餐人批量计算停餐并集时间窗。避免逐个调用,内部一次性查询并用 Map 聚合。
59 + * 返回的 Map key 为 dinerId,仅为存在停餐窗口的订餐人返回条目。
60 + */
61 + Map<Integer, SuspensionWindow> getSuspensionUnionWindowsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners);
62 +
63 + /**
64 + * 获取单个订餐人的停餐多段时间区间(对学校/班级/学生三个维度的最新记录做标准并集,保留间隙)
65 + */
66 + List<SuspensionInterval> getSuspensionIntervalsForDiner(int enterpriseId, int clientId, int classId, int dinerId);
67 +
68 + /**
69 + * 批量获取多个订餐人的停餐多段时间区间。返回 Map<dinerId, List<Interval>>。
70 + */
71 + Map<Integer, List<SuspensionInterval>> getSuspensionIntervalsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners);
72 +}
73 +
74 +