mitlist/be/app/schemas/chore.py
mohamad fb951acb72 feat: Add chore history and scheduling functionality
This commit introduces new models and endpoints for managing chore history and scheduling within the application. Key changes include:

- Added `ChoreHistory` and `ChoreAssignmentHistory` models to track changes and events related to chores and assignments.
- Implemented CRUD operations for chore history in the `history.py` module.
- Created endpoints to retrieve chore and assignment history in the `chores.py` and `groups.py` files.
- Introduced a scheduling feature for group chores, allowing for round-robin assignment generation.
- Updated existing chore and assignment CRUD operations to log history entries for create, update, and delete actions.

This enhancement improves the tracking of chore-related events and facilitates better management of group chore assignments.
2025-06-08 01:17:53 +02:00

139 lines
5.3 KiB
Python

from datetime import date, datetime
from typing import Optional, List, Any
from pydantic import BaseModel, ConfigDict, field_validator
# Assuming ChoreFrequencyEnum is imported from models
# Adjust the import path if necessary based on your project structure.
# e.g., from app.models import ChoreFrequencyEnum
from ..models import ChoreFrequencyEnum, ChoreTypeEnum, User as UserModel, ChoreHistoryEventTypeEnum # For UserPublic relation
from .user import UserPublic # For embedding user information
# Forward declaration for circular dependencies
class ChoreAssignmentPublic(BaseModel):
pass
# History Schemas
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)
# Chore Schemas
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
@field_validator('custom_interval_days', mode='before')
@classmethod
def check_custom_interval_days(cls, value, values):
# Pydantic v2 uses `values.data` to get all fields
# For older Pydantic, it might just be `values`
# This is a simplified check; actual access might differ slightly
# based on Pydantic version context within the validator.
# The goal is to ensure custom_interval_days is present if frequency is 'custom'.
# This validator might be more complex in a real Pydantic v2 setup.
# A more direct way if 'frequency' is already parsed into values.data:
# freq = values.data.get('frequency')
# For this example, we'll assume 'frequency' might not be in 'values.data' yet
# if 'custom_interval_days' is validated 'before' 'frequency'.
# A truly robust validator might need to be on the whole model or run 'after'.
# For now, this is a placeholder for the logic.
# Consider if this validation is better handled at the service/CRUD layer for complex cases.
return value
class ChoreCreate(ChoreBase):
group_id: Optional[int] = None
@field_validator('group_id')
@classmethod
def validate_group_id(cls, v, values):
if values.data.get('type') == ChoreTypeEnum.group and v is None:
raise ValueError("group_id is required for group chores")
if values.data.get('type') == ChoreTypeEnum.personal and v is not None:
raise ValueError("group_id must be None for personal chores")
return v
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
# last_completed_at should generally not be updated directly by user
@field_validator('group_id')
@classmethod
def validate_group_id(cls, v, values):
if values.data.get('type') == ChoreTypeEnum.group and v is None:
raise ValueError("group_id is required for group chores")
if values.data.get('type') == ChoreTypeEnum.personal and v is not None:
raise ValueError("group_id must be None for personal chores")
return v
class ChorePublic(ChoreBase):
id: int
group_id: Optional[int] = None
created_by_id: int
last_completed_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
creator: Optional[UserPublic] = None # Embed creator UserPublic schema
assignments: List[ChoreAssignmentPublic] = []
history: List[ChoreHistoryPublic] = []
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()