Gqingci 6 ngày trước cách đây
mục cha
commit
72defc78bc
30 tập tin đã thay đổi với 885 bổ sung26 xóa
  1. 1 0
      .gitignore
  2. 2 0
      ruoyi-admin/src/main/resources/application.yml
  3. 18 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamEvaluationController.java
  4. 20 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainPostCandidateController.java
  5. 10 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalCheckController.java
  6. 55 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/RefundController.java
  7. 8 2
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackCandidate.java
  8. 27 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPostCandidateHireAttachment.java
  9. 60 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/Refund.java
  10. 16 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostCandidateHireBo.java
  11. 37 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/RefundBo.java
  12. 1 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainBackOrderCandidateVo.java
  13. 15 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamApplyListVo.java
  14. 16 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostCandidateVo.java
  15. 5 12
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainStudentVo.java
  16. 70 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/RefundVo.java
  17. 4 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainBackCandidateMapper.java
  18. 7 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainPostCandidateHireAttachmentMapper.java
  19. 8 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/RefundMapper.java
  20. 6 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainBackOrderService.java
  21. 2 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainExamEvaluationService.java
  22. 7 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPostCandidateService.java
  23. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IPaymentService.java
  24. 15 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IRefundService.java
  25. 121 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackOrderServiceImpl.java
  26. 9 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java
  27. 65 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostCandidateServiceImpl.java
  28. 42 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/PaymentServiceImpl.java
  29. 204 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/RefundServiceImpl.java
  30. 31 11
      ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainBackCandidateMapper.xml

+ 1 - 0
.gitignore

@@ -42,6 +42,7 @@ nbdist/
 *.log.gz
 *.xml.versionsBackup
 *.swp
+logs/
 
 !*/build/*.java
 !*/build/*.html

+ 2 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -131,6 +131,8 @@ tenant:
     - sys_menu
 
     - sys_tenant_package
+    - main_post_candidate_hire_attachment
+    - main_student_project
     - sys_role_dept
     - sys_role_menu
     - sys_user_post

+ 18 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamEvaluationController.java

@@ -1,5 +1,6 @@
 package org.dromara.main.controller;
 
+import jakarta.servlet.http.HttpServletResponse;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
@@ -7,6 +8,7 @@ import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.mybatis.core.page.PageQuery;
@@ -48,6 +50,13 @@ public class MainExamEvaluationController extends BaseController {
         return examEvaluationService.queryPageList(bo, pageQuery);
     }
 
+    @Log(title = "测评管理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(MainExamEvaluationBo bo, HttpServletResponse response) {
+        List<MainExamEvaluationVo> list = examEvaluationService.queryList(bo);
+        ExcelUtil.exportExcel(list, "测评数据", MainExamEvaluationVo.class, response);
+    }
+
 //    @SaCheckPermission("main:examEvaluation:list")
     @GetMapping("/stats/participant")
     public R<Map<Long, Map<String, Long>>> participantStats(@RequestParam("ids") Collection<Long> ids) {
@@ -75,6 +84,15 @@ public class MainExamEvaluationController extends BaseController {
         return examEvaluationService.getApplyList(evaluationId, keyword, pageQuery);
     }
 
+    @Log(title = "测评报名列表", businessType = BusinessType.EXPORT)
+    @PostMapping("/applyList/export")
+    public void exportApplyList(@RequestParam("evaluationId") Long evaluationId,
+                                @RequestParam(value = "keyword", required = false) String keyword,
+                                HttpServletResponse response) {
+        List<MainExamApplyListVo> list = examEvaluationService.queryApplyList(evaluationId, keyword);
+        ExcelUtil.exportExcel(list, "测评报名列表", MainExamApplyListVo.class, response);
+    }
+
 //    @SaCheckPermission("main:examEvaluation:edit")
     @Log(title = "移除测评报名", businessType = BusinessType.DELETE)
     @DeleteMapping("/apply/{applyId}")

+ 20 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainPostCandidateController.java

@@ -1,20 +1,28 @@
 package org.dromara.main.controller;
 
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.bo.MainPostCandidateHireBo;
 import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
 import org.dromara.main.domain.bo.MainPostCandidateStatusBo;
 import org.dromara.main.domain.vo.MainPostCandidateVo;
 import org.dromara.main.service.IMainPostCandidateService;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 @RequiredArgsConstructor
 @RestController
 @RequestMapping("/main/postCandidate")
@@ -27,6 +35,18 @@ public class MainPostCandidateController extends BaseController {
         return mainPostCandidateService.queryPageList(bo, pageQuery);
     }
 
+    @Log(title = "岗位报名列表", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(MainPostCandidateQueryBo bo, HttpServletResponse response) {
+        List<MainPostCandidateVo> list = mainPostCandidateService.queryList(bo);
+        ExcelUtil.exportExcel(list, "岗位报名列表", MainPostCandidateVo.class, response);
+    }
+
+    @PostMapping("/hire")
+    public R<Void> hire(@RequestBody MainPostCandidateHireBo bo) {
+        return toAjax(mainPostCandidateService.hireCandidate(bo));
+    }
+
     @PutMapping("/status")
     public R<Void> updateStatus(@RequestBody MainPostCandidateStatusBo bo) {
         return toAjax(mainPostCandidateService.updateStatus(bo));

+ 10 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/PortalCheckController.java

@@ -12,6 +12,7 @@ import org.dromara.main.domain.bo.MainBackClauseBo;
 import org.dromara.main.domain.bo.PortalCheckOrderCreateBo;
 import org.dromara.main.domain.vo.MainBackCategoryVo;
 import org.dromara.main.domain.vo.MainBackClauseVo;
+import org.dromara.main.domain.vo.MainBackOrderCandidateVo;
 import org.dromara.main.domain.vo.MainBackOrderVo;
 import org.dromara.main.service.IMainBackCategoryService;
 import org.dromara.main.service.IMainBackClauseService;
@@ -152,6 +153,15 @@ public class PortalCheckController extends BaseController {
         return R.ok(detail);
     }
 
+    @PostMapping("/order/candidate/{recordId}/authorize/confirm")
+    public R<MainBackOrderCandidateVo> confirmPortalCandidateAuthorization(@PathVariable Long recordId) {
+        String tenantId = LoginHelper.getTenantId();
+        if (tenantId == null || tenantId.isBlank()) {
+            return R.fail("未登录或token已失效");
+        }
+        return R.ok(mainBackOrderService.confirmPortalCandidateAuthorization(recordId, tenantId));
+    }
+
     @PostMapping("/order/create")
     public R<MainBackOrderVo> createOrder(@Validated @RequestBody PortalCheckOrderCreateBo bo) {
         String tenantId = LoginHelper.getTenantId();

+ 55 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/RefundController.java

@@ -0,0 +1,55 @@
+package org.dromara.main.controller;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.bo.RefundBo;
+import org.dromara.main.domain.vo.RefundVo;
+import org.dromara.main.service.IRefundService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 退款管理Controller
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping({"/system/refund", "/main/refund"})
+public class RefundController extends BaseController {
+
+    private final IRefundService refundService;
+
+    @Log(title = "退款管理", businessType = BusinessType.INSERT)
+    @PostMapping("/create")
+    public R<Long> createRefund(@Valid @RequestBody RefundBo bo) {
+        Long refundId = refundService.createRefund(
+            bo.getOrderId(),
+            bo.getRefundAmount(),
+            bo.getRefundReason(),
+            bo.getOperatorId(),
+            bo.getOperatorName()
+        );
+        return R.ok(refundId);
+    }
+
+    @GetMapping("/list/{orderId}")
+    public R<List<RefundVo>> listByOrderId(@PathVariable Long orderId) {
+        return R.ok(refundService.queryByOrderId(orderId));
+    }
+
+    @GetMapping("/{id}")
+    public R<RefundVo> getInfo(@PathVariable Long id) {
+        return R.ok(refundService.queryById(id));
+    }
+}

+ 8 - 2
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackCandidate.java

@@ -1,4 +1,5 @@
 package org.dromara.main.domain;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -30,8 +31,13 @@ public class MainBackCandidate extends BaseEntity {
     @TableLogic
     private String delFlag;
 
-    /** 状态 */
-    private String status;
+    /** 企业意愿状态(pending/adopted/rejected) */
+    @TableField("enterprise_status")
+    private String enterpriseStatus;
+
+    /** 学员意愿状态(pending/accepted/rejected) */
+    @TableField("student_status")
+    private String studentStatus;
 
     private String remark;
 }

