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<Long> ids) {
|
if (CollUtil.isEmpty(ids)) {
|
return;
|
}
|
intangibleAssetMapper.deleteByIds(ids);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public FinIntangibleAssetAmortizeRespVO amortizeIntangibleAssetList(List<Long> ids) {
|
// 1. 查询待计提的资产:已失效的不再计提
|
List<FinIntangibleAssetDO> assets = CollUtil.isEmpty(ids)
|
? intangibleAssetMapper.selectList() : intangibleAssetMapper.selectListByIds(ids);
|
List<FinIntangibleAssetDO> 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<FinIntangibleAssetDO> getIntangibleAssetPage(FinIntangibleAssetPageReqVO pageReqVO) {
|
return intangibleAssetMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public List<FinIntangibleAssetDO> getIntangibleAssetList(Collection<Long> ids) {
|
if (CollUtil.isEmpty(ids)) {
|
return Collections.emptyList();
|
}
|
return intangibleAssetMapper.selectListByIds(ids);
|
}
|
|
@Override
|
public List<FinIntangibleAssetDO> 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();
|
}
|
|
}
|