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, 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, 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: 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.") 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: if start_date <= chore.next_due_date <= end_date: 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, 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 [] await create_chore_history_entry( db, chore_id=None, 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() for assign in new_assignments: await db.refresh(assign) return new_assignments