From bfa5dc6a269045e33f6fe0682c7b0dff3d945e5b Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 13 Apr 2026 14:23:35 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A2=E5=8D=95=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/order/app/HardAppCreateOrderDto.java | 17 +- .../app/HardAppCreateTakeOutOrderDto.java | 20 +-- .../app/HardTakeOutDeliveryTimeDayVO.java | 21 +++ .../app/HardTakeOutDeliveryTimeOptionVO.java | 48 +++++ .../app/HardTakeOutDeliveryTimeSlotVO.java | 23 +++ .../controller/order/HardOrderController.java | 19 ++ .../HardTakeOutDeliveryTimeOptionService.java | 21 +++ ...dTakeOutDeliveryTimeOptionServiceImpl.java | 118 +++++++++++++ .../impl/order/HardOrderServiceImpl.java | 74 +++++++- .../pjcloud/hard/util/MchJobTimeUtils.java | 167 ++++++++++++++++++ 10 files changed, 484 insertions(+), 44 deletions(-) create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeDayVO.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeOptionVO.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeSlotVO.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardTakeOutDeliveryTimeOptionService.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardTakeOutDeliveryTimeOptionServiceImpl.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/util/MchJobTimeUtils.java diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateOrderDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateOrderDto.java index 177c920e9..4e14ce098 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateOrderDto.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateOrderDto.java @@ -10,10 +10,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; - @Builder @AllArgsConstructor @NoArgsConstructor @@ -57,18 +53,7 @@ public class HardAppCreateOrderDto { // 地址 private HardAppTakeOutAddressDto address; - // 预约时间信息 + // 预约时间信息(外卖:立即送/预约时段) private HardAppTakeOutApptDto appt; - @Schema(description = "IMMEDIATE立即送 | SCHEDULED预约时段") - private String expectDeliveryType; - - private LocalDate expectDeliveryDate; - - private LocalTime expectDeliveryStart; - - private LocalTime expectDeliveryEnd; - - private LocalDateTime expectArriveTime; - } diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateTakeOutOrderDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateTakeOutOrderDto.java index 1ff290906..07d64ae4e 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateTakeOutOrderDto.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/app/HardAppCreateTakeOutOrderDto.java @@ -4,10 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; - @Data public class HardAppCreateTakeOutOrderDto { @@ -32,21 +28,7 @@ public class HardAppCreateTakeOutOrderDto { * 外卖预约信息 */ @NotNull(message = "预约时间信息不能为空") + @Schema(description = "立即送/预约时段;字段见 HardAppTakeOutApptDto") private HardAppTakeOutApptDto appt; - @Schema(description = "IMMEDIATE立即送(默认)| SCHEDULED预约时段") - private String expectDeliveryType; - - @Schema(description = "预约送达日期,SCHEDULED 时必填") - private LocalDate expectDeliveryDate; - - @Schema(description = "预约时段开始,SCHEDULED 时必填") - private LocalTime expectDeliveryStart; - - @Schema(description = "预约时段结束,SCHEDULED 时必填") - private LocalTime expectDeliveryEnd; - - @Schema(description = "立即送预计送达时间,可选") - private LocalDateTime expectArriveTime; - } diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeDayVO.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeDayVO.java new file mode 100644 index 000000000..1a9d098d1 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeDayVO.java @@ -0,0 +1,21 @@ +package com.bsd.pjcloud.hard.api.vo.order.app; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +/** + * 某一配送日的可预约时段列表(多天查询时按天分组)。 + */ +@Data +@Schema(description = "单日可预约配送时段") +public class HardTakeOutDeliveryTimeDayVO { + + @Schema(description = "配送日") + private LocalDate expectDate; + + @Schema(description = "该日可选时段") + private List slots; +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeOptionVO.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeOptionVO.java new file mode 100644 index 000000000..363151b4f --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeOptionVO.java @@ -0,0 +1,48 @@ +package com.bsd.pjcloud.hard.api.vo.order.app; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +/** + * 配送详情页:根据商户 {@code jobTime} 生成可预约时段;支持连续多天(今天/明天/后天等)。 + */ +@Data +@Schema(description = "外卖可预约配送时段(按商户营业时间切分)") +public class HardTakeOutDeliveryTimeOptionVO { + + @Schema(description = "商户ID") + private Long merchantId; + + @Schema(description = "商户名称") + private String merchantName; + + @Schema(description = "原始营业时间串,如 09:00-12:00,16:00-19:00") + private String jobTime; + + @Schema(description = "本次查询的起始配送日(未传 expectDate 时服务端为「今天」)") + private LocalDate startDate; + + @Schema(description = "连续查询的天数(含起始日),如 3 表示今天+明天+后天") + private Integer dayCount; + + @Schema(description = "每格时长(分钟)") + private Integer slotMinutes; + + @Schema(description = "「今天」时,过滤掉当前时刻之前不足 leadMinutes 分钟的格子;其它日期不按此过滤") + private Integer leadMinutes; + + @Schema(description = "jobTime 是否解析出有效营业时段") + private Boolean jobTimeValid; + + @Schema(description = "按天分组的时段;前端左侧「今天/明天」切换时取对应元素的 slots") + private List daySlots; + + /** + * 仅当 {@code dayCount == 1} 时与 {@link #daySlots} 中单日 slots 一致,便于旧前端兼容;多天时请使用 {@link #daySlots}。 + */ + @Schema(description = "单日查询时与 daySlots[0].slots 相同;多天时为 null") + private List slots; +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeSlotVO.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeSlotVO.java new file mode 100644 index 000000000..224f22a8c --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/app/HardTakeOutDeliveryTimeSlotVO.java @@ -0,0 +1,23 @@ +package com.bsd.pjcloud.hard.api.vo.order.app; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalTime; + +/** + * 外卖预约可选时段(与 {@link com.bsd.pjcloud.hard.api.dto.order.app.HardAppTakeOutApptDto} 中 expectDeliveryStart/End 对应)。 + */ +@Data +@Schema(description = "外卖预约可选时段") +public class HardTakeOutDeliveryTimeSlotVO { + + @Schema(description = "时段开始") + @JsonFormat(pattern = "HH:mm:ss") + private LocalTime expectDeliveryStart; + + @Schema(description = "时段结束") + @JsonFormat(pattern = "HH:mm:ss") + private LocalTime expectDeliveryEnd; +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java index 0bc99c25f..a7b249470 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java @@ -40,12 +40,14 @@ import com.bsd.pjcloud.hard.api.dto.order.app.HardAppCreateTakeOutOrderDto; import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateTakeOutStatusDto; import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity; import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; +import com.bsd.pjcloud.hard.api.vo.order.app.HardTakeOutDeliveryTimeOptionVO; import com.bsd.pjcloud.hard.api.vo.order.excel.HardOrderExcelVo; import com.bsd.pjcloud.hard.mapper.order.HardOrderMapper; import com.bsd.pjcloud.hard.api.vo.order.HardOnlineCanteenStatsVo; import com.bsd.pjcloud.hard.service.HardManagerService; import com.bsd.pjcloud.hard.service.HardOnlineCanteenStatsService; import com.bsd.pjcloud.hard.service.HardOrderService; +import com.bsd.pjcloud.hard.service.HardTakeOutDeliveryTimeOptionService; import com.bsd.pjcloud.hard.service.HardOrderSyncService; import com.bsd.pjcloud.wallet.api.constant.WalletTypeEnum; import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; @@ -60,8 +62,10 @@ import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.format.annotation.DateTimeFormat; import java.io.IOException; +import java.time.LocalDate; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.HashMap; @@ -82,6 +86,7 @@ import java.util.Map; public class HardOrderController { private final HardOrderService hardwareOrderService; + private final HardTakeOutDeliveryTimeOptionService takeOutDeliveryTimeOptionService; private final HardManagerService hardManagerService; private final HardOnlineCanteenStatsService hardOnlineCanteenStatsService; private final HardOrderSyncService hardOrderSyncService; @@ -206,6 +211,20 @@ public class HardOrderController { return R.ok(hardwareOrderService.appCreateOrder(dto)); } + @Operation(summary = "外卖-查询可预约配送时段(支持连续多天)", + description = "配送详情页:按商户 MchManage.jobTime 切分时段;与下单校验一致。" + + "不传 expectDate 时从「今天」起算;days 为连续天数(含起始日),如 days=3 表示今天+明天+后天。" + + "仅「日历日为今天」时按 leadMinutes 过滤已过近的格子。") + @GetMapping("/app/take-out/delivery-time-slots") + public R takeOutDeliveryTimeSlots( + @RequestParam Long merchantId, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate expectDate, + @RequestParam(required = false) Integer days, + @RequestParam(required = false) Integer slotMinutes, + @RequestParam(required = false) Integer leadMinutes) { + return takeOutDeliveryTimeOptionService.listDeliveryTimeSlots(merchantId, expectDate, days, slotMinutes, leadMinutes); + } + @Operation(summary = "app创建食堂外卖订单", description = "app创建食堂外卖订单") @PostMapping("/app/create-take-out-order") @Idempotent(expireTime = 3, key = "'/app/hard-order/app/create-take-out-order' + #dto.getCarParentId() ", info = "正在创建订单中,请勿重复操作") diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardTakeOutDeliveryTimeOptionService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardTakeOutDeliveryTimeOptionService.java new file mode 100644 index 000000000..a5e6dfdc1 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardTakeOutDeliveryTimeOptionService.java @@ -0,0 +1,21 @@ +package com.bsd.pjcloud.hard.service; + +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.hard.api.vo.order.app.HardTakeOutDeliveryTimeOptionVO; + +import java.time.LocalDate; + +/** + * 外卖配送详情:按商户 {@code jobTime} 生成可下单预约时段。 + */ +public interface HardTakeOutDeliveryTimeOptionService { + + /** + * @param merchantId 商户主键 + * @param expectDate 起始配送日,{@code null} 表示从「今天」起算 + * @param days 连续天数(含起始日),默认 1,最大 14;如 3 表示今天+明天+后天(起始日为今天时) + * @param slotMinutes 切分步长(分钟),默认 30 + * @param leadMinutes 仅对「日历日为今天」的格子生效:去掉当前时刻 + leadMinutes 之前的格子;默认 30 + */ + R listDeliveryTimeSlots(Long merchantId, LocalDate expectDate, Integer days, Integer slotMinutes, Integer leadMinutes); +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardTakeOutDeliveryTimeOptionServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardTakeOutDeliveryTimeOptionServiceImpl.java new file mode 100644 index 000000000..a016a4c8a --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardTakeOutDeliveryTimeOptionServiceImpl.java @@ -0,0 +1,118 @@ +package com.bsd.pjcloud.hard.service.impl; + +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.hard.api.vo.order.app.HardTakeOutDeliveryTimeDayVO; +import com.bsd.pjcloud.hard.api.vo.order.app.HardTakeOutDeliveryTimeOptionVO; +import com.bsd.pjcloud.hard.api.vo.order.app.HardTakeOutDeliveryTimeSlotVO; +import com.bsd.pjcloud.hard.service.HardTakeOutDeliveryTimeOptionService; +import com.bsd.pjcloud.hard.util.MchJobTimeUtils; +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.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class HardTakeOutDeliveryTimeOptionServiceImpl implements HardTakeOutDeliveryTimeOptionService { + + private static final int DEFAULT_SLOT_MINUTES = 30; + private static final int DEFAULT_LEAD_MINUTES = 30; + private static final int DEFAULT_DAY_COUNT = 1; + private static final int MAX_DAY_COUNT = 14; + private static final int MAX_SLOT_MINUTES = 24 * 60; + + private final RemoteMchService remoteMchService; + + @Override + public R listDeliveryTimeSlots(Long merchantId, LocalDate expectDate, Integer days, Integer slotMinutes, Integer leadMinutes) { + if (merchantId == null) { + return R.failed("商户ID不能为空"); + } + LocalDate today = LocalDate.now(); + LocalDate startDate = expectDate != null ? expectDate : today; + if (startDate.isBefore(today)) { + return R.failed("不能选择过去的日期"); + } + int dayCount = days == null || days < 1 ? DEFAULT_DAY_COUNT : Math.min(days, MAX_DAY_COUNT); + int sm = slotMinutes == null || slotMinutes <= 0 ? DEFAULT_SLOT_MINUTES : Math.min(slotMinutes, MAX_SLOT_MINUTES); + int lm = leadMinutes == null || leadMinutes < 0 ? DEFAULT_LEAD_MINUTES : leadMinutes; + + R r = remoteMchService.getById(merchantId); + if (r == null || !r.isOk() || r.getData() == null) { + return R.failed("商户不存在"); + } + MchManageVo mch = r.getData(); + String jobTime = mch.getJobTime(); + + HardTakeOutDeliveryTimeOptionVO vo = new HardTakeOutDeliveryTimeOptionVO(); + vo.setMerchantId(merchantId); + vo.setMerchantName(mch.getName()); + vo.setJobTime(jobTime); + vo.setStartDate(startDate); + vo.setDayCount(dayCount); + vo.setSlotMinutes(sm); + vo.setLeadMinutes(lm); + + if (MchJobTimeUtils.isBlankJobTime(jobTime)) { + vo.setJobTimeValid(false); + vo.setDaySlots(List.of()); + vo.setSlots(List.of()); + return R.ok(vo); + } + + List ranges = MchJobTimeUtils.splitJobTimeIntoSlots(jobTime, sm); + if (ranges.isEmpty()) { + vo.setJobTimeValid(false); + vo.setDaySlots(List.of()); + vo.setSlots(List.of()); + return R.ok(vo); + } + + vo.setJobTimeValid(true); + List daySlots = new ArrayList<>(dayCount); + for (int i = 0; i < dayCount; i++) { + LocalDate d = startDate.plusDays(i); + HardTakeOutDeliveryTimeDayVO dayVo = new HardTakeOutDeliveryTimeDayVO(); + dayVo.setExpectDate(d); + dayVo.setSlots(buildSlotsForCalendarDay(d, ranges, today, lm)); + daySlots.add(dayVo); + } + vo.setDaySlots(daySlots); + if (dayCount == 1 && daySlots.size() == 1) { + vo.setSlots(daySlots.get(0).getSlots()); + } else { + vo.setSlots(null); + } + return R.ok(vo); + } + + private static List buildSlotsForCalendarDay( + LocalDate expectDate, + List ranges, + LocalDate today, + int leadMinutes) { + LocalDateTime cutoff = null; + if (expectDate.equals(today)) { + cutoff = LocalDateTime.now().plusMinutes(leadMinutes); + } + List slots = new ArrayList<>(ranges.size()); + for (MchJobTimeUtils.TimeRange tr : ranges) { + if (cutoff != null) { + LocalDateTime slotStart = LocalDateTime.of(expectDate, tr.start); + if (slotStart.isBefore(cutoff)) { + continue; + } + } + HardTakeOutDeliveryTimeSlotVO row = new HardTakeOutDeliveryTimeSlotVO(); + row.setExpectDeliveryStart(tr.start); + row.setExpectDeliveryEnd(tr.end); + slots.add(row); + } + return slots; + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java index 1c11ecee8..3689f38e1 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java @@ -55,6 +55,7 @@ import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto; import com.bsd.pjcloud.hard.api.dto.order.act.HardCreateActOrderDto; import com.bsd.pjcloud.hard.api.dto.order.app.HardAppCreateFarmerOrderDto; import com.bsd.pjcloud.hard.api.dto.order.app.HardAppCreateOrderDto; +import com.bsd.pjcloud.hard.api.dto.order.app.HardAppTakeOutApptDto; import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateTakeOutStatusDto; import com.bsd.pjcloud.hard.api.dto.order.xfj.HardXfjFastCreateOrderDto; import com.bsd.pjcloud.hard.api.dto.spu.DeductionSpuStockDto; @@ -71,6 +72,7 @@ import com.bsd.pjcloud.hard.api.vo.manager.HardManagerVo; 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; +import com.bsd.pjcloud.hard.util.MchJobTimeUtils; import com.bsd.pjcloud.hard.api.vo.wechat.WechatMicropayVo; import com.bsd.pjcloud.hard.mapper.HardThirdMiOrderMapper; import com.bsd.pjcloud.hard.mapper.manager.HardManagerMapper; @@ -627,6 +629,7 @@ public class HardOrderServiceImpl extends ServiceImpl carList = hardCarService.getByParentCarId(dto.getCarParentId()); if (ObjectUtil.isEmpty(carList)) throw new RuntimeException("购物车不存在"); @@ -729,11 +732,7 @@ public class HardOrderServiceImpl extends ServiceImpl merged = mergeJobTimeIntervals(jobTime); + if (merged.isEmpty()) { + return false; + } + for (TimeRange r : merged) { + if (!r.start.isAfter(slotStart) && !r.end.isBefore(slotEnd)) { + return true; + } + } + return false; + } + + /** + * 单个时刻(如立即送预计送达的时间部分)是否落在营业时间内。 + */ + public static boolean isTimeWithinJobTime(String jobTime, LocalTime t) { + if (t == null) { + return false; + } + List merged = mergeJobTimeIntervals(jobTime); + for (TimeRange r : merged) { + if (!t.isBefore(r.start) && !t.isAfter(r.end)) { + return true; + } + } + return false; + } + + /** + * 解析并合并相邻或重叠的时段(如 09:00-12:00 与 12:00-14:00 合并为 09:00-14:00)。 + */ + public static List mergeJobTimeIntervals(String jobTime) { + List raw = new ArrayList<>(); + if (isBlankJobTime(jobTime)) { + return raw; + } + for (String part : jobTime.split(",")) { + part = StrUtil.trim(part); + if (StrUtil.isBlank(part)) { + continue; + } + String[] se = part.split("-"); + if (se.length != 2) { + continue; + } + LocalTime a = parseHm(StrUtil.trim(se[0])); + LocalTime b = parseHm(StrUtil.trim(se[1])); + if (a == null || b == null) { + continue; + } + if (!b.isAfter(a)) { + continue; + } + raw.add(new TimeRange(a, b)); + } + if (raw.isEmpty()) { + return raw; + } + raw.sort(Comparator.comparing(r -> r.start)); + List merged = new ArrayList<>(); + TimeRange cur = raw.get(0); + for (int i = 1; i < raw.size(); i++) { + TimeRange next = raw.get(i); + if (!next.start.isAfter(cur.end)) { + if (next.end.isAfter(cur.end)) { + cur = new TimeRange(cur.start, next.end); + } + } else { + merged.add(cur); + cur = next; + } + } + merged.add(cur); + return merged; + } + + /** + * 将合并后的营业时段按固定步长切成预约格子(整段落在原时段内;最后不足一个步长则丢弃)。 + * + * @param jobTime 商户 jobTime 原始串 + * @param slotMinutes 每格时长(分钟),如 30 + */ + public static List splitJobTimeIntoSlots(String jobTime, int slotMinutes) { + if (slotMinutes <= 0) { + return List.of(); + } + List merged = mergeJobTimeIntervals(jobTime); + List out = new ArrayList<>(); + for (TimeRange r : merged) { + LocalTime cursor = r.start; + while (true) { + LocalTime slotEnd = cursor.plusMinutes(slotMinutes); + if (slotEnd.isAfter(r.end)) { + break; + } + out.add(new TimeRange(cursor, slotEnd)); + cursor = slotEnd; + } + } + return out; + } + + private static LocalTime parseHm(String s) { + try { + return LocalTime.parse(s, HM_OPTIONAL_SEC); + } catch (DateTimeParseException e) { + try { + return LocalTime.parse(s, HM); + } catch (DateTimeParseException e2) { + return null; + } + } + } + + public static final class TimeRange { + public final LocalTime start; + public final LocalTime end; + + public TimeRange(LocalTime start, LocalTime end) { + this.start = start; + this.end = end; + } + } +}