131 lines
4.9 KiB
Python
131 lines
4.9 KiB
Python
# 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 # Try importing directly
|
|
|
|
# --- 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] # If we want to nest user details
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
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
|
|
splits: List[ExpenseSplitPublic] = []
|
|
# paid_by_user: Optional[UserPublic] # If nesting user details
|
|
# 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
|
|
# payer: Optional[UserPublic]
|
|
# payee: Optional[UserPublic]
|
|
# group: Optional[GroupPublic]
|
|
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 |