Sfoglia il codice sorgente

修改根据订单ID查询后台订单详情接口,增加距离字段

jinshihui 2 giorni fa
parent
commit
7149f6f5a0

+ 208 - 8
nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/AdminOrderDetailVO.java

@@ -9,141 +9,341 @@ import java.util.List;
 @Data
 public class AdminOrderDetailVO {
 
+    /**
+     * 订单基础信息。
+     */
     @ApiModelProperty("订单信息")
     private OrderInfoVO orderInfo;
 
+    /**
+     * 订单关联的项目信息。
+     */
     @ApiModelProperty("项目信息")
     private ProjectInfoVO projectInfo;
 
+    /**
+     * 服务凭证图片列表。
+     */
     @ApiModelProperty("服务凭证图片")
     private List<String> serviceVoucherList;
 
+    /**
+     * 订单操作日志列表。
+     */
     @ApiModelProperty("订单操作日志")
     private List<OperationLogVO> operationLogList;
 
+    /**
+     * 订单信息。
+     */
     @Data
     public static class OrderInfoVO {
+        /**
+         * 订单ID。
+         */
         @ApiModelProperty("订单ID")
         private Long id;
+
+        /**
+         * 订单号。
+         */
         @ApiModelProperty("订单号")
         private String orderNo;
+
+        /**
+         * 订单状态编码。
+         */
         @ApiModelProperty("订单状态")
         private Integer status;
 
         /**
-         * 订单状态名称
+         * 订单状态名称
          */
         @ApiModelProperty("订单状态名称")
         private String statusName;
+
+        /**
+         * 下单用户昵称。
+         */
         @ApiModelProperty("下单用户")
         private String userNickName;
+
+        /**
+         * 联系人姓名。
+         */
         @ApiModelProperty("联系人")
         private String contactPersonName;
+
+        /**
+         * 联系人电话。
+         */
         @ApiModelProperty("联系电话")
         private String contactPhoneNumber;
+
+        /**
+         * 联系地址信息。
+         */
         @ApiModelProperty("联系地址")
         private String contactAddressInfo;
+
+        /**
+         * 商户昵称。
+         */
         @ApiModelProperty("商户昵称")
         private String merchantNickName;
+
+        /**
+         * 商户联系电话。
+         */
         @ApiModelProperty("商户电话")
         private String merchantPhone;
+
+        /**
+         * 预约开始时间。
+         */
         @ApiModelProperty("预约开始时间")
         private String appointmentStartTime;
+
+        /**
+         * 服务开始时间。
+         */
         @ApiModelProperty("服务开始时间")
         private String startTime;
+
+        /**
+         * 服务结束时间。
+         */
         @ApiModelProperty("服务结束时间")
         private String completedTime;
+
+        /**
+         * 下单时间。
+         */
         @ApiModelProperty("下单时间")
         private String createTime;
+
+        /**
+         * 付款时间。
+         */
         @ApiModelProperty("付款时间")
         private String paidTime;
+
+        /**
+         * 付款方式编码。
+         */
         @ApiModelProperty("付款方式")
         private Integer paymentMethod;
+
+        /**
+         * 付款方式名称。
+         */
         @ApiModelProperty("付款方式名称")
         private String paymentMethodName;
+
+        /**
+         * 项目服务时长。
+         */
         @ApiModelProperty("项目时长")
         private Integer projectDuration;
+
+        /**
+         * 用户纬度。
+         */
         @ApiModelProperty("用户纬度")
         private BigDecimal userLatitude;
+
+        /**
+         * 用户经度。
+         */
         @ApiModelProperty("用户经度")
         private BigDecimal userLongitude;
+
+        /**
+         * 商户真实纬度。
+         */
         @ApiModelProperty("商户真实纬度")
         private BigDecimal merchantLatitude;
+
+        /**
+         * 商户真实经度。
+         */
         @ApiModelProperty("商户真实经度")
         private BigDecimal merchantLongitude;
+
+        /**
+         * 商户虚拟纬度。
+         */
         @ApiModelProperty("商户虚拟纬度")
         private BigDecimal virtualLatitude;
+
+        /**
+         * 商户虚拟经度。
+         */
         @ApiModelProperty("商户虚拟经度")
         private BigDecimal virtualLongitude;
+
+        /**
+         * 用户与商户之间的距离。
+         */
+        @ApiModelProperty("距离")
+        private BigDecimal distance;
+
+        /**
+         * 服务凭证图片原始字段。
+         */
         @ApiModelProperty("服务凭证原始字段")
         private String startPhoto;
+
+        /**
+         * 最新售后单ID。
+         */
         @ApiModelProperty("售后单ID")
         private Long afterSalesServiceId;
+
+        /**
+         * 售后状态编码。
+         */
         @ApiModelProperty("售后状态")
         private Integer afterSalesServiceStatus;
+
+        /**
+         * 售后状态名称。
+         */
         @ApiModelProperty("售后状态名称")
         private String afterSalesServiceStatusName;
+
+        /**
+         * 平台收益金额。
+         */
         @ApiModelProperty("平台收益")
         private BigDecimal platformIncome;
+
+        /**
+         * 商户收益金额。
+         */
         @ApiModelProperty("商户收益")
         private BigDecimal merchantIncome;
     }
 
     @Data
     public static class ProjectInfoVO {
+        /**
+         * 项目名称。
+         */
         @ApiModelProperty("项目名称")
         private String projectName;
 
+        /**
+         * 项目封面图片地址。
+         */
         @ApiModelProperty("项目封面")
         private String projectCover;
 
         /**
-         * 标准时长
+         * 项目标准服务时长
          */
         @ApiModelProperty("标准时长")
         private Integer standardDuration;
 
         /**
-         * 计量单位(0:分钟 1:小时 2:次)
+         * 计费单位编码,0-分钟,1-小时,2-次。
          */
         @ApiModelProperty("计费单位")
         private Integer unitType;
+
+        /**
+         * 计费单位名称。
+         */
         @ApiModelProperty("计费单位名称")
         private String unitName;
+
+        /**
+         * 项目售价。
+         */
         @ApiModelProperty("售价")
         private BigDecimal unitPrice;
+
+        /**
+         * 商户佣金比例。
+         */
         @ApiModelProperty("商户佣金比例")
         private BigDecimal merchantCommission;
+
+        /**
+         * 订单实付金额。
+         */
         @ApiModelProperty("实付金额")
         private BigDecimal finalAmount;
+
+        /**
+         * 订单应收金额。
+         */
         @ApiModelProperty("订单应收")
         private BigDecimal basePrice;
+
+        /**
+         * 出行车费。
+         */
         @ApiModelProperty("出行车费")
         private BigDecimal trafficFee;
+
+        /**
+         * 优惠券抵扣金额。
+         */
         @ApiModelProperty("优惠券抵扣")
         private BigDecimal couponDiscount;
+
+        /**
+         * 最新售后单ID。
+         */
         @ApiModelProperty("售后单ID")
         private Long afterSalesServiceId;
-        @ApiModelProperty("售后状态")
-        private Integer afterSalesServiceStatus;
 
         /**
-         * 售后状态名称
+         * 售后状态编码。
          */
-        @ApiModelProperty("售后状态名称")
-        private String afterSalesServiceStatusName;
+        /*@ApiModelProperty("售后状态")
+        private Integer afterSalesServiceStatus;*/
+
+        /**
+         * 售后状态名称。
+         */
+        /*@ApiModelProperty("售后状态名称")
+        private String afterSalesServiceStatusName;*/
     }
 
+    /**
+     * 订单操作日志
+     */
     @Data
     public static class OperationLogVO {
+        /**
+         * 操作人。
+         */
         @ApiModelProperty("操作人")
         private String operator;
+
+        /**
+         * 操作时间。
+         */
         @ApiModelProperty("操作时间")
         private String operationTime;
+
+        /**
+         * 操作后的订单状态编码。
+         */
         @ApiModelProperty("状态")
         private Integer status;
+
+        /**
+         * 操作后的订单状态名称。
+         */
         @ApiModelProperty("状态名称")
         private String statusName;
+
+        /**
+         * 操作内容描述。
+         */
         @ApiModelProperty("操作内容")
         private String operationContent;
     }

+ 3 - 2
nightFragrance-massage/src/main/java/com/ylx/order/mapper/AdminOrderMapper.java

@@ -1,8 +1,9 @@
 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.CancelOrderApplication;
 import com.ylx.order.domain.OrderDispatch;
+import com.ylx.order.domain.TOrder;
 import com.ylx.order.domain.dto.AdminOrderDispatchMerchantQueryDTO;
 import com.ylx.order.domain.dto.AdminOrderQueryDTO;
 import com.ylx.order.domain.vo.AdminOrderDetailVO;
@@ -16,7 +17,7 @@ import org.apache.ibatis.annotations.Param;
 import java.util.List;
 
 @Mapper
-public interface AdminOrderMapper {
+public interface AdminOrderMapper extends BaseMapper<TOrder> {
 
     Page<AdminOrderPageVO> selectAdminOrderPage(Page<AdminOrderPageVO> page, @Param("dto") AdminOrderQueryDTO dto);
 

+ 79 - 4
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/AdminOrderServiceImpl.java

@@ -3,6 +3,7 @@ package com.ylx.order.service.impl;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.common.core.domain.model.LoginUser;
 import com.ylx.common.exception.ServiceException;
+import com.ylx.common.utils.DistanceUtil;
 import com.ylx.massage.domain.CancelOrderApplication;
 import com.ylx.order.domain.OrderDispatch;
 import com.ylx.order.domain.dto.AdminOrderDispatchDTO;
@@ -16,6 +17,7 @@ import com.ylx.order.domain.vo.AdminOrderServiceCategoryVO;
 import com.ylx.order.enums.AfterSaleServiceStatusEnum;
 import com.ylx.order.mapper.AdminOrderMapper;
 import com.ylx.order.service.AdminOrderService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Service;
@@ -24,6 +26,7 @@ import org.springframework.util.StringUtils;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
@@ -38,6 +41,7 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 
 @Service
+@Slf4j
 public class AdminOrderServiceImpl implements AdminOrderService {
 
     private static final String PLATFORM_REFUND_REASON = "平台发起退款申请";
@@ -54,6 +58,8 @@ public class AdminOrderServiceImpl implements AdminOrderService {
     private static final Integer DISPATCH_SOURCE_ADMIN = 1;
     private static final Integer CURRENT_DISPATCH = 1;
     private static final Integer NOT_DELETED = 0;
+    private static final BigDecimal MAX_LATITUDE = new BigDecimal("90");
+    private static final BigDecimal MAX_LONGITUDE = new BigDecimal("180");
 
     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
     private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@@ -147,18 +153,20 @@ public class AdminOrderServiceImpl implements AdminOrderService {
     public AdminOrderDetailVO detail(Long id) {
         validateOrderId(id);
 
+        // 查询订单详情
         AdminOrderDetailVO.OrderInfoVO orderInfo = adminOrderMapper.selectOrderDetailInfoById(id);
         if (orderInfo == null) {
             throw new ServiceException("订单不存在或已删除");
         }
 
         fillOrderDetailDisplayFields(orderInfo);
-
+        // 查询项目详情
         AdminOrderDetailVO.ProjectInfoVO projectInfo = adminOrderMapper.selectOrderProjectDetailById(id);
         if (projectInfo != null) {
             fillProjectDetailDisplayFields(projectInfo);
         }
 
+        // 查询订单操作日志
         List<AdminOrderDetailVO.OperationLogVO> operationLogs = adminOrderMapper.selectOrderOperationLogs(id);
         if (operationLogs == null) {
             operationLogs = Collections.emptyList();
@@ -345,21 +353,82 @@ public class AdminOrderServiceImpl implements AdminOrderService {
 
     /**
      * 填充订单详情显示字段
+     *
      * @param orderInfo
      */
     private void fillOrderDetailDisplayFields(AdminOrderDetailVO.OrderInfoVO orderInfo) {
         orderInfo.setStatusName(getStatusName(orderInfo.getStatus()));
         orderInfo.setPaymentMethodName(getPaymentMethodName(orderInfo.getStatus(), orderInfo.getPaymentMethod()));
         orderInfo.setAfterSalesServiceStatusName(getAfterSalesServiceStatusName(orderInfo.getAfterSalesServiceStatus()));
-        orderInfo.setPlatformIncome(null);
-        orderInfo.setMerchantIncome(null);
+
+        // 获取用户下单的经纬度
+        BigDecimal userLatitude = orderInfo.getUserLatitude();
+        BigDecimal userLongitude = orderInfo.getUserLongitude();
+
+        //获取商户下单的真实经纬度
+        BigDecimal merchantLatitude = orderInfo.getMerchantLatitude();
+        BigDecimal merchantLongitude = orderInfo.getMerchantLongitude();
+
+        //计算距离
+        BigDecimal distanceMeters = calculateDistance(userLatitude, userLongitude, merchantLatitude, merchantLongitude);
+        log.info("用户与商户距离:{}米", distanceMeters);
+        //把米转换为公里
+        orderInfo.setDistance(distanceMeters.divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP));
+    }
+
+    /**
+     * 计算用户与商户两点之间的球面直线距离,单位:米。
+     *
+     * @param userLatitude      用户纬度
+     * @param userLongitude     用户经度
+     * @param merchantLatitude  商户纬度
+     * @param merchantLongitude 商户经度
+     * @return 距离,保留2位小数;坐标缺失或非法时返回null
+     */
+    private BigDecimal calculateDistance(BigDecimal userLatitude, BigDecimal userLongitude, BigDecimal merchantLatitude, BigDecimal merchantLongitude) {
+        if (!isCoordinateValid(userLatitude, userLongitude) || !isCoordinateValid(merchantLatitude, merchantLongitude)) {
+            return null;
+        }
+        double distanceMeters = DistanceUtil.getDistance(
+                userLatitude.doubleValue(),
+                userLongitude.doubleValue(),
+                merchantLatitude.doubleValue(),
+                merchantLongitude.doubleValue());
+        return BigDecimal.valueOf(distanceMeters).setScale(2, RoundingMode.HALF_UP);
     }
 
+    /**
+     * 校验单组经纬度是否完整且在合法范围内。
+     *
+     * @param latitude  纬度
+     * @param longitude 经度
+     * @return true-合法,false-非法
+     */
+    private boolean isCoordinateValid(BigDecimal latitude, BigDecimal longitude) {
+        if (latitude == null || longitude == null) {
+            return false;
+        }
+        if (BigDecimal.ZERO.compareTo(latitude) == 0 || BigDecimal.ZERO.compareTo(longitude) == 0) {
+            return false;
+        }
+        return latitude.abs().compareTo(MAX_LATITUDE) <= 0
+                && longitude.abs().compareTo(MAX_LONGITUDE) <= 0;
+    }
+
+    /**
+     * 填充项目详情显示字段
+     *
+     * @param projectInfo
+     */
     private void fillProjectDetailDisplayFields(AdminOrderDetailVO.ProjectInfoVO projectInfo) {
         projectInfo.setUnitName(getUnitName(projectInfo.getUnitType()));
-        projectInfo.setAfterSalesServiceStatusName(getAfterSalesServiceStatusName(projectInfo.getAfterSalesServiceStatus()));
+        //projectInfo.setAfterSalesServiceStatusName(getAfterSalesServiceStatusName(projectInfo.getAfterSalesServiceStatus()));
     }
 
+    /**
+     * 填充订单操作日志显示字段
+     * @param log
+     */
     private void fillOperationLogDisplayFields(AdminOrderDetailVO.OperationLogVO log) {
         String statusName = getStatusName(log.getStatus());
         log.setStatusName(statusName);
@@ -371,6 +440,12 @@ public class AdminOrderServiceImpl implements AdminOrderService {
         return statusEnum == null ? "" : statusEnum.getDesc();
     }
 
+    /**
+     * 解析开始服务照片
+     *
+     * @param startPhoto
+     * @return List<String> 开始服务照片列表
+     */
     private List<String> splitStartPhoto(String startPhoto) {
         if (!StringUtils.hasText(startPhoto)) {
             return new ArrayList<>();

+ 2 - 0
nightFragrance-massage/src/main/resources/mapper/order/AdminOrderMapper.xml

@@ -293,6 +293,7 @@
         )
     </insert>
 
+    <!-- 通过订单ID查询订单详情 -->
     <select id="selectOrderDetailInfoById" resultType="com.ylx.order.domain.vo.AdminOrderDetailVO$OrderInfoVO">
         SELECT
             o.id,
@@ -367,6 +368,7 @@
         LIMIT 1
     </select>
 
+    <!-- 根据订单ID查询订单操作日志 -->
     <select id="selectOrderOperationLogs" resultType="com.ylx.order.domain.vo.AdminOrderDetailVO$OperationLogVO">
         SELECT
             COALESCE(osf.create_by, '系统') AS operator,

+ 63 - 0
nightFragrance-massage/src/test/java/com/ylx/order/service/impl/AdminOrderServiceImplTest.java

@@ -22,6 +22,7 @@ import java.util.Collections;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -244,6 +245,68 @@ public class AdminOrderServiceImplTest {
         assertEquals("服务中", detail.getOperationLogList().get(0).getStatusName());
     }
 
+    @Test
+    public void detailCalculatesDistanceBetweenUserAndMerchantCoordinates() {
+        AdminOrderMapper mapper = mock(AdminOrderMapper.class);
+        AdminOrderDetailVO.OrderInfoVO orderInfo = new AdminOrderDetailVO.OrderInfoVO();
+        orderInfo.setUserLatitude(new BigDecimal("30.000000"));
+        orderInfo.setUserLongitude(new BigDecimal("120.000000"));
+        orderInfo.setMerchantLatitude(new BigDecimal("30.010000"));
+        orderInfo.setMerchantLongitude(new BigDecimal("120.010000"));
+
+        when(mapper.selectOrderDetailInfoById(1L)).thenReturn(orderInfo);
+        when(mapper.selectOrderProjectDetailById(1L)).thenReturn(null);
+        when(mapper.selectOrderOperationLogs(1L)).thenReturn(Collections.emptyList());
+        AdminOrderServiceImpl service = new AdminOrderServiceImpl();
+        ReflectionTestUtils.setField(service, "adminOrderMapper", mapper);
+
+        AdminOrderDetailVO detail = service.detail(1L);
+
+        BigDecimal distance = detail.getOrderInfo().getDistance();
+        assertNotNull(distance);
+        assertTrue(distance.compareTo(new BigDecimal("1460")) > 0);
+        assertTrue(distance.compareTo(new BigDecimal("1480")) < 0);
+        assertEquals(2, distance.scale());
+    }
+
+    @Test
+    public void detailLeavesDistanceEmptyWhenCoordinatesAreMissing() {
+        AdminOrderMapper mapper = mock(AdminOrderMapper.class);
+        AdminOrderDetailVO.OrderInfoVO orderInfo = new AdminOrderDetailVO.OrderInfoVO();
+        orderInfo.setUserLatitude(new BigDecimal("30.000000"));
+        orderInfo.setUserLongitude(new BigDecimal("120.000000"));
+
+        when(mapper.selectOrderDetailInfoById(1L)).thenReturn(orderInfo);
+        when(mapper.selectOrderProjectDetailById(1L)).thenReturn(null);
+        when(mapper.selectOrderOperationLogs(1L)).thenReturn(Collections.emptyList());
+        AdminOrderServiceImpl service = new AdminOrderServiceImpl();
+        ReflectionTestUtils.setField(service, "adminOrderMapper", mapper);
+
+        AdminOrderDetailVO detail = service.detail(1L);
+
+        assertEquals(null, detail.getOrderInfo().getDistance());
+    }
+
+    @Test
+    public void detailLeavesDistanceEmptyWhenCoordinatesAreZero() {
+        AdminOrderMapper mapper = mock(AdminOrderMapper.class);
+        AdminOrderDetailVO.OrderInfoVO orderInfo = new AdminOrderDetailVO.OrderInfoVO();
+        orderInfo.setUserLatitude(BigDecimal.ZERO);
+        orderInfo.setUserLongitude(new BigDecimal("120.000000"));
+        orderInfo.setMerchantLatitude(new BigDecimal("30.010000"));
+        orderInfo.setMerchantLongitude(new BigDecimal("120.010000"));
+
+        when(mapper.selectOrderDetailInfoById(1L)).thenReturn(orderInfo);
+        when(mapper.selectOrderProjectDetailById(1L)).thenReturn(null);
+        when(mapper.selectOrderOperationLogs(1L)).thenReturn(Collections.emptyList());
+        AdminOrderServiceImpl service = new AdminOrderServiceImpl();
+        ReflectionTestUtils.setField(service, "adminOrderMapper", mapper);
+
+        AdminOrderDetailVO detail = service.detail(1L);
+
+        assertEquals(null, detail.getOrderInfo().getDistance());
+    }
+
     @Test
     public void dispatchMerchantListRejectsOrderNotPendingDispatch() {
         AdminOrderMapper mapper = mock(AdminOrderMapper.class);