644 lines
28 KiB
Python
644 lines
28 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, date, datetime
|
|
from decimal import Decimal
|
|
from uuid import uuid4
|
|
|
|
from mousehold_domain import (
|
|
AccountType,
|
|
EventObservationRelation,
|
|
FinancialEventStatus,
|
|
FinancialEventType,
|
|
ImportConnectionStatus,
|
|
ImportConnectionType,
|
|
ObservationStatus,
|
|
PaymentLineStatus,
|
|
PaymentStatus,
|
|
PlanningStatus,
|
|
ProjectStatus,
|
|
ReconciliationStatus,
|
|
RecurringCommitmentStatus,
|
|
RecurringInterval,
|
|
RelationType,
|
|
SettlementStatus,
|
|
SourceType,
|
|
VisibilityMode,
|
|
)
|
|
from sqlalchemy import (
|
|
Boolean,
|
|
Date,
|
|
DateTime,
|
|
ForeignKey,
|
|
Index,
|
|
Integer,
|
|
Numeric,
|
|
String,
|
|
Text,
|
|
UniqueConstraint,
|
|
)
|
|
from sqlalchemy import Enum as SqlEnum
|
|
from sqlalchemy.dialects.sqlite import JSON
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from .db import Base
|
|
|
|
|
|
def new_id() -> str:
|
|
return str(uuid4())
|
|
|
|
|
|
def utcnow() -> datetime:
|
|
return datetime.now(UTC)
|
|
|
|
|
|
Money = Numeric(14, 2)
|
|
|
|
|
|
class Household(Base):
|
|
__tablename__ = "households"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
default_currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
users: Mapped[list[User]] = relationship(
|
|
back_populates="household", cascade="all, delete-orphan"
|
|
)
|
|
accounts: Mapped[list[Account]] = relationship(
|
|
back_populates="household", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
display_name: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
email: Mapped[str | None] = mapped_column(String(320), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
household: Mapped[Household] = relationship(back_populates="users")
|
|
login_identities: Mapped[list[LoginIdentity]] = relationship(
|
|
back_populates="user", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class LoginIdentity(Base):
|
|
__tablename__ = "login_identities"
|
|
__table_args__ = (
|
|
UniqueConstraint("provider", "provider_subject", name="uq_login_provider_subject"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
|
|
provider: Mapped[str] = mapped_column(String(80), nullable=False)
|
|
provider_subject: Mapped[str] = mapped_column(String(320), nullable=False)
|
|
email: Mapped[str | None] = mapped_column(String(320), nullable=True)
|
|
display_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
user: Mapped[User] = relationship(back_populates="login_identities")
|
|
|
|
|
|
class LocalSession(Base):
|
|
__tablename__ = "local_sessions"
|
|
__table_args__ = (
|
|
Index("ix_local_session_token", "token_hash"),
|
|
Index("ix_local_session_user", "user_id", "expires_at"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
|
|
household_id: Mapped[str] = mapped_column(ForeignKey("households.id"), nullable=False, index=True)
|
|
token_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
last_seen_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
|
class Account(Base):
|
|
__tablename__ = "accounts"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
owner_user_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("users.id"), nullable=True, index=True
|
|
)
|
|
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
account_type: Mapped[AccountType] = mapped_column(SqlEnum(AccountType), nullable=False)
|
|
institution_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
|
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
|
|
visibility_mode: Mapped[VisibilityMode] = mapped_column(SqlEnum(VisibilityMode), nullable=False)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
household: Mapped[Household] = relationship(back_populates="accounts")
|
|
|
|
|
|
class RawObservation(Base):
|
|
__tablename__ = "raw_observations"
|
|
__table_args__ = (
|
|
Index("ix_observation_dedupe", "household_id", "source_type", "raw_hash"),
|
|
Index("ix_observation_external_refs", "provider_transaction_id", "message_id"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
source_type: Mapped[SourceType] = mapped_column(SqlEnum(SourceType), nullable=False, index=True)
|
|
source_account_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("accounts.id"), nullable=True, index=True
|
|
)
|
|
owner_user_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("users.id"), nullable=True, index=True
|
|
)
|
|
observed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
received_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
raw_hash: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
|
|
source_reference: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
provider_transaction_id: Mapped[str | None] = mapped_column(
|
|
String(200), nullable=True, index=True
|
|
)
|
|
message_id: Mapped[str | None] = mapped_column(String(500), nullable=True, index=True)
|
|
raw_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
raw_file_path: Mapped[str | None] = mapped_column(String(1000), nullable=True)
|
|
raw_payload_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
parsed_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
parser_name: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
|
parser_version: Mapped[str | None] = mapped_column(String(40), nullable=True)
|
|
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
|
|
status: Mapped[ObservationStatus] = mapped_column(
|
|
SqlEnum(ObservationStatus), default=ObservationStatus.NEW, nullable=False
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
event_links: Mapped[list[EventObservationLink]] = relationship(
|
|
back_populates="observation", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class FinancialEvent(Base):
|
|
__tablename__ = "financial_events"
|
|
__table_args__ = (
|
|
Index("ix_events_household_status_date", "household_id", "status", "event_date"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
source_account_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("accounts.id"), nullable=True, index=True
|
|
)
|
|
payment_account_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("accounts.id"), nullable=True, index=True
|
|
)
|
|
payer_user_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("users.id"), nullable=True, index=True
|
|
)
|
|
event_type: Mapped[FinancialEventType] = mapped_column(
|
|
SqlEnum(FinancialEventType), nullable=False
|
|
)
|
|
status: Mapped[FinancialEventStatus] = mapped_column(
|
|
SqlEnum(FinancialEventStatus), default=FinancialEventStatus.CANDIDATE, nullable=False
|
|
)
|
|
event_date: Mapped[date] = mapped_column(Date, nullable=False)
|
|
booking_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
|
due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
|
amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
|
|
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
|
|
merchant: Mapped[str | None] = mapped_column(String(300), nullable=True)
|
|
counterparty: Mapped[str | None] = mapped_column(String(300), nullable=True)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
category_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("categories.id"), nullable=True, index=True
|
|
)
|
|
project_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("projects.id"), nullable=True, index=True
|
|
)
|
|
created_by_user_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("users.id"), nullable=True, index=True
|
|
)
|
|
fingerprint: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
|
|
)
|
|
|
|
observation_links: Mapped[list[EventObservationLink]] = relationship(
|
|
back_populates="event", cascade="all, delete-orphan"
|
|
)
|
|
split_lines: Mapped[list[SplitLine]] = relationship(
|
|
back_populates="event", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class EventObservationLink(Base):
|
|
__tablename__ = "event_observation_links"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"event_id", "observation_id", "relation", name="uq_event_observation_relation"
|
|
),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
event_id: Mapped[str] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=False, index=True
|
|
)
|
|
observation_id: Mapped[str] = mapped_column(
|
|
ForeignKey("raw_observations.id"), nullable=False, index=True
|
|
)
|
|
relation: Mapped[EventObservationRelation] = mapped_column(
|
|
SqlEnum(EventObservationRelation), nullable=False
|
|
)
|
|
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
event: Mapped[FinancialEvent] = relationship(back_populates="observation_links")
|
|
observation: Mapped[RawObservation] = relationship(back_populates="event_links")
|
|
|
|
|
|
class EventRelation(Base):
|
|
__tablename__ = "event_relations"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
from_event_id: Mapped[str] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=False, index=True
|
|
)
|
|
to_event_id: Mapped[str] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=False, index=True
|
|
)
|
|
relation_type: Mapped[RelationType] = mapped_column(SqlEnum(RelationType), nullable=False)
|
|
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
|
|
created_by: Mapped[str] = mapped_column(String(40), default="system", nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class ReconciliationDecision(Base):
|
|
__tablename__ = "reconciliation_decisions"
|
|
__table_args__ = (
|
|
UniqueConstraint("household_id", "suggestion_id", name="uq_reconciliation_decision"),
|
|
Index("ix_reconciliation_decision_household", "household_id", "decision"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
suggestion_id: Mapped[str] = mapped_column(String(160), nullable=False)
|
|
decision: Mapped[str] = mapped_column(String(40), nullable=False)
|
|
kind: Mapped[str] = mapped_column(String(80), nullable=False)
|
|
relation_type: Mapped[RelationType] = mapped_column(SqlEnum(RelationType), nullable=False)
|
|
primary_event_id: Mapped[str] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=False, index=True
|
|
)
|
|
confirming_event_id: Mapped[str] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=False, index=True
|
|
)
|
|
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
|
|
actor_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
|
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class SplitLine(Base):
|
|
__tablename__ = "split_lines"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
event_id: Mapped[str] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=False, index=True
|
|
)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
|
|
share_amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
|
|
share_percent: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
|
|
reason: Mapped[str] = mapped_column(String(80), default="manual", nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
event: Mapped[FinancialEvent] = relationship(back_populates="split_lines")
|
|
|
|
|
|
class Category(Base):
|
|
__tablename__ = "categories"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
parent_id: Mapped[str | None] = mapped_column(ForeignKey("categories.id"), nullable=True)
|
|
name: Mapped[str] = mapped_column(String(160), nullable=False)
|
|
path: Mapped[str] = mapped_column(String(500), nullable=False)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
|
class Tag(Base):
|
|
__tablename__ = "tags"
|
|
__table_args__ = (UniqueConstraint("household_id", "name", name="uq_tag_household_name"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
name: Mapped[str] = mapped_column(String(120), nullable=False)
|
|
|
|
|
|
class EventTag(Base):
|
|
__tablename__ = "event_tags"
|
|
__table_args__ = (UniqueConstraint("event_id", "tag_id", name="uq_event_tag"),)
|
|
|
|
event_id: Mapped[str] = mapped_column(ForeignKey("financial_events.id"), primary_key=True)
|
|
tag_id: Mapped[str] = mapped_column(ForeignKey("tags.id"), primary_key=True)
|
|
|
|
|
|
class Project(Base):
|
|
__tablename__ = "projects"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
name: Mapped[str] = mapped_column(String(220), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
start_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
|
end_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
|
default_split_rule_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
status: Mapped[ProjectStatus] = mapped_column(
|
|
SqlEnum(ProjectStatus), default=ProjectStatus.PLANNING, nullable=False
|
|
)
|
|
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class ProjectParticipant(Base):
|
|
__tablename__ = "project_participants"
|
|
__table_args__ = (UniqueConstraint("project_id", "user_id", name="uq_project_participant"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("projects.id"), nullable=False, index=True)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
|
|
role: Mapped[str] = mapped_column(String(80), default="participant", nullable=False)
|
|
default_share_percent: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
|
|
|
|
|
|
class ProjectBudgetLine(Base):
|
|
__tablename__ = "project_budget_lines"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("projects.id"), nullable=False, index=True)
|
|
category_id: Mapped[str | None] = mapped_column(ForeignKey("categories.id"), nullable=True)
|
|
name: Mapped[str] = mapped_column(String(180), nullable=False)
|
|
budgeted_amount: Mapped[Decimal] = mapped_column(Money, default=Decimal("0.00"), nullable=False)
|
|
committed_amount_cached: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
|
|
paid_amount_cached: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
|
|
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
|
|
class ProjectPlannedItem(Base):
|
|
__tablename__ = "project_planned_items"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
project_id: Mapped[str] = mapped_column(ForeignKey("projects.id"), nullable=False, index=True)
|
|
budget_line_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("project_budget_lines.id"), nullable=True, index=True
|
|
)
|
|
description: Mapped[str] = mapped_column(String(300), nullable=False)
|
|
estimated_amount: Mapped[Decimal] = mapped_column(
|
|
Money, default=Decimal("0.00"), nullable=False
|
|
)
|
|
committed_amount: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
|
|
expected_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
|
due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
|
expected_payer_account_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("accounts.id"), nullable=True
|
|
)
|
|
split_rule_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
planning_status: Mapped[PlanningStatus] = mapped_column(
|
|
SqlEnum(PlanningStatus), default=PlanningStatus.IDEA, nullable=False
|
|
)
|
|
payment_status: Mapped[PaymentStatus] = mapped_column(
|
|
SqlEnum(PaymentStatus), default=PaymentStatus.UNPAID, nullable=False
|
|
)
|
|
reconciliation_status: Mapped[ReconciliationStatus] = mapped_column(
|
|
SqlEnum(ReconciliationStatus), default=ReconciliationStatus.NO_EVIDENCE, nullable=False
|
|
)
|
|
settlement_status: Mapped[SettlementStatus] = mapped_column(
|
|
SqlEnum(SettlementStatus), default=SettlementStatus.UNSETTLED, nullable=False
|
|
)
|
|
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class PaymentLine(Base):
|
|
__tablename__ = "payment_lines"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
planned_item_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("project_planned_items.id"), nullable=True, index=True
|
|
)
|
|
event_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=True, index=True
|
|
)
|
|
amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
|
|
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
|
|
date: Mapped[date] = mapped_column(Date, nullable=False)
|
|
paid_by_user_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("users.id"), nullable=True, index=True
|
|
)
|
|
payment_account_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("accounts.id"), nullable=True, index=True
|
|
)
|
|
source_observation_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("raw_observations.id"), nullable=True
|
|
)
|
|
status: Mapped[PaymentLineStatus] = mapped_column(
|
|
SqlEnum(PaymentLineStatus), default=PaymentLineStatus.EXPECTED, nullable=False
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class RecurringCommitment(Base):
|
|
__tablename__ = "recurring_commitments"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
provider: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
amount_expected: Mapped[Decimal] = mapped_column(Money, nullable=False)
|
|
amount_min: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
|
|
amount_max: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
|
|
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
|
|
interval: Mapped[RecurringInterval] = mapped_column(SqlEnum(RecurringInterval), nullable=False)
|
|
next_due_date: Mapped[date] = mapped_column(Date, nullable=False)
|
|
payer_account_id: Mapped[str | None] = mapped_column(ForeignKey("accounts.id"), nullable=True)
|
|
split_rule_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
category_id: Mapped[str | None] = mapped_column(ForeignKey("categories.id"), nullable=True)
|
|
project_id: Mapped[str | None] = mapped_column(ForeignKey("projects.id"), nullable=True)
|
|
notice_period: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
|
renewal_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
|
status: Mapped[RecurringCommitmentStatus] = mapped_column(
|
|
SqlEnum(RecurringCommitmentStatus), default=RecurringCommitmentStatus.ACTIVE, nullable=False
|
|
)
|
|
matching_rule_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class PromisedPayment(Base):
|
|
__tablename__ = "promised_payments"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
related_event_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("financial_events.id"), nullable=True, index=True
|
|
)
|
|
provider: Mapped[str] = mapped_column(String(120), nullable=False)
|
|
amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
|
|
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
|
|
created_date: Mapped[date] = mapped_column(Date, nullable=False)
|
|
due_date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
|
|
expected_funding_account_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("accounts.id"), nullable=True
|
|
)
|
|
responsible_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(40), default="expected", nullable=False)
|
|
source_observation_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("raw_observations.id"), nullable=True
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class ImportConnection(Base):
|
|
__tablename__ = "import_connections"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
owner_user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
|
|
connection_type: Mapped[ImportConnectionType] = mapped_column(
|
|
SqlEnum(ImportConnectionType), nullable=False
|
|
)
|
|
display_name: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
status: Mapped[ImportConnectionStatus] = mapped_column(
|
|
SqlEnum(ImportConnectionStatus), default=ImportConnectionStatus.NEEDS_AUTH, nullable=False
|
|
)
|
|
last_sync_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
secret_storage_ref: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
runs_locally: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
connector_config_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
|
|
)
|
|
|
|
|
|
class ImportRun(Base):
|
|
__tablename__ = "import_runs"
|
|
__table_args__ = (
|
|
Index("ix_import_runs_household_started", "household_id", "started_at"),
|
|
Index("ix_import_runs_connection_started", "connection_id", "started_at"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
connection_id: Mapped[str | None] = mapped_column(
|
|
ForeignKey("import_connections.id"), nullable=True, index=True
|
|
)
|
|
actor_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
|
source_type: Mapped[str | None] = mapped_column(String(80), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(40), default="running", nullable=False)
|
|
imported_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
duplicate_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
created_event_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
skipped_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
summary_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
started_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|
|
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
|
class AuditLog(Base):
|
|
__tablename__ = "audit_log"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
|
|
household_id: Mapped[str] = mapped_column(
|
|
ForeignKey("households.id"), nullable=False, index=True
|
|
)
|
|
actor_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
|
action: Mapped[str] = mapped_column(String(120), nullable=False)
|
|
entity_type: Mapped[str] = mapped_column(String(120), nullable=False)
|
|
entity_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
details_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=utcnow, nullable=False
|
|
)
|