Pārlūkot izejas kodu

订单及举报代码修改

zhangchen 3 nedēļas atpakaļ
vecāks
revīzija
0dd05d387b

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/LawyerConsultationOrder.java

@@ -163,5 +163,9 @@ public class LawyerConsultationOrder extends Model<LawyerConsultationOrder> {
     @ApiModelProperty(value = "拒绝退款原因")
     @TableField("reject_refund_reason")
     private String rejectRefundReason;
+
+    @ApiModelProperty(value = "退款申请处理动作:1-同意,2-拒绝")
+    @TableField(exist = false)
+    private String processAction;
 }
 

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java

@@ -203,5 +203,13 @@ public class LawyerConsultationOrderVO implements Serializable {
     @ApiModelProperty(value = "是否被举报(1:被举报,2:未被举报)")
     private  String isViolation;
 
+    @ApiModelProperty(value = "拒绝退款原因")
+    private  String rejectRefundReason;
+
+    @ApiModelProperty(value = "是否可申请退款(1-可申请,2-不可申请)")
+    private  String isCanApplyRefund;
+
+    @ApiModelProperty(value = "是否可申请举报(1-可举报,2-不可举报)")
+    private  String isCanApplyViolation;
 }
 

+ 1 - 0
alien-entity/src/main/java/shop/alien/mapper/LawyerConsultationOrderMapper.java

@@ -310,6 +310,7 @@ public interface LawyerConsultationOrderMapper extends BaseMapper<LawyerConsulta
             "        lco.alipay_no,\n" +
             "        lco.order_str,\n" +
             "        lco.apply_refund_status,\n" +
+            "        lco.reject_refund_reason,\n" +
             "        lu.name AS lawyer_name,\n" +
             "        lu.phone AS lawyer_phone,\n" +
             "        lu.email AS lawyer_email,\n" +

+ 30 - 18
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerClientConsultationOrderController.java

@@ -3,14 +3,11 @@ package shop.alien.lawyer.controller;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerConsultationOrder;
 import shop.alien.entity.store.vo.LawyerConsultationOrderVO;
 import shop.alien.lawyer.service.LawyerClientConsultationOrderService;
-import shop.alien.lawyer.service.LawyerConsultationOrderService;
-import shop.alien.lawyer.service.OrderExpirationService;
 
 import java.util.Map;
 
@@ -106,24 +103,46 @@ public class LawyerClientConsultationOrderController {
         return R.data(lawyerClientConsultationOrderService.getLawyerConsultationOrderInfo(pageNum, pageSize, startDate, endDate, clientUserName, orderStatus, lawyerId));
     }
 
+    /**
+     * 律师确认订单(律师端)
+     * <p>
+     * 支持两种操作类型:
+     * 1. 接单:将订单状态从待接单更新为进行中
+     * 2. 取消:将订单状态更新为已取消
+     * </p>
+     *
+     * @param id        订单ID
+     * @param actionType 操作类型:1-接单,2-取消
+     * @return 操作结果
+     */
     @ApiOperation("律师确认订单(律师端)")
     @ApiOperationSupport(order = 5)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "id", value = "订单ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "actionType", value = "操作类型:1-接单,2-取消", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "id", value = "订单ID", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "actionType", value = "操作类型:1-接单,2-取消", dataType = "String", paramType = "query", required = true)
     })
     @PutMapping("/confirmOrder")
