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.FinFixedAssetDto; import com.ruoyi.account.mapper.financial.FinFixedAssetMapper; import com.ruoyi.account.pojo.financial.FinFixedAsset; import com.ruoyi.account.service.financial.FinFixedAssetService; 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.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; /** * 固定资产服务实现。 */ @Service @RequiredArgsConstructor public class FinFixedAssetServiceImpl extends ServiceImpl implements FinFixedAssetService { 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 pageList(Page page, FinFixedAssetDto queryDto) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetCode())) { wrapper.like(FinFixedAsset::getAssetCode, queryDto.getAssetCode()); } if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetName())) { wrapper.like(FinFixedAsset::getAssetName, queryDto.getAssetName()); } if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCategory())) { wrapper.eq(FinFixedAsset::getCategory, queryDto.getCategory()); } if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) { wrapper.eq(FinFixedAsset::getStatus, queryDto.getStatus()); } wrapper.orderByDesc(FinFixedAsset::getId); return page(page, wrapper); } @Override @Transactional(rollbackFor = Exception.class) public Boolean add(FinFixedAssetDto dto) { validateForSave(dto, false); if (StringUtils.isEmpty(dto.getAssetCode())) { dto.setAssetCode(generateAssetCode()); } BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate()); dto.setResidualRate(residualRate); BigDecimal accumulatedDepreciation = defaultMoney(dto.getAccumulatedDepreciation()); dto.setAccumulatedDepreciation(accumulatedDepreciation); dto.setNetValue(calculateNetValue(dto.getOriginalValue(), accumulatedDepreciation)); if (StringUtils.isEmpty(dto.getStatus())) { dto.setStatus("in_use"); } return save(dto); } @Override @Transactional(rollbackFor = Exception.class) public Boolean update(FinFixedAssetDto dto) { if (dto == null || dto.getId() == null) { throw new ServiceException("修改失败,资产ID不能为空"); } FinFixedAsset 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.getAccumulatedDepreciation() == null) { dto.setAccumulatedDepreciation(defaultMoney(existed.getAccumulatedDepreciation())); } dto.setNetValue(calculateNetValue(dto.getOriginalValue(), dto.getAccumulatedDepreciation())); return updateById(dto); } @Override @Transactional(rollbackFor = Exception.class) public Boolean deleteByIds(List ids) { if (ids == null || ids.isEmpty()) { throw new ServiceException("删除失败,请选择要删除的数据"); } return removeByIds(ids); } @Override @Transactional(rollbackFor = Exception.class) public Map depreciate(List ids) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); if (ids != null && !ids.isEmpty()) { wrapper.in(FinFixedAsset::getId, ids); } else { wrapper.eq(FinFixedAsset::getStatus, "in_use"); } List assets = list(wrapper); BigDecimal totalMonthlyDepreciation = ZERO; int processedCount = 0; for (FinFixedAsset asset : assets) { if (!"in_use".equals(asset.getStatus())) { continue; } BigDecimal monthlyDepreciation = calculateMonthlyDepreciation( asset.getOriginalValue(), asset.getResidualRate(), asset.getUsefulLife() ); BigDecimal accumulatedDepreciation = defaultMoney(asset.getAccumulatedDepreciation()).add(monthlyDepreciation); if (accumulatedDepreciation.compareTo(defaultMoney(asset.getOriginalValue())) > 0) { accumulatedDepreciation = defaultMoney(asset.getOriginalValue()); } asset.setAccumulatedDepreciation(roundMoney(accumulatedDepreciation)); asset.setNetValue(calculateNetValue(asset.getOriginalValue(), asset.getAccumulatedDepreciation())); updateById(asset); processedCount++; totalMonthlyDepreciation = totalMonthlyDepreciation.add(monthlyDepreciation); } Map result = new HashMap<>(4); result.put("processedCount", processedCount); result.put("totalMonthlyDepreciation", roundMoney(totalMonthlyDepreciation)); result.put("executionTime", LocalDateTime.now()); return result; } /** * 按文档规则校验固定资产数据。 */ private void validateForSave(FinFixedAssetDto dto, boolean isUpdate) { if (dto == null) { throw new ServiceException("固定资产数据不能为空"); } if (isUpdate && dto.getId() == null) { throw new ServiceException("修改失败,资产ID不能为空"); } if (StringUtils.isEmpty(dto.getAssetName())) { throw new ServiceException("资产名称不能为空"); } if (StringUtils.isEmpty(dto.getCategory())) { throw new ServiceException("资产类别不能为空"); } if (dto.getPurchaseDate() == null) { throw new ServiceException("购置日期不能为空"); } if (dto.getOriginalValue() == null || dto.getOriginalValue().compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("资产原值不能为空且不能小于0"); } if (dto.getUsefulLife() == null || dto.getUsefulLife() <= 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 wrapper = new LambdaQueryWrapper<>(); wrapper.eq(FinFixedAsset::getAssetCode, dto.getAssetCode()); if (isUpdate) { wrapper.ne(FinFixedAsset::getId, dto.getId()); } if (count(wrapper) > 0) { throw new ServiceException("资产编号已存在,请勿重复提交"); } } } /** * 固定资产折旧公式: * monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12) */ private BigDecimal calculateMonthlyDepreciation(BigDecimal originalValue, BigDecimal residualRate, Integer usefulLife) { BigDecimal normalizedOriginalValue = defaultMoney(originalValue); BigDecimal normalizedResidualRate = normalizeResidualRate(residualRate); BigDecimal depreciableRatio = BigDecimal.ONE.subtract(normalizedResidualRate.divide(ONE_HUNDRED, 8, RoundingMode.HALF_UP)); BigDecimal months = BigDecimal.valueOf((long) usefulLife * 12L); if (months.compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("使用年限无效,无法计提折旧"); } return roundMoney(normalizedOriginalValue.multiply(depreciableRatio).divide(months, 8, RoundingMode.HALF_UP)); } /** * 净值 = 原值 - 累计折旧。 */ private BigDecimal calculateNetValue(BigDecimal originalValue, BigDecimal accumulatedDepreciation) { BigDecimal value = defaultMoney(originalValue).subtract(defaultMoney(accumulatedDepreciation)); 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 "GD" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10); } }