瀏覽代碼

商户端订单详情接口

wangzhijun 5 天之前
父節點
當前提交
acea1608dc

+ 56 - 0
nightFragrance-common/src/main/java/com/ylx/common/utils/PhoneUtils.java

@@ -0,0 +1,56 @@
+package com.ylx.common.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import java.util.regex.Pattern;
+
+/**
+ * 手机号工具类
+ */
+public class PhoneUtils {
+
+    /**
+     * 国内手机号正则表达式 (支持11位手机号)
+     */
+    private static final Pattern PHONE_PATTERN = Pattern.compile("^(1[3-9]\\d{9})$");
+
+    /**
+     * 手机号脱敏处理
+     * 规则:保留前3位,中间4位用*替换,保留后4位
+     * 示例:13812345678 -> 138****5678
+     *
+     * @param phoneNumber 原始手机号
+     * @return 脱敏后的手机号,若传入为空或格式不对则返回原值
+     */
+    public static String maskPhoneNumber(String phoneNumber) {
+        // 1. 空值防御
+        if (StringUtils.isBlank(phoneNumber)) {
+            return null;
+        }
+
+        // 去除可能存在的空格
+        phoneNumber = phoneNumber.trim();
+
+        // 2. 格式校验:如果是标准的11位手机号,则进行脱敏
+        if (PHONE_PATTERN.matcher(phoneNumber).matches()) {
+            // 方式一:使用正则替换(推荐,简洁优雅)
+            return phoneNumber.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
+
+            // 方式二:使用字符串截取(备选方案)
+            // return phoneNumber.substring(0, 3) + "****" + phoneNumber.substring(7);
+        }
+
+        // 3. 如果不是标准手机号(如座机、海外号码、虚拟号等),可选择返回原值或全部打码
+        // 这里为了安全起见,非标准手机号直接返回原值
+        return phoneNumber;
+    }
+
+    /**
+     * 验证是否为合法的手机号格式
+     */
+    public static boolean isValidPhoneNumber(String phoneNumber) {
+        if (StringUtils.isBlank(phoneNumber)) {
+            return false;
+        }
+        return PHONE_PATTERN.matcher(phoneNumber.trim()).matches();
+    }
+}

+ 9 - 4
nightFragrance-massage/src/main/java/com/ylx/order/controller/MerchantOrderController.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.common.core.domain.R;
 import com.ylx.order.domain.dto.*;
 import com.ylx.order.domain.vo.merchant.MerchantCancelOrderDTO;
+import com.ylx.order.domain.vo.merchant.MerchantOrderDetailVO;
 import com.ylx.order.domain.vo.merchant.OrderCustomerPhoneVO;
 import com.ylx.order.domain.vo.merchant.OrderPageVO;
 import com.ylx.order.service.TOrderService;
@@ -12,10 +13,7 @@ import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 
@@ -85,4 +83,11 @@ public class MerchantOrderController {
         return R.ok();
     }
 
+    @PostMapping("/detail")
+    @ApiOperation("商户端订单详情接口")
+    public R<MerchantOrderDetailVO> getMerchantOrderDetail(@Validated @RequestBody MerchantOrderDetailDTO dto) {
+        MerchantOrderDetailVO vo = this.orderService.getMerchantOrderDetail(dto);
+        return R.ok(vo);
+    }
+
 }

+ 20 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/MerchantOrderDetailDTO.java

@@ -0,0 +1,20 @@
+package com.ylx.order.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel("商户端订单详情DTO")
+public class MerchantOrderDetailDTO {
+
+    @NotNull(message = "订单ID不能为空")
+    @ApiModelProperty("关联的主订单ID")
+    private Long orderId;
+
+    @NotNull(message = "城市代码不能为空")
+    @ApiModelProperty("城市编码")
+    private String cityCode;
+}

+ 87 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/merchant/MerchantOrderDetailVO.java

