# app/schemas/expense.py from pydantic import BaseModel, ConfigDict, validator from typing import List, Optional from decimal import Decimal from datetime import datetime # Assuming SplitTypeEnum is accessible here, e.g., from app.models or app.core.enums # For now, let's redefine it or import it if models.py is parsable by Pydantic directly # If it's from app.models, you might need to make app.models.SplitTypeEnum Pydantic-compatible or map it. # For simplicity during schema definition, I'll redefine a string enum here. # In a real setup, ensure this aligns with the SQLAlchemy enum in models.py. from app.models import SplitTypeEnum, ExpenseSplitStatusEnum, ExpenseOverallStatusEnum # Try importing directly from app.schemas.user import UserPublic # For user details in responses from app.schemas.settlement_activity import SettlementActivityPublic # For settlement activities # --- ExpenseSplit Schemas --- class ExpenseSplitBase(BaseModel): user_id: int owed_amount: Decimal share_percentage: Optional[Decimal] = None share_units: Optional[int] = None class ExpenseSplitCreate(ExpenseSplitBase): pass # All fields from base are needed for creation class ExpenseSplitPublic(ExpenseSplitBase): id: int expense_id: int user: Optional[UserPublic] = None # If we want to nest user details created_at: datetime updated_at: datetime status: ExpenseSplitStatusEnum # New field paid_at: Optional[datetime] = None # New field settlement_activities: List[SettlementActivityPublic] = [] # New field model_config = ConfigDict(from_attributes=True) # --- Expense Schemas --- 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 # Should be present if list_id is not, and vice-versa item_id: Optional[int] = None paid_by_user_id: int class ExpenseCreate(ExpenseBase): # For EQUAL split, splits are generated. For others, they might be provided. # This logic will be in the CRUD: if split_type is EXACT_AMOUNTS, PERCENTAGE, SHARES, # then 'splits_in' should be provided. 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 # Basic validation: if list_id is None, group_id must be provided. # More complex cross-field validation might be needed. @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 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 # paid_by_user_id is usually not updatable directly to maintain integrity. # Updating splits would be a more complex operation, potentially a separate endpoint or careful logic. version: int # For optimistic locking 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 # If nesting user details overall_settlement_status: ExpenseOverallStatusEnum # New field # list: Optional[ListPublic] # If nesting list details # group: Optional[GroupPublic] # If nesting group details # item: Optional[ItemPublic] # If nesting item details model_config = ConfigDict(from_attributes=True) # --- Settlement Schemas --- 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 # group_id, paid_by_user_id, paid_to_user_id are typically not updatable. version: int # For optimistic locking class SettlementPublic(SettlementBase): id: int created_at: datetime updated_at: datetime version: int created_by_user_id: int # payer: Optional[UserPublic] # If we want to include payer details # payee: Optional[UserPublic] # If we want to include payee details # group: Optional[GroupPublic] # If we want to include group details model_config = ConfigDict(from_attributes=True) # Placeholder for nested schemas (e.g., UserPublic) if needed # from app.schemas.user import UserPublic # from app.schemas.list import ListPublic # from app.schemas.group import GroupPublic # from app.schemas.item import ItemPublic