Przeglądaj źródła

Merge branch 'sit' into uat-20260202

dujian 1 miesiąc temu
rodzic
commit
68fe0390ad

+ 3 - 1
alien-entity/src/main/java/shop/alien/mapper/UserReservationMapper.java

@@ -42,7 +42,9 @@ public interface UserReservationMapper extends BaseMapper<UserReservation> {
     int physicalDeleteById(@Param("id") Integer id);
 
     /**
-     * 关联查询:订单为待使用且预约结束时间已过的 reservation_id 列表。
+     * 关联查询:订单为待使用(1),且已超过未到店截止时间的 reservation_id 列表。
+     * 当门店 store_booking_settings.retain_position_flag = 1 时:截止时间 = start_time + retention_duration(分钟);
+     * 否则:仅按 start_time 判断超时。
      * 用于定时任务将「未到店超时」的预订与订单批量更新。
      *
      * @return 需要标记为已过期/未到店超时的 user_reservation.id 列表

+ 32 - 4
alien-entity/src/main/resources/mapper/UserReservationMapper.xml

@@ -139,16 +139,44 @@
         DELETE FROM user_reservation WHERE id = #{id}
     </delete>
 
-    <!-- 关联查询:订单待使用 + 预约结束时间已过,仅返回 reservation_id。end_time 格式为 yyyy-MM-dd HH:mm -->
+    <!--
+      未到店超时:订单待使用(1) + 预约待确认/已确认(0,1)
+      - retain_position_flag = 1:截止时间 = start_time + retention_duration(分钟),NULL 时长按 0
+      - 否则:仅按 start_time 判断(当前时间已超过预约开始时间即超时)
+      start_time 格式 yyyy-MM-dd HH:mm
+    -->
     <select id="listReservationIdsForTimeoutMark" resultType="java.lang.Integer">
-        SELECT DISTINCT r.id
+        SELECT r.id
         FROM user_reservation r
         INNER JOIN user_reservation_order o ON o.reservation_id = r.id AND o.delete_flag = 0
+        LEFT JOIN (
+            SELECT s1.store_id, s1.retain_position_flag, s1.retention_duration
+            FROM store_booking_settings s1
+            INNER JOIN (
+                SELECT store_id, MAX(id) AS max_id
+                FROM store_booking_settings
+                WHERE delete_flag = 0
+                GROUP BY store_id
+            ) sm ON sm.max_id = s1.id AND sm.store_id = s1.store_id
+        ) bs ON bs.store_id = r.store_id
         WHERE r.delete_flag = 0
           AND o.order_status = 1
           AND r.status IN (0, 1)
-          AND r.end_time IS NOT NULL AND TRIM(r.end_time) != ''
-          AND STR_TO_DATE(TRIM(r.end_time), '%Y-%m-%d %H:%i') &lt; NOW()
+          AND r.start_time IS NOT NULL AND TRIM(r.start_time) != ''
+          AND (
+              (
+                  bs.retain_position_flag = 1
+                  AND DATE_ADD(
+                        STR_TO_DATE(TRIM(r.start_time), '%Y-%m-%d %H:%i'),
+                        INTERVAL COALESCE(bs.retention_duration, 0) MINUTE
+                      ) &lt; NOW()
+              )
+              OR
+              (
+                  (bs.retain_position_flag IS NULL OR bs.retain_position_flag != 1)
+                  AND STR_TO_DATE(TRIM(r.start_time), '%Y-%m-%d %H:%i') &lt; NOW()
+              )
+          )
     </select>
 
     <!-- 查询分类下是否有符合条件的预订信息

+ 1 - 1
alien-job/src/main/java/shop/alien/job/feign/AlienStoreFeign.java

@@ -70,7 +70,7 @@ public interface AlienStoreFeign {
     public R refunds(@RequestBody Map<String, String> params);
 
     /**
-     * 预订未到店超时定时任务:将 end_time 已过且订单状态为待使用的预订标记为已过期/未到店超时
+     * 预订未到店超时:retain_position_flag=1 时用 start_time+retention_duration,否则仅用 start_time;订单待使用则标记过期
      *
      * @return R.data 为本次更新的预约数量
      */

+ 2 - 1
alien-job/src/main/java/shop/alien/job/store/ReservationTimeoutJob.java

@@ -9,7 +9,8 @@ import shop.alien.job.feign.AlienStoreFeign;
 
 /**
  * 预订未到店超时定时任务
- * 将 user_reservation.end_time 小于当前时间且对应 user_reservation_order 订单状态为「待使用」的预订:
+ * retain_position_flag=1:start_time + retention_duration(分钟)早于当前时间;
+ * 否则仅 start_time 早于当前时间;且 user_reservation_order 为「待使用」的预订:
  * - user_reservation.status 置为 4(未到店超时)
  * - user_reservation_order.order_status 置为 3(已过期)
  */

+ 1 - 1
alien-store/src/main/java/shop/alien/store/controller/ReservationJobController.java

@@ -24,7 +24,7 @@ public class ReservationJobController {
     private final UserReservationService userReservationService;
     private final StoreReservationService storeReservationService;
 
-    @ApiOperation("标记「结束时间已过且订单待使用」的预订为未到店超时/已过期")
+    @ApiOperation("未到店超时:保留位置=开始时间+保留分钟,否则仅开始时间;订单待使用则标记已过期")
     @PostMapping("/markTimeout")
     public R<Integer> markReservationTimeoutByEndTime() {
         log.info("reservation job: markTimeout 开始");

+ 2 - 1
alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java

@@ -658,9 +658,10 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
                 storeUserId = String.valueOf(lifeUser1.getId());
             } else {
                 StoreUser storeUser1 = storeUserService.getOne(new QueryWrapper<StoreUser>().eq("phone", phoneId1.substring(6)));
-                storeUserId = String.valueOf(storeUser1.getStoreId());
+                storeUserId = String.valueOf(storeUser1.getId());
             }
             dynamicsVo.setStoreUserId(storeUserId);
+            dynamicsVo.setStoreOrUserId(storeUserId);
         }
 
         // 好友获赞数量

+ 2 - 1
alien-store/src/main/java/shop/alien/store/service/UserReservationService.java

@@ -184,7 +184,8 @@ public interface UserReservationService extends IService<UserReservation> {
     ReservationOrderDetailVo getOrderDetailByOrderId(Integer orderId, String jingdu, String weidu);
 
     /**
-     * 定时任务:将「预约结束时间已过」且「订单状态为待使用」的预订标记为已过期/未到店超时。
+     * 定时任务:将超过未到店截止时间且「订单状态为待使用」的预订标记为已过期/未到店超时。
+     * retain_position_flag=1:截止 = start_time + retention_duration;否则仅按 start_time 判断超时。
      * 更新:user_reservation.status = 4(未到店超时),user_reservation_order.order_status = 3(已过期)。
      *
      * @return 本次更新的预约数量(即更新的 order 数量)

+ 52 - 8
alien-store/src/main/java/shop/alien/store/service/impl/StoreBookingCategoryServiceImpl.java

@@ -6,17 +6,19 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 import shop.alien.entity.store.StoreBookingCategory;
+import shop.alien.entity.store.StoreBookingSettings;
 import shop.alien.entity.store.StoreBookingTable;
 import shop.alien.entity.store.UserReservation;
 import shop.alien.mapper.StoreBookingCategoryMapper;
 import shop.alien.mapper.UserReservationMapper;
 import shop.alien.store.service.StoreBookingCategoryService;
+import shop.alien.store.service.StoreBookingSettingsService;
 import shop.alien.store.service.StoreBookingTableService;
 import shop.alien.util.common.JwtUtil;
 
@@ -31,11 +33,20 @@ import java.util.List;
 @Slf4j
 @Service
 @Transactional
-@RequiredArgsConstructor
 public class StoreBookingCategoryServiceImpl extends ServiceImpl<StoreBookingCategoryMapper, StoreBookingCategory> implements StoreBookingCategoryService {
 
     private final StoreBookingTableService storeBookingTableService;
     private final UserReservationMapper userReservationMapper;
+    private final StoreBookingSettingsService storeBookingSettingsService;
+
+    public StoreBookingCategoryServiceImpl(
+            StoreBookingTableService storeBookingTableService,
+            UserReservationMapper userReservationMapper,
+            @Lazy StoreBookingSettingsService storeBookingSettingsService) {
+        this.storeBookingTableService = storeBookingTableService;
+        this.userReservationMapper = userReservationMapper;
+        this.storeBookingSettingsService = storeBookingSettingsService;
+    }
 
     @Override
     public List<StoreBookingCategory> getCategoryList(Integer storeId) {
@@ -92,6 +103,23 @@ public class StoreBookingCategoryServiceImpl extends ServiceImpl<StoreBookingCat
             throw new RuntimeException("最长预订时间必须大于0");
         }
         
+        // 校验:如果当前门店有信息设置,分类中的最长预订时间要超过信息设置的保留时间
+        // 如果没有信息设置,分类的最长预订时间可随意设置
+        StoreBookingSettings settings = storeBookingSettingsService.getByStoreId(category.getStoreId());
+        if (settings != null) {
+            // 如果门店有信息设置,且设置了保留位置(retain_position_flag = 1)
+            if (settings.getRetainPositionFlag() != null && settings.getRetainPositionFlag() == 1) {
+                // 如果设置了保留时长,则分类的最长预订时间要超过保留时长
+                if (settings.getRetentionDuration() != null && settings.getRetentionDuration() > 0) {
+                    if (category.getMaxBookingTime() <= settings.getRetentionDuration()) {
+                        log.warn("新增预订服务分类失败:分类的最长预订时间要超过信息设置的保留时间,maxBookingTime={}, retentionDuration={}", 
+                                category.getMaxBookingTime(), settings.getRetentionDuration());
+                        throw new RuntimeException("分类的最长预订时间要超过信息设置的保留时间(" + settings.getRetentionDuration() + "分钟)");
+                    }
+                }
+            }
+        }
+        
         // 验证图片数量(最多9张)
         String[] images = category.getFloorPlanImages().split(",");
         if (images.length > 9) {
@@ -230,16 +258,32 @@ public class StoreBookingCategoryServiceImpl extends ServiceImpl<StoreBookingCat
             throw new RuntimeException("分类不存在");
         }
         
-        // 校验:检查当前分类下是否有桌号,如果有则不允许删除
+        // 查询当前分类下的所有桌号
         LambdaQueryWrapper<StoreBookingTable> tableWrapper = new LambdaQueryWrapper<>();
         tableWrapper.eq(StoreBookingTable::getCategoryId, id);
-        long tableCount = storeBookingTableService.count(tableWrapper);
-        if (tableCount > 0) {
-            log.warn("删除预订服务分类失败:当前分类下存在桌号,需要先删除桌号才能删除该分类,categoryId={}, tableCount={}", id, tableCount);
-            throw new RuntimeException("当前分类下存在桌号,需要先删除桌号才能删除该分类");
+        // @TableLogic 会自动过滤已删除的记录(delete_flag=0)
+        List<StoreBookingTable> tables = storeBookingTableService.list(tableWrapper);
+        
+        // 如果存在桌号,先批量删除这些桌号
+        if (tables != null && !tables.isEmpty()) {
+            List<Integer> tableIds = tables.stream()
+                    .map(StoreBookingTable::getId)
+                    .collect(java.util.stream.Collectors.toList());
+            
+            log.info("删除分类时同时删除该分类下的所有桌号,categoryId={}, tableCount={}, tableIds={}", 
+                    id, tables.size(), tableIds);
+            
+            // 批量逻辑删除桌号
+            boolean deleteTablesResult = storeBookingTableService.removeByIds(tableIds);
+            if (!deleteTablesResult) {
+                log.warn("删除分类下的桌号失败,categoryId={}, tableIds={}", id, tableIds);
+                throw new RuntimeException("删除分类下的桌号失败");
+            }
+            
+            log.info("成功删除分类下的所有桌号,categoryId={}, tableCount={}", id, tables.size());
         }
         
-        // 逻辑删除
+        // 逻辑删除分类
         return this.removeById(id);
     }
 

+ 39 - 2
alien-store/src/main/java/shop/alien/store/service/impl/StoreBookingSettingsServiceImpl.java

@@ -4,14 +4,15 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 import shop.alien.entity.store.EssentialHolidayComparison;
 import shop.alien.entity.store.StoreBookingBusinessHours;
+import shop.alien.entity.store.StoreBookingCategory;
 import shop.alien.entity.store.StoreBookingSettings;
 import shop.alien.entity.store.StoreBusinessInfo;
 import shop.alien.entity.store.dto.StoreBookingBusinessHoursDTO;
@@ -20,6 +21,7 @@ import shop.alien.mapper.EssentialHolidayComparisonMapper;
 import shop.alien.mapper.StoreBookingSettingsMapper;
 import shop.alien.mapper.StoreBusinessInfoMapper;
 import shop.alien.store.service.StoreBookingBusinessHoursService;
+import shop.alien.store.service.StoreBookingCategoryService;
 import shop.alien.store.service.StoreBookingSettingsService;
 import shop.alien.util.common.JwtUtil;
 
@@ -37,12 +39,23 @@ import java.util.regex.Pattern;
 @Slf4j
 @Service
 @Transactional
-@RequiredArgsConstructor
 public class StoreBookingSettingsServiceImpl extends ServiceImpl<StoreBookingSettingsMapper, StoreBookingSettings> implements StoreBookingSettingsService {
 
     private final StoreBookingBusinessHoursService storeBookingBusinessHoursService;
     private final EssentialHolidayComparisonMapper essentialHolidayComparisonMapper;
     private final StoreBusinessInfoMapper storeBusinessInfoMapper;
+    private final StoreBookingCategoryService storeBookingCategoryService;
+
+    public StoreBookingSettingsServiceImpl(
+            StoreBookingBusinessHoursService storeBookingBusinessHoursService,
+            EssentialHolidayComparisonMapper essentialHolidayComparisonMapper,
+            StoreBusinessInfoMapper storeBusinessInfoMapper,
+            @Lazy StoreBookingCategoryService storeBookingCategoryService) {
+        this.storeBookingBusinessHoursService = storeBookingBusinessHoursService;
+        this.essentialHolidayComparisonMapper = essentialHolidayComparisonMapper;
+        this.storeBusinessInfoMapper = storeBusinessInfoMapper;
+        this.storeBookingCategoryService = storeBookingCategoryService;
+    }
 
     // 时间格式正则:HH:mm
     private static final Pattern TIME_PATTERN = Pattern.compile("^([0-1][0-9]|2[0-3]):[0-5][0-9]$");
@@ -185,6 +198,30 @@ public class StoreBookingSettingsServiceImpl extends ServiceImpl<StoreBookingSet
     public boolean saveSettingsWithBusinessHours(StoreBookingSettingsDTO dto) {
         log.info("StoreBookingSettingsServiceImpl.saveSettingsWithBusinessHours?dto={}", dto);
         
+        // 校验:如果有保留时长(retain_position_flag = 1),保留时长不能超过分类中的最短最长预订时间
+        // 如果当前门店没有分类,那保留时长可随意设置
+        if (dto.getRetainPositionFlag() != null && dto.getRetainPositionFlag() == 1) {
+            if (dto.getRetentionDuration() != null && dto.getRetentionDuration() > 0) {
+                // 查询该门店下所有分类的最短最长预订时间(最小值)
+                List<StoreBookingCategory> categories = storeBookingCategoryService.getCategoryList(dto.getStoreId());
+                // 如果门店有分类,才进行校验
+                if (categories != null && !categories.isEmpty()) {
+                    Integer minMaxBookingTime = categories.stream()
+                            .filter(c -> c.getMaxBookingTime() != null && c.getMaxBookingTime() > 0)
+                            .map(StoreBookingCategory::getMaxBookingTime)
+                            .min(Integer::compareTo)
+                            .orElse(null);
+                    
+                    if (minMaxBookingTime != null && dto.getRetentionDuration() > minMaxBookingTime) {
+                        log.warn("保留时长不能超过最长预订时长,retentionDuration={}, minMaxBookingTime={}", 
+                                dto.getRetentionDuration(), minMaxBookingTime);
+                        throw new RuntimeException("保留时长不能超过最长预订时长");
+                    }
+                }
+                // 如果门店没有分类,跳过校验,保留时长可随意设置
+            }
+        }
+        
         // 1. 先保存或更新设置信息
         StoreBookingSettings settings = new StoreBookingSettings();
         settings.setId(dto.getId());

+ 55 - 8
alien-store/src/main/java/shop/alien/store/service/impl/StoreReservationServiceImpl.java

@@ -590,7 +590,7 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
         validateAddTimeNotExceedNextReservation(reservation, normalizedAddTimeStart, newEndTime);
 
         // 校验:不能超过营业时间的结束时间
-        validateAddTimeNotExceedBusinessHours(reservation, newEndTime);
+        validateAddTimeNotExceedBusinessHours(reservation, normalizedAddTimeStart, newEndTime);
 
         // 更新预约结束时间
         reservation.setEndTime(newEndTime);
@@ -761,11 +761,11 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             queryBaseTime = addTimeStart;
         }
 
-        // 查询同一门店、同一日期、状态为已确认(status=1)的预约
+        // 查询同一门店、同一日期、状态为已确认(status=1 2)的预约
         LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(UserReservation::getStoreId, reservation.getStoreId())
                 .apply("DATE(user_reservation.reservation_date) = DATE({0})", reservation.getReservationDate())
-                .eq(UserReservation::getStatus, 1) // 已确认
+                .in(UserReservation::getStatus,1,2)
                 .ge(UserReservation::getStartTime, queryBaseTime)
                 .ne(UserReservation::getId, reservation.getId())
                 .eq(UserReservation::getDeleteFlag, 0)
@@ -776,9 +776,9 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
 
         if (nextReservation != null) {
             if (compareTime(newEndTime, nextReservation.getStartTime()) > 0) {
-                throw new RuntimeException(
-                        String.format("新的结束时间 %s 超过了下一个已确认预约的开始时间 %s(预约号:%s)",
-                                newEndTime, nextReservation.getStartTime(), nextReservation.getReservationNo()));
+                // 计算最多可加时的分钟数
+                int maxAddTimeMinutes = calculateMaxAddTimeMinutes(queryBaseTime, nextReservation.getStartTime());
+                throw new RuntimeException("最多可加时" + maxAddTimeMinutes + "分钟");
             }
         }
     }
@@ -843,6 +843,44 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     }
 
     /**
+     * 计算两个时间之间的分钟差(最多可加时的分钟数)
+     * @param startTime 开始时间(支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm 格式)
+     * @param endTime 结束时间(支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm 格式)
+     * @return 分钟差,如果结束时间早于开始时间返回0
+     */
+    private int calculateMaxAddTimeMinutes(String startTime, String endTime) {
+        try {
+            // 先标准化时间格式
+            String normalizedStartTime = normalizeDateTime(startTime);
+            String normalizedEndTime = normalizeDateTime(endTime);
+            
+            if (normalizedStartTime == null || normalizedEndTime == null) {
+                log.warn("时间格式不正确,无法计算分钟差,startTime={}, endTime={}", startTime, endTime);
+                return 0;
+            }
+            
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            sdf.setLenient(false);
+            Date startDate = sdf.parse(normalizedStartTime);
+            Date endDate = sdf.parse(normalizedEndTime);
+            
+            // 如果结束时间早于开始时间,返回0
+            if (endDate.before(startDate)) {
+                return 0;
+            }
+            
+            // 计算分钟差
+            long diffInMillis = endDate.getTime() - startDate.getTime();
+            int minutes = (int) (diffInMillis / (1000 * 60));
+            
+            return minutes;
+        } catch (ParseException e) {
+            log.error("计算最多可加时分钟数失败,startTime={}, endTime={}", startTime, endTime, e);
+            return 0;
+        }
+    }
+
+    /**
      * 计算新的结束时间:加时开始时间 + 加时分钟数
      * 返回格式:yyyy-MM-dd HH:mm(不含秒)
      */
@@ -893,7 +931,7 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
      * @param reservation 预约信息
      * @param newEndTime 新的结束时间(yyyy-MM-dd HH:mm:ss格式)
      */
-    private void validateAddTimeNotExceedBusinessHours(UserReservation reservation, String newEndTime) {
+    private void validateAddTimeNotExceedBusinessHours(UserReservation reservation, String addTimeStart, String newEndTime) {
         try {
             if (reservation == null || reservation.getStoreId() == null || reservation.getReservationDate() == null) {
                 log.warn("预约信息不完整,跳过营业时间校验");
@@ -1025,7 +1063,16 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             int comparison = compareTime(normalizedNewEndTime, normalizedBusinessEndTime);
             if (comparison > 0) {
                 // 新的结束时间超过了营业时间的结束时间
-                throw new RuntimeException("加时后结束时间不能超过营业时间,营业结束时间为:" + businessEndTime);
+                // 计算最多可加时的分钟数
+                // 确定计算基准时间:取当前结束时间和加时开始时间的较大值
+                String normalizedEndTime = normalizeDateTime(reservation.getEndTime());
+                String normalizedAddTimeStart = normalizeDateTime(addTimeStart);
+                String baseTime = normalizedEndTime;
+                if (normalizedAddTimeStart != null && compareTime(normalizedAddTimeStart, normalizedEndTime) > 0) {
+                    baseTime = normalizedAddTimeStart;
+                }
+                int maxAddTimeMinutes = calculateMaxAddTimeMinutes(baseTime, normalizedBusinessEndTime);
+                throw new RuntimeException("最多可加时" + maxAddTimeMinutes + "分钟");
             }
 
             log.info("加时校验通过,新的结束时间={},营业结束时间={}", normalizedNewEndTime, normalizedBusinessEndTime);

+ 1 - 1
alien-store/src/main/java/shop/alien/store/service/impl/UserReservationServiceImpl.java

@@ -1161,7 +1161,7 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
 
     @Override
     public int markReservationTimeoutByEndTime() {
-        // 关联查询:订单待使用 + 预约结束时间已过,在库内一次筛出需更新的 reservation_id
+        // 关联查询:订单待使用 + 已超过截止时间(retain_position_flag=1 为 start_time+保留分钟,否则仅 start_time)
         List<Integer> toUpdateReservationIds = baseMapper.listReservationIdsForTimeoutMark();
         if (toUpdateReservationIds == null || toUpdateReservationIds.isEmpty()) {
             return 0;