فهرست منبع

feat(reservation): 添加首个未约满日期预约数据查询功能

- 移除分类详情查询接口,精简控制器功能
- 新增firstAvailableDay接口用于查找首个未约满的预约日期
- 实现findFirstAvailableDayReservations服务方法,支持按门店ID查找可用预约
- 添加门店信息查询,丰富预约列表返回数据
- 集成门店容量限制逻辑,支持按单时段最大容纳人数计算约满状态
- 实现连续366天检查机制,确保能找到可用预约日期或返回最后检查日期
fcw 1 ماه پیش
والد
کامیت
d5684ed156

+ 0 - 24
alien-store/src/main/java/shop/alien/store/controller/StoreBookingTableController.java

@@ -193,28 +193,4 @@ public class StoreBookingTableController {
         }
     }
 
-    @ApiOperationSupport(order = 6)
-    @ApiOperation("根据分类ID查询分类详细信息")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "categoryId", value = "分类ID", dataType = "Integer", paramType = "query", required = true)
-    })
-    @GetMapping("/category/detail")
-    public R<StoreBookingCategory> getCategoryDetailByCategoryId(@RequestParam Integer categoryId) {
-        log.info("StoreBookingTableController.getCategoryDetailByCategoryId?categoryId={}", categoryId);
-
-        if (categoryId == null) {
-            return R.fail("分类ID不能为空");
-        }
-
-        try {
-            StoreBookingCategory category = storeBookingCategoryService.getById(categoryId);
-            if (category == null) {
-                return R.fail("分类不存在");
-            }
-            return R.data(category);
-        } catch (Exception e) {
-            log.error("根据分类ID查询分类详情失败", e);
-            return R.fail("查询失败:" + e.getMessage());
-        }
-    }
 }

+ 16 - 0
alien-store/src/main/java/shop/alien/store/controller/UserReservationController.java

@@ -149,4 +149,20 @@ public class UserReservationController {
         Map<String, Object> list = userReservationService.getBookingsByStoreId(storeId);
         return R.data(list);
     }
+
+    /**
+     * 获取首个未约满日期的预约数据:从今天起判断是否约满,约满则顺延到下一天,直到找到未约满的日期并返回该日的预约列表。
+     */
+    @ApiOperation("获取首个未约满日期的预约数据")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
+    @GetMapping("/firstAvailableDay")
+    public R<Map<String, Object>> firstAvailableDay(@RequestParam Integer storeId) {
+        log.info("UserReservationController.firstAvailableDay?storeId={}", storeId);
+        if (storeId == null) {
+            return R.fail("门店ID不能为空");
+        }
+        Map<String, Object> data = userReservationService.findFirstAvailableDayReservations(storeId);
+        return R.data(data);
+    }
 }

+ 9 - 0
alien-store/src/main/java/shop/alien/store/service/UserReservationService.java

@@ -76,4 +76,13 @@ public interface UserReservationService extends IService<UserReservation> {
     List<UserReservationVo> list(Integer userId, Integer storeId, Integer status);
 
     Map<String, Object> getBookingsByStoreId(Integer storeId);
+
+    /**
+     * 从今天起查找第一个未约满的日期,并返回该日的预约数据。
+     * 若当天约满则顺延到下一天再判断,直到找到未约满的日期。
+     *
+     * @param storeId 门店ID
+     * @return 含 date(yyyy-MM-dd)与 reservations(该日预约列表)的 Map;未找到则 reservations 为空且 date 为最后检查的日期
+     */
+    Map<String, Object> findFirstAvailableDayReservations(Integer storeId);
 }

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

@@ -11,14 +11,13 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.UserReservationDTO;
+import shop.alien.entity.store.vo.StoreMainInfoVo;
 import shop.alien.entity.store.vo.UserReservationVo;
 import shop.alien.mapper.UserReservationMapper;
 import shop.alien.mapper.UserReservationTableMapper;
-import shop.alien.store.service.StoreBookingCategoryService;
-import shop.alien.store.service.StoreBookingSettingsService;
-import shop.alien.store.service.StoreBookingTableService;
-import shop.alien.store.service.UserReservationService;
+import shop.alien.store.service.*;
 
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
@@ -42,7 +41,14 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
 
     private final StoreBookingCategoryService storeBookingCategoryService;
 
+    private final StoreInfoService storeInfoService;
+
+    /** 预约状态:待确认 */
     private static final int STATUS_PENDING = 0;
