feat:加入预约时间模式

This commit is contained in:
dzc committed 2026-07-22 13:42:25 +08:00
1 parent 6b2a95cc65
commit 61bb43e5c5
14 files changed
+500 -100

No files matched your search

@@ -7,7 +7,7 @@ import lombok.Getter;
import java.util.Arrays;
/**
* 订餐预约限制模式。
* 订餐预约限制模式
*/
@AllArgsConstructor
@Getter
@@ -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<List<HardApptEntity>> appList(@ParameterObject HardApptDto dto) {
List<HardApptEntity> list = hardApptService.list(new LambdaQueryWrapper<HardApptEntity>()
.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<List<HardApptDateOptionsVo>> appV3List(@ParameterObject HardApptDto dto) {
@@ -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<List<HardApptDateOptionsVo>> 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')")
@@ -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) {
}
}
@@ -12,10 +12,21 @@ import java.util.List;
*/
public interface HardApptReservationService {
List<HardApptEntity> listLegacyFlatOptions(Long mchId);
List<HardApptTimeCascadeVo> listLegacyOptions(Long mchId);
List<HardApptDateOptionsVo> listOptions(Long mchId);
/**
* 按今天起连续自然日推演商户最终预约视图,供管理端核对多条规则叠加结果。
*
* @param mchId 商户ID
* @param days 推演自然日数量,包含今天
* @return 每个自然日及其最终餐段配置
*/
List<HardApptDateOptionsVo> previewOptions(Long mchId, Integer days);
ReservationValidation validateReservation(Long mchId, Long apptId, Long apptRuleId, String hourType,
LocalDate apptDate, String legacyApptTimeType);
@@ -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<HardApptCycleRuleVo> scopePage(Page page, HardApptCycleRuleDto dto, DataScope dataScope) {
@@ -147,12 +150,11 @@ public class HardApptCycleRuleServiceImpl
}
List<HardApptCycleRuleItemDto> items = normalizeItems(dto.getItemList(), dto.getTargetDayCount());
R<MchManageVo> 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);
@@ -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<MchManageVo> 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);
}
}
@@ -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<HardApptEntity> listLegacyFlatOptions(Long mchId) {
HardApptReserveModeEnum merchantMode = resolveMerchantMode(mchId);
if (merchantMode != HardApptReserveModeEnum.TIME_WINDOW) {
return List.of();
}
return findEnabledAppts(mchId, HardApptReserveModeEnum.TIME_WINDOW);
}
@Override
public List<HardApptTimeCascadeVo> listLegacyOptions(Long mchId) {
validateMerchantId(mchId);
List<HardApptEntity> 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<HardApptEntity> dynamicAppts = findEnabledAppts(mchId, merchantMode);
if (dynamicAppts.isEmpty()) {
return List.of();
}
@@ -88,28 +104,26 @@ public class HardApptReservationServiceImpl implements HardApptReservationServic
}
List<HardApptDateOptionsVo> listOptions(Long mchId, LocalDateTime now) {
validateMerchantId(mchId);
List<HardApptEntity> allAppts = findEnabledAppts(mchId);
HardApptReserveModeEnum merchantMode = resolveMerchantMode(mchId);
LocalDate today = now.toLocalDate();
Map<LocalDate, List<HardApptOptionVo>> optionsByDate = new TreeMap<>();
List<HardApptEntity> dynamicAppts = allAppts.stream()
.filter(appt -> resolveMode(appt) == HardApptReserveModeEnum.TIME_WINDOW)
.toList();
Map<String, Set<Long>> apptIdsByDateType = groupMappingsByDateType(mchId, dynamicAppts);
for (HardApptTimeTypeEnum dateType : HardApptTimeTypeEnum.values()) {
if (!HardApptTimeTypeEnum.isReservationDateType(dateType.getType())) {
continue;
if (merchantMode == HardApptReserveModeEnum.TIME_WINDOW) {
List<HardApptEntity> dynamicAppts = findEnabledAppts(mchId, merchantMode);
Map<String, Set<Long>> apptIdsByDateType = groupMappingsByDateType(mchId, dynamicAppts);
for (HardApptTimeTypeEnum dateType : HardApptTimeTypeEnum.values()) {
if (!HardApptTimeTypeEnum.isReservationDateType(dateType.getType())) {
continue;
}
Set<Long> 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<Long> 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<HardApptDateOptionsVo> previewOptions(Long mchId, Integer days) {
return previewOptions(mchId, days, nowInShanghai());
}
List<HardApptDateOptionsVo> 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<LocalDate, LinkedHashMap<String, HardApptOptionVo>> 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<LocalDate, LinkedHashMap<String, HardApptOptionVo>> optionsByDate) {
List<HardApptEntity> dynamicAppts = findEnabledAppts(mchId, HardApptReserveModeEnum.TIME_WINDOW);
Map<String, Set<Long>> 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<Long> 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<LocalDate, LinkedHashMap<String, HardApptOptionVo>> 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<String, HardApptOptionVo> 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<HardApptTimeEntity>()
.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<HardApptTimeEntity>()
.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<HardApptEntity> findEnabledAppts(Long mchId) {
private List<HardApptEntity> findEnabledAppts(Long mchId, HardApptReserveModeEnum reserveMode) {
return hardApptService.list(new LambdaQueryWrapper<HardApptEntity>()
.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<String, Set<Long>> groupMappingsByDateType(Long mchId, List<HardApptEntity> 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();
}
@@ -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<HardApptMapper, HardApptEnt
private final HardApptTimeMapper hardApptTimeMapper;
private final RemoteMchService remoteMchService;
private final HardApptMerchantModeService merchantModeService;
/**
* 新增
@@ -46,16 +45,18 @@ public class HardApptServiceImpl extends ServiceImpl<HardApptMapper, HardApptEnt
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(HardApptDto dto) {
HardApptMerchantModeService.MerchantModeContext merchantContext = requireTimeWindowMerchant(dto.getMchId());
if (ObjectUtil.isNotEmpty(dto.getReserveMode())
&& HardApptReserveModeEnum.valueOfByType(dto.getReserveMode()) != HardApptReserveModeEnum.TIME_WINDOW) {
throw new RuntimeException("动态餐段只能使用动态时间窗模式");
}
dto.setReserveMode(HardApptReserveModeEnum.TIME_WINDOW.getType());
normalizeAndValidateRule(dto, HardApptReserveModeEnum.TIME_WINDOW.getType());
// title不能重复
if (existTitle(dto.getTitle(), dto.getMchId(), null)) {
throw new RuntimeException("标题不能重复");
}
// 查询商户id
MchManageVo mchDeatil = remoteMchService.getById(dto.getMchId()).getData();
if (ObjectUtil.isNotEmpty(mchDeatil)) {
dto.setDeptId(mchDeatil.getDeptId());
}
dto.setDeptId(merchantContext.merchant().getDeptId());
baseMapper.insert(dto);
createApptTime(dto, dto.getReserveTimeList());
return dto.getId();
@@ -71,16 +72,19 @@ public class HardApptServiceImpl extends ServiceImpl<HardApptMapper, HardApptEnt
if (existing == null) {
throw new RuntimeException("时间预约不存在");
}
normalizeAndValidateRule(dto, existing.getReserveMode());
if (ObjectUtil.notEqual(existing.getMchId(), dto.getMchId())) {
throw new RuntimeException("预约餐段不允许更换商户");
}
if (HardApptReserveModeEnum.valueOfByType(existing.getReserveMode()) != HardApptReserveModeEnum.TIME_WINDOW) {
throw new RuntimeException("周期预约餐段由周期预约规则维护");
}
HardApptMerchantModeService.MerchantModeContext merchantContext = requireTimeWindowMerchant(dto.getMchId());
normalizeAndValidateRule(dto, HardApptReserveModeEnum.TIME_WINDOW.getType());
// title不能重复
if (existTitle(dto.getTitle(), dto.getMchId(), dto.getId())) {
throw new RuntimeException("标题不能重复");
}
// 查询商户id
MchManageVo mchDeatil = remoteMchService.getById(dto.getMchId()).getData();
if (ObjectUtil.isNotEmpty(mchDeatil)) {
dto.setDeptId(mchDeatil.getDeptId());
}
dto.setDeptId(merchantContext.merchant().getDeptId());
baseMapper.updateById(dto);
createApptTime(dto, dto.getReserveTimeList());
return dto.getId();
@@ -104,7 +108,9 @@ public class HardApptServiceImpl extends ServiceImpl<HardApptMapper, HardApptEnt
@Override
public Page<HardApptVo> scopePage(Page page, HardApptDto dto, DataScope of) {
LambdaQueryWrapper<HardApptEntity> wrapper = new LambdaQueryWrapper<HardApptEntity>()
.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<HardApptMapper, HardApptEnt
@Override
public List<HardApptVo> scopeList(HardApptDto dto, DataScope of) {
LambdaQueryWrapper<HardApptEntity> wrapper = new LambdaQueryWrapper<HardApptEntity>()
.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<HardApptEntity> list = baseMapper.selectListByScope(wrapper, of);
@@ -220,6 +228,17 @@ public class HardApptServiceImpl extends ServiceImpl<HardApptMapper, HardApptEnt
}
}
/**
* 餐段时间窗只在商户当前选择动态模式时允许维护,切换模式不会删除原有数据。
*/
private HardApptMerchantModeService.MerchantModeContext requireTimeWindowMerchant(Long mchId) {
HardApptMerchantModeService.MerchantModeContext context = merchantModeService.resolve(mchId);
if (context.reserveMode() != HardApptReserveModeEnum.TIME_WINDOW) {
throw new RuntimeException("当前商户使用按周集中预约,请在周期预约规则中配置");
}
return context;
}
private boolean existTitle(String title, Long mchId, Long id) {
Long count = baseMapper.selectCount(new LambdaQueryWrapper<HardApptEntity>()
.eq(ObjectUtil.isNotEmpty(title), HardApptEntity::getTitle, title)
@@ -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) {
@@ -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));
}
}
@@ -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<HardApptDateOptionsVo> result = new HardApptReservationServiceImpl(apptService, timeService, cycleRuleService)
List<HardApptDateOptionsVo> 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<HardApptDateOptionsVo> result = new HardApptReservationServiceImpl(apptService, timeService, cycleRuleService)
List<HardApptDateOptionsVo> 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.<Wrapper<HardApptEntity>>any()))
.thenReturn(List.of(dynamic, weekly));
.thenReturn(List.of(dynamic));
when(timeService.list(org.mockito.ArgumentMatchers.<Wrapper<HardApptTimeEntity>>any()))
.thenReturn(List.of(mapping));
HardApptCycleRuleService cycleRuleService = mock(HardApptCycleRuleService.class);
List<HardApptTimeCascadeVo> 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,
@@ -231,6 +231,11 @@ public class MchManageEntity extends Model<MchManageEntity> {
@Data
public static class TableReservationConf {
/**
* 商户当前使用的预约模式。历史 JSON 缺少该属性时由字段默认值兼容为动态时间窗。
*/
private String reserveMode = "TIME_WINDOW";
/**
* 订餐购物车是否只允许有一个商品
*/
@@ -107,6 +107,7 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(MchManageEntity dto) {
normalizeTableReservationConf(dto);
if (ObjectUtil.isEmpty(dto.getParentId())) dto.setParentId(-1L);
if (existName(dto.getName(), dto.getTypes(), null)) {
throw new RuntimeException("商户名不能重复");
@@ -138,6 +139,7 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
beforeInvocation = true
)
public void edit(MchManageEntity dto) {
normalizeTableReservationConf(dto);
if (existName(dto.getName(), dto.getTypes(), dto.getId())) {
throw new RuntimeException("商户名不能重复");
}
@@ -176,6 +178,26 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
// walletThirdZyService.syncAllMch(dto.getId(), Boolean.FALSE);
}
/**
* 只规范化本次明确提交的 JSON 配置,避免其他局部更新接口覆盖数据库中的原配置。
*/
private void normalizeTableReservationConf(MchManageEntity dto) {
MchManageEntity.TableReservationConf conf = dto.getTableReservationConf();
if (conf == null) {
return;
}
String reserveMode = conf.getReserveMode();
if (reserveMode == null || reserveMode.trim().isEmpty()) {
conf.setReserveMode("TIME_WINDOW");
return;
}
reserveMode = reserveMode.trim();
if (!"TIME_WINDOW".equals(reserveMode) && !"WEEKLY_CYCLE".equals(reserveMode)) {
throw new RuntimeException("预约时间模式无效");
}
conf.setReserveMode(reserveMode);
}
@CacheEvict(
value = CacheConstants.WALLET_VIEWS_CANTEEN_MCH,
allEntries = true,
@@ -628,4 +650,4 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
private MchManageServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
}
}