"""Chore Completion Log API endpoints.""" from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from sqlalchemy import func, and_ from typing import List, Optional from datetime import datetime, timedelta from app.core.database import get_db from app.api.v1.auth import get_current_user from app.models.user import User from app.models.chore import Chore from app.models.chore_assignment import ChoreAssignment from app.models.chore_completion_log import ChoreCompletionLog from app.schemas import chore_completion_log as log_schemas router = APIRouter() def enrich_completion_log(db: Session, log: ChoreCompletionLog) -> dict: """Add related information to completion log.""" <<<<<<< HEAD # Get chore info chore = db.query(Chore).filter(Chore.id == log.chore_id).first() # Get user info user = db.query(User).filter(User.id == log.user_id).first() # Get verified_by info if exists ======= chore = db.query(Chore).filter(Chore.id == log.chore_id).first() user = db.query(User).filter(User.id == log.user_id).first() >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 verified_by_name = None if log.verified_by_user_id: verified_by = db.query(User).filter(User.id == log.verified_by_user_id).first() if verified_by: verified_by_name = verified_by.full_name or verified_by.username return { "id": log.id, "chore_id": log.chore_id, "user_id": log.user_id, "completed_at": log.completed_at, "notes": log.notes, "verified_by_user_id": log.verified_by_user_id, "created_at": log.created_at, "chore_title": chore.title if chore else None, "user_name": user.full_name or user.username if user else None, "user_avatar": user.avatar_url if user else None, "verified_by_name": verified_by_name } @router.post("/{chore_id}/complete", response_model=log_schemas.ChoreCompletionLog, status_code=status.HTTP_201_CREATED) <<<<<<< HEAD def complete_chore( chore_id: int, notes: Optional[str] = None, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Log a chore completion. Creates a completion log entry and updates the chore assignment. This is the primary endpoint for completing chores. """ # Check if chore exists chore = db.query(Chore).filter(Chore.id == chore_id).first() if not chore: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Chore not found" ) # Check if user is assigned to this chore assignment = db.query(ChoreAssignment).filter( ChoreAssignment.chore_id == chore_id, ChoreAssignment.user_id == current_user.id ).first() if not assignment: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You are not assigned to this chore" ) # Create completion log completion_log = ChoreCompletionLog( chore_id=chore_id, user_id=current_user.id, completed_at=datetime.utcnow(), notes=notes ) db.add(completion_log) # Update assignment completed_at assignment.completed_at = datetime.utcnow() # Check if all assignments are completed all_assignments = db.query(ChoreAssignment).filter( ChoreAssignment.chore_id == chore_id ).all() ======= def complete_chore(chore_id: int, notes: Optional[str] = None, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): chore = db.query(Chore).filter(Chore.id == chore_id).first() if not chore: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Chore not found") assignment = db.query(ChoreAssignment).filter(ChoreAssignment.chore_id == chore_id, ChoreAssignment.user_id == current_user.id).first() if not assignment: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You are not assigned to this chore") completion_log = ChoreCompletionLog(chore_id=chore_id, user_id=current_user.id, completed_at=datetime.utcnow(), notes=notes) db.add(completion_log) assignment.completed_at = datetime.utcnow() all_assignments = db.query(ChoreAssignment).filter(ChoreAssignment.chore_id == chore_id).all() >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 if all(a.completed_at is not None for a in all_assignments): chore.completed_at = datetime.utcnow() chore.status = "completed" db.commit() db.refresh(completion_log) <<<<<<< HEAD ======= >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 return enrich_completion_log(db, completion_log) @router.get("/completions", response_model=List[log_schemas.ChoreCompletionLog]) <<<<<<< HEAD def get_completion_logs( skip: int = 0, limit: int = 100, chore_id: Optional[int] = Query(None, description="Filter by chore ID"), user_id: Optional[int] = Query(None, description="Filter by user ID"), start_date: Optional[datetime] = Query(None, description="Filter completions after this date"), end_date: Optional[datetime] = Query(None, description="Filter completions before this date"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Get chore completion logs with optional filters. - **chore_id**: Filter by specific chore - **user_id**: Filter by specific user - **start_date**: Filter completions after this date - **end_date**: Filter completions before this date """ query = db.query(ChoreCompletionLog) # Apply filters if chore_id: query = query.filter(ChoreCompletionLog.chore_id == chore_id) if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id) if start_date: query = query.filter(ChoreCompletionLog.completed_at >= start_date) if end_date: query = query.filter(ChoreCompletionLog.completed_at <= end_date) # Order by most recent first query = query.order_by(ChoreCompletionLog.completed_at.desc()) logs = query.offset(skip).limit(limit).all() # Enrich with related data ======= def get_completion_logs(skip: int = 0, limit: int = 100, chore_id: Optional[int] = Query(None), user_id: Optional[int] = Query(None), start_date: Optional[datetime] = Query(None), end_date: Optional[datetime] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): query = db.query(ChoreCompletionLog) if chore_id: query = query.filter(ChoreCompletionLog.chore_id == chore_id) if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id) if start_date: query = query.filter(ChoreCompletionLog.completed_at >= start_date) if end_date: query = query.filter(ChoreCompletionLog.completed_at <= end_date) query = query.order_by(ChoreCompletionLog.completed_at.desc()) logs = query.offset(skip).limit(limit).all() >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 return [enrich_completion_log(db, log) for log in logs] @router.get("/reports/weekly", response_model=log_schemas.WeeklyChoreReport) <<<<<<< HEAD def get_weekly_report( user_id: Optional[int] = Query(None, description="Get report for specific user (omit for family-wide)"), weeks_ago: int = Query(0, description="Number of weeks ago (0 = current week)"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Generate a weekly chore completion report. - **user_id**: Optional - get report for specific user - **weeks_ago**: Which week to report on (0 = current week, 1 = last week, etc.) Returns comprehensive statistics including: - Total completions - Completions by user - Completions by chore - Completions by day - Top performers - Recent completions """ # Calculate week boundaries ======= def get_weekly_report(user_id: Optional[int] = Query(None), weeks_ago: int = Query(0), db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) start_of_week = today - timedelta(days=today.weekday()) - timedelta(weeks=weeks_ago) end_of_week = start_of_week + timedelta(days=7) <<<<<<< HEAD # Base query query = db.query(ChoreCompletionLog).filter( and_( ChoreCompletionLog.completed_at >= start_of_week, ChoreCompletionLog.completed_at < end_of_week ) ) # Apply user filter if specified if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id) logs = query.all() # Calculate statistics total_completions = len(logs) # Completions by user ======= query = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.completed_at >= start_of_week, ChoreCompletionLog.completed_at < end_of_week)) if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id) logs = query.all() >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 completions_by_user = {} for log in logs: user = db.query(User).filter(User.id == log.user_id).first() if user: username = user.full_name or user.username completions_by_user[username] = completions_by_user.get(username, 0) + 1 <<<<<<< HEAD # Completions by chore completions_by_chore = {} for log in logs: chore = db.query(Chore).filter(Chore.id == log.chore_id).first() if chore: completions_by_chore[chore.title] = completions_by_chore.get(chore.title, 0) + 1 # Completions by day ======= completions_by_chore = {} for log in logs: chore = db.query(Chore).filter(Chore.id == log.chore_id).first() if chore: completions_by_chore[chore.title] = completions_by_chore.get(chore.title, 0) + 1 >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 completions_by_day = {} for log in logs: day_name = log.completed_at.strftime("%A") completions_by_day[day_name] = completions_by_day.get(day_name, 0) + 1 <<<<<<< HEAD # Top performers user_stats = [] for user_name, count in completions_by_user.items(): user = db.query(User).filter( (User.full_name == user_name) | (User.username == user_name) ).first() user_stats.append({ "username": user_name, "count": count, "avatar_url": user.avatar_url if user else None }) user_stats.sort(key=lambda x: x["count"], reverse=True) top_performers = user_stats[:5] # Top 5 performers # Recent completions (last 10) recent_logs = sorted(logs, key=lambda x: x.completed_at, reverse=True)[:10] recent_completions = [enrich_completion_log(db, log) for log in recent_logs] return { "start_date": start_of_week, "end_date": end_of_week, "total_completions": total_completions, "completions_by_user": completions_by_user, "completions_by_chore": completions_by_chore, "completions_by_day": completions_by_day, "top_performers": top_performers, "recent_completions": recent_completions } @router.get("/reports/user/{user_id}", response_model=log_schemas.UserChoreStats) def get_user_stats( user_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Get comprehensive statistics for a specific user. Returns: - Total completions (all time) - Completions this week - Completions this month - Favorite chore (most completed) - Recent completions """ # Check if user exists user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Total completions total_completions = db.query(ChoreCompletionLog).filter( ChoreCompletionLog.user_id == user_id ).count() # This week today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) start_of_week = today - timedelta(days=today.weekday()) completions_this_week = db.query(ChoreCompletionLog).filter( and_( ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_week ) ).count() # This month start_of_month = today.replace(day=1) completions_this_month = db.query(ChoreCompletionLog).filter( and_( ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_month ) ).count() # Favorite chore (most completed) favorite_chore = None chore_counts = db.query( ChoreCompletionLog.chore_id, func.count(ChoreCompletionLog.id).label('count') ).filter( ChoreCompletionLog.user_id == user_id ).group_by( ChoreCompletionLog.chore_id ).order_by( func.count(ChoreCompletionLog.id).desc() ).first() if chore_counts: chore = db.query(Chore).filter(Chore.id == chore_counts[0]).first() if chore: favorite_chore = chore.title # Recent completions recent_logs = db.query(ChoreCompletionLog).filter( ChoreCompletionLog.user_id == user_id ).order_by( ChoreCompletionLog.completed_at.desc() ).limit(10).all() recent_completions = [enrich_completion_log(db, log) for log in recent_logs] return { "user_id": user.id, "username": user.username, "full_name": user.full_name, "avatar_url": user.avatar_url, "total_completions": total_completions, "completions_this_week": completions_this_week, "completions_this_month": completions_this_month, "favorite_chore": favorite_chore, "recent_completions": recent_completions } @router.delete("/completions/{log_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_completion_log( log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Delete a completion log entry (admin only or log owner). Useful for correcting mistakes or removing accidental completions. """ log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first() if not log: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found" ) # Only admin or the user who completed can delete if not current_user.is_admin and log.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to delete this completion log" ) ======= user_stats = [] for user_name, count in completions_by_user.items(): user = db.query(User).filter((User.full_name == user_name) | (User.username == user_name)).first() user_stats.append({"username": user_name, "count": count, "avatar_url": user.avatar_url if user else None}) user_stats.sort(key=lambda x: x["count"], reverse=True) top_performers = user_stats[:5] recent_logs = sorted(logs, key=lambda x: x.completed_at, reverse=True)[:10] recent_completions = [enrich_completion_log(db, log) for log in recent_logs] return {"start_date": start_of_week, "end_date": end_of_week, "total_completions": len(logs), "completions_by_user": completions_by_user, "completions_by_chore": completions_by_chore, "completions_by_day": completions_by_day, "top_performers": top_performers, "recent_completions": recent_completions} @router.get("/reports/user/{user_id}", response_model=log_schemas.UserChoreStats) def get_user_stats(user_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") total_completions = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.user_id == user_id).count() today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) start_of_week = today - timedelta(days=today.weekday()) completions_this_week = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_week)).count() start_of_month = today.replace(day=1) completions_this_month = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_month)).count() favorite_chore = None chore_counts = db.query(ChoreCompletionLog.chore_id, func.count(ChoreCompletionLog.id).label('count')).filter(ChoreCompletionLog.user_id == user_id).group_by(ChoreCompletionLog.chore_id).order_by(func.count(ChoreCompletionLog.id).desc()).first() if chore_counts: chore = db.query(Chore).filter(Chore.id == chore_counts[0]).first() if chore: favorite_chore = chore.title recent_logs = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.user_id == user_id).order_by(ChoreCompletionLog.completed_at.desc()).limit(10).all() recent_completions = [enrich_completion_log(db, log) for log in recent_logs] return {"user_id": user.id, "username": user.username, "full_name": user.full_name, "avatar_url": user.avatar_url, "total_completions": total_completions, "completions_this_week": completions_this_week, "completions_this_month": completions_this_month, "favorite_chore": favorite_chore, "recent_completions": recent_completions} @router.delete("/completions/{log_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_completion_log(log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first() if not log: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found") if not current_user.is_admin and log.user_id != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized") >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 db.delete(log) db.commit() return None @router.post("/completions/{log_id}/verify", response_model=log_schemas.ChoreCompletionLog) <<<<<<< HEAD def verify_completion( log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Verify a chore completion (requires different user than completer). Useful for parents verifying kids' chores, or quality checks. """ log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first() if not log: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found" ) # Can't verify your own completion if log.user_id == current_user.id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="You cannot verify your own completion" ) log.verified_by_user_id = current_user.id db.commit() db.refresh(log) ======= def verify_completion(log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first() if not log: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found") if log.user_id == current_user.id: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot verify own completion") log.verified_by_user_id = current_user.id db.commit() db.refresh(log) >>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2 return enrich_completion_log(db, log)