+ 27 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPostCandidateHireAttachment.java

@@ -0,0 +1,27 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_post_candidate_hire_attachment")
+public class MainPostCandidateHireAttachment extends BaseEntity {
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long candidateId;
+
+    /**
+     * 录用附件 OSS ID 列表,逗号分隔
+     */
+    private String ossIds;
+
+    @TableLogic
+    private String delFlag;
+}

+ 60 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/Refund.java

@@ -0,0 +1,60 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 退款记录对象 main_refund
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_refund")
+public class Refund extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String tenantId;
+
+    private String refundNo;
+
+    private Long paymentId;
+
+    private Long orderId;
+
+    private String orderNo;
+
+    private String tradeNo;
+
+    private BigDecimal refundAmount;
+
+    private String refundReason;
+
+    private Integer refundType;
+
+    private Integer paymentMethod;
+
+    private Integer refundStatus;
+
+    private Date refundTime;
+
+    private String failReason;
+
+    private Long operatorId;
+
+    private String operatorName;
+
+    @TableLogic
+    private String delFlag;
+}

+ 16 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostCandidateHireBo.java

@@ -0,0 +1,16 @@
+package org.dromara.main.domain.bo;
+
+import lombok.Data;
+
+@Data
+public class MainPostCandidateHireBo {
+
+    private Long id;
+
+    private String remark;
+
+    /**
+     * 录用附件 OSS ID 列表,逗号分隔
+     */
+    private String offerAttachment;
+}

+ 37 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/RefundBo.java

