import uuid from datetime import date as DateType, datetime from decimal import Decimal from typing import Literal from pydantic import BaseModel, Field TransactionType = Literal["income", "expense", "transfer", "investment"] TransactionStatus = Literal["pending", "cleared", "reconciled", "void"] class TransactionCreate(BaseModel): account_id: uuid.UUID transfer_account_id: uuid.UUID | None = None category_id: uuid.UUID | None = None type: TransactionType status: TransactionStatus = "cleared" amount: Decimal currency: str = Field(default="GBP", min_length=3, max_length=10) date: DateType description: str = Field(..., min_length=1, max_length=500) merchant: str | None = None notes: str | None = None tags: list[str] = [] is_recurring: bool = False recurring_rule: dict | None = None class TransactionUpdate(BaseModel): category_id: uuid.UUID | None = None status: TransactionStatus | None = None amount: Decimal | None = None date: DateType | None = None description: str | None = Field(default=None, min_length=1, max_length=500) merchant: str | None = None notes: str | None = None tags: list[str] | None = None class TransactionFilter(BaseModel): account_id: uuid.UUID | None = None category_id: uuid.UUID | None = None type: TransactionType | None = None status: TransactionStatus | None = None date_from: DateType | None = None date_to: DateType | None = None min_amount: Decimal | None = None max_amount: Decimal | None = None search: str | None = None tags: list[str] = [] page: int = Field(default=1, ge=1) page_size: int = Field(default=50, ge=1, le=200) class TransactionResponse(BaseModel): id: uuid.UUID account_id: uuid.UUID transfer_account_id: uuid.UUID | None category_id: uuid.UUID | None type: str status: str amount: Decimal amount_base: Decimal | None currency: str base_currency: str exchange_rate: Decimal | None date: DateType description: str merchant: str | None notes: str | None tags: list[str] is_recurring: bool attachment_refs: list[dict] = [] created_at: datetime updated_at: datetime model_config = {"from_attributes": True}