From a5d7ef82a926d72651bb35779a59883528a9d641 Mon Sep 17 00:00:00 2001
From: liyong <18434998025@163.com>
Date: 星期一, 18 五月 2026 11:58:24 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_New_pro' into dev_New_pro

---
 src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java |  307 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 307 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java
new file mode 100644
index 0000000..b7548ef
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java
@@ -0,0 +1,307 @@
+package com.ruoyi.account.service.impl.financial;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.account.bean.dto.financial.FinVoucherDto;
+import com.ruoyi.account.bean.dto.financial.FinVoucherEntryDto;
+import com.ruoyi.account.bean.dto.financial.FinVoucherPageDto;
+import com.ruoyi.account.bean.vo.financial.FinVoucherDetailVo;
+import com.ruoyi.account.mapper.AccountSubjectMapper;
+import com.ruoyi.account.mapper.financial.FinVoucherEntryMapper;
+import com.ruoyi.account.mapper.financial.FinVoucherMapper;
+import com.ruoyi.account.pojo.AccountSubject;
+import com.ruoyi.account.pojo.financial.FinVoucher;
+import com.ruoyi.account.pojo.financial.FinVoucherEntry;
+import com.ruoyi.account.service.financial.FinVoucherService;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 鍑瘉鏈嶅姟瀹炵幇銆�
+ */
+@Service
+@RequiredArgsConstructor
+public class FinVoucherServiceImpl extends ServiceImpl<FinVoucherMapper, FinVoucher> implements FinVoucherService {
+
+    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+
+    private final FinVoucherEntryMapper finVoucherEntryMapper;
+    private final AccountSubjectMapper accountSubjectMapper;
+    private final FileUtil fileUtil;
+
+    @Override
+    public IPage<FinVoucher> pageList(Page<FinVoucher> page, FinVoucherPageDto queryDto) {
+        LambdaQueryWrapper<FinVoucher> wrapper = new LambdaQueryWrapper<>();
+        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getVoucherNo())) {
+            wrapper.like(FinVoucher::getVoucherNo, queryDto.getVoucherNo());
+        }
+        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCreator())) {
+            wrapper.eq(FinVoucher::getCreator, queryDto.getCreator());
+        }
+        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) {
+            wrapper.eq(FinVoucher::getStatus, queryDto.getStatus());
+        }
+        if (queryDto != null && queryDto.getStartDate() != null) {
+            wrapper.ge(FinVoucher::getVoucherDate, queryDto.getStartDate());
+        }
+        if (queryDto != null && queryDto.getEndDate() != null) {
+            wrapper.le(FinVoucher::getVoucherDate, queryDto.getEndDate());
+        }
+        wrapper.orderByDesc(FinVoucher::getVoucherDate).orderByDesc(FinVoucher::getId);
+        return page(page, wrapper);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean addVoucher(FinVoucherDto dto) {
+        validateVoucherBasicInfo(dto, false);
+        List<FinVoucherEntry> validEntries = buildAndValidateEntries(dto);
+
+        FinVoucher voucher = new FinVoucher();
+        BeanUtils.copyProperties(dto, voucher);
+        voucher.setStatus("unposted");
+        voucher.setAttachmentCount(voucher.getAttachmentCount() == null ? 0 : voucher.getAttachmentCount());
+        BigDecimal totalDebit = calculateTotalDebit(validEntries);
+        BigDecimal totalCredit = calculateTotalCredit(validEntries);
+        voucher.setDebit(totalDebit);
+        voucher.setCredit(totalCredit);
+        if (StringUtils.isEmpty(voucher.getSummary())) {
+            voucher.setSummary(findDefaultSummary(validEntries));
+        }
+        save(voucher);
+        saveEntries(voucher.getId(), validEntries);
+        // 5. 淇濆瓨閿�鍞彴璐﹂檮浠�
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_VOUCHER, voucher.getId(), dto.getStorageBlobDTOs());
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateVoucher(FinVoucherDto dto) {
+        validateVoucherBasicInfo(dto, true);
+        FinVoucher existed = getById(dto.getId());
+        if (existed == null) {
+            throw new ServiceException("淇敼澶辫触锛屽嚟璇佷笉瀛樺湪");
+        }
+        if (!"unposted".equals(existed.getStatus())) {
+            throw new ServiceException("浠呮湭杩囪处鍑瘉鍏佽淇敼");
+        }
+        List<FinVoucherEntry> validEntries = buildAndValidateEntries(dto);
+
+        FinVoucher voucher = new FinVoucher();
+        BeanUtils.copyProperties(dto, voucher);
+        voucher.setStatus(existed.getStatus());
+        voucher.setAttachmentCount(voucher.getAttachmentCount() == null ? 0 : voucher.getAttachmentCount());
+        BigDecimal totalDebit = calculateTotalDebit(validEntries);
+        BigDecimal totalCredit = calculateTotalCredit(validEntries);
+        voucher.setDebit(totalDebit);
+        voucher.setCredit(totalCredit);
+        if (StringUtils.isEmpty(voucher.getSummary())) {
+            voucher.setSummary(findDefaultSummary(validEntries));
+        }
+        updateById(voucher);
+
+        LambdaQueryWrapper<FinVoucherEntry> deleteWrapper = new LambdaQueryWrapper<>();
+        deleteWrapper.eq(FinVoucherEntry::getVoucherId, voucher.getId());
+        finVoucherEntryMapper.delete(deleteWrapper);
+        saveEntries(voucher.getId(), validEntries);
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_VOUCHER, voucher.getId(), dto.getStorageBlobDTOs());
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean postVoucher(Long id) {
+        FinVoucher voucher = getById(id);
+        if (voucher == null) {
+            throw new ServiceException("杩囪处澶辫触锛屽嚟璇佷笉瀛樺湪");
+        }
+        if (!"unposted".equals(voucher.getStatus())) {
+            throw new ServiceException("浠呮湭杩囪处鍑瘉鍏佽杩囪处");
+        }
+        voucher.setStatus("posted");
+        return updateById(voucher);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean cancelVoucher(Long id) {
+        FinVoucher voucher = getById(id);
+        if (voucher == null) {
+            throw new ServiceException("浣滃簾澶辫触锛屽嚟璇佷笉瀛樺湪");
+        }
+        if (!"unposted".equals(voucher.getStatus())) {
+            throw new ServiceException("浠呮湭杩囪处鍑瘉鍏佽浣滃簾");
+        }
+        voucher.setStatus("cancelled");
+        return updateById(voucher);
+    }
+
+    @Override
+    public FinVoucherDetailVo detail(Long id) {
+        FinVoucher voucher = getById(id);
+        if (voucher == null) {
+            throw new ServiceException("鏌ヨ澶辫触锛屽嚟璇佷笉瀛樺湪");
+        }
+        LambdaQueryWrapper<FinVoucherEntry> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(FinVoucherEntry::getVoucherId, id)
+                .orderByAsc(FinVoucherEntry::getRowNo)
+                .orderByAsc(FinVoucherEntry::getId);
+        List<FinVoucherEntry> entries = finVoucherEntryMapper.selectList(wrapper);
+
+        FinVoucherDetailVo vo = new FinVoucherDetailVo();
+        BeanUtils.copyProperties(voucher, vo);
+        vo.setEntries(entries);
+        vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_VOUCHER, id));
+        return vo;
+    }
+
+    /**
+     * 鏍¢獙鍑瘉涓昏〃瀛楁銆佺姸鎬佸瓧娈典笌鍞竴鎬с��
+     */
+    private void validateVoucherBasicInfo(FinVoucherDto dto, boolean isUpdate) {
+        if (dto == null) {
+            throw new ServiceException("鍑瘉鏁版嵁涓嶈兘涓虹┖");
+        }
+        if (isUpdate && dto.getId() == null) {
+            throw new ServiceException("淇敼澶辫触锛屽嚟璇両D涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isEmpty(dto.getVoucherNo())) {
+            throw new ServiceException("鍑瘉瀛楀彿涓嶈兘涓虹┖");
+        }
+        if (dto.getVoucherDate() == null) {
+            throw new ServiceException("鍑瘉鏃ユ湡涓嶈兘涓虹┖");
+        }
+        LambdaQueryWrapper<FinVoucher> uniqueWrapper = new LambdaQueryWrapper<>();
+        uniqueWrapper.eq(FinVoucher::getVoucherNo, dto.getVoucherNo());
+        if (isUpdate) {
+            uniqueWrapper.ne(FinVoucher::getId, dto.getId());
+        }
+        if (count(uniqueWrapper) > 0) {
+            throw new ServiceException("鍑瘉瀛楀彿宸插瓨鍦紝璇峰嬁閲嶅鎻愪氦");
+        }
+    }
+
+    /**
+     * 杩囨护鏈夋晥鍒嗗綍骞舵墽琛屽�熻捶骞宠 鏍¢獙銆�
+     */
+    private List<FinVoucherEntry> buildAndValidateEntries(FinVoucherDto dto) {
+        List<FinVoucherEntryDto> rawEntries = dto.getEntries();
+        if (rawEntries == null || rawEntries.isEmpty()) {
+            throw new ServiceException("鍒嗗綍涓嶈兘涓虹┖锛岃嚦灏戦渶瑕佷竴鏉℃湁鏁堝垎褰�");
+        }
+        List<FinVoucherEntry> validEntries = new ArrayList<>();
+        int rowNo = 1;
+        for (FinVoucherEntryDto entryDto : rawEntries) {
+            if (entryDto == null || StringUtils.isEmpty(entryDto.getSubjectCode())) {
+                continue;
+            }
+            BigDecimal debit = defaultMoney(entryDto.getDebit());
+            BigDecimal credit = defaultMoney(entryDto.getCredit());
+            if (debit.compareTo(BigDecimal.ZERO) <= 0 && credit.compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            if (debit.compareTo(BigDecimal.ZERO) > 0 && credit.compareTo(BigDecimal.ZERO) > 0) {
+                throw new ServiceException("鍒嗗綍鍊熸柟鍜岃捶鏂逛笉鑳藉悓鏃跺ぇ浜�0");
+            }
+            FinVoucherEntry entry = new FinVoucherEntry();
+            BeanUtils.copyProperties(entryDto, entry);
+            entry.setDebit(debit);
+            entry.setCredit(credit);
+            entry.setRowNo(rowNo++);
+            validEntries.add(entry);
+        }
+        if (validEntries.isEmpty()) {
+            throw new ServiceException("鍒嗗綍鑷冲皯闇�瑕佷竴鏉℃湁鏁堣锛堢鐩笉绌猴紝涓斿�熸柟鎴栬捶鏂瑰ぇ浜�0锛�");
+        }
+
+        // 鍒嗗綍绉戠洰蹇呴』瀛樺湪锛岄伩鍏嶈剰绉戠洰缂栫爜鍏ヨ处銆�
+        Set<String> subjectCodes = validEntries.stream()
+                .map(FinVoucherEntry::getSubjectCode)
+                .filter(StringUtils::isNotEmpty)
+                .collect(Collectors.toSet());
+        if (subjectCodes.isEmpty()) {
+            throw new ServiceException("鍒嗗綍绉戠洰涓嶈兘涓虹┖");
+        }
+        LambdaQueryWrapper<AccountSubject> subjectWrapper = new LambdaQueryWrapper<>();
+        subjectWrapper.in(AccountSubject::getSubjectCode, subjectCodes);
+        List<AccountSubject> subjects = accountSubjectMapper.selectList(subjectWrapper);
+        Map<String, AccountSubject> subjectMap = subjects.stream()
+                .collect(Collectors.toMap(AccountSubject::getSubjectCode, it -> it, (a, b) -> a));
+        for (FinVoucherEntry entry : validEntries) {
+            AccountSubject accountSubject = subjectMap.get(entry.getSubjectCode());
+            if (accountSubject == null) {
+                throw new ServiceException("绉戠洰缂栫爜涓嶅瓨鍦細" + entry.getSubjectCode());
+            }
+            if (StringUtils.isEmpty(entry.getSubjectName())) {
+                entry.setSubjectName(accountSubject.getSubjectName());
+            }
+        }
+
+        BigDecimal totalDebit = calculateTotalDebit(validEntries);
+        BigDecimal totalCredit = calculateTotalCredit(validEntries);
+        if (totalDebit.compareTo(BigDecimal.ZERO) <= 0 || totalCredit.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("鍊熻捶閲戦蹇呴』澶т簬0");
+        }
+        if (totalDebit.compareTo(totalCredit) != 0) {
+            throw new ServiceException("鍊熻捶涓嶅钩琛★紝绂佹淇濆瓨");
+        }
+        return validEntries;
+    }
+
+    private void saveEntries(Long voucherId, List<FinVoucherEntry> entries) {
+        if (voucherId == null) {
+            throw new ServiceException("鍑瘉ID涓嶈兘涓虹┖");
+        }
+        for (FinVoucherEntry entry : entries) {
+            entry.setVoucherId(voucherId);
+            finVoucherEntryMapper.insert(entry);
+        }
+    }
+
+    private String findDefaultSummary(List<FinVoucherEntry> entries) {
+        for (FinVoucherEntry entry : entries) {
+            if (StringUtils.isNotEmpty(entry.getSummary())) {
+                return entry.getSummary();
+            }
+        }
+        return "";
+    }
+
+    private BigDecimal calculateTotalDebit(List<FinVoucherEntry> entries) {
+        BigDecimal total = BigDecimal.ZERO;
+        for (FinVoucherEntry entry : entries) {
+            total = total.add(defaultMoney(entry.getDebit()));
+        }
+        return total.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal calculateTotalCredit(List<FinVoucherEntry> entries) {
+        BigDecimal total = BigDecimal.ZERO;
+        for (FinVoucherEntry entry : entries) {
+            total = total.add(defaultMoney(entry.getCredit()));
+        }
+        return total.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal defaultMoney(BigDecimal value) {
+        if (value == null) {
+            return ZERO;
+        }
+        return value.setScale(2, RoundingMode.HALF_UP);
+    }
+}

--
Gitblit v1.9.3