EvaluationStateInfoService.java

package com.tdmconsult.ete.evaluation;

import static com.tdmconsult.ete.accounting.BalanceRecords.ZERO_AMOUNT;
import static com.tdmconsult.ete.accounting.SKR03Account.A2715;
import static com.tdmconsult.ete.accounting.SKR03Account.A4875;

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class EvaluationStateInfoService {

    public static final NumberFormat AMT_FORMAT = NumberFormat.getCurrencyInstance(Locale.GERMANY);
    public static final NumberFormat AMT_FORMAT_RATE = NumberFormat.getCurrencyInstance(Locale.GERMANY);

    static {
        AMT_FORMAT_RATE.setMaximumFractionDigits(6);
    }

    public String getBuySellPhrase(final EvaluationState state) {

        final var answer = new StringBuffer();

        answer.append("Im Fiskaljahr " + state.getFiscalYear() + " wurden ");
        if (state.getTotalBuyCost().signum() > 0) {
            answer.append("Papiere im Wert von %s angeschafft".formatted(AMT_FORMAT.format(state.getTotalBuyCost())));
        } else if (state.getTotalBuyCost().signum() == 0) {
            answer.append("keine neuen Papiere dem Bestand hinzugefügt");
        }

        answer.append(" und ");

        if (state.getTotalSellCost().signum() > 0) {
            answer.append("Papiere im Wert von %s veräußert.".formatted(AMT_FORMAT.format(state.getTotalSellCost())));
        } else if (state.getTotalSellCost().signum() == 0) {
            answer.append("keine Papiere aus dem Bestand veräußert.");
        }

        final var diff = state.getTotalSellCost().subtract(state.getTotalBuyCost());
        if (diff.signum() != 0) {
            answer.append(" Damit wurde in diesem Jahr die Position insgesamt um %s %s.".formatted(
                    AMT_FORMAT.format(diff.abs()),
                    diff.signum() > 0 ? "reduziert" : "ausgeweitet"));
        }

        return answer.toString();
    }

    @SuppressWarnings("checkstyle:LineLength")
    public String getProfitLossPhrase(final EvaluationState state) {

        if (state.getTotalProfit().signum() > 0) {
            return ("Mit den Verkäufen wurde ein realisierter Gewinn von %s erzielt, "
                    + "der auf Konto 1348 im Haben ausgebucht wurde und das Konto 2725 im Soll verstärkt.")
                    .formatted(AMT_FORMAT.format(state.getTotalProfit().abs()));
        }

        if (state.getTotalLoss().signum() > 0) {
            return ("Über die Verkäufe musste dabei ein realisierter Verlust von %s realisiert werden, "
                    + "der aus Konto 1348 im Soll ausgebucht und gegen Konto 2325 im Haben eingebucht wurde.")
                    .formatted(AMT_FORMAT.format(state.getTotalLoss().abs()));
        }

        return "Es gab keine Gewinn-/Verlustrealisierung, demzufolge wurde weder Konto 2725 noch Konto 2325 im Jahresverlauf bebucht.";
    }

    @SuppressWarnings("checkstyle:LineLength")
    public String getEoyEvaluationPhrase(final EvaluationState state) {

        final var fiscalYear = state.getFiscalYear();
        final var fiscalYearBefore = state.getFiscalYear() - 1;

        final var statement1 = state.getTotalQuantity() == 0L
                ? "Zum Jahresabschluss %d gibt es keinen aktiven Bestand, demzufolge entfällt eine Folgebewertung des Umlaufvermögens nach Handelsrecht."
                        .formatted(fiscalYear)
                : ("Zum Jahresabschluss %d erfolgt eine Jahresendbewertung. Der %s Stück umfassende Portfolio-Bestand wird "
                    + "mit einem Schlußkurs von %s gemäß Jahresdepotauszug bewertet. "
                    + "Per %s folgt daraus ein Marktwert der Wertpapiere zu %s.").formatted(
                    fiscalYear,
                            state.getTotalQuantity(),
                            AMT_FORMAT_RATE.format(state.getEvaluationRate(fiscalYear).map(EvaluationPosition.Rate::value).orElse(ZERO_AMOUNT)),
                            "31.12." + state.getFiscalYear(),
                            AMT_FORMAT.format(state.getEvaluationRate(fiscalYear).map(EvaluationPosition.Rate::value).orElse(ZERO_AMOUNT).multiply(new BigDecimal(state.getTotalQuantity()))));

        // e.g. -64.979,05 € in 2024 - RWE AG
        final var evaluationTotalDeltaT0 = state.getEvaluationTotalDelta(fiscalYear);
        // e.g. -4.785,99 € € in 2023 - RWE AG
        final var evaluationTotalDeltaT1 = state.getEvaluationTotalDelta(fiscalYear - 1);

        final var statement2a = state.getEvaluationRate(fiscalYearBefore).map(rate -> (
                        "Im Vorjahr %d wurde der Portfolio-Bestand mit einem Schlußkurs von %s bewertet, woraus sich damals ein %s von %s ergab.").formatted(
                        fiscalYearBefore,
                        AMT_FORMAT_RATE.format(rate.value()),
                        evaluationTotalDeltaT1.signum() > 0 ? "Bewertungsgewinn" : "Bewertungsverlust",
                        AMT_FORMAT.format(evaluationTotalDeltaT1.abs())))
                .orElse("Aus dem Vorjahr %d sind keine Zu- oder Abschreibungen zu berücksichtigen.".formatted(fiscalYearBefore));

        final var statement2b = evaluationTotalDeltaT0.signum() == 0
            ? "In diesem Jahr entfällt die Bewertung in Ermangelung an einem Bestand."
            : "In diesem Jahr ergibt sich für den Bestand ein %s von %s.".formatted(
                        evaluationTotalDeltaT0.signum() > 0 ? "Bewertungsgewinn" : "Bewertungsverlust",
                        AMT_FORMAT.format(evaluationTotalDeltaT0.abs()));

        final var statement2 = statement2a + " " + statement2b;

        var statement3 = "Die Wertpapiere sind im Umlaufvermögen zu führen, daher gilt das strenges Niederstwertprinzip "
                + "mit Abschreibungspflicht auf den Stichtagskurs (§ 253 Abs. 4 HGB) und Zuschreibungspflicht bis zu den Anschaffunsgkosten "
                + "bei Wertaufholung. ";

        final var hrResult = new EvaluationHrCalculator(state).calculate();
        final var statement4 = switch (hrResult.action()) {
            case VALUE_NOOP -> "Es gibt keine Ab- und Zuschreibungen in diesem Zeitraum.";
            case VALUE_DECREASE_WITH_WRITE_DOWN ->
                "Unter Berücksichtigung der vorgenannten Rahmenbedingungen ist in diesem Jahr eine Abschreibung i.H.v "
                            + AMT_FORMAT.format(hrResult.amount()) + " nach Handelsrecht durchzuführen. "
                            + "Die Buchung erfolgt im Rahmen des Jahresabsachlusses zum 31.12. gegen das Konto 4875 " + A4875.getDisplayName() + ".";
            case VALUE_DECREASE_NO_WRITE_DOWN ->
                "Unter Berücksichtigung der vorgenannten Rahmenbedingungen ist in diesem Jahr keine Abschreibung zulässig, "
                            + "weil der Kurswert zwar gegenüber dem Vorjahr abgenommen hat, aber noch über den Anschaffungskosten liegt.";
            case VALUE_INCREASE_WITH_WRITE_UP ->
                "Unter Berücksichtigung der vorgenannten Rahmenbedingungen ist in diesem Jahr eine Zuschreibung i.H.v "
                            + AMT_FORMAT.format(hrResult.amount()) + " nach Handelsrecht durchzuführen. "
                            + "Die Buchung erfolgt im Rahmen des Jahresabsachlusses zum 31.12. gegen das Konto 2715 " + A2715.getDisplayName() + ".";
            case VALUE_INCREASE_NO_WRITE_UP ->
                "Unter Berücksichtigung der vorgenannten Rahmenbedingungen ist in diesem Jahr keine Zuschreibung zulässig. "
                            + "Zwar steigt der Marktwert des Bestandes in diesem Fiskaljahr, allerdings ist eine Zuschreibung im Handelsrecht über die Anschaffungskosten hinaus nicht zulässig.";
        };

        return statement1 + " " + statement2 + " " + statement3 + " " + statement4;
    }
}