Ver Fonte

售后单代码提交

wangzhijun há 8 horas atrás
pai
commit
644cf092c9

+ 9 - 0
nightFragrance-massage/src/main/java/com/ylx/order/controller/AfterSalesServiceController.java

@@ -6,6 +6,7 @@ import com.ylx.common.core.domain.R;
 import com.ylx.common.core.domain.entity.SysDictData;
 import com.ylx.common.utils.DictUtils;
 import com.ylx.order.domain.dto.AfterSalesServiceDTO;
+import com.ylx.order.domain.vo.RefundCalculationVO;
 import com.ylx.order.domain.vo.RegulationConfigVO;
 import com.ylx.order.enums.AfterSaleServiceDictTypeEnum;
 import com.ylx.order.service.IAfterSalesServiceService;
@@ -65,6 +66,14 @@ public class AfterSalesServiceController {
         return R.ok();
     }
 
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("客户端发起售后计算退款金额")
+    @PostMapping("/calculate/refund")
+    public R<RefundCalculationVO> calculateRefund(@Validated @RequestBody AfterSalesServiceDTO dto) {
+        RefundCalculationVO vo = this.afterSalesServiceService.calculateRefund(dto);
+        return R.ok(vo);
+    }
+
 
     @ApiOperation("根据商户履约状态获取退款描述")
     @GetMapping("/desc/list")

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

@@ -16,4 +16,7 @@ public class AfterSalesServiceDTO implements Serializable {
     @ApiModelProperty("关联的主订单ID")
     private Long orderId;
 
+    @ApiModelProperty("退款说明")
+    private String remark;
+
 }

+ 19 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/RefundCalculationVO.java

@@ -0,0 +1,19 @@
+package com.ylx.order.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@ApiModel("计算退款金额VO")
+@Data
+public class RefundCalculationVO {
+
+    private BigDecimal refundAmount;
+    private String refundDesc;
+
+    public RefundCalculationVO(BigDecimal refundAmount, String refundDesc) {
+        this.refundAmount = refundAmount;
+        this.refundDesc = refundDesc;
+    }
+}

+ 0 - 2
nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/RegulationConfigVO.java

@@ -1,7 +1,5 @@
 package com.ylx.order.domain.vo;
 
-import cn.hutool.core.util.ObjectUtil;
-import com.ylx.giftCard.domain.GiftCard;
 import com.ylx.order.domain.RefundRuleDetail;
 import io.swagger.annotations.ApiModel;
 import lombok.Data;

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

@@ -3,7 +3,10 @@ package com.ylx.order.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.order.domain.AfterSalesService;
 import com.ylx.order.domain.dto.AfterSalesServiceDTO;
+import com.ylx.order.domain.vo.RefundCalculationVO;
 
 public interface IAfterSalesServiceService extends IService<AfterSalesService> {
     void submitAfterSale(AfterSalesServiceDTO dto);
+
+    RefundCalculationVO calculateRefund(AfterSalesServiceDTO dto);
 }

+ 187 - 1
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/AfterSalesServiceServiceImpl.java

@@ -1,24 +1,210 @@
 package com.ylx.order.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ylx.common.core.domain.model.WxLoginUser;
+import com.ylx.common.exception.ServiceException;
+import com.ylx.common.utils.DateUtils;
+import com.ylx.common.utils.SecurityUtils;
 import com.ylx.order.domain.AfterSalesService;
+import com.ylx.order.domain.RefundRuleDetail;
+import com.ylx.order.domain.TOrder;
 import com.ylx.order.domain.dto.AfterSalesServiceDTO;
+import com.ylx.order.domain.vo.RefundCalculationVO;
+import com.ylx.order.enums.AfterSaleServiceStatusEnum;
+import com.ylx.order.enums.OrderStatusEnum;
+import com.ylx.order.enums.RefundStageTypeEnum;
 import com.ylx.order.mapper.AfterSalesServiceMapper;
 import com.ylx.order.service.IAfterSalesServiceService;