+    /** 预约状态:已取消(不参与约满统计与展示) */
+    private static final int STATUS_CANCELLED = 3;
+    /** 查找首个未约满日期时,最多往后检查的天数 */
+    private static final int MAX_DAYS_TO_CHECK = 366;
 
     @Override
     public Integer add(UserReservationDTO dto) {
@@ -145,9 +151,76 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
         list.put("storeBookingTables", storeBookingTables);
         List<StoreBookingCategory> storeBookingCategorys = storeBookingCategoryService.list(new LambdaQueryWrapper<StoreBookingCategory>().eq(StoreBookingCategory::getStoreId, storeId));
         list.put("storeBookingCategorys", storeBookingCategorys);
+        StoreMainInfoVo storeInfo = storeInfoService.getStoreInfo(storeId);
+        list.put("storeInfo", storeInfo);
         return list;
     }
 
+    /**
+     * 从今天起查找第一个未约满的日期,并返回该日的预约数据。
+     * 判断逻辑:若当天已预约人数(guest_count 之和)>= 门店单时段最大容纳人数,则视为约满,顺延到下一天再判断。
+     *
+     * @param storeId 门店ID
+     * @return Map:date 为 yyyy-MM-dd 格式的日期,reservations 为该日的预约 VO 列表;未配置或 storeId 为空时 date 可能为 null、reservations 为空列表
+     */
+    @Override
+    public Map<String, Object> findFirstAvailableDayReservations(Integer storeId) {
+        Map<String, Object> result = new HashMap<>();
+        if (storeId == null) {
+            result.put("date", null);
+//            result.put("reservations", List.of());
+            return result;
+        }
+        // 取门店预订设置中的单时段最大容纳人数,未配置或为 0 则视为不设上限
+        int maxCapacity = Integer.MAX_VALUE;
+        List<StoreBookingSettings> settingsList = storeBookingSettingsService.list(
+                new LambdaQueryWrapper<StoreBookingSettings>().eq(StoreBookingSettings::getStoreId, storeId));
+        if (!settingsList.isEmpty() && settingsList.get(0).getMaxCapacityPerSlot() != null
+                && settingsList.get(0).getMaxCapacityPerSlot() > 0) {
+            maxCapacity = settingsList.get(0).getMaxCapacityPerSlot();
+        }
+        // 从今天 00:00:00 开始,按天往后检查
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        for (int i = 0; i < MAX_DAYS_TO_CHECK; i++) {
+            Date dayStart = cal.getTime();
+            cal.add(Calendar.DAY_OF_MONTH, 1);
+            Date dayEnd = cal.getTime();
+            // 统计该日该门店下非取消状态的预约总人数
+            LambdaQueryWrapper<UserReservation> countWrapper = new LambdaQueryWrapper<>();
+            countWrapper.eq(UserReservation::getStoreId, storeId)
+                    .ne(UserReservation::getStatus, STATUS_CANCELLED)
+                    .ge(UserReservation::getReservationDate, dayStart)
+                    .lt(UserReservation::getReservationDate, dayEnd);
+            List<UserReservation> dayList = this.list(countWrapper);
+            int totalGuests = dayList.stream()
+                    .mapToInt(r -> r.getGuestCount() == null ? 0 : r.getGuestCount())
+                    .sum();
+            // 未约满:总人数小于上限,返回该日及该日全部预约数据
+            if (totalGuests < maxCapacity) {
+                LambdaQueryWrapper<UserReservation> listWrapper = new LambdaQueryWrapper<>();
+                listWrapper.eq(UserReservation::getStoreId, storeId)
+                        .ge(UserReservation::getReservationDate, dayStart)
+                        .lt(UserReservation::getReservationDate, dayEnd)
+                        .orderByAsc(UserReservation::getReservationDate)
+                        .orderByAsc(UserReservation::getStartTime);
+                List<UserReservation> reservations = this.list(listWrapper);
+                List<UserReservationVo> voList = reservations.stream().map(this::toVoWithTableIds).collect(Collectors.toList());
+                result.put("date", new SimpleDateFormat("yyyy-MM-dd").format(dayStart));
+                result.put("reservations", voList);
+                return result;
+            }
+        }
+        // 连续 MAX_DAYS_TO_CHECK 天都约满时,返回最后检查的日期,预约列表为空
+        cal.add(Calendar.DAY_OF_MONTH, -1);
+        result.put("date", new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime()));
+//        result.put("reservations", List.of());
+        return result;
+    }
+
     private LambdaQueryWrapper<UserReservation> buildListWrapper(Integer userId, Integer storeId, Integer status,
                                                                   Date dateFrom, Date dateTo) {
         LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();