dongpo 1 рік тому
батько
коміт
5d0136de63

+ 170 - 42
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/expense/OaExpenseController.java

@@ -3,6 +3,14 @@ package cn.iocoder.yudao.module.bpm.controller.admin.oa.expense;
 import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpensePageReqVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseRespVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpensePageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskReturnReqVO;
+import cn.iocoder.yudao.module.bpm.service.oa.expense.OaExpenseService;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import org.springdoc.api.annotations.ParameterObject;
 import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
@@ -37,71 +45,191 @@ import cn.iocoder.yudao.module.bpm.service.oa.expense.OaExpenseService;
 @Validated
 public class OaExpenseController {
 
+
     @Resource
     private OaExpenseService oaExpenseService;
 
-    @PostMapping("/create")
-    @Operation(summary = "创建报销流程信息")
-    @PreAuthorize("@ss.hasPermission('bpm:oa-expense:create')")
-    public CommonResult<Long> createOaExpense(@Valid @RequestBody OaExpenseSaveReqVO createReqVO) {
-        return success(oaExpenseService.createOaExpense(createReqVO));
+    @PostMapping("/staging")
+    @Operation(summary = "暂存报销审批流程信息")
+    @ApiOperationSupport(order = 1)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:staging')")
+    public CommonResult<Long> stagingOaExpense(@RequestBody OaExpenseSaveReqVO stagingReqVO) {
+        Long oaExpenseId = oaExpenseService.stagingOaExpense(stagingReqVO);
+        return success(oaExpenseId, "暂存成功");
+    }
+
+    @PostMapping("/commit")
+    @Operation(summary = "提交报销审批流程信息")
+    @ApiOperationSupport(order = 2)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:commit')")
+    public CommonResult<Long> commitOaExpense(@Valid @RequestBody OaExpenseSaveReqVO commitReqVO) {
+        Long oaExpenseId = oaExpenseService.commitOaExpense(commitReqVO);
+        return success(oaExpenseId, "提交成功");
+    }
+
+    @PostMapping("/agree")
+    @Operation(summary = "审批同意报销审批流程信息")
+    @ApiOperationSupport(order = 3)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:agree')")
+    public CommonResult<Long> agreeOaExpense(@Valid @RequestBody BpmTaskApproveReqVO agreeReqVO) {
+        Long result = oaExpenseService.agreeOaExpense(agreeReqVO);
+        return success(result, "审批成功");
+    }
+
+    @PostMapping("/disagree")
+    @Operation(summary = "驳回报销审批流程信息")
+    @ApiOperationSupport(order = 4)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:disagree')")
+    public CommonResult<Long> disagreeOaExpense(@Valid @RequestBody BpmTaskReturnReqVO disagreeReqVO) {
+        Long result = oaExpenseService.disagreeOaExpense(disagreeReqVO);
+        return success(result, "驳回成功");
     }
 
-    @PutMapping("/update")
-    @Operation(summary = "更新报销流程信息")
-    @PreAuthorize("@ss.hasPermission('bpm:oa-expense:update')")
-    public CommonResult<Boolean> updateOaExpense(@Valid @RequestBody OaExpenseSaveReqVO updateReqVO) {
-        oaExpenseService.updateOaExpense(updateReqVO);
-        return success(true);
+    @PostMapping("/revocation")
+    @Operation(summary = "撤回报销审批流程信息")
+    @ApiOperationSupport(order = 5)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:revocation')")
+    public CommonResult<Long> revocationOaExpense(@Valid @RequestBody BpmTaskApproveReqVO revocationReqVO) {
+        Long result = oaExpenseService.revocationOaExpense(revocationReqVO);
+        return success(result, "撤回成功");
+    }
+
+    @PostMapping("/reCommit")
+    @Operation(summary = "驳回或撤回后再次提交报销审批流程信息")
+    @ApiOperationSupport(order = 6)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:re-commit')")
+    public CommonResult<Long> reCommitOaExpense(@Valid @RequestBody OaExpenseSaveReqVO reCommitReqVO) {
+        Long result = oaExpenseService.reCommitOaExpense(reCommitReqVO);
+        return success(result, "再次提交成功");
+    }
+
+    @DeleteMapping("/close")
+    @Operation(summary = "驳回或撤回后关闭报销审批流程信息")
+    @ApiOperationSupport(order = 7)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-entry:close')")
+    public CommonResult<Long> closeOaExpense(@RequestParam("id") Long id) {
+        Long result = oaExpenseService.closeOaExpense(id);
+
+        return success(result, "关闭成功");
     }
 
     @DeleteMapping("/delete")
-    @Operation(summary = "删除报销流程信息")
+    @Operation(summary = "删除报销审批流程信息")
+    @ApiOperationSupport(order = 8)
     @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('bpm:oa-expense:delete')")
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:delete')")
     public CommonResult<Boolean> deleteOaExpense(@RequestParam("id") Long id) {
         oaExpenseService.deleteOaExpense(id);
-        return success(true);
+        return success(true, "删除成功");
     }
 
     @GetMapping("/get")
-    @Operation(summary = "获得报销流程信息")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query')")
+    @Operation(summary = "根据id获得报销审批流程信息")
+    @Parameter(name = "id", description = "编号", required = true, example = "1")
+    @ApiOperationSupport(order = 9)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query-id')")
     public CommonResult<OaExpenseRespVO> getOaExpense(@RequestParam("id") Long id) {
-        OaExpenseDO oaExpense = oaExpenseService.getOaExpense(id);
-        return success(BeanUtils.toBean(oaExpense, OaExpenseRespVO.class));
+        OaExpenseRespVO respVO = oaExpenseService.getOaExpense(id);
+        return success(respVO, "查询对象成功");
+    }
+
+    @GetMapping("/getByProcInstId")
+    @Operation(summary = "根据流程实例id获得报销审批流程信息")
+    @Parameter(name = "procInstId", description = "流程实例id", required = true, example = "9528a78d-457e-11ef-853c-4c034fce6445")
+    @ApiOperationSupport(order = 10)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query-proc-inst-id')")
+    public CommonResult<OaExpenseRespVO> getOaExpenseByProcInstId(@RequestParam("procInstId") String procInstId) {
+        OaExpenseRespVO respVO = oaExpenseService.getOaExpenseByProcInstId(procInstId);
+        return success(respVO, "查询对象成功");
     }
 
     @GetMapping("/page")
-    @Operation(summary = "获得报销流程信息分页")
-    @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query')")
-    public CommonResult<PageResult<OaExpenseRespVO>> getOaExpensePage(@Valid OaExpensePageReqVO pageReqVO) {
-        PageResult<OaExpenseDO> pageResult = oaExpenseService.getOaExpensePage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, OaExpenseRespVO.class));
+    @Operation(summary = "获得报销审批流程信息分页")
+    @ApiOperationSupport(order = 11)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query-page')")
+    public CommonResult<PageResult<OaExpenseRespVO>> getOaExpensePage(@Valid @ParameterObject OaExpensePageReqVO pageReqVO) {
+        return success(oaExpenseService.getOaExpensePage(pageReqVO), "查询列表成功");
     }
 
     @GetMapping("/export-excel")
-    @Operation(summary = "导出报销流程信息 Excel")
-    @PreAuthorize("@ss.hasPermission('bpm:oa-expense:export')")
+    @Operation(summary = "导出报销审批流程信息 Excel")
+    @ApiOperationSupport(order = 12)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:export')")
     @ApiAccessLog(operateType = EXPORT)
-    public void exportOaExpenseExcel(@Valid OaExpensePageReqVO pageReqVO,
-              HttpServletResponse response) throws IOException {
-        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<OaExpenseDO> list = oaExpenseService.getOaExpensePage(pageReqVO).getList();
+    public void exportOaExpenseExcel(@ParameterObject OaExpensePageReqVO pageReqVO,
+                                      HttpServletResponse response) throws IOException {
+        PageResult<OaExpenseRespVO> pageResult = oaExpenseService.exportOaExpenseExcel(pageReqVO);
+        List<OaExpenseRespVO> list = pageResult.getList();
         // 导出 Excel
-        ExcelUtils.write(response, "报销流程信息.xls", "数据", OaExpenseRespVO.class,
-                        BeanUtils.toBean(list, OaExpenseRespVO.class));
-    }
-
-    // ==================== 子表(报销流程信息子) ====================
-
-    @GetMapping("/oa-expense-obj/list-by-expense-id")
-    @Operation(summary = "获得报销流程信息子列表")
-    @Parameter(name = "expenseId", description = "报销主表主键id")
-    @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query')")
-    public CommonResult<List<OaExpenseObjDO>> getOaExpenseObjListByExpenseId(@RequestParam("expenseId") Long expenseId) {
-        return success(oaExpenseService.getOaExpenseObjListByExpenseId(expenseId));
+        ExcelUtils.write(response, "报销审批流程信息.xls", "数据", OaExpenseRespVO.class,
+                BeanUtils.toBean(list, OaExpenseRespVO.class));
     }
+    
+    // @Resource
+    // private OaExpenseService oaExpenseService;
+    //
+    // @PostMapping("/create")
+    // @Operation(summary = "创建报销流程信息")
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:create')")
+    // public CommonResult<Long> createOaExpense(@Valid @RequestBody OaExpenseSaveReqVO createReqVO) {
+    //     return success(oaExpenseService.createOaExpense(createReqVO));
+    // }
+    //
+    // @PutMapping("/update")
+    // @Operation(summary = "更新报销流程信息")
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:update')")
+    // public CommonResult<Boolean> updateOaExpense(@Valid @RequestBody OaExpenseSaveReqVO updateReqVO) {
+    //     oaExpenseService.updateOaExpense(updateReqVO);
+    //     return success(true);
+    // }
+    //
+    // @DeleteMapping("/delete")
+    // @Operation(summary = "删除报销流程信息")
+    // @Parameter(name = "id", description = "编号", required = true)
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:delete')")
+    // public CommonResult<Boolean> deleteOaExpense(@RequestParam("id") Long id) {
+    //     oaExpenseService.deleteOaExpense(id);
+    //     return success(true);
+    // }
+    //
+    // @GetMapping("/get")
+    // @Operation(summary = "获得报销流程信息")
+    // @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query')")
+    // public CommonResult<OaExpenseRespVO> getOaExpense(@RequestParam("id") Long id) {
+    //     OaExpenseDO oaExpense = oaExpenseService.getOaExpense(id);
+    //     return success(BeanUtils.toBean(oaExpense, OaExpenseRespVO.class));
+    // }
+    //
+    // @GetMapping("/page")
+    // @Operation(summary = "获得报销流程信息分页")
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query')")
+    // public CommonResult<PageResult<OaExpenseRespVO>> getOaExpensePage(@Valid OaExpensePageReqVO pageReqVO) {
+    //     PageResult<OaExpenseDO> pageResult = oaExpenseService.getOaExpensePage(pageReqVO);
+    //     return success(BeanUtils.toBean(pageResult, OaExpenseRespVO.class));
+    // }
+    //
+    // @GetMapping("/export-excel")
+    // @Operation(summary = "导出报销流程信息 Excel")
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:export')")
+    // @ApiAccessLog(operateType = EXPORT)
+    // public void exportOaExpenseExcel(@Valid OaExpensePageReqVO pageReqVO,
+    //           HttpServletResponse response) throws IOException {
+    //     pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+    //     List<OaExpenseDO> list = oaExpenseService.getOaExpensePage(pageReqVO).getList();
+    //     // 导出 Excel
+    //     ExcelUtils.write(response, "报销流程信息.xls", "数据", OaExpenseRespVO.class,
+    //                     BeanUtils.toBean(list, OaExpenseRespVO.class));
+    // }
+    //
+    // // ==================== 子表(报销流程信息子) ====================
+    //
+    // @GetMapping("/oa-expense-obj/list-by-expense-id")
+    // @Operation(summary = "获得报销流程信息子列表")
+    // @Parameter(name = "expenseId", description = "报销主表主键id")
+    // @PreAuthorize("@ss.hasPermission('bpm:oa-expense:query')")
+    // public CommonResult<List<OaExpenseObjDO>> getOaExpenseObjListByExpenseId(@RequestParam("expenseId") Long expenseId) {
+    //     return success(oaExpenseService.getOaExpenseObjListByExpenseId(expenseId));
+    // }
 
 }

+ 0 - 37
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/expense/vo/OaExpensePageReqVO.java

@@ -16,18 +16,12 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @ToString(callSuper = true)
 public class OaExpensePageReqVO extends PageParam {
 
-    @Schema(description = "报销申请人id")
-    private Long employeeId;
-
     @Schema(description = "报销申请员工姓名")
     private String employeeName;
 
     @Schema(description = "报销申请员工手机号")
     private String employeePhone;
 
-    @Schema(description = "用户账号id")
-    private Long userId;
-
     @Schema(description = "部门id")
     private Long deptId;
 
@@ -37,40 +31,9 @@ public class OaExpensePageReqVO extends PageParam {
     @Schema(description = "报销类型主键id")
     private Long expenseTypeId;
 
-    @Schema(description = "报销类型名称")
-    private String expenseTypeName;
-
-    @Schema(description = "费用所属区间")
-    private String expenseMonth;
-
-    @Schema(description = "总预算金额,单位(元)")
-    private BigDecimal totalMoney;
-
-    @Schema(description = "备注")
-    private String remarks;
-
-    @Schema(description = "流程实例id")
-    private String procInstId;
-
     @Schema(description = "审核状态(0暂存、1已提交、2审核中、3已审核、4已关闭、5已驳回)")
     private String auditStatus;
 
-    @Schema(description = "当前审核人员工id")
-    private Long currentAuditEmployeeId;
-
-    @Schema(description = "当前审核人员工姓名")
-    private String currentAuditEmployeeName;
-
-    @Schema(description = "当前审核人用户id")
-    private Long currentAuditUserId;
-
-    @Schema(description = "最后审核时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] finalAuditDate;
-
-    @Schema(description = "申请人员工id")
-    private Long applyEmployeeId;
-
     @Schema(description = "申请人员工姓名")
     private String applyEmployeeName;
 

+ 28 - 5
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/expense/vo/OaExpenseRespVO.java

@@ -1,13 +1,17 @@
 package cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo;
 
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.expense.OaExpenseObjDO;
+import cn.iocoder.yudao.module.infra.api.file.dto.FileDTO;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-import java.util.*;
+import lombok.Data;
+
 import java.math.BigDecimal;
-import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
-import com.alibaba.excel.annotation.*;
+import java.util.List;
 
 @Schema(description = "管理后台 - 报销流程信息 Response VO")
 @Data
@@ -38,6 +42,10 @@ public class OaExpenseRespVO {
     @ExcelProperty("部门id")
     private Long deptId;
 
+    @Schema(description = "部门名称")
+    @ExcelProperty("部门名称")
+    private String deptName;
+
     @Schema(description = "员工职位")
     @ExcelProperty("员工职位")
     private String position;
@@ -98,4 +106,19 @@ public class OaExpenseRespVO {
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;
 
+    @Schema(description = "采购流程信息子表列表")
+    private List<OaExpenseObjDO> oaExpenseObjs;
+
+    @Schema(description = "附件列表")
+    private List<FileDTO> fileList;
+
+    @Schema(description = "审批记录列表")
+    private List<BpmTaskRespVO> auditRecordList;
+
+    @Schema(description = "审批人员列表")
+    private List<AdminUserRespDTO> auditUserList;
+
+    @Schema(description = "当前用户待处理的任务id")
+    private String taskId;
+
 }

+ 9 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/expense/vo/OaExpenseSaveReqVO.java

@@ -22,7 +22,7 @@ public class OaExpenseSaveReqVO {
     private String expenseTypeName;
 
     @Schema(description = "费用所属区间")
-    @NotEmpty(message = "费用所属区间不能为空")
+    @NotBlank(message = "费用所属区间不能为空")
     private String expenseMonth;
 
     @Schema(description = "总预算金额,单位(元)")
@@ -34,6 +34,14 @@ public class OaExpenseSaveReqVO {
     private String remarks;
 
     @Schema(description = "报销流程信息子列表")
+    @NotEmpty(message = "报销流程信息子项列表不能为空")
     private List<OaExpenseObjDO> oaExpenseObjs;
 
+    @Schema(description = "发起人自选审批人", example = " [100, 1]")
+    @NotEmpty(message = "发起人自选审批人不能为空")
+    private List<Long> startUserSelectAssignees;
+
+    @Schema(description = "附件主键id", example = "[1, 2]")
+    private List<Long> fileIdList;
+
 }

+ 1 - 13
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/oa/expense/OaExpenseMapper.java

@@ -17,24 +17,12 @@ public interface OaExpenseMapper extends BaseMapperX<OaExpenseDO> {
 
     default PageResult<OaExpenseDO> selectPage(OaExpensePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<OaExpenseDO>()
-                .eqIfPresent(OaExpenseDO::getEmployeeId, reqVO.getEmployeeId())
                 .likeIfPresent(OaExpenseDO::getEmployeeName, reqVO.getEmployeeName())
-                .eqIfPresent(OaExpenseDO::getEmployeePhone, reqVO.getEmployeePhone())
-                .eqIfPresent(OaExpenseDO::getUserId, reqVO.getUserId())
+                .likeIfPresent(OaExpenseDO::getEmployeePhone, reqVO.getEmployeePhone())
                 .eqIfPresent(OaExpenseDO::getDeptId, reqVO.getDeptId())
                 .eqIfPresent(OaExpenseDO::getPosition, reqVO.getPosition())
                 .eqIfPresent(OaExpenseDO::getExpenseTypeId, reqVO.getExpenseTypeId())
-                .likeIfPresent(OaExpenseDO::getExpenseTypeName, reqVO.getExpenseTypeName())
-                .eqIfPresent(OaExpenseDO::getExpenseMonth, reqVO.getExpenseMonth())
-                .eqIfPresent(OaExpenseDO::getTotalMoney, reqVO.getTotalMoney())
-                .eqIfPresent(OaExpenseDO::getRemarks, reqVO.getRemarks())
-                .eqIfPresent(OaExpenseDO::getProcInstId, reqVO.getProcInstId())
                 .eqIfPresent(OaExpenseDO::getAuditStatus, reqVO.getAuditStatus())
-                .eqIfPresent(OaExpenseDO::getCurrentAuditEmployeeId, reqVO.getCurrentAuditEmployeeId())
-                .likeIfPresent(OaExpenseDO::getCurrentAuditEmployeeName, reqVO.getCurrentAuditEmployeeName())
-                .eqIfPresent(OaExpenseDO::getCurrentAuditUserId, reqVO.getCurrentAuditUserId())
-                .betweenIfPresent(OaExpenseDO::getFinalAuditDate, reqVO.getFinalAuditDate())
-                .eqIfPresent(OaExpenseDO::getApplyEmployeeId, reqVO.getApplyEmployeeId())
                 .likeIfPresent(OaExpenseDO::getApplyEmployeeName, reqVO.getApplyEmployeeName())
                 .betweenIfPresent(OaExpenseDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(OaExpenseDO::getId));

+ 62 - 37
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/expense/OaExpenseService.java

@@ -1,13 +1,14 @@
 package cn.iocoder.yudao.module.bpm.service.oa.expense;
 
-import java.util.*;
-import javax.validation.*;
-
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpensePageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseRespVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseSaveReqVO;
-import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.expense.OaExpenseDO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskReturnReqVO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.expense.OaExpenseObjDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import java.util.List;
 
 /**
  * 报销流程信息 Service 接口
@@ -16,43 +17,67 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
  */
 public interface OaExpenseService {
 
-    /**
-     * 创建报销流程信息
-     *
-     * @param createReqVO 创建信息
-     * @return 编号
-     */
-    Long createOaExpense(@Valid OaExpenseSaveReqVO createReqVO);
+    Long stagingOaExpense(OaExpenseSaveReqVO stagingReqVO);
 
-    /**
-     * 更新报销流程信息
-     *
-     * @param updateReqVO 更新信息
-     */
-    void updateOaExpense(@Valid OaExpenseSaveReqVO updateReqVO);
+    Long commitOaExpense(OaExpenseSaveReqVO commitReqVO);
+
+    Long agreeOaExpense(BpmTaskApproveReqVO agreeReqVO);
+
+    Long disagreeOaExpense(BpmTaskReturnReqVO disagreeReqVO);
+
+    Long revocationOaExpense(BpmTaskApproveReqVO revocationReqVO);
+
+    Long reCommitOaExpense(OaExpenseSaveReqVO reCommitReqVO);
+
+    Long closeOaExpense(Long id);
 
-    /**
-     * 删除报销流程信息
-     *
-     * @param id 编号
-     */
     void deleteOaExpense(Long id);
 
-    /**
-     * 获得报销流程信息
-     *
-     * @param id 编号
-     * @return 报销流程信息
-     */
-    OaExpenseDO getOaExpense(Long id);
+    OaExpenseRespVO getOaExpense(Long id);
 
-    /**
-     * 获得报销流程信息分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 报销流程信息分页
-     */
-    PageResult<OaExpenseDO> getOaExpensePage(OaExpensePageReqVO pageReqVO);
+    OaExpenseRespVO getOaExpenseByProcInstId(String procInstId);
+
+    PageResult<OaExpenseRespVO> getOaExpensePage(OaExpensePageReqVO pageReqVO);
+
+    PageResult<OaExpenseRespVO> exportOaExpenseExcel(OaExpensePageReqVO pageReqVO);
+
+    // /**
+    //  * 创建报销流程信息
+    //  *
+    //  * @param createReqVO 创建信息
+    //  * @return 编号
+    //  */
+    // Long createOaExpense(@Valid OaExpenseSaveReqVO createReqVO);
+    //
+    // /**
+    //  * 更新报销流程信息
+    //  *
+    //  * @param updateReqVO 更新信息
+    //  */
+    // void updateOaExpense(@Valid OaExpenseSaveReqVO updateReqVO);
+    //
+    // /**
+    //  * 删除报销流程信息
+    //  *
+    //  * @param id 编号
+    //  */
+    // void deleteOaExpense(Long id);
+    //
+    // /**
+    //  * 获得报销流程信息
+    //  *
+    //  * @param id 编号
+    //  * @return 报销流程信息
+    //  */
+    // OaExpenseDO getOaExpense(Long id);
+    //
+    // /**
+    //  * 获得报销流程信息分页
+    //  *
+    //  * @param pageReqVO 分页查询
+    //  * @return 报销流程信息分页
+    //  */
+    // PageResult<OaExpenseDO> getOaExpensePage(OaExpensePageReqVO pageReqVO);
 
     // ==================== 子表(报销流程信息子) ====================
 

+ 718 - 30
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/expense/OaExpenseServiceImpl.java

@@ -1,13 +1,49 @@
 package cn.iocoder.yudao.module.bpm.service.oa.expense;
 
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
 import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpensePageReqVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpensePageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.expense.vo.OaExpenseSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskReturnReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.expense.OaExpenseDO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.expense.OaExpenseObjDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.oa.expense.OaExpenseMapper;
+import cn.iocoder.yudao.module.bpm.dal.mysql.oa.expense.OaExpenseObjMapper;
+import cn.iocoder.yudao.module.bpm.enums.DictDataConstants;
+import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
+import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
+import cn.iocoder.yudao.module.infra.api.file.FileApi;
+import cn.iocoder.yudao.module.infra.api.file.dto.FileDTO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.engine.TaskService;
+import org.flowable.task.api.Task;
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.expense.OaExpenseDO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.expense.OaExpenseObjDO;
@@ -29,65 +65,717 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
 @Validated
 public class OaExpenseServiceImpl implements OaExpenseService {
 
+
+    /**
+     * 对应的流程定义 KEY
+     */
+    public static final String PROCESS_KEY = "oa_expense";
+
+
     @Resource
     private OaExpenseMapper oaExpenseMapper;
+
     @Resource
     private OaExpenseObjMapper oaExpenseObjMapper;
 
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+
+    @Resource
+    private BpmTaskService bpmTaskService;
+
+    @Resource
+    private TaskService taskService;
+
+    @Resource
+    private FileApi fileApi;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Resource
+    private DeptApi deptApi;
+
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Long createOaExpense(OaExpenseSaveReqVO createReqVO) {
-        // 插入
-        OaExpenseDO oaExpense = BeanUtils.toBean(createReqVO, OaExpenseDO.class);
-        oaExpenseMapper.insert(oaExpense);
-
-        // 插入子表
-        createOaExpenseObjList(oaExpense.getId(), createReqVO.getOaExpenseObjs());
-        // 返回
+    @Transactional
+    public Long stagingOaExpense(OaExpenseSaveReqVO stagingReqVO) {
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        AdminUserRespDTO loginUser = adminUserApi.getUser(loginUserId);
+        Objects.requireNonNull(loginUser, "登录用户不能为空");
+        // 人信息
+        AdminUserRespDTO employee = adminUserApi.getUser(loginUserId);
+
+        OaExpenseDO oaExpense = BeanUtils.toBean(stagingReqVO, OaExpenseDO.class);
+        if (StringUtils.isBlank(oaExpense.getExpenseUuid())) {
+            // 创建单据uuid
+            String uuid = IdUtil.fastSimpleUUID();
+            oaExpense.setExpenseUuid(uuid);
+        }
+        if (employee != null) {
+            oaExpense.setEmployeeId(loginUser.getId());
+            oaExpense.setEmployeeName(loginUser.getNickname());
+            oaExpense.setEmployeePhone(loginUser.getMobile());
+            oaExpense.setDeptId(loginUser.getDeptId());
+            oaExpense.setPosition("员工职位");
+            oaExpense.setUserId(loginUser.getId());
+        }
+        oaExpense.setAuditStatus(DictDataConstants.OA_AUDIT_STATUS_STAGING);
+        oaExpense.setInfoSource("0");
+        oaExpense.setApplyEmployeeId(loginUser.getId());
+        oaExpense.setApplyEmployeeName(loginUser.getNickname());
+        // 暂存不保存审批人信息
+        oaExpense.setStartUserSelectAssignees(null);
+
+        // 保存或更新表单信息
+        if (oaExpense.getId() == null) {
+            oaExpenseMapper.insert(oaExpense);
+        } else {
+            validateOaExpenseExists(oaExpense.getId());
+            oaExpenseMapper.updateById(oaExpense);
+        }
+        // 保存子表信息
+        List<OaExpenseObjDO> oaExpenseObjs = stagingReqVO.getOaExpenseObjs();
+        if (CollectionUtil.isNotEmpty(oaExpenseObjs)) {
+            updateOaExpenseObjList(oaExpense.getId(), oaExpenseObjs);
+        }
+
+        // 保存业务uuid到附件中
+        saveFileList(stagingReqVO.getFileIdList(), oaExpense.getExpenseUuid());
+
         return oaExpense.getId();
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateOaExpense(OaExpenseSaveReqVO updateReqVO) {
-        // 校验存在
-        validateOaExpenseExists(updateReqVO.getId());
-        // 更新
-        OaExpenseDO updateObj = BeanUtils.toBean(updateReqVO, OaExpenseDO.class);
-        oaExpenseMapper.updateById(updateObj);
+    @Transactional
+    public Long commitOaExpense(OaExpenseSaveReqVO commitReqVO) {
+        if (CollectionUtil.isEmpty(commitReqVO.getStartUserSelectAssignees())) {
+            throw exception(ErrorCodeConstants.TASK_CREATE_FAIL_NO_START_SELECT_ASSIGNEE);
+        }
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        AdminUserRespDTO loginUser = adminUserApi.getUser(loginUserId);
+        if (loginUser == null) {
+            throw exception(ErrorCodeConstants.OA_LOGIN_USER_NOT_EXISTS);
+        }
+        // 人信息
+        AdminUserRespDTO employee = adminUserApi.getUser(loginUserId);
+        if (employee == null) {
+            throw exception(ErrorCodeConstants.OA_EMPLOYEE_NOT_EXISTS);
+        }
+
+
+        OaExpenseDO oaExpense = BeanUtils.toBean(commitReqVO, OaExpenseDO.class);
+        if (StringUtils.isBlank(oaExpense.getExpenseUuid())) {
+            // 创建单据uuid
+            String uuid = IdUtil.fastSimpleUUID();
+            oaExpense.setExpenseUuid(uuid);
+        }
+        oaExpense.setEmployeeId(employee.getId());
+        oaExpense.setEmployeeName(employee.getNickname());
+        oaExpense.setEmployeePhone(employee.getMobile());
+        oaExpense.setDeptId(employee.getDeptId());
+        oaExpense.setPosition("员工职位");
+        oaExpense.setUserId(loginUser.getId());
+        oaExpense.setInfoSource("0");
+        oaExpense.setApplyEmployeeId(loginUser.getId());
+        oaExpense.setApplyEmployeeName(loginUser.getNickname());
+
+        // 子表信息
+        List<OaExpenseObjDO> oaExpenseObjs = commitReqVO.getOaExpenseObjs();
+        for (OaExpenseObjDO oaExpenseObj : oaExpenseObjs) {
+            if (oaExpenseObj.getExpenseItemId() == null) {
+                throw exception(ErrorCodeConstants.OA_EXPENSE_OBJ_ITEM_BLANK);
+            }
+            if (oaExpenseObj.getMoney() == null || oaExpenseObj.getMoney().compareTo(BigDecimal.ZERO) < 1) {
+                throw exception(OA_EXPENSE_OBJ_MONEY_ZERO);
+            }
+            if (StrUtil.isBlank(oaExpenseObj.getStartDate())) {
+                throw exception(OA_EXPENSE_OBJ_START_DATE_NULL);
+            }
+            if (StrUtil.isBlank(oaExpenseObj.getEndDate())) {
+                throw exception(OA_EXPENSE_OBJ_END_DATE_NULL);
+            }
+        }
+
+        // 保存或更新表单信息
+        if (oaExpense.getId() == null) {
+            oaExpenseMapper.insert(oaExpense);
+        } else {
+            OaExpenseDO oaExpenseDO = validateOaExpenseExists(oaExpense.getId());
+            if (StrUtil.isNotBlank(oaExpenseDO.getProcInstId())) {
+                throw exception(ErrorCodeConstants.PROCESS_INSTANCE_CREATE_FAIL_HAS_PROCESS);
+            }
+            oaExpenseMapper.updateById(oaExpense);
+        }
+        // 保存子表
+        if (CollectionUtil.isNotEmpty(oaExpenseObjs)) {
+            updateOaExpenseObjList(oaExpense.getId(), oaExpenseObjs);
+        }
+
+        // 发起流程
+        Map<String, Object> processInstanceVariables = new HashMap<>();
+        processInstanceVariables.put("auditPass", "true");
+        // 添加审批人信息
+        Map<String, List<Long>> startUserSelectAssignees = new HashMap<>();
+        List<Long> selectAssignees = commitReqVO.getStartUserSelectAssignees();
+        startUserSelectAssignees.put("approver", selectAssignees);
+        String processInstanceId = processInstanceApi
+                .createProcessInstance(loginUser.getId(),
+                        new BpmProcessInstanceCreateReqDTO()
+                                .setProcessDefinitionKey(PROCESS_KEY)
+                                .setVariables(processInstanceVariables)
+                                .setBusinessKey(String.valueOf(oaExpense.getId()))
+                                .setStartUserSelectAssignees(startUserSelectAssignees));
+
+        //获取下一个审批人
+        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
+
+        // 审批同意
+        BpmTaskApproveReqVO agreeReqVO = new BpmTaskApproveReqVO();
+        agreeReqVO.setId(task.getId());
+        agreeReqVO.setReason("[首次提交]");
+        agreeReqVO.setTaskStatus(Integer.valueOf(DictDataConstants.OA_AUDIT_STATUS_COMMITTED));
+        taskService.setVariable(task.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_COMMITTED);
+        taskService.setVariable(task.getId(), "auditPass", "true");
+        bpmTaskService.approveTask(loginUserId, agreeReqVO);
+
+        Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
+        if (nextTask == null) {
+            throw exception(ErrorCodeConstants.TASK_CREATE_FAIL_NO_START_SELECT_ASSIGNEE);
+        }
+        Long currentAuditEmployeeId = Long.valueOf(nextTask.getAssignee());
+        AdminUserRespDTO currentAuditEmployee = adminUserApi.getUser(currentAuditEmployeeId);
+
+
+        // 将工作流的流程实例ID、单据状态、最后一次审批时间、当前审批人更新到单据信息中
+        oaExpenseMapper.updateById(new OaExpenseDO()
+                .setId(oaExpense.getId())
+                .setProcInstId(processInstanceId)
+                .setAuditStatus(DictDataConstants.OA_AUDIT_STATUS_COMMITTED)
+                .setFinalAuditDate(LocalDateTime.now())
+                .setCurrentAuditEmployeeId(currentAuditEmployeeId)
+                .setCurrentAuditEmployeeName(currentAuditEmployee.getNickname())
+                .setStartUserSelectAssignees(selectAssignees.stream().map(String::valueOf).collect(Collectors.joining(","))));
+        // 保存业务uuid到附件中
+        saveFileList(commitReqVO.getFileIdList(), oaExpense.getExpenseUuid());
+        // TODO DP 发送提交成功站内信
+        return oaExpense.getId();
+    }
+
+    @Override
+    @Transactional
+    public Long agreeOaExpense(BpmTaskApproveReqVO agreeReqVO) {
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // TODO DP 根据登录人查询出对应的员工信息
+
+        Task currentTask = bpmTaskService.getTask(agreeReqVO.getId());
+        if (currentTask == null) {
+            throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
+        }
+        // 先更新为审批中
+        taskService.setVariable(currentTask.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_AUDITING);
+        LambdaQueryWrapper<OaExpenseDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lambdaQueryWrapper.eq(OaExpenseDO::getProcInstId, currentTask.getProcessInstanceId());
+        OaExpenseDO oaExpense = oaExpenseMapper.selectOne(lambdaQueryWrapper);
+        // 如果是最后一个人审批同意,设置流程审批状态为已审核
+        String[] auditPersons = oaExpense.getStartUserSelectAssignees().split(",");
+        String lastAuditPerson = Arrays.stream(auditPersons)
+                .reduce((first, second) -> second)
+                .orElse(null);
+        if (currentTask.getAssignee().equals(lastAuditPerson)) {
+            // 更新为已审核(暂时未生效,未找到原因,先不处理)
+            taskService.setVariable(agreeReqVO.getId(),BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_AUDITED);
+        }
+        // 审批同意
+        String reason = "[同意]";
+        if (StrUtil.isNotBlank(agreeReqVO.getReason())) {
+            reason = reason + agreeReqVO.getReason();
+        }
+        agreeReqVO.setReason(reason);
+        agreeReqVO.setTaskStatus(Integer.valueOf(DictDataConstants.OA_AUDIT_STATUS_AUDITING));
+        taskService.setVariable(agreeReqVO.getId(), "auditPass", "true");
+        bpmTaskService.approveTask(loginUserId, agreeReqVO);
+
+        Task nextTask = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).singleResult();
+        OaExpenseDO oaExpenseDO = new OaExpenseDO();
+        if (nextTask != null) {
+            Long currentAuditEmployeeId = Long.valueOf(nextTask.getAssignee());
+            AdminUserRespDTO currentAuditEmployee = adminUserApi.getUser(currentAuditEmployeeId);
+
+            // 如果审批人重复,实际未结束,再次更新为审核中
+            taskService.setVariable(nextTask.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_AUDITING);
+            oaExpenseDO.setAuditStatus(DictDataConstants.OA_AUDIT_STATUS_AUDITING)
+                    .setCurrentAuditEmployeeId(currentAuditEmployeeId)
+                    .setCurrentAuditEmployeeName(currentAuditEmployee.getNickname())
+                    .setFinalAuditDate(LocalDateTime.now())
+                    .setId(oaExpense.getId());
+
+        } else {
+            oaExpenseDO.setAuditStatus(DictDataConstants.OA_AUDIT_STATUS_AUDITED)
+                    .setCurrentAuditEmployeeId(null)
+                    .setCurrentAuditEmployeeName(null)
+                    .setFinalAuditDate(LocalDateTime.now())
+                    .setId(oaExpense.getId());
+
+            // TODO 复制业务单据信息到业务模块单据表
+
+        }
+        oaExpenseMapper.updateById(oaExpenseDO);
+        // 发送通知
+
+        // 返回
+        return 1L;
+    }
+
+    @Override
+    @Transactional
+    public Long disagreeOaExpense(BpmTaskReturnReqVO disagreeReqVO) {
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // TODO DP 根据登录人查询出对应的员工信息
+
+        Task currentTask = bpmTaskService.getTask(disagreeReqVO.getId());
+
+        disagreeReqVO.setTargetTaskDefinitionKey("modifyApply");
+        disagreeReqVO.setReason("[驳回]" + disagreeReqVO.getReason());
+        disagreeReqVO.setTaskStatus(Integer.valueOf(DictDataConstants.OA_AUDIT_STATUS_RETURNED));
+        taskService.setVariable(disagreeReqVO.getId(), "auditPass", "false");
+        taskService.setVariable(disagreeReqVO.getId(),BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_RETURNED);
+        bpmTaskService.returnTask(loginUserId, disagreeReqVO);
+
+        Task nextTask = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).singleResult();
+        Long currentAuditEmployeeId = Long.valueOf(nextTask.getAssignee());
+        AdminUserRespDTO currentAuditEmployee = adminUserApi.getUser(currentAuditEmployeeId);
+
+        // 更新单据状态,当前处理人,最后处理时间
+        LambdaUpdateWrapper<OaExpenseDO> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(OaExpenseDO::getCurrentAuditEmployeeId, currentAuditEmployeeId)
+                .set(OaExpenseDO::getCurrentAuditEmployeeName, currentAuditEmployee.getNickname())
+                .set(OaExpenseDO::getFinalAuditDate, LocalDateTime.now())
+                .set(OaExpenseDO::getAuditStatus, DictDataConstants.OA_AUDIT_STATUS_RETURNED)
+                .eq(OaExpenseDO::getProcInstId, currentTask.getProcessInstanceId());
+        oaExpenseMapper.update(updateWrapper);
+
+        // 发送通知
 
-        // 更新子表
-        updateOaExpenseObjList(updateReqVO.getId(), updateReqVO.getOaExpenseObjs());
+        return 1L;
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional
+    public Long revocationOaExpense(BpmTaskApproveReqVO revocationReqVO) {
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // TODO DP 根据登录人查询出对应的员工信息
+
+        Task currentTask = bpmTaskService.getTask(revocationReqVO.getId());
+        if (currentTask == null) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TASK_NOT_EXISTS);
+        }
+        LambdaQueryWrapper<OaExpenseDO> lambdaQueryWrapper = new LambdaQueryWrapper<OaExpenseDO>()
+                .eq(OaExpenseDO::getProcInstId, currentTask.getProcessInstanceId());
+        OaExpenseDO oaExpenseDO = oaExpenseMapper.selectOne(lambdaQueryWrapper);
+        if (!DictDataConstants.OA_AUDIT_STATUS_COMMITTED.equals(oaExpenseDO.getAuditStatus())
+                || !Objects.equals(String.valueOf(loginUserId), oaExpenseDO.getCreator())) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TASK_REVOCATION_NOT_ALLOWED);
+        }
+
+        BpmTaskReturnReqVO returnReqVO = BeanUtils.toBean(revocationReqVO, BpmTaskReturnReqVO.class);
+        returnReqVO.setTargetTaskDefinitionKey("modifyApply");
+        String reason = "[撤回]";
+        if (StrUtil.isNotBlank(returnReqVO.getReason())) {
+            reason = reason + returnReqVO.getReason();
+        }
+        returnReqVO.setReason(reason);
+        returnReqVO.setTaskStatus(Integer.valueOf(DictDataConstants.OA_AUDIT_STATUS_RECALLED));
+        taskService.setVariable(currentTask.getId(),BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_RECALLED);
+        bpmTaskService.revocationTask(loginUserId, returnReqVO);
+
+        Task nextTask = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).singleResult();
+        Long currentAuditEmployeeId = Long.valueOf(nextTask.getAssignee());
+        AdminUserRespDTO currentAuditEmployee = adminUserApi.getUser(currentAuditEmployeeId);
+
+        // 更新单据状态,当前处理人,最后处理时间
+        LambdaUpdateWrapper<OaExpenseDO> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(OaExpenseDO::getCurrentAuditEmployeeId, currentAuditEmployeeId)
+                .set(OaExpenseDO::getCurrentAuditEmployeeName, currentAuditEmployee.getNickname())
+                .set(OaExpenseDO::getFinalAuditDate, LocalDateTime.now())
+                .set(OaExpenseDO::getAuditStatus, DictDataConstants.OA_AUDIT_STATUS_RECALLED)
+                .eq(OaExpenseDO::getProcInstId, currentTask.getProcessInstanceId());
+        oaExpenseMapper.update(updateWrapper);
+
+        // 发送通知
+
+        return 1L;
+    }
+
+    @Override
+    @Transactional
+    public Long reCommitOaExpense(OaExpenseSaveReqVO reCommitReqVO) {
+        if (reCommitReqVO.getId() == null) {
+            throw exception(ErrorCodeConstants.SERVICE_ID_NOT_EXISTS);
+        }
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        AdminUserRespDTO loginUser = adminUserApi.getUser(loginUserId);
+        if (loginUser == null) {
+            throw exception(ErrorCodeConstants.OA_LOGIN_USER_NOT_EXISTS);
+        }
+        // 人信息
+        AdminUserRespDTO employee = adminUserApi.getUser(loginUserId);
+        if (employee == null) {
+            throw exception(ErrorCodeConstants.OA_EMPLOYEE_NOT_EXISTS);
+        }
+
+        OaExpenseDO oaExpenseNew = BeanUtils.toBean(reCommitReqVO, OaExpenseDO.class);
+        OaExpenseDO oaExpenseOld = validateOaExpenseExists(oaExpenseNew.getId());
+        oaExpenseNew.setExpenseUuid(oaExpenseOld.getExpenseUuid())
+                .setProcInstId(oaExpenseOld.getProcInstId());
+
+        oaExpenseNew.setEmployeeId(employee.getId());
+        oaExpenseNew.setEmployeeName(employee.getNickname());
+        oaExpenseNew.setEmployeePhone(employee.getMobile());
+        oaExpenseNew.setDeptId(employee.getDeptId());
+        oaExpenseNew.setPosition("员工职位");
+        oaExpenseNew.setUserId(loginUser.getId());
+        oaExpenseNew.setApplyEmployeeId(loginUser.getId());
+        oaExpenseNew.setApplyEmployeeName(loginUser.getNickname());
+
+        // 子表信息
+        List<OaExpenseObjDO> oaExpenseObjs = reCommitReqVO.getOaExpenseObjs();
+        for (OaExpenseObjDO oaExpenseObj : oaExpenseObjs) {
+            if (oaExpenseObj.getExpenseItemId() == null) {
+                throw exception(ErrorCodeConstants.OA_EXPENSE_OBJ_ITEM_BLANK);
+            }
+            if (oaExpenseObj.getMoney() == null || oaExpenseObj.getMoney().compareTo(BigDecimal.ZERO) < 1) {
+                throw exception(OA_EXPENSE_OBJ_MONEY_ZERO);
+            }
+            if (StrUtil.isBlank(oaExpenseObj.getStartDate())) {
+                throw exception(OA_EXPENSE_OBJ_START_DATE_NULL);
+            }
+            if (StrUtil.isBlank(oaExpenseObj.getEndDate())) {
+                throw exception(OA_EXPENSE_OBJ_END_DATE_NULL);
+            }
+        }
+
+        Task currentTask = taskService.createTaskQuery().processInstanceId(oaExpenseNew.getProcInstId()).singleResult();
+        BpmTaskApproveReqVO approveReqVO = new BpmTaskApproveReqVO();
+        approveReqVO.setId(currentTask.getId())
+                .setReason("[再次提交]");
+        approveReqVO.setTaskStatus(Integer.valueOf(DictDataConstants.OA_AUDIT_STATUS_COMMITTED));
+        if (CollectionUtil.isNotEmpty(reCommitReqVO.getStartUserSelectAssignees())) {
+            // 添加审批人信息到流程参数中
+            Map<String, List<Long>> startUserSelectAssignees = new HashMap<>();
+            List<Long> selectAssignees = reCommitReqVO.getStartUserSelectAssignees();
+            startUserSelectAssignees.put("approver", selectAssignees);
+            taskService.setVariable(currentTask.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);
+            // 保存审批人信息
+            oaExpenseNew.setStartUserSelectAssignees(selectAssignees.stream().map(String::valueOf).collect(Collectors.joining(",")));
+        }
+        taskService.setVariable(currentTask.getId(), "auditPass", "true");
+        taskService.setVariable(currentTask.getId(),BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_COMMITTED);
+        // 再次提交,和审批通过逻辑相同
+        bpmTaskService.approveTask(loginUserId, approveReqVO);
+
+        Task nextTask = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).singleResult();
+        Long currentAuditEmployeeId = Long.valueOf(nextTask.getAssignee());
+        AdminUserRespDTO currentAuditEmployee = adminUserApi.getUser(currentAuditEmployeeId);
+        oaExpenseNew.setAuditStatus(DictDataConstants.OA_AUDIT_STATUS_COMMITTED)
+                .setCurrentAuditEmployeeId(currentAuditEmployeeId)
+                .setCurrentAuditEmployeeName(currentAuditEmployee.getNickname())
+                .setFinalAuditDate(LocalDateTime.now());
+        oaExpenseMapper.updateById(oaExpenseNew);
+
+        // 保存子表信息
+        if (CollectionUtil.isNotEmpty(oaExpenseObjs)) {
+            updateOaExpenseObjList(oaExpenseNew.getId(), oaExpenseObjs);
+        }
+
+        // 保存业务uuid到附件中
+        saveFileList(reCommitReqVO.getFileIdList(), oaExpenseNew.getExpenseUuid());
+
+        return 1L;
+    }
+
+    @Override
+    @Transactional
+    public Long closeOaExpense(Long id) {
+        if (id == null) {
+            throw exception(ErrorCodeConstants.SERVICE_ID_NOT_EXISTS);
+        }
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // TODO DP 根据登录人查询出对应的员工信息
+
+        OaExpenseDO oaExpenseDO = oaExpenseMapper.selectById(id);
+        if (!DictDataConstants.OA_AUDIT_STATUS_RETURNED.equals(oaExpenseDO.getAuditStatus())
+                && !DictDataConstants.OA_AUDIT_STATUS_RECALLED.equals(oaExpenseDO.getAuditStatus())) {
+            throw exception(ErrorCodeConstants.TASK_CLOSE_NOT_ALLOWED);
+        }
+
+        Task currentTask = taskService.createTaskQuery().processInstanceId(oaExpenseDO.getProcInstId()).singleResult();
+        BpmTaskApproveReqVO approveReqVO = new BpmTaskApproveReqVO();
+        approveReqVO.setId(currentTask.getId())
+                .setReason("[关闭]");
+        approveReqVO.setTaskStatus(Integer.valueOf(DictDataConstants.OA_AUDIT_STATUS_CLOSED));
+        taskService.setVariable(currentTask.getId(), "auditPass", "false");
+        taskService.setVariable(currentTask.getId(),BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, DictDataConstants.OA_AUDIT_STATUS_CLOSED);
+        // 关闭,使用审批通过的方法实现
+        bpmTaskService.approveTask(loginUserId, approveReqVO);
+
+        LambdaUpdateWrapper<OaExpenseDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+        lambdaUpdateWrapper.set(OaExpenseDO::getAuditStatus, DictDataConstants.OA_AUDIT_STATUS_CLOSED)
+                .set(OaExpenseDO::getCurrentAuditEmployeeId, null)
+                .set(OaExpenseDO::getCurrentAuditEmployeeName, null)
+                .set(OaExpenseDO::getFinalAuditDate, LocalDateTime.now())
+                .eq(OaExpenseDO::getId, id);
+        oaExpenseMapper.update(lambdaUpdateWrapper);
+        return 1L;
+    }
+
+    @Override
+    @Transactional
     public void deleteOaExpense(Long id) {
         // 校验存在
-        validateOaExpenseExists(id);
+        validateOaExpenseDelete(id);
         // 删除
         oaExpenseMapper.deleteById(id);
-
-        // 删除子表
+        // 删除子表数据
         deleteOaExpenseObjByExpenseId(id);
     }
 
-    private void validateOaExpenseExists(Long id) {
-        if (oaExpenseMapper.selectById(id) == null) {
-            throw exception(OA_EXPENSE_NOT_EXISTS);
+    @Override
+    public OaExpenseRespVO getOaExpense(Long id) {
+        // 登录人信息
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // TODO DP 根据登录人查询出对应的员工信息
+
+        OaExpenseDO oaExpenseDO = validateOaExpenseExists(id);
+        OaExpenseRespVO oaExpenseRespVO = BeanUtils.toBean(oaExpenseDO, OaExpenseRespVO.class);
+        if (oaExpenseDO.getDeptId() != null) {
+            DeptRespDTO dept = deptApi.getDept(oaExpenseDO.getDeptId());
+            if (dept != null) {
+                oaExpenseRespVO.setDeptName(dept.getName());
+            }
+        }
+        // 子表数据
+        List<OaExpenseObjDO> oaExpenseObjDOS = getOaExpenseObjListByExpenseId(oaExpenseDO.getId());
+        oaExpenseRespVO.setOaExpenseObjs(oaExpenseObjDOS);
+
+        String procInstId = oaExpenseDO.getProcInstId();
+        if (StrUtil.isNotBlank(procInstId)) {
+            Task task = taskService.createTaskQuery()
+                    .processInstanceId(procInstId)
+                    .taskAssignee(String.valueOf(loginUserId))
+                    .singleResult();
+            if (DictDataConstants.OA_AUDIT_STATUS_COMMITTED.equals(oaExpenseDO.getAuditStatus())) {
+                // 如果是已提交,不限制任务处理人是当前登录人,用于直接撤回
+                task = taskService.createTaskQuery()
+                        .processInstanceId(procInstId)
+                        .singleResult();
+            }
+            if (task != null) {
+                oaExpenseRespVO.setTaskId(task.getId());
+            }
         }
+
+        // 附件列表
+        List<FileDTO> fileList = fileApi.getFileDTOListByBiz(oaExpenseDO.getExpenseUuid());
+        oaExpenseRespVO.setFileList(fileList);
+
+        // 审批记录
+        if (StrUtil.isNotBlank(oaExpenseDO.getProcInstId())) {
+            List<BpmTaskRespVO> auditRecordList = bpmTaskService.getAuditRecordListByProcessInstanceId(oaExpenseDO.getProcInstId());
+            oaExpenseRespVO.setAuditRecordList(auditRecordList);
+        }
+
+        // 审批人
+        String startUserSelectAssignees = oaExpenseDO.getStartUserSelectAssignees();
+        if (StrUtil.isNotEmpty(startUserSelectAssignees)) {
+            List<Long> userIdList = Arrays.stream(startUserSelectAssignees.split(","))
+                    .map(Long::valueOf).collect(Collectors.toList());
+            List<AdminUserRespDTO> userList = adminUserApi.getUserList(userIdList);
+            List<AdminUserRespDTO> auditUserList = new ArrayList<>();
+            for (Long userId : userIdList) {
+                for (AdminUserRespDTO adminUserRespDTO : userList) {
+                    if (Objects.equals(userId, adminUserRespDTO.getId())) {
+                        auditUserList.add(adminUserRespDTO);
+                        break;
+                    }
+                }
+            }
+            oaExpenseRespVO.setAuditUserList(auditUserList);
+        }
+
+        return oaExpenseRespVO;
     }
 
     @Override
-    public OaExpenseDO getOaExpense(Long id) {
-        return oaExpenseMapper.selectById(id);
+    public OaExpenseRespVO getOaExpenseByProcInstId(String procInstId) {
+        OaExpenseDO oaExpenseDO = oaExpenseMapper.selectOne(OaExpenseDO::getProcInstId, procInstId);
+        OaExpenseRespVO oaExpenseRespVO = BeanUtils.toBean(oaExpenseDO, OaExpenseRespVO.class);
+        if (oaExpenseDO.getDeptId() != null) {
+            DeptRespDTO dept = deptApi.getDept(oaExpenseDO.getDeptId());
+            if (dept != null) {
+                oaExpenseRespVO.setDeptName(dept.getName());
+            }
+        }
+
+        // 附件列表
+        List<FileDTO> fileList = fileApi.getFileDTOListByBiz(oaExpenseDO.getExpenseUuid());
+        oaExpenseRespVO.setFileList(fileList);
+
+        // 审批记录
+        if (StrUtil.isNotBlank(oaExpenseDO.getProcInstId())) {
+            List<BpmTaskRespVO> auditRecordList = bpmTaskService.getAuditRecordListByProcessInstanceId(oaExpenseDO.getProcInstId());
+            oaExpenseRespVO.setAuditRecordList(auditRecordList);
+        }
+
+        // 审批人
+        String startUserSelectAssignees = oaExpenseDO.getStartUserSelectAssignees();
+        if (StrUtil.isNotEmpty(startUserSelectAssignees)) {
+            List<Long> userIdList = Arrays.stream(startUserSelectAssignees.split(","))
+                    .map(Long::valueOf).collect(Collectors.toList());
+            List<AdminUserRespDTO> userList = adminUserApi.getUserList(userIdList);
+            List<AdminUserRespDTO> auditUserList = new ArrayList<>();
+            for (Long userId : userIdList) {
+                for (AdminUserRespDTO adminUserRespDTO : userList) {
+                    if (Objects.equals(userId, adminUserRespDTO.getId())) {
+                        auditUserList.add(adminUserRespDTO);
+                        break;
+                    }
+                }
+            }
+            oaExpenseRespVO.setAuditUserList(auditUserList);
+        }
+
+        return oaExpenseRespVO;
     }
 
     @Override
-    public PageResult<OaExpenseDO> getOaExpensePage(OaExpensePageReqVO pageReqVO) {
-        return oaExpenseMapper.selectPage(pageReqVO);
+    public PageResult<OaExpenseRespVO> getOaExpensePage(OaExpensePageReqVO pageReqVO) {
+        PageResult<OaExpenseDO> oaExpenseDOPageResult = oaExpenseMapper.selectPage(pageReqVO);
+        PageResult<OaExpenseRespVO> oaExpenseRespVOPageResult = BeanUtils.toBean(oaExpenseDOPageResult, OaExpenseRespVO.class);
+
+        List<OaExpenseRespVO> oaExpenseRespVOList = oaExpenseRespVOPageResult.getList();
+        if (CollectionUtil.isNotEmpty(oaExpenseRespVOList)) {
+            List<Long> employeeIdList = oaExpenseRespVOList.stream().map(OaExpenseRespVO::getCurrentAuditEmployeeId).collect(Collectors.toList());
+            List<AdminUserRespDTO> employeeList = adminUserApi.getUserList(employeeIdList);
+            for (OaExpenseRespVO respVO : oaExpenseRespVOList) {
+                for (AdminUserRespDTO employee : employeeList) {
+                    if (employee.getId() != null && employee.getId().equals(respVO.getCurrentAuditEmployeeId())) {
+                        respVO.setCurrentAuditEmployeeName(employee.getNickname());
+                        break;
+                    }
+                }
+            }
+
+            List<Long> deptIdList = oaExpenseRespVOList.stream().map(OaExpenseRespVO::getDeptId).collect(Collectors.toList());
+            List<DeptRespDTO> deptList = deptApi.getDeptList(deptIdList);
+            for (OaExpenseRespVO respVO : oaExpenseRespVOList) {
+                for (DeptRespDTO dept : deptList) {
+                    if (dept.getId() != null && dept.getId().equals(respVO.getDeptId())) {
+                        respVO.setDeptName(dept.getName());
+                        break;
+                    }
+                }
+            }
+        }
+
+        return oaExpenseRespVOPageResult;
+    }
+
+    @Override
+    public PageResult<OaExpenseRespVO> exportOaExpenseExcel(OaExpensePageReqVO pageReqVO) {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        return getOaExpensePage(pageReqVO);
+    }
+
+    private void validateOaExpenseDelete(Long id) {
+        OaExpenseDO oaExpenseDO = validateOaExpenseExists(id);
+        if (!DictDataConstants.OA_AUDIT_STATUS_STAGING.equals(oaExpenseDO.getAuditStatus())) {
+            throw exception(ErrorCodeConstants.DELETE_FAIL_NOT_STAGING);
+        }
+    }
+
+    private OaExpenseDO validateOaExpenseExists(Long id) {
+        OaExpenseDO oaExpenseDO = oaExpenseMapper.selectById(id);
+        if (oaExpenseDO == null) {
+            throw exception(OA_EXPENSE_NOT_EXISTS);
+        }
+        return oaExpenseDO;
     }
 
+    /**
+     * 保存业务uuid到附件中
+     */
+    private void saveFileList(List<Long> fileIdList, String serviceId) {
+        if (CollectionUtil.isNotEmpty(fileIdList) && StrUtil.isNotEmpty(serviceId)) {
+            fileApi.updateFileBiz(fileIdList, serviceId);
+        }
+    }
+
+    // @Resource
+    // private OaExpenseMapper oaExpenseMapper;
+    // @Resource
+    // private OaExpenseObjMapper oaExpenseObjMapper;
+    //
+    // @Override
+    // @Transactional(rollbackFor = Exception.class)
+    // public Long createOaExpense(OaExpenseSaveReqVO createReqVO) {
+    //     // 插入
+    //     OaExpenseDO oaExpense = BeanUtils.toBean(createReqVO, OaExpenseDO.class);
+    //     oaExpenseMapper.insert(oaExpense);
+    //
+    //     // 插入子表
+    //     createOaExpenseObjList(oaExpense.getId(), createReqVO.getOaExpenseObjs());
+    //     // 返回
+    //     return oaExpense.getId();
+    // }
+    //
+    // @Override
+    // @Transactional(rollbackFor = Exception.class)
+    // public void updateOaExpense(OaExpenseSaveReqVO updateReqVO) {
+    //     // 校验存在
+    //     validateOaExpenseExists(updateReqVO.getId());
+    //     // 更新
+    //     OaExpenseDO updateObj = BeanUtils.toBean(updateReqVO, OaExpenseDO.class);
+    //     oaExpenseMapper.updateById(updateObj);
+    //
+    //     // 更新子表
+    //     updateOaExpenseObjList(updateReqVO.getId(), updateReqVO.getOaExpenseObjs());
+    // }
+    //
+    // @Override
+    // @Transactional(rollbackFor = Exception.class)
+    // public void deleteOaExpense(Long id) {
+    //     // 校验存在
+    //     validateOaExpenseExists(id);
+    //     // 删除
+    //     oaExpenseMapper.deleteById(id);
+    //
+    //     // 删除子表
+    //     deleteOaExpenseObjByExpenseId(id);
+    // }
+    //
+    // private void validateOaExpenseExists(Long id) {
+    //     if (oaExpenseMapper.selectById(id) == null) {
+    //         throw exception(OA_EXPENSE_NOT_EXISTS);
+    //     }
+    // }
+    //
+    // @Override
+    // public OaExpenseDO getOaExpense(Long id) {
+    //     return oaExpenseMapper.selectById(id);
+    // }
+    //
+    // @Override
+    // public PageResult<OaExpenseDO> getOaExpensePage(OaExpensePageReqVO pageReqVO) {
+    //     return oaExpenseMapper.selectPage(pageReqVO);
+    // }
+
     // ==================== 子表(报销流程信息子) ====================
 
     @Override
@@ -102,7 +790,7 @@ public class OaExpenseServiceImpl implements OaExpenseService {
 
     private void updateOaExpenseObjList(Long expenseId, List<OaExpenseObjDO> list) {
         deleteOaExpenseObjByExpenseId(expenseId);
-		list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
+		list.forEach(o -> o.setId(null).setCreator(null).setCreateTime(null).setUpdater(null).setUpdateTime(null).setDeleted(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
         createOaExpenseObjList(expenseId, list);
     }