@@ -0,0 +1,87 @@
+package com.ylx.order.domain.vo.merchant;
+
+import com.ylx.order.service.IAfterSaleDisplay;
+import com.ylx.order.service.IGeoRiskInfo;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class MerchantOrderDetailVO<T> implements IAfterSaleDisplay, IGeoRiskInfo {
+
+    @ApiModelProperty("距离")
+    private BigDecimal distanceKm;
+
+    @ApiModelProperty("用户下单时纬度")
+    private BigDecimal userLatitude;
+
+    @ApiModelProperty("用户下单时经度")
+    private BigDecimal userLongitude;
+
+    @ApiModelProperty("围栏介绍")
+    private String fenceIntro;
+
+    @ApiModelProperty("风险提示")
+    private Boolean isHighRiskArea;
+
+    @ApiModelProperty("订单状态")
+    private Integer orderStatus;
+
+    @ApiModelProperty("订单状态中文")
+    private String orderStatusName;
+
+    @ApiModelProperty("项目名称")
+    private String projectName;
+
+    @ApiModelProperty("项目时长")
+    private Integer projectDuration;
+
+    @ApiModelProperty("最终应付/实付金额")
+    private BigDecimal finalAmount;
+
+    @ApiModelProperty("预约开始时间")
+    private String appointmentStartTime;
+
+    @ApiModelProperty("预约结束时间")
+    private String appointmentEndTime;
+
+    @ApiModelProperty("预约时间范围(格式化,示例:5月12日 16:00-18:00)")
+    private String appointmentTimeRange;
+
+    @ApiModelProperty("详细服务地址")
+    private String contactAddressInfo;
+
+    @ApiModelProperty("联系人姓名")
+    private String contactPersonName;
+
+    @ApiModelProperty("联系人电话号码")
+    private String contactPhoneNumber;
+
+    @ApiModelProperty("订单价格")
+    private BigDecimal basePrice;
+
+    @ApiModelProperty("交通费")
+    private BigDecimal trafficFee;
+
+    @ApiModelProperty("优惠券抵扣金额")
+    private BigDecimal couponDiscount;
+
+    @ApiModelProperty("订单号")
+    private String orderNo;
+
+    @ApiModelProperty("订单id")
+    private String orderId;
+
+    @ApiModelProperty("下单时间")
+    private String createTime;
+
+    @ApiModelProperty("付款时间")
+    private String paidTime;
+
+    @ApiModelProperty("售后单ID")
+    private Long afterSalesServiceId;
+
+    @ApiModelProperty("售后状态文案")
+    private String afterSalesServiceStatus;
+}

+ 3 - 2
nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/merchant/OrderPageVO.java

@@ -2,6 +2,7 @@ package com.ylx.order.domain.vo.merchant;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ylx.order.service.IAfterSaleDisplay;
+import com.ylx.order.service.IGeoRiskInfo;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -11,7 +12,7 @@ import java.time.LocalDateTime;
 
 @Data
 @ApiModel("商户端订单分页VO")
