From d24547e47e8846af1d81219c9ca1ddc1655d38b3 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 14 五月 2026 15:54:36 +0800
Subject: [PATCH] feat(seal): 添加用印申请管理的文件上传功能

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

diff --git a/src/main/java/com/ruoyi/account/service/impl/financial/FinIntangibleAssetServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/financial/FinIntangibleAssetServiceImpl.java
new file mode 100644
index 0000000..72e5f06
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/service/impl/financial/FinIntangibleAssetServiceImpl.java
@@ -0,0 +1,250 @@
+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.FinIntangibleAssetDto;
+import com.ruoyi.account.mapper.financial.FinIntangibleAssetMapper;
+import com.ruoyi.account.pojo.financial.FinIntangibleAsset;
+import com.ruoyi.account.service.financial.FinIntangibleAssetService;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * 鏃犲舰璧勪骇鏈嶅姟瀹炵幇銆�
+ */
+@Service
+@RequiredArgsConstructor
+public class FinIntangibleAssetServiceImpl extends ServiceImpl<FinIntangibleAssetMapper, FinIntangibleAsset> implements FinIntangibleAssetService {
+
+    private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
+    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+
+    @Override
+    public IPage<FinIntangibleAsset> pageList(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto) {
+        LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
+        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetCode())) {
+            wrapper.like(FinIntangibleAsset::getAssetCode, queryDto.getAssetCode());
+        }
+        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetName())) {
+            wrapper.like(FinIntangibleAsset::getAssetName, queryDto.getAssetName());
+        }
+        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCategory())) {
+            wrapper.eq(FinIntangibleAsset::getCategory, queryDto.getCategory());
+        }
+        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) {
+            wrapper.eq(FinIntangibleAsset::getStatus, queryDto.getStatus());
+        }
+        wrapper.orderByDesc(FinIntangibleAsset::getId);
+        return page(page, wrapper);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean add(FinIntangibleAssetDto dto) {
+        validateForSave(dto, false);
+        if (StringUtils.isEmpty(dto.getAssetCode())) {
+            dto.setAssetCode(generateAssetCode());
+        }
+        BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate());
+        dto.setResidualRate(residualRate);
+        BigDecimal accumulatedAmortization = defaultMoney(dto.getAccumulatedAmortization());
+        dto.setAccumulatedAmortization(accumulatedAmortization);
+        dto.setNetValue(calculateNetValue(dto.getOriginalValue(), accumulatedAmortization));
+        if (StringUtils.isEmpty(dto.getStatus())) {
+            dto.setStatus("in_use");
+        }
+        return save(dto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean update(FinIntangibleAssetDto dto) {
+        if (dto == null || dto.getId() == null) {
+            throw new ServiceException("淇敼澶辫触锛岃祫浜D涓嶈兘涓虹┖");
+        }
+        FinIntangibleAsset existed = getById(dto.getId());
+        if (existed == null) {
+            throw new ServiceException("淇敼澶辫触锛屾棤褰㈣祫浜т笉瀛樺湪");
+        }
+        if (StringUtils.isEmpty(dto.getAssetCode())) {
+            dto.setAssetCode(existed.getAssetCode());
+        }
+        if (StringUtils.isEmpty(dto.getStatus())) {
+            dto.setStatus(existed.getStatus());
+        }
+        validateForSave(dto, true);
+        BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate());
+        dto.setResidualRate(residualRate);
+        if (dto.getAccumulatedAmortization() == null) {
+            dto.setAccumulatedAmortization(defaultMoney(existed.getAccumulatedAmortization()));
+        }
+        dto.setNetValue(calculateNetValue(dto.getOriginalValue(), dto.getAccumulatedAmortization()));
+        if (dto.getNetValue().compareTo(BigDecimal.ZERO) <= 0) {
+            dto.setStatus("amortized");
+        } else if ("amortized".equals(dto.getStatus())) {
+            dto.setStatus("in_use");
+        }
+        if (dto.getValidityDate() != null
+                && dto.getValidityDate().isBefore(LocalDate.now())
+                && !"amortized".equals(dto.getStatus())) {
+            dto.setStatus("expired");
+        }
+        return updateById(dto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean deleteByIds(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            throw new ServiceException("鍒犻櫎澶辫触锛岃閫夋嫨瑕佸垹闄ょ殑鏁版嵁");
+        }
+        return removeByIds(ids);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> amortize(List<Long> ids) {
+        LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
+        if (ids != null && !ids.isEmpty()) {
+            wrapper.in(FinIntangibleAsset::getId, ids);
+        } else {
+            wrapper.eq(FinIntangibleAsset::getStatus, "in_use");
+        }
+        List<FinIntangibleAsset> assets = list(wrapper);
+        BigDecimal totalMonthlyAmortization = ZERO;
+        int processedCount = 0;
+        for (FinIntangibleAsset asset : assets) {
+            if (!"in_use".equals(asset.getStatus())) {
+                continue;
+            }
+            BigDecimal monthlyAmortization = calculateMonthlyAmortization(
+                    asset.getOriginalValue(),
+                    asset.getResidualRate(),
+                    asset.getAmortizationPeriod()
+            );
+            BigDecimal accumulatedAmortization = defaultMoney(asset.getAccumulatedAmortization()).add(monthlyAmortization);
+            if (accumulatedAmortization.compareTo(defaultMoney(asset.getOriginalValue())) > 0) {
+                accumulatedAmortization = defaultMoney(asset.getOriginalValue());
+            }
+            asset.setAccumulatedAmortization(roundMoney(accumulatedAmortization));
+            asset.setNetValue(calculateNetValue(asset.getOriginalValue(), asset.getAccumulatedAmortization()));
+
+            // 瑙勫垯锛氬綋鍑�鍊� <= 0 鏃讹紝鍑�鍊煎綊闆跺苟鏍囪涓哄凡鎽婇攢瀹屻��
+            if (asset.getNetValue().compareTo(BigDecimal.ZERO) <= 0) {
+                asset.setNetValue(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
+                asset.setStatus("amortized");
+            } else if (asset.getValidityDate() != null && asset.getValidityDate().isBefore(LocalDate.now())) {
+                asset.setStatus("expired");
+            }
+            updateById(asset);
+            processedCount++;
+            totalMonthlyAmortization = totalMonthlyAmortization.add(monthlyAmortization);
+        }
+        Map<String, Object> result = new HashMap<>(4);
+        result.put("processedCount", processedCount);
+        result.put("totalMonthlyAmortization", roundMoney(totalMonthlyAmortization));
+        result.put("executionTime", LocalDateTime.now());
+        return result;
+    }
+
+    /**
+     * 鎸夋枃妗h鍒欐牎楠屾棤褰㈣祫浜ф暟鎹��
+     */
+    private void validateForSave(FinIntangibleAssetDto dto, boolean isUpdate) {
+        if (dto == null) {
+            throw new ServiceException("鏃犲舰璧勪骇鏁版嵁涓嶈兘涓虹┖");
+        }
+        if (isUpdate && dto.getId() == null) {
+            throw new ServiceException("淇敼澶辫触锛岃祫浜D涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isEmpty(dto.getAssetName())) {
+            throw new ServiceException("璧勪骇鍚嶇О涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isEmpty(dto.getCategory())) {
+            throw new ServiceException("璧勪骇绫诲埆涓嶈兘涓虹┖");
+        }
+        if (dto.getAcquisitionDate() == null) {
+            throw new ServiceException("鍙栧緱鏃ユ湡涓嶈兘涓虹┖");
+        }
+        if (dto.getOriginalValue() == null || dto.getOriginalValue().compareTo(BigDecimal.ZERO) < 0) {
+            throw new ServiceException("璧勪骇鍘熷�间笉鑳戒负绌轰笖涓嶈兘灏忎簬0");
+        }
+        if (dto.getAmortizationPeriod() == null || dto.getAmortizationPeriod() <= 0) {
+            throw new ServiceException("鎽婇攢骞撮檺蹇呴』澶т簬0");
+        }
+        if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(BigDecimal.ZERO) < 0) {
+            throw new ServiceException("娈嬪�肩巼涓嶈兘灏忎簬0");
+        }
+        if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(ONE_HUNDRED) > 0) {
+            throw new ServiceException("娈嬪�肩巼涓嶈兘澶т簬100%");
+        }
+        if (StringUtils.isNotEmpty(dto.getAssetCode())) {
+            LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(FinIntangibleAsset::getAssetCode, dto.getAssetCode());
+            if (isUpdate) {
+                wrapper.ne(FinIntangibleAsset::getId, dto.getId());
+            }
+            if (count(wrapper) > 0) {
+                throw new ServiceException("璧勪骇缂栧彿宸插瓨鍦紝璇峰嬁閲嶅鎻愪氦");
+            }
+        }
+    }
+
+    /**
+     * 鏃犲舰璧勪骇鎽婇攢鍏紡锛�
+     * monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)
+     */
+    private BigDecimal calculateMonthlyAmortization(BigDecimal originalValue, BigDecimal residualRate, Integer amortizationPeriod) {
+        BigDecimal normalizedOriginalValue = defaultMoney(originalValue);
+        BigDecimal normalizedResidualRate = normalizeResidualRate(residualRate);
+        BigDecimal amortizableRatio = BigDecimal.ONE.subtract(normalizedResidualRate.divide(ONE_HUNDRED, 8, RoundingMode.HALF_UP));
+        BigDecimal months = BigDecimal.valueOf((long) amortizationPeriod * 12L);
+        if (months.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("鎽婇攢骞撮檺鏃犳晥锛屾棤娉曡鎻愭憡閿�");
+        }
+        return roundMoney(normalizedOriginalValue.multiply(amortizableRatio).divide(months, 8, RoundingMode.HALF_UP));
+    }
+
+    /**
+     * 鍑�鍊� = 鍘熷�� - 绱鎽婇攢銆�
+     */
+    private BigDecimal calculateNetValue(BigDecimal originalValue, BigDecimal accumulatedAmortization) {
+        BigDecimal value = defaultMoney(originalValue).subtract(defaultMoney(accumulatedAmortization));
+        if (value.compareTo(BigDecimal.ZERO) < 0) {
+            value = BigDecimal.ZERO;
+        }
+        return roundMoney(value);
+    }
+
+    private BigDecimal normalizeResidualRate(BigDecimal residualRate) {
+        return residualRate == null ? BigDecimal.ZERO : residualRate;
+    }
+
+    private BigDecimal defaultMoney(BigDecimal value) {
+        return value == null ? ZERO : roundMoney(value);
+    }
+
+    private BigDecimal roundMoney(BigDecimal value) {
+        if (value == null) {
+            return ZERO;
+        }
+        return value.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private String generateAssetCode() {
+        return "WX" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
+    }
+}

--
Gitblit v1.9.3