@@ -0,0 +1,37 @@
+package org.dromara.main.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.main.domain.Refund;
+
+import java.math.BigDecimal;
+
+/**
+ * 退款业务对象
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = Refund.class, reverseConvertGenerate = false)
+public class RefundBo extends BaseEntity {
+
+    private Long id;
+
+    @NotNull(message = "订单ID不能为空")
+    private Long orderId;
+
+    @NotNull(message = "退款金额不能为空")
+    @DecimalMin(value = "0.01", message = "退款金额必须大于0")
+    private BigDecimal refundAmount;
+
+    @NotBlank(message = "退款原因不能为空")
+    private String refundReason;
+
+    private Long operatorId;
+
+    private String operatorName;
+}

+ 1 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainBackOrderCandidateVo.java

@@ -14,5 +14,6 @@ public class MainBackOrderCandidateVo implements Serializable {
     private String candidateIdType;
     private String candidateIdNumber;
     private Integer status;
+    private String statusText;
     private String reportUrl;
 }

+ 15 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamApplyListVo.java

@@ -1,29 +1,44 @@
 package org.dromara.main.domain.vo;
 
+import cn.idev.excel.annotation.ExcelIgnore;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
 import lombok.Data;
 
 @Data
+@ExcelIgnoreUnannotated
 public class MainExamApplyListVo {
 
+    @ExcelIgnore
     private Long id;
 
+    @ExcelIgnore
     private Long evaluationId;
 
+    @ExcelIgnore
     private Long studentId;
 
+    @ExcelProperty(value = "姓名")
     private String name;
 
+    @ExcelProperty(value = "性别")
     private String gender;
 
+    @ExcelProperty(value = "部门")
     private String department;
 
+    @ExcelProperty(value = "手机号")
     private String phone;
 
+    @ExcelProperty(value = "状态")
     private String statusText;
 
+    @ExcelIgnore
     private String statusType;
 
+    @ExcelProperty(value = "测评时间")
     private String evaluateTime;
 
+    @ExcelIgnore
     private String actionType;
 }

+ 16 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostCandidateVo.java

@@ -1,37 +1,53 @@
 package org.dromara.main.domain.vo;
 
+import cn.idev.excel.annotation.ExcelIgnore;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
 import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
 
 @Data
+@ExcelIgnoreUnannotated
 public class MainPostCandidateVo implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
+    @ExcelIgnore
     private Long id;
 
+    @ExcelIgnore
     private Long postId;
 
+    @ExcelIgnore
     private Long studentId;
 
+    @ExcelProperty(value = "姓名")
     private String name;
 
+    @ExcelProperty(value = "性别")
     private String gender;
 
+    @ExcelProperty(value = "手机号")
     private String phone;
 
+    @ExcelIgnore
     private String status;
 
+    @ExcelProperty(value = "状态")
     private String statusText;
 
+    @ExcelIgnore
     private String statusType;
 
+    @ExcelProperty(value = "测评时间")
     private String evaluateTime;
 
+    @ExcelIgnore
     private Long resumeFile;
 
+    @ExcelProperty(value = "备注")
     private String remark;
 }

+ 5 - 12
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainStudentVo.java

@@ -5,19 +5,11 @@ import lombok.Data;
 import org.dromara.common.translation.annotation.Translation;
 import org.dromara.common.translation.constant.TransConstant;
 import org.dromara.main.domain.MainStudent;
-import org.dromara.main.domain.MainStudentEducation;
-import org.dromara.main.domain.MainStudentExperience;
-import org.dromara.main.domain.MainStudentProject;
-
 import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.List;
-import org.dromara.main.domain.vo.MainStudentEducationVo;
-import org.dromara.main.domain.vo.MainStudentExperienceVo;
-import org.dromara.main.domain.vo.MainStudentProjectVo;
 
 @Data
 @AutoMapper(target = MainStudent.class)
@@ -142,15 +134,16 @@ public class MainStudentVo implements Serializable {
      */
     private Date createTime;
 
+    /**
+     * 最后信息更新时间
+     */
+    private Date updateTime;
+
     /**
      * 备注信息
      */
     private String remark;
 
-
-
-
-
     /**
      * 教育经历列表
      */

+ 70 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/RefundVo.java

@@ -0,0 +1,70 @@
+package org.dromara.main.domain.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.main.domain.Refund;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 退款视图对象
+ */
+@Data
+@AutoMapper(target = Refund.class)
+public class RefundVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "退款ID")
+    private Long id;
+
+    @ExcelProperty(value = "退款单号")
+    private String refundNo;
+
+    @ExcelProperty(value = "支付记录ID")
+    private Long paymentId;
+
+    @ExcelProperty(value = "订单ID")
+    private Long orderId;
+
+    @ExcelProperty(value = "订单号")
+    private String orderNo;
+
+    @ExcelProperty(value = "支付宝交易号")
+    private String tradeNo;
+
+    @ExcelProperty(value = "退款金额")
+    private BigDecimal refundAmount;
+
+    @ExcelProperty(value = "退款原因")
+    private String refundReason;
+
+    @ExcelProperty(value = "退款类型")
+    private Integer refundType;
+
+    @ExcelProperty(value = "支付方式")
+    private Integer paymentMethod;
+
+    @ExcelProperty(value = "退款状态")
+    private Integer refundStatus;
+
+    @ExcelProperty(value = "退款时间")
+    private Date refundTime;
+
+    @ExcelProperty(value = "失败原因")
+    private String failReason;
+
+    @ExcelProperty(value = "操作人ID")
+    private Long operatorId;
+
+    @ExcelProperty(value = "操作人姓名")
+    private String operatorName;
+
+    @ExcelProperty(value = "创建时间")
+    private Date createTime;
+}

+ 4 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainBackCandidateMapper.java

@@ -9,7 +9,11 @@ import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
 import org.dromara.main.domain.vo.MainBackCandidateVo;
 import org.dromara.main.domain.vo.MainPostCandidateVo;
 
+import java.util.List;
+
 public interface MainBackCandidateMapper extends BaseMapperPlus<MainBackCandidate, MainBackCandidateVo> {
 
     IPage<MainPostCandidateVo> selectPostCandidatePage(Page<MainPostCandidateVo> page, @Param("bo") MainPostCandidateQueryBo bo);
+
+    List<MainPostCandidateVo> selectPostCandidateList(@Param("bo") MainPostCandidateQueryBo bo);
 }

+ 7 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainPostCandidateHireAttachmentMapper.java

@@ -0,0 +1,7 @@
+package org.dromara.main.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainPostCandidateHireAttachment;
+
+public interface MainPostCandidateHireAttachmentMapper extends BaseMapperPlus<MainPostCandidateHireAttachment, MainPostCandidateHireAttachment> {
+}

+ 8 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/RefundMapper.java

@@ -0,0 +1,8 @@
+package org.dromara.main.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.Refund;
+import org.dromara.main.domain.vo.RefundVo;
+
+public interface RefundMapper extends BaseMapperPlus<Refund, RefundVo> {
+}

+ 6 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainBackOrderService.java

@@ -4,6 +4,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.main.domain.bo.MainBackOrderBo;
 import org.dromara.main.domain.bo.PortalCheckOrderCreateBo;
