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 ids) { if (CollUtil.isEmpty(ids)) { return; } fixedAssetMapper.deleteByIds(ids); } @Override @Transactional(rollbackFor = Exception.class) public FinFixedAssetDepreciateRespVO depreciateFixedAssetList(List ids) { // 1. 查询待计提的资产:已报废的不再计提 List assets = CollUtil.isEmpty(ids) ? fixedAssetMapper.selectList() : fixedAssetMapper.selectListByIds(ids); List 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 getFixedAssetPage(FinFixedAssetPageReqVO pageReqVO) { return fixedAssetMapper.selectPage(pageReqVO); } @Override public List getFixedAssetList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } return fixedAssetMapper.selectListByIds(ids); } @Override public List 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(); } }