Browse Source

Merge remote-tracking branch 'origin/dev' into dev

jinshihui 6 hours ago
parent
commit
85779a008c
16 changed files with 426 additions and 31 deletions
  1. 14 8
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java
  2. 9 1
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantApplyFileRequestDto.java
  3. 9 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java
  4. 31 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java
  5. 23 7
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TAddressServiceImpl.java
  6. 53 3
      nightFragrance-massage/src/main/java/com/ylx/order/controller/AfterSalesServiceController.java
  7. 22 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/AfterSalesServiceDTO.java
  8. 19 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/RefundCalculationVO.java
  9. 0 2
      nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/RegulationConfigVO.java
  10. 36 0
      nightFragrance-massage/src/main/java/com/ylx/order/enums/AfterSaleServiceDictTypeEnum.java
  11. 3 4
      nightFragrance-massage/src/main/java/com/ylx/order/enums/OrderStatusEnum.java
  12. 5 0
      nightFragrance-massage/src/main/java/com/ylx/order/service/IAfterSalesServiceService.java
  13. 196 0
      nightFragrance-massage/src/main/java/com/ylx/order/service/impl/AfterSalesServiceServiceImpl.java
  14. 3 3
      nightFragrance-massage/src/main/java/com/ylx/project/controller/ProjectController.java
  15. 1 1
      nightFragrance-massage/src/main/java/com/ylx/project/service/ProjectService.java
  16. 2 2
      nightFragrance-massage/src/main/java/com/ylx/project/service/impl/ProjectServiceImpl.java

+ 14 - 8
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java

@@ -659,15 +659,10 @@ public class MaTechnicianController extends BaseController {
      * 支持 Tab 切换:all(全部), active(已开通), applying(申请中), rejected(驳回)
      */
     @PostMapping("/getSkillList")
-    public TableDataInfo getSkillList(@RequestBody MaProjectGetVo req) {
-        startPage();
+    public Result<List<MaProject>> getSkillList(@RequestBody MaProjectGetVo req) {
+
         List<MaProject> list = maTechnicianService.selectMaTechnicianListBy(req.getUserId(), req.getAuditStatus());
-        if (ObjectUtils.isEmpty(list)) {
-            List<Project> projectslist = maTechnicianService.selectTechnicianListBy(req.getTypeId());
-            return getDataTable(projectslist);
-        } else {
-            return getDataTable(list);
-        }
+        return Result.ok(list);
     }
 
     /**
@@ -760,4 +755,15 @@ public class MaTechnicianController extends BaseController {
         return Result.ok(maTechnicianService.getTechnicianList(userId));
     }
 
+    /**
+     * 查询商户合同记录信息
+     *
+     * @param userId
+     * @return
+     */
+
+    @GetMapping("/getContractRecords")
+    public Result<?> getContractRecords(@RequestParam(value = "userId") Long userId) {
+        return Result.ok(maTechnicianService.getContractRecords(userId));
+    }
 }

+ 9 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MerchantApplyFileRequestDto.java

@@ -1,10 +1,18 @@
 package com.ylx.massage.domain.dto;
 
+import com.ylx.massage.domain.MaTechnician;
 import lombok.Data;
 
 import java.util.List;
 
 @Data
 public class MerchantApplyFileRequestDto {
-    private List<MerchantApplyFileDto> req;
+    /**
+     * 商家信息
+     */
+    private MaTechnician technician;
+    /**
+     * 申请入驻文件
+     */
+    private  List<MerchantApplyFileDto> req;
 }

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

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.common.core.domain.AjaxResult;
 import com.ylx.common.core.domain.model.LoginUser;
+import com.ylx.massage.domain.ContractRecord;
 import com.ylx.massage.domain.MaProject;
 import com.ylx.massage.domain.MaTechnician;
 import com.ylx.massage.domain.dto.*;
@@ -269,4 +270,12 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
      * @return
      */
     MerchantAuditFile getTechnicianList(Long userId);
+
+    /**
+     * 查询商户合同记录信息
+     *
+     * @param userId
+     * @return
+     */
+    List<ContractRecord> getContractRecords(Long userId);
 }

+ 31 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java

@@ -118,6 +118,8 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
     private MerchantApplyFileMapper merchantApplyFileMapper;
     @Resource
     private TOrderMapper orderMapper;
+    @Autowired
+    private IMaTechnicianService maTechnicianService;
     @Resource
     private  CityOperationApplicationMapper cityOperationApplicationMapper;
 
@@ -195,6 +197,11 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
 
 
         }
