Răsfoiți Sursa

订单规则配置相关代码提交

wangzhijun 1 zi în urmă
părinte
comite
25096ca8e1

+ 21 - 5
nightFragrance-massage/src/main/java/com/ylx/order/controller/RegulationController.java

@@ -1,11 +1,15 @@
 package com.ylx.order.controller;
 
+import com.ylx.common.core.domain.R;
+import com.ylx.order.domain.dto.RegulationConfigDTO;
 import com.ylx.order.service.AutoFlowConfigService;
 import com.ylx.order.service.RefundRuleMasterService;
+import com.ylx.order.service.RegulationService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 
@@ -16,8 +20,20 @@ import javax.annotation.Resource;
 public class RegulationController {
 
     @Resource
-    private AutoFlowConfigService autoFlowConfigService;
-    @Resource
-    private RefundRuleMasterService refundRuleMasterService;
+    private RegulationService regulationService;
+
+    @ApiOperation("保存订单流转与退款规则配置")
+    @PostMapping("/save")
+    public R<?> saveConfig(@RequestBody @Validated RegulationConfigDTO dto) {
+        this.regulationService.saveFullConfig(dto);
+        return R.ok();
+    }
+
+    @ApiOperation("获取当前配置(用于回显)")
+    @GetMapping("/get")
+    public R<RegulationConfigDTO> getConfig() {
+        RegulationConfigDTO config = this.regulationService.getFullConfig();
+        return R.ok(config);
+    }
 
 }

+ 0 - 28
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/AutoFlowConfigDTO.java

@@ -1,28 +0,0 @@
-package com.ylx.order.domain.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-
-@Data
-@ApiModel("客户端提交订单DTO")
-public class AutoFlowConfigDTO implements Serializable {
-    private static final long serialVersionUID = 134359989887419040L;
-
-
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("是否开启自动流转: 0=关闭, 1=开启")
-    private Integer isEnabled;
-
-    @ApiModelProperty("超时阈值(小时)")
-    private BigDecimal timeoutHours;
-
-    @ApiModelProperty("系统处理动作: 0=自动退款")
-    private Integer defaultAction;
-
-}

+ 123 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/RegulationConfigDTO.java

@@ -0,0 +1,123 @@
+package com.ylx.order.domain.dto;
+
+import lombok.Data;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 订单流转与退款规则配置 DTO
+ * 用于接收前端“保存”操作提交的完整配置信息
+ */
+@Data
+public class RegulationConfigDTO {
+
+    // ==================== 1. 订单自动流转配置 ====================
+
+    /**
+     * 是否开启自动流转: true-开启, false-关闭
+     * 对应 DB: auto_flow_config.is_enabled
+     */
+    @NotNull(message = "自动流转开关不能为空")
+    private Boolean autoFlowEnabled;
+
+    /**
+     * 自动流转超时阈值(小时)
+     * 对应 DB: auto_flow_config.timeout_hours
+     * UI: "用户下单超 [ ] 小时"
+     */
+    @NotNull(message = "自动流转时间不能为空")
+    private BigDecimal timeoutHours;
+
+    /**
+     * 系统自动处理动作
+     * 对应 DB: auto_flow_config.default_action
+     * 0=自动退款 (目前UI只展示了这一个选项,预留扩展)
+     */
+    private Integer defaultAction;
+
+
+    // ==================== 2. 退款规则配置 ====================
+    // UI 上将退款规则分为了三个主要板块,我们将其封装为内部类列表
+
+    @NotNull(message = "未出发阶段策略不能为空")
+    private Integer preDepartureStrategy; // 0 or 1
+
+    /**
+     * 阶段一:商户未出发前 (Stage Type 0)
+     * UI 特征:支持多行配置(起始小时 至 结束小时,退款%)
+     */
+    @Valid
+    private List<TimeRangeRuleItem> preDepartureRules;
+
+    /**
+     * 阶段二:商户已出发/途中 (Stage Type 1)
+     * UI 特征:通常是一个固定比例,或者简单的列表
+     */
+    @Valid
+    private List<SimplePercentRuleItem> onWayRules;
+
+    /**
+     * 阶段三:服务进行中 (Stage Type 2)
+     * UI 特征:通常是一个固定比例
+     */
+    @Valid
+    private List<SimplePercentRuleItem> inServiceRules;
+
+
+    // ==================== 内部辅助类 ====================
+
+    /**
+     * 时间段规则项 (用于“商户未出发前”)
+     * 对应 DB: refund_rule_detail (stage_type=0)
+     */
+    @Data
+    public static class TimeRangeRuleItem {
+        /**
+         * 距离服务开始时间的起始小时数 (如 24.0)
+         * 对应 DB: time_start_hours
+         */
+        @NotNull(message = "起始小时不能为空")
+        private BigDecimal timeStartHours;
+
+        /**
+         * 距离服务开始时间的结束小时数 (如 8.0)
+         * 对应 DB: time_end_hours
+         */
+        @NotNull(message = "结束小时不能为空")
+        private BigDecimal timeEndHours;
+
+        /**
+         * 退款百分比 (如 80.00 代表 80%)
+         * 对应 DB: refund_percent
+         */
+        @NotNull(message = "退款比例不能为空")
+        private BigDecimal refundPercent;
+
+        /**
+         * 排序权重
+         */
+        private Integer sortOrder;
+    }
+
+    /**
+     * 简单比例规则项 (用于“商户已出发”和“服务中”)
+     * 对应 DB: refund_rule_detail (stage_type=1 or 2)
+     * 注意:这两个阶段通常不涉及复杂的时间窗口,或者时间窗口是隐含的
+     */
+    @Data
+    public static class SimplePercentRuleItem {
+        /**
+         * 退款类型: 0=全额, 1=部分
+         */
+        private Integer refundType;
+
+        /**
+         * 退款百分比
+         */
+        @NotNull(message = "退款比例不能为空")
+        private BigDecimal refundPercent;
+
+    }
+}