-public class OrderPageVO<T> implements IAfterSaleDisplay {
+public class OrderPageVO<T> implements IAfterSaleDisplay, IGeoRiskInfo {
 
     @ApiModelProperty("订单ID")
     private Long id;
@@ -76,7 +77,7 @@ public class OrderPageVO<T> implements IAfterSaleDisplay {
     @ApiModelProperty("订单状态")
     private Integer status;
 
-    @ApiModelProperty("客户标签编码 0新客/1复购/2老客 ")
+    @ApiModelProperty("客户标签编码 0 = 新客; 1 = 复购; 2 = 老客")
     private Integer customerTagCode;
 
     @ApiModelProperty("客户标签展示文案")

+ 16 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/IGeoRiskInfo.java

@@ -0,0 +1,16 @@
+package com.ylx.order.service;
+
+import java.math.BigDecimal;
+
+public interface IGeoRiskInfo {
+
+    BigDecimal getUserLatitude();
+
+    BigDecimal getUserLongitude();
+
+    void setIsHighRiskArea(Boolean isHighRisk);
+
+    void setDistanceKm(BigDecimal distance);
+
+    void setFenceIntro(String intro);
+}

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

@@ -14,6 +14,7 @@ import com.ylx.order.domain.vo.OrderDateQueryVo;
 import com.ylx.order.domain.vo.OrderDetailVO;
 import com.ylx.order.domain.vo.RecentMerchantVO;
 import com.ylx.order.domain.vo.merchant.MerchantCancelOrderDTO;
+import com.ylx.order.domain.vo.merchant.MerchantOrderDetailVO;
 import com.ylx.order.domain.vo.merchant.OrderCustomerPhoneVO;
 import com.ylx.order.domain.vo.merchant.OrderPageVO;
 
@@ -213,4 +214,6 @@ public interface TOrderService extends IService<TOrder> {
     void merchantArriveSign(MerchantOrderOperateDTO dto);
 
     Page<RecentMerchantVO> getRecentMerchants(RecentOrderQueryDTO dto);
+
+    MerchantOrderDetailVO getMerchantOrderDetail(MerchantOrderDetailDTO dto);
 }

+ 82 - 6
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/TOrderServiceImpl.java

@@ -1,5 +1,6 @@
 package com.ylx.order.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
@@ -35,14 +36,12 @@ 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.merchant.MerchantCancelOrderDTO;
+import com.ylx.order.domain.vo.merchant.MerchantOrderDetailVO;
 import com.ylx.order.domain.vo.merchant.OrderCustomerPhoneVO;
 import com.ylx.order.domain.vo.merchant.OrderPageVO;
 import com.ylx.order.enums.*;
 import com.ylx.order.mapper.TOrderMapper;
-import com.ylx.order.service.IAfterSaleDisplay;
-import com.ylx.order.service.IAfterSalesServiceService;
-import com.ylx.order.service.OrderStatusFlowService;
-import com.ylx.order.service.TOrderService;
+import com.ylx.order.service.*;
 import com.ylx.project.domain.Project;
 import com.ylx.project.service.ProjectService;
 import com.ylx.shopingfundsdetail.domain.vo.ShoppingFundsDetailAddDto;
@@ -893,6 +892,8 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
             Integer tagCode = calcCustomerTagCode(vo, merchantId, now);
             vo.setCustomerTagCode(tagCode);
             vo.setCustomerTagDesc(CustomerTagEnum.getDescByCode(tagCode));
+
+            this.fillCurrentAfterSaleInfo(vo, vo.getId());
         }
 
         return page;
@@ -1026,7 +1027,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
                     .set(TOrder::getCancelledReason, cancelReason)
                     .set(TOrder::getUpdateTime, LocalDateTime.now())
                     .set(TOrder::getUpdateBy, loginUser.getCNickName())
-                    .set(TOrder::getDispatchedStatus,DispatchedStatusEnum.UN_DISPATCHED.getCode())
+                    .set(TOrder::getDispatchedStatus, DispatchedStatusEnum.UN_DISPATCHED.getCode())
                     .eq(TOrder::getId, orderId)
                     .eq(TOrder::getStatus, statusCode);
             int row = baseMapper.update(null, updateWrapper);
@@ -1213,6 +1214,48 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
         return page;
     }
 
+    @Override
+    public MerchantOrderDetailVO getMerchantOrderDetail(MerchantOrderDetailDTO dto) {
+
+        // 1. 根据ID查询订单主表
+        TOrder order = this.baseMapper.selectById(dto.getOrderId());
+        if (ObjectUtil.isNull(order)) {
+            throw new ServiceException("订单不存在");
+        }
+
+        if (ObjectUtil.notEqual(order.getIsDelete(), NOT_DELETE)) {
+            throw new ServiceException("订单已被删除");
+        }
+
+        MerchantOrderDetailVO vo = new MerchantOrderDetailVO<>();
+
+        // 2. 基础字段映射 (使用 BeanUtil 或手动 set)
+        BeanUtil.copyProperties(order, vo);
+        vo.setOrderStatus(order.getStatus());
+        vo.setOrderStatusName(OrderStatusEnum.getInfoByCode(order.getStatus()));
+
+        // 特殊处理 ID 转 String (前端 Long 精度丢失问题)
+        vo.setOrderId(String.valueOf(order.getId()));
+
+        // 格式化时间范围 (例如: 5月12日 16:00-18:00)
+        vo.setAppointmentTimeRange(formatTimeRange(order.getAppointmentStartTime(), order.getProjectDuration()));
+
+        // 3. 联系信息脱敏处理
+        vo.setContactPhoneNumber(PhoneUtils.maskPhoneNumber(order.getContactPhoneNumber()));
+
+        // 4. 计算距离与告警提示 (依赖地理围栏表 t_geo_fence)
+        List<TGeoFence> validFenceList = geoFenceService.selectValidFences(dto.getCityCode());
+        List<TGeoFence> usableFence = filterUsableFence(validFenceList);
+
+
+        // 5. 查询关联的售后信息 (如果有)
+        fillCurrentAfterSaleInfo(vo, order.getId());
+        // 6. 计算高风险围栏信息
+        calcOrderGeoFenceInfo(vo, usableFence);
+
+        return vo;
+    }
+
     private void fillCurrentAfterSaleInfo(IAfterSaleDisplay vo, Long orderId) {
         LambdaQueryWrapper<AfterSalesService> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(AfterSalesService::getOrderId, orderId).orderByDesc(AfterSalesService::getCreateTime).last("LIMIT 1");
@@ -1434,7 +1477,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     /**
      * 计算单条订单围栏距离、是否高风险区域、围栏备注
      */
-    private void calcOrderGeoFenceInfo(OrderPageVO orderVO, List<TGeoFence> usableFence) {
+    private void calcOrderGeoFenceInfo(IGeoRiskInfo orderVO, List<TGeoFence> usableFence) {
         // 用户坐标为空直接标记非风险
         BigDecimal userLat = orderVO.getUserLatitude();
         BigDecimal userLon = orderVO.getUserLongitude();
@@ -1474,4 +1517,37 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
         orderVO.setFenceIntro(nearestFence.getFenceIntro());
     }
 
+    /**
+     * 格式化预约时间范围
+     * 示例输出: "5月12日 16:00-18:00"
+     *
+     * @param startTime       预约开始时间
+     * @param durationMinutes 项目时长(分钟)
+     * @return 格式化后的时间范围字符串
+     */
+    private String formatTimeRange(LocalDateTime startTime, Integer durationMinutes) {
+        if (startTime == null || durationMinutes == null || durationMinutes <= 0) {
+            return "";
+        }
+
+        LocalDateTime endTime = startTime.plusMinutes(durationMinutes);
+        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M月d日", Locale.CHINA);
+        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
+
+        String dateStr = dateFormatter.format(startTime);
+        String startTimeStr = timeFormatter.format(startTime);
+        String endTimeStr = timeFormatter.format(endTime);
+
+        // 判断是否跨天
+        if (!startTime.toLocalDate().isEqual(endTime.toLocalDate())) {
+            // 跨天场景:在结束时间前加上日期
+            String endDateStr = dateFormatter.format(endTime);
+            return String.format("%s %s-%s %s", dateStr, startTimeStr, endDateStr, endTimeStr);
+            // 输出示例: "5月12日 23:00-5月13日 01:00"
+        }
+
+        // 同天场景:正常拼接
+        return String.format("%s %s-%s", dateStr, startTimeStr, endTimeStr);
+    }
+
 }

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

@@ -310,18 +310,18 @@
     <resultMap id="OrderPageVOResultMap" type="com.ylx.order.domain.vo.merchant.OrderPageVO">
         <id column="id" property="id"/>
         <result column="user_id" property="userId"/>
-        <result column="project_name" property="projectName"/>
+        <result column="title" property="projectName"/>
         <result column="project_cover" property="projectCover"/>
         <result column="project_duration" property="projectDuration"/>
-        <result column="base_price" property="basePrice"/>
+        <result column="price" property="basePrice"/>
         <result column="category_id" property="categoryId"/>
         <result column="traffic_fee" property="trafficFee"/>
         <result column="final_amount" property="finalAmount"/>
         <result column="appointment_start_time" property="appointmentStartTime"/>
         <result column="appointment_end_time" property="appointmentEndTime"/>
         <result column="contact_person_name" property="contactPersonName"/>
-        <result column="contact_phone_number" property="contactPhoneNumber"/>
-        <result column="service_address_detail" property="contactAddressInfo"/>
+        <result column="contactPhoneNumber" property="contactPhoneNumber"/>
+        <result column="contact_address_info" property="contactAddressInfo"/>
         <result column="user_latitude" property="userLatitude"/>
         <result column="user_longitude" property="userLongitude"/>
         <result column="status" property="status"/>
@@ -333,18 +333,18 @@
         SELECT
             o.id,
             o.user_id,
-            p.project_name,
-            p.cover_url AS project_cover,
-            p.duration AS project_duration,
-            p.base_price,
+            p.title,
+            p.cover AS project_cover,
+            p.standard_duration AS project_duration,
+            p.price,
             p.category_id,
             o.traffic_fee,
             o.final_amount,
             o.appointment_start_time,
             o.appointment_end_time,
             o.contact_person_name,
-            IF(o.contact_phone_number IS NULL, '', CONCAT(SUBSTRING(o.contact_phone_number,1,3), '******', SUBSTRING(o.contact_phone_number,10,2))) AS contactPhoneNumber
-            o.service_address_detail,
+            IF(o.contact_phone_number IS NULL, '', CONCAT(SUBSTRING(o.contact_phone_number,1,3), '******', SUBSTRING(o.contact_phone_number,10,2))) AS contactPhoneNumber,
+            o.contact_address_info,
             o.user_latitude,
             o.user_longitude,
             o.status,
@@ -356,7 +356,7 @@
             END AS distance_km,
             o.create_time
         FROM t_order o
-        LEFT JOIN t_project p ON o.project_id = p.id
+        LEFT JOIN project p ON o.project_id = p.id
         <where>
             o.is_delete = 0
             -- 商户ID
@@ -372,7 +372,7 @@
 
             -- 项目名称模糊
             <if test="dto.projectName != null and dto.projectName != ''">
-                AND p.project_name LIKE CONCAT('%', #{dto.projectName}, '%')
+                AND p.title LIKE CONCAT('%', #{dto.projectName}, '%')
             </if>
 
             -- 联系人手机号【修复:订单表字段o,不是项目表p】