|
|
@@ -19,6 +19,7 @@ import com.ylx.common.exception.ServiceException;
|
|
|
import com.ylx.common.utils.*;
|
|
|
import com.ylx.common.weixinPay.enums.WxPayTypeEnum;
|
|
|
import com.ylx.common.weixinPay.service.WxPayV3Service;
|
|
|
+import com.ylx.fareSetting.service.IMaProjectFareSettingService;
|
|
|
import com.ylx.massage.domain.*;
|
|
|
import com.ylx.massage.domain.vo.HomeBlock;
|
|
|
import com.ylx.massage.domain.vo.OrderVerificationVo;
|
|
|
@@ -33,10 +34,7 @@ import com.ylx.order.domain.AfterSalesService;
|
|
|
import com.ylx.order.domain.OrderStatusFlow;
|
|
|
import com.ylx.order.domain.TOrder;
|
|
|
import com.ylx.order.domain.dto.*;
|
|
|
-import com.ylx.order.domain.vo.OrderDateQueryVo;
|
|
|
-import com.ylx.order.domain.vo.OrderDetailVO;
|
|
|
-import com.ylx.order.domain.vo.OrderStatusFlowVO;
|
|
|
-import com.ylx.order.domain.vo.RecentMerchantVO;
|
|
|
+import com.ylx.order.domain.vo.*;
|
|
|
import com.ylx.order.domain.vo.merchant.MerchantCancelOrderDTO;
|
|
|
import com.ylx.order.domain.vo.merchant.MerchantOrderDetailVO;
|
|
|
import com.ylx.order.domain.vo.merchant.OrderCustomerPhoneVO;
|
|
|
@@ -125,6 +123,9 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
@Value("${ylx.fixedVerifyCode:123456}")
|
|
|
private String fixedVerifyCode;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private IMaProjectFareSettingService maProjectFareSettingService;
|
|
|
+
|
|
|
@Override
|
|
|
public TOrder addOrder(TOrder order) {
|
|
|
return null;
|
|
|
@@ -832,8 +833,6 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
// 2. 组装目标时间段
|
|
|
LocalDateTime newStart = LocalDateTime.of(dto.getAppointmentDate(), dto.getStartTime());
|
|
|
- LocalDateTime actualEnd = newStart.plusMinutes(duration); // 实际服务结束时间
|
|
|
- LocalDateTime newEndWithBuffer = actualEnd.plusMinutes(APPOINT_BUFFER_MINUTES); // 含30分钟缓冲的结束时间
|
|
|
|
|
|
// 3. 查询该商户当天的【有效占用】订单
|
|
|
LocalDateTime dayStart = dto.getAppointmentDate().atStartOfDay();
|
|
|
@@ -841,20 +840,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
List<TOrder> validOrders = this.baseMapper.selectValidOrdersByMerchantAndDate(dto.getMerchantId(), dto.getProjectId(), dayStart, dayEnd);
|
|
|
|
|
|
- // 4. 内存中遍历判断重叠
|
|
|
- for (TOrder order : validOrders) {
|
|
|
- LocalDateTime existStart = order.getAppointmentStartTime();
|
|
|
- LocalDateTime existEnd = order.getAppointmentEndTime();
|
|
|
-
|
|
|
- // 核心重叠算法:
|
|
|
- // 新订单开始时间 < 已有订单结束时间 且 新订单结束时间(含30min缓冲) > 已有订单开始时间
|
|
|
- if (newStart.isBefore(existEnd) && newEndWithBuffer.isAfter(existStart)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 5. 没有任何冲突,可以预约
|
|
|
- return true;
|
|
|
+ return isAppointmentSlotAvailable(newStart, duration, validOrders);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -1258,6 +1244,78 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public List<BookingSlotsVO> bookingSlots(BookingSlotsDTO dto) {
|
|
|
+
|
|
|
+ // 1. 获取项目信息(关键:服务时长)
|
|
|
+ Project project = this.projectService.getById(dto.getProjectId());
|
|
|
+ if (ObjectUtil.isNull(project)) {
|
|
|
+ throw new ServiceException("项目不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer standardDuration = project.getStandardDuration();
|
|
|
+ Integer unitType = project.getUnitType();
|
|
|
+ String unitTypeName = "未知单位";
|
|
|
+ List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
|
|
|
+ if (CollUtil.isNotEmpty(dictList) && ObjectUtil.isNotNull(unitType)) {
|
|
|
+ unitTypeName = dictList.stream()
|
|
|
+ .filter(dict -> unitType.toString().equals(dict.getDictValue()))
|
|
|
+ .map(SysDictData::getDictLabel)
|
|
|
+ .findFirst()
|
|
|
+ .orElse("未知单位");
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer durationMinutes = convertToMinutes(standardDuration, unitType);
|
|
|
+ if (ObjectUtil.isNull(durationMinutes) || durationMinutes <= 0) {
|
|
|
+ throw new ServiceException("项目时长配置异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 生成所有候选开始时间(半点粒度,步进30分钟)
|
|
|
+ List<LocalTime> candidateStarts = TimeSlotUtil.getAvailableTimeSlots(dto.getAppointmentDate(), 30);
|
|
|
+
|
|
|
+ // 3. 查询该商户当天的【有效占用】订单
|
|
|
+ LocalDateTime dayStart = dto.getAppointmentDate().atStartOfDay();
|
|
|
+ LocalDateTime dayEnd = dto.getAppointmentDate().atTime(LocalTime.MAX);
|
|
|
+ List<TOrder> validOrders = this.baseMapper.selectValidOrdersByMerchantAndDate(
|
|
|
+ dto.getMerchantId(), dto.getProjectId(), dayStart, dayEnd);
|
|
|
+
|
|
|
+ // 4. 遍历候选时段,构建 VO
|
|
|
+ List<BookingSlotsVO> voList = new ArrayList<>(candidateStarts.size());
|
|
|
+ for (LocalTime startTime : candidateStarts) {
|
|
|
+ LocalDateTime slotStart = LocalDateTime.of(dto.getAppointmentDate(), startTime);
|
|
|
+ LocalDateTime actualEnd = slotStart.plusMinutes(durationMinutes);
|
|
|
+
|
|
|
+ BookingSlotsVO vo = new BookingSlotsVO();
|
|
|
+ vo.setStartTime(startTime);
|
|
|
+ vo.setStandardDuration(standardDuration);
|
|
|
+ vo.setUnitTypeName(unitTypeName);
|
|
|
+ vo.setEndTime(actualEnd.toLocalTime());
|
|
|
+ vo.setIsDay(maProjectFareSettingService.isDayTimePeriod(slotStart));
|
|
|
+
|
|
|
+ boolean exceedsDay = !actualEnd.toLocalDate().isEqual(dto.getAppointmentDate());
|
|
|
+ vo.setAvailable(!exceedsDay && isAppointmentSlotAvailable(slotStart, durationMinutes, validOrders));
|
|
|
+ voList.add(vo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return voList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断预约时段是否与已有订单冲突(含30分钟缓冲)
|
|
|
+ */
|
|
|
+ private boolean isAppointmentSlotAvailable(LocalDateTime newStart, int durationMinutes, List<TOrder> validOrders) {
|
|
|
+ LocalDateTime actualEnd = newStart.plusMinutes(durationMinutes);
|
|
|
+ LocalDateTime newEndWithBuffer = actualEnd.plusMinutes(APPOINT_BUFFER_MINUTES);
|
|
|
+ for (TOrder order : validOrders) {
|
|
|
+ LocalDateTime existStart = order.getAppointmentStartTime();
|
|
|
+ LocalDateTime existEnd = order.getAppointmentEndTime();
|
|
|
+ if (newStart.isBefore(existEnd) && newEndWithBuffer.isAfter(existStart)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
private void fillCurrentAfterSaleInfo(IAfterSaleDisplay vo, Long orderId) {
|
|
|
LambdaQueryWrapper<AfterSalesService> wrapper = new LambdaQueryWrapper<>();
|
|
|
wrapper.eq(AfterSalesService::getOrderId, orderId).orderByDesc(AfterSalesService::getCreateTime).last("LIMIT 1");
|