Переглянути джерело

添加用户端是否可以预定接口

wangzhijun 21 годин тому
батько
коміт
515fb42218

+ 12 - 4
nightFragrance-massage/src/main/java/com/ylx/order/controller/OrderController.java

@@ -2,10 +2,7 @@ package com.ylx.order.controller;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.common.core.domain.R;
-import com.ylx.order.domain.dto.OrderCancleDTO;
-import com.ylx.order.domain.dto.OrderDateQueryDTO;
-import com.ylx.order.domain.dto.OrderDeleteDTO;
-import com.ylx.order.domain.dto.OrderSubmitDTO;
+import com.ylx.order.domain.dto.*;
 import com.ylx.order.domain.vo.OrderDateQueryVo;
 import com.ylx.order.domain.vo.OrderDetailVO;
 import com.ylx.order.service.TOrderService;
@@ -60,6 +57,8 @@ public class OrderController {
             @PathVariable @ApiParam(value = "订单ID", required = true, example = "1") Long orderId) {
         return R.ok(orderService.getOrderDetailById(orderId));
     }
+
+    @PreAuthorize("@customerAuth.isCustomer()")
     @ApiOperation("用户取消订单")
     @PostMapping("/cancel")
     public R<?> cancelOrder(@RequestBody @Validated OrderCancleDTO dto) {
@@ -67,4 +66,13 @@ public class OrderController {
         int result = orderService.cancelOrder(dto);
         return result > 0 ? R.ok("订单已取消") : R.fail("订单取消失败");
     }
+
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("客户端是否可以预约当前时段")
+    @PostMapping("/booking/check")
+    public R<Boolean> bookingCheck(@RequestBody @Validated BookingCheckDTO dto) {
+        Boolean result = orderService.bookingCheck(dto);
+        return R.ok(result);
+    }
+
 }

+ 3 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/TOrder.java

@@ -186,4 +186,7 @@ public class TOrder extends BaseEntity {
 
     @ApiModelProperty("优惠券id")
     private String couponId;
+
+    @ApiModelProperty("预约结束时间")
+    private LocalDateTime appointmentEndTime;
 }

+ 34 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/BookingCheckDTO.java

@@ -0,0 +1,34 @@
+package com.ylx.order.domain.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDate;
+import java.time.LocalTime;
+
+@Data
+@ApiModel("客户端预约校验DTO")
+public class BookingCheckDTO {
+
+    @NotNull(message = "商户ID不能为空")
+    @ApiModelProperty("商户ID")
+    private Long merchantId;
+
+    @NotNull(message = "项目ID不能为空")
+    @ApiModelProperty("项目ID")
+    private Long projectId;
+
+    @NotNull(message = "预约日期不能为空")
+    @ApiModelProperty(value = "预约日期", example = "2026-06-12")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private LocalDate appointmentDate;
+
+    @NotNull(message = "预约开始时间不能为空")
+    @ApiModelProperty(value = "预约开始时间", example = "14:30")
+    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
+    private LocalTime startTime;
+
+}

+ 8 - 1
nightFragrance-massage/src/main/java/com/ylx/order/mapper/TOrderMapper.java

@@ -3,11 +3,12 @@ package com.ylx.order.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.massage.domain.vo.HomeBlock;
+import com.ylx.order.domain.TOrder;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
-import com.ylx.order.domain.TOrder;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.List;
 
@@ -39,4 +40,10 @@ public interface TOrderMapper extends BaseMapper<TOrder> {
 
     List<TOrder> getAll(@Param("param") TOrder param);
 
+    List<TOrder> selectValidOrdersByMerchantAndDate(
+            @Param("merchantId") Long merchantId,
+            @Param("projectId") Long projectId,
+            @Param("dayStart") LocalDateTime dayStart,
+            @Param("dayEnd") LocalDateTime dayEnd
+    );
 }

+ 3 - 4
nightFragrance-massage/src/main/java/com/ylx/order/service/TOrderService.java

@@ -9,10 +9,7 @@ import com.ylx.massage.domain.TWxUser;
 import com.ylx.massage.domain.vo.HomeBlock;
 import com.ylx.massage.domain.vo.OrderVerificationVo;
 import com.ylx.massage.domain.vo.TechnicianAvailabilityVo;
-import com.ylx.order.domain.dto.OrderCancleDTO;
-import com.ylx.order.domain.dto.OrderDateQueryDTO;
-import com.ylx.order.domain.dto.OrderSubmitDTO;
-import com.ylx.order.domain.dto.OrderUpdateStatusDTO;
+import com.ylx.order.domain.dto.*;
 import com.ylx.order.domain.vo.OrderDateQueryVo;
 import com.ylx.order.domain.vo.OrderDetailVO;
 