-    public R<Boolean> confirmOrder(@RequestParam(value = "id", required = true) Integer id,
-                                   @RequestParam(value = "actionType", required = true) Integer actionType) {
+    public R<Boolean> confirmOrder(@RequestParam(value = "id", required = true) String id,
+                                   @RequestParam(value = "actionType", required = true) String actionType) {
         log.info("律师确认订单,订单ID={}, 操作类型={}", id, actionType);
-        if (id == null) {
+        
+        // 参数校验
+        if (id == null || id.trim().isEmpty()) {
             log.warn("律师确认订单失败:订单ID为空");
             return R.fail("订单ID不能为空");
         }
-        if (actionType == null || (actionType != 1 && actionType != 2)) {
+        
+        if (actionType == null || actionType.trim().isEmpty()) {
+            log.warn("律师确认订单失败:操作类型为空");
+            return R.fail("操作类型不能为空");
+        }
+        
+        // 操作类型格式校验
+        if (!"1".equals(actionType.trim()) && !"2".equals(actionType.trim())) {
             log.warn("律师确认订单失败:操作类型无效,actionType={}", actionType);
             return R.fail("操作类型无效,1-接单,2-取消");
         }
+        
         return lawyerClientConsultationOrderService.confirmOrder(id, actionType);
     }
 
@@ -151,16 +170,9 @@ public class LawyerClientConsultationOrderController {
 
     @ApiOperation("退款申请处理(律师端)")
     @ApiOperationSupport(order = 7)
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "lawyerId", value = "律师ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "orderId", value = "订单ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "processAction", value = "处理动作:1-同意,2-反对", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "rejectRefundReason", value = "拒绝退款原因", dataType = "String", paramType = "query", required = false)
-    })
     @PostMapping("/refundApplyProcess")
-    public R<Boolean> refundApplyProcess(@RequestParam Integer lawyerId, @RequestParam Integer orderId, @RequestParam String processAction, @RequestParam String rejectRefundReason) {
-        log.info("LawyerConsultationOrderController.completeOrder?lawyerId={},orderId{},processAction{},rejectRefundReason{}", lawyerId, orderId, processAction, rejectRefundReason);
-        return lawyerClientConsultationOrderService.refundApplyProcess(lawyerId, orderId, processAction, rejectRefundReason);
+    public R<Boolean> refundApplyProcess(@RequestBody LawyerConsultationOrder lawyerConsultationOrder) {
+        return lawyerClientConsultationOrderService.refundApplyProcess(lawyerConsultationOrder);
     }
 }
 

+ 9 - 7
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerClientConsultationOrderService.java

@@ -46,12 +46,17 @@ public interface LawyerClientConsultationOrderService extends IService<LawyerCon
 
     /**
      * 律师确认订单
+     * <p>
+     * 支持两种操作类型:
+     * 1. 接单:将订单状态从待接单更新为进行中
+     * 2. 取消:将订单状态更新为已取消
+     * </p>
      *
-     * @param id 订单ID
+     * @param id        订单ID
      * @param actionType 操作类型:1-接单,2-取消
      * @return R<Boolean> 是否成功
      */
-    R<Boolean> confirmOrder(Integer id, Integer actionType);
+    R<Boolean> confirmOrder(String id, String actionType);
 
     /**
      * 申请退款
@@ -66,12 +71,9 @@ public interface LawyerClientConsultationOrderService extends IService<LawyerCon
     /**
      * 退款申请处理
      *
-     * @param lawyerId 律师ID
-     * @param orderId 订单ID
-     * @param processAction 处理动作
-     * @param rejectRefundReason 拒绝原因
+     * @param lawyerConsultationOrder 订单对象
      * @return R<Boolean> 是否成功
      */
-    R<Boolean> refundApplyProcess(Integer lawyerId, Integer orderId, String processAction, String rejectRefundReason);
+    R<Boolean> refundApplyProcess(LawyerConsultationOrder lawyerConsultationOrder);
 }
 

+ 275 - 54
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerClientConsultationOrderServiceImpl.java

@@ -46,6 +46,22 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
 import java.util.stream.Collectors;
