|
|
@@ -1,13 +1,338 @@
|
|
|
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.exception.ServiceException;
|
|
|
+import com.ylx.common.utils.DateUtils;
|
|
|
+import com.ylx.common.utils.SecurityUtils;
|
|
|
+import com.ylx.order.domain.RefundRuleDetail;
|
|
|
import com.ylx.order.domain.RefundRuleMaster;
|
|
|
+import com.ylx.order.domain.dto.RegulationConfigDTO;
|
|
|
+import com.ylx.order.enums.RefundStageTypeEnum;
|
|
|
import com.ylx.order.mapper.RefundRuleMasterMapper;
|
|
|
+import com.ylx.order.service.RefundRuleDetailService;
|
|
|
import com.ylx.order.service.RefundRuleMasterService;
|
|
|
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.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
public class RefundRuleMasterServiceImpl extends ServiceImpl<RefundRuleMasterMapper, RefundRuleMaster> implements RefundRuleMasterService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private RefundRuleDetailService refundRuleDetailService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void handleRefundRules(RegulationConfigDTO dto) {
|
|
|
+
|
|
|
+ String operator = SecurityUtils.getUsername();
|
|
|
+ // 第一步:处理 MASTER 表
|
|
|
+ Long preDepartureMasterId = createOrUpdateMaster(RefundStageTypeEnum.PRE_DEPARTURE.getCode(), operator);
|
|
|
+ Long onWayMasterId = createOrUpdateMaster(RefundStageTypeEnum.ON_THE_WAY.getCode(), operator);
|
|
|
+ Long inServiceMasterId = createOrUpdateMaster(RefundStageTypeEnum.IN_SERVICE.getCode(), operator);
|
|
|
+
|
|
|
+ // 第二步:清理旧的 DETAIL 数据
|
|
|
+ clearDetailsByStage(RefundStageTypeEnum.PRE_DEPARTURE.getCode(), operator);
|
|
|
+ clearDetailsByStage(RefundStageTypeEnum.ON_THE_WAY.getCode(), operator);
|
|
|
+ clearDetailsByStage(RefundStageTypeEnum.IN_SERVICE.getCode(), operator);
|
|
|
+
|
|
|
+ // 第三步:组装并插入新的 DETAIL 数据
|
|
|
+ List<RefundRuleDetail> allDetails = new ArrayList<>();
|
|
|
+
|
|
|
+ // 1. 处理【商户未出发前】逻辑 (最复杂的部分)
|
|
|
+ handlePreDepartureDetails(preDepartureMasterId, dto.getPreDepartureStrategy(), dto.getPreDepartureRules(), allDetails, operator);
|
|
|
+
|
|
|
+ // 2. 处理【商户已出发】逻辑
|
|
|
+ handleSimpleStageDetails(onWayMasterId, dto.getOnWayRules(), RefundStageTypeEnum.ON_THE_WAY.getCode(), allDetails, operator);
|
|
|
+
|
|
|
+ // 3. 处理【服务进行中】逻辑
|
|
|
+ handleSimpleStageDetails(inServiceMasterId, dto.getInServiceRules(), RefundStageTypeEnum.IN_SERVICE.getCode(), allDetails, operator);
|
|
|
+
|
|
|
+ // 批量插入明细
|
|
|
+ if (!allDetails.isEmpty()) {
|
|
|
+ this.refundRuleDetailService.saveBatch(allDetails);
|
|
|
+ log.info("成功保存退款规则明细,共 {} 条", allDetails.size());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void loadRefundRules(RegulationConfigDTO dto) {
|
|
|
+ loadPreDepartureRules(dto);
|
|
|
+ dto.setOnWayRules(loadSimpleStageRules(RefundStageTypeEnum.ON_THE_WAY.getCode()));
|
|
|
+ dto.setInServiceRules(loadSimpleStageRules(RefundStageTypeEnum.IN_SERVICE.getCode()));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void loadPreDepartureRules(RegulationConfigDTO dto) {
|
|
|
+ List<RefundRuleDetail> details = listDetailsByStage(RefundStageTypeEnum.PRE_DEPARTURE.getCode());
|
|
|
+
|
|
|
+ if (CollUtil.isEmpty(details)) {
|
|
|
+ dto.setPreDepartureStrategy(0);
|
|
|
+ dto.setPreDepartureRules(Collections.emptyList());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ RefundRuleDetail first = CollUtil.getFirst(details);
|
|
|
+ if (details.size() == 1 && ObjectUtil.equals(0, first.getRefundType())) {
|
|
|
+ dto.setPreDepartureStrategy(0);
|
|
|
+ dto.setPreDepartureRules(Collections.emptyList());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ dto.setPreDepartureStrategy(1);
|
|
|
+ dto.setPreDepartureRules(details.stream().map(this::toTimeRangeRuleItem).collect(Collectors.toList()));
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<RegulationConfigDTO.SimplePercentRuleItem> loadSimpleStageRules(Integer stageType) {
|
|
|
+ List<RefundRuleDetail> details = listDetailsByStage(stageType);
|
|
|
+ if (CollUtil.isEmpty(details)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<RegulationConfigDTO.SimplePercentRuleItem> rules = new ArrayList<>(1);
|
|
|
+ rules.add(toSimplePercentRuleItem(CollUtil.getFirst(details)));
|
|
|
+ return rules;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 RegulationConfigDTO.TimeRangeRuleItem toTimeRangeRuleItem(RefundRuleDetail detail) {
|
|
|
+ RegulationConfigDTO.TimeRangeRuleItem item = new RegulationConfigDTO.TimeRangeRuleItem();
|
|
|
+ item.setTimeStartHours(detail.getTimeStartHours());
|
|
|
+ item.setTimeEndHours(detail.getTimeEndHours());
|
|
|
+ item.setRefundPercent(detail.getRefundPercent());
|
|
|
+ item.setSortOrder(detail.getSortOrder());
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ private RegulationConfigDTO.SimplePercentRuleItem toSimplePercentRuleItem(RefundRuleDetail detail) {
|
|
|
+ RegulationConfigDTO.SimplePercentRuleItem item = new RegulationConfigDTO.SimplePercentRuleItem();
|
|
|
+ item.setRefundType(detail.getRefundType());
|
|
|
+ item.setRefundPercent(detail.getRefundPercent());
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建或更新主表记录
|
|
|
+ * 逻辑:查找是否存在该 stage_type 的记录,存在则更新时间,不存在则新增
|
|
|
+ */
|
|
|
+ private Long createOrUpdateMaster(Integer stageType, String operator) {
|
|
|
+
|
|
|
+ // 查询是否存在
|
|
|
+ LambdaQueryWrapper<RefundRuleMaster> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(RefundRuleMaster::getStageType, stageType).eq(RefundRuleMaster::getIsDelete, 0);
|
|
|
+ RefundRuleMaster existing = this.baseMapper.selectOne(wrapper);
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotNull(existing)) {
|
|
|
+ existing.setUpdateTime(DateUtils.getNowDate());
|
|
|
+ existing.setUpdateBy(operator);
|
|
|
+ this.baseMapper.updateById(existing);
|
|
|
+ return existing.getId();
|
|
|
+ } else {
|
|
|
+ // 新增
|
|
|
+ RefundRuleMaster master = new RefundRuleMaster();
|
|
|
+ master.setStageType(stageType);
|
|
|
+ master.setCreateBy(operator);
|
|
|
+ master.setCreateTime(DateUtils.getNowDate());
|
|
|
+ master.setIsDelete(0);
|
|
|
+ this.baseMapper.insert(master);
|
|
|
+ return master.getId();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理“未出发前”的特殊逻辑
|
|
|
+ *
|
|
|
+ * @param masterId 主表ID
|
|
|
+ * @param strategy 策略: 0=全额, 1=部分
|
|
|
+ * @param rules 前端传来的时间段列表
|
|
|
+ * @param operator
|
|
|
+ */
|
|
|
+ private void handlePreDepartureDetails(Long masterId, Integer strategy, List<RegulationConfigDTO.TimeRangeRuleItem> rules, List<RefundRuleDetail> targetList, String operator) {
|
|
|
+ if (strategy == 0) {
|
|
|
+ // === 情况 A:全额退款 ===
|
|
|
+ RefundRuleDetail detail = new RefundRuleDetail();
|
|
|
+ detail.setMasterId(masterId);
|
|
|
+ detail.setStageType(RefundStageTypeEnum.PRE_DEPARTURE.getCode());
|
|
|
+ detail.setRefundType(0); // 全额
|
|
|
+ detail.setRefundPercent(BigDecimal.valueOf(100));
|
|
|
+ detail.setSortOrder(1);
|
|
|
+ detail.setRefundDesc("出发前全额退款");
|
|
|
+ detail.setCreateBy(operator);
|
|
|
+ detail.setCreateTime(DateUtils.getNowDate());
|
|
|
+ targetList.add(detail);
|
|
|
+
|
|
|
+ } else if (strategy == 1) {
|
|
|
+ // === 情况 B:部分退款(分时段) ===
|
|
|
+ if (ObjectUtil.isEmpty(rules)) {
|
|
|
+ throw new ServiceException("选择部分退款时,必须配置时间段规则!");
|
|
|
+ }
|
|
|
+
|
|
|
+ rules.sort(Comparator.comparing(RegulationConfigDTO.TimeRangeRuleItem::getTimeStartHours));
|
|
|
+
|
|
|
+ for (RegulationConfigDTO.TimeRangeRuleItem rule : rules) {
|
|
|
+ RefundRuleDetail detail = new RefundRuleDetail();
|
|
|
+ detail.setMasterId(masterId);
|
|
|
+ detail.setStageType(RefundStageTypeEnum.PRE_DEPARTURE.getCode());
|
|
|
+
|
|
|
+ // 时间窗口
|
|
|
+ detail.setTimeStartHours(rule.getTimeStartHours());
|
|
|
+ detail.setTimeEndHours(rule.getTimeEndHours());
|
|
|
+
|
|
|
+ // 退款比例
|
|
|
+ detail.setRefundType(1); // 部分
|
|
|
+ detail.setRefundPercent(rule.getRefundPercent());
|
|
|
+ detail.setSortOrder(rule.getSortOrder());
|
|
|
+
|
|
|
+ // 生成描述 (可选)
|
|
|
+ String desc = generatePreDepartureDesc(rule);
|
|
|
+ detail.setRefundDesc(desc);
|
|
|
+
|
|
|
+ detail.setCreateBy(operator);
|
|
|
+ detail.setCreateTime(DateUtils.getNowDate());
|
|
|
+ targetList.add(detail);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理简单阶段(已出发、进行中)
|
|
|
+ * 逻辑:无论全额还是部分,都只有一条记录
|
|
|
+ */
|
|
|
+ private void handleSimpleStageDetails(Long masterId, List<RegulationConfigDTO.SimplePercentRuleItem> list, Integer refundStageTypeCode, List<RefundRuleDetail> targetList, String operator) {
|
|
|
+
|
|
|
+ // 1. 通过枚举获取阶段描述,用于日志输出
|
|
|
+ RefundStageTypeEnum stageEnum = RefundStageTypeEnum.getByCode(refundStageTypeCode);
|
|
|
+ if (ObjectUtil.isNull(stageEnum)) {
|
|
|
+ throw new ServiceException("refundStageTypeCode参数有误,查不到对应的类型");
|
|
|
+ }
|
|
|
+ String stageDesc = stageEnum.getInfo();
|
|
|
+
|
|
|
+ // 2. 打印日志,清晰明了
|
|
|
+ log.info("开始处理【{}】阶段的退款规则配置...", stageDesc);
|
|
|
+
|
|
|
+ if (CollUtil.isEmpty(list)) {
|
|
|
+ throw new ServiceException("【" + stageDesc + "】的比例规则项不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ RegulationConfigDTO.SimplePercentRuleItem ruleItem = CollUtil.getFirst(list);
|
|
|
+
|
|
|
+ RefundRuleDetail detail = new RefundRuleDetail();
|
|
|
+ detail.setMasterId(masterId);
|
|
|
+ detail.setStageType(refundStageTypeCode);
|
|
|
+
|
|
|
+ detail.setRefundType(ruleItem.getRefundType());
|
|
|
+ detail.setRefundPercent(ruleItem.getRefundPercent());
|
|
|
+ detail.setSortOrder(1);
|
|
|
+
|
|
|
+ detail.setCreateBy(operator);
|
|
|
+ detail.setCreateTime(DateUtils.getNowDate());
|
|
|
+
|
|
|
+ targetList.add(detail);
|
|
|
+
|
|
|
+ // 3. 处理完成日志
|
|
|
+ log.debug("【{}】阶段规则构建完成: Type={}, Percent={}",
|
|
|
+ stageDesc, ruleItem.getRefundType(), ruleItem.getRefundPercent());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void clearDetailsByStage(Integer stageType, String operator) {
|
|
|
+ log.warn("正在清理 stage_type={} 的旧明细数据, operator ={} ", stageType, operator);
|
|
|
+ this.refundRuleDetailService.clearDetailsByStage(stageType, operator);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理复杂阶段(未出发前)
|
|
|
+ * 逻辑:根据时间段列表生成多条明细,并动态生成 refundDesc
|
|
|
+ */
|
|
|
+ private void handleComplexStageDetails(Long masterId, List<RegulationConfigDTO.TimeRangeRuleItem> list, Integer stageType, List<RefundRuleDetail> targetList) {
|
|
|
+ if (CollUtil.isEmpty(list)) {
|
|
|
+ throw new ServiceException("未出发阶段的退款规则不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按开始时间排序,确保生成的文案顺序正确(可选,视前端是否已排好序而定)
|
|
|
+ list.sort(Comparator.comparing(RegulationConfigDTO.TimeRangeRuleItem::getTimeStartHours));
|
|
|
+
|
|
|
+ int sortIndex = 1;
|
|
|
+ for (RegulationConfigDTO.TimeRangeRuleItem item : list) {
|
|
|
+ RefundRuleDetail detail = new RefundRuleDetail();
|
|
|
+ detail.setMasterId(masterId);
|
|
|
+ detail.setStageType(stageType);
|
|
|
+ detail.setRefundType(1); // 1代表部分退款/自定义
|
|
|
+ detail.setRefundPercent(item.getRefundPercent());
|
|
|
+ detail.setSortOrder(sortIndex++);
|
|
|
+
|
|
|
+ // ================= 核心修改:动态生成描述文案 =================
|
|
|
+ String desc = generatePreDepartureDesc(item);
|
|
|
+ detail.setRefundDesc(desc);
|
|
|
+ // ==========================================================
|
|
|
+
|
|
|
+ targetList.add(detail);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据时间段规则项生成对应的中文描述
|
|
|
+ * @param item 规则项(包含开始时间、结束时间、比例)
|
|
|
+ * @return 格式化后的文案
|
|
|
+ */
|
|
|
+ private String generatePreDepartureDesc(RegulationConfigDTO.TimeRangeRuleItem item) {
|
|
|
+ BigDecimal startHour = item.getTimeStartHours();
|
|
|
+ BigDecimal endHour = item.getTimeEndHours();
|
|
|
+ BigDecimal percent = item.getRefundPercent();
|
|
|
+
|
|
|
+ // 基础文案模板
|
|
|
+ String prefix = "距离服务开始前";
|
|
|
+ String middle = "小时申请退款,只退付金额的";
|
|
|
+ String suffix = "%";
|
|
|
+
|
|
|
+ // 场景 1:标准的区间(例如:24-48小时)
|
|
|
+ // 注意:这里需要判断 startHour 是否有值。
|
|
|
+ // 如果业务定义 startHour 为 null 或 0 代表“不足X小时”,则走场景 2
|
|
|
+ if (startHour != null && startHour.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ return String.format("%s%s至%s%s%s%s",
|
|
|
+ prefix,
|
|
|
+ formatNumber(startHour), // 去除小数点后的 .0
|
|
|
+ formatNumber(endHour),
|
|
|
+ middle,
|
|
|
+ formatNumber(percent),
|
|
|
+ suffix
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 场景 2:兜底区间(例如:不足 24 小时)
|
|
|
+ else {
|
|
|
+ return String.format("距离服务开始不足%s%s%s%s",
|
|
|
+ formatNumber(endHour),
|
|
|
+ middle,
|
|
|
+ formatNumber(percent),
|
|
|
+ suffix
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 辅助方法:格式化数字,如果是整数则去掉小数点(24.0 -> 24)
|
|
|
+ */
|
|
|
+ private String formatNumber(BigDecimal number) {
|
|
|
+ if (number == null) return "";
|
|
|
+ return number.stripTrailingZeros().toPlainString();
|
|
|
+ }
|
|
|
+
|
|
|
}
|