订单部分代码
This commit is contained in:
1 parent
6cdc873a02
commit
bfa5dc6a26
10 files changed
+484
-44
No files matched your search
+1
-16
@@ -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;
|
||||
|
||||
}
|
||||
+1
-19
@@ -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;
|
||||
|
||||
}
|
||||
+21
@@ -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<HardTakeOutDeliveryTimeSlotVO> slots;
|
||||
}
|
||||
+48
@@ -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<HardTakeOutDeliveryTimeDayVO> daySlots;
|
||||
|
||||
/**
|
||||
* 仅当 {@code dayCount == 1} 时与 {@link #daySlots} 中单日 slots 一致,便于旧前端兼容;多天时请使用 {@link #daySlots}。
|
||||
*/
|
||||
@Schema(description = "单日查询时与 daySlots[0].slots 相同;多天时为 null")
|
||||
private List<HardTakeOutDeliveryTimeSlotVO> slots;
|
||||
}
|
||||
+23
@@ -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;
|
||||
}
|
||||
+19
@@ -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<HardTakeOutDeliveryTimeOptionVO> 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 = "正在创建订单中,请勿重复操作")
|
||||
|
||||
+21
@@ -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<HardTakeOutDeliveryTimeOptionVO> listDeliveryTimeSlots(Long merchantId, LocalDate expectDate, Integer days, Integer slotMinutes, Integer leadMinutes);
|
||||
}
|
||||
+118
@@ -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<HardTakeOutDeliveryTimeOptionVO> 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<MchManageVo> 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<MchJobTimeUtils.TimeRange> 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<HardTakeOutDeliveryTimeDayVO> 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<HardTakeOutDeliveryTimeSlotVO> buildSlotsForCalendarDay(
|
||||
LocalDate expectDate,
|
||||
List<MchJobTimeUtils.TimeRange> ranges,
|
||||
LocalDate today,
|
||||
int leadMinutes) {
|
||||
LocalDateTime cutoff = null;
|
||||
if (expectDate.equals(today)) {
|
||||
cutoff = LocalDateTime.now().plusMinutes(leadMinutes);
|
||||
}
|
||||
List<HardTakeOutDeliveryTimeSlotVO> 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;
|
||||
}
|
||||
}
|
||||
+65
-9
@@ -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<HardOrderMapper, HardOrder
|
||||
// 查询商户
|
||||
MchManageVo hardManager = remoteMchService.getById(parentCar.getMerchantId()).getData();
|
||||
if (ObjectUtil.isEmpty(hardManager)) throw new RuntimeException("商户不存在");
|
||||
validateTakeOutApptAgainstMerchantJobTime(hardManager, dto.getAppt());
|
||||
// 查询父购物车
|
||||
List<HardCarEntity> carList = hardCarService.getByParentCarId(dto.getCarParentId());
|
||||
if (ObjectUtil.isEmpty(carList)) throw new RuntimeException("购物车不存在");
|
||||
@@ -729,11 +732,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
canteenDeliveryOrderSyncDTO.setId(String.valueOf(orderObj.getId()));
|
||||
canteenDeliveryOrderSyncDTO.setFullAddress(dto.getAddress().getFullAddress());
|
||||
canteenDeliveryOrderSyncDTO.setBuildingId(dto.getAddress().getBuildingId());
|
||||
canteenDeliveryOrderSyncDTO.setExpectDeliveryType(StrUtil.emptyToDefault(dto.getExpectDeliveryType(), "IMMEDIATE"));
|
||||
canteenDeliveryOrderSyncDTO.setExpectDeliveryDate(dto.getExpectDeliveryDate());
|
||||
canteenDeliveryOrderSyncDTO.setExpectDeliveryStart(dto.getExpectDeliveryStart());
|
||||
canteenDeliveryOrderSyncDTO.setExpectDeliveryEnd(dto.getExpectDeliveryEnd());
|
||||
canteenDeliveryOrderSyncDTO.setExpectArriveTime(dto.getExpectArriveTime());
|
||||
fillCanteenExpectDeliveryFromAppt(canteenDeliveryOrderSyncDTO, dto.getAppt());
|
||||
remoteCanteenDeliveryOrderService.syncFromOrder(canteenDeliveryOrderSyncDTO);
|
||||
|
||||
return orderObj.getId();
|
||||
@@ -1897,24 +1896,81 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
|
||||
/**
|
||||
* 外卖预约配送:SCHEDULED 须带日期与时段;立即送默认 IMMEDIATE。
|
||||
* 预约/立即送时间须落在商户 {@link com.bsd.pjcloud.wallet.api.entity.mch.MchManageEntity#getJobTime()} 营业时段内(如 {@code 09:00-12:00,16:00-19:00})。
|
||||
*/
|
||||
private void validateTakeOutApptAgainstMerchantJobTime(MchManageVo merchant, HardAppTakeOutApptDto appt) {
|
||||
if (appt == null || merchant == null) {
|
||||
return;
|
||||
}
|
||||
String type = StrUtil.emptyToDefault(appt.getExpectDeliveryType(), "IMMEDIATE").trim();
|
||||
if ("IMMEDIATE".equalsIgnoreCase(type)) {
|
||||
if (appt.getExpectArriveTime() != null) {
|
||||
if (MchJobTimeUtils.isBlankJobTime(merchant.getJobTime())) {
|
||||
throw new RuntimeException("商户未配置营业时间");
|
||||
}
|
||||
if (!MchJobTimeUtils.isTimeWithinJobTime(merchant.getJobTime(), appt.getExpectArriveTime().toLocalTime())) {
|
||||
throw new RuntimeException("预计送达时间不在商户营业时间内,请重新选择");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!"SCHEDULED".equalsIgnoreCase(type)) {
|
||||
return;
|
||||
}
|
||||
if (MchJobTimeUtils.isBlankJobTime(merchant.getJobTime())) {
|
||||
throw new RuntimeException("商户未配置营业时间,暂不可预约配送");
|
||||
}
|
||||
if (MchJobTimeUtils.mergeJobTimeIntervals(merchant.getJobTime()).isEmpty()) {
|
||||
throw new RuntimeException("商户营业时间格式无效,请先在后台正确配置 jobTime");
|
||||
}
|
||||
if (!MchJobTimeUtils.isDeliverySlotWithinJobTime(merchant.getJobTime(), appt.getExpectDeliveryStart(), appt.getExpectDeliveryEnd())) {
|
||||
throw new RuntimeException("预约时段不在商户营业时间内,请重新选择");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 外卖预约配送:字段在 {@link HardAppTakeOutApptDto};SCHEDULED 须带日期与时段;立即送默认 IMMEDIATE。
|
||||
*/
|
||||
private void validateTakeOutExpectDelivery(HardAppCreateOrderDto dto) {
|
||||
String t = dto.getExpectDeliveryType();
|
||||
if (dto.getAppt() == null) {
|
||||
return;
|
||||
}
|
||||
HardAppTakeOutApptDto a = dto.getAppt();
|
||||
String t = StrUtil.emptyToDefault(a.getExpectDeliveryType(), "IMMEDIATE");
|
||||
if (StrUtil.isBlank(t) || "IMMEDIATE".equalsIgnoreCase(t.trim())) {
|
||||
return;
|
||||
}
|
||||
if (!"SCHEDULED".equalsIgnoreCase(t.trim())) {
|
||||
throw new RuntimeException("配送时间类型无效");
|
||||
}
|
||||
if (dto.getExpectDeliveryDate() == null || dto.getExpectDeliveryStart() == null || dto.getExpectDeliveryEnd() == null) {
|
||||
if (a.getExpectDeliveryDate() == null || a.getExpectDeliveryStart() == null || a.getExpectDeliveryEnd() == null) {
|
||||
throw new RuntimeException("预约配送须选择日期与时段");
|
||||
}
|
||||
if (!dto.getExpectDeliveryEnd().isAfter(dto.getExpectDeliveryStart())) {
|
||||
if (!a.getExpectDeliveryEnd().isAfter(a.getExpectDeliveryStart())) {
|
||||
throw new RuntimeException("预约时段结束须晚于开始");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将外卖请求中的 {@code appt} 写入食堂外送同步 DTO(与 {@link HardTakeOutApptEntity} 语义一致)。
|
||||
*/
|
||||
private void fillCanteenExpectDeliveryFromAppt(CanteenDeliveryOrderSyncDTO sync, HardAppTakeOutApptDto appt) {
|
||||
if (appt == null) {
|
||||
sync.setExpectDeliveryType("IMMEDIATE");
|
||||
return;
|
||||
}
|
||||
String raw = StrUtil.emptyToDefault(appt.getExpectDeliveryType(), "IMMEDIATE").trim();
|
||||
if ("SCHEDULED".equalsIgnoreCase(raw)) {
|
||||
sync.setExpectDeliveryType("SCHEDULED");
|
||||
} else {
|
||||
sync.setExpectDeliveryType("IMMEDIATE");
|
||||
}
|
||||
sync.setExpectDeliveryDate(appt.getExpectDeliveryDate());
|
||||
sync.setExpectDeliveryStart(appt.getExpectDeliveryStart());
|
||||
sync.setExpectDeliveryEnd(appt.getExpectDeliveryEnd());
|
||||
sync.setExpectArriveTime(appt.getExpectArriveTime());
|
||||
}
|
||||
|
||||
// 是否在预约时间段内
|
||||
private boolean isInReserveTime(Long apptId) {
|
||||
if (ObjectUtil.isEmpty(apptId)) return true;
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package com.bsd.pjcloud.hard.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商户 {@code jobTime} 解析:如 {@code "09:00-12:00,12:00-14:00,16:00-19:00"},用于校验预约/预计送达时刻是否在营业时间内。
|
||||
*/
|
||||
public final class MchJobTimeUtils {
|
||||
|
||||
private static final DateTimeFormatter HM = DateTimeFormatter.ofPattern("HH:mm");
|
||||
/** 兼容 jobTime 配置与前端 LocalTime 序列化(可带秒) */
|
||||
private static final DateTimeFormatter HM_OPTIONAL_SEC = new DateTimeFormatterBuilder()
|
||||
.appendPattern("HH:mm")
|
||||
.optionalStart()
|
||||
.appendPattern(":ss")
|
||||
.optionalEnd()
|
||||
.toFormatter();
|
||||
|
||||
private MchJobTimeUtils() {
|
||||
}
|
||||
|
||||
public static boolean isBlankJobTime(String jobTime) {
|
||||
return StrUtil.isBlank(StrUtil.trim(jobTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 预约配送时段 [slotStart, slotEnd] 是否完全落在某一合并后的营业时段内(闭区间,含边界)。
|
||||
*/
|
||||
public static boolean isDeliverySlotWithinJobTime(String jobTime, LocalTime slotStart, LocalTime slotEnd) {
|
||||
if (slotStart == null || slotEnd == null) {
|
||||
return false;
|
||||
}
|
||||
if (!slotEnd.isAfter(slotStart)) {
|
||||
return false;
|
||||
}
|
||||
List<TimeRange> 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<TimeRange> 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<TimeRange> mergeJobTimeIntervals(String jobTime) {
|
||||
List<TimeRange> 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<TimeRange> 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<TimeRange> splitJobTimeIntoSlots(String jobTime, int slotMinutes) {
|
||||
if (slotMinutes <= 0) {
|
||||
return List.of();
|
||||
}
|
||||
List<TimeRange> merged = mergeJobTimeIntervals(jobTime);
|
||||
List<TimeRange> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user