@@ -192,4 +189,6 @@ public interface TOrderService extends IService<TOrder> {
      * @return 取消结果
      */
     int cancelOrder(OrderCancleDTO dto);
+
+    Boolean bookingCheck(BookingCheckDTO dto);
 }

+ 53 - 5
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/TOrderServiceImpl.java

@@ -35,10 +35,7 @@ import com.ylx.massage.utils.OrderNumberGenerator;
 import com.ylx.order.domain.AfterSalesService;
 import com.ylx.order.domain.OrderStatusFlow;
 import com.ylx.order.domain.TOrder;
-import com.ylx.order.domain.dto.OrderCancleDTO;
-import com.ylx.order.domain.dto.OrderDateQueryDTO;
-import com.ylx.order.domain.dto.OrderSubmitDTO;
-import com.ylx.order.domain.dto.OrderUpdateStatusDTO;
+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;
@@ -83,7 +80,9 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M月d日");
     private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
 
-    /** 仅允许取消的状态:待付款(0) */
+    /**
+     * 仅允许取消的状态:待付款(0)
+     */
     private static final List<Integer> ALLOWED_CANCEL_STATUS = Collections.singletonList(0);
     @Resource
     private ProjectService projectService;
@@ -113,6 +112,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     @Resource
     private IAfterSalesServiceService afterSalesServiceService;
     private static final String DICT_TYPE = "unit_type";
+    private static final int BUFFER_MINUTES = 30; // 30分钟缓冲期
 
     @Override
     public TOrder addOrder(TOrder order) {
@@ -419,6 +419,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     /**
      *
      * 更新订单状态(修改订单表状态必须使用此接口)
+     *
      * @param dto
      */
     @Override
@@ -630,6 +631,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
         // 3. 组装VO
         return buildOrderDetailVO(order, flowList);
     }
+
     /**
      * 将订单实体和流转记录转换为详情VO
      */
@@ -723,8 +725,10 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
 
         return vo;
     }
+
     /**
      * 格式化预约时间范围
+     *
      * @param start 预约开始时间
      * @param end   预约结束时间
      * @return 示例:5月12日 16:00-18:00
@@ -742,6 +746,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
         String endTime = (end != null) ? end.format(timeFormatter) : "";
         return dateStr + " " + startTime + "-" + endTime;
     }
+
     /**
      * 构建联系信息(姓名 + 脱敏手机号)
      * 示例:王先生,188****5555
@@ -819,6 +824,48 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
         return rows;
     }
 
+    @Override
+    public Boolean bookingCheck(BookingCheckDTO dto) {
+
+        // 1. 根据 projectId 获取项目时长
+        Project project = projectService.getById(dto.getProjectId());
+        if (ObjectUtil.isNull(project)) {
+            throw new ServiceException("项目不存在");
+        }
+        Integer duration = convertToMinutes(project.getStandardDuration(), project.getUnitType());
+
+        // 2. 组装目标时间段
+        LocalDateTime newStart = LocalDateTime.of(dto.getAppointmentDate(), dto.getStartTime());
+        LocalDateTime actualEnd = newStart.plusMinutes(duration); // 实际服务结束时间
+        LocalDateTime newEndWithBuffer = actualEnd.plusMinutes(BUFFER_MINUTES); // 含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. 内存中遍历判断重叠
+        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;
+    }
+
     private void fillCurrentAfterSaleInfo(IAfterSaleDisplay vo, Long orderId) {
         LambdaQueryWrapper<AfterSalesService> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(AfterSalesService::getOrderId, orderId)
@@ -843,6 +890,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
 
     /**
      * 根据单位类型将时长转换为分钟
+     *
      * @param duration 原始时长
      * @param unitType 单位类型 (对应字典表: 1-分钟, 2-小时, 3-小时/次)
      * @return 转换后的分钟数

+ 15 - 0
nightFragrance-massage/src/main/resources/mapper/order/TOrderMapper.xml

@@ -291,4 +291,19 @@
     <select id="callAutoAccount" statementType="CALLABLE">
         {call updateUserBalance(#{hCount, mode=IN, jdbcType=INTEGER}, #{percent, mode=IN, jdbcType=DECIMAL})}
     </select>
+
+    <select id="selectValidOrdersByMerchantAndDate" resultType="com.ylx.order.domain.TOrder">
+        SELECT
+            appointment_start_time,
+            appointment_end_time
+        FROM t_order
+        WHERE merchant_id = #{merchantId}
+          AND project_id = #{projectId}
+          AND is_delete = 0
+          -- 核心:只查询有效状态的订单 (待派单, 待接单, 待服务, 服务中)
+          AND status IN (1, 2, 3, 4)
+          -- 性能优化:限定查询当天的数据,避免全表扫描
+          AND appointment_start_time >= #{dayStart}
+          AND appointment_start_time &lt;= #{dayEnd}
+    </select>
 </mapper>