feat(entries+billing): integrate automatic fee booking for entries with billing service
- **Entries-Service Updates:** - Implemented automatic booking of fees (entry fees and late fees) during entry submission using `TeilnehmerKontoService`. - Enhanced `Bewerb` entity with financial fields (`nenngeldCent`, `nachnenngebuehrCent`). - Added Flyway migration to update `bewerbe` table with new financial fields. - Updated `EntriesServiceApplication` to include billing package scanning for integration. - **Billing-Service Enhancements:** - Adjusted `TeilnehmerKontoService` to support fetching accounts by event and person. - Improved database configuration to handle missing JDBC URLs during tests. - **Tests:** - Added integration tests to validate fee booking logic for entries, including late fee scenarios. - Introduced H2 database setup for test isolation. - **Misc:** - Updated tenant-aware transactions to support H2 and PostgreSQL dialects. - Adjusted log and error handling for robust integration between services.
This commit is contained in:
+1
-1
@@ -11,7 +11,7 @@ fun main(args: Array<String>) {
|
||||
runApplication<EntriesServiceApplication>(*args)
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(scanBasePackages = ["at.mocode.entries", "at.mocode.billing"])
|
||||
@EnableAspectJAutoProxy
|
||||
class EntriesServiceApplication {
|
||||
|
||||
|
||||
+2
@@ -35,6 +35,8 @@ data class Bewerb(
|
||||
val stechenGeplant: Boolean = false,
|
||||
// Finanzen
|
||||
val startgeldCent: Long? = null,
|
||||
val nenngeldCent: Long? = null,
|
||||
val nachnenngebuehrCent: Long? = null,
|
||||
val geldpreisAusbezahlt: Boolean = false,
|
||||
// ZNS-Integration
|
||||
val znsNummer: Int? = null,
|
||||
|
||||
+6
@@ -72,6 +72,8 @@ class BewerbRepositoryImpl : BewerbRepository {
|
||||
stechenGeplant = row[BewerbTable.stechenGeplant],
|
||||
// Finanzen
|
||||
startgeldCent = row[BewerbTable.startgeldCent],
|
||||
nenngeldCent = row[BewerbTable.nenngeldCent],
|
||||
nachnenngebuehrCent = row[BewerbTable.nachnenngebuehrCent],
|
||||
geldpreisAusbezahlt = row[BewerbTable.geldpreisAusbezahlt],
|
||||
// ZNS-Integration
|
||||
znsNummer = row[BewerbTable.znsNummer],
|
||||
@@ -106,6 +108,8 @@ class BewerbRepositoryImpl : BewerbRepository {
|
||||
s[BewerbTable.stechenGeplant] = b.stechenGeplant
|
||||
// Finanzen
|
||||
s[BewerbTable.startgeldCent] = b.startgeldCent
|
||||
s[BewerbTable.nenngeldCent] = b.nenngeldCent
|
||||
s[BewerbTable.nachnenngebuehrCent] = b.nachnenngebuehrCent
|
||||
s[BewerbTable.geldpreisAusbezahlt] = b.geldpreisAusbezahlt
|
||||
// ZNS-Integration
|
||||
s[BewerbTable.znsNummer] = b.znsNummer
|
||||
@@ -153,6 +157,8 @@ class BewerbRepositoryImpl : BewerbRepository {
|
||||
s[BewerbTable.stechenGeplant] = b.stechenGeplant
|
||||
// Finanzen
|
||||
s[BewerbTable.startgeldCent] = b.startgeldCent
|
||||
s[BewerbTable.nenngeldCent] = b.nenngeldCent
|
||||
s[BewerbTable.nachnenngebuehrCent] = b.nachnenngebuehrCent
|
||||
s[BewerbTable.geldpreisAusbezahlt] = b.geldpreisAusbezahlt
|
||||
// ZNS-Integration
|
||||
s[BewerbTable.znsNummer] = b.znsNummer
|
||||
|
||||
+2
@@ -36,6 +36,8 @@ object BewerbTable : Table("bewerbe") {
|
||||
|
||||
// Finanzen
|
||||
val startgeldCent = long("startgeld_cent").nullable()
|
||||
val nenngeldCent = long("nenngeld_cent").nullable()
|
||||
val nachnenngebuehrCent = long("nachnenngebuehr_cent").nullable()
|
||||
val geldpreisAusbezahlt = bool("geldpreis_ausbezahlt").default(false)
|
||||
|
||||
// ZNS-Integration
|
||||
|
||||
+6
-1
@@ -10,6 +10,11 @@ suspend inline fun <T> tenantTransaction(crossinline block: () -> T): T = transa
|
||||
val schema = TenantContextHolder.current()?.schemaName
|
||||
?: error("No tenant in context. Ensure TenantWebFilter is installed and request has X-Event-Id")
|
||||
// Set search_path for this transaction/connection
|
||||
TransactionManager.current().exec("SET search_path TO \"$schema\"")
|
||||
val dialect = TransactionManager.current().db.vendor
|
||||
if (dialect == "postgresql") {
|
||||
TransactionManager.current().exec("SET search_path TO \"$schema\"")
|
||||
} else if (dialect == "h2") {
|
||||
TransactionManager.current().exec("SET SCHEMA \"$schema\"")
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
+42
-1
@@ -2,12 +2,15 @@
|
||||
|
||||
package at.mocode.entries.service.usecase
|
||||
|
||||
import at.mocode.billing.domain.model.BuchungsTyp
|
||||
import at.mocode.billing.service.TeilnehmerKontoService
|
||||
import at.mocode.core.domain.model.NennStatusE
|
||||
import at.mocode.entries.api.*
|
||||
import at.mocode.entries.domain.model.Nennung
|
||||
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 org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import kotlin.uuid.Uuid
|
||||
@@ -23,7 +26,9 @@ import kotlin.uuid.Uuid
|
||||
@Service
|
||||
class NennungUseCases(
|
||||
private val nennungRepository: NennungRepository,
|
||||
private val transferRepository: NennungsTransferRepository
|
||||
private val transferRepository: NennungsTransferRepository,
|
||||
private val bewerbRepository: BewerbRepository,
|
||||
private val kontoService: TeilnehmerKontoService
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(NennungUseCases::class.java)
|
||||
|
||||
@@ -74,6 +79,42 @@ class NennungUseCases(
|
||||
)
|
||||
val saved = nennungRepository.save(nennung)
|
||||
log.info("Nennung eingereicht: nennungId={} turnierId={}", saved.nennungId, saved.turnierId)
|
||||
|
||||
// Automatische Buchung der Gebühren (Nenngeld / Nachnenngebühr)
|
||||
// Wir nutzen den zahlerId, oder falls nicht gesetzt den reiterId
|
||||
val zahlerId = saved.zahlerId ?: saved.reiterId
|
||||
val bewerb = bewerbRepository.findById(saved.bewerbId)
|
||||
if (bewerb != null && (bewerb.nenngeldCent != null || bewerb.nachnenngebuehrCent != null)) {
|
||||
try {
|
||||
val konto = kontoService.getOrCreateKonto(saved.turnierId, zahlerId, "Zahler für Nennung ${saved.nennungId}")
|
||||
|
||||
// Nenngeld buchen
|
||||
if (bewerb.nenngeldCent != null && bewerb.nenngeldCent > 0) {
|
||||
kontoService.buche(
|
||||
kontoId = konto.kontoId,
|
||||
betragCent = -bewerb.nenngeldCent, // Gebühr ist negativ
|
||||
typ = BuchungsTyp.NENNGELD,
|
||||
zweck = "Nenngeld Bewerb ${bewerb.bezeichnung} (${bewerb.klasse})"
|
||||
)
|
||||
}
|
||||
|
||||
// Nachnenngebühr buchen (falls fällig und nicht erlassen)
|
||||
if (saved.isNachnenngebuehrFaellig() && bewerb.nachnenngebuehrCent != null && bewerb.nachnenngebuehrCent > 0) {
|
||||
kontoService.buche(
|
||||
kontoId = konto.kontoId,
|
||||
betragCent = -bewerb.nachnenngebuehrCent, // Gebühr ist negativ
|
||||
typ = BuchungsTyp.NACHNENNGEBUEHR,
|
||||
zweck = "Nachnenngebühr Bewerb ${bewerb.bezeichnung}"
|
||||
)
|
||||
}
|
||||
} 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.
|
||||
// In einem echten System würde man hier evtl. ein Domain Event publizieren
|
||||
// oder die Transaktion rollbacken (wenn gewünscht).
|
||||
}
|
||||
}
|
||||
|
||||
return saved.toDetailDto()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS control.tenants (
|
||||
db_url TEXT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'ACTIVE', -- ACTIVE | READ_ONLY | LOCKED
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Index to speed up lookups by status when we add list operations later
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- Migration: Hinzufügen von Finanz-Feldern zu Bewerben für automatische Buchung
|
||||
ALTER TABLE bewerbe ADD COLUMN IF NOT EXISTS nenngeld_cent BIGINT;
|
||||
ALTER TABLE bewerbe ADD COLUMN IF NOT EXISTS nachnenngebuehr_cent BIGINT;
|
||||
Reference in New Issue
Block a user