package cn.iocoder.yudao.module.finance.service.asset; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.finance.controller.admin.asset.vo.FinIntangibleAssetAmortizeRespVO; import cn.iocoder.yudao.module.finance.controller.admin.asset.vo.FinIntangibleAssetPageReqVO; import cn.iocoder.yudao.module.finance.controller.admin.asset.vo.FinIntangibleAssetSaveReqVO; import cn.iocoder.yudao.module.finance.dal.dataobject.asset.FinIntangibleAssetDO; import cn.iocoder.yudao.module.finance.dal.mysql.asset.FinIntangibleAssetMapper; import cn.iocoder.yudao.module.finance.enums.FinIntangibleAssetStatusEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.finance.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.finance.service.asset.FinAssetAmountUtils.calcDepreciableBase; import static cn.iocoder.yudao.module.finance.service.asset.FinAssetAmountUtils.calcMonthlyAmount; /** * 无形资产 Service 实现类 * * @author 超级管理员 */ @Service @Validated public class FinIntangibleAssetServiceImpl implements FinIntangibleAssetService { @Resource private FinIntangibleAssetMapper intangibleAssetMapper; @Override public Long createIntangibleAsset(FinIntangibleAssetSaveReqVO createReqVO) { // 1. 校验资产编号唯一 validateAssetCodeUnique(null, createReqVO.getAssetCode()); // 2. 插入:新资产尚未计提摊销 FinIntangibleAssetDO asset = BeanUtils.toBean(createReqVO, FinIntangibleAssetDO.class); asset.setAssetCode(StrUtil.trim(asset.getAssetCode())); asset.setResidualRate(ObjectUtil.defaultIfNull(asset.getResidualRate(), BigDecimal.ZERO)); asset.setStatus(ObjectUtil.defaultIfNull(asset.getStatus(), FinIntangibleAssetStatusEnum.IN_USE.getStatus())); asset.setAccumulatedAmortization(BigDecimal.ZERO); fillNetValue(asset); intangibleAssetMapper.insert(asset); return asset.getId(); } @Override public void updateIntangibleAsset(FinIntangibleAssetSaveReqVO updateReqVO) { // 1.1 校验存在 FinIntangibleAssetDO oldAsset = validateIntangibleAssetExists(updateReqVO.getId()); // 1.2 校验资产编号唯一 validateAssetCodeUnique(updateReqVO.getId(), updateReqVO.getAssetCode()); // 2.1 更新(累计摊销不从前端接收,沿用库中值) FinIntangibleAssetDO updateObj = BeanUtils.toBean(updateReqVO, FinIntangibleAssetDO.class); updateObj.setAssetCode(StrUtil.trim(updateObj.getAssetCode())); updateObj.setResidualRate(ObjectUtil.defaultIfNull(updateObj.getResidualRate(), BigDecimal.ZERO)); updateObj.setStatus(ObjectUtil.defaultIfNull(updateObj.getStatus(), oldAsset.getStatus())); updateObj.setAccumulatedAmortization( ObjectUtil.defaultIfNull(oldAsset.getAccumulatedAmortization(), BigDecimal.ZERO)); // 2.2 原值、残值率变化后,按新的应摊销总额封顶累计摊销,并重算净值 BigDecimal amortizableBase = calcDepreciableBase(defaultIfNull(updateObj.getOriginalValue()), updateObj.getResidualRate()); if (updateObj.getAccumulatedAmortization().compareTo(amortizableBase) > 0) { updateObj.setAccumulatedAmortization(amortizableBase); } fillNetValue(updateObj); // 2.3 已摊完但状态还在用的,同步为「已摊销完」 if (updateObj.getNetValue().compareTo(BigDecimal.ZERO) <= 0 && FinIntangibleAssetStatusEnum.IN_USE.getStatus().equals(updateObj.getStatus())) { updateObj.setStatus(FinIntangibleAssetStatusEnum.AMORTIZED.getStatus()); } intangibleAssetMapper.updateById(updateObj); } @Override public void deleteIntangibleAsset(Long id) { validateIntangibleAssetExists(id); intangibleAssetMapper.deleteById(id); } @Override public void deleteIntangibleAssetList(List ids) { if (CollUtil.isEmpty(ids)) { return; } intangibleAssetMapper.deleteByIds(ids); } @Override @Transactional(rollbackFor = Exception.class) public FinIntangibleAssetAmortizeRespVO amortizeIntangibleAssetList(List ids) { // 1. 查询待计提的资产:已失效的不再计提 List assets = CollUtil.isEmpty(ids) ? intangibleAssetMapper.selectList() : intangibleAssetMapper.selectListByIds(ids); List updateList = new ArrayList<>(); BigDecimal totalMonthlyAmortization = BigDecimal.ZERO; for (FinIntangibleAssetDO asset : assets) { if (FinIntangibleAssetStatusEnum.EXPIRED.getStatus().equals(asset.getStatus())) { continue; } BigDecimal originalValue = defaultIfNull(asset.getOriginalValue()); BigDecimal residualRate = defaultIfNull(asset.getResidualRate()); int years = ObjectUtil.defaultIfNull(asset.getAmortizationPeriod(), 0); BigDecimal accumulated = defaultIfNull(asset.getAccumulatedAmortization()); // 1.1 已摊完的不再计提 BigDecimal amortizableBase = calcDepreciableBase(originalValue, residualRate); BigDecimal remaining = amortizableBase.subtract(accumulated); if (remaining.compareTo(BigDecimal.ZERO) <= 0) { continue; } // 1.2 参数异常导致月摊销额非正 BigDecimal monthlyAmortization = years <= 0 ? BigDecimal.ZERO : calcMonthlyAmount(originalValue, residualRate, years); if (monthlyAmortization.compareTo(BigDecimal.ZERO) <= 0) { throw exception(INTANGIBLE_ASSET_AMORTIZE_NEGATIVE, asset.getAssetName(), formatAmount(originalValue), years, formatAmount(residualRate)); } // 1.3 最后一期按剩余可摊销额封顶,净值不会被摊成负数 BigDecimal periodAmortization = monthlyAmortization.min(remaining); BigDecimal newAccumulated = accumulated.add(periodAmortization); BigDecimal newNetValue = originalValue.subtract(newAccumulated).max(BigDecimal.ZERO); FinIntangibleAssetDO updateObj = new FinIntangibleAssetDO().setId(asset.getId()) .setAccumulatedAmortization(newAccumulated) .setNetValue(newNetValue); // 1.4 摊完时自动置为「已摊销完」 if (newNetValue.compareTo(BigDecimal.ZERO) <= 0) { updateObj.setStatus(FinIntangibleAssetStatusEnum.AMORTIZED.getStatus()); } updateList.add(updateObj); totalMonthlyAmortization = totalMonthlyAmortization.add(periodAmortization); } // 2. 没有可计提的资产,明确告知 if (updateList.isEmpty()) { throw exception(INTANGIBLE_ASSET_AMORTIZE_EMPTY); } // 3. 批量更新 intangibleAssetMapper.updateBatch(updateList); return new FinIntangibleAssetAmortizeRespVO().setProcessedCount(updateList.size()) .setTotalMonthlyAmortization(totalMonthlyAmortization); } @Override public FinIntangibleAssetDO getIntangibleAsset(Long id) { return intangibleAssetMapper.selectById(id); } @Override public PageResult getIntangibleAssetPage(FinIntangibleAssetPageReqVO pageReqVO) { return intangibleAssetMapper.selectPage(pageReqVO); } @Override public List getIntangibleAssetList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } return intangibleAssetMapper.selectListByIds(ids); } @Override public List getIntangibleAssetList() { return intangibleAssetMapper.selectList(); } private FinIntangibleAssetDO validateIntangibleAssetExists(Long id) { if (id == null) { throw exception(INTANGIBLE_ASSET_NOT_EXISTS); } FinIntangibleAssetDO asset = intangibleAssetMapper.selectById(id); if (asset == null) { throw exception(INTANGIBLE_ASSET_NOT_EXISTS); } return asset; } private void validateAssetCodeUnique(Long id, String assetCode) { FinIntangibleAssetDO asset = intangibleAssetMapper.selectByAssetCode(StrUtil.trim(assetCode)); if (asset == null) { return; } if (id == null || !asset.getId().equals(id)) { throw exception(INTANGIBLE_ASSET_CODE_DUPLICATE, assetCode); } } /** * 净值 = 原值 - 累计摊销,且不小于 0 */ private void fillNetValue(FinIntangibleAssetDO asset) { BigDecimal netValue = defaultIfNull(asset.getOriginalValue()) .subtract(defaultIfNull(asset.getAccumulatedAmortization())); asset.setNetValue(netValue.max(BigDecimal.ZERO)); } private static BigDecimal defaultIfNull(BigDecimal amount) { return amount == null ? BigDecimal.ZERO : amount; } private static String formatAmount(BigDecimal amount) { return amount.setScale(2, RoundingMode.HALF_UP).toPlainString(); } }