From d62c702c8e7a17a875752b7ec83dd2ec2494d435 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Fri, 24 Jul 2026 17:30:55 +0800 Subject: [PATCH] =?UTF-8?q?eat:=E6=9F=A5=E8=AF=A2=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=B8=BB=E5=B1=8F+=E5=89=AF=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationRecognitionTypeEnum.java | 9 + .../api/dto/hard/ReservationMenuQueryDto.java | 27 +++ .../vo/android/ReservationMenuQueryVo.java | 43 ++++ .../controller/hard/HardApiController.java | 8 + .../service/ReservationMenuQueryService.java | 12 + .../impl/ReservationMenuQueryServiceImpl.java | 208 ++++++++++++++++++ .../impl/order/HardOrderServiceImpl.java | 14 +- .../ReservationMenuQueryServiceImplTest.java | 185 ++++++++++++++++ 8 files changed, 498 insertions(+), 8 deletions(-) create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/ReservationRecognitionTypeEnum.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImplTest.java diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/ReservationRecognitionTypeEnum.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/ReservationRecognitionTypeEnum.java new file mode 100644 index 000000000..ad7005cd5 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/ReservationRecognitionTypeEnum.java @@ -0,0 +1,9 @@ +package com.bsd.pjcloud.hard.api.constant; + +/** + * 终端预约菜单查询的身份识别方式。 + */ +public enum ReservationRecognitionTypeEnum { + CARD, + FACE +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java new file mode 100644 index 000000000..175191a38 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java @@ -0,0 +1,27 @@ +package com.bsd.pjcloud.hard.api.dto.hard; + +import com.bsd.pjcloud.hard.api.constant.ReservationRecognitionTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 终端本人预约菜单查询参数。 + */ +@Data +public class ReservationMenuQueryDto { + + @NotNull(message = "设备ID不能为空") + @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long hardId; + + @NotNull(message = "识别方式不能为空") + @Schema(description = "识别方式:CARD饭卡、FACE人脸", requiredMode = Schema.RequiredMode.REQUIRED) + private ReservationRecognitionTypeEnum recognitionType; + + @Schema(description = "饭卡卡号,CARD识别时必填") + private String cardNumber; + + @Schema(description = "人脸插件识别出的用户ID,FACE识别时必填") + private Long faceUserId; +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java new file mode 100644 index 000000000..2d3941e4c --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java @@ -0,0 +1,43 @@ +package com.bsd.pjcloud.hard.api.vo.android; + +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +/** + * 终端本人预约菜单查询结果,仅包含主副屏展示所需的脱敏信息。 + */ +@Data +public class ReservationMenuQueryVo { + + private String maskedName; + + private String deptName; + + private String merchantName; + + private Integer totalDayCount; + + private Integer totalMealCount; + + private List days; + + @Data + public static class Day { + + private LocalDate date; + + private List meals; + } + + @Data + public static class Meal { + + private String hourType; + + private String mealTitle; + + private List items; + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java index 16a551eec..c60b75cef 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java @@ -16,6 +16,7 @@ import com.bsd.pjcloud.hard.api.dto.order.HardCreateCarDto; import com.bsd.pjcloud.hard.api.vo.HardInvoiceVo; import com.bsd.pjcloud.hard.api.vo.manager.HardManagerVo; import com.bsd.pjcloud.hard.api.vo.android.HardAdUserVo; +import com.bsd.pjcloud.hard.api.vo.android.ReservationMenuQueryVo; import com.bsd.pjcloud.hard.api.vo.order.HardCarVo; import com.bsd.pjcloud.hard.api.vo.order.HardCreateHardQrCodeScanOrderVo; import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; @@ -47,6 +48,7 @@ public class HardApiController { private final HardOrderSyncService hardOrderSyncService; private final HardApiService hardApiService; private final HardOrderTotalService hardOrderTotalService; + private final ReservationMenuQueryService reservationMenuQueryService; @PostMapping("/bind") @Operation(summary = "绑定服务端设备") @@ -170,6 +172,12 @@ public class HardApiController { return R.ok(hardApiService.getUserByCard(card)); } + @PostMapping("/reservation-menu/query") + @Operation(summary = "查询终端当前识别用户的预约菜单") + public R queryReservationMenu(@Valid @RequestBody ReservationMenuQueryDto dto) { + return R.ok(reservationMenuQueryService.query(dto)); + } + @GetMapping("/chart-latitude") @Operation(summary = "统计每天、每周、每月、每年的交易人数和次数") public R chartLatitude(@RequestParam Long merchantId) { diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java new file mode 100644 index 000000000..842098e14 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java @@ -0,0 +1,12 @@ +package com.bsd.pjcloud.hard.service; + +import com.bsd.pjcloud.hard.api.dto.hard.ReservationMenuQueryDto; +import com.bsd.pjcloud.hard.api.vo.android.ReservationMenuQueryVo; + +/** + * 终端本人预约菜单查询服务。 + */ +public interface ReservationMenuQueryService { + + ReservationMenuQueryVo query(ReservationMenuQueryDto dto); +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java new file mode 100644 index 000000000..14d2b51de --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java @@ -0,0 +1,208 @@ +package com.bsd.pjcloud.hard.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.bsd.pjcloud.admin.api.dto.UserInfo; +import com.bsd.pjcloud.admin.api.entity.SysDept; +import com.bsd.pjcloud.admin.api.entity.SysUser; +import com.bsd.pjcloud.admin.api.feign.RemoteDeptService; +import com.bsd.pjcloud.admin.api.feign.RemoteUserService; +import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.hard.api.constant.ReservationRecognitionTypeEnum; +import com.bsd.pjcloud.hard.api.constant.order.HardHourTypeEnum; +import com.bsd.pjcloud.hard.api.constant.order.HardOrderStatusEnum; +import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum; +import com.bsd.pjcloud.hard.api.constant.refund.HardRefundStatusEnum; +import com.bsd.pjcloud.hard.api.dto.hard.ReservationMenuQueryDto; +import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto; +import com.bsd.pjcloud.hard.api.entity.manager.HardManagerEntity; +import com.bsd.pjcloud.hard.api.vo.android.HardAdUserVo; +import com.bsd.pjcloud.hard.api.vo.android.ReservationMenuQueryVo; +import com.bsd.pjcloud.hard.api.vo.order.HardCarVo; +import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; +import com.bsd.pjcloud.hard.service.HardApiService; +import com.bsd.pjcloud.hard.service.HardManagerService; +import com.bsd.pjcloud.hard.service.HardOrderService; +import com.bsd.pjcloud.hard.service.ReservationMenuQueryService; +import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; +import com.bsd.pjcloud.wallet.api.vo.MchManageVo; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +@Service +@RequiredArgsConstructor +public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryService { + + private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai"); + + private final HardManagerService hardManagerService; + private final HardApiService hardApiService; + private final HardOrderService hardOrderService; + private final RemoteUserService remoteUserService; + private final RemoteDeptService remoteDeptService; + private final RemoteMchService remoteMchService; + + @Override + public ReservationMenuQueryVo query(ReservationMenuQueryDto dto) { + TerminalMerchant terminalMerchant = resolveTerminalMerchant(dto.getHardId()); + RecognizedUser recognizedUser = recognizeUser(dto); + + HardOrderDto query = new HardOrderDto(); + query.setUserId(recognizedUser.userId()); + query.setMerchantId(terminalMerchant.merchant().getId()); + query.setOrderType(HardOrderTypeEnum.RESERVE.getType()); + query.setOrderStatusCodes(List.of(HardOrderStatusEnum.PAY.getType())); + query.setRefundStatusCodes(List.of(HardRefundStatusEnum.NONE.getStatus(), HardRefundStatusEnum.REJECT.getStatus())); + query.setApptUseDateStart(LocalDate.now(BUSINESS_ZONE).atStartOfDay()); + query.setIsFindCarList(true); + query.setIsFindApptList(true); + + // 身份与商户条件只在服务端生成,终端无法借此接口查询其他用户或其他食堂。 + List orders = hardOrderService.scopeList(query, null); + return aggregate(recognizedUser, terminalMerchant, orders); + } + + private TerminalMerchant resolveTerminalMerchant(Long hardId) { + HardManagerEntity hard = hardManagerService.getById(hardId); + if (hard == null) { + throw new RuntimeException("设备不存在或无权访问"); + } + if (hard.getMerchantId() == null) { + throw new RuntimeException("设备未绑定食堂"); + } + R merchantResult = remoteMchService.getById(hard.getMerchantId()); + MchManageVo merchant = requireData(merchantResult, "查询设备绑定食堂失败"); + if (!Objects.equals(merchant.getTypes(), MerchantTypeEnum.CANTEEN.getType())) { + throw new RuntimeException("设备未绑定有效食堂"); + } + return new TerminalMerchant(merchant); + } + + private RecognizedUser recognizeUser(ReservationMenuQueryDto dto) { + if (dto.getRecognitionType() == ReservationRecognitionTypeEnum.CARD) { + if (StrUtil.isBlank(dto.getCardNumber())) { + throw new RuntimeException("饭卡卡号不能为空"); + } + HardAdUserVo user = hardApiService.getUserByCard(dto.getCardNumber().trim()); + if (user == null || user.getUserId() == null) { + throw new RuntimeException("未识别到有效饭卡用户"); + } + return new RecognizedUser(user.getUserId(), user.getUsername(), user.getDeptId()); + } + + if (dto.getFaceUserId() == null) { + throw new RuntimeException("人脸用户ID不能为空"); + } + UserInfo userInfo = requireData(remoteUserService.getInnerByUserId(dto.getFaceUserId()), "未识别到有效人脸用户"); + SysUser user = userInfo.getSysUser(); + if (user == null || user.getUserId() == null) { + throw new RuntimeException("未识别到有效人脸用户"); + } + return new RecognizedUser(user.getUserId(), user.getName(), user.getDeptId()); + } + + private ReservationMenuQueryVo aggregate(RecognizedUser user, TerminalMerchant terminalMerchant, List sourceOrders) { + List orders = (sourceOrders == null ? List.of() : sourceOrders).stream() + .filter(order -> order != null && order.getApptUseDate() != null && StrUtil.isNotBlank(resolveHourType(order))) + .sorted(Comparator.comparing(HardOrderVo::getApptUseDate) + .thenComparing(ReservationMenuQueryServiceImpl::resolveHourType) + .thenComparing(HardOrderVo::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(HardOrderVo::getId, Comparator.nullsLast(Comparator.naturalOrder()))) + .toList(); + + Map> grouped = new TreeMap<>(); + for (HardOrderVo order : orders) { + String hourType = resolveHourType(order); + LinkedHashMap meals = grouped.computeIfAbsent(order.getApptUseDate(), ignored -> new LinkedHashMap<>()); + MealAccumulator meal = meals.computeIfAbsent(hourType, ignored -> new MealAccumulator(hourType, resolveMealTitle(order, hourType))); + if (order.getCarList() == null) continue; + for (HardCarVo car : order.getCarList()) { + if (car != null && StrUtil.isNotBlank(car.getProductName())) { + meal.items().add(car.getProductName().trim()); + } + } + } + + List days = new ArrayList<>(); + int totalMealCount = 0; + for (Map.Entry> dayEntry : grouped.entrySet()) { + ReservationMenuQueryVo.Day day = new ReservationMenuQueryVo.Day(); + day.setDate(dayEntry.getKey()); + List meals = dayEntry.getValue().values().stream().map(ReservationMenuQueryServiceImpl::toMeal).toList(); + day.setMeals(meals); + totalMealCount += meals.size(); + days.add(day); + } + + ReservationMenuQueryVo result = new ReservationMenuQueryVo(); + result.setMaskedName(maskName(user.name())); + result.setDeptName(resolveDeptName(user.deptId())); + result.setMerchantName(terminalMerchant.merchant().getName()); + result.setTotalDayCount(days.size()); + result.setTotalMealCount(totalMealCount); + result.setDays(days); + return result; + } + + private static ReservationMenuQueryVo.Meal toMeal(MealAccumulator source) { + ReservationMenuQueryVo.Meal meal = new ReservationMenuQueryVo.Meal(); + meal.setHourType(source.hourType()); + meal.setMealTitle(source.mealTitle()); + meal.setItems(new ArrayList<>(source.items())); + return meal; + } + + private static String resolveHourType(HardOrderVo order) { + return StrUtil.blankToDefault(order.getHourType(), order.getApptHourType()); + } + + private static String resolveMealTitle(HardOrderVo order, String hourType) { + HardHourTypeEnum type = HardHourTypeEnum.valueOfByType(hourType); + if (type != null) return type.getDescription(); + return StrUtil.blankToDefault(order.getApptTitle(), "餐段"); + } + + private static String maskName(String name) { + if (StrUtil.isBlank(name)) return "*"; + String normalized = name.trim(); + return normalized.length() == 1 ? "*" : normalized.substring(0, 1) + "*"; + } + + private static T requireData(R result, String message) { + if (result == null || !result.isOk() || result.getData() == null) { + throw new RuntimeException(message); + } + return result.getData(); + } + + private String resolveDeptName(Long deptId) { + if (deptId == null) return null; + R result = remoteDeptService.getDeptByDeptId(deptId); + if (result == null || !result.isOk() || result.getData() == null) return null; + return result.getData().getName(); + } + + record RecognizedUser(Long userId, String name, Long deptId) { + } + + record TerminalMerchant(MchManageVo merchant) { + } + + private record MealAccumulator(String hourType, String mealTitle, LinkedHashSet items) { + + private MealAccumulator(String hourType, String mealTitle) { + this(hourType, mealTitle, new LinkedHashSet<>()); + } + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java index 7d7157973..33107a757 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java @@ -402,13 +402,13 @@ public class HardOrderServiceImpl extends ServiceImpl queryCaptor = ArgumentCaptor.forClass(HardOrderDto.class); + verify(hardOrderService).scopeList(queryCaptor.capture(), isNull()); + HardOrderDto query = queryCaptor.getValue(); + assertEquals(300L, query.getUserId()); + assertEquals(200L, query.getMerchantId()); + assertEquals(HardOrderTypeEnum.RESERVE.getType(), query.getOrderType()); + assertEquals(List.of(HardOrderStatusEnum.PAY.getType()), query.getOrderStatusCodes()); + assertTrue(query.getIsFindCarList()); + assertTrue(query.getIsFindApptList()); + assertEquals("林*", result.getMaskedName()); + assertEquals("二年级3班", result.getDeptName()); + assertEquals("第一食堂", result.getMerchantName()); + assertEquals(0, result.getTotalDayCount()); + } + + @Test + void faceQueryRequiresExistingUser() { + stubTerminal(100L, 200L, "第一食堂"); + when(remoteUserService.getInnerByUserId(300L)).thenReturn(R.ok(null)); + ReservationMenuQueryDto query = new ReservationMenuQueryDto(); + query.setHardId(100L); + query.setRecognitionType(ReservationRecognitionTypeEnum.FACE); + query.setFaceUserId(300L); + + assertThrows(RuntimeException.class, () -> service.query(query)); + verifyNoMoreInteractions(hardOrderService); + } + + @Test + void mealsAreMergedByDateAndSegmentWithStableItemDeduplication() { + stubTerminal(100L, 200L, "第一食堂"); + stubFaceUser(300L, "王小华", 400L); + stubDept(400L, "三年级1班"); + when(hardOrderService.scopeList(any(), isNull())).thenReturn(List.of( + order(2L, LocalDate.of(2026, 7, 25), "1", "早餐预约", LocalDateTime.of(2026, 7, 20, 9, 0), "牛奶", "鸡蛋"), + order(1L, LocalDate.of(2026, 7, 24), "2", "午餐预约", LocalDateTime.of(2026, 7, 20, 8, 0), "米饭", "青菜"), + order(3L, LocalDate.of(2026, 7, 24), "2", "午餐预约", LocalDateTime.of(2026, 7, 20, 10, 0), "青菜", "红烧肉"))); + + ReservationMenuQueryVo result = service.query(faceQuery(100L, 300L)); + + assertEquals(2, result.getTotalDayCount()); + assertEquals(2, result.getTotalMealCount()); + assertEquals(LocalDate.of(2026, 7, 24), result.getDays().get(0).getDate()); + assertEquals("2", result.getDays().get(0).getMeals().get(0).getHourType()); + assertEquals("午餐", result.getDays().get(0).getMeals().get(0).getMealTitle()); + assertEquals(List.of("米饭", "青菜", "红烧肉"), result.getDays().get(0).getMeals().get(0).getItems()); + } + + @Test + void invalidTerminalBindingIsRejectedBeforeIdentityLookup() { + HardManagerEntity hard = new HardManagerEntity(); + hard.setId(100L); + when(hardManagerService.getById(100L)).thenReturn(hard); + + assertThrows(RuntimeException.class, () -> service.query(cardQuery(100L, "CARD-001"))); + verifyNoMoreInteractions(hardApiService, hardOrderService); + } + + private void stubTerminal(Long hardId, Long merchantId, String merchantName) { + HardManagerEntity hard = new HardManagerEntity(); + hard.setId(hardId); + hard.setMerchantId(merchantId); + when(hardManagerService.getById(hardId)).thenReturn(hard); + MchManageVo merchant = new MchManageVo(); + merchant.setId(merchantId); + merchant.setName(merchantName); + merchant.setTypes(MerchantTypeEnum.CANTEEN.getType()); + when(remoteMchService.getById(merchantId)).thenReturn(R.ok(merchant)); + } + + private void stubFaceUser(Long userId, String name, Long deptId) { + SysUser user = new SysUser(); + user.setUserId(userId); + user.setName(name); + user.setDeptId(deptId); + UserInfo userInfo = new UserInfo(); + userInfo.setSysUser(user); + when(remoteUserService.getInnerByUserId(userId)).thenReturn(R.ok(userInfo)); + } + + private void stubDept(Long deptId, String deptName) { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + dept.setName(deptName); + when(remoteDeptService.getDeptByDeptId(deptId)).thenReturn(R.ok(dept)); + } + + private static ReservationMenuQueryDto cardQuery(Long hardId, String cardNumber) { + ReservationMenuQueryDto query = new ReservationMenuQueryDto(); + query.setHardId(hardId); + query.setRecognitionType(ReservationRecognitionTypeEnum.CARD); + query.setCardNumber(cardNumber); + return query; + } + + private static ReservationMenuQueryDto faceQuery(Long hardId, Long userId) { + ReservationMenuQueryDto query = new ReservationMenuQueryDto(); + query.setHardId(hardId); + query.setRecognitionType(ReservationRecognitionTypeEnum.FACE); + query.setFaceUserId(userId); + return query; + } + + private static HardOrderVo order(Long id, LocalDate date, String hourType, String title, LocalDateTime createTime, String... items) { + HardOrderVo order = new HardOrderVo(); + order.setId(id); + order.setApptUseDate(date); + order.setHourType(hourType); + order.setApptTitle(title); + order.setCreateTime(createTime); + order.setCarList(java.util.Arrays.stream(items).map(ReservationMenuQueryServiceImplTest::car).toList()); + return order; + } + + private static HardCarVo car(String productName) { + HardCarVo car = new HardCarVo(); + car.setProductName(productName); + return car; + } +}