from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List from datetime import datetime, time, date from app.db.session import get_db from app.schemas.attendance import AttendanceRecord, AttendanceRecordCreate, AttendanceRecordUpdate from app.models.attendance import AttendanceRecord as AttendanceRecordModel from app.models.users import User, UserRole from app.models.employees import Employee from app.core.deps import get_current_user, require_role router = APIRouter() def calculate_hours_worked(clock_in: time, clock_out: time) -> str: """Calculate hours worked between clock in and clock out times""" if not clock_in or not clock_out: return "0:00" # Convert to datetime for calculation today = date.today() dt_in = datetime.combine(today, clock_in) dt_out = datetime.combine(today, clock_out) # Handle overnight shifts if dt_out < dt_in: dt_out = dt_out.replace(day=today.day + 1) duration = dt_out - dt_in hours = duration.seconds // 3600 minutes = (duration.seconds % 3600) // 60 return f"{hours}:{minutes:02d}" @router.post("", response_model=AttendanceRecord) def create_attendance_record( attendance: AttendanceRecordCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): # Employees can only create attendance records for themselves unless they're HR/Admin if (current_user.role not in [UserRole.ADMIN, UserRole.HR_MANAGER]): current_employee = db.query(Employee).filter(Employee.user_id == current_user.id).first() if not current_employee or attendance.employee_id != current_employee.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Can only create attendance records for yourself" ) # Check if attendance record already exists for this date existing_record = db.query(AttendanceRecordModel).filter( AttendanceRecordModel.employee_id == attendance.employee_id, AttendanceRecordModel.date == attendance.date ).first() if existing_record: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Attendance record already exists for this date" ) attendance_data = attendance.dict() # Calculate hours worked if both clock in and out are provided if attendance.clock_in and attendance.clock_out: attendance_data['hours_worked'] = calculate_hours_worked(attendance.clock_in, attendance.clock_out) db_attendance = AttendanceRecordModel(**attendance_data) db.add(db_attendance) db.commit() db.refresh(db_attendance) return db_attendance @router.get("", response_model=List[AttendanceRecord]) def read_attendance_records( skip: int = 0, limit: int = 100, employee_id: int = None, start_date: date = None, end_date: date = None, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): query = db.query(AttendanceRecordModel) # Apply filters based on user role if current_user.role in [UserRole.ADMIN, UserRole.HR_MANAGER]: # HR and Admin can see all attendance records if employee_id: query = query.filter(AttendanceRecordModel.employee_id == employee_id) else: # Employees can only see their own attendance records current_employee = db.query(Employee).filter(Employee.user_id == current_user.id).first() if not current_employee: raise HTTPException(status_code=404, detail="Employee profile not found") query = query.filter(AttendanceRecordModel.employee_id == current_employee.id) # Apply date filters if start_date: query = query.filter(AttendanceRecordModel.date >= start_date) if end_date: query = query.filter(AttendanceRecordModel.date <= end_date) attendance_records = query.offset(skip).limit(limit).all() return attendance_records @router.get("/{attendance_id}", response_model=AttendanceRecord) def read_attendance_record( attendance_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): attendance_record = db.query(AttendanceRecordModel).filter(AttendanceRecordModel.id == attendance_id).first() if attendance_record is None: raise HTTPException(status_code=404, detail="Attendance record not found") # Check permissions if current_user.role not in [UserRole.ADMIN, UserRole.HR_MANAGER]: current_employee = db.query(Employee).filter(Employee.user_id == current_user.id).first() if not current_employee or attendance_record.employee_id != current_employee.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) return attendance_record @router.put("/{attendance_id}", response_model=AttendanceRecord) def update_attendance_record( attendance_id: int, attendance_update: AttendanceRecordUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): attendance_record = db.query(AttendanceRecordModel).filter(AttendanceRecordModel.id == attendance_id).first() if attendance_record is None: raise HTTPException(status_code=404, detail="Attendance record not found") # Check permissions if current_user.role not in [UserRole.ADMIN, UserRole.HR_MANAGER]: current_employee = db.query(Employee).filter(Employee.user_id == current_user.id).first() if not current_employee or attendance_record.employee_id != current_employee.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) update_data = attendance_update.dict(exclude_unset=True) for field, value in update_data.items(): setattr(attendance_record, field, value) # Recalculate hours worked if times are updated if attendance_record.clock_in and attendance_record.clock_out: attendance_record.hours_worked = calculate_hours_worked( attendance_record.clock_in, attendance_record.clock_out ) db.commit() db.refresh(attendance_record) return attendance_record @router.post("/{attendance_id}/clock-out") def clock_out( attendance_id: int, clock_out_time: time, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): attendance_record = db.query(AttendanceRecordModel).filter(AttendanceRecordModel.id == attendance_id).first() if attendance_record is None: raise HTTPException(status_code=404, detail="Attendance record not found") # Check permissions if current_user.role not in [UserRole.ADMIN, UserRole.HR_MANAGER]: current_employee = db.query(Employee).filter(Employee.user_id == current_user.id).first() if not current_employee or attendance_record.employee_id != current_employee.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) attendance_record.clock_out = clock_out_time # Calculate hours worked if attendance_record.clock_in: attendance_record.hours_worked = calculate_hours_worked( attendance_record.clock_in, clock_out_time ) db.commit() db.refresh(attendance_record) return {"message": "Clocked out successfully", "attendance_record": attendance_record} @router.delete("/{attendance_id}") def delete_attendance_record( attendance_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_role([UserRole.ADMIN, UserRole.HR_MANAGER])) ): attendance_record = db.query(AttendanceRecordModel).filter(AttendanceRecordModel.id == attendance_id).first() if attendance_record is None: raise HTTPException(status_code=404, detail="Attendance record not found") db.delete(attendance_record) db.commit() return {"message": "Attendance record deleted successfully"}