From 620bb4712a31791231c4381581f0f60088f079fe Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 27 五月 2026 14:03:45 +0800
Subject: [PATCH] Merge branch 'refs/heads/dev_New_pro' into dev_宁夏_英泽防锈
---
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java | 544 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 544 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java
new file mode 100644
index 0000000..305ea9d
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java
@@ -0,0 +1,544 @@
+package com.ruoyi.approve.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeApproverDto;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeDto;
+import com.ruoyi.approve.bean.dto.FinReimbursementDto;
+import com.ruoyi.approve.bean.vo.FinReimbursementVo;
+import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
+import com.ruoyi.approve.mapper.FinReimbursementDetailMapper;
+import com.ruoyi.approve.mapper.FinReimbursementMapper;
+import com.ruoyi.approve.mapper.FinReimbursementTravelMapper;
+import com.ruoyi.approve.pojo.*;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.approve.service.*;
+import com.ruoyi.common.enums.TypeEnums;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.OrderUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.project.system.service.ISysNoticeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曚富琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:15
+ */
+@Service
+@RequiredArgsConstructor
+public class FinReimbursementServiceImpl extends ServiceImpl<FinReimbursementMapper, FinReimbursement> implements FinReimbursementService {
+
+ private static final String BILL_STATUS_DRAFT = "DRAFT";
+ private static final String BILL_STATUS_IN_APPROVAL = "IN_APPROVAL";
+ private static final String NODE_STATUS_WAITING = "WAITING";
+
+ private final ApprovalInstanceMapper approvalInstanceMapper;
+ private final ApprovalInstanceService approvalInstanceService;
+ private final ApprovalInstanceNodeService approvalInstanceNodeService;
+ private final ApprovalTaskService approvalTaskService;
+ private final ApprovalRecordService approvalRecordService;
+ private final FinReimbursementMapper finReimbursementMapper;
+ private final FinReimbursementTravelMapper finReimbursementTravelMapper;
+ private final FinReimbursementDetailMapper finReimbursementDetailMapper;
+ private final FileUtil fileUtil;
+ private final ISysNoticeService sysNoticeService;
+ @Override
+ public IPage<FinReimbursementVo> listPage(FinReimbursementDto finReimbursementDto, Page<FinReimbursementVo> page) {
+ IPage<FinReimbursementVo> finReimbursementVoIPage = finReimbursementMapper.listPage(finReimbursementDto, page);
+ finReimbursementVoIPage.getRecords().forEach(vo -> {
+ vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_REIMBURSEMENT, vo.getId()));
+ });
+
+ return finReimbursementVoIPage;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean add(FinReimbursementDto finReimbursementDto) {
+ String billStatus = validateAddParam(finReimbursementDto);
+
+ // 鐢熸垚鎶ラ攢鍗曞彿
+ String billNo = OrderUtils.countTodayByCreateTime(finReimbursementMapper, "BXD", "bill_no", finReimbursementDto.getCreateTime() != null ? finReimbursementDto.getCreateTime() : LocalDateTime.now());
+ List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+ BigDecimal totalAmount = details.stream()
+ .map(FinReimbursementDetail::getAmount)
+ .filter(Objects::nonNull)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ FinReimbursement reimbursement = buildReimbursement(finReimbursementDto, billNo, totalAmount, billStatus);
+ // 淇濆瓨鎶ラ攢鍗曚富琛�
+ boolean saved = this.save(reimbursement);
+ if (!saved || reimbursement.getId() == null) {
+ throw new ServiceException("鏂板鎶ラ攢鍗曞け璐�");
+ }
+ Long reimbursementId = reimbursement.getId();
+
+ // 淇濆瓨宸梾鎶ラ攢鎵╁睍淇℃伅锛堟姤閿�绫诲瀷涓哄樊鏃呮姤閿�鏃讹級
+ FinReimbursementTravel travel = finReimbursementDto.getTravel();
+ if (isTravelReimbursement(finReimbursementDto.getReimbursementType())) {
+ travel.setReimbursementId(reimbursementId);
+ int travelRows = finReimbursementTravelMapper.insert(travel);
+ if (travelRows != 1) {
+ throw new ServiceException("鏂板宸梾鎶ラ攢鎵╁睍淇℃伅澶辫触");
+ }
+ }
+
+ // 淇濆瓨鎶ラ攢鍗曟槑缁�
+ for (int i = 0; i < details.size(); i++) {
+ FinReimbursementDetail detail = details.get(i);
+ detail.setId(null);
+ detail.setReimbursementId(reimbursementId);
+ detail.setRowNo(i + 1);
+ int detailRows = finReimbursementDetailMapper.insert(detail);
+ if (detailRows != 1) {
+ throw new ServiceException("鏂板鎶ラ攢鍗曟槑缁嗗け璐�");
+ }
+ }
+
+ if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+ startApproval(reimbursement, finReimbursementDto);
+ }
+ fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, reimbursementId, finReimbursementDto.getStorageBlobDTOs());
+ return true;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean update(FinReimbursementDto finReimbursementDto) {
+ String billStatus = validateUpdateParam(finReimbursementDto);
+
+ Long reimbursementId = finReimbursementDto.getId();
+ FinReimbursement existing = finReimbursementMapper.selectById(reimbursementId);
+ if (existing == null) {
+ throw new ServiceException("鎶ラ攢鍗曚笉瀛樺湪");
+ }
+
+ // 璁$畻鏄庣粏姹囨�婚噾棰�
+ List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+ BigDecimal totalAmount = details.stream()
+ .map(FinReimbursementDetail::getAmount)
+ .filter(Objects::nonNull)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ // 鏇存柊涓昏〃
+ FinReimbursement reimbursement = buildReimbursement(
+ finReimbursementDto,
+ existing.getBillNo(),
+ totalAmount,
+ billStatus
+ );
+ reimbursement.setId(reimbursementId);
+ int mainRows = finReimbursementMapper.updateById(reimbursement);
+ if (mainRows != 1) {
+ throw new ServiceException("鏇存柊鎶ラ攢鍗曚富琛ㄥけ璐�");
+ }
+
+ // 鏌ヨ鏁版嵁搴撲腑宸叉湁鐨勬槑缁�
+ List<FinReimbursementDetail> existingDetails = finReimbursementDetailMapper.selectList(
+ new LambdaQueryWrapper<FinReimbursementDetail>()
+ .eq(FinReimbursementDetail::getReimbursementId, reimbursementId));
+ Set<Long> existingDetailIds = existingDetails.stream()
+ .map(FinReimbursementDetail::getId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ // 鏂版槑缁嗕腑鏈塈D鐨� 鈫� 鏇存柊锛涙棤ID鐨� 鈫� 鏂板
+ Set<Long> submittedDetailIds = new HashSet<>();
+ for (int i = 0; i < details.size(); i++) {
+ FinReimbursementDetail detail = details.get(i);
+ detail.setReimbursementId(reimbursementId);
+ detail.setRowNo(i + 1);
+ if (detail.getId() != null && existingDetailIds.contains(detail.getId())) {
+ finReimbursementDetailMapper.updateById(detail);
+ submittedDetailIds.add(detail.getId());
+ } else {
+ detail.setId(null);
+ finReimbursementDetailMapper.insert(detail);
+ }
+ }
+
+ // 鏁版嵁搴撲腑宸叉湁浣嗘柊鏄庣粏涓病鏈夌殑 鈫� 鍒犻櫎
+ for (Long existingId : existingDetailIds) {
+ if (!submittedDetailIds.contains(existingId)) {
+ finReimbursementDetailMapper.deleteById(existingId);
+ }
+ }
+
+ // 宸梾鎵╁睍锛氭湁鍒欐洿鏂帮紝鏃犲垯鏂板
+ FinReimbursementTravel existingTravel = finReimbursementTravelMapper.selectOne(
+ new LambdaQueryWrapper<FinReimbursementTravel>()
+ .eq(FinReimbursementTravel::getReimbursementId, reimbursementId)
+ .last("LIMIT 1"));
+ FinReimbursementTravel travel = finReimbursementDto.getTravel();
+ if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && travel != null) {
+ travel.setReimbursementId(reimbursementId);
+ if (existingTravel != null) {
+ travel.setId(existingTravel.getId());
+ finReimbursementTravelMapper.updateById(travel);
+ } else {
+ travel.setId(null);
+ finReimbursementTravelMapper.insert(travel);
+ }
+ }
+
+ resetApprovalFlow(existing, reimbursementId);
+ if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+ reimbursement.setApprovalInstanceId(null);
+ startApproval(reimbursement, finReimbursementDto);
+ }
+ fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, reimbursementId, finReimbursementDto.getStorageBlobDTOs());
+
+ return true;
+ }
+
+ @Override
+ public FinReimbursementVo detail(Long id) {
+ if (id == null ) {
+ throw new ServiceException("鎶ラ攢鍗旾D涓嶈兘涓虹┖");
+ }
+
+ FinReimbursement reimbursement = finReimbursementMapper.selectById(id);
+ if (reimbursement == null) {
+ throw new ServiceException("鎶ラ攢鍗曚笉瀛樺湪");
+ }
+
+ FinReimbursementVo vo = new FinReimbursementVo();
+ vo.setId(reimbursement.getId());
+ vo.setBillNo(reimbursement.getBillNo());
+ vo.setReimbursementType(reimbursement.getReimbursementType());
+ vo.setExpenseType(reimbursement.getExpenseType());
+ vo.setApplicantId(reimbursement.getApplicantId());
+ vo.setApplicantCode(reimbursement.getApplicantCode());
+ vo.setApplicantName(reimbursement.getApplicantName());
+ vo.setApplicantDeptId(reimbursement.getApplicantDeptId());
+ vo.setApplicantDeptName(reimbursement.getApplicantDeptName());
+ vo.setReason(reimbursement.getReason());
+ vo.setApplyAmount(reimbursement.getApplyAmount());
+ vo.setDetailTotalAmount(reimbursement.getDetailTotalAmount());
+ vo.setPayeeName(reimbursement.getPayeeName());
+ vo.setPayeeAccount(reimbursement.getPayeeAccount());
+ vo.setPayeeBank(reimbursement.getPayeeBank());
+ vo.setApprovalInstanceId(reimbursement.getApprovalInstanceId());
+ vo.setApproveProcessId(reimbursement.getApproveProcessId());
+ vo.setBillStatus(reimbursement.getBillStatus());
+ vo.setApprovedTime(reimbursement.getApprovedTime());
+ vo.setPaidTime(reimbursement.getPaidTime());
+ vo.setAccountExpenseId(reimbursement.getAccountExpenseId());
+ vo.setRemark(reimbursement.getRemark());
+ vo.setTenantId(reimbursement.getTenantId());
+ vo.setCreateUser(reimbursement.getCreateUser());
+ vo.setCreateTime(reimbursement.getCreateTime());
+ vo.setUpdateUser(reimbursement.getUpdateUser());
+ vo.setUpdateTime(reimbursement.getUpdateTime());
+ vo.setDeptId(reimbursement.getDeptId());
+ vo.setDeleted(reimbursement.getDeleted());
+
+ vo.setDetails(finReimbursementDetailMapper.selectList(
+ new LambdaQueryWrapper<FinReimbursementDetail>()
+ .eq(FinReimbursementDetail::getReimbursementId, reimbursement.getId())
+ .orderByAsc(FinReimbursementDetail::getRowNo)
+ ));
+
+ if (isTravelReimbursement(reimbursement.getReimbursementType())) {
+ vo.setTravel(finReimbursementTravelMapper.selectOne(
+ new LambdaQueryWrapper<FinReimbursementTravel>()
+ .eq(FinReimbursementTravel::getReimbursementId, reimbursement.getId())
+ .last("LIMIT 1")
+ ));
+ }
+ vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_REIMBURSEMENT, reimbursement.getId()));
+ //瀹℃壒璁板綍杩斿洖
+ vo.setTasks(approvalTaskService.list(new LambdaQueryWrapper<ApprovalTask>().eq(ApprovalTask::getInstanceId, reimbursement.getApprovalInstanceId())));
+ vo.setRecords(approvalRecordService.list(new LambdaQueryWrapper<ApprovalRecord>().eq(ApprovalRecord::getInstanceId, reimbursement.getApprovalInstanceId())));
+ return vo;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean delete(List<Long> ids) {
+ fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, ids);
+ //鍏堝垹闄ゆ槑缁�
+ finReimbursementDetailMapper.delete(new LambdaQueryWrapper<FinReimbursementDetail>().in(FinReimbursementDetail::getReimbursementId, ids));
+ //鍒犻櫎宸梾
+ finReimbursementTravelMapper.delete(new LambdaQueryWrapper<FinReimbursementTravel>().in(FinReimbursementTravel::getReimbursementId, ids));
+ //鍒犻櫎涓昏〃
+ int rows = finReimbursementMapper.delete(new LambdaQueryWrapper<FinReimbursement>().in(FinReimbursement::getId, ids));
+ return rows == ids.size();
+ }
+
+ private String validateUpdateParam(FinReimbursementDto finReimbursementDto) {
+ if (finReimbursementDto == null || finReimbursementDto.getId() == null) {
+ throw new ServiceException("鎶ラ攢鍗旾D涓嶈兘涓虹┖");
+ }
+ if (finReimbursementDto.getReimbursementType() == null) {
+ throw new ServiceException("鎶ラ攢绫诲瀷涓嶈兘涓虹┖");
+ }
+ String billStatus = normalizeBillStatus(finReimbursementDto.getBillStatus());
+ if (billStatus == null) {
+ throw new ServiceException("鍗曟嵁鐘舵�佸彧鏀寔 DRAFT 鎴� IN_APPROVAL");
+ }
+ if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+ validateApprovalNodes(finReimbursementDto.getNodes());
+ }
+ List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+ if (details == null || details.isEmpty()) {
+ throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+ }
+ for (FinReimbursementDetail detail : details) {
+ if (detail == null) {
+ throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+ }
+ if (detail.getAmount() == null || detail.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+ throw new ServiceException("鎶ラ攢鍗曟槑缁嗛噾棰濆繀椤诲ぇ浜�0");
+ }
+ }
+ if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() == null) {
+ throw new ServiceException("宸梾鎶ラ攢蹇呴』濉啓宸梾鎵╁睍淇℃伅");
+ }
+ if (!isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() != null) {
+ throw new ServiceException("闈炲樊鏃呮姤閿�涓嶅厑璁稿~鍐欏樊鏃呮墿灞曚俊鎭�");
+ }
+ return billStatus;
+ }
+
+ private String validateAddParam(FinReimbursementDto finReimbursementDto) {
+ if (finReimbursementDto == null) {
+ throw new ServiceException("鎶ラ攢鍗曟暟鎹笉鑳戒负绌�");
+ }
+ if (finReimbursementDto.getReimbursementType() == null) {
+ throw new ServiceException("鎶ラ攢绫诲瀷涓嶈兘涓虹┖");
+ }
+ String billStatus = normalizeBillStatus(finReimbursementDto.getBillStatus());
+ if (billStatus == null) {
+ throw new ServiceException("鍗曟嵁鐘舵�佸彧鏀寔 DRAFT 鎴� IN_APPROVAL");
+ }
+ if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+ validateApprovalNodes(finReimbursementDto.getNodes());
+ }
+ List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+ if (details == null || details.isEmpty()) {
+ throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+ }
+ for (FinReimbursementDetail detail : details) {
+ if (detail == null) {
+ throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+ }
+ if (detail.getAmount() == null || detail.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+ throw new ServiceException("鎶ラ攢鍗曟槑缁嗛噾棰濆繀椤诲ぇ浜�0");
+ }
+ }
+ if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() == null) {
+ throw new ServiceException("宸梾鎶ラ攢蹇呴』濉啓宸梾鎵╁睍淇℃伅");
+ }
+ if (!isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() != null) {
+ throw new ServiceException("闈炲樊鏃呮姤閿�涓嶅厑璁稿~鍐欏樊鏃呮墿灞曚俊鎭�");
+ }
+ return billStatus;
+ }
+
+ private FinReimbursement buildReimbursement(FinReimbursementDto finReimbursementDto, String billNo, BigDecimal totalAmount, String billStatus) {
+ FinReimbursement reimbursement = new FinReimbursement();
+ reimbursement.setId(null);
+ reimbursement.setBillNo(billNo);
+ reimbursement.setReimbursementType(finReimbursementDto.getReimbursementType());
+ reimbursement.setExpenseType(finReimbursementDto.getExpenseType());
+ reimbursement.setApplicantId(finReimbursementDto.getApplicantId());
+ reimbursement.setApplicantCode(finReimbursementDto.getApplicantCode());
+ reimbursement.setApplicantName(finReimbursementDto.getApplicantName());
+ reimbursement.setApplicantDeptId(finReimbursementDto.getApplicantDeptId());
+ reimbursement.setApplicantDeptName(finReimbursementDto.getApplicantDeptName());
+ reimbursement.setReason(finReimbursementDto.getReason());
+ reimbursement.setApplyAmount(finReimbursementDto.getApplyAmount());
+ reimbursement.setDetailTotalAmount(totalAmount);
+ reimbursement.setPayeeName(finReimbursementDto.getPayeeName());
+ reimbursement.setPayeeAccount(finReimbursementDto.getPayeeAccount());
+ reimbursement.setPayeeBank(finReimbursementDto.getPayeeBank());
+ reimbursement.setRemark(finReimbursementDto.getRemark());
+ reimbursement.setTenantId(finReimbursementDto.getTenantId());
+ reimbursement.setApproveProcessId(null);
+ reimbursement.setBillStatus(billStatus);
+ return reimbursement;
+ }
+
+ private void startApproval(FinReimbursement reimbursement, FinReimbursementDto finReimbursementDto) {
+ Long businessType = resolveBusinessType(finReimbursementDto.getReimbursementType());
+ ApprovalInstanceDto approvalInstanceDto = new ApprovalInstanceDto();
+ approvalInstanceDto.setInstanceNo(OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no", approvalInstanceDto.getCreateTime() != null ? approvalInstanceDto.getCreateTime() : LocalDateTime.now()));
+ approvalInstanceDto.setBusinessId(reimbursement.getId());
+ approvalInstanceDto.setTemplateId(null);
+ approvalInstanceDto.setTemplateName(TypeEnums.getLabelByValue(businessType) + "瀹℃壒");
+ approvalInstanceDto.setBusinessType(businessType);
+ approvalInstanceDto.setTitle("鎶ラ攢鍗曞彿锛�" + reimbursement.getBillNo());
+ approvalInstanceDto.setApplicantId(reimbursement.getApplicantId() != null ? reimbursement.getApplicantId() : SecurityUtils.getUserId());
+ approvalInstanceDto.setApplicantName(reimbursement.getApplicantName() != null ? reimbursement.getApplicantName() : SecurityUtils.getLoginUser().getNickName());
+ approvalInstanceDto.setApplyTime(LocalDateTime.now());
+ approvalInstanceDto.setStatus("PENDING");
+ approvalInstanceDto.setCurrentLevel(1);
+
+ boolean approvalSaved = approvalInstanceService.save(approvalInstanceDto);
+ if (!approvalSaved || approvalInstanceDto.getId() == null) {
+ throw new ServiceException("鍙戣捣瀹℃壒澶辫触");
+ }
+ List<ApprovalTask> firstTasks = createApprovalNodes(approvalInstanceDto, finReimbursementDto.getNodes());
+ sendApproveNotice(approvalInstanceDto, firstTasks);
+
+ FinReimbursement update = new FinReimbursement();
+ update.setId(reimbursement.getId());
+ update.setApprovalInstanceId(approvalInstanceDto.getId());
+ update.setBillStatus(BILL_STATUS_IN_APPROVAL);
+ int rows = finReimbursementMapper.updateById(update);
+ if (rows != 1) {
+ throw new ServiceException("鍥炲~瀹℃壒瀹炰緥澶辫触");
+ }
+ }
+
+ private List<ApprovalTask> createApprovalNodes(ApprovalInstanceDto approvalInstanceDto, List<ApprovalTemplateNodeDto> nodes) {
+ List<ApprovalTask> firstTasks = Collections.emptyList();
+ for (int i = 0; i < nodes.size(); i++) {
+ ApprovalTemplateNodeDto nodeDto = nodes.get(i);
+ ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
+ instanceNode.setInstanceId(approvalInstanceDto.getId());
+ instanceNode.setLevelNo(nodeDto.getLevelNo());
+ instanceNode.setApproveType(nodeDto.getApproveType());
+ instanceNode.setStatus(i == 0 ? "PENDING" : NODE_STATUS_WAITING);
+ instanceNode.setStartTime(i == 0 ? LocalDateTime.now() : null);
+ instanceNode.setDeleted((byte) 0);
+ approvalInstanceNodeService.save(instanceNode);
+
+ List<ApprovalTask> tasks = nodeDto.getApprovers().stream().map(approver -> {
+ ApprovalTask task = new ApprovalTask();
+ task.setInstanceId(approvalInstanceDto.getId());
+ task.setNodeId(instanceNode.getId());
+ task.setLevelNo(instanceNode.getLevelNo());
+ task.setApproverId(approver.getApproverId());
+ task.setApproverName(approver.getApproverName());
+ task.setTaskStatus("PENDING");
+ task.setIsRead((byte) 0);
+ task.setDeleted((byte) 0);
+ return task;
+ }).collect(Collectors.toList());
+ approvalTaskService.saveBatch(tasks);
+
+ if (i == 0) {
+ firstTasks = tasks;
+ ApprovalRecord record = new ApprovalRecord();
+ record.setInstanceId(approvalInstanceDto.getId());
+ record.setNodeId(instanceNode.getId());
+ record.setOperatorId(approvalInstanceDto.getApplicantId());
+ record.setOperatorName(approvalInstanceDto.getApplicantName());
+ record.setAction("SUBMIT");
+ record.setComment("鍙戣捣瀹℃壒");
+ record.setDeleted((byte) 0);
+ approvalRecordService.save(record);
+ }
+ }
+ return firstTasks;
+ }
+
+ private void validateApprovalNodes(List<ApprovalTemplateNodeDto> nodes) {
+ if (nodes == null || nodes.isEmpty()) {
+ throw new ServiceException("鎻愪氦瀹℃壒鏃跺鎵硅妭鐐逛笉鑳戒负绌�");
+ }
+ for (int i = 0; i < nodes.size(); i++) {
+ ApprovalTemplateNodeDto node = nodes.get(i);
+ if (node == null) {
+ throw new ServiceException("瀹℃壒鑺傜偣涓嶈兘涓虹┖");
+ }
+ if (node.getLevelNo() == null) {
+ node.setLevelNo(i + 1);
+ }
+ if (!StringUtils.hasText(node.getApproveType())) {
+ throw new ServiceException("瀹℃壒鑺傜偣瀹℃壒鏂瑰紡涓嶈兘涓虹┖");
+ }
+ List<ApprovalTemplateNodeApproverDto> approvers = node.getApprovers();
+ if (approvers == null || approvers.isEmpty()) {
+ throw new ServiceException("瀹℃壒鑺傜偣瀹℃壒浜轰笉鑳戒负绌�");
+ }
+ for (ApprovalTemplateNodeApproverDto approver : approvers) {
+ if (approver == null || approver.getApproverId() == null) {
+ throw new ServiceException("瀹℃壒浜轰笉鑳戒负绌�");
+ }
+ }
+ }
+ }
+
+ private void sendApproveNotice(ApprovalInstanceDto instance, List<ApprovalTask> tasks) {
+ if (instance == null || tasks == null || tasks.isEmpty()) {
+ return;
+ }
+ List<Long> approverIds = tasks.stream()
+ .map(ApprovalTask::getApproverId)
+ .filter(id -> id != null && id > 0)
+ .distinct()
+ .collect(Collectors.toList());
+ if (approverIds.isEmpty()) {
+ return;
+ }
+ String title = "鎶ラ攢瀹℃壒";
+ String message = "瀹℃壒鍗曞彿 " + instance.getInstanceNo() + " 闇�瑕佹偍瀹℃壒";
+ String jumpPath = "/approvalInstance?id=" + instance.getId();
+ sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
+ }
+
+ private void resetApprovalFlow(FinReimbursement existing, Long reimbursementId) {
+ if (existing == null || existing.getApprovalInstanceId() == null) {
+ return;
+ }
+ Long approvalInstanceId = existing.getApprovalInstanceId();
+ if (!"REJECTED".equals(existing.getBillStatus())) {
+ approvalInstanceService.delete(Collections.singletonList(approvalInstanceId));
+ }
+ clearApprovalBinding(reimbursementId);
+ }
+
+ private void clearApprovalBinding(Long reimbursementId) {
+ int rows = finReimbursementMapper.update(
+ null,
+ Wrappers.<FinReimbursement>lambdaUpdate()
+ .eq(FinReimbursement::getId, reimbursementId)
+ .set(FinReimbursement::getApprovalInstanceId, null)
+ );
+ if (rows != 1) {
+ throw new ServiceException("閲嶇疆瀹℃壒娴佺▼澶辫触");
+ }
+ }
+
+ private Long resolveBusinessType(Byte reimbursementType) {
+ return isTravelReimbursement(reimbursementType)
+ ? TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode()
+ : TypeEnums.EXPENSE_APPROVAL.getCode();
+ }
+
+ private String normalizeBillStatus(String billStatus) {
+ if (billStatus == null) {
+ return BILL_STATUS_DRAFT;
+ }
+ String normalized = billStatus.trim().toUpperCase();
+ if (BILL_STATUS_DRAFT.equals(normalized) || BILL_STATUS_IN_APPROVAL.equals(normalized)) {
+ return normalized;
+ }
+ return null;
+ }
+
+ private boolean isTravelReimbursement(Byte reimbursementType) {
+ return Byte.valueOf((byte) 1).equals(reimbursementType);
+ }
+}
--
Gitblit v1.9.3