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.FinFixedAssetDepreciateRespVO;
|
import cn.iocoder.yudao.module.finance.controller.admin.asset.vo.FinFixedAssetPageReqVO;
|
import cn.iocoder.yudao.module.finance.controller.admin.asset.vo.FinFixedAssetSaveReqVO;
|
import cn.iocoder.yudao.module.finance.dal.dataobject.asset.FinFixedAssetDO;
|
import cn.iocoder.yudao.module.finance.dal.mysql.asset.FinFixedAssetMapper;
|
import cn.iocoder.yudao.module.finance.enums.FinFixedAssetStatusEnum;
|
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 FinFixedAssetServiceImpl implements FinFixedAssetService {
|
|
@Resource
|
private FinFixedAssetMapper fixedAssetMapper;
|
|
@Override
|
public Long createFixedAsset(FinFixedAssetSaveReqVO createReqVO) {
|
// 1. 校验资产编号唯一
|
validateAssetCodeUnique(null, createReqVO.getAssetCode());
|
// 2. 插入:新资产尚未计提折旧
|
FinFixedAssetDO asset = BeanUtils.toBean(createReqVO, FinFixedAssetDO.class);
|
asset.setAssetCode(StrUtil.trim(asset.getAssetCode()));
|
asset.setResidualRate(ObjectUtil.defaultIfNull(asset.getResidualRate(), BigDecimal.ZERO));
|
asset.setStatus(ObjectUtil.defaultIfNull(asset.getStatus(), FinFixedAssetStatusEnum.IN_USE.getStatus()));
|
asset.setAccumulatedDepreciation(BigDecimal.ZERO);
|
fillNetValue(asset);
|
fixedAssetMapper.insert(asset);
|
return asset.getId();
|
}
|
|
@Override
|
public void updateFixedAsset(FinFixedAssetSaveReqVO updateReqVO) {
|
// 1.1 校验存在
|
FinFixedAssetDO oldAsset = validateFixedAssetExists(updateReqVO.getId());
|
// 1.2 校验资产编号唯一
|
validateAssetCodeUnique(updateReqVO.getId(), updateReqVO.getAssetCode());
|
|
// 2.1 更新(累计折旧不从前端接收,沿用库中值)
|
FinFixedAssetDO updateObj = BeanUtils.toBean(updateReqVO, FinFixedAssetDO.class);
|
updateObj.setAssetCode(StrUtil.trim(updateObj.getAssetCode()));
|
updateObj.setResidualRate(ObjectUtil.defaultIfNull(updateObj.getResidualRate(), BigDecimal.ZERO));
|
updateObj.setStatus(ObjectUtil.defaultIfNull(updateObj.getStatus(), oldAsset.getStatus()));
|
updateObj.setAccumulatedDepreciation(ObjectUtil.defaultIfNull(oldAsset.getAccumulatedDepreciation(), BigDecimal.ZERO));
|
// 2.2 原值、残值率变化后,按新的应计提总额封顶累计折旧,并重算净值
|
BigDecimal depreciableBase = calcDepreciableBase(defaultIfNull(updateObj.getOriginalValue()),
|
updateObj.getResidualRate());
|
if (updateObj.getAccumulatedDepreciation().compareTo(depreciableBase) > 0) {
|
updateObj.setAccumulatedDepreciation(depreciableBase);
|
}
|
fillNetValue(updateObj);
|
fixedAssetMapper.updateById(updateObj);
|
}
|
|
@Override
|
public void deleteFixedAsset(Long id) {
|
validateFixedAssetExists(id);
|
fixedAssetMapper.deleteById(id);
|
}
|
|
@Override
|
public void deleteFixedAssetList(List<Long> ids) {
|
if (CollUtil.isEmpty(ids)) {
|
return;
|
}
|
fixedAssetMapper.deleteByIds(ids);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public FinFixedAssetDepreciateRespVO depreciateFixedAssetList(List<Long> ids) {
|
// 1. 查询待计提的资产:已报废的不再计提
|
List<FinFixedAssetDO> assets = CollUtil.isEmpty(ids)
|
? fixedAssetMapper.selectList() : fixedAssetMapper.selectListByIds(ids);
|
List<FinFixedAssetDO> updateList = new ArrayList<>();
|
BigDecimal totalMonthlyDepreciation = BigDecimal.ZERO;
|
for (FinFixedAssetDO asset : assets) {
|
if (FinFixedAssetStatusEnum.SCRAPPED.getStatus().equals(asset.getStatus())) {
|
continue;
|
}
|
BigDecimal originalValue = defaultIfNull(asset.getOriginalValue());
|
BigDecimal residualRate = defaultIfNull(asset.getResidualRate());
|
int usefulLife = ObjectUtil.defaultIfNull(asset.getUsefulLife(), 0);
|
BigDecimal accumulated = defaultIfNull(asset.getAccumulatedDepreciation());
|
// 1.1 已提足的不再计提
|
BigDecimal depreciableBase = calcDepreciableBase(originalValue, residualRate);
|
BigDecimal remaining = depreciableBase.subtract(accumulated);
|
if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
|
continue;
|
}
|
// 1.2 参数异常导致月折旧额非正
|
BigDecimal monthlyDepreciation = usefulLife <= 0 ? BigDecimal.ZERO
|
: calcMonthlyAmount(originalValue, residualRate, usefulLife);
|
if (monthlyDepreciation.compareTo(BigDecimal.ZERO) <= 0) {
|
throw exception(FIXED_ASSET_DEPRECIATE_NEGATIVE, asset.getAssetName(),
|
formatAmount(originalValue), usefulLife, formatAmount(residualRate));
|
}
|
// 1.3 最后一期按剩余可折旧额封顶,净值不会被提成负数
|
BigDecimal periodDepreciation = monthlyDepreciation.min(remaining);
|
BigDecimal newAccumulated = accumulated.add(periodDepreciation);
|
updateList.add(new FinFixedAssetDO().setId(asset.getId())
|
.setAccumulatedDepreciation(newAccumulated)
|
.setNetValue(originalValue.subtract(newAccumulated).max(BigDecimal.ZERO)));
|
totalMonthlyDepreciation = totalMonthlyDepreciation.add(periodDepreciation);
|
}
|
// 2. 没有可计提的资产,明确告知
|
if (updateList.isEmpty()) {
|
throw exception(FIXED_ASSET_DEPRECIATE_EMPTY);
|
}
|
// 3. 批量更新
|
fixedAssetMapper.updateBatch(updateList);
|
return new FinFixedAssetDepreciateRespVO().setProcessedCount(updateList.size())
|
.setTotalMonthlyDepreciation(totalMonthlyDepreciation);
|
}
|
|
@Override
|
public FinFixedAssetDO getFixedAsset(Long id) {
|
return fixedAssetMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<FinFixedAssetDO> getFixedAssetPage(FinFixedAssetPageReqVO pageReqVO) {
|
return fixedAssetMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public List<FinFixedAssetDO> getFixedAssetList(Collection<Long> ids) {
|
if (CollUtil.isEmpty(ids)) {
|
return Collections.emptyList();
|
}
|
return fixedAssetMapper.selectListByIds(ids);
|
}
|
|
@Override
|
public List<FinFixedAssetDO> getFixedAssetList() {
|
return fixedAssetMapper.selectList();
|
}
|
|
private FinFixedAssetDO validateFixedAssetExists(Long id) {
|
if (id == null) {
|
throw exception(FIXED_ASSET_NOT_EXISTS);
|
}
|
FinFixedAssetDO asset = fixedAssetMapper.selectById(id);
|
if (asset == null) {
|
throw exception(FIXED_ASSET_NOT_EXISTS);
|
}
|
return asset;
|
}
|
|
private void validateAssetCodeUnique(Long id, String assetCode) {
|
FinFixedAssetDO asset = fixedAssetMapper.selectByAssetCode(StrUtil.trim(assetCode));
|
if (asset == null) {
|
return;
|
}
|
if (id == null || !asset.getId().equals(id)) {
|
throw exception(FIXED_ASSET_CODE_DUPLICATE, assetCode);
|
}
|
}
|
|
/**
|
* 净值 = 原值 - 累计折旧,且不小于 0
|
*/
|
private void fillNetValue(FinFixedAssetDO asset) {
|
BigDecimal netValue = defaultIfNull(asset.getOriginalValue())
|
.subtract(defaultIfNull(asset.getAccumulatedDepreciation()));
|
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();
|
}
|
|
}
|