package com.ruoyi.account.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.account.bean.dto.AccountReportDto; import com.ruoyi.account.bean.dto.DeviceTypeDetail; import com.ruoyi.account.bean.dto.DeviceTypeDistributionVO; import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto; import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto; import com.ruoyi.account.bean.dto.sales.SalesOutboundDto; import com.ruoyi.account.bean.dto.sales.SalesReturnDto; import com.ruoyi.account.bean.vo.AccountReportVo; import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo; import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo; import com.ruoyi.account.bean.vo.sales.SalesOutboundVo; import com.ruoyi.account.bean.vo.sales.SalesReturnVo; import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper; import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper; import com.ruoyi.account.pojo.purchase.AccountPurchasePayment; import com.ruoyi.account.pojo.sales.AccountSalesCollection; import com.ruoyi.account.service.AccountingService; import com.ruoyi.device.mapper.DeviceLedgerMapper; import com.ruoyi.device.pojo.DeviceLedger; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.procurementrecord.mapper.CustomStorageMapper; import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper; import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper; import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper; import com.ruoyi.procurementrecord.pojo.CustomStorage; import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut; import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage; import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper; import com.ruoyi.stock.mapper.StockInRecordMapper; import com.ruoyi.stock.mapper.StockOutRecordMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Year; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; /** * @author :yys * @date : 2026/1/17 10:41 */ @Service @Slf4j @RequiredArgsConstructor public class AccountingServiceImpl implements AccountingService { private final DeviceLedgerMapper deviceLedgerMapper; private final CustomStorageMapper customStorageMapper; private final ProcurementRecordMapper procurementRecordMapper; private final ProcurementRecordOutMapper procurementRecordOutMapper; private final AccountSalesCollectionMapper accountSalesCollectionMapper; private final AccountPurchasePaymentMapper accountPurchasePaymentMapper; private final StockOutRecordMapper stockOutRecordMapper; private final ReturnManagementMapper returnManagementMapper; private final StockInRecordMapper stockInRecordMapper; private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper; @Override public AjaxResult total(Integer year) { Map map = new HashMap<>(); map.put("deprAmount",0); // 折旧金额 map.put("deviceTotal",0); // 设备总数 map.put("deviceAmount",0); // 设备资产原值 map.put("netValue",0); // 净值 map.put("debt",0); // 负债 map.put("inventoryValue",0); // 库存资产 LambdaQueryWrapper deviceLedgerLambdaQueryWrapper = new LambdaQueryWrapper<>(); deviceLedgerLambdaQueryWrapper.like(DeviceLedger::getCreateTime,year); List deviceLedgers = deviceLedgerMapper.selectList(deviceLedgerLambdaQueryWrapper); if(CollectionUtils.isNotEmpty(deviceLedgers)){ map.put("deviceTotal",deviceLedgers.size()); BigDecimal reduce = deviceLedgers.stream() .map(DeviceLedger::getTaxIncludingPriceTotal) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add); map.put("deviceAmount",reduce); List collect = deviceLedgers.stream().filter(deviceLedger -> deviceLedger.getIsDepr() != null && deviceLedger.getIsDepr() == 1).collect(Collectors.toList()); // 根据当前年份和设备录入时间年份比较 * 每年折旧金额 = 设备折旧金额 BigDecimal total = new BigDecimal(0); if(CollectionUtils.isNotEmpty(collect)){ for (DeviceLedger deviceLedger : collect) { BigDecimal totalDepreciation = calculatePreciseDepreciation(deviceLedger); total = total.add(totalDepreciation); } map.put("deprAmount",total); } // 净值 = 设备资产原值 - 设备累计折旧金额 map.put("netValue",reduce.subtract(total)); } // 负债 map.put("debt",BigDecimal.ZERO); // 库存资产 LambdaQueryWrapper procurementRecordStorageLambdaQueryWrapper = new LambdaQueryWrapper<>(); procurementRecordStorageLambdaQueryWrapper.like(ProcurementRecordStorage::getCreateTime,year); List procurementRecordStorages = procurementRecordMapper.selectList(procurementRecordStorageLambdaQueryWrapper); BigDecimal procurementRecordTotal = new BigDecimal(0); if(CollectionUtils.isNotEmpty(procurementRecordStorages)){ // 获取出库数据 LambdaQueryWrapper procurementRecordOutLambdaQueryWrapper = new LambdaQueryWrapper<>(); procurementRecordOutLambdaQueryWrapper.like(ProcurementRecordOut::getCreateTime,year) .in(ProcurementRecordOut::getProcurementRecordStorageId, procurementRecordStorages .stream() .map(ProcurementRecordStorage::getId) .collect(Collectors.toList())) .in(ProcurementRecordOut::getType,Arrays.asList(1,2)); List procurementRecordOuts = procurementRecordOutMapper.selectList(procurementRecordOutLambdaQueryWrapper); for (ProcurementRecordStorage procurementRecordStorage : procurementRecordStorages) { // 采购,生产入库总价值 procurementRecordTotal.add(procurementRecordStorage.getUnitPrice().multiply(procurementRecordStorage.getInboundNum())); // 通过入库id,类型获取出库数据 List procurementRecordOutsByStorageId = procurementRecordOuts.stream() .filter(procurementRecordOut -> procurementRecordOut.getProcurementRecordStorageId().equals(procurementRecordStorage.getId()) && procurementRecordOut.getType().equals(procurementRecordStorage.getType())) .collect(Collectors.toList()); if(CollectionUtils.isNotEmpty(procurementRecordOutsByStorageId)){ for (ProcurementRecordOut procurementRecordOut : procurementRecordOutsByStorageId) { // 采购,生产出库总价值 procurementRecordTotal.subtract(procurementRecordStorage.getUnitPrice().multiply(procurementRecordOut.getInboundNum())); } } } } LambdaQueryWrapper customStorageLambdaQueryWrapper = new LambdaQueryWrapper<>(); customStorageLambdaQueryWrapper.like(CustomStorage::getInboundDate,year); List customStorages = customStorageMapper.selectList(customStorageLambdaQueryWrapper); BigDecimal customStorageTotal = new BigDecimal(0); if(CollectionUtils.isNotEmpty(customStorages)){ // 获取出库数据 LambdaQueryWrapper procurementRecordOutLambdaQueryWrapper = new LambdaQueryWrapper<>(); procurementRecordOutLambdaQueryWrapper.like(ProcurementRecordOut::getCreateTime,year) .in(ProcurementRecordOut::getProcurementRecordStorageId, customStorages .stream() .map(CustomStorage::getId) .collect(Collectors.toList())) .eq(ProcurementRecordOut::getType,3); List procurementRecordOuts = procurementRecordOutMapper.selectList(procurementRecordOutLambdaQueryWrapper); customStorages.forEach(customStorage -> { // 自定义入库总价值 customStorageTotal.add(customStorage.getTaxInclusiveUnitPrice().multiply(customStorage.getInboundNum())); // 通过入库id,类型获取出库数据 List procurementRecordOutsByStorageId = procurementRecordOuts.stream() .filter(procurementRecordOut -> procurementRecordOut.getProcurementRecordStorageId().equals(customStorage.getId())) .collect(Collectors.toList()); if(CollectionUtils.isNotEmpty(procurementRecordOutsByStorageId)){ for (ProcurementRecordOut procurementRecordOut : procurementRecordOutsByStorageId) { // 自定义出库总价值 customStorageTotal.subtract(procurementRecordOut.getInboundNum().multiply(customStorage.getTaxInclusiveUnitPrice())); } } }); } map.put("inventoryValue",procurementRecordTotal.add(customStorageTotal)); return AjaxResult.success( map); } /** * 计算设备累计折旧金额 * @param deviceLedger 设备台账实体 * @return 累计折旧金额(BigDecimal,保留2位小数) */ public static BigDecimal calculateTotalDepreciation(DeviceLedger deviceLedger) { // 1. 空值校验 if (deviceLedger == null) { return BigDecimal.ZERO; } // 2. 判断是否开启折旧,未开启则折旧金额为0 Integer isDepr = deviceLedger.getIsDepr(); if (isDepr == null || isDepr != 1) { // 1-是 2-否 return BigDecimal.ZERO; } // 3. 获取每年折旧金额,为空则返回0 BigDecimal annualDepreciation = deviceLedger.getAnnualDepreciationAmount(); if (annualDepreciation == null || annualDepreciation.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ZERO; } // 4. 获取设备录入时间,为空则返回0 LocalDateTime createTime = deviceLedger.getCreateTime(); if (createTime == null) { return BigDecimal.ZERO; } // 5. 计算年份差值 int currentYear = Year.now().getValue(); // 当前年份 int createYear = createTime.getYear(); // 设备录入年份 int yearDiff = currentYear - createYear; // 6. 处理年份差值为负数的情况(录入时间在未来) if (yearDiff < 0) { return BigDecimal.ZERO; } // 7. 计算总折旧金额 = 年份差值 * 每年折旧金额 BigDecimal totalDepreciation = annualDepreciation.multiply(BigDecimal.valueOf(yearDiff)); // 8. 返回保留2位小数的结果(金额规范) return totalDepreciation.setScale(2, BigDecimal.ROUND_HALF_UP); } /** * 【进阶版】按实际天数计算折旧(更精准) * @param deviceLedger 设备台账实体 * @return 累计折旧金额 */ public static BigDecimal calculatePreciseDepreciation(DeviceLedger deviceLedger) { if (deviceLedger == null) { return BigDecimal.ZERO; } // 判断是否开启折旧 Integer isDepr = deviceLedger.getIsDepr(); if (isDepr == null || isDepr != 1) { return BigDecimal.ZERO; } // 获取每年折旧金额 BigDecimal annualDepreciation = deviceLedger.getAnnualDepreciationAmount(); if (annualDepreciation == null || annualDepreciation.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ZERO; } // 获取设备录入时间 LocalDateTime createTime = deviceLedger.getCreateTime(); if (createTime == null) { return BigDecimal.ZERO; } // 当前时间 LocalDateTime now = LocalDateTime.now(); // 如果录入时间在未来,返回0 if (createTime.isAfter(now)) { return BigDecimal.ZERO; } // 计算总天数 long days = ChronoUnit.DAYS.between(createTime, now); // 按每年365天计算折旧金额 BigDecimal dailyDepreciation = annualDepreciation.divide(BigDecimal.valueOf(365), 6, BigDecimal.ROUND_HALF_UP); BigDecimal totalDepreciation = dailyDepreciation.multiply(BigDecimal.valueOf(days)); return totalDepreciation.setScale(2, BigDecimal.ROUND_HALF_UP); } @Override public AjaxResult deviceTypeDistribution(Integer year) { // 2. 组装返回VO DeviceTypeDistributionVO vo = new DeviceTypeDistributionVO(); List details = deviceLedgerMapper.getDeviceTypeDistributionByYear( year); vo.setDetails(details); if(CollectionUtils.isNotEmpty(details)){ // 3. 提取图表所需的分类和数据 vo.setCategories(details.stream() .map(DeviceTypeDetail::getType) .collect(Collectors.toList())); vo.setCountData(details.stream() .map(DeviceTypeDetail::getCount) .collect(Collectors.toList())); vo.setAmountData(details.stream() .map(DeviceTypeDetail::getAmount) .collect(Collectors.toList())); vo.setTotalCount(vo.getCategories().size()); } return AjaxResult.success(vo); } @Override public AjaxResult calculateDepreciation(Page page, Integer year) { LambdaQueryWrapper deviceLedgerLambdaQueryWrapper = new LambdaQueryWrapper<>(); deviceLedgerLambdaQueryWrapper.like(DeviceLedger::getCreateTime,year) .eq(DeviceLedger::getIsDepr,1); IPage deviceLedgerIPage = deviceLedgerMapper.selectPage(page, deviceLedgerLambdaQueryWrapper); for (DeviceLedger record : deviceLedgerIPage.getRecords()) { record.setDeprAmount(calculatePreciseDepreciation(record)); record.setNetValue(record.getTaxIncludingPriceTotal().subtract(record.getDeprAmount())); } return AjaxResult.success(deviceLedgerIPage); } @Override public AccountReportVo getAccountStatementDetailsByMonth(AccountReportDto accountReportDto) { AccountReportVo accountReportVo = new AccountReportVo(); LocalDate start = accountReportDto.getEntryDateStart(); LocalDate end = accountReportDto.getEntryDateEnd(); DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM"); // ========== 1. 顶部卡片数据 ========== // 1.1 总营收 = 收款单总金额 List accountSalesCollections = accountSalesCollectionMapper.selectList( Wrappers.lambdaQuery() .between(AccountSalesCollection::getCollectionDate, start, end) ); BigDecimal totalIncome = Optional.of( accountSalesCollections.stream() .map(AccountSalesCollection::getCollectionAmount) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add) ).orElse(BigDecimal.ZERO); accountReportVo.setTotalIncome(totalIncome); // 1.2 总支出 = 付款单总金额 List accountPurchasePayments = accountPurchasePaymentMapper.selectList( Wrappers.lambdaQuery() .between(AccountPurchasePayment::getPaymentDate, start, end) ); BigDecimal totalExpense = Optional.of( accountPurchasePayments.stream() .map(AccountPurchasePayment::getPaymentAmount) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add) ).orElse(BigDecimal.ZERO); accountReportVo.setTotalExpense(totalExpense); // 1.3 应收账款 = 销售出库金额合计 - 销售退货金额合计 SalesOutboundDto salesOutboundDto = new SalesOutboundDto(); salesOutboundDto.setStartDate(accountReportDto.getEntryDateStart()); salesOutboundDto.setEndDate(accountReportDto.getEntryDateEnd()); List salesOutboundVos = stockOutRecordMapper.listPageAccountSales(new Page(1, -1), salesOutboundDto).getRecords(); BigDecimal salesOutAmount = Optional.of( salesOutboundVos.stream() .map(SalesOutboundVo::getOutboundAmount) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add) ).orElse(BigDecimal.ZERO); SalesReturnDto salesReturnDto = new SalesReturnDto(); salesReturnDto.setStartDate(accountReportDto.getEntryDateStart()); salesReturnDto.setEndDate(accountReportDto.getEntryDateEnd()); List salesReturnVos = returnManagementMapper.listPageAccountSalesReturn(new Page(1, -1), salesReturnDto).getRecords(); BigDecimal salesReturnAmount = Optional.of( salesReturnVos.stream() .map(SalesReturnVo::getRefundAmount) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add) ).orElse(BigDecimal.ZERO); accountReportVo.setAccountsReceivable(salesOutAmount.subtract(salesReturnAmount)); // 1.4 应付账款 = 采购入库金额合计 - 采购退货金额合计 PurchaseInboundDto purchaseInboundDto = new PurchaseInboundDto(); purchaseInboundDto.setStartDate(accountReportDto.getEntryDateStart()); purchaseInboundDto.setEndDate(accountReportDto.getEntryDateEnd()); List purchaseInboundVos = stockInRecordMapper.listPageAccountPurchase(new Page(1, -1), purchaseInboundDto).getRecords(); BigDecimal purchaseInAmount = Optional.of( purchaseInboundVos.stream() .map(PurchaseInboundVo::getInboundAmount) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add) ).orElse(BigDecimal.ZERO); PurchaseReturnDto purchaseReturnDto = new PurchaseReturnDto(); purchaseReturnDto.setStartDate(accountReportDto.getEntryDateStart()); purchaseReturnDto.setEndDate(accountReportDto.getEntryDateEnd()); List purchaseReturnVos = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1, -1), purchaseReturnDto).getRecords(); BigDecimal purchaseReturnAmount = Optional.of( purchaseReturnVos.stream() .map(PurchaseReturnVo::getTotalAmount) .filter(Objects::nonNull) .reduce(BigDecimal.ZERO, BigDecimal::add) ).orElse(BigDecimal.ZERO); accountReportVo.setAccountsPayable(purchaseInAmount.subtract(purchaseReturnAmount)); // 1.5 净利润 = 总营收 - 总支出 BigDecimal netProfit = totalIncome.subtract(totalExpense); accountReportVo.setNetRevenue(netProfit); // ========== 2. 折线图:月度营收/支出/净利润趋势 ========== Map monthIncomeMap = new HashMap<>(); Map monthExpenseMap = new HashMap<>(); // 月度营收 accountSalesCollections.forEach(item -> { String month = item.getCollectionDate().format(monthFormatter); monthIncomeMap.put(month, monthIncomeMap.getOrDefault(month, BigDecimal.ZERO) .add(Optional.ofNullable(item.getCollectionAmount()).orElse(BigDecimal.ZERO))); }); // 月度支出 accountPurchasePayments.forEach(item -> { String month = item.getPaymentDate().format(monthFormatter); monthExpenseMap.put(month, monthExpenseMap.getOrDefault(month, BigDecimal.ZERO) .add(Optional.ofNullable(item.getPaymentAmount()).orElse(BigDecimal.ZERO))); }); // 生成连续月份列表 List monthList = new ArrayList<>(); LocalDate current = start.withDayOfMonth(1); while (!current.isAfter(end.withDayOfMonth(1))) { monthList.add(current.format(monthFormatter)); current = current.plusMonths(1); } // 组装趋势数据 List trendList = new ArrayList<>(); for (String month : monthList) { BigDecimal income = monthIncomeMap.getOrDefault(month, BigDecimal.ZERO); BigDecimal expense = monthExpenseMap.getOrDefault(month, BigDecimal.ZERO); AccountReportVo.MonthlyTrendVO trend = new AccountReportVo.MonthlyTrendVO(); trend.setMonth(month); trend.setIncome(income); trend.setExpense(expense); trend.setProfit(income.subtract(expense)); trendList.add(trend); } accountReportVo.setMonthlyTrendList(trendList); // ========== 3. 柱状图:月度应收/应付数据 ========== Map monthReceivableMap = new HashMap<>(); Map monthPayableMap = new HashMap<>(); // 月度应收(销售出库-退货) salesOutboundVos.forEach(item -> { String month = item.getShippingDate().format(monthFormatter); monthReceivableMap.put(month, monthReceivableMap.getOrDefault(month, BigDecimal.ZERO) .add(Optional.ofNullable(item.getOutboundAmount()).orElse(BigDecimal.ZERO))); }); salesReturnVos.forEach(item -> { String month = item.getMakeTime().format(monthFormatter); monthReceivableMap.put(month, monthReceivableMap.getOrDefault(month, BigDecimal.ZERO) .subtract(Optional.ofNullable(item.getRefundAmount()).orElse(BigDecimal.ZERO))); }); // 月度应付(采购入库-退货) purchaseInboundVos.forEach(item -> { String month = item.getInboundDate().format(monthFormatter); monthPayableMap.put(month, monthPayableMap.getOrDefault(month, BigDecimal.ZERO) .add(Optional.ofNullable(item.getInboundAmount()).orElse(BigDecimal.ZERO))); }); purchaseReturnVos.forEach(item -> { String month = item.getPreparedAt().format(monthFormatter); monthPayableMap.put(month, monthPayableMap.getOrDefault(month, BigDecimal.ZERO) .subtract(Optional.ofNullable(item.getTotalAmount()).orElse(BigDecimal.ZERO))); }); // 组装应收应付数据 List rpList = new ArrayList<>(); for (String month : monthList) { AccountReportVo.ReceivablePayableVO rp = new AccountReportVo.ReceivablePayableVO(); rp.setMonth(month); rp.setReceivable(monthReceivableMap.getOrDefault(month, BigDecimal.ZERO)); rp.setPayable(monthPayableMap.getOrDefault(month, BigDecimal.ZERO)); rpList.add(rp); } accountReportVo.setReceivablePayableList(rpList); return accountReportVo; } }