package com.ruoyi.account.service.impl.financial; import com.ruoyi.account.bean.dto.financial.FinDetailLedgerQueryDto; import com.ruoyi.account.bean.dto.financial.FinLedgerQueryDto; import com.ruoyi.account.bean.vo.financial.FinLedgerEntryRecordVo; import com.ruoyi.account.bean.vo.financial.FinLedgerRowVo; import com.ruoyi.account.mapper.financial.FinVoucherEntryMapper; import com.ruoyi.account.service.financial.FinLedgerService; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.YearMonth; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.*; /** * 科目总账/明细账服务实现。 */ @Service @RequiredArgsConstructor public class FinLedgerServiceImpl implements FinLedgerService { private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); private final FinVoucherEntryMapper finVoucherEntryMapper; @Override public List queryGeneralLedger(FinLedgerQueryDto queryDto) { if (queryDto == null || StringUtils.isEmpty(queryDto.getSubjectCode())) { return Collections.emptyList(); } YearMonth startMonth = parseMonth(queryDto.getStartMonth(), "开始月份"); YearMonth endMonth = parseMonth(queryDto.getEndMonth(), "结束月份"); if (startMonth.isAfter(endMonth)) { throw new ServiceException("开始月份不能大于结束月份"); } return buildLedgerRows(queryDto.getSubjectCode(), startMonth, endMonth, null, null); } @Override public List queryDetailLedger(FinDetailLedgerQueryDto queryDto) { if (queryDto == null || StringUtils.isEmpty(queryDto.getSubjectCode())) { return Collections.emptyList(); } YearMonth startMonth = parseMonth(queryDto.getStartMonth(), "开始月份"); YearMonth endMonth = parseMonth(queryDto.getEndMonth(), "结束月份"); if (startMonth.isAfter(endMonth)) { throw new ServiceException("开始月份不能大于结束月份"); } return buildLedgerRows(queryDto.getSubjectCode(), startMonth, endMonth, queryDto.getAuxiliaryType(), queryDto.getAuxiliaryId()); } /** * 构建账簿行数据,输出期初、分录、本月合计、本年累计。 */ private List buildLedgerRows(String subjectCode, YearMonth startMonth, YearMonth endMonth, String auxiliaryType, String auxiliaryId) { LocalDate startDate = startMonth.atDay(1); LocalDate endDate = endMonth.atEndOfMonth(); List openingEntries = finVoucherEntryMapper.listPostedEntriesBefore( subjectCode, startDate, auxiliaryType, auxiliaryId ); BigDecimal openingBalance = calculateBalance(openingEntries); List currentPeriodEntries = finVoucherEntryMapper.listPostedEntries( subjectCode, startDate, endDate, auxiliaryType, auxiliaryId ); Map> monthEntriesMap = groupEntriesByMonth(currentPeriodEntries); List rows = new ArrayList<>(); BigDecimal runningBalance = openingBalance; BigDecimal yearDebit = ZERO; BigDecimal yearCredit = ZERO; for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) { rows.add(buildOpeningRow(month.atDay(1), runningBalance)); List monthEntries = monthEntriesMap.getOrDefault(month, Collections.emptyList()); BigDecimal monthDebit = ZERO; BigDecimal monthCredit = ZERO; for (FinLedgerEntryRecordVo entry : monthEntries) { BigDecimal debit = money(entry.getDebit()); BigDecimal credit = money(entry.getCredit()); runningBalance = runningBalance.add(debit).subtract(credit); monthDebit = monthDebit.add(debit); monthCredit = monthCredit.add(credit); FinLedgerRowVo row = new FinLedgerRowVo(); row.setRowType("entry"); row.setDate(entry.getVoucherDate()); row.setVoucherNo(entry.getVoucherNo()); row.setSummary(StringUtils.isNotEmpty(entry.getSummary()) ? entry.getSummary() : ""); row.setDebit(debit); row.setCredit(credit); row.setBalance(money(runningBalance)); row.setDirection(resolveDirection(runningBalance)); rows.add(row); } rows.add(buildMonthlyTotalRow(month.atEndOfMonth(), monthDebit, monthCredit, runningBalance)); yearDebit = yearDebit.add(monthDebit); yearCredit = yearCredit.add(monthCredit); } rows.add(buildYearlyTotalRow(endMonth.atEndOfMonth(), yearDebit, yearCredit, runningBalance)); return rows; } private Map> groupEntriesByMonth(List entries) { Map> map = new LinkedHashMap<>(); for (FinLedgerEntryRecordVo entry : entries) { if (entry.getVoucherDate() == null) { continue; } YearMonth month = YearMonth.from(entry.getVoucherDate()); map.computeIfAbsent(month, key -> new ArrayList<>()).add(entry); } return map; } private FinLedgerRowVo buildOpeningRow(LocalDate date, BigDecimal openingBalance) { FinLedgerRowVo row = new FinLedgerRowVo(); row.setRowType("opening"); row.setDate(date); row.setVoucherNo("-"); row.setSummary("期初余额"); row.setDebit(ZERO); row.setCredit(ZERO); row.setBalance(money(openingBalance)); row.setDirection(resolveDirection(openingBalance)); return row; } private FinLedgerRowVo buildMonthlyTotalRow(LocalDate date, BigDecimal monthDebit, BigDecimal monthCredit, BigDecimal monthBalance) { FinLedgerRowVo row = new FinLedgerRowVo(); row.setRowType("monthly_total"); row.setDate(date); row.setVoucherNo("-"); row.setSummary("本月合计"); row.setDebit(money(monthDebit)); row.setCredit(money(monthCredit)); row.setBalance(money(monthBalance)); row.setDirection(resolveDirection(monthBalance)); return row; } private FinLedgerRowVo buildYearlyTotalRow(LocalDate date, BigDecimal yearDebit, BigDecimal yearCredit, BigDecimal yearBalance) { FinLedgerRowVo row = new FinLedgerRowVo(); row.setRowType("yearly_total"); row.setDate(date); row.setVoucherNo("-"); row.setSummary("合计"); row.setDebit(money(yearDebit)); row.setCredit(money(yearCredit)); row.setBalance(money(yearBalance)); row.setDirection(resolveDirection(yearBalance)); return row; } private BigDecimal calculateBalance(List entries) { BigDecimal balance = ZERO; for (FinLedgerEntryRecordVo entry : entries) { balance = balance.add(money(entry.getDebit())).subtract(money(entry.getCredit())); } return money(balance); } private String resolveDirection(BigDecimal balance) { return money(balance).compareTo(BigDecimal.ZERO) >= 0 ? "借" : "贷"; } private YearMonth parseMonth(String value, String fieldLabel) { if (StringUtils.isEmpty(value)) { throw new ServiceException(fieldLabel + "不能为空,格式应为YYYY-MM"); } try { return YearMonth.parse(value, MONTH_FORMATTER); } catch (DateTimeParseException ex) { throw new ServiceException(fieldLabel + "格式错误,格式应为YYYY-MM"); } } private BigDecimal money(BigDecimal value) { if (value == null) { return ZERO; } return value.setScale(2, RoundingMode.HALF_UP); } }