+ 27 - 0
nightFragrance-massage/src/main/java/com/ylx/order/enums/RefundStageTypeEnum.java

@@ -0,0 +1,27 @@
+package com.ylx.order.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum RefundStageTypeEnum {
+
+    PRE_DEPARTURE(0, "技师未出发前"),
+    ON_THE_WAY(1, "商户已出发(途中)"),
+    IN_SERVICE(2, "服务进行中");
+
+    private final Integer code;
+    private final String info;
+
+    RefundStageTypeEnum(Integer code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    public static RefundStageTypeEnum getByCode(Integer code) {
+        for (RefundStageTypeEnum e : values()) {
+            if (e.getCode().equals(code)) return e;
+        }
+        return null;
+    }
+
+}

+ 4 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/AutoFlowConfigService.java

@@ -2,6 +2,10 @@ package com.ylx.order.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.order.domain.AutoFlowConfig;
+import com.ylx.order.domain.dto.RegulationConfigDTO;
 
 public interface AutoFlowConfigService extends IService<AutoFlowConfig> {
+    void handleAutoFlowConfig(RegulationConfigDTO dto);
+
+    void loadAutoFlowConfig(RegulationConfigDTO dto);
 }

+ 1 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/RefundRuleDetailService.java

@@ -4,4 +4,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.order.domain.RefundRuleDetail;
 
 public interface RefundRuleDetailService extends IService<RefundRuleDetail> {
+    void clearDetailsByStage(Integer stageType, String operator);
 }

+ 6 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/RefundRuleMasterService.java

@@ -2,6 +2,12 @@ package com.ylx.order.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.order.domain.RefundRuleMaster;
+import com.ylx.order.domain.dto.RegulationConfigDTO;
 
 public interface RefundRuleMasterService extends IService<RefundRuleMaster> {
+
+    void handleRefundRules(RegulationConfigDTO dto);
+
+    void loadRefundRules(RegulationConfigDTO dto);
+
 }

+ 9 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/RegulationService.java

@@ -0,0 +1,9 @@
+package com.ylx.order.service;
+
+import com.ylx.order.domain.dto.RegulationConfigDTO;
+
+public interface RegulationService {
+    void saveFullConfig(RegulationConfigDTO dto);
+
+    RegulationConfigDTO getFullConfig();
+}

+ 47 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/AutoFlowConfigServiceImpl.java

@@ -1,13 +1,60 @@
 package com.ylx.order.service.impl;
 
+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.order.domain.AutoFlowConfig;
+import com.ylx.order.domain.dto.RegulationConfigDTO;
 import com.ylx.order.mapper.AutoFlowConfigMapper;
 import com.ylx.order.service.AutoFlowConfigService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 @Slf4j
 @Service
 public class AutoFlowConfigServiceImpl extends ServiceImpl<AutoFlowConfigMapper, AutoFlowConfig> implements AutoFlowConfigService {
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleAutoFlowConfig(RegulationConfigDTO dto) {
+
+        // 查询是否存在记录
+        LambdaQueryWrapper<AutoFlowConfig> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(AutoFlowConfig::getIsDelete, 0);
+        AutoFlowConfig config = this.baseMapper.selectOne(wrapper);
+
+        if (ObjectUtil.isNull(config)) {
+            config = new AutoFlowConfig();
+            config.setIsEnabled(dto.getAutoFlowEnabled() ? 1 : 0);
+            config.setTimeoutHours(dto.getTimeoutHours());
+            config.setDefaultAction(ObjectUtil.isNotNull(dto.getDefaultAction()) ? dto.getDefaultAction() : 0);
+            this.baseMapper.insert(config);
+        } else {
+            config.setIsEnabled(dto.getAutoFlowEnabled() ? 1 : 0);
+            config.setTimeoutHours(dto.getTimeoutHours());
+            if (ObjectUtil.isNotNull(dto.getDefaultAction())) {
+                config.setDefaultAction(dto.getDefaultAction());
+            }
+            this.baseMapper.updateById(config);
+        }
+
+    }
+
+    @Override
+    public void loadAutoFlowConfig(RegulationConfigDTO dto) {
+        LambdaQueryWrapper<AutoFlowConfig> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(AutoFlowConfig::getIsDelete, 0);
+        AutoFlowConfig config = this.baseMapper.selectOne(wrapper);
+
+        if (ObjectUtil.isNull(config)) {
+            dto.setAutoFlowEnabled(false);
+            dto.setDefaultAction(0);
+            return;
+        }
+
+        dto.setAutoFlowEnabled(ObjectUtil.equals(1, config.getIsEnabled()));
+        dto.setTimeoutHours(config.getTimeoutHours());
+        dto.setDefaultAction(ObjectUtil.isNotNull(config.getDefaultAction()) ? config.getDefaultAction() : 0);
+    }
 }

+ 23 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/RefundRuleDetailServiceImpl.java

@@ -1,14 +1,37 @@
 package com.ylx.order.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ylx.common.utils.DateUtils;
 import com.ylx.order.domain.RefundRuleDetail;
 import com.ylx.order.mapper.RefundRuleDetailMapper;
 import com.ylx.order.service.RefundRuleDetailService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 @Slf4j
 @Service
 public class RefundRuleDetailServiceImpl extends ServiceImpl<RefundRuleDetailMapper, RefundRuleDetail> implements RefundRuleDetailService {
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void clearDetailsByStage(Integer stageType, String operator) {
+        if (ObjectUtil.isNull(stageType)) {
+            throw new IllegalArgumentException("阶段类型不能为空");
+        }
+        // 构建更新条件:只修改 is_delete 和 update_by/update_time
+        LambdaUpdateWrapper<RefundRuleDetail> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(RefundRuleDetail::getStageType, stageType)
+                .eq(RefundRuleDetail::getIsDelete, 0) // 确保只处理未删除的数据
+                .set(RefundRuleDetail::getIsDelete, 1) // 逻辑删除标记
+                .set(RefundRuleDetail::getUpdateBy, operator)
+                .set(RefundRuleDetail::getUpdateTime, DateUtils.getNowDate());
+
+        int count = this.baseMapper.update(null, updateWrapper);
+        log.info("逻辑删除阶段 [{}] 的退款规则明细,影响行数: {}", stageType, count);
+
+    }
 }

+ 325 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/RefundRuleMasterServiceImpl.java

@@ -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();
+    }
+
 }

