|
|
@@ -0,0 +1,158 @@
|
|
|
+package shop.alien.job.store;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import shop.alien.entity.store.StoreOrder;
|
|
|
+import shop.alien.entity.store.UserReservation;
|
|
|
+import shop.alien.entity.store.UserReservationTable;
|
|
|
+import shop.alien.mapper.StoreOrderMapper;
|
|
|
+import shop.alien.mapper.UserReservationMapper;
|
|
|
+import shop.alien.mapper.UserReservationTableMapper;
|
|
|
+
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Calendar;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.TimeZone;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 点餐订单支付成功后,延迟将「已到店」预约改为「用餐结束」,由定时任务触发(非支付回调即时更新)。
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class ReservationDiningFinishedDelayService {
|
|
|
+
|
|
|
+ private final StoreOrderMapper storeOrderMapper;
|
|
|
+ private final UserReservationTableMapper userReservationTableMapper;
|
|
|
+ private final UserReservationMapper userReservationMapper;
|
|
|
+
|
|
|
+ /** 支付成功起算延迟多少分钟后再把预约置为用餐结束 */
|
|
|
+ private static final int DELAY_MINUTES = 20;
|
|
|
+ /** 仅扫描近 N 天内已支付订单,避免历史数据全表扫描 */
|
|
|
+ private static final int PAY_TIME_LOOKBACK_DAYS = 30;
|
|
|
+
|
|
|
+ private static final int USER_RES_STATUS_ARRIVED = 2;
|
|
|
+ private static final int USER_RES_STATUS_DINING_FINISHED = 5;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理:已支付超过 {@link #DELAY_MINUTES} 分钟、且未在历史窗口之外的 store_order。
|
|
|
+ * 若订单含 {@code user_reservation_id},直接更新该预约(须门店一致且状态为已到店);
|
|
|
+ * 否则沿用桌位+「预约日=支付日」推断(兼容旧数据与散客未绑预约订单)。
|
|
|
+ *
|
|
|
+ * @return 本次成功更新的预约条数
|
|
|
+ */
|
|
|
+ public int processEligibleOrders() {
|
|
|
+ Date now = new Date();
|
|
|
+ TimeZone shanghai = TimeZone.getTimeZone("Asia/Shanghai");
|
|
|
+ Calendar cal = Calendar.getInstance(shanghai);
|
|
|
+ cal.setTime(now);
|
|
|
+ cal.add(Calendar.MINUTE, -DELAY_MINUTES);
|
|
|
+ Date cutoff = cal.getTime();
|
|
|
+
|
|
|
+ cal.setTime(now);
|
|
|
+ cal.add(Calendar.DAY_OF_MONTH, -PAY_TIME_LOOKBACK_DAYS);
|
|
|
+ Date oldest = cal.getTime();
|
|
|
+
|
|
|
+ List<StoreOrder> orders = storeOrderMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreOrder>()
|
|
|
+ .eq(StoreOrder::getPayStatus, 1)
|
|
|
+ .eq(StoreOrder::getDeleteFlag, 0)
|
|
|
+ .isNotNull(StoreOrder::getPayTime)
|
|
|
+ .isNotNull(StoreOrder::getStoreId)
|
|
|
+ .and(w -> w.isNotNull(StoreOrder::getUserReservationId)
|
|
|
+ .or()
|
|
|
+ .isNotNull(StoreOrder::getTableId))
|
|
|
+ .le(StoreOrder::getPayTime, cutoff)
|
|
|
+ .ge(StoreOrder::getPayTime, oldest));
|
|
|
+
|
|
|
+ if (orders == null || orders.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ SimpleDateFormat dayFmt = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ dayFmt.setTimeZone(shanghai);
|
|
|
+
|
|
|
+ int updated = 0;
|
|
|
+ for (StoreOrder order : orders) {
|
|
|
+ try {
|
|
|
+ if (order.getPayTime() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (order.getUserReservationId() != null) {
|
|
|
+ updated += finishReservationByOrderBinding(order, now);
|
|
|
+ } else if (order.getTableId() != null) {
|
|
|
+ String payDayStr = dayFmt.format(order.getPayTime());
|
|
|
+ updated += syncUserReservationDiningFinished(
|
|
|
+ order.getStoreId(), order.getTableId(), payDayStr, now, dayFmt);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("延迟标记用餐结束失败 orderId={} err={}", order.getId(), e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return updated;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 订单已绑定 user_reservation_id:仅校验门店与已到店状态后置为用餐结束 */
|
|
|
+ private int finishReservationByOrderBinding(StoreOrder order, Date now) {
|
|
|
+ UserReservation r = userReservationMapper.selectById(order.getUserReservationId());
|
|
|
+ if (r == null || r.getStoreId() == null || !r.getStoreId().equals(order.getStoreId())) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (r.getStatus() == null || r.getStatus() != USER_RES_STATUS_ARRIVED) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ r.setStatus(USER_RES_STATUS_DINING_FINISHED);
|
|
|
+ r.setUpdatedTime(now);
|
|
|
+ userReservationMapper.updateById(r);
|
|
|
+ log.info("延迟任务:按订单绑定更新预约为用餐结束 reservationId={} orderId={}",
|
|
|
+ order.getUserReservationId(), order.getId());
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int syncUserReservationDiningFinished(Integer storeId, Integer storeTableId,
|
|
|
+ String reservationDayYyyyMmDd, Date now,
|
|
|
+ SimpleDateFormat dayFmt) {
|
|
|
+ List<UserReservationTable> links = userReservationTableMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UserReservationTable>()
|
|
|
+ .eq(UserReservationTable::getTableId, storeTableId)
|
|
|
+ .eq(UserReservationTable::getDeleteFlag, 0));
|
|
|
+ if (links == null || links.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ Set<Integer> reservationIds = links.stream()
|
|
|
+ .map(UserReservationTable::getReservationId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toCollection(LinkedHashSet::new));
|
|
|
+
|
|
|
+ int n = 0;
|
|
|
+ for (Integer reservationId : reservationIds) {
|
|
|
+ UserReservation r = userReservationMapper.selectById(reservationId);
|
|
|
+ if (r == null || r.getStoreId() == null || !r.getStoreId().equals(storeId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (r.getStatus() == null || r.getStatus() != USER_RES_STATUS_ARRIVED) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (r.getReservationDate() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!reservationDayYyyyMmDd.equals(dayFmt.format(r.getReservationDate()))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ r.setStatus(USER_RES_STATUS_DINING_FINISHED);
|
|
|
+ r.setUpdatedTime(now);
|
|
|
+ userReservationMapper.updateById(r);
|
|
|
+ n++;
|
|
|
+ log.info("延迟任务:预约已更新为用餐结束 reservationId={} tableId={} matchDay={}",
|
|
|
+ reservationId, storeTableId, reservationDayYyyyMmDd);
|
|
|
+ }
|
|
|
+ return n;
|
|
|
+ }
|
|
|
+}
|