+import org.dromara.main.domain.vo.MainBackOrderCandidateVo;
 import org.dromara.main.domain.vo.MainBackOrderVo;
 
 import java.util.List;
@@ -42,4 +43,9 @@ public interface IMainBackOrderService {
      * 完成背调订单
      */
     Boolean completeOrder(Long id);
+
+    /**
+     * 门户打印授权书后确认候选人授权
+     */
+    MainBackOrderCandidateVo confirmPortalCandidateAuthorization(Long recordId, String tenantId);
 }

+ 2 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainExamEvaluationService.java

@@ -69,5 +69,7 @@ public interface IMainExamEvaluationService {
 
     TableDataInfo<MainExamApplyListVo> getApplyList(Long evaluationId, String keyword, PageQuery pageQuery);
 
+    List<MainExamApplyListVo> queryApplyList(Long evaluationId, String keyword);
+
     Boolean removeApply(Long applyId);
 }

+ 7 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPostCandidateService.java

@@ -2,13 +2,20 @@ package org.dromara.main.service;
 
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.bo.MainPostCandidateHireBo;
 import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
 import org.dromara.main.domain.bo.MainPostCandidateStatusBo;
 import org.dromara.main.domain.vo.MainPostCandidateVo;
 
+import java.util.List;
+
 public interface IMainPostCandidateService {
 
     TableDataInfo<MainPostCandidateVo> queryPageList(MainPostCandidateQueryBo bo, PageQuery pageQuery);
 
+    List<MainPostCandidateVo> queryList(MainPostCandidateQueryBo bo);
+
+    Boolean hireCandidate(MainPostCandidateHireBo bo);
+
     Boolean updateStatus(MainPostCandidateStatusBo bo);
 }

+ 3 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IPaymentService.java

@@ -2,6 +2,7 @@ package org.dromara.main.service;
 
 import org.dromara.main.domain.vo.PaymentVo;
 