+        LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(MaTechnician::getId, req.getTechnician().getId());
+        updateWrapper.set(MaTechnician::getTeNickName, req.getTechnician().getTeNickName());
+        updateWrapper.set(MaTechnician::getTeBrief, req.getTechnician().getTeBrief());
+        maTechnicianService.update(updateWrapper);
     }
 
     /**
@@ -915,6 +922,30 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         merchantAuditFile.setMerchantAuditFile(merchantApplyFile);
         return merchantAuditFile;
     }
+    /**
+     * 查询商户合同记录信息
+     *
+     * @param userId
+     * @return
+     */
+    @Override
+   public List<ContractRecord> getContractRecords(Long userId){
+        LambdaQueryWrapper<ContractRecord> query = new LambdaQueryWrapper<>();
+        query.eq(ContractRecord::getMerchantId, userId);
+        List<ContractRecord> contractRecordList = contractRecordMapper.selectList(query);
+        if(contractRecordList.size() == 0) {
+            return new ArrayList<>();
+        }else {
+            Set<String> seen = new HashSet<>();
+            contractRecordList = contractRecordList.stream()
+                                                        .filter(record -> record.getContractName() != null && seen.add(record.getContractName()))
+                                                        .collect(Collectors.toList());
+
+        }
+
+        return contractRecordList;
+
+    }
 
     private void extracted(MaProjectSaveDto dto) {
         LambdaQueryWrapper<Project> query = new LambdaQueryWrapper<>();

+ 23 - 7
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TAddressServiceImpl.java

@@ -6,7 +6,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.utils.StringUtils;
+import com.ylx.massage.domain.dto.CoordinateDTO;
+import com.ylx.massage.domain.vo.CityInfoVo;
 import com.ylx.massage.domain.vo.UserAddressListVO;
+import com.ylx.massage.service.AreaService;
 import com.ylx.useradress.domain.dto.UserAddressAddDto;
 import com.ylx.useradress.domain.dto.UserAddressDeleteDto;
 import com.ylx.useradress.domain.dto.UserAddressDto;
@@ -16,6 +19,7 @@ import com.ylx.massage.mapper.TAddressMapper;
 import com.ylx.massage.domain.TAddress;
 import com.ylx.massage.service.TAddressService;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -43,6 +47,8 @@ public class TAddressServiceImpl extends ServiceImpl<TAddressMapper, TAddress> i
     private static final Integer IS_DEFAULT = 1;
     //0=不是默认地址
     private static final Integer NO_DEFAULT = 0;
+    @Autowired
+    private AreaService areaService;
 
 
     @Override
@@ -119,9 +125,9 @@ public class TAddressServiceImpl extends ServiceImpl<TAddressMapper, TAddress> i
      @Override
     @Transactional(rollbackFor = Exception.class)
     public Object insertVirtualAddress(TAddress tAddress) {
-//        if (StringUtils.isBlank(tAddress.getOpenid())) {
-//            throw new RuntimeException("openid不能为空");
-//        }
+        if (StringUtils.isBlank(tAddress.getOpenid())) {
+            throw new RuntimeException("openid不能为空");
+        }
         //检查用户类型是否为空
         if (Objects.isNull(tAddress.getUserType())) {
             throw new RuntimeException("用户类型不能为空");
@@ -139,10 +145,20 @@ public class TAddressServiceImpl extends ServiceImpl<TAddressMapper, TAddress> i
          if (Objects.isNull(tAddress.getType())) {
              throw new RuntimeException("地址类型不能为空");
          }
-//         //检查地址类型是否为1或2
-//         if (tAddress.getType() != 2) {
-//             throw new RuntimeException("地址类型错误,地址类型只能为2");
-//         }
+         //检查地址类型是否为1或2
+         if (tAddress.getType() != 2) {
+             throw new RuntimeException("地址类型错误,地址类型只能为2");
+         }
+         if(StrUtil.isBlank(tAddress.getCityCode())){
+             CoordinateDTO dto = new CoordinateDTO();
+             dto.setLongitude(tAddress.getLongitude());
+             dto.setLatitude(tAddress.getLatitude());
+             CityInfoVo cityInfoVo = areaService.getCityInfoByCoordinates(dto);
+             tAddress.setCityCode(cityInfoVo.getCityCode());
+         }
+         if (StringUtils.isBlank(tAddress.getDetailAddress())){
+             tAddress.setDetailAddress(tAddress.getAddress());
+         }
         return this.save(tAddress);
     }
 

+ 53 - 3
nightFragrance-massage/src/main/java/com/ylx/order/controller/AfterSalesServiceController.java

@@ -1,17 +1,26 @@
 package com.ylx.order.controller;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 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;
 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.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @RestController
@@ -25,6 +34,47 @@ public class AfterSalesServiceController {
     @Resource
     private IAfterSalesServiceService afterSalesServiceService;
 
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("客户端获取售后原因接口:退款=1, 取消订单=2")
+    @GetMapping("/dict/{type}/reason")
+    public R<List<String>> getReasonList(@PathVariable("type") Integer type) {
+
+        String dictType = AfterSaleServiceDictTypeEnum.getDictTypeByCode(type);
+
+        if (ObjectUtil.isNull(dictType)) {
+            return R.fail("无效的type类型参数");
+        }
+
+        List<SysDictData> reasonDictDataList = DictUtils.getSortedDictCache(dictType);
+
+        if (CollUtil.isEmpty(reasonDictDataList)) {
+            return R.ok(Collections.emptyList());
+        }
+
+        List<String> list = new ArrayList<>(reasonDictDataList.size());
+        for (SysDictData data : reasonDictDataList) {
+            list.add(data.getDictValue());
+        }
+        return R.ok(list);
+    }
+
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("客户端用户提交退款")
+    @PostMapping("/submit")
+    public R<?> submitAfterSale(@Validated @RequestBody AfterSalesServiceDTO dto) {
+        this.afterSalesServiceService.submitAfterSale(dto);
+        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")
     public R<List<RegulationConfigVO>> getDescList(Integer execStatus) {

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

@@ -0,0 +1,22 @@
+package com.ylx.order.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+@ApiModel("客户端提交售后DTO")
+public class AfterSalesServiceDTO implements Serializable {
+    private static final long serialVersionUID = -6661594498170063665L;
+
+    @NotNull(message = "订单ID不能为空")
+    @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;

+ 36 - 0
nightFragrance-massage/src/main/java/com/ylx/order/enums/AfterSaleServiceDictTypeEnum.java

@@ -0,0 +1,36 @@
+package com.ylx.order.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum AfterSaleServiceDictTypeEnum {
+
+
+    /**
+     * 售后退款原因 (对应前端传参: 1)
+     */
+    REFUND_REASON(1, "after_sales_service_reason"),
+
+    /**
+     * 订单取消原因 (对应前端传参: 2)
+     */
+    ORDER_CANCELLED_REASON(2, "order_cancelled_reason");
+
+    private final Integer code;
+    private final String desc;
+
+    AfterSaleServiceDictTypeEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public static String getDictTypeByCode(Integer code) {
+        if (code == null) return null;
+        for (AfterSaleServiceDictTypeEnum e : values()) {
+            if (e.getCode().equals(code)) {
+                return e.getDesc();
+            }
+        }
+        return null;
+    }
+}

+ 3 - 4
nightFragrance-massage/src/main/java/com/ylx/order/enums/OrderStatusEnum.java

@@ -11,10 +11,9 @@ public enum OrderStatusEnum {
     PENDING_SERVICE(3, "待服务"),
     IN_SERVICE(4, "服务中"),
     COMPLETED(5, "已完成"),
-    REFUNDED(6, "已退款"),
-    CANCELLED(7, "已取消"),
-    CLOSED(8, "已关闭"),
-    REJECTED(9, "拒绝接单");
+    CANCELLED(6, "已取消"),
+    CLOSED(7, "已关闭"),
+    REJECTED(8, "拒绝接单");
 
     private final Integer code;
     private final String info;

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

@@ -2,6 +2,11 @@ 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);
 }

+ 196 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/AfterSalesServiceServiceImpl.java

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

+ 3 - 3
nightFragrance-massage/src/main/java/com/ylx/project/controller/ProjectController.java

@@ -135,9 +135,9 @@ public class ProjectController {
 
     @PreAuthorize("@customerAuth.isCustomer()")
     @ApiOperation("客户端根据服务标签获取服务项目集合数据")
-    @GetMapping("/type/{type}")
-    public R<List<ProjectBaseVo>> getProjectTabList(@PathVariable("type") Integer type) {
-        List<ProjectBaseVo> list = this.projectService.getProjectTabListByType(type);
+    @GetMapping("/categoryId/{categoryId}")
+    public R<List<ProjectBaseVo>> getProjectTabList(@PathVariable("categoryId") Integer categoryId) {
+        List<ProjectBaseVo> list = this.projectService.getProjectTabListByCategoryId(categoryId);
         return R.ok(list);
     }
 

+ 1 - 1
nightFragrance-massage/src/main/java/com/ylx/project/service/ProjectService.java

@@ -36,7 +36,7 @@ public interface ProjectService extends IService<Project> {
 
     Page<ProductServiceOptionVO> selectServiceOptionsPage(Page page, ServiceOptionDTO dto);
 
-    List<ProjectBaseVo> getProjectTabListByType(Integer type);
+    List<ProjectBaseVo> getProjectTabListByCategoryId(Integer categoryId);
 
     BookProjectDetailVO getBookingProjectDetail(BookMerchantDTO dto);
 }

+ 2 - 2
nightFragrance-massage/src/main/java/com/ylx/project/service/impl/ProjectServiceImpl.java

@@ -182,10 +182,10 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
     }
 
     @Override
-    public List<ProjectBaseVo> getProjectTabListByType(Integer type) {
+    public List<ProjectBaseVo> getProjectTabListByCategoryId(Integer categoryId) {
 
         LambdaQueryWrapper<Project> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(Project::getType, type)
+        wrapper.eq(Project::getCategoryId, categoryId)
                 .eq(Project::getStatus, ProjectStatusEnum.ON_SHELF.getCode());
 
         List<Project> projects = this.baseMapper.selectList(wrapper);