+ 47 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/RegulationServiceImpl.java

@@ -0,0 +1,47 @@
+package com.ylx.order.service.impl;
+
+import com.ylx.order.domain.dto.RegulationConfigDTO;
+import com.ylx.order.service.AutoFlowConfigService;
+import com.ylx.order.service.RefundRuleMasterService;
+import com.ylx.order.service.RegulationService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+@Slf4j
+@Service
+public class RegulationServiceImpl implements RegulationService {
+
+    @Resource
+    private AutoFlowConfigService autoFlowConfigService;
+    @Resource
+    private RefundRuleMasterService refundRuleMasterService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveFullConfig(RegulationConfigDTO dto) {
+        // 1. 保存/更新自动流转配置 (auto_flow_config)
+        handleAutoFlowConfig(dto);
+
+        // 2. 保存/更新退款规则 (refund_rule_master + detail)
+        handleRefundRules(dto);
+    }
+
+    private void handleAutoFlowConfig(RegulationConfigDTO dto) {
+        this.autoFlowConfigService.handleAutoFlowConfig(dto);
+    }
+
+    private void handleRefundRules(RegulationConfigDTO dto) {
+        this.refundRuleMasterService.handleRefundRules(dto);
+    }
+
+    @Override
+    public RegulationConfigDTO getFullConfig() {
+        RegulationConfigDTO dto = new RegulationConfigDTO();
+        this.autoFlowConfigService.loadAutoFlowConfig(dto);
+        this.refundRuleMasterService.loadRefundRules(dto);
+        return dto;
+    }
+}