AccountingService.java

package com.tdmconsult.ete.accounting;

import static com.tdmconsult.ete.accounting.SKR03Account.A1120;
import static com.tdmconsult.ete.accounting.SKR03Account.A1348;
import static com.tdmconsult.ete.accounting.SKR03Account.A2213;
import static com.tdmconsult.ete.accounting.SKR03Account.A2216;
import static com.tdmconsult.ete.accounting.SKR03Account.A2219;
import static com.tdmconsult.ete.accounting.SKR03Account.A2325;
import static com.tdmconsult.ete.accounting.SKR03Account.A2654;
import static com.tdmconsult.ete.accounting.SKR03Account.A2725;

import com.tdmconsult.ete.evaluation.EvaluationHrCalculator;
import com.tdmconsult.ete.evaluation.EvaluationService;
import com.tdmconsult.ete.evaluation.EvaluationState;
import com.tdmconsult.ete.masterdata.Security;
import com.tdmconsult.ete.transactions.PaymentTransactionRepository;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class AccountingService {

    public static final List<AccountingRecord> EB = List.of(
            AccountingRecord.builder()
                    .setTradingDate(LocalDate.of(2025, 1, 1))
                    .setDebitAccount(SKR03Account.A1348)
                    .setCreditAccount(SKR03Account.A9000)
                    .setAmount(new BigDecimal("1883553.68"))
                    .setDescription("EB Wert").build()
    );

    private final EvaluationService evaluationService;
    private final PaymentTransactionRepository paymentTransactionRepository;

    private List<AccountingRecord> createAccountingRecordsForPayments(final int fiscalYear) {

        final var records = new ArrayList<AccountingRecord>();
        final var payments = paymentTransactionRepository.findAll().stream()
                .filter(x -> x.getTradingDate().getYear() == fiscalYear)
                .toList();

        payments.forEach(p -> {

            records.add(AccountingRecord.builder()
                    .setDebitAccount(A1120)
                    .setCreditAccount(A2654)
                    .setSource(p)
                    .setTradingDate(p.getTradingDate())
                    .setAmount(p.getNetAmount())
                    .setDescription("%s %s %d ST".formatted(
                            p.getType().getDisplayName(),
                            p.getSecurity().getName(),
                            p.getQuantity())).build());

            if (p.getDomesticWithholdingTax() != null) {
                records.add(AccountingRecord.builder()
                        .setDebitAccount(A2213)
                        .setCreditAccount(A2654)
                        .setSource(p)
                        .setTradingDate(p.getTradingDate())
                        .setAmount(p.getDomesticWithholdingTax())
                        .setDescription("%s %s %d ST KapSt".formatted(
                                p.getType().getDisplayName(),
                                p.getSecurity().getName(),
                                p.getQuantity())).build());
            }

            if (p.getDomesticWithholdingSoli() != null) {
                records.add(AccountingRecord.builder()
                        .setDebitAccount(A2216)
                        .setCreditAccount(A2654)
                        .setSource(p)
                        .setTradingDate(p.getTradingDate())
                        .setAmount(p.getDomesticWithholdingSoli())
                        .setDescription("%s %s %d ST SolZ auf KapSt".formatted(
                                p.getType().getDisplayName(),
                                p.getSecurity().getName(),
                                p.getQuantity())).build());
            }

            if (p.getForeignWithholdingTax() != null) {
                records.add(AccountingRecord.builder()
                        .setDebitAccount(A2219)
                        .setCreditAccount(A2654)
                        .setSource(p)
                        .setTradingDate(p.getTradingDate())
                        .setAmount(p.getForeignWithholdingTax())
                        .setDescription("%s %s %d ST Quellensteuer".formatted(
                                p.getType().getDisplayName(),
                                p.getSecurity().getName(),
                                p.getQuantity())).build());
            }
        });


        return records;
    }


    private List<AccountingRecord> getSecurityAccountRecords(final List<EvaluationState> evaluations) {

        final var records = new ArrayList<AccountingRecord>();
        evaluations.stream().forEach(evaluation -> {

            evaluation.getPositions().forEach(position -> {

                if (position.isBuy()) {

                    records.add(AccountingRecord.builder()
                            .setSource(position.getSource().getPortfolioTransaction())
                            .setDescription("WP-Kauf %s %s ST".formatted(
                                    position.getSource().getSecurity().getName(),
                                    position.getQuantity()))
                            .setTradingDate(position.getTradingDate())
                            .setAmount(position.getTotalCost())
                            .setDebitAccount(A1348)
                            .setCreditAccount(A1120).build());

                } else if (position.isSell()) {

                    records.add(AccountingRecord.builder()
                            .setSource(position.getSource().getPortfolioTransaction())
                            .setDescription("WP-%s %s %s ST".formatted(
                                    position.getSource().getSecurity().getType() == Security.EquityType.BOND
                                            ? "Rückzahlung"
                                            : "Verkauf",
                                    position.getSource().getSecurity().getName(),
                                    position.getQuantity()))
                            .setTradingDate(position.getTradingDate())
                            .setAmount(position.getTotalCost())
                            .setDebitAccount(A1120)
                            .setCreditAccount(A1348).build());

                    if (position.getProfitLoss().signum() != 0) {
                        records.add(AccountingRecord.builder()
                                .setSource(position.getSource().getPortfolioTransaction())
                                .setDescription("WP-Verkauf %s %s %s ST".formatted(
                                        position.getProfitLoss().signum() > 0 ? "Gewinn" : "Verlust",
                                        position.getSource().getSecurity().getName(),
                                        position.getSource().getQuantity()))
                                .setTradingDate(position.getTradingDate())
                                .setAmount(position.getProfitLoss().abs())
                                .setDebitAccount(position.getProfitLoss().signum() > 0 ? A1348 : A2325)
                                .setCreditAccount(position.getProfitLoss().signum() > 0 ? A2725 : A1348).build());
                    }
                }
            });

            final var hrResult = new EvaluationHrCalculator(evaluation).calculate();
            switch (hrResult.action()) {
                case VALUE_INCREASE_WITH_WRITE_UP -> records.add(AccountingRecord.builder()
                        .setSource(null)
                        .setDebitAccount(SKR03Account.A1348)
                        .setCreditAccount(SKR03Account.A2715)
                        .setAmount(hrResult.amount())
                        .setDescription("JA Zuschreibung %s".formatted(evaluation.getSecurity().getName()))
                        .setTradingDate(LocalDate.of(evaluation.getFiscalYear(), 12, 31)).build());

                case VALUE_DECREASE_WITH_WRITE_DOWN -> records.add(AccountingRecord.builder()
                        .setSource(null)
                        .setDebitAccount(SKR03Account.A4875)
                        .setCreditAccount(SKR03Account.A1348)
                        .setAmount(hrResult.amount())
                        .setDescription("JA Abschreibung %s".formatted(evaluation.getSecurity().getName()))
                        .setTradingDate(LocalDate.of(evaluation.getFiscalYear(), 12, 31)).build());

                default -> {}
            }

        });

        return records;
    }

    public List<AccountingRecord> getAccountingRecords(final int fiscalYear, final int accountNumber) {

        final var evaluations = evaluationService.evaluate(fiscalYear);
        final var securityRecords = getSecurityAccountRecords(evaluations);

        final var ebRecords = EB.stream()
                .map(account -> AccountingRecord.builder()
                        .setDebitAccount(account.getDebitAccount())
                        .setCreditAccount(account.getCreditAccount())
                        .setAmount(account.getAmount())
                        .setDescription("EB-Wert")
                        .setTradingDate(account.getTradingDate()).build()).toList();

        final var paymentsRecords = createAccountingRecordsForPayments(fiscalYear);

        return Stream.of(securityRecords, ebRecords, paymentsRecords)
                .flatMap(Collection::stream)
                .filter(r -> r.getTradingDate().getYear() == fiscalYear)
                .filter(r ->
                        r.getCreditAccount().getAccountNumber() == accountNumber
                        || r.getDebitAccount().getAccountNumber() == accountNumber)
                .toList();
    }
}