云
2026-09-29 a0d62d13f1d537e84e13d8ee4d8b37b27439fa00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package cn.iocoder.yudao.module.finance.api.reimbursement;
 
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementCreateReqDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementRespDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementUpdateReqDTO;
import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.vo.FinanceReimbursementSaveReqVO;
import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.vo.item.FinanceReimbursementItemSaveReqVO;
import cn.iocoder.yudao.module.finance.dal.dataobject.reimbursement.FinanceReimbursementDO;
import cn.iocoder.yudao.module.finance.service.reimbursement.FinanceReimbursementService;
import jakarta.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.finance.enums.ErrorCodeConstants.*;
 
/**
 * 财务报销单 API 实现类
 *
 * 供其他模块(课题管理经费拨付)以「自动建单 + 自动提交审批」的方式落一张财务报销单,
 * 明细由系统固定生成 1 行(费用类型=其他),发票金额取报销总金额。
 *
 * @author 超级管理员
 */
@RestController
@RequestMapping(FinanceReimbursementApi.PREFIX)
@Validated
public class FinanceReimbursementApiImpl implements FinanceReimbursementApi {
 
    /** 费用类型:其他(字典 finance_expense_type:1-差旅 2-办公 3-招待 4-交通 5-其他) */
    private static final Integer EXPENSE_TYPE_OTHER = 5;
 
    @Resource
    private FinanceReimbursementService reimbursementService;
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Long> createReimbursementAndSubmit(FinanceReimbursementCreateReqDTO reqDTO) {
        // 1. 先解析可用的报销审批流程:没有已发布流程时直接失败,避免留下无法提交的孤儿草稿单
        String processDefinitionKey = resolveApproveProcessDefinitionKey();
 
        // 2. 创建报销单(1 行明细)
        FinanceReimbursementSaveReqVO createReqVO = buildSaveReqVO(null, reqDTO.getTitle(),
                reqDTO.getTotalAmount(), reqDTO.getApplyDate(), reqDTO.getRemark(),
                reqDTO.getBlobIds(), reqDTO.getExpenseDescription());
        Long reimbursementId = reimbursementService.createReimbursement(createReqVO);
 
        // 3. 立即提交审批
        Long applicantId = reqDTO.getApplicantId() != null ? reqDTO.getApplicantId() : getLoginUserId();
        reimbursementService.submitReimbursement(reimbursementId, processDefinitionKey, applicantId);
 
        return success(reimbursementId);
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Boolean> updateReimbursementAndResubmit(FinanceReimbursementUpdateReqDTO reqDTO) {
        // 1. 先解析可用的报销审批流程
        String processDefinitionKey = resolveApproveProcessDefinitionKey();
 
        // 2. 修改报销单(明细重建为 1 行)
        FinanceReimbursementSaveReqVO updateReqVO = buildSaveReqVO(reqDTO.getReimbursementId(),
                reqDTO.getTitle(), reqDTO.getTotalAmount(), reqDTO.getApplyDate(), reqDTO.getRemark(),
                reqDTO.getBlobIds(), reqDTO.getExpenseDescription());
        reimbursementService.updateReimbursement(updateReqVO);
 
        // 3. 重新提交审批
        reimbursementService.submitReimbursement(reqDTO.getReimbursementId(), processDefinitionKey, getLoginUserId());
 
        return success(true);
    }
 
    @Override
    public CommonResult<Boolean> deleteReimbursement(Long reimbursementId) {
        reimbursementService.deleteReimbursement(reimbursementId);
        return success(true);
    }
 
    @Override
    public CommonResult<FinanceReimbursementRespDTO> getReimbursement(Long reimbursementId) {
        // 报销单不存在时返回 null,由调用方决定如何处理(课题侧据此容忍被直接删除的报销单)
        FinanceReimbursementDO reimbursement = reimbursementService.getReimbursement(reimbursementId);
        return success(BeanUtils.toBean(reimbursement, FinanceReimbursementRespDTO.class));
    }
 
    // ==================== 内部方法 ====================
 
    /**
     * 解析报销审批分类下可用的流程定义 key;分类下没有已发布的流程时抛出可执行的精确提示
     */
    private String resolveApproveProcessDefinitionKey() {
        List<Map<String, Object>> definitionList = reimbursementService.getReimbursementApproveProcessDefinitionList();
        if (CollUtil.isEmpty(definitionList)) {
            throw exception(REIMBURSEMENT_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        return String.valueOf(definitionList.get(0).get("key"));
    }
 
    /** 组装报销单保存 VO:明细固定 1 行,费用类型「其他」,发票金额=报销总金额 */
    private FinanceReimbursementSaveReqVO buildSaveReqVO(Long id, String title, BigDecimal totalAmount,
                                                         LocalDate applyDate, String remark, List<Long> blobIds,
                                                         String expenseDescription) {
        FinanceReimbursementItemSaveReqVO item = new FinanceReimbursementItemSaveReqVO();
        item.setExpenseType(EXPENSE_TYPE_OTHER);
        item.setInvoiceAmount(totalAmount);
        item.setInvoiceDate(applyDate);
        item.setDescription(expenseDescription);
 
        FinanceReimbursementSaveReqVO reqVO = new FinanceReimbursementSaveReqVO();
        reqVO.setId(id);
        reqVO.setTitle(title);
        reqVO.setTotalAmount(totalAmount);
        reqVO.setApplyDate(applyDate != null ? applyDate : LocalDate.now());
        reqVO.setRemark(remark);
        reqVO.setBlobIds(blobIds);
        reqVO.setItems(Collections.singletonList(item));
        return reqVO;
    }
 
}