from pydantic import BaseModel, ConfigDict, validator, Field from typing import List, Optional from decimal import Decimal from datetime import datetime from app.models import SplitTypeEnum, ExpenseSplitStatusEnum, ExpenseOverallStatusEnum from app.schemas.user import UserPublic from app.schemas.settlement_activity import SettlementActivityPublic class ExpenseSplitBase(BaseModel): user_id: int owed_amount: Decimal share_percentage: Optional[Decimal] = None share_units: Optional[int] = None class ExpenseSplitCreate(ExpenseSplitBase): pass class ExpenseSplitPublic(ExpenseSplitBase): id: int expense_id: int user: Optional[UserPublic] = None created_at: datetime updated_at: datetime status: ExpenseSplitStatusEnum paid_at: Optional[datetime] = None settlement_activities: List[SettlementActivityPublic] = [] model_config = ConfigDict(from_attributes=True) class RecurrencePatternBase(BaseModel): type: str = Field(..., description="Type of recurrence: daily, weekly, monthly, yearly") interval: int = Field(..., description="Interval of recurrence (e.g., every X days/weeks/months/years)") days_of_week: Optional[List[int]] = Field(None, description="Days of week for weekly recurrence (0-6, Sunday-Saturday)") end_date: Optional[datetime] = Field(None, description="Optional end date for the recurrence") max_occurrences: Optional[int] = Field(None, description="Optional maximum number of occurrences") class RecurrencePatternCreate(RecurrencePatternBase): pass class RecurrencePatternUpdate(RecurrencePatternBase): pass class RecurrencePatternInDB(RecurrencePatternBase): id: int created_at: datetime updated_at: datetime class Config: from_attributes = True class ExpenseBase(BaseModel): description: str total_amount: Decimal currency: Optional[str] = "USD" expense_date: Optional[datetime] = None split_type: SplitTypeEnum list_id: Optional[int] = None group_id: Optional[int] = None item_id: Optional[int] = None paid_by_user_id: int is_recurring: bool = Field(False, description="Whether this is a recurring expense") recurrence_pattern: Optional[RecurrencePatternCreate] = Field(None, description="Recurrence pattern for recurring expenses") class ExpenseCreate(ExpenseBase): splits_in: Optional[List[ExpenseSplitCreate]] = None @validator('total_amount') def total_amount_must_be_positive(cls, v): if v <= Decimal('0'): raise ValueError('Total amount must be positive') return v @validator('group_id', always=True) def check_list_or_group_id(cls, v, values): if values.get('list_id') is None and v is None: raise ValueError('Either list_id or group_id must be provided for an expense') return v @validator('recurrence_pattern') def validate_recurrence_pattern(cls, v, values): if values.get('is_recurring') and not v: raise ValueError('Recurrence pattern is required for recurring expenses') if not values.get('is_recurring') and v: raise ValueError('Recurrence pattern should not be provided for non-recurring expenses') return v class ExpenseUpdate(BaseModel): description: Optional[str] = None total_amount: Optional[Decimal] = None currency: Optional[str] = None expense_date: Optional[datetime] = None split_type: Optional[SplitTypeEnum] = None list_id: Optional[int] = None group_id: Optional[int] = None item_id: Optional[int] = None version: int is_recurring: Optional[bool] = None recurrence_pattern: Optional[RecurrencePatternUpdate] = None next_occurrence: Optional[datetime] = None class ExpensePublic(ExpenseBase): id: int created_at: datetime updated_at: datetime version: int created_by_user_id: int splits: List[ExpenseSplitPublic] = [] paid_by_user: Optional[UserPublic] = None overall_settlement_status: ExpenseOverallStatusEnum is_recurring: bool next_occurrence: Optional[datetime] last_occurrence: Optional[datetime] recurrence_pattern: Optional[RecurrencePatternInDB] parent_expense_id: Optional[int] generated_expenses: List['ExpensePublic'] = [] model_config = ConfigDict(from_attributes=True) class SettlementBase(BaseModel): group_id: int paid_by_user_id: int paid_to_user_id: int amount: Decimal settlement_date: Optional[datetime] = None description: Optional[str] = None class SettlementCreate(SettlementBase): @validator('amount') def amount_must_be_positive(cls, v): if v <= Decimal('0'): raise ValueError('Settlement amount must be positive') return v @validator('paid_to_user_id') def payer_and_payee_must_be_different(cls, v, values): if 'paid_by_user_id' in values and v == values['paid_by_user_id']: raise ValueError('Payer and payee cannot be the same user') return v class SettlementUpdate(BaseModel): amount: Optional[Decimal] = None settlement_date: Optional[datetime] = None description: Optional[str] = None version: int class SettlementPublic(SettlementBase): id: int created_at: datetime updated_at: datetime version: int created_by_user_id: int model_config = ConfigDict(from_attributes=True)