feat(recurrence): Expansions-Engine für wiederkehrende Posten
Datenbankfreie Kernberechnung auf Basis schlanker Protokolle, sodass sowohl ORM-Objekte als auch Testdatenklassen verarbeitet werden. - expand() mit voller RFC-5545-Unterstützung, Fensterbegrenzung und Overlays - expand_by_due_date() für Sichten, die nach dem tatsächlichen Zahltag gruppieren - Werktagsverschiebung über den NRW-Feiertagskalender, nominales Datum als Schlüssel - Preishistorie, Ratenzahlungen, Vertragsfristen und Rücklagenberechnung - validate_rrule() und next_dates() für Eingabeprüfung und Terminvorschau - 81 Unit-Tests, davon alle in Abschnitt 2 geforderten Fälle Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Leichtgewichtige Testobjekte für die Recurrence-Engine – bewusst ohne Datenbank."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from app.models.enums import BusinessDayShift, EntryKind, OccurrenceStatus
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakeAmountVersion:
|
||||
"""Preisversion, gültig ab `valid_from`."""
|
||||
|
||||
amount: Decimal
|
||||
valid_from: date
|
||||
note: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakeOccurrence:
|
||||
"""Materialisierte Fälligkeit, die das virtuelle Ergebnis überlagert."""
|
||||
|
||||
occurrence_date: date
|
||||
status: OccurrenceStatus = OccurrenceStatus.PLANNED
|
||||
actual_amount: Decimal | None = None
|
||||
actual_date: date | None = None
|
||||
account_id: int | None = None
|
||||
note: str | None = None
|
||||
id: int | None = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakeRecurrence:
|
||||
"""Erfüllt `RecurrenceLike` mit denselben Vorgabewerten wie das ORM-Modell."""
|
||||
|
||||
rrule: str
|
||||
dtstart: date
|
||||
amount: Decimal = Decimal("100.00")
|
||||
kind: EntryKind = EntryKind.EXPENSE
|
||||
id: int | None = 1
|
||||
is_variable: bool = False
|
||||
until: date | None = None
|
||||
business_day_shift: BusinessDayShift = BusinessDayShift.NONE
|
||||
holiday_region: str = "DE-NW"
|
||||
installments_total: int | None = None
|
||||
principal_amount: Decimal | None = None
|
||||
contract_start: date | None = None
|
||||
contract_min_term_months: int | None = None
|
||||
contract_notice_period_days: int | None = None
|
||||
contract_auto_renew_months: int | None = None
|
||||
contract_cancelled_at: date | None = None
|
||||
reserve_enabled: bool = False
|
||||
account_id: int | None = 1
|
||||
amount_versions: list[FakeAmountVersion] = field(default_factory=list)
|
||||
|
||||
|
||||
def d(value: str) -> date:
|
||||
"""Kurzschreibweise für ISO-Datumsangaben in Tests."""
|
||||
return date.fromisoformat(value)
|
||||
|
||||
|
||||
def euro(value: str) -> Decimal:
|
||||
return Decimal(value)
|
||||
Reference in New Issue
Block a user