|
@@ -1,5 +1,6 @@
|
|
|
package shop.alien.dining.service.impl;
|
|
package shop.alien.dining.service.impl;
|
|
|
|
|
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
@@ -16,9 +17,19 @@ import shop.alien.mapper.UserReservationMapper;
|
|
|
import shop.alien.mapper.UserReservationTableMapper;
|
|
import shop.alien.mapper.UserReservationTableMapper;
|
|
|
|
|
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.time.LocalTime;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.time.format.DateTimeParseException;
|
|
|
import java.util.Date;
|
|
import java.util.Date;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.HashSet;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.Set;
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 点餐流程:填写就餐信息 → 仅写 user_reservation + user_reservation_table,状态已到店,不产生 user_reservation_order
|
|
* 点餐流程:填写就餐信息 → 仅写 user_reservation + user_reservation_table,状态已到店,不产生 user_reservation_order
|
|
@@ -33,6 +44,11 @@ public class DiningWalkInReservationServiceImpl extends ServiceImpl<UserReservat
|
|
|
private final UserReservationTableMapper userReservationTableMapper;
|
|
private final UserReservationTableMapper userReservationTableMapper;
|
|
|
|
|
|
|
|
private static final int STATUS_ARRIVED = 2;
|
|
private static final int STATUS_ARRIVED = 2;
|
|
|
|
|
+ /** 占桌状态:与其它预订冲突检测范围内 */
|
|
|
|
|
+ private static final ZoneId SHANGHAI = ZoneId.of("Asia/Shanghai");
|
|
|
|
|
+ /** 未填结束时间时,按 N 小时作为占用窗口做冲突判断(与订金预订「结束时间」空值场景对齐) */
|
|
|
|
|
+ private static final int DEFAULT_BLOCK_HOURS_WHEN_NO_END = 4;
|
|
|
|
|
+ private static final DateTimeFormatter HM = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@@ -47,10 +63,17 @@ public class DiningWalkInReservationServiceImpl extends ServiceImpl<UserReservat
|
|
|
|
|
|
|
|
Date reservationDate = dto.getReservationDate();
|
|
Date reservationDate = dto.getReservationDate();
|
|
|
if (reservationDate == null) {
|
|
if (reservationDate == null) {
|
|
|
- ZoneId shanghai = ZoneId.of("Asia/Shanghai");
|
|
|
|
|
- reservationDate = Date.from(LocalDate.now(shanghai).atStartOfDay(shanghai).toInstant());
|
|
|
|
|
|
|
+ reservationDate = Date.from(LocalDate.now(SHANGHAI).atStartOfDay(SHANGHAI).toInstant());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ LocalDate day = reservationDate.toInstant().atZone(SHANGHAI).toLocalDate();
|
|
|
|
|
+ LocalTime newStartLt = parseHm(dto.getStartTime());
|
|
|
|
|
+ if (newStartLt == null) {
|
|
|
|
|
+ throw new RuntimeException("开始时间格式不正确,请使用 HH:mm");
|
|
|
|
|
+ }
|
|
|
|
|
+ LocalTime newEndLt = parseHm(dto.getEndTime());
|
|
|
|
|
+ assertNoTableBookingConflict(table.getId(), table.getStoreId(), day, newStartLt, newEndLt);
|
|
|
|
|
+
|
|
|
Date now = new Date();
|
|
Date now = new Date();
|
|
|
UserReservation entity = new UserReservation();
|
|
UserReservation entity = new UserReservation();
|
|
|
entity.setReservationNo(generateReservationNo());
|
|
entity.setReservationNo(generateReservationNo());
|
|
@@ -98,4 +121,100 @@ public class DiningWalkInReservationServiceImpl extends ServiceImpl<UserReservat
|
|
|
private static String generateReservationNo() {
|
|
private static String generateReservationNo() {
|
|
|
return "RV" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000, 9999);
|
|
return "RV" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000, 9999);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 与其它有效预订(同桌、同日、状态为待确认/已确认/已到店)时段是否重叠;重叠则不允许到店就餐。
|
|
|
|
|
+ */
|
|
|
|
|
+ private void assertNoTableBookingConflict(Integer tableId, Integer storeId, LocalDate day,
|
|
|
|
|
+ LocalTime newStart, LocalTime newEnd) {
|
|
|
|
|
+ List<UserReservationTable> links = userReservationTableMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<UserReservationTable>()
|
|
|
|
|
+ .eq(UserReservationTable::getTableId, tableId)
|
|
|
|
|
+ .eq(UserReservationTable::getDeleteFlag, 0));
|
|
|
|
|
+ if (links == null || links.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Set<Integer> resIds = links.stream()
|
|
|
|
|
+ .map(UserReservationTable::getReservationId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toCollection(HashSet::new));
|
|
|
|
|
+ if (resIds.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ LocalDateTime newStartLdt = LocalDateTime.of(day, newStart);
|
|
|
|
|
+ LocalDateTime newEndLdt = windowEnd(day, newStart, newEnd);
|
|
|
|
|
+
|
|
|
|
|
+ List<UserReservation> others = new ArrayList<>(this.listByIds(resIds));
|
|
|
|
|
+ if (others.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (UserReservation other : others) {
|
|
|
|
|
+ if (other == null || !storeId.equals(other.getStoreId())) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isOccupyingBookingStatus(other.getStatus())) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ LocalDate otherDay = toLocalDate(other.getReservationDate());
|
|
|
|
|
+ if (otherDay == null || !day.equals(otherDay)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ LocalTime oStart = parseHm(other.getStartTime());
|
|
|
|
|
+ if (oStart == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ LocalTime oEnd = parseHm(other.getEndTime());
|
|
|
|
|
+ LocalDateTime oStartLdt = LocalDateTime.of(otherDay, oStart);
|
|
|
|
|
+ LocalDateTime oEndLdt = windowEnd(otherDay, oStart, oEnd);
|
|
|
|
|
+ if (newStartLdt.isBefore(oEndLdt) && oStartLdt.isBefore(newEndLdt)) {
|
|
|
|
|
+ log.warn("到店就餐信息与既有预订冲突 tableId={} day={} new=[{} - {}] otherResId={} other=[{} - {}]",
|
|
|
|
|
+ tableId, day, newStartLdt, newEndLdt, other.getId(), oStartLdt, oEndLdt);
|
|
|
|
|
+ throw new RuntimeException("该时段与已有预订冲突,无法就餐");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 待确认、已确认、已到店视为占用桌位;已取消、未到店超时、用餐结束不占用 */
|
|
|
|
|
+ private static boolean isOccupyingBookingStatus(Integer status) {
|
|
|
|
|
+ return status != null && (status == 0 || status == 1 || status == 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static LocalDate toLocalDate(Date d) {
|
|
|
|
|
+ if (d == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return d.toInstant().atZone(SHANGHAI).toLocalDate();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static LocalTime parseHm(String raw) {
|
|
|
|
|
+ if (!StringUtils.hasText(raw)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String s = raw.trim();
|
|
|
|
|
+ try {
|
|
|
|
|
+ return LocalTime.parse(s, HM);
|
|
|
|
|
+ } catch (DateTimeParseException e) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return LocalTime.parse(s, DateTimeFormatter.ofPattern("H:mm"));
|
|
|
|
|
+ } catch (DateTimeParseException e2) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 预约结束时刻:结束早于或等于开始(同一张日历日上的钟点)则视为跨日至次日;结束时间为空则用默认时长。
|
|
|
|
|
+ */
|
|
|
|
|
+ private static LocalDateTime windowEnd(LocalDate day, LocalTime start, LocalTime end) {
|
|
|
|
|
+ if (end == null) {
|
|
|
|
|
+ return LocalDateTime.of(day, start).plusHours(DEFAULT_BLOCK_HOURS_WHEN_NO_END);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (end.isAfter(start)) {
|
|
|
|
|
+ return LocalDateTime.of(day, end);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (end.equals(start)) {
|
|
|
|
|
+ return LocalDateTime.of(day, start).plusHours(DEFAULT_BLOCK_HOURS_WHEN_NO_END);
|
|
|
|
|
+ }
|
|
|
|
|
+ return LocalDateTime.of(day.plusDays(1), end);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|