eat:查询菜单页面主屏+副屏
This commit is contained in:
1 parent
cce873b1c6
commit
d62c702c8e
8 files changed
+498
-8
No files matched your search
+9
@@ -0,0 +1,9 @@
|
||||
package com.bsd.pjcloud.hard.api.constant;
|
||||
|
||||
/**
|
||||
* 终端预约菜单查询的身份识别方式。
|
||||
*/
|
||||
public enum ReservationRecognitionTypeEnum {
|
||||
CARD,
|
||||
FACE
|
||||
}
|
||||
+27
@@ -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;
|
||||
}
|
||||
+43
@@ -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<Day> days;
|
||||
|
||||
@Data
|
||||
public static class Day {
|
||||
|
||||
private LocalDate date;
|
||||
|
||||
private List<Meal> meals;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Meal {
|
||||
|
||||
private String hourType;
|
||||
|
||||
private String mealTitle;
|
||||
|
||||
private List<String> items;
|
||||
}
|
||||
}
|
||||
+8
@@ -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<ReservationMenuQueryVo> queryReservationMenu(@Valid @RequestBody ReservationMenuQueryDto dto) {
|
||||
return R.ok(reservationMenuQueryService.query(dto));
|
||||
}
|
||||
|
||||
@GetMapping("/chart-latitude")
|
||||
@Operation(summary = "统计每天、每周、每月、每年的交易人数和次数")
|
||||
public R<HardChartLatitudeVo> chartLatitude(@RequestParam Long merchantId) {
|
||||
|
||||
+12
@@ -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);
|
||||
}
|
||||
+208
@@ -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<HardOrderVo> 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<MchManageVo> 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<HardOrderVo> sourceOrders) {
|
||||
List<HardOrderVo> orders = (sourceOrders == null ? List.<HardOrderVo>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<LocalDate, LinkedHashMap<String, MealAccumulator>> grouped = new TreeMap<>();
|
||||
for (HardOrderVo order : orders) {
|
||||
String hourType = resolveHourType(order);
|
||||
LinkedHashMap<String, MealAccumulator> 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<ReservationMenuQueryVo.Day> days = new ArrayList<>();
|
||||
int totalMealCount = 0;
|
||||
for (Map.Entry<LocalDate, LinkedHashMap<String, MealAccumulator>> dayEntry : grouped.entrySet()) {
|
||||
ReservationMenuQueryVo.Day day = new ReservationMenuQueryVo.Day();
|
||||
day.setDate(dayEntry.getKey());
|
||||
List<ReservationMenuQueryVo.Meal> 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> T requireData(R<T> 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<SysDept> 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<String> items) {
|
||||
|
||||
private MealAccumulator(String hourType, String mealTitle) {
|
||||
this(hourType, mealTitle, new LinkedHashSet<>());
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-8
@@ -402,13 +402,13 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
for (HardOrderVo order : orders) {
|
||||
Long payerUserId = resolvePayerUserId(order);
|
||||
boolean isDiner = Objects.equals(order.getUserId(), viewerUserId);
|
||||
// 目前允许家长查看核销二维码
|
||||
isDiner = true;
|
||||
boolean isPayer = Objects.equals(payerUserId, viewerUserId);
|
||||
order.setPayerUserId(payerUserId);
|
||||
boolean isCanteenReserve = isCanteenReserveOrder(order);
|
||||
order.setIsProxyOrder(isCanteenReserve && payerUserId != null
|
||||
&& !Objects.equals(payerUserId, order.getUserId()));
|
||||
// These flags are meaningful only for the newly supported canteen reservation flow.
|
||||
// Preserve the old App behavior for supermarket, farmer, and delivery orders.
|
||||
order.setIsProxyOrder(isCanteenReserve && payerUserId != null && !Objects.equals(payerUserId, order.getUserId()));
|
||||
// 这些权限标记仅适用于新支持的食堂预约流程,商城、共富商城和配送订单继续保持原有 App 行为。
|
||||
order.setCanRefund(isCanteenReserve ? isPayer : null);
|
||||
order.setCanViewReserveCode(isCanteenReserve ? isDiner : null);
|
||||
if (!isDiner) {
|
||||
@@ -505,8 +505,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
return order == null ? null : order.getUserId();
|
||||
}
|
||||
HardParentCarEntity parentCar = hardParentCarService.getById(order.getParentCarId());
|
||||
// Legacy orders may not have a parent cart. For those records wallet ownership
|
||||
// remains the only reliable payer signal and must not be changed by this feature.
|
||||
// 历史订单可能没有父购物车,此时钱包归属仍是唯一可靠的付款人判断依据,本功能不得改变该规则。
|
||||
return parentCar == null ? order.getUserId() : parentCar.getUserId();
|
||||
}
|
||||
|
||||
@@ -1279,8 +1278,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
log.error("|{}|多人订餐钱包补偿失败,orderId:|{}|", TAG, order.getId(), compensationException);
|
||||
}
|
||||
}
|
||||
// Any cloned carts are in the same local transaction and are rolled back with the
|
||||
// orders. The original cart is intentionally not deleted so a failed batch can be retried.
|
||||
// 克隆购物车与订单处于同一本地事务中,会随订单一并回滚;原购物车刻意保留,便于整批失败后重试。
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package com.bsd.pjcloud.hard.service.impl;
|
||||
|
||||
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.HardOrderStatusEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum;
|
||||
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.wallet.api.feign.RemoteMchService;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ReservationMenuQueryServiceImplTest {
|
||||
|
||||
private final HardManagerService hardManagerService = mock(HardManagerService.class);
|
||||
private final HardApiService hardApiService = mock(HardApiService.class);
|
||||
private final HardOrderService hardOrderService = mock(HardOrderService.class);
|
||||
private final RemoteUserService remoteUserService = mock(RemoteUserService.class);
|
||||
private final RemoteDeptService remoteDeptService = mock(RemoteDeptService.class);
|
||||
private final RemoteMchService remoteMchService = mock(RemoteMchService.class);
|
||||
private final ReservationMenuQueryServiceImpl service = new ReservationMenuQueryServiceImpl(
|
||||
hardManagerService, hardApiService, hardOrderService, remoteUserService, remoteDeptService, remoteMchService);
|
||||
|
||||
@Test
|
||||
void cardQueryUsesTerminalMerchantAndSelfOnlyFilters() {
|
||||
stubTerminal(100L, 200L, "第一食堂");
|
||||
HardAdUserVo cardUser = new HardAdUserVo();
|
||||
cardUser.setUserId(300L);
|
||||
cardUser.setUsername("林小明");
|
||||
cardUser.setDeptId(400L);
|
||||
when(hardApiService.getUserByCard("CARD-001")).thenReturn(cardUser);
|
||||
stubDept(400L, "二年级3班");
|
||||
when(hardOrderService.scopeList(any(), isNull())).thenReturn(List.of());
|
||||
|
||||
ReservationMenuQueryVo result = service.query(cardQuery(100L, " CARD-001 "));
|
||||
|
||||
ArgumentCaptor<HardOrderDto> 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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user