mitlist/be/app/schemas/expense.py

150 lines
5.3 KiB
Python

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)