+import com.ylx.order.service.RefundRuleDetailService;
+import com.ylx.order.service.TOrderService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
 @Slf4j
 @Service
 public class AfterSalesServiceServiceImpl extends ServiceImpl<AfterSalesServiceMapper, AfterSalesService>
         implements IAfterSalesServiceService {
 
+    @Resource
+    private TOrderService tOrderService;
+    @Resource
+    private RefundRuleDetailService refundRuleDetailService;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void submitAfterSale(AfterSalesServiceDTO dto) {
 
-        // 1. 获取订单信息
+        // 1. 获取当前用户
+        WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
+        if (ObjectUtil.isNull(wxLoginUser)) {
+            log.warn("用户未登录,无法创建订单");
+            throw new ServiceException("用户未登录");
+        }
+
+        Long currentUserId = Long.valueOf(wxLoginUser.getId());
+
+        // 2. 获取订单信息
+        TOrder order = this.tOrderService.getById(dto.getOrderId());
+        if (ObjectUtil.isNull(order) || ObjectUtil.equals(1, order.getIsDelete())) {
+            throw new ServiceException("订单不存在");
+        }
+        if (ObjectUtil.notEqual(order.getUserId(), currentUserId)) {
+            throw new ServiceException("您无权操作此订单");
+        }
+
+        validateOrderStatus(order);
+        validateNoPendingAfterSale(dto.getOrderId());
+
+        RefundCalculationVO refundResult = calculateRefund(order);
+
+        AfterSalesService afterSalesService = new AfterSalesService();
+        afterSalesService.setServiceNo(generateServiceNo(dto.getOrderId()));
+        afterSalesService.setOrderId(dto.getOrderId());
+        afterSalesService.setUserId(currentUserId);
+        afterSalesService.setStatus(AfterSaleServiceStatusEnum.PENDING_AUDIT.getCode());
+        afterSalesService.setActualRefundAmount(refundResult.getRefundAmount());
+        afterSalesService.setRefundDesc(refundResult.getRefundDesc());
+        afterSalesService.setCreateTime(DateUtils.getNowDate());
+        afterSalesService.setRemark(dto.getRemark());
+
+        if (!this.save(afterSalesService)) {
+            throw new ServiceException("创建售后服务单失败");
+        }
+
+        log.info("用户提交售后成功, orderId={}, serviceNo={}, refundAmount={}",
+                dto.getOrderId(), afterSalesService.getServiceNo(), refundResult.getRefundAmount());
+    }
+
+    @Override
+    public RefundCalculationVO calculateRefund(AfterSalesServiceDTO dto) {
+
+        TOrder order = this.tOrderService.getById(dto.getOrderId());
+        if (ObjectUtil.isNull(order) || ObjectUtil.equals(1, order.getIsDelete())) {
+            throw new ServiceException("订单不存在");
+        }
+
+        return calculateRefund(order);
+    }
+
+    private void validateOrderStatus(TOrder order) {
+        Integer status = order.getStatus();
+        if (OrderStatusEnum.IN_SERVICE.getCode().equals(status)) {
+            throw new ServiceException("操作错误,请重试");
+        }
+        if (OrderStatusEnum.CANCELLED.getCode().equals(status)) {
+            throw new ServiceException("已取消订单不支持售后");
+        }
+    }
+
+    private void validateNoPendingAfterSale(Long orderId) {
+        long count = this.count(new LambdaQueryWrapper<AfterSalesService>()
+                .eq(AfterSalesService::getOrderId, orderId)
+                .in(AfterSalesService::getStatus,
+                        AfterSaleServiceStatusEnum.PENDING_AUDIT.getCode(),
+                        AfterSaleServiceStatusEnum.APPROVED.getCode(),
+                        AfterSaleServiceStatusEnum.REFUND_SUCCESS.getCode()
+                ));
+        if (count > 0) {
+            throw new ServiceException("操作错误,请重试");
+        }
+    }
+
+    private RefundCalculationVO calculateRefund(TOrder order) {
+        Integer stageType = resolveStageType(order);
+        List<RefundRuleDetail> details = listDetailsByStage(stageType);
+        if (CollUtil.isEmpty(details)) {
+            throw new ServiceException("未配置退款规则,请联系客服");
+        }
+
+        RefundRuleDetail matchedRule;
+        if (RefundStageTypeEnum.PRE_DEPARTURE.getCode().equals(stageType)) {
+            matchedRule = matchPreDepartureRule(details, order.getAppointmentStartTime());
+        } else {
+            matchedRule = CollUtil.getFirst(details);
+        }
+
+        BigDecimal percent = Optional.ofNullable(matchedRule.getRefundPercent()).orElse(BigDecimal.ZERO);
+        BigDecimal finalAmount = Optional.ofNullable(order.getFinalAmount()).orElse(BigDecimal.ZERO);
+        BigDecimal refundAmount = finalAmount.multiply(percent)
+                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+
+        return new RefundCalculationVO(refundAmount, matchedRule.getRefundDesc());
+    }
+
+    private Integer resolveStageType(TOrder order) {
+        if (OrderStatusEnum.IN_SERVICE.getCode().equals(order.getStatus())) {
+            return RefundStageTypeEnum.IN_SERVICE.getCode();
+        }
+        return ObjectUtil.defaultIfNull(order.getExecStatus(), RefundStageTypeEnum.PRE_DEPARTURE.getCode());
+    }
+
+    private List<RefundRuleDetail> listDetailsByStage(Integer stageType) {
+        LambdaQueryWrapper<RefundRuleDetail> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(RefundRuleDetail::getStageType, stageType)
+                .eq(RefundRuleDetail::getIsDelete, 0)
+                .orderByAsc(RefundRuleDetail::getSortOrder)
+                .orderByAsc(RefundRuleDetail::getId);
+        return this.refundRuleDetailService.list(wrapper);
+    }
+
+    private RefundRuleDetail matchPreDepartureRule(List<RefundRuleDetail> details, LocalDateTime appointmentStartTime) {
+
+        // 预排序:确保按时间区间顺序匹配
+        details.sort(Comparator.comparing(RefundRuleDetail::getSortOrder, Comparator.nullsLast(Integer::compareTo)));
+
+        RefundRuleDetail first = CollUtil.getFirst(details);
+        if (details.size() == 1 && ObjectUtil.equals(0, first.getRefundType())) {
+            return first;
+        }
+
+        BigDecimal hoursUntilStart = calcHoursUntilStart(appointmentStartTime);
+        if (hoursUntilStart.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("已过预约服务时间,无法申请退款");
+        }
+
+        for (RefundRuleDetail detail : details) {
+            if (matchTimeRange(detail, hoursUntilStart)) {
+                return detail;
+            }
+        }
+        throw new ServiceException("未匹配到适用的退款规则");
+    }
+
+    private boolean matchTimeRange(RefundRuleDetail detail, BigDecimal hoursUntilStart) {
+        BigDecimal start = detail.getTimeStartHours();
+        BigDecimal end = detail.getTimeEndHours();
+        if (start != null && start.compareTo(BigDecimal.ZERO) > 0) {
+            return hoursUntilStart.compareTo(end) > 0 && hoursUntilStart.compareTo(start) <= 0;
+        }
+        if (end != null) {
+            return hoursUntilStart.compareTo(end) <= 0;
+        }
+        return false;
+    }
+
+    private BigDecimal calcHoursUntilStart(LocalDateTime appointmentStartTime) {
+        if (ObjectUtil.isNull(appointmentStartTime)) {
+            throw new ServiceException("订单预约时间缺失,无法计算退款金额");
+        }
+        long minutes = Duration.between(LocalDateTime.now(), appointmentStartTime).toMinutes();
+        return BigDecimal.valueOf(minutes).divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+    }
+
+    private String generateServiceNo(Long orderId) {
+        return "ASS" + orderId + System.currentTimeMillis() + (int)(Math.random() * 1000);
     }
 
 }