+import java.math.BigDecimal;
 import java.util.Map;
 
 public interface IPaymentService {
@@ -10,6 +11,8 @@ public interface IPaymentService {
 
     String handleAlipayNotify(Map<String, String> params);
 
+    Boolean alipayRefund(String tradeNo, BigDecimal refundAmount, String refundNo, String refundReason);
+
     PaymentVo queryPaymentStatus(Long orderId);
 
     PaymentVo queryByPaymentNo(String paymentNo);

+ 15 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IRefundService.java

@@ -0,0 +1,15 @@
+package org.dromara.main.service;
+
+import org.dromara.main.domain.vo.RefundVo;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public interface IRefundService {
+
+    Long createRefund(Long orderId, BigDecimal refundAmount, String refundReason, Long operatorId, String operatorName);
+
+    List<RefundVo> queryByOrderId(Long orderId);
+
+    RefundVo queryById(Long refundId);
+}

+ 121 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackOrderServiceImpl.java

@@ -44,19 +44,28 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 @RequiredArgsConstructor
 @Service
 public class MainBackOrderServiceImpl implements IMainBackOrderService {
 
+    private static final String RECORD_STATUS_PENDING_AUTH = "未完成";
+    private static final String RECORD_STATUS_AUTHORIZED = "已授权";
+    private static final String RECORD_STATUS_UNAUTHORIZED = "未授权";
+    private static final Set<String> AUTHORIZATION_TERMINAL_STATUSES =
+        new HashSet<>(List.of(RECORD_STATUS_AUTHORIZED, RECORD_STATUS_UNAUTHORIZED));
+
     private final MainBackOrderMapper baseMapper;
     private final MainBackRecordMapper recordMapper;
     private final MainOrderMapper mainOrderMapper;
@@ -462,6 +471,51 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         return true;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MainBackOrderCandidateVo confirmPortalCandidateAuthorization(Long recordId, String tenantId) {
+        if (recordId == null) {
+            throw new ServiceException("候选人记录不能为空");
+        }
+        if (StringUtils.isBlank(tenantId)) {
+            throw new ServiceException("未登录或token已失效");
+        }
+
+        MainBackRecord record = recordMapper.selectById(recordId);
+        if (record == null) {
+            throw new ServiceException("候选人记录不存在");
+        }
+
+        MainBackOrder backOrder = baseMapper.selectById(record.getOrderId());
+        if (backOrder == null || !StringUtils.equals(backOrder.getTenantId(), tenantId)) {
+            throw new ServiceException("无权操作该候选人授权记录");
+        }
+
+        MainOrder mainOrder = mainOrderMapper.selectOne(
+            Wrappers.<MainOrder>lambdaQuery()
+                .eq(MainOrder::getBusinessId, backOrder.getId())
+                .eq(MainOrder::getOrderType, 3)
+                .eq(MainOrder::getTenantId, tenantId)
+                .last("limit 1")
+        );
+        if (mainOrder == null) {
+            throw new ServiceException("订单不存在");
+        }
+
+        if (!RECORD_STATUS_AUTHORIZED.equals(record.getStatus())) {
+            MainBackRecord update = new MainBackRecord();
+            update.setId(record.getId());
+            update.setStatus(RECORD_STATUS_AUTHORIZED);
+            update.setFinishTime(LocalDateTime.now());
+            recordMapper.updateById(update);
+            record.setStatus(RECORD_STATUS_AUTHORIZED);
+            record.setFinishTime(update.getFinishTime());
+        }
+
+        completePortalOrderWhenAuthorizationFinished(backOrder, mainOrder);
+        return buildPortalCandidateVo(record);
+    }
+
     private void fillPortalFields(MainBackOrderVo vo, MainOrder mainOrder) {
         Long count = recordMapper.selectCount(
             new LambdaQueryWrapper<MainBackRecord>()
@@ -525,6 +579,55 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         return amount == null ? BigDecimal.ZERO : amount;
     }
 
+    private void completePortalOrderWhenAuthorizationFinished(MainBackOrder backOrder, MainOrder mainOrder) {
+        List<MainBackRecord> records = recordMapper.selectList(
+            Wrappers.<MainBackRecord>lambdaQuery()
+                .eq(MainBackRecord::getOrderId, backOrder.getId())
+        );
+        if (records.isEmpty()) {
+            return;
+        }
+
+        boolean allAuthorizationCompleted = records.stream()
+            .map(MainBackRecord::getStatus)
+            .allMatch(AUTHORIZATION_TERMINAL_STATUSES::contains);
+        if (!allAuthorizationCompleted) {
+            return;
+        }
+
+        if (!"2".equals(backOrder.getStatus())) {
+            MainBackOrder orderUpdate = new MainBackOrder();
+            orderUpdate.setId(backOrder.getId());
+            orderUpdate.setStatus("2");
+            baseMapper.updateById(orderUpdate);
+        }
+
+        if (!Integer.valueOf(2).equals(mainOrder.getOrderStatus())) {
+            MainOrder mainOrderUpdate = new MainOrder();
+            mainOrderUpdate.setId(mainOrder.getId());
+            mainOrderUpdate.setOrderStatus(2);
+            mainOrderUpdate.setCompleteTime(new Date());
+            mainOrderMapper.updateById(mainOrderUpdate);
+        }
+    }
+
+    private MainBackOrderCandidateVo buildPortalCandidateVo(MainBackRecord record) {
+        MainBackCandidate candidate = mainBackCandidateMapper.selectById(record.getCandidateId());
+        MainStudent student = candidate == null ? null : mainStudentMapper.selectById(candidate.getStudentId());
+
+        MainBackOrderCandidateVo vo = new MainBackOrderCandidateVo();
+        vo.setId(record.getId());
+        vo.setOrderId(record.getOrderId());
+        vo.setStudentName(student == null ? null : student.getName());
+        vo.setCandidatePhone(student == null ? null : student.getMobile());
+        vo.setCandidateIdType("1");
+        vo.setCandidateIdNumber(student == null ? null : student.getIdCardNumber());
+        vo.setStatus(mapRecordStatus(record.getStatus()));
+        vo.setStatusText(mapRecordStatusText(record.getStatus()));
+        vo.setReportUrl(record.getReportUrl());
+        return vo;
+    }
+
     private Payment queryLatestPayment(Long orderId) {
         return paymentMapper.selectOne(
             Wrappers.<Payment>lambdaQuery()
@@ -580,6 +683,7 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
             vo.setCandidateIdType("1");
             vo.setCandidateIdNumber(student == null ? null : student.getIdCardNumber());
             vo.setStatus(mapRecordStatus(record.getStatus()));
+            vo.setStatusText(mapRecordStatusText(record.getStatus()));
             vo.setReportUrl(record.getReportUrl());
             result.add(vo);
         }
@@ -630,11 +734,28 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         return switch (status) {
             case "进行中" -> 1;
             case "已完成", "完成" -> 2;
+            case "已授权" -> 4;
+            case "未授权" -> 5;
             case "已取消", "失败" -> 3;
             default -> 0;
         };
     }
 
+    private String mapRecordStatusText(String status) {
+        if (StringUtils.isBlank(status)) {
+            return "待授权";
+        }
+        return switch (status) {
+            case RECORD_STATUS_PENDING_AUTH -> "待授权";
+            case RECORD_STATUS_AUTHORIZED -> RECORD_STATUS_AUTHORIZED;
+            case RECORD_STATUS_UNAUTHORIZED -> RECORD_STATUS_UNAUTHORIZED;
+            case "进行中" -> "背调中";
+            case "已完成", "完成" -> "已完成";
+            case "已取消", "失败" -> "已取消";
+            default -> "待授权";
+        };
+    }
+
     private String toPaymentStatusText(Integer status) {
         if (status == null) {
             return "未知";

+ 9 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java

@@ -189,6 +189,15 @@ public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService
         return TableDataInfo.build(list, pageQuery.build());
     }
 
+    @Override
+    public List<MainExamApplyListVo> queryApplyList(Long evaluationId, String keyword) {
+        if (evaluationId == null) {
+            throw new ServiceException("测评ID不能为空");
+        }
+        String tenantId = getCurrentTenantId();
+        return baseMapper.selectApplyListByEvaluationId(evaluationId, tenantId, keyword);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean removeApply(Long applyId) {

+ 65 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostCandidateServiceImpl.java

@@ -1,23 +1,34 @@
 package org.dromara.main.service.impl;
 
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.main.domain.MainBackCandidate;
+import org.dromara.main.domain.MainPostCandidateHireAttachment;
+import org.dromara.main.domain.bo.MainPostCandidateHireBo;
 import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
 import org.dromara.main.domain.bo.MainPostCandidateStatusBo;
 import org.dromara.main.domain.vo.MainPostCandidateVo;
 import org.dromara.main.mapper.MainBackCandidateMapper;
+import org.dromara.main.mapper.MainPostCandidateHireAttachmentMapper;
 import org.dromara.main.service.IMainPostCandidateService;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 @RequiredArgsConstructor
 @Service
 public class MainPostCandidateServiceImpl implements IMainPostCandidateService {
 
+    private static final String ENTERPRISE_STATUS_ADOPTED = "adopted";
+    private static final String ENTERPRISE_STATUS_REJECTED = "rejected";
+    private static final String ENTERPRISE_STATUS_PENDING = "pending";
+
     private final MainBackCandidateMapper mainBackCandidateMapper;
+    private final MainPostCandidateHireAttachmentMapper hireAttachmentMapper;
 
     @Override
     public TableDataInfo<MainPostCandidateVo> queryPageList(MainPostCandidateQueryBo bo, PageQuery pageQuery) {
@@ -28,6 +39,46 @@ public class MainPostCandidateServiceImpl implements IMainPostCandidateService {
         return TableDataInfo.build(page);
     }
 
+    @Override
+    public List<MainPostCandidateVo> queryList(MainPostCandidateQueryBo bo) {
+        if (bo.getPostId() == null) {
+            throw new ServiceException("岗位ID不能为空");
+        }
+        return mainBackCandidateMapper.selectPostCandidateList(bo);
+    }
+
+    @Override
+    public Boolean hireCandidate(MainPostCandidateHireBo bo) {
+        if (bo.getId() == null) {
+            throw new ServiceException("报名记录ID不能为空");
+        }
+        if (bo.getOfferAttachment() == null || bo.getOfferAttachment().isBlank()) {
+            throw new ServiceException("请先上传录用附件");
+        }
+        MainBackCandidate entity = new MainBackCandidate();
+        entity.setId(bo.getId());
+        entity.setEnterpriseStatus(ENTERPRISE_STATUS_ADOPTED);
+        entity.setRemark(bo.getRemark());
+        boolean updated = mainBackCandidateMapper.updateById(entity) > 0;
+        if (!updated) {
+            return false;
+        }
+
+        MainPostCandidateHireAttachment attachment = hireAttachmentMapper.selectOne(
+            Wrappers.<MainPostCandidateHireAttachment>lambdaQuery()
+                .eq(MainPostCandidateHireAttachment::getCandidateId, bo.getId())
+                .last("limit 1")
+        );
+        if (attachment == null) {
+            attachment = new MainPostCandidateHireAttachment();
+            attachment.setCandidateId(bo.getId());
+            attachment.setOssIds(bo.getOfferAttachment());
+            return hireAttachmentMapper.insert(attachment) > 0;
+        }
+        attachment.setOssIds(bo.getOfferAttachment());
+        return hireAttachmentMapper.updateById(attachment) > 0;
+    }
+
     @Override
     public Boolean updateStatus(MainPostCandidateStatusBo bo) {
         if (bo.getId() == null) {
@@ -36,10 +87,23 @@ public class MainPostCandidateServiceImpl implements IMainPostCandidateService {
         if (bo.getStatus() == null || bo.getStatus().isBlank()) {
             throw new ServiceException("状态不能为空");
         }
+        String normalizedStatus = normalizeStatus(bo.getStatus());
+        if (normalizedStatus == null) {
+            throw new ServiceException("状态值不合法");
+        }
         MainBackCandidate entity = new MainBackCandidate();
         entity.setId(bo.getId());
-        entity.setStatus(bo.getStatus());
+        entity.setEnterpriseStatus(normalizedStatus);
         entity.setRemark(bo.getRemark());
         return mainBackCandidateMapper.updateById(entity) > 0;
     }
+
+    private String normalizeStatus(String status) {
+        return switch (status.trim()) {
+            case ENTERPRISE_STATUS_ADOPTED, "0" -> ENTERPRISE_STATUS_ADOPTED;
+            case ENTERPRISE_STATUS_REJECTED, "1" -> ENTERPRISE_STATUS_REJECTED;
+            case ENTERPRISE_STATUS_PENDING, "2" -> ENTERPRISE_STATUS_PENDING;
+            default -> null;
+        };
+    }
 }

+ 42 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/PaymentServiceImpl.java

@@ -210,6 +210,48 @@ public class PaymentServiceImpl implements IPaymentService {
         }
     }
 
+    @Override
+    public Boolean alipayRefund(String tradeNo, BigDecimal refundAmount, String refundNo, String refundReason) {
+        try {
+            PaymentConfig config = paymentConfigService.getEnabledAlipayConfig();
+            if (config == null) {
+                throw new ServiceException("支付宝配置不存在");
+            }
+
+            AlipayClient alipayClient = new DefaultAlipayClient(
+                config.getGatewayUrl(),
+                config.getAppId(),
+                config.getPrivateKey(),
+                defaultIfBlank(config.getFormat(), "json"),
+                defaultIfBlank(config.getCharset(), "UTF-8"),
+                config.getPublicKey(),
+                defaultIfBlank(config.getSignType(), "RSA2")
+            );
+
+            com.alipay.api.request.AlipayTradeRefundRequest request =
+                new com.alipay.api.request.AlipayTradeRefundRequest();
+            JSONObject bizContent = new JSONObject();
+            bizContent.set("trade_no", tradeNo);
+            bizContent.set("refund_amount", refundAmount);
+            bizContent.set("refund_reason", refundReason);
+            bizContent.set("out_request_no", refundNo);
+            request.setBizContent(bizContent.toString());
+
+            com.alipay.api.response.AlipayTradeRefundResponse response = alipayClient.execute(request);
+            if (response.isSuccess()) {
+                log.info("支付宝退款成功:tradeNo={}, refundAmount={}, refundNo={}", tradeNo, refundAmount, refundNo);
+                return true;
+            }
+
+            log.error("支付宝退款失败:tradeNo={}, code={}, msg={}, subCode={}, subMsg={}",
+                tradeNo, response.getCode(), response.getMsg(), response.getSubCode(), response.getSubMsg());
+            return false;
+        } catch (AlipayApiException e) {
+            log.error("支付宝退款异常:tradeNo={}, error={}", tradeNo, e.getMessage(), e);
+            throw new ServiceException("支付宝退款失败:{}", e.getMessage());
+        }
+    }
+
     @Override
     public PaymentVo queryPaymentStatus(Long orderId) {
         Payment payment = paymentMapper.selectOne(

+ 204 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/RefundServiceImpl.java

@@ -0,0 +1,204 @@
+package org.dromara.main.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.main.domain.MainBackOrder;
+import org.dromara.main.domain.MainBackRecord;
+import org.dromara.main.domain.MainOrder;
+import org.dromara.main.domain.Payment;
+import org.dromara.main.domain.Refund;
+import org.dromara.main.domain.vo.RefundVo;
+import org.dromara.main.mapper.MainBackOrderMapper;
+import org.dromara.main.mapper.MainBackRecordMapper;
+import org.dromara.main.mapper.MainOrderMapper;
+import org.dromara.main.mapper.PaymentMapper;
+import org.dromara.main.mapper.RefundMapper;
+import org.dromara.main.service.ICompanyAccountService;
+import org.dromara.main.service.IPaymentService;
+import org.dromara.main.service.IRefundService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 退款服务实现
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class RefundServiceImpl implements IRefundService {
+
+    private static final int PAYMENT_METHOD_ALIPAY = 2;
+    private static final int PAYMENT_METHOD_BALANCE = 3;
+
+    private final RefundMapper refundMapper;
+    private final MainOrderMapper mainOrderMapper;
+    private final MainBackOrderMapper mainBackOrderMapper;
+    private final MainBackRecordMapper mainBackRecordMapper;
+    private final PaymentMapper paymentMapper;
+    private final ICompanyAccountService companyAccountService;
+    private final IPaymentService paymentService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createRefund(Long orderId, BigDecimal refundAmount, String refundReason, Long operatorId, String operatorName) {
+        MainOrder order = mainOrderMapper.selectById(orderId);
+        if (order == null) {
+            throw new ServiceException("订单不存在");
+        }
+        if (order.getOrderType() == null || (order.getOrderType() != 3 && order.getOrderType() != 4)) {
+            throw new ServiceException("仅支持背调订单退款");
+        }
+        if (order.getBusinessId() == null) {
+            throw new ServiceException("背调订单不存在");
+        }
+
+        MainBackOrder backOrder = mainBackOrderMapper.selectById(order.getBusinessId());
+        if (backOrder == null) {
+            throw new ServiceException("背调订单不存在");
+        }
+
+        if (order.getPayStatus() == null || order.getPayStatus() != 2) {
+            throw new ServiceException("订单未支付,无法退款");
+        }
+        if (order.getOrderStatus() != null && (order.getOrderStatus() == 4 || order.getOrderStatus() == 5)) {
+            throw new ServiceException("订单已在退款流程中,无法重复退款");
+        }
+        if (order.getRefundAmount() != null && order.getRefundAmount().compareTo(BigDecimal.ZERO) > 0) {
+            throw new ServiceException("订单已退款,不支持多次退款");
+        }
+        if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("退款金额必须大于0");
+        }
+        if (order.getPaidAmount() == null || refundAmount.compareTo(order.getPaidAmount()) > 0) {
+            throw new ServiceException("退款金额不能大于已支付金额");
+        }
+
+        boolean fullRefund = refundAmount.compareTo(order.getPaidAmount()) == 0;
+        int refundType = fullRefund ? 1 : 2;
+
+        Payment payment = paymentMapper.selectOne(
+            Wrappers.<Payment>lambdaQuery()
+                .eq(Payment::getOrderId, order.getId())
+                .orderByDesc(Payment::getCreateTime)
+                .last("limit 1")
+        );
+
+        Integer paymentMethod = payment == null || payment.getPaymentMethod() == null
+            ? PAYMENT_METHOD_BALANCE
+            : payment.getPaymentMethod();
+
+        Refund refund = new Refund();
+        refund.setTenantId(order.getTenantId());
+        refund.setRefundNo(generateRefundNo());
+        refund.setPaymentId(payment == null ? null : payment.getId());
+        refund.setOrderId(order.getId());
+        refund.setOrderNo(order.getOrderNo());
+        refund.setTradeNo(payment == null ? null : payment.getTradeNo());
+        refund.setRefundAmount(refundAmount);
+        refund.setRefundReason(refundReason);
+        refund.setRefundType(refundType);
+        refund.setPaymentMethod(paymentMethod);
+        refund.setRefundStatus(0);
+        refund.setOperatorId(operatorId);
+        refund.setOperatorName(operatorName);
+        refundMapper.insert(refund);
+
+        try {
+            if (paymentMethod == PAYMENT_METHOD_BALANCE) {
+                companyAccountService.refundToAvailable(
+                    order.getBuyerId(),
+                    refundAmount,
+                    order.getId(),
+                    order.getOrderNo(),
+                    refundReason
+                );
+            } else if (paymentMethod == PAYMENT_METHOD_ALIPAY) {
+                if (payment == null || payment.getTradeNo() == null || payment.getTradeNo().isBlank()) {
+                    throw new ServiceException("支付宝交易号不存在,无法退款");
+                }
+                boolean success = paymentService.alipayRefund(payment.getTradeNo(), refundAmount, refund.getRefundNo(), refundReason);
+                if (!success) {
+                    throw new ServiceException("支付宝退款失败");
+                }
+            } else {
+                throw new ServiceException("不支持的支付方式:" + paymentMethod);
+            }
+
+            refund.setRefundStatus(1);
+            refund.setRefundTime(new Date());
+            refundMapper.updateById(refund);
+        } catch (Exception e) {
+            refund.setRefundStatus(2);
+            refund.setFailReason(e.getMessage());
+            refundMapper.updateById(refund);
+            log.error("退款失败:orderId={}, error={}", order.getId(), e.getMessage(), e);
+            throw new ServiceException("退款失败:{}", e.getMessage());
+        }
+
+        if (paymentMethod == PAYMENT_METHOD_BALANCE && !fullRefund) {
+            BigDecimal remainingAmount = order.getPaidAmount().subtract(refundAmount);
+            if (remainingAmount.compareTo(BigDecimal.ZERO) > 0) {
+                companyAccountService.deductInUseBalance(
+                    order.getBuyerId(),
+                    remainingAmount,
+                    order.getId(),
+                    order.getOrderNo(),
+                    "部分退款,扣除剩余金额"
+                );
+            }
+        }
+
+        order.setRefundAmount(refundAmount);
+        order.setOrderStatus(fullRefund ? 4 : 2);
+        if (fullRefund) {
+            order.setCancelTime(new Date());
+        } else {
+            order.setCompleteTime(new Date());
+        }
+        mainOrderMapper.updateById(order);
+
+        backOrder.setStatus(fullRefund ? "3" : "2");
+        mainBackOrderMapper.updateById(backOrder);
+
+        List<MainBackRecord> records = mainBackRecordMapper.selectList(
+            Wrappers.<MainBackRecord>lambdaQuery()
+                .eq(MainBackRecord::getOrderId, backOrder.getId())
+        );
+        for (MainBackRecord record : records) {
+            if (!"已完成".equals(record.getStatus()) && !"完成".equals(record.getStatus())) {
+                record.setStatus("已取消");
+                mainBackRecordMapper.updateById(record);
+            }
+        }
+
+        log.info("退款完成:orderId={}, refundNo={}, refundAmount={}, refundType={}",
+            order.getId(), refund.getRefundNo(), refundAmount, fullRefund ? "全额" : "部分");
+        return refund.getId();
+    }
+
+    @Override
+    public List<RefundVo> queryByOrderId(Long orderId) {
+        return refundMapper.selectVoList(
+            Wrappers.<Refund>lambdaQuery()
+                .eq(Refund::getOrderId, orderId)
+                .orderByDesc(Refund::getCreateTime)
+        );
+    }
+
+    @Override
+    public RefundVo queryById(Long refundId) {
+        return refundMapper.selectVoById(refundId);
+    }
+
+    private String generateRefundNo() {
+        return "RF" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(4);
+    }
+}

+ 31 - 11
ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainBackCandidateMapper.xml

@@ -4,7 +4,7 @@
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.dromara.main.mapper.MainBackCandidateMapper">
 
-    <select id="selectPostCandidatePage" resultType="org.dromara.main.domain.vo.MainPostCandidateVo">
+    <sql id="selectPostCandidateColumns">
         SELECT
             c.id,
             c.post_id AS postId,
@@ -16,22 +16,28 @@
                 ELSE '--'
             END AS gender,
             s.mobile AS phone,
-            COALESCE(NULLIF(c.status, ''), 'pending') AS status,
-            CASE COALESCE(NULLIF(c.status, ''), 'pending')
-                WHEN 'adopted' THEN '录用'
-                WHEN 'rejected' THEN '不录用'
-                WHEN 'closed' THEN '学员已拒绝'
+            CASE
+                WHEN COALESCE(NULLIF(c.student_status, ''), 'pending') = 'rejected' THEN 'closed'
+                ELSE COALESCE(NULLIF(c.enterprise_status, ''), 'pending')
+            END AS status,
+            CASE
+                WHEN COALESCE(NULLIF(c.student_status, ''), 'pending') = 'rejected' THEN '学员已拒绝'
+                WHEN COALESCE(NULLIF(c.enterprise_status, ''), 'pending') = 'adopted' THEN '录用'
+                WHEN COALESCE(NULLIF(c.enterprise_status, ''), 'pending') = 'rejected' THEN '不录用'
                 ELSE '待回复'
             END AS statusText,
-            CASE COALESCE(NULLIF(c.status, ''), 'pending')
-                WHEN 'adopted' THEN 'success'
-                WHEN 'rejected' THEN 'danger'
-                WHEN 'closed' THEN 'danger'
+            CASE
+                WHEN COALESCE(NULLIF(c.student_status, ''), 'pending') = 'rejected' THEN 'danger'
+                WHEN COALESCE(NULLIF(c.enterprise_status, ''), 'pending') = 'adopted' THEN 'success'
+                WHEN COALESCE(NULLIF(c.enterprise_status, ''), 'pending') = 'rejected' THEN 'danger'
                 ELSE 'warning'
             END AS statusType,
             DATE_FORMAT(COALESCE(c.update_time, c.create_time), '%Y-%m-%d %H:%i:%s') AS evaluateTime,
             s.resume_file AS resumeFile,
             c.remark
+    </sql>
+
+    <sql id="selectPostCandidateFromWhere">
         FROM main_back_candidate c
         LEFT JOIN main_student s ON c.student_id = s.id AND IFNULL(s.del_flag, '0') = '0'
         WHERE IFNULL(c.del_flag, '0') = '0'
@@ -43,8 +49,22 @@
             )
         </if>
         <if test="bo.status != null and bo.status != ''">
-            AND COALESCE(NULLIF(c.status, ''), 'pending') = #{bo.status}
+            AND CASE
+                    WHEN COALESCE(NULLIF(c.student_status, ''), 'pending') = 'rejected' THEN 'closed'
+                    ELSE COALESCE(NULLIF(c.enterprise_status, ''), 'pending')
+                END = #{bo.status}
         </if>
+    </sql>
+
+    <select id="selectPostCandidatePage" resultType="org.dromara.main.domain.vo.MainPostCandidateVo">
+        <include refid="selectPostCandidateColumns" />
+        <include refid="selectPostCandidateFromWhere" />
+        ORDER BY c.create_time DESC, c.id DESC
+    </select>
+
+    <select id="selectPostCandidateList" resultType="org.dromara.main.domain.vo.MainPostCandidateVo">
+        <include refid="selectPostCandidateColumns" />
+        <include refid="selectPostCandidateFromWhere" />
         ORDER BY c.create_time DESC, c.id DESC
     </select>