+import java.util.Objects;
+
+/**
+ * 操作类型常量
+ */
+class OrderActionType {
+    /**
+     * 接单
+     */
+    static final String ACCEPT = "1";
+    
+    /**
+     * 取消
+     */
+    static final String CANCEL = "2";
+}
 
 /**
  * 咨询订单 服务实现类
@@ -627,82 +643,111 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
     /**
      * 律师确认订单
      * <p>
-     * 支持两种操作:
-     * 1. 接单:将订单状态从待接单(1)修改为进行中(2)
-     * 2. 取消:将订单状态修改为已取消(4),并进行退款
+     * 支持两种操作类型
+     * 1. 接单:将订单状态从待接单更新为进行中
+     * 2. 取消:将订单状态更新为已取消
      * </p>
      *
-     * @param id 订单ID
+     * @param id        订单ID
      * @param actionType 操作类型:1-接单,2-取消
      * @return 确认结果
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public R<Boolean> confirmOrder(Integer id, Integer actionType) {
+    public R<Boolean> confirmOrder(String id, String actionType) {
         log.info("开始律师确认订单,订单ID={}, 操作类型={}", id, actionType);
 
         // 参数校验
-        if (id == null) {
+        if (id == null || id.trim().isEmpty()) {
             log.warn("律师确认订单失败:订单ID为空");
             return R.fail("订单ID不能为空");
         }
 
-        if (actionType == null || (actionType != 1 && actionType != 2)) {
+        if (actionType == null || actionType.trim().isEmpty()) {
+            log.warn("律师确认订单失败:操作类型为空");
+            return R.fail("操作类型不能为空");
+        }
+
+        // 操作类型格式校验
+        String trimmedActionType = actionType.trim();
+        if (!OrderActionType.ACCEPT.equals(trimmedActionType) 
+                && !OrderActionType.CANCEL.equals(trimmedActionType)) {
             log.warn("律师确认订单失败:操作类型无效,actionType={}", actionType);
             return R.fail("操作类型无效,1-接单,2-取消");
         }
 
+        // 将订单ID转换为Integer
+        Integer orderId;
+        try {
+            orderId = Integer.parseInt(id.trim());
+        } catch (NumberFormatException e) {
+            log.warn("律师确认订单失败:订单ID格式错误,id={}", id, e);
+            return R.fail("订单ID格式错误");
+        }
+
         // 查询订单信息
-        LawyerConsultationOrder order = consultationOrderMapper.selectById(id);
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
         if (order == null) {
-            log.warn("律师确认订单失败:订单不存在,订单ID={}", id);
+            log.warn("律师确认订单失败:订单不存在,订单ID={}", orderId);
             return R.fail("订单不存在");
         }
 
-        //清除redis缓存,避免调用订单超时处理
+        // 清除redis缓存,避免调用订单超时处理
         orderExpirationService.cancelOrderAcceptTimeout(order.getOrderNumber());
 
-
-        // 验证订单状态是否为待接单(1)
+        // 验证订单状态是否为待接单
         Integer orderStatus = order.getOrderStatus();
-        Integer waitAcceptStatus = 1; // 待接单
+        Integer waitAcceptStatus = LawyerStatusEnum.WAIT_ACCEPT.getStatus();
         if (!waitAcceptStatus.equals(orderStatus)) {
             log.warn("律师确认订单失败:订单状态不是待接单,订单ID={}, 订单编号={}, 当前状态={}",
-                    id, order.getOrderNumber(), orderStatus);
+                    orderId, order.getOrderNumber(), orderStatus);
             return R.fail("订单状态不是待接单,无法确认");
         }
 
         // 根据操作类型处理
-        if (actionType == 1) {
-            // 接单:更新订单状态为进行中(2)
-            Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus(); // 2:进行中
+        if (OrderActionType.ACCEPT.equals(trimmedActionType)) {
+            // 接单:更新订单状态为进行中
+            Integer inProgressStatus = LawyerStatusEnum.INPROGRESS.getStatus();
+            Date acceptTime = new Date();
             order.setOrderStatus(inProgressStatus);
-            order.setUpdatedTime(new Date());
+            order.setAcceptOrdersTime(acceptTime);
+            order.setUpdatedTime(acceptTime);
 
             // 执行更新操作
             boolean result = this.updateById(order);
             if (result) {
                 log.info("律师接单成功,订单ID={}, 订单编号={}, 状态从{}变更为{}",
-                        id, order.getOrderNumber(), waitAcceptStatus, inProgressStatus);
+                        orderId, order.getOrderNumber(), waitAcceptStatus, inProgressStatus);
+                
+                // 发送接单通知
+                sendAcceptOrderNotice(order, acceptTime);
+                
                 return R.data(true, "接单成功");
             } else {
-                log.error("律师接单失败:数据库操作失败,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+                log.error("律师接单失败:数据库操作失败,订单ID={}, 订单编号={}", orderId, order.getOrderNumber());
                 return R.fail("接单失败");
             }
         } else {
-            // 取消:更新订单状态为已取消(4),并进行退款
-            Integer cancelStatus = LawyerStatusEnum.REFUNDED.getStatus(); // 4:已取消
+            // 取消:更新订单状态为已取消
+            Integer cancelStatus = LawyerStatusEnum.REFUNDED.getStatus();
             order.setOrderStatus(cancelStatus);
             order.setUpdatedTime(new Date());
 
             // 执行更新操作
             boolean result = this.updateById(order);
-            if (!result) {
-                log.error("律师取消订单失败:数据库操作失败,订单ID={}, 订单编号={}", id, order.getOrderNumber());
+            if (result) {
+                log.info("律师取消订单成功,订单ID={}, 订单编号={}, 状态从{}变更为{}",
+                        orderId, order.getOrderNumber(), waitAcceptStatus, cancelStatus);
+                
+                // 发送拒绝接单通知
+                sendRejectOrderNotice(order);
+                
+                return R.data(true, "取消订单成功");
+            } else {
+                log.error("律师取消订单失败:数据库操作失败,订单ID={}, 订单编号={}", orderId, order.getOrderNumber());
                 return R.fail("取消订单失败");
             }
         }
-        return R.success("接单成功");
     }
 
     /**
@@ -800,62 +845,117 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
      * - 拒绝退款:将申请退款状态设置为已拒绝(2),更新拒绝原因,并发送拒绝通知
      * </p>
      *
-     * @param lawyerId          律师ID
-     * @param orderId           订单ID
-     * @param processAction     处理动作:1-同意,2-拒绝
-     * @param rejectRefundReason 拒绝原因(拒绝时必填)
+     * @param lawyerConsultationOrder 订单对象
      * @return 处理结果
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public R<Boolean> refundApplyProcess(Integer lawyerId, Integer orderId, String processAction, String rejectRefundReason) {
-        log.info("开始处理退款申请,律师ID={}, 订单ID={}, 处理动作={}, 拒绝原因={}", 
-                lawyerId, orderId, processAction, rejectRefundReason);
+    public R<Boolean> refundApplyProcess(LawyerConsultationOrder lawyerConsultationOrder) {
+        Integer lawyerId = lawyerConsultationOrder.getLawyerUserId();
+        Integer orderId = lawyerConsultationOrder.getId();
+        String processAction = lawyerConsultationOrder.getProcessAction();
+        String rejectRefundReason = lawyerConsultationOrder.getRejectRefundReason();
+        
+        log.info("开始处理退款申请,律师ID={}, 订单ID={}, 处理动作={}", lawyerId, orderId, processAction);
 
         // 1. 参数校验
-        if (lawyerId == null || lawyerId <= 0) {
+        R<Boolean> paramValidateResult = validateRefundProcessParams(lawyerId, orderId, processAction, rejectRefundReason);
+        if (paramValidateResult != null) {
+            return paramValidateResult;
+        }
+
+        // 2. 查询订单信息
+        LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
+        if (Objects.isNull(order)) {
+            log.warn("处理退款申请失败:订单不存在,订单ID={}", orderId);
+            return R.fail("订单不存在");
+        }
+
+        // 3. 业务校验
+        R<Boolean> businessValidateResult = validateRefundProcessBusiness(lawyerId, order);
+        if (businessValidateResult != null) {
+            return businessValidateResult;
+        }
+
+        // 4. 取消退款超时计时器
+        try {
+            orderExpirationService.cancelOrderRefundTimeout(order.getOrderNumber());
+        } catch (Exception e) {
+            log.error("取消退款超时计时器失败,订单编号={}", order.getOrderNumber(), e);
+            // 继续执行,不因取消计时器失败而中断
+        }
+
+        // 5. 根据处理动作进行相应处理
+        if (ACTION_AGREE.equals(processAction)) {
+            return processAgreeRefund(order);
+        } else {
+            return processRejectRefund(order, rejectRefundReason);
+        }
+    }
+
+    /**
+     * 校验退款处理参数
+     *
+     * @param lawyerId          律师ID
+     * @param orderId           订单ID
+     * @param processAction     处理动作
+     * @param rejectRefundReason 拒绝原因
+     * @return 校验失败时返回错误结果,校验通过返回null
+     */
+    private R<Boolean> validateRefundProcessParams(Integer lawyerId, Integer orderId, 
+                                                     String processAction, String rejectRefundReason) {
+        // 律师ID校验
+        if (Objects.isNull(lawyerId) || lawyerId <= 0) {
             log.warn("处理退款申请失败:律师ID为空或无效,lawyerId={}", lawyerId);
             return R.fail("律师ID不能为空");
         }
 
-        if (orderId == null || orderId <= 0) {
+        // 订单ID校验
+        if (Objects.isNull(orderId) || orderId <= 0) {
             log.warn("处理退款申请失败:订单ID为空或无效,orderId={}", orderId);
             return R.fail("订单ID不能为空");
         }
 
+        // 处理动作校验
         if (!StringUtils.hasText(processAction)) {
             log.warn("处理退款申请失败:处理动作为空,订单ID={}", orderId);
             return R.fail("处理动作不能为空");
         }
 
-        // 2. 处理动作校验
+        // 处理动作校验
         if (!ACTION_AGREE.equals(processAction) && !ACTION_REJECT.equals(processAction)) {
             log.warn("处理退款申请失败:处理动作无效,订单ID={}, 处理动作={}", orderId, processAction);
             return R.fail("处理动作无效,1-同意,2-拒绝");
         }
 
-        // 3. 拒绝原因校验:拒绝时必须提供拒绝原因
+        // 拒绝原因校验:拒绝时必须提供拒绝原因
         if (ACTION_REJECT.equals(processAction) && !StringUtils.hasText(rejectRefundReason)) {
             log.warn("处理退款申请失败:拒绝退款时必须提供拒绝原因,订单ID={}", orderId);
             return R.fail("拒绝退款时必须提供拒绝原因");
         }
 
-        // 4. 查询订单信息
-        LawyerConsultationOrder order = consultationOrderMapper.selectById(orderId);
-        if (order == null) {
-            log.warn("处理退款申请失败:订单不存在,订单ID={}", orderId);
-            return R.fail("订单不存在");
-        }
+        return null;
+    }
 
-        // 5. 订单归属校验:订单必须属于该律师
+    /**
+     * 校验退款处理业务规则
+     *
+     * @param lawyerId 律师ID
+     * @param order    订单对象
+     * @return 校验失败时返回错误结果,校验通过返回null
+     */
+    private R<Boolean> validateRefundProcessBusiness(Integer lawyerId, LawyerConsultationOrder order) {
+        Integer orderId = order.getId();
+        
+        // 订单归属校验:订单必须属于该律师
         Integer orderLawyerId = order.getLawyerUserId();
-        if (orderLawyerId == null || !lawyerId.equals(orderLawyerId)) {
+        if (Objects.isNull(orderLawyerId) || !lawyerId.equals(orderLawyerId)) {
             log.warn("处理退款申请失败:订单不属于该律师,订单ID={}, 订单律师ID={}, 请求律师ID={}", 
                     orderId, orderLawyerId, lawyerId);
             return R.fail("订单不属于该律师,无法处理退款申请");
         }
 
-        // 6. 退款状态校验:订单必须处于已申请退款状态
+        // 退款状态校验:订单必须处于已申请退款状态
         String applyRefundStatus = order.getApplyRefundStatus();
         if (!REFUND_STATUS_APPLIED.equals(applyRefundStatus)) {
             log.warn("处理退款申请失败:订单未申请退款或已处理,订单ID={}, 订单编号={}, 退款状态={}", 
@@ -863,15 +963,7 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
             return R.fail("订单未申请退款或已处理,无法重复处理");
         }
 
-        // 7.取消退款超时计时器
-        orderExpirationService.cancelOrderRefundTimeout(order.getOrderNumber());
-
-        // 8. 根据处理动作进行相应处理
-        if (ACTION_AGREE.equals(processAction)) {
-            return processAgreeRefund(order);
-        } else {
-            return processRejectRefund(order, rejectRefundReason);
-        }
+        return null;
     }
 
     /**
@@ -1116,5 +1208,134 @@ public class LawyerClientConsultationOrderServiceImpl extends ServiceImpl<Lawyer
             log.error("发送WebSocket消息异常,接收人ID={}, 异常信息={}", receiverId, e.getMessage(), e);
         }
     }
+
+    /**
+     * 发送接单通知给客户端用户
+     *
+     * @param order      订单对象
+     * @param acceptTime 接单时间
+     */
+    private void sendAcceptOrderNotice(LawyerConsultationOrder order, Date acceptTime) {
+        try {
+            Integer clientUserId = order.getClientUserId();
+            if (clientUserId == null) {
+                log.warn("发送接单通知失败:客户端用户ID为空,订单ID={}", order.getId());
+                return;
+            }
+
+            // 获取客户端用户接收ID
+            String receiverId = getClientReceiverId(clientUserId);
+            if (!StringUtils.hasText(receiverId)) {
+                log.warn("发送接单通知失败:获取客户端用户接收ID失败,订单ID={}, 用户ID={}", 
+                        order.getId(), clientUserId);
+                return;
+            }
+
+            // 格式化日期
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String acceptTimeStr = dateFormat.format(acceptTime);
+            String validityPeriodStr = "";
+            if (order.getValidityPeriod() != null) {
+                validityPeriodStr = dateFormat.format(order.getValidityPeriod());
+            } else {
+                // 如果有效期为空,使用默认提示
+                validityPeriodStr = "待定";
+            }
+
+            // 构建通知消息
+            String orderNumber = order.getOrderNumber() != null ? order.getOrderNumber() : "";
+            String message = String.format("您的编号为%s的订单已于%s被律师接单,有效期至%s。请在有效期内与律师沟通。", 
+                    orderNumber, acceptTimeStr, validityPeriodStr);
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createOrderNotice(order.getId(), receiverId, "接单通知", message);
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("发送接单通知失败:保存通知失败,订单ID={}", order.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+            log.info("发送接单通知成功,接收人ID={}, 订单编号={}", receiverId, orderNumber);
+        } catch (Exception e) {
+            log.error("发送接单通知异常,订单ID={}, 异常信息={}", order.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 发送拒绝接单通知给客户端用户
+     *
+     * @param order 订单对象
+     */
+    private void sendRejectOrderNotice(LawyerConsultationOrder order) {
+        try {
+            Integer clientUserId = order.getClientUserId();
+            if (clientUserId == null) {
+                log.warn("发送拒绝接单通知失败:客户端用户ID为空,订单ID={}", order.getId());
+                return;
+            }
+
+            // 获取客户端用户接收ID
+            String receiverId = getClientReceiverId(clientUserId);
+            if (!StringUtils.hasText(receiverId)) {
+                log.warn("发送拒绝接单通知失败:获取客户端用户接收ID失败,订单ID={}, 用户ID={}", 
+                        order.getId(), clientUserId);
+                return;
+            }
+
+            // 获取拒绝原因
+            String rejectReason = order.getReasonOrderRefusal();
+            if (!StringUtils.hasText(rejectReason)) {
+                rejectReason = "无";
+            }
+
+            // 构建通知消息
+            String orderNumber = order.getOrderNumber() != null ? order.getOrderNumber() : "";
+            String message = String.format("您的编号为%s的订单已被拒绝,拒绝原因:%s。订单金额将在1-3个工作日原路返还,请注意查收。", 
+                    orderNumber, rejectReason);
+
+            // 创建并保存通知
+            LifeNotice lifeNotice = createOrderNotice(order.getId(), receiverId, "拒绝接单通知", message);
+            int noticeResult = lifeNoticeMapper.insert(lifeNotice);
+            if (noticeResult <= 0) {
+                log.warn("发送拒绝接单通知失败:保存通知失败,订单ID={}", order.getId());
+                return;
+            }
+
+            // 发送WebSocket消息
+            sendWebSocketMessage(receiverId, lifeNotice);
+
+            log.info("发送拒绝接单通知成功,接收人ID={}, 订单编号={}", receiverId, orderNumber);
+        } catch (Exception e) {
+            log.error("发送拒绝接单通知异常,订单ID={}, 异常信息={}", order.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 创建订单通知对象
+     *
+     * @param businessId 业务ID(订单ID)
+     * @param receiverId 接收人ID
+     * @param title      通知标题
+     * @param message    通知消息
+     * @return 通知对象
+     */
+    private LifeNotice createOrderNotice(Integer businessId, String receiverId, String title, String message) {
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId(SYSTEM_SENDER_ID);
+        lifeNotice.setBusinessId(businessId);
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setTitle(title);
+        lifeNotice.setNoticeType(1);
+        lifeNotice.setIsRead(0);
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("message", message);
+        lifeNotice.setContext(jsonObject.toJSONString());
+
+        return lifeNotice;
+    }
 }
 

+ 32 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java

@@ -32,6 +32,7 @@ import shop.alien.lawyer.config.WebSocketProcess;
 import shop.alien.lawyer.service.LawyerConsultationOrderService;
 import shop.alien.lawyer.service.LawyerUserService;
 import shop.alien.lawyer.service.OrderExpirationService;
+import shop.alien.lawyer.util.DateUtils;
 import shop.alien.mapper.*;
 import shop.alien.util.common.constant.LawyerStatusEnum;
 
@@ -608,6 +609,10 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
             calculateCountdownForPendingOrders(voPage);
         }
 
+        // 判断工作日信息
+        calculateWorkDayInfo(voPage);
+
+
         // 获取统计信息
         statisticsWrapper.groupBy("lco.order_status");
         List<Map<String, Object>> statisticsInfo = consultationOrderMapper.getLawyerStatisticsInfo(
@@ -1156,6 +1161,33 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     }
 
     /**
+     * 根据工作日判断是否可以申请退款/举报
+     *
+     * @param voPage 订单分页对象
+     */
+    private void calculateWorkDayInfo(IPage<LawyerConsultationOrderVO> voPage) {
+        if (voPage == null || voPage.getRecords() == null) {
+            return;
+        }
+        boolean isCanApplyRefund = DateUtils.getLastThreeWorkdaysRangeInfo(2);
+        boolean isCanApplyViolation = DateUtils.getLastThreeWorkdaysRangeInfo(3);
+
+        for (LawyerConsultationOrderVO vo : voPage.getRecords()) {
+            if(isCanApplyRefund){
+                vo.setIsCanApplyRefund("2");
+            } else {
+                vo.setIsCanApplyRefund("1");
+            }
+
+            if(isCanApplyViolation){
+                vo.setIsCanApplyViolation("2");
+            } else {
+                vo.setIsCanApplyViolation("1");
+            }
+        }
+    }
+
+    /**
      * 填充律师信息到订单VO
      *
      * @param orderVO    订单VO对象

+ 160 - 16
alien-lawyer/src/main/java/shop/alien/lawyer/util/DateUtils.java

@@ -1,10 +1,21 @@
 package shop.alien.lawyer.util;
 
+import com.nlf.calendar.Holiday;
+import com.nlf.calendar.util.HolidayUtil;
+
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 日期计算工具类
@@ -15,22 +26,6 @@ import java.util.Date;
  */
 public class DateUtils {
 
-    public static void main(String[] args) throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-//        Date date = calcHours(new Date(), 1);
-//        System.out.println(sdf.format(date));
-//
-//        boolean b = dateCompare(new Date(), date);
-//        System.out.println(b);
-
-
-        Date date1 = sdf.parse("2024-05-10 19:10:00");
-        Date date2 = sdf.parse("2024-05-10 19:11:00");
-        System.out.println(calcMinute(date1, 1));
-        System.out.println(dateCompare(date1, calcMinute(date1, 1)));
-
-    }
-
     /**
      * 天数加减
      *
@@ -97,4 +92,153 @@ public class DateUtils {
         int second = now.getSecond();
         return daySecond - hour - minute - second;
     }
+
+    /**
+     * 工作日信息类
+     */
+    public static class WorkdayInfo {
+        private LocalDateTime startTime;
+        private LocalDateTime endTime;
+
+        public WorkdayInfo(LocalDateTime startTime, LocalDateTime endTime) {
+            this.startTime = startTime;
+            this.endTime = endTime;
+        }
+
+        public LocalDateTime getStartTime() {
+            return startTime;
+        }
+
+        public void setStartTime(LocalDateTime startTime) {
+            this.startTime = startTime;
+        }
+
+        public LocalDateTime getEndTime() {
+            return endTime;
+        }
+
+        public void setEndTime(LocalDateTime endTime) {
+            this.endTime = endTime;
+        }
+    }
+
+    /**
+     * 判断是否为工作日(考虑周末和法定节假日)
+     * 
+     * @param date 日期
+     * @return 是否为工作日
+     */
+    private static boolean isWorkday(LocalDate date) {
+        DayOfWeek dayOfWeek = date.getDayOfWeek();
+        
+        // 如果是周末(周六或周日),直接返回 false
+        if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {
+            return false;
+        }
+        
+        // 通过 HolidayUtil 判断是否为法定节假日
+        Holiday holiday = HolidayUtil.getHoliday(date.getYear(), date.getMonthValue(), date.getDayOfMonth());
+        
+        // 如果没有节假日信息,按照周一到周五判断为工作日
+        if (holiday == null) {
+            return true;
+        }
+        
+        // 使用节假日信息中的 isWork() 方法判断
+        // true 表示工作日(包括调休工作日),false 表示节假日
+        return holiday.isWork();
+    }
+
+    /**
+     * 获取当月最后三个工作日的开始时间点和结束时间点
+     * 工作日定义为周一到周五,且排除周末和法定节假日(包括调休工作日)
+     *
+     * @return 最后三个工作日的时间信息列表,按时间从早到晚排序(第一个是最早的,第三个是最晚的)
+     */
+    public static List<WorkdayInfo> getLastThreeWorkdaysOfCurrentMonth(int day) {
+        LocalDate now = LocalDate.now();
+        // 获取当月最后一天
+        LocalDate lastDayOfMonth = now.withDayOfMonth(now.lengthOfMonth());
+        
+        List<WorkdayInfo> workdayList = new ArrayList<>();
+        
+        // 从最后一天往前遍历,找到工作日
+        LocalDate currentDate = lastDayOfMonth;
+        while (workdayList.size() < day && currentDate.getMonthValue() == now.getMonthValue()) {
+            if (isWorkday(currentDate)) {
+                // 开始时间:当天的 00:00:00
+                LocalDateTime startTime = LocalDateTime.of(currentDate, LocalTime.MIN);
+                // 结束时间:当天的 23:59:59.999999999,或者使用第二天的 00:00:00(不包含)
+                // 这里使用当天的 23:59:59.999999999
+                LocalDateTime endTime = LocalDateTime.of(currentDate, LocalTime.MAX);
+                workdayList.add(0, new WorkdayInfo(startTime, endTime)); // 插入到列表开头,保持时间顺序
+            }
+            currentDate = currentDate.minusDays(1);
+        }
+        
+        return workdayList;
+    }
+
+    /**
+     * 获取当月最后三个工作日的时间范围
+     * 返回第一个工作日的开始时间和本月最后一天的最晚时间,格式为 yyyy-MM-dd HH:mm:ss
+     * 工作日定义为周一到周五,且排除周末和法定节假日(包括调休工作日)
+     *
+     * @return 包含开始时间和结束时间的Map,key为"startTime"和"endTime",值为格式化的字符串
+     */
+    public static Map<String, String> getLastThreeWorkdaysRange(int day) {
+        List<WorkdayInfo> workdays = getLastThreeWorkdaysOfCurrentMonth(day);
+        Map<String, String> result = new HashMap<>();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        
+        if (workdays.isEmpty()) {
+            result.put("startTime", null);
+            result.put("endTime", null);
+            return result;
+        }
+        
+        // 第一个工作日(最早的那个)的开始时间
+        WorkdayInfo firstWorkday = workdays.get(0);
+        String startTime = firstWorkday.getStartTime().format(formatter);
+        
+        // 本月最后一天的最晚时间(23:59:59)
+        LocalDate now = LocalDate.now();
+        LocalDate lastDayOfMonth = now.withDayOfMonth(now.lengthOfMonth());
+        LocalDateTime endDateTime = LocalDateTime.of(lastDayOfMonth, LocalTime.MAX);
+        String endTime = endDateTime.format(formatter);
+        
+        result.put("startTime", startTime);
+        result.put("endTime", endTime);
+        
+        return result;
+    }
+
+    /**
+     * 判断当前时间是否在当月最后几个工作日的时间范围内
+     * 根据 getLastThreeWorkdaysRangeInfo 方法的逻辑,判断当前时间是否在返回的时间范围内
+     * 工作日定义为周一到周五,且排除周末和法定节假日(包括调休工作日)
+     *
+     * @param day 工作日天数
+     * @return true 表示当前时间在时间范围内(包含边界),false 表示不在范围内
+     */
+    public static boolean getLastThreeWorkdaysRangeInfo(int day) {
+        List<WorkdayInfo> workdays = getLastThreeWorkdaysOfCurrentMonth(day);
+        
+        if (workdays.isEmpty()) {
+            return false;
+        }
+        
+        // 第一个工作日(最早的那个)的开始时间
+        WorkdayInfo firstWorkday = workdays.get(0);
+        LocalDateTime startDateTime = firstWorkday.getStartTime();
+        
+        // 本月最后一天的最晚时间(23:59:59)
+        LocalDate now = LocalDate.now();
+        LocalDate lastDayOfMonth = now.withDayOfMonth(now.lengthOfMonth());
+        LocalDateTime endDateTime = LocalDateTime.of(lastDayOfMonth, LocalTime.MAX);
+        
+        // 判断当前时间是否在范围内(包含边界)
+        LocalDateTime currentTime = LocalDateTime.now();
+        return !currentTime.isBefore(startDateTime) && !currentTime.isAfter(endDateTime);
+    }
 }