from __future__ import annotations from datetime import date, datetime from typing import Optional, List, Any from pydantic import BaseModel, ConfigDict, field_validator, model_validator from ..models import ChoreFrequencyEnum, ChoreTypeEnum, ChoreHistoryEventTypeEnum from .user import UserPublic class ChoreAssignmentPublic(BaseModel): pass class ChoreHistoryPublic(BaseModel): id: int event_type: ChoreHistoryEventTypeEnum event_data: Optional[dict[str, Any]] = None changed_by_user: Optional[UserPublic] = None timestamp: datetime model_config = ConfigDict(from_attributes=True) class ChoreAssignmentHistoryPublic(BaseModel): id: int event_type: ChoreHistoryEventTypeEnum event_data: Optional[dict[str, Any]] = None changed_by_user: Optional[UserPublic] = None timestamp: datetime model_config = ConfigDict(from_attributes=True) class ChoreBase(BaseModel): name: str description: Optional[str] = None frequency: ChoreFrequencyEnum custom_interval_days: Optional[int] = None next_due_date: date # For creation, this will be the initial due date type: ChoreTypeEnum @model_validator(mode='after') def validate_custom_frequency(self): if self.frequency == ChoreFrequencyEnum.custom: if self.custom_interval_days is None or self.custom_interval_days <= 0: raise ValueError("custom_interval_days must be a positive integer when frequency is 'custom'") return self class ChoreCreate(ChoreBase): group_id: Optional[int] = None parent_chore_id: Optional[int] = None assigned_to_user_id: Optional[int] = None @model_validator(mode='after') def validate_group_id_with_type(self): if self.type == ChoreTypeEnum.group and self.group_id is None: raise ValueError("group_id is required for group chores") if self.type == ChoreTypeEnum.personal and self.group_id is not None: # Automatically clear group_id for personal chores instead of raising an error self.group_id = None return self class ChoreUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None frequency: Optional[ChoreFrequencyEnum] = None custom_interval_days: Optional[int] = None next_due_date: Optional[date] = None # Allow updating next_due_date directly if needed type: Optional[ChoreTypeEnum] = None group_id: Optional[int] = None parent_chore_id: Optional[int] = None # Allow moving a chore under a parent or removing association # last_completed_at should generally not be updated directly by user @model_validator(mode='after') def validate_group_id_with_type(self): if self.type == ChoreTypeEnum.group and self.group_id is None: raise ValueError("group_id is required for group chores") if self.type == ChoreTypeEnum.personal and self.group_id is not None: # Automatically clear group_id for personal chores instead of raising an error self.group_id = None return self class ChorePublic(ChoreBase): id: int group_id: Optional[int] = None created_by_id: int last_completed_at: Optional[datetime] = None parent_chore_id: Optional[int] = None created_at: datetime updated_at: datetime creator: Optional[UserPublic] = None # Embed creator UserPublic schema assignments: List[ChoreAssignmentPublic] = [] history: List[ChoreHistoryPublic] = [] child_chores: List[ChorePublic] = [] model_config = ConfigDict(from_attributes=True) # Chore Assignment Schemas class ChoreAssignmentBase(BaseModel): chore_id: int assigned_to_user_id: int due_date: date class ChoreAssignmentCreate(ChoreAssignmentBase): pass class ChoreAssignmentUpdate(BaseModel): # Only completion status and perhaps due_date can be updated for an assignment is_complete: Optional[bool] = None due_date: Optional[date] = None # If rescheduling an existing assignment is allowed assigned_to_user_id: Optional[int] = None # For reassigning the chore class ChoreAssignmentPublic(ChoreAssignmentBase): id: int is_complete: bool completed_at: Optional[datetime] = None created_at: datetime updated_at: datetime # Embed ChorePublic and UserPublic for richer responses chore: Optional[ChorePublic] = None assigned_user: Optional[UserPublic] = None history: List[ChoreAssignmentHistoryPublic] = [] model_config = ConfigDict(from_attributes=True) # To handle potential circular imports if ChorePublic needs GroupPublic and GroupPublic needs ChorePublic # We can update forward refs after all models are defined. ChorePublic.model_rebuild() ChoreAssignmentPublic.model_rebuild()