|
|
@@ -1,14 +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. 获取当前用户
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
}
|