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.FinIntangibleAssetDto;
|
import com.ruoyi.account.mapper.financial.FinIntangibleAssetMapper;
|
import com.ruoyi.account.pojo.financial.FinIntangibleAsset;
|
import com.ruoyi.account.service.financial.FinIntangibleAssetService;
|
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.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
|
/**
|
* 无形资产服务实现。
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class FinIntangibleAssetServiceImpl extends ServiceImpl<FinIntangibleAssetMapper, FinIntangibleAsset> implements FinIntangibleAssetService {
|
|
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<FinIntangibleAsset> pageList(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto) {
|
LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
|
if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetCode())) {
|
wrapper.like(FinIntangibleAsset::getAssetCode, queryDto.getAssetCode());
|
}
|
if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetName())) {
|
wrapper.like(FinIntangibleAsset::getAssetName, queryDto.getAssetName());
|
}
|
if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCategory())) {
|
wrapper.eq(FinIntangibleAsset::getCategory, queryDto.getCategory());
|
}
|
if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) {
|
wrapper.eq(FinIntangibleAsset::getStatus, queryDto.getStatus());
|
}
|
wrapper.orderByDesc(FinIntangibleAsset::getId);
|
return page(page, wrapper);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Boolean add(FinIntangibleAssetDto dto) {
|
validateForSave(dto, false);
|
if (StringUtils.isEmpty(dto.getAssetCode())) {
|
dto.setAssetCode(generateAssetCode());
|
}
|
BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate());
|
dto.setResidualRate(residualRate);
|
BigDecimal accumulatedAmortization = defaultMoney(dto.getAccumulatedAmortization());
|
dto.setAccumulatedAmortization(accumulatedAmortization);
|
dto.setNetValue(calculateNetValue(dto.getOriginalValue(), accumulatedAmortization));
|
if (StringUtils.isEmpty(dto.getStatus())) {
|
dto.setStatus("in_use");
|
}
|
return save(dto);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Boolean update(FinIntangibleAssetDto dto) {
|
if (dto == null || dto.getId() == null) {
|
throw new ServiceException("修改失败,资产ID不能为空");
|
}
|
FinIntangibleAsset 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.getAccumulatedAmortization() == null) {
|
dto.setAccumulatedAmortization(defaultMoney(existed.getAccumulatedAmortization()));
|
}
|
dto.setNetValue(calculateNetValue(dto.getOriginalValue(), dto.getAccumulatedAmortization()));
|
if (dto.getNetValue().compareTo(BigDecimal.ZERO) <= 0) {
|
dto.setStatus("amortized");
|
} else if ("amortized".equals(dto.getStatus())) {
|
dto.setStatus("in_use");
|
}
|
if (dto.getValidityDate() != null
|
&& dto.getValidityDate().isBefore(LocalDate.now())
|
&& !"amortized".equals(dto.getStatus())) {
|
dto.setStatus("expired");
|
}
|
return updateById(dto);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Boolean deleteByIds(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new ServiceException("删除失败,请选择要删除的数据");
|
}
|
return removeByIds(ids);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Map<String, Object> amortize(List<Long> ids) {
|
LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
|
if (ids != null && !ids.isEmpty()) {
|
wrapper.in(FinIntangibleAsset::getId, ids);
|
} else {
|
wrapper.eq(FinIntangibleAsset::getStatus, "in_use");
|
}
|
List<FinIntangibleAsset> assets = list(wrapper);
|
BigDecimal totalMonthlyAmortization = ZERO;
|
int processedCount = 0;
|
for (FinIntangibleAsset asset : assets) {
|
if (!"in_use".equals(asset.getStatus())) {
|
continue;
|
}
|
BigDecimal monthlyAmortization = calculateMonthlyAmortization(
|
asset.getOriginalValue(),
|
asset.getResidualRate(),
|
asset.getAmortizationPeriod()
|
);
|
BigDecimal accumulatedAmortization = defaultMoney(asset.getAccumulatedAmortization()).add(monthlyAmortization);
|
if (accumulatedAmortization.compareTo(defaultMoney(asset.getOriginalValue())) > 0) {
|
accumulatedAmortization = defaultMoney(asset.getOriginalValue());
|
}
|
asset.setAccumulatedAmortization(roundMoney(accumulatedAmortization));
|
asset.setNetValue(calculateNetValue(asset.getOriginalValue(), asset.getAccumulatedAmortization()));
|
|
// 规则:当净值 <= 0 时,净值归零并标记为已摊销完。
|
if (asset.getNetValue().compareTo(BigDecimal.ZERO) <= 0) {
|
asset.setNetValue(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
|
asset.setStatus("amortized");
|
} else if (asset.getValidityDate() != null && asset.getValidityDate().isBefore(LocalDate.now())) {
|
asset.setStatus("expired");
|
}
|
updateById(asset);
|
processedCount++;
|
totalMonthlyAmortization = totalMonthlyAmortization.add(monthlyAmortization);
|
}
|
Map<String, Object> result = new HashMap<>(4);
|
result.put("processedCount", processedCount);
|
result.put("totalMonthlyAmortization", roundMoney(totalMonthlyAmortization));
|
result.put("executionTime", LocalDateTime.now());
|
return result;
|
}
|
|
/**
|
* 按文档规则校验无形资产数据。
|
*/
|
private void validateForSave(FinIntangibleAssetDto 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.getAcquisitionDate() == null) {
|
throw new ServiceException("取得日期不能为空");
|
}
|
if (dto.getOriginalValue() == null || dto.getOriginalValue().compareTo(BigDecimal.ZERO) < 0) {
|
throw new ServiceException("资产原值不能为空且不能小于0");
|
}
|
if (dto.getAmortizationPeriod() == null || dto.getAmortizationPeriod() <= 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<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
|
wrapper.eq(FinIntangibleAsset::getAssetCode, dto.getAssetCode());
|
if (isUpdate) {
|
wrapper.ne(FinIntangibleAsset::getId, dto.getId());
|
}
|
if (count(wrapper) > 0) {
|
throw new ServiceException("资产编号已存在,请勿重复提交");
|
}
|
}
|
}
|
|
/**
|
* 无形资产摊销公式:
|
* monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)
|
*/
|
private BigDecimal calculateMonthlyAmortization(BigDecimal originalValue, BigDecimal residualRate, Integer amortizationPeriod) {
|
BigDecimal normalizedOriginalValue = defaultMoney(originalValue);
|
BigDecimal normalizedResidualRate = normalizeResidualRate(residualRate);
|
BigDecimal amortizableRatio = BigDecimal.ONE.subtract(normalizedResidualRate.divide(ONE_HUNDRED, 8, RoundingMode.HALF_UP));
|
BigDecimal months = BigDecimal.valueOf((long) amortizationPeriod * 12L);
|
if (months.compareTo(BigDecimal.ZERO) <= 0) {
|
throw new ServiceException("摊销年限无效,无法计提摊销");
|
}
|
return roundMoney(normalizedOriginalValue.multiply(amortizableRatio).divide(months, 8, RoundingMode.HALF_UP));
|
}
|
|
/**
|
* 净值 = 原值 - 累计摊销。
|
*/
|
private BigDecimal calculateNetValue(BigDecimal originalValue, BigDecimal accumulatedAmortization) {
|
BigDecimal value = defaultMoney(originalValue).subtract(defaultMoney(accumulatedAmortization));
|
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 "WX" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
|
}
|
}
|