|
|
@@ -79,25 +79,45 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
this.reservationOrderPageService = reservationOrderPageService;
|
|
|
}
|
|
|
|
|
|
- /** 预约状态:待确认 */
|
|
|
+ /**
|
|
|
+ * 预约状态:待确认
|
|
|
+ */
|
|
|
private static final int STATUS_PENDING = 0;
|
|
|
- /** 预约状态:已确认(待使用) */
|
|
|
+ /**
|
|
|
+ * 预约状态:已确认(待使用)
|
|
|
+ */
|
|
|
private static final int STATUS_CONFIRMED = 1;
|
|
|
- /** 预约状态:已取消(不参与约满统计与展示) */
|
|
|
+ /**
|
|
|
+ * 预约状态:已取消(不参与约满统计与展示)
|
|
|
+ */
|
|
|
private static final int STATUS_CANCELLED = 3;
|
|
|
- /** 预约状态:未到店超时 */
|
|
|
+ /**
|
|
|
+ * 预约状态:未到店超时
|
|
|
+ */
|
|
|
private static final int STATUS_NO_SHOW_TIMEOUT = 4;
|
|
|
- /** 订单状态:待使用 */
|
|
|
+ /**
|
|
|
+ * 订单状态:待使用
|
|
|
+ */
|
|
|
private static final int ORDER_STATUS_TO_USE = 1;
|
|
|
- /** 订单状态:已过期 */
|
|
|
+ /**
|
|
|
+ * 订单状态:已过期
|
|
|
+ */
|
|
|
private static final int ORDER_STATUS_EXPIRED = 3;
|
|
|
- /** 查找首个未约满日期时,最多往后检查的天数 */
|
|
|
+ /**
|
|
|
+ * 查找首个未约满日期时,最多往后检查的天数
|
|
|
+ */
|
|
|
private static final int MAX_DAYS_TO_CHECK = 366;
|
|
|
- /** 全天预订时使用的结束分钟数(24*60,即到次日0点) */
|
|
|
+ /**
|
|
|
+ * 全天预订时使用的结束分钟数(24*60,即到次日0点)
|
|
|
+ */
|
|
|
private static final int MINUTES_DAY_END = 24 * 60;
|
|
|
- /** 到店提醒 Redis key 前缀,用于防重复发送 */
|
|
|
+ /**
|
|
|
+ * 到店提醒 Redis key 前缀,用于防重复发送
|
|
|
+ */
|
|
|
private static final String REDIS_KEY_ARRIVAL_REMINDER_PREFIX = "reservation:arrival:reminder:order:";
|
|
|
- /** 到店提醒已发送标记的 Redis 过期时间(25 分钟) */
|
|
|
+ /**
|
|
|
+ * 到店提醒已发送标记的 Redis 过期时间(25 分钟)
|
|
|
+ */
|
|
|
private static final long ARRIVAL_REMINDER_SENT_TTL_SECONDS = 30 * 60;
|
|
|
|
|
|
@Override
|
|
|
@@ -411,7 +431,7 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
LambdaQueryWrapper<StoreBookingSettings> eq = new LambdaQueryWrapper<StoreBookingSettings>().eq(StoreBookingSettings::getStoreId, storeId);
|
|
|
List<StoreBookingSettings> storeBookingSettings = storeBookingSettingsService.list(eq);
|
|
|
list.put("storeBookingSettings", storeBookingSettings);
|
|
|
-
|
|
|
+
|
|
|
// 查询营业时间:如果有设置信息,则查询对应的营业时间
|
|
|
// 特殊营业时间通过 essential_id 关联 essential_holiday_comparison 节假日表
|
|
|
List<StoreBookingBusinessHoursVo> storeBookingBusinessHours = new ArrayList<>();
|
|
|
@@ -424,9 +444,9 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
for (StoreBookingBusinessHours businessHours : businessHoursList) {
|
|
|
StoreBookingBusinessHoursVo vo = new StoreBookingBusinessHoursVo();
|
|
|
BeanUtils.copyProperties(businessHours, vo);
|
|
|
-
|
|
|
+
|
|
|
// 如果是特殊营业时间(business_type = 2)且有关联的节假日ID,查询节假日信息
|
|
|
- if (businessHours.getBusinessType() != null && businessHours.getBusinessType() == 2
|
|
|
+ if (businessHours.getBusinessType() != null && businessHours.getBusinessType() == 2
|
|
|
&& businessHours.getEssentialId() != null) {
|
|
|
try {
|
|
|
EssentialHolidayComparison holiday = essentialHolidayComparisonMapper.selectById(businessHours.getEssentialId());
|
|
|
@@ -445,28 +465,28 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
log.warn("查询节假日信息失败,essentialId={}", businessHours.getEssentialId(), e);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 设置 businessDate:优先使用 holidayDate,如果没有则使用 holidayInfo 中的日期
|
|
|
if (vo.getBusinessDate() == null && businessHours.getHolidayDate() != null) {
|
|
|
vo.setBusinessDate(businessHours.getHolidayDate());
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 将 holidayType 的值也放到 businessDate 中
|
|
|
if (businessHours.getHolidayType() != null && !businessHours.getHolidayType().trim().isEmpty()) {
|
|
|
vo.setBusinessDate(businessHours.getHolidayType());
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 将 businessDate 的值也设置到 holidayType(用于回显)
|
|
|
if (vo.getBusinessDate() != null) {
|
|
|
vo.setHolidayType(vo.getBusinessDate());
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
storeBookingBusinessHours.add(vo);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
list.put("storeBookingBusinessHours", storeBookingBusinessHours);
|
|
|
-
|
|
|
+
|
|
|
List<StoreBookingTable> storeBookingTables = storeBookingTableService.list(new LambdaQueryWrapper<StoreBookingTable>().eq(StoreBookingTable::getStoreId, storeId));
|
|
|
list.put("storeBookingTables", storeBookingTables);
|
|
|
List<StoreBookingCategory> storeBookingCategorys = storeBookingCategoryService.list(
|
|
|
@@ -518,7 +538,9 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /** 商户营业时间类型:正常营业时间 */
|
|
|
+ /**
|
|
|
+ * 商户营业时间类型:正常营业时间
|
|
|
+ */
|
|
|
private static final int BUSINESS_TYPE_NORMAL = 1;
|
|
|
|
|
|
/**
|
|
|
@@ -527,19 +549,25 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
*/
|
|
|
private int[] getBookingRangeMinutes(Integer storeId) {
|
|
|
int[] range = new int[]{0, MINUTES_DAY_END};
|
|
|
+ // 先从 store_booking_settings 按 storeId 查设置,再用 settingsId 关联
|
|
|
List<StoreBookingSettings> list = storeBookingSettingsService.list(
|
|
|
new LambdaQueryWrapper<StoreBookingSettings>().eq(StoreBookingSettings::getStoreId, storeId));
|
|
|
if (!list.isEmpty()) {
|
|
|
StoreBookingSettings settings = list.get(0);
|
|
|
- if (settings.getBookingTimeType() != null && settings.getBookingTimeType() == 1) {
|
|
|
- return range;
|
|
|
- }
|
|
|
- int start = timeToMinutes(settings.getBookingStartTime());
|
|
|
- int end = timeToMinutes(settings.getBookingEndTime());
|
|
|
- if (start >= 0 && end > start) {
|
|
|
- range[0] = start;
|
|
|
- range[1] = end;
|
|
|
- return range;
|
|
|
+ List<StoreBookingBusinessHours> businessHoursList = storeBookingBusinessHoursService.getListBySettingsId(settings.getId());
|
|
|
+ if (!businessHoursList.isEmpty()) {
|
|
|
+ StoreBookingBusinessHours businessHours = businessHoursList.get(0);
|
|
|
+
|
|
|
+ if (businessHours.getBookingTimeType() != null && businessHours.getBookingTimeType() == 1) {
|
|
|
+ return range;
|
|
|
+ }
|
|
|
+ int start = timeToMinutes(businessHours.getStartTime());
|
|
|
+ int end = timeToMinutes(businessHours.getEndTime());
|
|
|
+ if (start >= 0 && end > start) {
|
|
|
+ range[0] = start;
|
|
|
+ range[1] = end;
|
|
|
+ return range;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
// 预订开始/结束时间为空或无效时,取商户运营时间(营业时间)
|
|
|
@@ -791,7 +819,7 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
}
|
|
|
|
|
|
private LambdaQueryWrapper<UserReservation> buildListWrapper(Integer userId, Integer storeId, Integer status,
|
|
|
- Date dateFrom, Date dateTo) {
|
|
|
+ Date dateFrom, Date dateTo) {
|
|
|
LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();
|
|
|
if (userId != null) {
|
|
|
wrapper.eq(UserReservation::getUserId, userId);
|
|
|
@@ -827,7 +855,9 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
return list.stream().map(UserReservationTable::getTableId).collect(Collectors.toList());
|
|
|
}
|
|
|
|
|
|
- /** 将桌位ID列表转为桌号文案,如 "A01,A02",空或查不到为 "未知桌号" */
|
|
|
+ /**
|
|
|
+ * 将桌位ID列表转为桌号文案,如 "A01,A02",空或查不到为 "未知桌号"
|
|
|
+ */
|
|
|
private String tableIdsToTableNumberString(List<Integer> tableIds) {
|
|
|
if (tableIds == null || tableIds.isEmpty()) {
|
|
|
return "未知桌号";
|
|
|
@@ -863,38 +893,38 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
public List<StoreReservationListVo> getStoreReservationList(Integer storeId, Integer status, Date dateFrom, Date dateTo, Integer orderStatus) {
|
|
|
log.info("UserReservationServiceImpl.getStoreReservationList?storeId={}, status={}, dateFrom={}, dateTo={}, orderStatus={}",
|
|
|
storeId, status, dateFrom, dateTo, orderStatus);
|
|
|
-
|
|
|
+
|
|
|
if (storeId == null) {
|
|
|
throw new RuntimeException("门店ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return baseMapper.getStoreReservationList(storeId, status, dateFrom, dateTo, orderStatus);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean cancelReservationByStore(Integer reservationId) {
|
|
|
log.info("UserReservationServiceImpl.cancelReservationByStore?reservationId={}", reservationId);
|
|
|
-
|
|
|
+
|
|
|
if (reservationId == null) {
|
|
|
throw new RuntimeException("预约ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 查询预约信息
|
|
|
UserReservation reservation = this.getById(reservationId);
|
|
|
if (reservation == null) {
|
|
|
throw new RuntimeException("预约不存在");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 检查预约状态,已取消的不能再次取消
|
|
|
if (reservation.getStatus() != null && reservation.getStatus() == STATUS_CANCELLED) {
|
|
|
throw new RuntimeException("预约已取消,不能重复取消");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 查询关联的订单信息
|
|
|
LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
|
|
|
UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);
|
|
|
-
|
|
|
+
|
|
|
if (order == null) {
|
|
|
// 如果没有订单,直接更新预约状态为3(已取消)
|
|
|
reservation.setStatus(STATUS_CANCELLED); // STATUS_CANCELLED = 3
|
|
|
@@ -905,14 +935,14 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
log.info("商家端取消预约成功(无订单),reservationId={}", reservationId);
|
|
|
return true;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 判断订单费用类型:0-免费, 1-收费
|
|
|
Integer orderCostType = order.getOrderCostType();
|
|
|
if (orderCostType == null) {
|
|
|
// 如果订单费用类型为空,默认按免费处理
|
|
|
orderCostType = 0;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (orderCostType == 0) {
|
|
|
// 免费订单:更新订单状态为4(已取消),更新预约状态为3(已取消)
|
|
|
order.setOrderStatus(4); // 4:已取消
|
|
|
@@ -920,13 +950,13 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
if (!orderUpdateResult) {
|
|
|
throw new RuntimeException("更新订单状态失败");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
reservation.setStatus(STATUS_CANCELLED); // STATUS_CANCELLED = 3:已取消
|
|
|
boolean reservationUpdateResult = this.updateById(reservation);
|
|
|
if (!reservationUpdateResult) {
|
|
|
throw new RuntimeException("更新预约状态失败");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
log.info("商家端取消预约成功(免费订单),reservationId={}, orderId={}", reservationId, order.getId());
|
|
|
return true;
|
|
|
} else if (orderCostType == 1) {
|
|
|
@@ -940,22 +970,22 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
@Override
|
|
|
public boolean deleteReservationByStore(Integer reservationId) {
|
|
|
log.info("UserReservationServiceImpl.deleteReservationByStore?reservationId={}", reservationId);
|
|
|
-
|
|
|
+
|
|
|
if (reservationId == null) {
|
|
|
throw new RuntimeException("预约ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 查询预约信息
|
|
|
UserReservation reservation = this.getById(reservationId);
|
|
|
if (reservation == null) {
|
|
|
throw new RuntimeException("预约不存在");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 查询关联的订单信息
|
|
|
LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
|
|
|
UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);
|
|
|
-
|
|
|
+
|
|
|
if (order == null) {
|
|
|
// 如果没有订单,直接删除预约记录
|
|
|
boolean deleteResult = this.removeById(reservationId);
|
|
|
@@ -965,34 +995,34 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
log.info("商家端删除预订信息成功(无订单),reservationId={}", reservationId);
|
|
|
return true;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 判断订单状态:只有已取消(4)、已退款(7)、已完成(2)状态才能删除
|
|
|
Integer orderStatus = order.getOrderStatus();
|
|
|
if (orderStatus == null) {
|
|
|
throw new RuntimeException("订单状态异常,无法删除");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 定义可删除的订单状态:2:已完成, 4:已取消, 7:已退款
|
|
|
boolean canDelete = orderStatus == 2 || orderStatus == 4 || orderStatus == 7;
|
|
|
-
|
|
|
+
|
|
|
if (!canDelete) {
|
|
|
String statusText = getOrderStatusText(orderStatus);
|
|
|
throw new RuntimeException("订单状态为" + statusText + ",不允许删除。只有已取消、已退款、已完成状态的订单可以删除");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 删除订单记录(逻辑删除)
|
|
|
boolean orderDeleteResult = userReservationOrderService.removeById(order.getId());
|
|
|
if (!orderDeleteResult) {
|
|
|
throw new RuntimeException("删除订单记录失败");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 删除预约记录(逻辑删除)
|
|
|
boolean reservationDeleteResult = this.removeById(reservationId);
|
|
|
if (!reservationDeleteResult) {
|
|
|
throw new RuntimeException("删除预约记录失败");
|
|
|
}
|
|
|
-
|
|
|
- log.info("商家端删除预订信息成功,reservationId={}, orderId={}, orderStatus={}",
|
|
|
+
|
|
|
+ log.info("商家端删除预订信息成功,reservationId={}, orderId={}, orderStatus={}",
|
|
|
reservationId, order.getId(), orderStatus);
|
|
|
return true;
|
|
|
}
|
|
|
@@ -1005,61 +1035,71 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
return "未知";
|
|
|
}
|
|
|
switch (orderStatus) {
|
|
|
- case 0: return "待支付";
|
|
|
- case 1: return "待使用";
|
|
|
- case 2: return "已完成";
|
|
|
- case 3: return "已过期";
|
|
|
- case 4: return "已取消";
|
|
|
- case 5: return "已关闭";
|
|
|
- case 6: return "退款中";
|
|
|
- case 7: return "已退款";
|
|
|
- case 8: return "商家预订";
|
|
|
- default: return "未知";
|
|
|
+ case 0:
|
|
|
+ return "待支付";
|
|
|
+ case 1:
|
|
|
+ return "待使用";
|
|
|
+ case 2:
|
|
|
+ return "已完成";
|
|
|
+ case 3:
|
|
|
+ return "已过期";
|
|
|
+ case 4:
|
|
|
+ return "已取消";
|
|
|
+ case 5:
|
|
|
+ return "已关闭";
|
|
|
+ case 6:
|
|
|
+ return "退款中";
|
|
|
+ case 7:
|
|
|
+ return "已退款";
|
|
|
+ case 8:
|
|
|
+ return "商家预订";
|
|
|
+ default:
|
|
|
+ return "未知";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean addTimeByStore(Integer reservationId, String addTimeStart, Integer addTimeMinutes) {
|
|
|
- log.info("UserReservationServiceImpl.addTimeByStore?reservationId={}, addTimeStart={}, addTimeMinutes={}",
|
|
|
+ log.info("UserReservationServiceImpl.addTimeByStore?reservationId={}, addTimeStart={}, addTimeMinutes={}",
|
|
|
reservationId, addTimeStart, addTimeMinutes);
|
|
|
-
|
|
|
+
|
|
|
if (reservationId == null) {
|
|
|
throw new RuntimeException("预约ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (addTimeStart == null || addTimeStart.trim().isEmpty()) {
|
|
|
throw new RuntimeException("加时开始时间不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (addTimeMinutes == null || addTimeMinutes <= 0) {
|
|
|
throw new RuntimeException("加时分钟数必须大于0");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 验证时间格式 HH:mm
|
|
|
if (!addTimeStart.matches("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$")) {
|
|
|
throw new RuntimeException("加时开始时间格式错误,应为HH:mm格式");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 查询预约信息
|
|
|
UserReservation reservation = this.getById(reservationId);
|
|
|
if (reservation == null) {
|
|
|
throw new RuntimeException("预约不存在");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 保存原结束时间用于日志
|
|
|
String oldEndTime = reservation.getEndTime();
|
|
|
-
|
|
|
+
|
|
|
// 计算新的结束时间:加时开始时间 + 加时分钟数
|
|
|
String newEndTime = calculateNewEndTime(addTimeStart, addTimeMinutes);
|
|
|
-
|
|
|
+
|
|
|
// 更新预约结束时间
|
|
|
reservation.setEndTime(newEndTime);
|
|
|
boolean updateResult = this.updateById(reservation);
|
|
|
if (!updateResult) {
|
|
|
throw new RuntimeException("更新预约结束时间失败");
|
|
|
}
|
|
|
-
|
|
|
- log.info("商家端加时成功,reservationId={}, 原结束时间={}, 加时开始时间={}, 加时分钟数={}, 新结束时间={}",
|
|
|
+
|
|
|
+ log.info("商家端加时成功,reservationId={}, 原结束时间={}, 加时开始时间={}, 加时分钟数={}, 新结束时间={}",
|
|
|
reservationId, oldEndTime, addTimeStart, addTimeMinutes, newEndTime);
|
|
|
return true;
|
|
|
}
|
|
|
@@ -1067,7 +1107,7 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
/**
|
|
|
* 计算新的结束时间:加时开始时间 + 加时分钟数
|
|
|
*
|
|
|
- * @param addTimeStart 加时开始时间(HH:mm格式)
|
|
|
+ * @param addTimeStart 加时开始时间(HH:mm格式)
|
|
|
* @param addTimeMinutes 加时分钟数
|
|
|
* @return 新的结束时间(HH:mm格式)
|
|
|
*/
|
|
|
@@ -1076,12 +1116,12 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
// 解析加时开始时间
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
|
|
|
Date startDate = sdf.parse(addTimeStart);
|
|
|
-
|
|
|
+
|
|
|
// 加上加时分钟数
|
|
|
Calendar calendar = Calendar.getInstance();
|
|
|
calendar.setTime(startDate);
|
|
|
calendar.add(Calendar.MINUTE, addTimeMinutes);
|
|
|
-
|
|
|
+
|
|
|
// 格式化为HH:mm
|
|
|
return sdf.format(calendar.getTime());
|
|
|
} catch (ParseException e) {
|
|
|
@@ -1248,7 +1288,9 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- /** 将页面 VO 字段复制到详情 VO,供前端订单详情页与 page 接口一致展示 */
|
|
|
+ /**
|
|
|
+ * 将页面 VO 字段复制到详情 VO,供前端订单详情页与 page 接口一致展示
|
|
|
+ */
|
|
|
private void copyPageVoToDetailVo(ReservationOrderPageVo page, ReservationOrderDetailVo detail) {
|
|
|
detail.setOrderId(page.getOrderId());
|
|
|
detail.setOrderSn(page.getOrderSn());
|