feat(hrm): 新增拨款申请功能模块

- 创建拨款申请实体类 HrmAppropriationReq 包含业务字段和审计字段
- 实现拨款申请控制器提供增删改查和分页查询接口
- 开发拨款申请服务层实现业务逻辑和流程实例启动功能
- 设计拨款申请数据传输对象包括请求体、视图对象和业务对象
- 配置MyBatis映射文件实现数据库操作和项目关联查询
- 添加流程模板集成支持拨款申请的审批流程管理
- 实现手动审批人指定和自动模板匹配的审批启动机制
This commit is contained in:
2026-02-26 09:59:44 +08:00
parent b62d836734
commit ce65595d40
8 changed files with 640 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
package com.ruoyi.hrm.controller;
import com.ruoyi.common.annotation.Log;
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.common.enums.BusinessType;
import com.ruoyi.hrm.domain.bo.HrmAppropriationReqBo;
import com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo;
import com.ruoyi.hrm.service.IHrmAppropriationReqService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 拨款申请
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/hrm/appropriation")
public class HrmAppropriationReqController extends BaseController {
private final IHrmAppropriationReqService service;
@GetMapping("/list")
public TableDataInfo<HrmAppropriationReqVo> list(HrmAppropriationReqBo bo, PageQuery pageQuery) {
return service.queryPageList(bo, pageQuery);
}
@GetMapping("/{bizId}")
public R<HrmAppropriationReqVo> getInfo(@PathVariable @NotNull Long bizId) {
return R.ok(service.queryById(bizId));
}
@Log(title = "拨款单", businessType = BusinessType.INSERT)
@PostMapping
public R<HrmAppropriationReqVo> add(@Validated @RequestBody HrmAppropriationReqBo bo) {
return R.ok(service.insertByBo(bo));
}
@Log(title = "拨款单", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody HrmAppropriationReqBo bo) {
return toAjax(service.updateByBo(bo));
}
@Log(title = "拨款单", businessType = BusinessType.DELETE)
@DeleteMapping("/{bizIds}")
public R<Void> remove(@PathVariable @NotEmpty Long[] bizIds) {
return toAjax(service.deleteWithValidByIds(Arrays.asList(bizIds), true));
}
@GetMapping("/all")
public R<List<HrmAppropriationReqVo>> all(HrmAppropriationReqBo bo) {
return R.ok(service.queryList(bo));
}
}

View File

@@ -0,0 +1,64 @@
package com.ruoyi.hrm.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
* 拨款申请
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hrm_appropriation_req")
public class HrmAppropriationReq extends BaseEntity {
/** 业务ID */
@TableId
private Long bizId;
/** 申请人ID */
private Long empId;
/** 项目ID */
private Long projectId;
/** 拨款类型(预付款/进度款/尾款等) */
private String appropriationType;
/** 拨款金额 */
private BigDecimal amount;
/** 收款方名称 */
private String payeeName;
/** 收款银行 */
private String bankName;
/** 收款账号 */
private String bankAccount;
/** 拨款事由 */
private String reason;
/** 状态 draft/pending/approved/rejected/canceled */
private String status;
/** 申请附件oss_id列表(CSV) */
private String accessoryApplyIds;
/** 回执附件oss_id列表(CSV) */
private String accessoryReceiptIds;
/** 备注 */
private String remark;
/** 删除标识 0正常 2删除 */
@TableLogic
private Integer delFlag;
}

View File

@@ -0,0 +1,63 @@
package com.ruoyi.hrm.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 拨款申请 Bo
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class HrmAppropriationReqBo extends BaseEntity {
/** 业务ID编辑时必填 */
private Long bizId;
/** 申请人ID */
@NotNull(message = "申请人不能为空")
private Long empId;
/** 项目ID */
private Long projectId;
/** 拨款类型(预付款/进度款/尾款等) */
private String appropriationType;
/** 拨款金额 */
@NotNull(message = "拨款金额不能为空")
private BigDecimal amount;
/** 收款方名称 */
private String payeeName;
/** 收款银行 */
private String bankName;
/** 收款账号 */
private String bankAccount;
/** 拨款事由 */
private String reason;
/** 状态 draft/pending/approved/rejected/canceled */
private String status;
/** 无模板时,自选审批人 userId */
private Long manualAssigneeUserId;
/** 申请附件oss_id列表(CSV) */
private String accessoryApplyIds;
/** 回执附件oss_id列表(CSV) */
private String accessoryReceiptIds;
/** 备注 */
private String remark;
private Long tplId;
}

