提交审批能力部分代码
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package com.ruoyi.oa.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.oa.domain.bo.OaApprovalActionBo;
|
||||
import com.ruoyi.oa.domain.bo.OaApprovalConfigBo;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalConfigVo;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalInstanceVo;
|
||||
import com.ruoyi.oa.service.IOaApprovalService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 通用审批
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/oa/approval")
|
||||
public class OaApprovalController extends BaseController {
|
||||
|
||||
private final IOaApprovalService approvalService;
|
||||
|
||||
// ===== 配置 =====
|
||||
@GetMapping("/config/list")
|
||||
public R<List<OaApprovalConfigVo>> configList() {
|
||||
return R.ok(approvalService.listConfigs());
|
||||
}
|
||||
|
||||
@GetMapping("/config/{businessType}")
|
||||
public R<OaApprovalConfigVo> getConfig(@PathVariable String businessType) {
|
||||
return R.ok(approvalService.getConfig(businessType));
|
||||
}
|
||||
|
||||
@PostMapping("/config")
|
||||
public R<Void> saveConfig(@RequestBody OaApprovalConfigBo bo) {
|
||||
return toAjax(approvalService.saveConfig(bo));
|
||||
}
|
||||
|
||||
@DeleteMapping("/config/{id}")
|
||||
public R<Void> deleteConfig(@PathVariable Long id) {
|
||||
return toAjax(approvalService.deleteConfig(id));
|
||||
}
|
||||
|
||||
// ===== 审批 =====
|
||||
@PostMapping("/act")
|
||||
public R<Void> act(@Validated @RequestBody OaApprovalActionBo bo) {
|
||||
return toAjax(approvalService.act(bo));
|
||||
}
|
||||
|
||||
@PostMapping("/withdraw/{instanceId}")
|
||||
public R<Void> withdraw(@PathVariable Long instanceId) {
|
||||
return toAjax(approvalService.withdraw(instanceId));
|
||||
}
|
||||
|
||||
// ===== 查询 =====
|
||||
@GetMapping("/detail/{instanceId}")
|
||||
public R<OaApprovalInstanceVo> detail(@PathVariable Long instanceId) {
|
||||
return R.ok(approvalService.getDetail(instanceId));
|
||||
}
|
||||
|
||||
@GetMapping("/latest")
|
||||
public R<OaApprovalInstanceVo> latest(@NotNull @RequestParam String businessType,
|
||||
@NotNull @RequestParam Long businessId) {
|
||||
return R.ok(approvalService.getLatestByBusiness(businessType, businessId));
|
||||
}
|
||||
|
||||
@GetMapping("/mine/pending")
|
||||
public TableDataInfo<OaApprovalInstanceVo> myPending(PageQuery pageQuery,
|
||||
@RequestParam(required = false) String businessType) {
|
||||
return approvalService.pageMyPending(pageQuery, businessType);
|
||||
}
|
||||
|
||||
@GetMapping("/mine/done")
|
||||
public TableDataInfo<OaApprovalInstanceVo> myDone(PageQuery pageQuery,
|
||||
@RequestParam(required = false) String businessType) {
|
||||
return approvalService.pageMyDone(pageQuery, businessType);
|
||||
}
|
||||
|
||||
@GetMapping("/mine/submitted")
|
||||
public TableDataInfo<OaApprovalInstanceVo> mySubmitted(PageQuery pageQuery,
|
||||
@RequestParam(required = false) String businessType) {
|
||||
return approvalService.pageMySubmitted(pageQuery, businessType);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<OaApprovalInstanceVo> all(PageQuery pageQuery,
|
||||
@RequestParam(required = false) String businessType,
|
||||
@RequestParam(required = false) Integer status) {
|
||||
return approvalService.pageAll(pageQuery, businessType, status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.ruoyi.oa.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 业务审批配置 oa_approval_config
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("oa_approval_config")
|
||||
public class OaApprovalConfig extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/** 业务类型 key */
|
||||
private String businessType;
|
||||
|
||||
/** 业务名称(展示用) */
|
||||
private String businessName;
|
||||
|
||||
/** 审批人 user_id 列表,逗号分隔 */
|
||||
private String approverIds;
|
||||
|
||||
/** 1或签 2会签 */
|
||||
private Integer signType;
|
||||
|
||||
/** 是否启用:0停用 1启用 */
|
||||
private Integer enabled;
|
||||
|
||||
private String remark;
|
||||
|
||||
private Integer delFlag;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.ruoyi.oa.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 审批单实例 oa_approval_instance
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("oa_approval_instance")
|
||||
public class OaApprovalInstance extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
private String businessType;
|
||||
private Long businessId;
|
||||
private String businessTitle;
|
||||
|
||||
private Long applyUserId;
|
||||
private String applyUserName;
|
||||
private Date applyTime;
|
||||
|
||||
/** 快照:提交时的审批人 */
|
||||
private String approverIds;
|
||||
/** 快照:1或签 2会签 */
|
||||
private Integer signType;
|
||||
|
||||
/** 0待审 1通过 2驳回 3撤回 */
|
||||
private Integer status;
|
||||
private Date finishTime;
|
||||
|
||||
private Integer delFlag;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.oa.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 审批操作流水 oa_approval_record
|
||||
*/
|
||||
@Data
|
||||
@TableName("oa_approval_record")
|
||||
public class OaApprovalRecord implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
private Long instanceId;
|
||||
private Long approverId;
|
||||
private String approverName;
|
||||
|
||||
/** 1通过 2驳回 */
|
||||
private Integer action;
|
||||
private String comment;
|
||||
private Date opTime;
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.oa.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class OaApprovalActionBo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotNull
|
||||
private Long instanceId;
|
||||
|
||||
/** 1通过 2驳回 */
|
||||
@NotNull
|
||||
private Integer action;
|
||||
|
||||
private String comment;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.oa.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class OaApprovalConfigBo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String businessType;
|
||||
private String businessName;
|
||||
/** 审批人 user_id 列表,逗号分隔 */
|
||||
private String approverIds;
|
||||
/** 1或签 2会签 */
|
||||
private Integer signType;
|
||||
/** 0停用 1启用 */
|
||||
private Integer enabled;
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class OaApprovalConfigVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String businessType;
|
||||
private String businessName;
|
||||
private String approverIds;
|
||||
/** 显示用:审批人昵称 列表 */
|
||||
private String approverNames;
|
||||
private Integer signType;
|
||||
private Integer enabled;
|
||||
private String remark;
|
||||
private Date createTime;
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class OaApprovalInstanceVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String businessType;
|
||||
private String businessName;
|
||||
private Long businessId;
|
||||
private String businessTitle;
|
||||
|
||||
private Long applyUserId;
|
||||
private String applyUserName;
|
||||
private Date applyTime;
|
||||
|
||||
private String approverIds;
|
||||
private String approverNames;
|
||||
private Integer signType;
|
||||
|
||||
/** 0待审 1通过 2驳回 3撤回 */
|
||||
private Integer status;
|
||||
private Date finishTime;
|
||||
|
||||
/** 审批流水 */
|
||||
private List<OaApprovalRecordVo> records;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.oa.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class OaApprovalRecordVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private Long instanceId;
|
||||
private Long approverId;
|
||||
private String approverName;
|
||||
private Integer action;
|
||||
private String comment;
|
||||
private Date opTime;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.oa.mapper;
|
||||
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import com.ruoyi.oa.domain.OaApprovalConfig;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalConfigVo;
|
||||
|
||||
public interface OaApprovalConfigMapper extends BaseMapperPlus<OaApprovalConfigMapper, OaApprovalConfig, OaApprovalConfigVo> {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.oa.mapper;
|
||||
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import com.ruoyi.oa.domain.OaApprovalInstance;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalInstanceVo;
|
||||
|
||||
public interface OaApprovalInstanceMapper extends BaseMapperPlus<OaApprovalInstanceMapper, OaApprovalInstance, OaApprovalInstanceVo> {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.oa.mapper;
|
||||
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import com.ruoyi.oa.domain.OaApprovalRecord;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalRecordVo;
|
||||
|
||||
public interface OaApprovalRecordMapper extends BaseMapperPlus<OaApprovalRecordMapper, OaApprovalRecord, OaApprovalRecordVo> {
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ruoyi.oa.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.oa.domain.bo.OaApprovalActionBo;
|
||||
import com.ruoyi.oa.domain.bo.OaApprovalConfigBo;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalConfigVo;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalInstanceVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用审批 Service
|
||||
*/
|
||||
public interface IOaApprovalService {
|
||||
|
||||
// ===== 配置 =====
|
||||
List<OaApprovalConfigVo> listConfigs();
|
||||
|
||||
OaApprovalConfigVo getConfig(String businessType);
|
||||
|
||||
Boolean saveConfig(OaApprovalConfigBo bo);
|
||||
|
||||
Boolean deleteConfig(Long id);
|
||||
|
||||
// ===== 业务侧调用 =====
|
||||
|
||||
/**
|
||||
* 业务提交触发审批,若已有未结束的实例则直接返回。
|
||||
* @return 审批单 id
|
||||
*/
|
||||
Long submit(String businessType, Long businessId, String businessTitle);
|
||||
|
||||
/**
|
||||
* 审批操作(通过/驳回),自动判定或签/会签终结状态。
|
||||
*/
|
||||
Boolean act(OaApprovalActionBo bo);
|
||||
|
||||
/**
|
||||
* 撤回(仅申请人可在 status=0 时撤回)
|
||||
*/
|
||||
Boolean withdraw(Long instanceId);
|
||||
|
||||
/**
|
||||
* 查询某业务的最新审批单(用于业务详情展示)
|
||||
*/
|
||||
OaApprovalInstanceVo getLatestByBusiness(String businessType, Long businessId);
|
||||
|
||||
/**
|
||||
* 批量查询业务最新审批状态:返回 businessId -> status
|
||||
*/
|
||||
Map<Long, Integer> batchLatestStatus(String businessType, Collection<Long> businessIds);
|
||||
|
||||
// ===== 列表 =====
|
||||
|
||||
/** 我待办(审批人是我,status=0) */
|
||||
TableDataInfo<OaApprovalInstanceVo> pageMyPending(PageQuery pageQuery, String businessType);
|
||||
|
||||
/** 我已办(出现在 record 里) */
|
||||
TableDataInfo<OaApprovalInstanceVo> pageMyDone(PageQuery pageQuery, String businessType);
|
||||
|
||||
/** 我发起的 */
|
||||
TableDataInfo<OaApprovalInstanceVo> pageMySubmitted(PageQuery pageQuery, String businessType);
|
||||
|
||||
/** 全部(管理员) */
|
||||
TableDataInfo<OaApprovalInstanceVo> pageAll(PageQuery pageQuery, String businessType, Integer status);
|
||||
|
||||
/** 单条详情(带流水) */
|
||||
OaApprovalInstanceVo getDetail(Long instanceId);
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package com.ruoyi.oa.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.helper.LoginHelper;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.oa.domain.OaApprovalConfig;
|
||||
import com.ruoyi.oa.domain.OaApprovalInstance;
|
||||
import com.ruoyi.oa.domain.OaApprovalRecord;
|
||||
import com.ruoyi.oa.domain.bo.OaApprovalActionBo;
|
||||
import com.ruoyi.oa.domain.bo.OaApprovalConfigBo;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalConfigVo;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalInstanceVo;
|
||||
import com.ruoyi.oa.domain.vo.OaApprovalRecordVo;
|
||||
import com.ruoyi.oa.mapper.OaApprovalConfigMapper;
|
||||
import com.ruoyi.oa.mapper.OaApprovalInstanceMapper;
|
||||
import com.ruoyi.oa.mapper.OaApprovalRecordMapper;
|
||||
import com.ruoyi.oa.service.IOaApprovalService;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OaApprovalServiceImpl implements IOaApprovalService {
|
||||
|
||||
private final OaApprovalConfigMapper configMapper;
|
||||
private final OaApprovalInstanceMapper instanceMapper;
|
||||
private final OaApprovalRecordMapper recordMapper;
|
||||
private final ISysUserService userService;
|
||||
|
||||
// ===================== 配置 =====================
|
||||
|
||||
@Override
|
||||
public List<OaApprovalConfigVo> listConfigs() {
|
||||
List<OaApprovalConfigVo> list = configMapper.selectVoList(
|
||||
Wrappers.<OaApprovalConfig>lambdaQuery().orderByAsc(OaApprovalConfig::getBusinessType));
|
||||
list.forEach(this::fillApproverNames);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OaApprovalConfigVo getConfig(String businessType) {
|
||||
OaApprovalConfig cfg = configMapper.selectOne(
|
||||
Wrappers.<OaApprovalConfig>lambdaQuery().eq(OaApprovalConfig::getBusinessType, businessType));
|
||||
if (cfg == null) return null;
|
||||
OaApprovalConfigVo vo = BeanUtil.toBean(cfg, OaApprovalConfigVo.class);
|
||||
fillApproverNames(vo);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean saveConfig(OaApprovalConfigBo bo) {
|
||||
if (StringUtils.isBlank(bo.getBusinessType())) {
|
||||
throw new ServiceException("业务类型不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(bo.getApproverIds())) {
|
||||
throw new ServiceException("审批人不能为空");
|
||||
}
|
||||
if (bo.getSignType() == null) bo.setSignType(1);
|
||||
if (bo.getEnabled() == null) bo.setEnabled(1);
|
||||
|
||||
OaApprovalConfig exist = configMapper.selectOne(
|
||||
Wrappers.<OaApprovalConfig>lambdaQuery().eq(OaApprovalConfig::getBusinessType, bo.getBusinessType()));
|
||||
OaApprovalConfig entity = BeanUtil.toBean(bo, OaApprovalConfig.class);
|
||||
if (exist != null) {
|
||||
entity.setId(exist.getId());
|
||||
return configMapper.updateById(entity) > 0;
|
||||
}
|
||||
return configMapper.insert(entity) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteConfig(Long id) {
|
||||
return configMapper.deleteById(id) > 0;
|
||||
}
|
||||
|
||||
// ===================== 业务侧 =====================
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long submit(String businessType, Long businessId, String businessTitle) {
|
||||
OaApprovalConfig cfg = configMapper.selectOne(
|
||||
Wrappers.<OaApprovalConfig>lambdaQuery().eq(OaApprovalConfig::getBusinessType, businessType));
|
||||
if (cfg == null || cfg.getEnabled() != null && cfg.getEnabled() == 0) {
|
||||
// 未配置 / 未启用:跳过审批,不报错
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isBlank(cfg.getApproverIds())) {
|
||||
throw new ServiceException("业务【" + businessType + "】未配置审批人");
|
||||
}
|
||||
|
||||
// 若已存在未结束实例则复用
|
||||
OaApprovalInstance pending = instanceMapper.selectOne(
|
||||
Wrappers.<OaApprovalInstance>lambdaQuery()
|
||||
.eq(OaApprovalInstance::getBusinessType, businessType)
|
||||
.eq(OaApprovalInstance::getBusinessId, businessId)
|
||||
.eq(OaApprovalInstance::getStatus, 0)
|
||||
.last("LIMIT 1"));
|
||||
if (pending != null) return pending.getId();
|
||||
|
||||
OaApprovalInstance inst = new OaApprovalInstance();
|
||||
inst.setBusinessType(businessType);
|
||||
inst.setBusinessId(businessId);
|
||||
inst.setBusinessTitle(businessTitle);
|
||||
inst.setApplyUserId(LoginHelper.getUserId());
|
||||
inst.setApplyUserName(LoginHelper.getNickName());
|
||||
inst.setApplyTime(new Date());
|
||||
inst.setApproverIds(cfg.getApproverIds());
|
||||
inst.setSignType(cfg.getSignType());
|
||||
inst.setStatus(0);
|
||||
instanceMapper.insert(inst);
|
||||
return inst.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean act(OaApprovalActionBo bo) {
|
||||
OaApprovalInstance inst = instanceMapper.selectById(bo.getInstanceId());
|
||||
if (inst == null) throw new ServiceException("审批单不存在");
|
||||
if (inst.getStatus() == null || inst.getStatus() != 0) throw new ServiceException("审批已终结");
|
||||
|
||||
Long me = LoginHelper.getUserId();
|
||||
Set<Long> approvers = parseIds(inst.getApproverIds());
|
||||
if (!approvers.contains(me)) throw new ServiceException("无审批权限");
|
||||
|
||||
// 防重:同一人对同一单只能操作一次
|
||||
Long already = recordMapper.selectCount(
|
||||
Wrappers.<OaApprovalRecord>lambdaQuery()
|
||||
.eq(OaApprovalRecord::getInstanceId, inst.getId())
|
||||
.eq(OaApprovalRecord::getApproverId, me));
|
||||
if (already != null && already > 0) throw new ServiceException("您已审批过");
|
||||
|
||||
OaApprovalRecord rec = new OaApprovalRecord();
|
||||
rec.setInstanceId(inst.getId());
|
||||
rec.setApproverId(me);
|
||||
rec.setApproverName(LoginHelper.getNickName());
|
||||
rec.setAction(bo.getAction());
|
||||
rec.setComment(bo.getComment());
|
||||
rec.setOpTime(new Date());
|
||||
recordMapper.insert(rec);
|
||||
|
||||
// 终结判定
|
||||
int finalStatus = decideFinalStatus(inst, bo.getAction(), me, approvers);
|
||||
if (finalStatus != 0) {
|
||||
inst.setStatus(finalStatus);
|
||||
inst.setFinishTime(new Date());
|
||||
instanceMapper.updateById(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int decideFinalStatus(OaApprovalInstance inst, Integer action, Long me, Set<Long> approvers) {
|
||||
// 驳回:无论或签会签都立即驳回
|
||||
if (action != null && action == 2) return 2;
|
||||
// 通过场景
|
||||
if (inst.getSignType() != null && inst.getSignType() == 1) {
|
||||
// 或签:任一人通过即通过
|
||||
return 1;
|
||||
}
|
||||
// 会签:所有人都通过才通过
|
||||
Long passCount = recordMapper.selectCount(
|
||||
Wrappers.<OaApprovalRecord>lambdaQuery()
|
||||
.eq(OaApprovalRecord::getInstanceId, inst.getId())
|
||||
.eq(OaApprovalRecord::getAction, 1));
|
||||
return passCount != null && passCount >= approvers.size() ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean withdraw(Long instanceId) {
|
||||
OaApprovalInstance inst = instanceMapper.selectById(instanceId);
|
||||
if (inst == null) throw new ServiceException("审批单不存在");
|
||||
if (inst.getStatus() == null || inst.getStatus() != 0) throw new ServiceException("已终结,无法撤回");
|
||||
if (!Objects.equals(inst.getApplyUserId(), LoginHelper.getUserId())) throw new ServiceException("仅申请人可撤回");
|
||||
inst.setStatus(3);
|
||||
inst.setFinishTime(new Date());
|
||||
return instanceMapper.updateById(inst) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OaApprovalInstanceVo getLatestByBusiness(String businessType, Long businessId) {
|
||||
OaApprovalInstance inst = instanceMapper.selectOne(
|
||||
Wrappers.<OaApprovalInstance>lambdaQuery()
|
||||
.eq(OaApprovalInstance::getBusinessType, businessType)
|
||||
.eq(OaApprovalInstance::getBusinessId, businessId)
|
||||
.orderByDesc(OaApprovalInstance::getId)
|
||||
.last("LIMIT 1"));
|
||||
if (inst == null) return null;
|
||||
OaApprovalInstanceVo vo = toInstanceVo(inst);
|
||||
vo.setRecords(loadRecords(inst.getId()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Integer> batchLatestStatus(String businessType, Collection<Long> businessIds) {
|
||||
if (businessIds == null || businessIds.isEmpty()) return Collections.emptyMap();
|
||||
List<OaApprovalInstance> list = instanceMapper.selectList(
|
||||
Wrappers.<OaApprovalInstance>lambdaQuery()
|
||||
.eq(OaApprovalInstance::getBusinessType, businessType)
|
||||
.in(OaApprovalInstance::getBusinessId, businessIds)
|
||||
.orderByAsc(OaApprovalInstance::getId));
|
||||
// 后面的覆盖前面的,得到最新
|
||||
Map<Long, Integer> result = new HashMap<>();
|
||||
for (OaApprovalInstance i : list) {
|
||||
result.put(i.getBusinessId(), i.getStatus());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===================== 列表 =====================
|
||||
|
||||
@Override
|
||||
public TableDataInfo<OaApprovalInstanceVo> pageMyPending(PageQuery pageQuery, String businessType) {
|
||||
Long me = LoginHelper.getUserId();
|
||||
LambdaQueryWrapper<OaApprovalInstance> qw = Wrappers.<OaApprovalInstance>lambdaQuery()
|
||||
.eq(OaApprovalInstance::getStatus, 0)
|
||||
.and(w -> w.like(OaApprovalInstance::getApproverIds, "," + me + ",")
|
||||
.or().likeRight(OaApprovalInstance::getApproverIds, me + ",")
|
||||
.or().likeLeft(OaApprovalInstance::getApproverIds, "," + me)
|
||||
.or().eq(OaApprovalInstance::getApproverIds, String.valueOf(me)))
|
||||
.eq(StringUtils.isNotBlank(businessType), OaApprovalInstance::getBusinessType, businessType)
|
||||
.orderByDesc(OaApprovalInstance::getId);
|
||||
Page<OaApprovalInstance> page = instanceMapper.selectPage(pageQuery.build(), qw);
|
||||
return buildInstancePage(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<OaApprovalInstanceVo> pageMyDone(PageQuery pageQuery, String businessType) {
|
||||
Long me = LoginHelper.getUserId();
|
||||
List<Long> instIds = recordMapper.selectObjs(
|
||||
Wrappers.<OaApprovalRecord>lambdaQuery()
|
||||
.select(OaApprovalRecord::getInstanceId)
|
||||
.eq(OaApprovalRecord::getApproverId, me))
|
||||
.stream().filter(Objects::nonNull).map(o -> Long.parseLong(o.toString())).distinct().collect(Collectors.toList());
|
||||
if (instIds.isEmpty()) return TableDataInfo.build(new ArrayList<>());
|
||||
LambdaQueryWrapper<OaApprovalInstance> qw = Wrappers.<OaApprovalInstance>lambdaQuery()
|
||||
.in(OaApprovalInstance::getId, instIds)
|
||||
.eq(StringUtils.isNotBlank(businessType), OaApprovalInstance::getBusinessType, businessType)
|
||||
.orderByDesc(OaApprovalInstance::getId);
|
||||
Page<OaApprovalInstance> page = instanceMapper.selectPage(pageQuery.build(), qw);
|
||||
return buildInstancePage(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<OaApprovalInstanceVo> pageMySubmitted(PageQuery pageQuery, String businessType) {
|
||||
Long me = LoginHelper.getUserId();
|
||||
LambdaQueryWrapper<OaApprovalInstance> qw = Wrappers.<OaApprovalInstance>lambdaQuery()
|
||||
.eq(OaApprovalInstance::getApplyUserId, me)
|
||||
.eq(StringUtils.isNotBlank(businessType), OaApprovalInstance::getBusinessType, businessType)
|
||||
.orderByDesc(OaApprovalInstance::getId);
|
||||
Page<OaApprovalInstance> page = instanceMapper.selectPage(pageQuery.build(), qw);
|
||||
return buildInstancePage(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<OaApprovalInstanceVo> pageAll(PageQuery pageQuery, String businessType, Integer status) {
|
||||
LambdaQueryWrapper<OaApprovalInstance> qw = Wrappers.<OaApprovalInstance>lambdaQuery()
|
||||
.eq(StringUtils.isNotBlank(businessType), OaApprovalInstance::getBusinessType, businessType)
|
||||
.eq(status != null, OaApprovalInstance::getStatus, status)
|
||||
.orderByDesc(OaApprovalInstance::getId);
|
||||
Page<OaApprovalInstance> page = instanceMapper.selectPage(pageQuery.build(), qw);
|
||||
return buildInstancePage(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OaApprovalInstanceVo getDetail(Long instanceId) {
|
||||
OaApprovalInstance inst = instanceMapper.selectById(instanceId);
|
||||
if (inst == null) return null;
|
||||
OaApprovalInstanceVo vo = toInstanceVo(inst);
|
||||
vo.setRecords(loadRecords(instanceId));
|
||||
return vo;
|
||||
}
|
||||
|
||||
// ===================== 内部 =====================
|
||||
|
||||
private TableDataInfo<OaApprovalInstanceVo> buildInstancePage(Page<OaApprovalInstance> page) {
|
||||
List<OaApprovalInstanceVo> vos = page.getRecords().stream().map(this::toInstanceVo).collect(Collectors.toList());
|
||||
Page<OaApprovalInstanceVo> p = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||
p.setRecords(vos);
|
||||
return TableDataInfo.build(p);
|
||||
}
|
||||
|
||||
private OaApprovalInstanceVo toInstanceVo(OaApprovalInstance inst) {
|
||||
OaApprovalInstanceVo vo = BeanUtil.toBean(inst, OaApprovalInstanceVo.class);
|
||||
vo.setApproverNames(resolveNames(inst.getApproverIds()));
|
||||
OaApprovalConfig cfg = configMapper.selectOne(
|
||||
Wrappers.<OaApprovalConfig>lambdaQuery().eq(OaApprovalConfig::getBusinessType, inst.getBusinessType()));
|
||||
if (cfg != null) vo.setBusinessName(cfg.getBusinessName());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private List<OaApprovalRecordVo> loadRecords(Long instanceId) {
|
||||
return recordMapper.selectVoList(
|
||||
Wrappers.<OaApprovalRecord>lambdaQuery()
|
||||
.eq(OaApprovalRecord::getInstanceId, instanceId)
|
||||
.orderByAsc(OaApprovalRecord::getOpTime));
|
||||
}
|
||||
|
||||
private void fillApproverNames(OaApprovalConfigVo vo) {
|
||||
vo.setApproverNames(resolveNames(vo.getApproverIds()));
|
||||
}
|
||||
|
||||
private String resolveNames(String csvIds) {
|
||||
Set<Long> ids = parseIds(csvIds);
|
||||
if (ids.isEmpty()) return "";
|
||||
List<String> names = new ArrayList<>();
|
||||
for (Long id : ids) {
|
||||
try {
|
||||
SysUser u = userService.selectUserById(id);
|
||||
names.add(u != null ? u.getNickName() : String.valueOf(id));
|
||||
} catch (Exception e) {
|
||||
names.add(String.valueOf(id));
|
||||
}
|
||||
}
|
||||
return String.join(",", names);
|
||||
}
|
||||
|
||||
private Set<Long> parseIds(String csv) {
|
||||
if (StringUtils.isBlank(csv)) return Collections.emptySet();
|
||||
Set<Long> set = new LinkedHashSet<>();
|
||||
for (String s : csv.split(",")) {
|
||||
String t = s.trim();
|
||||
if (t.isEmpty()) continue;
|
||||
try { set.add(Long.parseLong(t)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
69
sql/oa_approval.sql
Normal file
69
sql/oa_approval.sql
Normal file
@@ -0,0 +1,69 @@
|
||||
-- ============================================================
|
||||
-- 通用审批框架(轻量自建)
|
||||
-- 1. oa_approval_config 业务-审批人配置(可改)
|
||||
-- 2. oa_approval_instance 审批单实例(一次提交一条)
|
||||
-- 3. oa_approval_record 审批操作流水(一人一条)
|
||||
-- ============================================================
|
||||
|
||||
DROP TABLE IF EXISTS oa_approval_record;
|
||||
DROP TABLE IF EXISTS oa_approval_instance;
|
||||
DROP TABLE IF EXISTS oa_approval_config;
|
||||
|
||||
CREATE TABLE oa_approval_config (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
business_type VARCHAR(64) NOT NULL COMMENT '业务类型 key(如 purchase_req/contract)',
|
||||
business_name VARCHAR(128) NOT NULL COMMENT '业务名称(展示用)',
|
||||
approver_ids VARCHAR(512) NOT NULL COMMENT '审批人 sys_user.user_id 列表,逗号分隔',
|
||||
sign_type TINYINT NOT NULL DEFAULT 1 COMMENT '1或签 2会签',
|
||||
enabled TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用:0停用 1启用',
|
||||
remark VARCHAR(255) DEFAULT NULL COMMENT '备注',
|
||||
create_by VARCHAR(64) DEFAULT NULL,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64) DEFAULT NULL,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
del_flag TINYINT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_biz_type (business_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='业务审批配置';
|
||||
|
||||
CREATE TABLE oa_approval_instance (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
business_type VARCHAR(64) NOT NULL COMMENT '业务类型 key',
|
||||
business_id BIGINT NOT NULL COMMENT '业务表主键',
|
||||
business_title VARCHAR(255) DEFAULT NULL COMMENT '业务标题(冗余展示)',
|
||||
apply_user_id BIGINT NOT NULL COMMENT '申请人 user_id',
|
||||
apply_user_name VARCHAR(64) DEFAULT NULL,
|
||||
apply_time DATETIME NOT NULL,
|
||||
approver_ids VARCHAR(512) NOT NULL COMMENT '快照:提交时的审批人',
|
||||
sign_type TINYINT NOT NULL COMMENT '快照:1或签 2会签',
|
||||
status TINYINT NOT NULL DEFAULT 0 COMMENT '0待审 1通过 2驳回 3撤回',
|
||||
finish_time DATETIME DEFAULT NULL,
|
||||
remark VARCHAR(255) DEFAULT NULL,
|
||||
create_by VARCHAR(64) DEFAULT NULL,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64) DEFAULT NULL,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
del_flag TINYINT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_biz (business_type, business_id),
|
||||
KEY idx_status (status),
|
||||
KEY idx_apply_user (apply_user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审批单实例';
|
||||
|
||||
CREATE TABLE oa_approval_record (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
instance_id BIGINT NOT NULL COMMENT '审批单 id',
|
||||
approver_id BIGINT NOT NULL COMMENT '审批人 user_id',
|
||||
approver_name VARCHAR(64) DEFAULT NULL,
|
||||
action TINYINT NOT NULL COMMENT '1通过 2驳回',
|
||||
comment VARCHAR(512) DEFAULT NULL,
|
||||
op_time DATETIME NOT NULL,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_inst (instance_id),
|
||||
KEY idx_approver (approver_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审批操作流水';
|
||||
|
||||
-- 采购需求 默认配置(先用 admin=1 占位,部署后改)
|
||||
INSERT INTO oa_approval_config (business_type, business_name, approver_ids, sign_type, enabled, remark)
|
||||
VALUES ('purchase_req', '采购需求', '1', 1, 1, '默认或签,请在「审批配置」修改审批人');
|
||||
Reference in New Issue
Block a user