Jiaqi Xia

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

Dev/nongxin adjust



See merge request !8
......@@ -20,6 +20,12 @@ application {
'-Dlogging.path=logs']
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
ext {
grpcVersion = '1.62.2'
protobufVersion = '3.25.3'
......
......@@ -7,6 +7,7 @@ import com.infoloop.tianting.mealorderservice.*;
import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient;
import com.infoloop.tianting.service.client.DeliveryRuleServiceRpcClient;
import com.infoloop.tianting.service.client.MealOrderServiceRpcClient;
import com.infoloop.tianting.service.SuspensionService;
import com.infoloop.tianting.store.WeeklyTaskStore;
import com.infoloop.tianting.utils.DateUtil;
import lombok.RequiredArgsConstructor;
......@@ -46,6 +47,8 @@ public class AutoCreateMealOrderTask {
private final WeeklyTaskStore weeklyTaskStore;
private final SuspensionService suspensionService;
@Scheduled(cron = "0 0/5 * * * ?")
public void executeTask() {
final int enterpriseId = 449;
......@@ -188,6 +191,7 @@ public class AutoCreateMealOrderTask {
// 构建创建请求
final var creations = notReservedDiners.stream()
.map(diner -> toMealOrderCreation(
enterpriseId,
diner,
clientMap,
gradeClassMap,
......@@ -217,29 +221,32 @@ public class AutoCreateMealOrderTask {
}
}
private MealOrderCreation toMealOrderCreation(SingleDinerRpcResponse diner,
Map<Integer, SingleClientRpcResponse> clientMap,
Map<Integer, SingleGradeClassRpcResponse> classMap,
Map<Integer, SingleMealMenuRecordRpcResponse> menuMap,
Map<Integer, SingleMpAccountDinerRefRpcResponse> mpAccountDinerRefMap,
private MealOrderCreation toMealOrderCreation(int enterpriseId,
SingleDinerRpcResponse diner,
Map<Integer, SingleClientRpcResponse> clientIdToClient,
Map<Integer, SingleGradeClassRpcResponse> classIdToClass,
Map<Integer, SingleMealMenuRecordRpcResponse> clientIdToLatestMenu,
Map<Integer, SingleMpAccountDinerRefRpcResponse> dinerIdToAccountRef,
ZonedDateTime startOfNextWeek,
Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> schoolSuspensionMap,
Map<Integer, SingleDinerMealSuspensionRecordRpcResponse> studentSuspensionMap,
Map<String, Integer> holidayMap) {
final var menu = menuMap.get(diner.getClientId());
final var client = clientMap.get(diner.getClientId());
final var gradeClass = classMap.get(diner.getClassId());
final var mpAccountDinerRef = mpAccountDinerRefMap.get(diner.getId());
Map<String, Integer> dateStrToHolidayStatus) {
final SingleMealMenuRecordRpcResponse menu = clientIdToLatestMenu.get(diner.getClientId());
final SingleClientRpcResponse client = clientIdToClient.get(diner.getClientId());
final SingleGradeClassRpcResponse gradeClass = classIdToClass.get(diner.getClassId());
final SingleMpAccountDinerRefRpcResponse mpAccountDinerRef = dinerIdToAccountRef.get(diner.getId());
if (menu == null || client == null || gradeClass == null) return null;
// 获取停餐信息,优先使用学校级别的
SingleDinerMealSuspensionRecordRpcResponse suspensionRecord = schoolSuspensionMap.get(diner.getClientId());
if (suspensionRecord == null) {
suspensionRecord = studentSuspensionMap.get(diner.getId());
}
// 计算多段停餐区间(学校/班级/学生维度最新记录的并集,保留间隙)
final var intervals = suspensionService.getSuspensionIntervalsForDiner(
enterpriseId,
diner.getClientId(),
diner.getClassId(),
diner.getId()
);
// 构建下周的餐食安排
List<MenuSchedule> meals = buildNextWeekMeals(startOfNextWeek, suspensionRecord, holidayMap);
// 构建下周的餐食安排(基于多段停餐区间)
List<MenuSchedule> meals = buildNextWeekMeals(startOfNextWeek, intervals, dateStrToHolidayStatus);
if (meals.isEmpty()) {
return null;
......@@ -261,41 +268,36 @@ public class AutoCreateMealOrderTask {
.build();
}
private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart,
SingleDinerMealSuspensionRecordRpcResponse suspensionRecord,
Map<String, Integer> holidayMap) {
// 已由并集窗口版本替代
private List<MenuSchedule> buildNextWeekMeals(ZonedDateTime nextWeekStart,
java.util.List<SuspensionService.SuspensionInterval> intervals,
Map<String, Integer> holidayMap) {
return IntStream.range(0, 7)
.mapToObj(i -> {
final var date = nextWeekStart.plusDays(i);
final var dateStr = date.format(DateUtil.YYYY_MM_DD);
// 检查是否为节假日
final var status = holidayMap.get(dateStr);
if (status != null && status != 0 && status != 2) {
// 如果不是工作日,返回null
return null;
}
// 如果有停餐信息,检查当前循环的日期是否在停餐时间范围内
if (suspensionRecord != null) {
if (intervals != null && !intervals.isEmpty()) {
final var dateStart = date.with(LocalTime.MIN).toInstant().toEpochMilli();
final var dateEnd = date.with(LocalTime.MAX).toInstant().toEpochMilli();
final var suspensionStart = suspensionRecord.getStartAt();
final var suspensionEnd = suspensionRecord.getEndAt();
// 如果停餐时间范围与当前日期有重叠,返回null
if (!(dateEnd < suspensionStart || dateStart > suspensionEnd)) {
return null;
}
final boolean overlaps = intervals.stream()
.anyMatch(interval -> !(dateEnd < interval.startAt || dateStart > interval.endAt));
if (overlaps) return null;
}
// 如果是工作日且不在停餐时间内,返回正常菜单
return MenuSchedule.newBuilder()
.setDate(date.format(DateUtil.YYYY_MM_DD_LEFT_SYMBOL))
.setMenu("A")
.build();
})
.filter(Objects::nonNull) // 过滤掉null值
.filter(Objects::nonNull)
.toList();
}
......
......@@ -6,6 +6,7 @@ import lombok.Builder;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@Builder
......@@ -38,12 +39,20 @@ public class DinerMealSuspensionRecordVO {
@ApiModelProperty("学号")
private String studentNo;
@ApiModelProperty("开始时间")
private Date startAt;
@ApiModelProperty("停餐时间区间列表")
private List<Interval> intervals;
@ApiModelProperty("结束时间")
private Date endAt;
@Data
@Builder
@ApiModel("停餐时间区间")
public static class Interval {
@ApiModelProperty("开始时间")
private Date startAt;
@ApiModelProperty("备注")
private String comment;
@ApiModelProperty("结束时间")
private Date endAt;
@ApiModelProperty("备注")
private String comment;
}
}
\ No newline at end of file
......
package com.infoloop.tianting.service;
import com.infoloop.tianting.mealorderservice.SingleDinerMealSuspensionRecordRpcResponse;
import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse;
import java.util.List;
import java.util.Map;
/**
* 公共停餐服务:为单个就餐人提供学校/班级/学生三个维度的最新停餐记录集合
*/
public interface SuspensionService {
/**
* 简单时间窗对象
*/
class SuspensionWindow {
public final long startAt;
public final long endAt;
public SuspensionWindow(long startAt, long endAt) {
this.startAt = startAt;
this.endAt = endAt;
}
}
/**
* 多段时间窗对象(标准闭区间 \[startAt, endAt\]),用于表示并集后可能出现的多段停餐区间
*/
class SuspensionInterval {
public final long startAt;
public final long endAt;
public SuspensionInterval(long startAt, long endAt) {
this.startAt = startAt;
this.endAt = endAt;
}
}
/**
* 获取就餐人的停餐记录(学校/班级/学生三个维度各取最新一条),用于做并集判断
*
* @param enterpriseId 企业ID
* @param clientId 学校ID
* @param classId 班级ID
* @param dinerId 就餐人ID
* @return 最多包含3条记录(学校级一条、班级级一条、学生级一条),按存在性返回非空集合
*/
List<SingleDinerMealSuspensionRecordRpcResponse> getLatestSuspensionsForDiner(int enterpriseId, int clientId, int classId, int dinerId);
/**
* 获取就餐人的停餐并集时间窗(取上述三维度最新记录的并集:min(startAt), max(endAt))。
* 若不存在任何停餐记录则返回 null。
*/
SuspensionWindow getSuspensionUnionWindowForDiner(int enterpriseId, int clientId, int classId, int dinerId);
/**
* 为多个订餐人批量计算停餐并集时间窗。避免逐个调用,内部一次性查询并用 Map 聚合。
* 返回的 Map key 为 dinerId,仅为存在停餐窗口的订餐人返回条目。
*/
Map<Integer, SuspensionWindow> getSuspensionUnionWindowsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners);
/**
* 获取单个订餐人的停餐多段时间区间(对学校/班级/学生三个维度的最新记录做标准并集,保留间隙)
*/
List<SuspensionInterval> getSuspensionIntervalsForDiner(int enterpriseId, int clientId, int classId, int dinerId);
/**
* 批量获取多个订餐人的停餐多段时间区间。返回 Map<dinerId, List<Interval>>。
*/
Map<Integer, List<SuspensionInterval>> getSuspensionIntervalsForDiners(int enterpriseId, List<SingleDinerRpcResponse> diners);
}