View File

@@ -0,0 +1,112 @@
package com.ruoyi.hrm.domain.vo;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 拨款申请 VO
*/
@Data
public class HrmAppropriationReqVo implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "业务ID")
private Long bizId;
@Excel(name = "申请人ID")
private Long empId;
@Excel(name = "项目ID")
private Long projectId;
@Excel(name = "项目代号")
private String projectCode;
@Excel(name = "项目名称")
private String projectName;
@Excel(name = "项目编号")
private String projectNum;
@Excel(name = "项目类型")
private String projectType;
@Excel(name = "项目地址")
private String address;
@Excel(name = "项目总款")
private BigDecimal funds;
@Excel(name = "项目负责人")
private String functionary;
@Excel(name = "开始日期")
private Date beginTime;
@Excel(name = "结束日期")
private Date finishTime;
@Excel(name = "交货期")
private String delivery;
@Excel(name = "质保期")
private String guarantee;
@Excel(name = "优先级")
private String projectGrade;
@Excel(name = "状态")
private String projectStatus;
@Excel(name = "生产结项状态")
private Integer productStatus;
@Excel(name = "项目代表色")
private String color;
@Excel(name = "客户ID")
private Long customerId;
@Excel(name = "拨款类型")
private String appropriationType;
@Excel(name = "拨款金额")
private BigDecimal amount;
@Excel(name = "收款方名称")
private String payeeName;
@Excel(name = "收款银行")
private String bankName;
@Excel(name = "收款账号")
private String bankAccount;
@Excel(name = "拨款事由")
private String reason;
@Excel(name = "状态")
private String status;
@Excel(name = "申请附件")
private String accessoryApplyIds;
@Excel(name = "回执附件")
private String accessoryReceiptIds;
@Excel(name = "备注")
private String remark;
private String createBy;
private Date createTime;
private String updateBy;
private Date updateTime;
/** 流程实例ID */
private Long instId;
}

View File

@@ -0,0 +1,23 @@
package com.ruoyi.hrm.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.hrm.domain.HrmAppropriationReq;
import com.ruoyi.hrm.domain.bo.HrmAppropriationReqBo;
import com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 拨款申请 Mapper
*/
public interface HrmAppropriationReqMapper extends BaseMapperPlus<HrmAppropriationReqMapper, HrmAppropriationReq, HrmAppropriationReqVo> {
HrmAppropriationReqVo selectVoWithProjectById(@Param("bizId") Long bizId);
List<HrmAppropriationReqVo> selectVoWithProjectByPage(IPage<HrmAppropriationReqVo> page, @Param("bo") HrmAppropriationReqBo bo);
List<HrmAppropriationReqVo> selectVoWithProjectList(@Param("bo") HrmAppropriationReqBo bo);
}

View File

