diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/HardApptReserveModeEnum.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/HardApptReserveModeEnum.java index ba15b55f1..9e89af07d 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/HardApptReserveModeEnum.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/constant/HardApptReserveModeEnum.java @@ -7,7 +7,7 @@ import lombok.Getter; import java.util.Arrays; /** - * 订餐预约限制模式。 + * 订餐预约限制模式 */ @AllArgsConstructor @Getter diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptController.java index c856b4fa0..18a5a5c54 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptController.java @@ -8,11 +8,9 @@ import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.data.datascope.DataScope; import com.bsd.pjcloud.common.log.annotation.SysLog; -import com.bsd.pjcloud.hard.api.constant.HardApptReserveModeEnum; import com.bsd.pjcloud.hard.api.dto.HardApptDto; import com.bsd.pjcloud.hard.api.entity.HardApptEntity; import com.bsd.pjcloud.hard.api.vo.HardApptDateOptionsVo; @@ -51,14 +49,7 @@ public class HardApptController { @ApiCryptoAes @GetMapping("/app/list") public R> appList(@ParameterObject HardApptDto dto) { - List list = hardApptService.list(new LambdaQueryWrapper() - .eq(ObjectUtil.isNotEmpty(dto.getMchId()), HardApptEntity::getMchId, dto.getMchId()) - .eq(HardApptEntity::getStatus, YesNoEnum.YES.getCode()) - .eq(HardApptEntity::getReserveMode, HardApptReserveModeEnum.TIME_WINDOW.getType()) - .orderByAsc(HardApptEntity::getHourType) - .orderByDesc(HardApptEntity::getSort) - ); - return R.ok(list); + return R.ok(hardApptReservationService.listLegacyFlatOptions(dto.getMchId())); } @Operation(summary = "app预约时间列表v2", description = "app预约时间列表v2") @@ -68,7 +59,7 @@ public class HardApptController { return R.ok(hardApptReservationService.listLegacyOptions(dto.getMchId())); } - @Operation(summary = "app预约时间列表v3", description = "按实际用餐日期返回动态时间窗与按周集中预约餐段") + @Operation(summary = "app预约时间列表v3", description = "按商户当前预约模式返回实际用餐日期和餐段") @ApiCryptoAes @GetMapping("/app/v3/list") public R> appV3List(@ParameterObject HardApptDto dto) { diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptCycleRuleController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptCycleRuleController.java index a53b53789..6045f6156 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptCycleRuleController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/HardApptCycleRuleController.java @@ -7,7 +7,9 @@ import com.bsd.pjcloud.common.data.datascope.DataScope; import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.hard.api.dto.HardApptCycleRuleDto; import com.bsd.pjcloud.hard.api.vo.HardApptCycleRuleVo; +import com.bsd.pjcloud.hard.api.vo.HardApptDateOptionsVo; import com.bsd.pjcloud.hard.service.HardApptCycleRuleService; +import com.bsd.pjcloud.hard.service.HardApptReservationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -22,6 +24,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -34,6 +37,7 @@ import java.util.List; public class HardApptCycleRuleController { private final HardApptCycleRuleService cycleRuleService; + private final HardApptReservationService reservationService; @Operation(summary = "周期预约规则分页") @GetMapping("/page") @@ -43,6 +47,14 @@ public class HardApptCycleRuleController { return R.ok(cycleRuleService.scopePage(page, dto, DataScope.of())); } + @Operation(summary = "推演商户用户预约视图") + @GetMapping("/preview") + @PreAuthorize("@pms.hasPermission('hard_hardAppt_view')") + public R> preview(@RequestParam Long mchId, + @RequestParam(defaultValue = "7") Integer days) { + return R.ok(reservationService.previewOptions(mchId, days)); + } + @Operation(summary = "周期预约规则详情") @GetMapping("/{id}") @PreAuthorize("@pms.hasPermission('hard_hardAppt_view')") diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardApptMerchantModeService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardApptMerchantModeService.java new file mode 100644 index 000000000..47e590882 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardApptMerchantModeService.java @@ -0,0 +1,21 @@ +package com.bsd.pjcloud.hard.service; + +import com.bsd.pjcloud.hard.api.constant.HardApptReserveModeEnum; +import com.bsd.pjcloud.wallet.api.vo.MchManageVo; + +/** + * 统一解析商户当前生效的订餐预约模式。 + */ +public interface HardApptMerchantModeService { + + /** + * 获取商户及其当前预约模式,历史空配置按动态时间窗处理。 + * + * @param mchId 商户ID + * @return 商户预约配置上下文 + */ + MerchantModeContext resolve(Long mchId); + + record MerchantModeContext(MchManageVo merchant, HardApptReserveModeEnum reserveMode) { + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardApptReservationService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardApptReservationService.java index 4ecfdf6df..254b935ce 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardApptReservationService.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardApptReservationService.java @@ -12,10 +12,21 @@ import java.util.List; */ public interface HardApptReservationService { + List listLegacyFlatOptions(Long mchId); + List listLegacyOptions(Long mchId); List listOptions(Long mchId); + /** + * 按今天起连续自然日推演商户最终预约视图,供管理端核对多条规则叠加结果。 + * + * @param mchId 商户ID + * @param days 推演自然日数量,包含今天 + * @return 每个自然日及其最终餐段配置 + */ + List previewOptions(Long mchId, Integer days); + ReservationValidation validateReservation(Long mchId, Long apptId, Long apptRuleId, String hourType, LocalDate apptDate, String legacyApptTimeType); diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImpl.java index 63ceabf44..5d50e1e6c 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImpl.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.data.datascope.DataScope; +import com.bsd.pjcloud.hard.api.constant.HardApptReserveModeEnum; import com.bsd.pjcloud.hard.api.constant.order.HardHourTypeEnum; import com.bsd.pjcloud.hard.api.dto.HardApptCycleRuleDto; import com.bsd.pjcloud.hard.api.dto.HardApptCycleRuleItemDto; @@ -18,6 +19,7 @@ import com.bsd.pjcloud.hard.api.vo.HardApptCycleRuleVo; import com.bsd.pjcloud.hard.mapper.HardApptCycleRuleItemMapper; import com.bsd.pjcloud.hard.mapper.HardApptCycleRuleMapper; import com.bsd.pjcloud.hard.service.HardApptCycleRuleService; +import com.bsd.pjcloud.hard.service.HardApptMerchantModeService; import com.bsd.pjcloud.hard.service.support.HardApptCycleRuleCalculator; import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; import com.bsd.pjcloud.wallet.api.vo.MchManageVo; @@ -40,6 +42,7 @@ public class HardApptCycleRuleServiceImpl private final HardApptCycleRuleItemMapper itemMapper; private final RemoteMchService remoteMchService; + private final HardApptMerchantModeService merchantModeService; @Override public Page scopePage(Page page, HardApptCycleRuleDto dto, DataScope dataScope) { @@ -147,12 +150,11 @@ public class HardApptCycleRuleServiceImpl } List items = normalizeItems(dto.getItemList(), dto.getTargetDayCount()); - R merchantResult = remoteMchService.getById(dto.getMchId()); - MchManageVo merchant = merchantResult == null || !merchantResult.isOk() ? null : merchantResult.getData(); - if (merchant == null) { - throw new RuntimeException("商户不存在"); + HardApptMerchantModeService.MerchantModeContext merchantContext = merchantModeService.resolve(dto.getMchId()); + if (merchantContext.reserveMode() != HardApptReserveModeEnum.WEEKLY_CYCLE) { + throw new RuntimeException("当前商户使用动态时间窗,不能维护周期预约规则"); } - dto.setDeptId(merchant.getDeptId()); + dto.setDeptId(merchantContext.merchant().getDeptId()); if (YesNoEnum.YES.getCode().equals(dto.getStatus())) { validateConflict(dto, items, excludeId); diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptMerchantModeServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptMerchantModeServiceImpl.java new file mode 100644 index 000000000..af3b1129c --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptMerchantModeServiceImpl.java @@ -0,0 +1,53 @@ +package com.bsd.pjcloud.hard.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.hard.api.constant.HardApptReserveModeEnum; +import com.bsd.pjcloud.hard.service.HardApptMerchantModeService; +import com.bsd.pjcloud.wallet.api.entity.mch.MchManageEntity; +import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; +import com.bsd.pjcloud.wallet.api.vo.MchManageVo; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 商户预约模式解析实现。 + */ +@Service +@RequiredArgsConstructor +public class HardApptMerchantModeServiceImpl implements HardApptMerchantModeService { + + private final RemoteMchService remoteMchService; + + @Override + public MerchantModeContext resolve(Long mchId) { + if (ObjectUtil.isEmpty(mchId)) { + throw new RuntimeException("商户ID不能为空"); + } + + R result; + try { + result = remoteMchService.getById(mchId); + } catch (RuntimeException exception) { + throw new RuntimeException("获取商户预约配置失败", exception); + } + if (result == null || !result.isOk()) { + String message = result == null || ObjectUtil.isEmpty(result.getMsg()) + ? "获取商户预约配置失败" : result.getMsg(); + throw new RuntimeException(message); + } + MchManageVo merchant = result.getData(); + if (merchant == null) { + throw new RuntimeException("商户不存在"); + } + + MchManageEntity.TableReservationConf conf = merchant.getTableReservationConf(); + String modeValue = conf == null ? null : conf.getReserveMode(); + HardApptReserveModeEnum reserveMode = HardApptReserveModeEnum.valueOfByType( + modeValue == null ? null : modeValue.trim()); + if (reserveMode == null) { + throw new RuntimeException("商户预约时间模式无效"); + } + return new MerchantModeContext(merchant, reserveMode); + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImpl.java index 6391b61e3..8ecf58746 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImpl.java @@ -15,6 +15,7 @@ import com.bsd.pjcloud.hard.api.vo.HardApptDateOptionsVo; import com.bsd.pjcloud.hard.api.vo.HardApptOptionVo; import com.bsd.pjcloud.hard.api.vo.HardApptTimeCascadeVo; import com.bsd.pjcloud.hard.service.HardApptCycleRuleService; +import com.bsd.pjcloud.hard.service.HardApptMerchantModeService; import com.bsd.pjcloud.hard.service.HardApptReservationService; import com.bsd.pjcloud.hard.service.HardApptService; import com.bsd.pjcloud.hard.service.HardApptTimeService; @@ -24,10 +25,12 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.DayOfWeek; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -46,17 +49,30 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic static final ZoneId RESERVATION_ZONE = ZoneId.of("Asia/Shanghai"); private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + private static final int DEFAULT_PREVIEW_DAYS = 7; + private static final int MAX_PREVIEW_DAYS = 31; private final HardApptService hardApptService; private final HardApptTimeService hardApptTimeService; private final HardApptCycleRuleService cycleRuleService; + private final HardApptMerchantModeService merchantModeService; + + @Override + public List listLegacyFlatOptions(Long mchId) { + HardApptReserveModeEnum merchantMode = resolveMerchantMode(mchId); + if (merchantMode != HardApptReserveModeEnum.TIME_WINDOW) { + return List.of(); + } + return findEnabledAppts(mchId, HardApptReserveModeEnum.TIME_WINDOW); + } @Override public List listLegacyOptions(Long mchId) { - validateMerchantId(mchId); - List dynamicAppts = findEnabledAppts(mchId).stream() - .filter(appt -> resolveMode(appt) == HardApptReserveModeEnum.TIME_WINDOW) - .toList(); + HardApptReserveModeEnum merchantMode = resolveMerchantMode(mchId); + if (merchantMode != HardApptReserveModeEnum.TIME_WINDOW) { + return List.of(); + } + List dynamicAppts = findEnabledAppts(mchId, merchantMode); if (dynamicAppts.isEmpty()) { return List.of(); } @@ -88,28 +104,26 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic } List listOptions(Long mchId, LocalDateTime now) { - validateMerchantId(mchId); - List allAppts = findEnabledAppts(mchId); + HardApptReserveModeEnum merchantMode = resolveMerchantMode(mchId); LocalDate today = now.toLocalDate(); Map> optionsByDate = new TreeMap<>(); - List dynamicAppts = allAppts.stream() - .filter(appt -> resolveMode(appt) == HardApptReserveModeEnum.TIME_WINDOW) - .toList(); - Map> apptIdsByDateType = groupMappingsByDateType(mchId, dynamicAppts); - for (HardApptTimeTypeEnum dateType : HardApptTimeTypeEnum.values()) { - if (!HardApptTimeTypeEnum.isReservationDateType(dateType.getType())) { - continue; + if (merchantMode == HardApptReserveModeEnum.TIME_WINDOW) { + List dynamicAppts = findEnabledAppts(mchId, merchantMode); + Map> apptIdsByDateType = groupMappingsByDateType(mchId, dynamicAppts); + for (HardApptTimeTypeEnum dateType : HardApptTimeTypeEnum.values()) { + if (!HardApptTimeTypeEnum.isReservationDateType(dateType.getType())) { + continue; + } + Set mappedIds = apptIdsByDateType.getOrDefault(dateType.getType(), Set.of()); + LocalDate targetDate = today.plusDays(dateType.getDay()); + dynamicAppts.stream() + .filter(appt -> mappedIds.contains(appt.getId())) + .map(appt -> buildOption(appt, evaluateAvailability(appt, targetDate, now))) + .forEach(option -> optionsByDate.computeIfAbsent(targetDate, key -> new ArrayList<>()).add(option)); } - Set mappedIds = apptIdsByDateType.getOrDefault(dateType.getType(), Set.of()); - LocalDate targetDate = today.plusDays(dateType.getDay()); - dynamicAppts.stream() - .filter(appt -> mappedIds.contains(appt.getId())) - .map(appt -> buildOption(appt, evaluateAvailability(appt, targetDate, now))) - .forEach(option -> optionsByDate.computeIfAbsent(targetDate, key -> new ArrayList<>()).add(option)); - } - - for (HardApptCycleRuleVo rule : cycleRuleService.listEnabledConfigurations(mchId)) { + } else { + for (HardApptCycleRuleVo rule : cycleRuleService.listEnabledConfigurations(mchId)) { HardApptCycleRuleCalculator.CycleBatch batch; try { HardApptCycleRuleCalculator.validate(rule); @@ -131,6 +145,7 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic option.setApptRuleId(rule.getId()); optionsByDate.computeIfAbsent(targetDate, key -> new ArrayList<>()).add(option); } + } } return optionsByDate.entrySet().stream().map(entry -> { @@ -144,6 +159,148 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic }).toList(); } + /** + * 管理端推演会覆盖多个未来周批次,并保留没有配置餐段的自然日,便于管理员核对规则空档。 + */ + @Override + public List previewOptions(Long mchId, Integer days) { + return previewOptions(mchId, days, nowInShanghai()); + } + + List previewOptions(Long mchId, Integer days, LocalDateTime now) { + HardApptReserveModeEnum merchantMode = resolveMerchantMode(mchId); + int previewDays = days == null ? DEFAULT_PREVIEW_DAYS : days; + if (previewDays < 1 || previewDays > MAX_PREVIEW_DAYS) { + throw new RuntimeException("推演天数必须在1至31天之间"); + } + + LocalDate startDate = now.toLocalDate(); + LocalDate endDate = startDate.plusDays(previewDays - 1L); + Map> optionsByDate = new TreeMap<>(); + for (int day = 0; day < previewDays; day++) { + optionsByDate.put(startDate.plusDays(day), new LinkedHashMap<>()); + } + + if (merchantMode == HardApptReserveModeEnum.TIME_WINDOW) { + addDynamicPreviewOptions(mchId, now, startDate, endDate, optionsByDate); + } else { + addCyclePreviewOptions(mchId, now, startDate, endDate, optionsByDate); + } + return optionsByDate.entrySet().stream().map(entry -> { + HardApptDateOptionsVo dateOptions = new HardApptDateOptionsVo(); + dateOptions.setApptDate(entry.getKey()); + dateOptions.setApptList(new ArrayList<>(entry.getValue().values())); + return dateOptions; + }).toList(); + } + + /** + * 动态模式仍只按已有今天、明天、后天映射展示,不把相对日期规则错误扩展到整个推演范围。 + */ + private void addDynamicPreviewOptions(Long mchId, LocalDateTime now, LocalDate startDate, LocalDate endDate, + Map> optionsByDate) { + List dynamicAppts = findEnabledAppts(mchId, HardApptReserveModeEnum.TIME_WINDOW); + Map> apptIdsByDateType = groupMappingsByDateType(mchId, dynamicAppts); + for (HardApptTimeTypeEnum dateType : HardApptTimeTypeEnum.values()) { + if (!HardApptTimeTypeEnum.isReservationDateType(dateType.getType())) { + continue; + } + LocalDate targetDate = startDate.plusDays(dateType.getDay()); + if (targetDate.isAfter(endDate)) { + continue; + } + Set mappedIds = apptIdsByDateType.getOrDefault(dateType.getType(), Set.of()); + dynamicAppts.stream() + .filter(appt -> mappedIds.contains(appt.getId())) + .forEach(appt -> optionsByDate.get(targetDate).put("appt:" + appt.getId(), + buildOption(appt, evaluateAvailability(appt, targetDate, now)))); + } + } + + /** + * 周期模式按每个实际周窗口逐批推演,避免20天预览只展示当前或下一批次。 + */ + private void addCyclePreviewOptions(Long mchId, LocalDateTime now, LocalDate startDate, LocalDate endDate, + Map> optionsByDate) { + for (HardApptCycleRuleVo rule : cycleRuleService.listEnabledConfigurations(mchId)) { + try { + HardApptCycleRuleCalculator.validate(rule); + } catch (RuntimeException ignored) { + continue; + } + if (rule.getItemList() == null || rule.getItemList().isEmpty()) { + continue; + } + int lookBehindDays = rule.getTargetStartOffsetDays() + rule.getTargetDayCount() + 14; + LocalDate monday = startDate.minusDays(lookBehindDays) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); + LocalDate lastMonday = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY)); + while (!monday.isAfter(lastMonday)) { + HardApptCycleRuleCalculator.CycleWindow window = + HardApptCycleRuleCalculator.windowForMonday(rule, monday); + LocalDate targetStart = window.endDateTime().toLocalDate() + .plusDays(rule.getTargetStartOffsetDays()); + Availability availability = cyclePreviewAvailability(rule, window, now); + for (HardApptCycleRuleItemVo item : rule.getItemList()) { + if (item.getTargetDayOffset() == null || item.getTargetDayOffset() < 0 + || item.getTargetDayOffset() >= rule.getTargetDayCount()) { + continue; + } + HardHourTypeEnum hourType = HardHourTypeEnum.valueOfByType(item.getHourType()); + LocalDate targetDate = targetStart.plusDays(item.getTargetDayOffset()); + if (hourType == null || targetDate.isBefore(startDate) || targetDate.isAfter(endDate)) { + continue; + } + HardApptOptionVo option = buildCycleOption(hourType, availability); + option.setApptRuleId(rule.getId()); + putPreviewOption(optionsByDate.get(targetDate), + "cycle:" + rule.getId() + ":" + hourType.getType(), option, now); + } + monday = monday.plusWeeks(1); + } + } + } + + /** + * 同一规则覆盖超过7天时,相邻周批次可能落到同一日期;优先展示当前可订,其次展示最近将开放的批次。 + */ + private static void putPreviewOption(Map options, String key, + HardApptOptionVo candidate, LocalDateTime now) { + HardApptOptionVo existing = options.get(key); + if (existing == null || previewPriority(candidate, now) > previewPriority(existing, now) + || previewPriority(candidate, now) == previewPriority(existing, now) + && candidate.getReserveStartDateTime() != null && existing.getReserveStartDateTime() != null + && candidate.getReserveStartDateTime().isBefore(existing.getReserveStartDateTime())) { + options.put(key, candidate); + } + } + + private static int previewPriority(HardApptOptionVo option, LocalDateTime now) { + if (Boolean.TRUE.equals(option.getAvailable())) { + return 3; + } + if (option.getReserveStartDateTime() != null && option.getReserveStartDateTime().isAfter(now)) { + return 2; + } + return 1; + } + + private static Availability cyclePreviewAvailability(HardApptCycleRuleVo rule, + HardApptCycleRuleCalculator.CycleWindow window, + LocalDateTime now) { + LocalDateTime start = window.startDateTime(); + LocalDateTime end = window.endDateTime(); + if (!now.isBefore(start) && !now.isAfter(end)) { + return Availability.available(start, end); + } + if (now.isBefore(start)) { + String reason = "周" + weekdayLabel(rule.getOpenWeekday()) + + rule.getOpenTime().format(TIME_FORMATTER) + "开放"; + return Availability.unavailable(reason, start, end); + } + return Availability.unavailable("本期预约已截止", start, end); + } + @Override public ReservationValidation validateReservation(Long mchId, Long apptId, Long apptRuleId, String hourType, LocalDate apptDate, String legacyApptTimeType) { @@ -152,11 +309,15 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic ReservationValidation validateReservation(Long mchId, Long apptId, Long apptRuleId, String hourType, LocalDate apptDate, String legacyApptTimeType, LocalDateTime now) { - if (ObjectUtil.isEmpty(mchId)) { - throw new RuntimeException("预约信息不完整"); + HardApptReserveModeEnum merchantMode = resolveMerchantMode(mchId); + if (merchantMode == HardApptReserveModeEnum.WEEKLY_CYCLE) { + if (apptRuleId == null) { + throw new RuntimeException("周期预约规则不能为空"); + } + return validateCycleReservation(mchId, apptRuleId, hourType, apptDate, now); } if (apptRuleId != null) { - return validateCycleReservation(mchId, apptRuleId, hourType, apptDate, now); + throw new RuntimeException("当前商户使用动态时间窗,不能提交周期预约规则"); } if (ObjectUtil.isEmpty(apptId)) { throw new RuntimeException("动态预约餐段不能为空"); @@ -174,27 +335,22 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic } HardApptReserveModeEnum reserveMode = resolveMode(appt); - if (reserveMode == null) { - throw new RuntimeException("预约规则模式无效"); + if (reserveMode != HardApptReserveModeEnum.TIME_WINDOW) { + throw new RuntimeException("预约餐段与商户当前模式不匹配"); } - LocalDate targetDate; - if (reserveMode != HardApptReserveModeEnum.TIME_WINDOW) { - throw new RuntimeException("周期预约请升级客户端后重试"); - } else { - targetDate = resolveLegacyApptDate(apptDate, legacyApptTimeType, now.toLocalDate()); - long dayOffset = ChronoUnit.DAYS.between(now.toLocalDate(), targetDate); - HardApptTimeTypeEnum dateType = HardApptTimeTypeEnum.valueOfByDay((int) dayOffset); - if (dateType == null || !HardApptTimeTypeEnum.isReservationDateType(dateType.getType())) { - throw new RuntimeException("该预约日期不在可选范围内"); - } - long mappingCount = hardApptTimeService.count(new LambdaQueryWrapper() - .eq(HardApptTimeEntity::getMchId, mchId) - .eq(HardApptTimeEntity::getApptId, apptId) - .eq(HardApptTimeEntity::getType, dateType.getType())); - if (mappingCount == 0) { - throw new RuntimeException("该日期未配置此预约餐段"); - } + LocalDate targetDate = resolveLegacyApptDate(apptDate, legacyApptTimeType, now.toLocalDate()); + long dayOffset = ChronoUnit.DAYS.between(now.toLocalDate(), targetDate); + HardApptTimeTypeEnum dateType = HardApptTimeTypeEnum.valueOfByDay((int) dayOffset); + if (dateType == null || !HardApptTimeTypeEnum.isReservationDateType(dateType.getType())) { + throw new RuntimeException("该预约日期不在可选范围内"); + } + long mappingCount = hardApptTimeService.count(new LambdaQueryWrapper() + .eq(HardApptTimeEntity::getMchId, mchId) + .eq(HardApptTimeEntity::getApptId, apptId) + .eq(HardApptTimeEntity::getType, dateType.getType())); + if (mappingCount == 0) { + throw new RuntimeException("该日期未配置此预约餐段"); } Availability availability = evaluateAvailability(appt, targetDate, now); @@ -320,14 +476,19 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic return Availability.unavailable(reason, start, end); } - private List findEnabledAppts(Long mchId) { + private List findEnabledAppts(Long mchId, HardApptReserveModeEnum reserveMode) { return hardApptService.list(new LambdaQueryWrapper() .eq(HardApptEntity::getMchId, mchId) .eq(HardApptEntity::getStatus, YesNoEnum.YES.getCode()) + .eq(HardApptEntity::getReserveMode, reserveMode.getType()) .orderByAsc(HardApptEntity::getHourType) .orderByDesc(HardApptEntity::getSort)); } + private HardApptReserveModeEnum resolveMerchantMode(Long mchId) { + return merchantModeService.resolve(mchId).reserveMode(); + } + private Map> groupMappingsByDateType(Long mchId, List appts) { if (apptListIsEmpty(appts)) { return Map.of(); @@ -359,12 +520,6 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic return HardApptReserveModeEnum.valueOfByType(appt.getReserveMode()); } - private static void validateMerchantId(Long mchId) { - if (ObjectUtil.isEmpty(mchId)) { - throw new RuntimeException("商户ID不能为空"); - } - } - private static LocalDateTime nowInShanghai() { return ZonedDateTime.now(RESERVATION_ZONE).toLocalDateTime(); } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptServiceImpl.java index e141c9168..5fe6501f8 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardApptServiceImpl.java @@ -14,9 +14,8 @@ import com.bsd.pjcloud.hard.api.entity.HardApptTimeEntity; import com.bsd.pjcloud.hard.api.vo.HardApptVo; import com.bsd.pjcloud.hard.mapper.HardApptMapper; import com.bsd.pjcloud.hard.mapper.HardApptTimeMapper; +import com.bsd.pjcloud.hard.service.HardApptMerchantModeService; import com.bsd.pjcloud.hard.service.HardApptService; -import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; -import com.bsd.pjcloud.wallet.api.vo.MchManageVo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -38,7 +37,7 @@ public class HardApptServiceImpl extends ServiceImpl scopePage(Page page, HardApptDto dto, DataScope of) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ObjectUtil.isNotEmpty(dto.getMchId()), HardApptEntity::getMchId, dto.getMchId()) .eq(ObjectUtil.isNotEmpty(dto.getDeptId()), HardApptEntity::getDeptId, dto.getDeptId()) + .eq(ObjectUtil.isNotEmpty(dto.getReserveMode()), HardApptEntity::getReserveMode, dto.getReserveMode()) .like(ObjectUtil.isNotEmpty(dto.getTitle()), HardApptEntity::getTitle, dto.getTitle()) .orderByAsc(HardApptEntity::getHourType); Page pageList = baseMapper.selectPageByScope(page, wrapper, of); @@ -121,7 +127,9 @@ public class HardApptServiceImpl extends ServiceImpl scopeList(HardApptDto dto, DataScope of) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ObjectUtil.isNotEmpty(dto.getMchId()), HardApptEntity::getMchId, dto.getMchId()) .eq(ObjectUtil.isNotEmpty(dto.getDeptId()), HardApptEntity::getDeptId, dto.getDeptId()) + .eq(ObjectUtil.isNotEmpty(dto.getReserveMode()), HardApptEntity::getReserveMode, dto.getReserveMode()) .like(ObjectUtil.isNotEmpty(dto.getTitle()), HardApptEntity::getTitle, dto.getTitle()) .orderByAsc(HardApptEntity::getHourType); List list = baseMapper.selectListByScope(wrapper, of); @@ -220,6 +228,17 @@ public class HardApptServiceImpl extends ServiceImpl() .eq(ObjectUtil.isNotEmpty(title), HardApptEntity::getTitle, title) diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImplTest.java b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImplTest.java index 04b60a00b..5d5e175aa 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImplTest.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptCycleRuleServiceImplTest.java @@ -1,13 +1,14 @@ package com.bsd.pjcloud.hard.service.impl; import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum; -import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.hard.api.constant.HardApptReserveModeEnum; import com.bsd.pjcloud.hard.api.dto.HardApptCycleRuleDto; import com.bsd.pjcloud.hard.api.dto.HardApptCycleRuleItemDto; import com.bsd.pjcloud.hard.api.entity.HardApptCycleRuleEntity; import com.bsd.pjcloud.hard.api.entity.HardApptCycleRuleItemEntity; import com.bsd.pjcloud.hard.mapper.HardApptCycleRuleItemMapper; import com.bsd.pjcloud.hard.mapper.HardApptCycleRuleMapper; +import com.bsd.pjcloud.hard.service.HardApptMerchantModeService; import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; import com.bsd.pjcloud.wallet.api.vo.MchManageVo; import org.junit.jupiter.api.Test; @@ -53,18 +54,33 @@ class HardApptCycleRuleServiceImplTest { assertTrue(exception.getMessage().contains("状态无效")); } + /** + * 日期配置允许稀疏保存,未提交的偏移表示该日不开放任何餐段。 + */ + @Test + void sparseTargetDayOffsetsAreAccepted() { + Fixture fixture = fixture(YesNoEnum.NO.getCode()); + fixture.candidate().getItemList().get(0).setTargetDayOffset(3); + + assertDoesNotThrow(() -> fixture.service().create(fixture.candidate())); + } + private Fixture fixture(String candidateStatus) { HardApptCycleRuleMapper ruleMapper = mock(HardApptCycleRuleMapper.class); HardApptCycleRuleItemMapper itemMapper = mock(HardApptCycleRuleItemMapper.class); RemoteMchService remoteMchService = mock(RemoteMchService.class); - TestableCycleRuleService service = new TestableCycleRuleService(itemMapper, remoteMchService); + HardApptMerchantModeService merchantModeService = mock(HardApptMerchantModeService.class); + TestableCycleRuleService service = new TestableCycleRuleService( + itemMapper, remoteMchService, merchantModeService); service.useMapper(ruleMapper); when(ruleMapper.selectCount(any())).thenReturn(0L); MchManageVo merchant = new MchManageVo(); merchant.setDeptId(20L); - when(remoteMchService.getById(10L)).thenReturn(R.ok(merchant)); + when(merchantModeService.resolve(10L)).thenReturn( + new HardApptMerchantModeService.MerchantModeContext( + merchant, HardApptReserveModeEnum.WEEKLY_CYCLE)); HardApptCycleRuleEntity existingRule = new HardApptCycleRuleEntity(); existingRule.setId(200L); @@ -105,8 +121,9 @@ class HardApptCycleRuleServiceImplTest { private static final class TestableCycleRuleService extends HardApptCycleRuleServiceImpl { private TestableCycleRuleService(HardApptCycleRuleItemMapper itemMapper, - RemoteMchService remoteMchService) { - super(itemMapper, remoteMchService); + RemoteMchService remoteMchService, + HardApptMerchantModeService merchantModeService) { + super(itemMapper, remoteMchService, merchantModeService); } private void useMapper(HardApptCycleRuleMapper mapper) { diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptMerchantModeServiceImplTest.java b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptMerchantModeServiceImplTest.java new file mode 100644 index 000000000..1a9123609 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptMerchantModeServiceImplTest.java @@ -0,0 +1,64 @@ +package com.bsd.pjcloud.hard.service.impl; + +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.hard.api.constant.HardApptReserveModeEnum; +import com.bsd.pjcloud.wallet.api.entity.mch.MchManageEntity; +import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; +import com.bsd.pjcloud.wallet.api.vo.MchManageVo; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class HardApptMerchantModeServiceImplTest { + + @Test + void missingNullAndBlankModesDefaultToTimeWindow() { + RemoteMchService remoteMchService = mock(RemoteMchService.class); + HardApptMerchantModeServiceImpl service = new HardApptMerchantModeServiceImpl(remoteMchService); + MchManageVo merchant = new MchManageVo(); + when(remoteMchService.getById(10L)).thenReturn(R.ok(merchant)); + + assertEquals(HardApptReserveModeEnum.TIME_WINDOW, service.resolve(10L).reserveMode()); + + MchManageEntity.TableReservationConf conf = new MchManageEntity.TableReservationConf(); + conf.setReserveMode(null); + merchant.setTableReservationConf(conf); + assertEquals(HardApptReserveModeEnum.TIME_WINDOW, service.resolve(10L).reserveMode()); + + conf.setReserveMode(" "); + assertEquals(HardApptReserveModeEnum.TIME_WINDOW, service.resolve(10L).reserveMode()); + } + + @Test + void explicitWeeklyModeIsResolved() { + RemoteMchService remoteMchService = mock(RemoteMchService.class); + HardApptMerchantModeServiceImpl service = new HardApptMerchantModeServiceImpl(remoteMchService); + MchManageVo merchant = new MchManageVo(); + MchManageEntity.TableReservationConf conf = new MchManageEntity.TableReservationConf(); + conf.setReserveMode(HardApptReserveModeEnum.WEEKLY_CYCLE.getType()); + merchant.setTableReservationConf(conf); + when(remoteMchService.getById(10L)).thenReturn(R.ok(merchant)); + + assertEquals(HardApptReserveModeEnum.WEEKLY_CYCLE, service.resolve(10L).reserveMode()); + } + + @Test + void invalidModeAndMerchantFailuresAreRejected() { + RemoteMchService remoteMchService = mock(RemoteMchService.class); + HardApptMerchantModeServiceImpl service = new HardApptMerchantModeServiceImpl(remoteMchService); + MchManageVo merchant = new MchManageVo(); + MchManageEntity.TableReservationConf conf = new MchManageEntity.TableReservationConf(); + conf.setReserveMode("UNKNOWN"); + merchant.setTableReservationConf(conf); + when(remoteMchService.getById(10L)).thenReturn(R.ok(merchant)); + when(remoteMchService.getById(20L)).thenReturn(R.ok(null)); + when(remoteMchService.getById(30L)).thenReturn(R.failed("钱包服务不可用")); + + assertThrows(RuntimeException.class, () -> service.resolve(10L)); + assertThrows(RuntimeException.class, () -> service.resolve(20L)); + assertThrows(RuntimeException.class, () -> service.resolve(30L)); + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImplTest.java b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImplTest.java index a36096f90..5e0fd958d 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImplTest.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardApptReservationServiceImplTest.java @@ -9,11 +9,13 @@ import com.bsd.pjcloud.hard.api.vo.HardApptCycleRuleVo; import com.bsd.pjcloud.hard.api.vo.HardApptDateOptionsVo; import com.bsd.pjcloud.hard.api.vo.HardApptTimeCascadeVo; import com.bsd.pjcloud.hard.service.HardApptCycleRuleService; +import com.bsd.pjcloud.hard.service.HardApptMerchantModeService; import com.bsd.pjcloud.hard.service.HardApptService; import com.bsd.pjcloud.hard.service.HardApptTimeService; import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.bsd.pjcloud.hard.service.support.HardApptCycleRuleCalculator; +import com.bsd.pjcloud.wallet.api.vo.MchManageVo; import org.junit.jupiter.api.Test; import java.time.LocalDate; @@ -136,7 +138,7 @@ class HardApptReservationServiceImplTest { } @Test - void v3ListCanContainDynamicAndWeeklyModesTogether() { + void v3ListOnlyReturnsDynamicRulesForTimeWindowMerchant() { HardApptService apptService = mock(HardApptService.class); HardApptTimeService timeService = mock(HardApptTimeService.class); HardApptEntity dynamic = timeWindowAppt(); @@ -156,15 +158,15 @@ class HardApptReservationServiceImplTest { HardApptCycleRuleService cycleRuleService = mock(HardApptCycleRuleService.class); when(cycleRuleService.listEnabledConfigurations(10L)).thenReturn(List.of(cycleRule)); - List result = new HardApptReservationServiceImpl(apptService, timeService, cycleRuleService) + List result = new HardApptReservationServiceImpl( + apptService, timeService, cycleRuleService, + merchantModeService(HardApptReserveModeEnum.TIME_WINDOW)) .listOptions(10L, LocalDateTime.of(2026, 7, 24, 17, 0)); - assertEquals(8, result.size()); + assertEquals(1, result.size()); assertEquals(1, result.get(0).getApptList().size()); - assertEquals(7, result.stream() - .filter(day -> day.getApptList().stream() - .anyMatch(option -> HardApptReserveModeEnum.WEEKLY_CYCLE.getType().equals(option.getReserveMode()))) - .count()); + assertTrue(result.stream().flatMap(day -> day.getApptList().stream()) + .allMatch(option -> HardApptReserveModeEnum.TIME_WINDOW.getType().equals(option.getReserveMode()))); } @Test @@ -179,7 +181,9 @@ class HardApptReservationServiceImplTest { HardApptCycleRuleService cycleRuleService = mock(HardApptCycleRuleService.class); when(cycleRuleService.listEnabledConfigurations(10L)).thenReturn(List.of(rule)); - List result = new HardApptReservationServiceImpl(apptService, timeService, cycleRuleService) + List result = new HardApptReservationServiceImpl( + apptService, timeService, cycleRuleService, + merchantModeService(HardApptReserveModeEnum.WEEKLY_CYCLE)) .listOptions(10L, LocalDateTime.of(2026, 7, 24, 18, 0)); assertEquals(List.of(LocalDate.of(2026, 7, 28), LocalDate.of(2026, 7, 30)), @@ -204,19 +208,34 @@ class HardApptReservationServiceImplTest { mapping.setApptId(1L); mapping.setType(HardApptTimeTypeEnum.TODAY.getType()); when(apptService.list(org.mockito.ArgumentMatchers.>any())) - .thenReturn(List.of(dynamic, weekly)); + .thenReturn(List.of(dynamic)); when(timeService.list(org.mockito.ArgumentMatchers.>any())) .thenReturn(List.of(mapping)); HardApptCycleRuleService cycleRuleService = mock(HardApptCycleRuleService.class); List result = - new HardApptReservationServiceImpl(apptService, timeService, cycleRuleService) + new HardApptReservationServiceImpl( + apptService, timeService, cycleRuleService, + merchantModeService(HardApptReserveModeEnum.TIME_WINDOW)) .listLegacyOptions(10L); assertEquals(1, result.size()); assertEquals(List.of(1L), result.get(0).getApptList().stream().map(HardApptEntity::getId).toList()); } + @Test + void legacyListsAreEmptyForWeeklyMerchant() { + HardApptService apptService = mock(HardApptService.class); + HardApptTimeService timeService = mock(HardApptTimeService.class); + HardApptCycleRuleService cycleRuleService = mock(HardApptCycleRuleService.class); + HardApptReservationServiceImpl service = new HardApptReservationServiceImpl( + apptService, timeService, cycleRuleService, + merchantModeService(HardApptReserveModeEnum.WEEKLY_CYCLE)); + + assertTrue(service.listLegacyFlatOptions(10L).isEmpty()); + assertTrue(service.listLegacyOptions(10L).isEmpty()); + } + @Test void validationRejectsWrongMerchantDisabledRuleTamperedDateAndMissingWeeklyDate() { HardApptEntity dynamic = timeWindowAppt(); @@ -283,7 +302,7 @@ class HardApptReservationServiceImplTest { RuntimeException exception = assertThrows(RuntimeException.class, () -> service.validateReservation(10L, null, null, "1", targetDate, null, now)); - assertTrue(exception.getMessage().contains("动态预约餐段不能为空")); + assertTrue(exception.getMessage().contains("周期预约规则不能为空")); assertEquals(100L, service.validateReservation(10L, null, 100L, "1", targetDate, null, now).apptRuleId()); } @@ -311,10 +330,19 @@ class HardApptReservationServiceImplTest { HardApptService apptService = mock(HardApptService.class); HardApptTimeService timeService = mock(HardApptTimeService.class); HardApptCycleRuleService cycleRuleService = mock(HardApptCycleRuleService.class); + HardApptMerchantModeService merchantModeService = merchantModeService( + HardApptReserveModeEnum.valueOfByType(appt.getReserveMode())); when(apptService.getById(appt.getId())).thenReturn(appt); when(timeService.count(any())).thenReturn(mappingCount); when(cycleRuleService.listEnabledConfigurations(appt.getMchId())).thenReturn(rules); - return new HardApptReservationServiceImpl(apptService, timeService, cycleRuleService); + return new HardApptReservationServiceImpl(apptService, timeService, cycleRuleService, merchantModeService); + } + + private HardApptMerchantModeService merchantModeService(HardApptReserveModeEnum reserveMode) { + HardApptMerchantModeService service = mock(HardApptMerchantModeService.class); + when(service.resolve(any())).thenReturn( + new HardApptMerchantModeService.MerchantModeContext(new MchManageVo(), reserveMode)); + return service; } private HardApptCycleRuleVo cycleRule(Long id, Long mchId, diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/mch/MchManageEntity.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/mch/MchManageEntity.java index d73eb6b63..f12be2c1a 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/mch/MchManageEntity.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/mch/MchManageEntity.java @@ -231,6 +231,11 @@ public class MchManageEntity extends Model { @Data public static class TableReservationConf { + /** + * 商户当前使用的预约模式。历史 JSON 缺少该属性时由字段默认值兼容为动态时间窗。 + */ + private String reserveMode = "TIME_WINDOW"; + /** * 订餐购物车是否只允许有一个商品 */ diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java index 5d95711aa..5077a40c8 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java @@ -107,6 +107,7 @@ public class MchManageServiceImpl extends ServiceImpl