
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.
120 lines
4.7 KiB
Python
120 lines
4.7 KiB
Python
# be/app/crud/schedule.py
|
|
import logging
|
|
from datetime import date, timedelta
|
|
from typing import List
|
|
from itertools import cycle
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.future import select
|
|
|
|
from app.models import Chore, Group, User, ChoreAssignment, UserGroup, ChoreTypeEnum, ChoreHistoryEventTypeEnum
|
|
from app.crud.group import get_group_by_id
|
|
from app.crud.history import create_chore_history_entry
|
|
from app.core.exceptions import GroupNotFoundError, ChoreOperationError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
async def generate_group_chore_schedule(
|
|
db: AsyncSession,
|
|
*,
|
|
group_id: int,
|
|
start_date: date,
|
|
end_date: date,
|
|
user_id: int, # The user initiating the action
|
|
member_ids: List[int] = None
|
|
) -> List[ChoreAssignment]:
|
|
"""
|
|
Generates a round-robin chore schedule for all group chores within a date range.
|
|
"""
|
|
if start_date > end_date:
|
|
raise ChoreOperationError("Start date cannot be after end date.")
|
|
|
|
group = await get_group_by_id(db, group_id)
|
|
if not group:
|
|
raise GroupNotFoundError(group_id)
|
|
|
|
if not member_ids:
|
|
# If no members are specified, use all members from the group
|
|
members_result = await db.execute(
|
|
select(UserGroup.user_id).where(UserGroup.group_id == group_id)
|
|
)
|
|
member_ids = members_result.scalars().all()
|
|
|
|
if not member_ids:
|
|
raise ChoreOperationError("Cannot generate schedule with no members.")
|
|
|
|
# Fetch all chores belonging to this group
|
|
chores_result = await db.execute(
|
|
select(Chore).where(Chore.group_id == group_id, Chore.type == ChoreTypeEnum.group)
|
|
)
|
|
group_chores = chores_result.scalars().all()
|
|
if not group_chores:
|
|
logger.info(f"No chores found in group {group_id} to generate a schedule for.")
|
|
return []
|
|
|
|
member_cycle = cycle(member_ids)
|
|
new_assignments = []
|
|
|
|
current_date = start_date
|
|
while current_date <= end_date:
|
|
for chore in group_chores:
|
|
# Check if a chore is due on the current day based on its frequency
|
|
# This is a simplified check. A more robust system would use the chore's next_due_date
|
|
# and frequency to see if it falls on the current_date.
|
|
# For this implementation, we assume we generate assignments for ALL chores on ALL days
|
|
# in the range, which might not be desired.
|
|
# A better approach is needed here. Let's assume for now we just create assignments for each chore
|
|
# on its *next* due date if it falls within the range.
|
|
|
|
if start_date <= chore.next_due_date <= end_date:
|
|
# Check if an assignment for this chore on this due date already exists
|
|
existing_assignment_result = await db.execute(
|
|
select(ChoreAssignment.id)
|
|
.where(ChoreAssignment.chore_id == chore.id, ChoreAssignment.due_date == chore.next_due_date)
|
|
.limit(1)
|
|
)
|
|
if existing_assignment_result.scalar_one_or_none():
|
|
logger.info(f"Skipping assignment for chore '{chore.name}' on {chore.next_due_date} as it already exists.")
|
|
continue
|
|
|
|
assigned_to_user_id = next(member_cycle)
|
|
|
|
assignment = ChoreAssignment(
|
|
chore_id=chore.id,
|
|
assigned_to_user_id=assigned_to_user_id,
|
|
due_date=chore.next_due_date, # Assign on the chore's own next_due_date
|
|
is_complete=False
|
|
)
|
|
db.add(assignment)
|
|
new_assignments.append(assignment)
|
|
logger.info(f"Created assignment for chore '{chore.name}' to user {assigned_to_user_id} on {chore.next_due_date}")
|
|
|
|
current_date += timedelta(days=1)
|
|
|
|
if not new_assignments:
|
|
logger.info(f"No new assignments were generated for group {group_id} in the specified date range.")
|
|
return []
|
|
|
|
# Log a single group-level event for the schedule generation
|
|
await create_chore_history_entry(
|
|
db,
|
|
chore_id=None, # This is a group-level event
|
|
group_id=group_id,
|
|
changed_by_user_id=user_id,
|
|
event_type=ChoreHistoryEventTypeEnum.SCHEDULE_GENERATED,
|
|
event_data={
|
|
"start_date": start_date.isoformat(),
|
|
"end_date": end_date.isoformat(),
|
|
"member_ids": member_ids,
|
|
"assignments_created": len(new_assignments)
|
|
}
|
|
)
|
|
|
|
await db.flush()
|
|
|
|
# Refresh assignments to load relationships if needed, although not strictly necessary
|
|
# as the objects are already in the session.
|
|
for assign in new_assignments:
|
|
await db.refresh(assign)
|
|
|
|
return new_assignments |