@@ -0,0 +1,31 @@
package com.ruoyi.hrm.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.hrm.domain.bo.HrmAppropriationReqBo;
import com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo;
import java.util.Collection;
import java.util.List;
/**
* 拨款申请 Service
*/
public interface IHrmAppropriationReqService {
HrmAppropriationReqVo queryById(Long bizId);
TableDataInfo<HrmAppropriationReqVo> queryPageList(HrmAppropriationReqBo bo, PageQuery pageQuery);
List<HrmAppropriationReqVo> queryList(HrmAppropriationReqBo bo);
List<HrmAppropriationReqVo> queryListWithProject(HrmAppropriationReqBo bo);
HrmAppropriationReqVo insertByBo(HrmAppropriationReqBo bo);
Boolean updateByBo(HrmAppropriationReqBo bo);
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -0,0 +1,134 @@
package com.ruoyi.hrm.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.helper.LoginHelper;
import com.ruoyi.hrm.domain.HrmAppropriationReq;
import com.ruoyi.hrm.domain.HrmFlowTemplate;
import com.ruoyi.hrm.domain.bo.HrmAppropriationReqBo;
import com.ruoyi.hrm.domain.bo.HrmFlowStartBo;
import com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo;
import com.ruoyi.hrm.mapper.HrmAppropriationReqMapper;
import com.ruoyi.hrm.mapper.HrmFlowTemplateMapper;
import com.ruoyi.hrm.service.IHrmAppropriationReqService;
import com.ruoyi.hrm.service.IHrmFlowInstanceService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
* 拨款申请 Service 实现
*/
@RequiredArgsConstructor
@Service
public class HrmAppropriationReqServiceImpl implements IHrmAppropriationReqService {
private final HrmAppropriationReqMapper baseMapper;
private final HrmFlowTemplateMapper flowTemplateMapper;
private final IHrmFlowInstanceService flowInstanceService;
@Override
public HrmAppropriationReqVo queryById(Long bizId) {
return baseMapper.selectVoWithProjectById(bizId);
}
@Override
public TableDataInfo<HrmAppropriationReqVo> queryPageList(HrmAppropriationReqBo bo, PageQuery pageQuery) {
Page<HrmAppropriationReqVo> result = pageQuery.build();
result.setRecords(baseMapper.selectVoWithProjectByPage(result, bo));
return TableDataInfo.build(result);
}
@Override
public List<HrmAppropriationReqVo> queryList(HrmAppropriationReqBo bo) {
return baseMapper.selectVoWithProjectList(bo);
}
@Override
public List<HrmAppropriationReqVo> queryListWithProject(HrmAppropriationReqBo bo) {
return baseMapper.selectVoWithProjectList(bo);
}
@Override
@Transactional(rollbackFor = Exception.class)
public HrmAppropriationReqVo insertByBo(HrmAppropriationReqBo bo) {
HrmAppropriationReq add = BeanUtil.toBean(bo, HrmAppropriationReq.class);
add.setStatus(defaultStatus(add.getStatus()));
boolean ok = baseMapper.insert(add) > 0;
HrmAppropriationReqVo bean = BeanUtil.toBean(add, HrmAppropriationReqVo.class);
if (ok && "pending".equalsIgnoreCase(add.getStatus())) {
// 获取流程启动人ID
Long startUserId = LoginHelper.getUserId();
HrmFlowTemplate tpl = null;
// 1) 优先前端明确选择了模板tplId 不为空)
if (bo.getTplId() != null) {
tpl = flowTemplateMapper.selectOne(Wrappers.<HrmFlowTemplate>lambdaQuery()
.eq(HrmFlowTemplate::getTplId, bo.getTplId())
.eq(HrmFlowTemplate::getBizType, "appropriation")
.eq(HrmFlowTemplate::getEnabled, 1)
.last("limit 1"));
}
// 2) 手动审批:前端选择了手动审批人时,不允许兜底去找模板,否则会意外走到模板里的规则(例如 dept_leader
boolean manualMode = bo.getTplId() == null && bo.getManualAssigneeUserId() != null;
// 3) 兜底:只有在既没有 tplId、也没有手动审批人的情况下才自动选择最新启用模板
if (!manualMode && tpl == null) {
tpl = flowTemplateMapper.selectOne(Wrappers.<HrmFlowTemplate>lambdaQuery()
.eq(HrmFlowTemplate::getBizType, "appropriation")
.eq(HrmFlowTemplate::getEnabled, 1)
.orderByDesc(HrmFlowTemplate::getVersion)
.last("limit 1"));
}
HrmFlowStartBo start = new HrmFlowStartBo();
if (tpl != null) {
start.setTplId(tpl.getTplId());
}
start.setManualAssigneeUserId(bo.getManualAssigneeUserId());
start.setBizType("appropriation");
start.setBizId(add.getBizId());
start.setStartUserId(startUserId);
Long instId = flowInstanceService.startInstance(start);
bean.setInstId(instId);
}
return bean;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(HrmAppropriationReqBo bo) {
HrmAppropriationReq update = BeanUtil.toBean(bo, HrmAppropriationReq.class);
return baseMapper.updateById(update) > 0;
}
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
return baseMapper.deleteBatchIds(ids) > 0;
}
@SuppressWarnings("unused")
private LambdaQueryWrapper<HrmAppropriationReq> buildQueryWrapper(HrmAppropriationReqBo bo) {
LambdaQueryWrapper<HrmAppropriationReq> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getBizId() != null, HrmAppropriationReq::getBizId, bo.getBizId());
lqw.eq(bo.getEmpId() != null, HrmAppropriationReq::getEmpId, bo.getEmpId());
lqw.eq(bo.getProjectId() != null, HrmAppropriationReq::getProjectId, bo.getProjectId());
lqw.eq(bo.getAppropriationType() != null, HrmAppropriationReq::getAppropriationType, bo.getAppropriationType());
lqw.eq(bo.getStatus() != null, HrmAppropriationReq::getStatus, bo.getStatus());
return lqw;
}
private String defaultStatus(String status) {
return status == null ? "draft" : status;
}
}

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.hrm.mapper.HrmAppropriationReqMapper">
<resultMap id="BaseResultMap" type="com.ruoyi.hrm.domain.HrmAppropriationReq">
<id column="biz_id" property="bizId"/>
<result column="emp_id" property="empId"/>
<result column="project_id" property="projectId"/>
<result column="appropriation_type" property="appropriationType"/>
<result column="amount" property="amount"/>
<result column="payee_name" property="payeeName"/>
<result column="bank_name" property="bankName"/>
<result column="bank_account" property="bankAccount"/>
<result column="reason" property="reason"/>
<result column="status" property="status"/>
<result column="accessory_apply_ids" property="accessoryApplyIds"/>
<result column="accessory_receipt_ids" property="accessoryReceiptIds"/>
<result column="remark" property="remark"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<resultMap id="VoResultMap" type="com.ruoyi.hrm.domain.vo.HrmAppropriationReqVo" extends="BaseResultMap">
<result column="project_code" property="projectCode"/>
<result column="project_name" property="projectName"/>
<result column="project_num" property="projectNum"/>
<result column="project_type" property="projectType"/>
<result column="address" property="address"/>
<result column="funds" property="funds"/>
<result column="functionary" property="functionary"/>
<result column="begin_time" property="beginTime"/>
<result column="finish_time" property="finishTime"/>
<result column="delivery" property="delivery"/>
<result column="guarantee" property="guarantee"/>
<result column="project_grade" property="projectGrade"/>
<result column="project_status" property="projectStatus"/>
<result column="product_status" property="productStatus"/>
<result column="color" property="color"/>
<result column="customerId" property="customerId"/>
</resultMap>
<select id="selectVoWithProjectById" resultMap="VoResultMap">
SELECT
a.*,
p.project_code,
p.project_name,
p.project_num,
p.project_type,
p.address,
p.funds,
p.functionary,
p.begin_time,
p.finish_time,
p.delivery,
p.guarantee,
p.project_grade,
p.project_status,
p.product_status,
p.color,
p.customer_id AS customerId
FROM hrm_appropriation_req a
LEFT JOIN sys_oa_project p ON a.project_id = p.project_id
WHERE a.biz_id = #{bizId}
AND a.del_flag = 0
</select>
<select id="selectVoWithProjectByPage" resultMap="VoResultMap">
SELECT
a.*,
p.project_code,
p.project_name,
p.project_num,
p.project_type,
p.address,
p.funds,
p.functionary,
p.begin_time,
p.finish_time,
p.delivery,
p.guarantee,
p.project_grade,
p.project_status,
p.product_status,
p.color,
p.customer_id AS customerId
FROM hrm_appropriation_req a
LEFT JOIN sys_oa_project p ON a.project_id = p.project_id
WHERE a.del_flag = 0
<if test="bo.bizId != null">
AND a.biz_id = #{bo.bizId}
</if>
<if test="bo.empId != null">
AND a.emp_id = #{bo.empId}
</if>
<if test="bo.projectId != null">
AND a.project_id = #{bo.projectId}
</if>
<if test="bo.appropriationType != null and bo.appropriationType != ''">
AND a.appropriation_type = #{bo.appropriationType}
</if>
<if test="bo.status != null and bo.status != ''">
AND a.status = #{bo.status}
</if>
ORDER BY a.create_time DESC
</select>
<select id="selectVoWithProjectList" resultMap="VoResultMap">
SELECT
a.*,
p.project_code,
p.project_name,
p.project_num,
p.project_type,
p.address,
p.funds,
p.functionary,
p.begin_time,
p.finish_time,
p.delivery,
p.guarantee,
p.project_grade,
p.project_status,
p.product_status,
p.color,
p.customer_id AS customerId
FROM hrm_appropriation_req a
LEFT JOIN sys_oa_project p ON a.project_id = p.project_id
WHERE a.del_flag = 0
<if test="bo.bizId != null">
AND a.biz_id = #{bo.bizId}
</if>
<if test="bo.empId != null">
AND a.emp_id = #{bo.empId}
</if>
<if test="bo.projectId != null">
AND a.project_id = #{bo.projectId}
</if>
<if test="bo.appropriationType != null and bo.appropriationType != ''">
AND a.appropriation_type = #{bo.appropriationType}
</if>
<if test="bo.status != null and bo.status != ''">
AND a.status = #{bo.status}
</if>
ORDER BY a.create_time DESC
</select>
</mapper>