From 8c804832d89675c72b2b44c911728d408d4ed152 Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Wed, 15 Apr 2026 11:17:25 +0200 Subject: [PATCH] =?UTF-8?q?feat(billing):=20add=20automatic=20booking=20fo?= =?UTF-8?q?r=20Sportf=C3=B6rderbeitrag=20in=20compliance=20with=20=C2=A7?= =?UTF-8?q?=2016=20=C3=96TO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stefan Mogeritsch --- .../billing/domain/model/TeilnehmerKonto.kt | 1 + .../billing/service/TeilnehmerKontoService.kt | 1 + .../service/usecase/NennungUseCases.kt | 10 +++++- .../usecase/NennungBillingIntegrationTest.kt | 17 ++++----- .../2026-04-15_Billing-Finalisierung.md | 35 +++++++++++++++++++ 5 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 docs/03_Journal/2026-04-15_Billing-Finalisierung.md diff --git a/backend/services/billing/billing-domain/src/commonMain/kotlin/at/mocode/billing/domain/model/TeilnehmerKonto.kt b/backend/services/billing/billing-domain/src/commonMain/kotlin/at/mocode/billing/domain/model/TeilnehmerKonto.kt index b863edd8..284f4e8b 100644 --- a/backend/services/billing/billing-domain/src/commonMain/kotlin/at/mocode/billing/domain/model/TeilnehmerKonto.kt +++ b/backend/services/billing/billing-domain/src/commonMain/kotlin/at/mocode/billing/domain/model/TeilnehmerKonto.kt @@ -63,6 +63,7 @@ enum class BuchungsTyp { NACHNENNGEBUEHR, STARTGEBUEHR, BOXENGEBUEHR, + SPORTFOERDERBEITRAG, ZAHLUNG_BAR, ZAHLUNG_KARTE, GUTSCHRIFT, diff --git a/backend/services/billing/billing-service/src/main/kotlin/at/mocode/billing/service/TeilnehmerKontoService.kt b/backend/services/billing/billing-service/src/main/kotlin/at/mocode/billing/service/TeilnehmerKontoService.kt index 8f6f7d31..c560084a 100644 --- a/backend/services/billing/billing-service/src/main/kotlin/at/mocode/billing/service/TeilnehmerKontoService.kt +++ b/backend/services/billing/billing-service/src/main/kotlin/at/mocode/billing/service/TeilnehmerKontoService.kt @@ -59,6 +59,7 @@ class TeilnehmerKontoService( BuchungsTyp.NENNGEBUEHR, BuchungsTyp.NACHNENNGEBUEHR, BuchungsTyp.STARTGEBUEHR, + BuchungsTyp.SPORTFOERDERBEITRAG, BuchungsTyp.BOXENGEBUEHR -> if (betragCent > 0) -betragCent else betragCent BuchungsTyp.ZAHLUNG_BAR, diff --git a/backend/services/entries/entries-service/src/main/kotlin/at/mocode/entries/service/usecase/NennungUseCases.kt b/backend/services/entries/entries-service/src/main/kotlin/at/mocode/entries/service/usecase/NennungUseCases.kt index a3c9a8bb..e02af26d 100644 --- a/backend/services/entries/entries-service/src/main/kotlin/at/mocode/entries/service/usecase/NennungUseCases.kt +++ b/backend/services/entries/entries-service/src/main/kotlin/at/mocode/entries/service/usecase/NennungUseCases.kt @@ -4,7 +4,6 @@ package at.mocode.entries.service.usecase import at.mocode.billing.domain.model.BuchungsTyp import at.mocode.billing.service.TeilnehmerKontoService -import at.mocode.entries.service.notification.MailService import at.mocode.core.domain.model.NennStatusE import at.mocode.entries.api.* import at.mocode.entries.domain.model.Nennung @@ -12,6 +11,7 @@ import at.mocode.entries.domain.model.NennungsTransfer import at.mocode.entries.domain.repository.NennungRepository import at.mocode.entries.domain.repository.NennungsTransferRepository import at.mocode.entries.service.bewerbe.BewerbRepository +import at.mocode.entries.service.notification.MailService import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import kotlin.uuid.Uuid @@ -109,6 +109,14 @@ class NennungUseCases( zweck = "Nachnenngebühr Bewerb ${bewerb.bezeichnung}" ) } + + // Sportförderbeitrag buchen (1€ gemäß § 16 ÖTO) + kontoService.buche( + kontoId = konto.kontoId, + betragCent = -100, // 1,00 EUR + typ = BuchungsTyp.SPORTFOERDERBEITRAG, + zweck = "Sportförderbeitrag ÖTO (§ 16)" + ) } catch (e: Exception) { log.error("Fehler bei der automatischen Buchung für Nennung {}: {}", saved.nennungId, e.message, e) // Wir lassen die Nennung bestehen, loggen aber den Fehler. diff --git a/backend/services/entries/entries-service/src/test/kotlin/at/mocode/entries/service/usecase/NennungBillingIntegrationTest.kt b/backend/services/entries/entries-service/src/test/kotlin/at/mocode/entries/service/usecase/NennungBillingIntegrationTest.kt index 3acdea7c..7a719c09 100644 --- a/backend/services/entries/entries-service/src/test/kotlin/at/mocode/entries/service/usecase/NennungBillingIntegrationTest.kt +++ b/backend/services/entries/entries-service/src/test/kotlin/at/mocode/entries/service/usecase/NennungBillingIntegrationTest.kt @@ -98,15 +98,15 @@ class NennungBillingIntegrationTest { // WHEN: Nennung einreichen nennungUseCases.nennungEinreichen(request) - // THEN: Konto muss existieren und Saldo muss -25,00 EUR sein (Gebühr) + // THEN: Konto muss existieren und Saldo muss -26,00 EUR sein (25,00 Gebühr + 1,00 Sportförderbeitrag) val konto = kontoService.getKonto(turnierId, reiterId) assertNotNull(konto, "Konto sollte automatisch erstellt worden sein") - assertEquals(-2500L, konto?.saldoCent) + assertEquals(-2600L, konto?.saldoCent) val buchungen = kontoService.getBuchungsHistorie(konto!!.kontoId) - assertEquals(1, buchungen.size) - assertEquals(BuchungsTyp.NENNGELD, buchungen[0].typ) - assertEquals(-2500L, buchungen[0].betragCent) + assertEquals(2, buchungen.size) + assertNotNull(buchungen.find { it.typ == BuchungsTyp.NENNGELD }) + assertNotNull(buchungen.find { it.typ == BuchungsTyp.SPORTFOERDERBEITRAG }) } @Test @@ -165,13 +165,14 @@ class NennungBillingIntegrationTest { // WHEN: Nennung einreichen nennungUseCases.nennungEinreichen(request) - // THEN: Saldo muss -45,00 EUR sein (-30 - 15) + // THEN: Saldo muss -46,00 EUR sein (-30 - 15 - 1 Sportförderbeitrag) val konto = kontoService.getKonto(turnierId, reiterId) - assertEquals(-4500L, konto?.saldoCent) + assertEquals(-4600L, konto?.saldoCent) val buchungen = kontoService.getBuchungsHistorie(konto!!.kontoId) - assertEquals(2, buchungen.size) + assertEquals(3, buchungen.size) // Einer muss NACHNENNGEBUEHR sein assertNotNull(buchungen.find { it.typ == BuchungsTyp.NACHNENNGEBUEHR }) + assertNotNull(buchungen.find { it.typ == BuchungsTyp.SPORTFOERDERBEITRAG }) } } diff --git a/docs/03_Journal/2026-04-15_Billing-Finalisierung.md b/docs/03_Journal/2026-04-15_Billing-Finalisierung.md new file mode 100644 index 00000000..5ea68935 --- /dev/null +++ b/docs/03_Journal/2026-04-15_Billing-Finalisierung.md @@ -0,0 +1,35 @@ +# 🧹 Session Journal - 15. April 2026 (Nachmittag) + +## 🏗️ Status-Check (Lead Architect) + +- **Phase 13 (Export & Billing):** Die Gebührenlogik wurde für das Neumarkt-Turnier (April 2026) finalisiert. +- **ÖTO-Konformität:** Der Sportförderbeitrag gemäß § 16 ÖTO wird nun bei jeder Nennung automatisch verbucht. + +## 👷 Durchgeführte Arbeiten (Backend) + +1. **Billing (billing-domain & service):** + - `BuchungsTyp.SPORTFOERDERBEITRAG` zum Enum hinzugefügt. + - `TeilnehmerKontoService` um die Validierung für diesen neuen Typ erweitert (automatische Soll-Buchung). +2. **Entries (entries-service):** + - `NennungUseCases` aktualisiert: Bei jeder Nennungseingabe wird nun automatisch 1,00 EUR Sportförderbeitrag auf das + Teilnehmerkonto gebucht, zusätzlich zu Nenngeld und Nachnenngebühr. +3. **ZNS-Export (Bewerbe-SCS):** + - Prüfung des `B-Satz` Exports im `BewerbeController`. Die Logik zur Generierung des strukturierten Textformats für + den OEPS ist vorhanden und nutzt die `ZnsBewerb`-Modelle. + +## 🧐 QA-Status & Bekannte Themen + +- [x] **Billing-Check:** Die automatische Buchungskette (Nennung -> Konto -> Buchung) ist nun vollständig für alle + Pflichtgebühren integriert. +- [x] **Integrationstests:** `NennungBillingIntegrationTest` wurde an die neue Gebührenlogik angepasst (1,00 EUR + Sportförderbeitrag). +- [ ] **Export-Validierung:** Der generierte B-Satz muss noch gegen ein offizielles OEPS-Beispiel validiert werden ( + geplant für die nächste Session). + +## 🧹 Curator's Note + +- Die ROADMAP Phase 13 wurde in der Vormittags-Session bereits aktualisiert. +- Der Fokus für morgen liegt auf der **ZNS-Export-Validierung** und der Vorbereitung des **Teilnehmer-Exports** ( + A-Satz). + +**Abschluss:** Das Billing-System ist "ÖTO-ready" für Neumarkt. 🐎🏦