This commit is contained in:
Iliyan Angelov
2025-12-03 01:31:34 +02:00
parent e32527ae8c
commit 5fb50983a9
37 changed files with 5844 additions and 201 deletions

View File

@@ -346,10 +346,11 @@ async def get_housekeeping_tasks(
date: Optional[str] = Query(None),
page: int = Query(1, ge=1),
limit: int = Query(20, ge=1, le=100),
include_cleaning_rooms: bool = Query(True, description='Include rooms in cleaning status even without tasks'),
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
db: Session = Depends(get_db)
):
"""Get housekeeping tasks with filtering"""
"""Get housekeeping tasks with filtering. Also includes rooms in cleaning status."""
try:
# Check user role - housekeeping and staff users should only see their assigned tasks
role = db.query(Role).filter(Role.id == current_user.role_id).first()
@@ -359,8 +360,14 @@ async def get_housekeeping_tasks(
query = db.query(HousekeepingTask)
# Filter by assigned_to for housekeeping and staff users (not admin)
# But also include unassigned tasks so they can pick them up
if is_housekeeping_or_staff:
query = query.filter(HousekeepingTask.assigned_to == current_user.id)
query = query.filter(
or_(
HousekeepingTask.assigned_to == current_user.id,
HousekeepingTask.assigned_to.is_(None)
)
)
if room_id:
query = query.filter(HousekeepingTask.room_id == room_id)
@@ -379,7 +386,11 @@ async def get_housekeeping_tasks(
tasks = query.offset(offset).limit(limit).all()
result = []
task_room_ids = set()
# Process existing tasks
for task in tasks:
task_room_ids.add(task.room_id)
result.append({
'id': task.id,
'room_id': task.room_id,
@@ -396,9 +407,84 @@ async def get_housekeeping_tasks(
'notes': task.notes,
'quality_score': task.quality_score,
'estimated_duration_minutes': task.estimated_duration_minutes,
'actual_duration_minutes': task.actual_duration_minutes
'actual_duration_minutes': task.actual_duration_minutes,
'room_status': task.room.status.value if task.room else None
})
# Include rooms in cleaning status that don't have tasks (or have unassigned tasks for housekeeping users)
if include_cleaning_rooms:
rooms_query = db.query(Room).filter(Room.status == RoomStatus.cleaning)
if room_id:
rooms_query = rooms_query.filter(Room.id == room_id)
# For housekeeping/staff users, also include rooms with unassigned tasks
if is_housekeeping_or_staff:
# Get room IDs with unassigned tasks
unassigned_task_rooms = db.query(HousekeepingTask.room_id).filter(
and_(
HousekeepingTask.assigned_to.is_(None),
HousekeepingTask.status.in_([HousekeepingStatus.pending, HousekeepingStatus.in_progress])
)
).distinct().all()
unassigned_room_ids = [r[0] for r in unassigned_task_rooms]
# Include rooms in cleaning status OR rooms with unassigned tasks
if unassigned_room_ids:
rooms_query = db.query(Room).filter(
or_(
Room.status == RoomStatus.cleaning,
Room.id.in_(unassigned_room_ids)
)
)
if room_id:
rooms_query = rooms_query.filter(Room.id == room_id)
cleaning_rooms = rooms_query.all()
# Add rooms in cleaning status that don't have tasks in current page results
for room in cleaning_rooms:
if room.id not in task_room_ids:
# Check if there are any pending tasks for this room
pending_tasks = db.query(HousekeepingTask).filter(
and_(
HousekeepingTask.room_id == room.id,
HousekeepingTask.status.in_([HousekeepingStatus.pending, HousekeepingStatus.in_progress])
)
).all()
# For housekeeping/staff, only show if there are unassigned tasks or if room is in cleaning
if is_housekeeping_or_staff:
has_unassigned = any(t.assigned_to is None for t in pending_tasks)
if not has_unassigned and room.status != RoomStatus.cleaning:
continue
# Create a virtual task entry for rooms in cleaning status
result.append({
'id': None, # No task ID since this is a room status entry
'room_id': room.id,
'room_number': room.room_number,
'booking_id': None,
'task_type': 'vacant', # Default task type
'status': 'pending',
'scheduled_time': datetime.utcnow().isoformat(),
'started_at': None,
'completed_at': None,
'assigned_to': None,
'assigned_staff_name': None,
'checklist_items': [],
'notes': 'Room is in cleaning mode',
'quality_score': None,
'estimated_duration_minutes': None,
'actual_duration_minutes': None,
'room_status': room.status.value,
'is_room_status_only': True # Flag to indicate this is from room status, not a task
})
# Update total count to include cleaning rooms
if include_cleaning_rooms:
total = len(result)
return {
'status': 'success',
'data': {
@@ -418,11 +504,16 @@ async def get_housekeeping_tasks(
@router.post('/housekeeping')
async def create_housekeeping_task(
task_data: dict,
current_user: User = Depends(authorize_roles('admin', 'staff')),
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
db: Session = Depends(get_db)
):
"""Create a new housekeeping task"""
try:
# Check user role - housekeeping users can only assign tasks to themselves
role = db.query(Role).filter(Role.id == current_user.role_id).first()
is_admin = role and role.name == 'admin'
is_housekeeping = role and role.name == 'housekeeping'
room = db.query(Room).filter(Room.id == task_data.get('room_id')).first()
if not room:
raise HTTPException(status_code=404, detail='Room not found')
@@ -430,6 +521,14 @@ async def create_housekeeping_task(
scheduled_time = datetime.fromisoformat(task_data['scheduled_time'].replace('Z', '+00:00'))
assigned_to = task_data.get('assigned_to')
# Housekeeping users can only assign tasks to themselves
if is_housekeeping and assigned_to and assigned_to != current_user.id:
raise HTTPException(status_code=403, detail='Housekeeping users can only assign tasks to themselves')
# If housekeeping user doesn't specify assigned_to, assign to themselves
if is_housekeeping and not assigned_to:
assigned_to = current_user.id
task = HousekeepingTask(
room_id=task_data['room_id'],
booking_id=task_data.get('booking_id'),
@@ -450,8 +549,7 @@ async def create_housekeeping_task(
# Send notification to assigned staff member if task is assigned
if assigned_to:
try:
from ..routes.chat_routes import manager
assigned_staff = db.query(User).filter(User.id == assigned_to).first()
from ...notifications.routes.notification_routes import notification_manager
task_data_notification = {
'id': task.id,
'room_id': task.room_id,
@@ -467,13 +565,9 @@ async def create_housekeeping_task(
'data': task_data_notification
}
# Send notification to the specific staff member
if assigned_to in manager.staff_connections:
try:
await manager.staff_connections[assigned_to].send_json(notification_data)
except Exception as e:
logger.error(f'Error sending housekeeping task notification to staff {assigned_to}: {str(e)}', exc_info=True, extra={'staff_id': assigned_to})
await notification_manager.send_to_user(assigned_to, notification_data)
except Exception as e:
logger.error(f'Error setting up housekeeping task notification: {str(e)}', exc_info=True)
logger.error(f'Error sending housekeeping task notification: {str(e)}', exc_info=True)
return {
'status': 'success',
@@ -504,21 +598,34 @@ async def update_housekeeping_task(
is_housekeeping_or_staff = role and role.name in ('housekeeping', 'staff')
if is_housekeeping_or_staff:
# Housekeeping and staff can only update tasks assigned to them
if task.assigned_to != current_user.id:
# Housekeeping and staff can start unassigned tasks (assign to themselves)
if task.assigned_to is None:
# Allow housekeeping users to assign unassigned tasks to themselves
if 'status' in task_data and task_data['status'] == 'in_progress':
task.assigned_to = current_user.id
task_data['assigned_to'] = current_user.id
elif task.assigned_to != current_user.id:
# If task is assigned, only the assigned user can update it
raise HTTPException(status_code=403, detail='You can only update tasks assigned to you')
# Housekeeping and staff cannot change assignment
if 'assigned_to' in task_data and task_data.get('assigned_to') != task.assigned_to:
# Housekeeping and staff cannot change assignment of already assigned tasks
if 'assigned_to' in task_data and task.assigned_to is not None and task_data.get('assigned_to') != task.assigned_to:
raise HTTPException(status_code=403, detail='You cannot change task assignment')
old_assigned_to = task.assigned_to
assigned_to_changed = False
if 'assigned_to' in task_data and is_admin:
new_assigned_to = task_data.get('assigned_to')
if new_assigned_to != old_assigned_to:
task.assigned_to = new_assigned_to
assigned_to_changed = True
# Handle assignment - admin can assign, housekeeping can self-assign unassigned tasks
if 'assigned_to' in task_data:
if is_admin:
new_assigned_to = task_data.get('assigned_to')
if new_assigned_to != old_assigned_to:
task.assigned_to = new_assigned_to
assigned_to_changed = True
elif is_housekeeping_or_staff and task.assigned_to is None:
# Housekeeping can assign unassigned tasks to themselves when starting
if task_data.get('assigned_to') == current_user.id:
task.assigned_to = current_user.id
assigned_to_changed = True
if 'status' in task_data:
new_status = HousekeepingStatus(task_data['status'])
@@ -534,6 +641,9 @@ async def update_housekeeping_task(
if new_status == HousekeepingStatus.in_progress and not task.started_at:
task.started_at = datetime.utcnow()
# If task was unassigned, assign it to the current user
if task.assigned_to is None and is_housekeeping_or_staff:
task.assigned_to = current_user.id
elif new_status == HousekeepingStatus.completed and not task.completed_at:
task.completed_at = datetime.utcnow()
if task.started_at:
@@ -568,8 +678,23 @@ async def update_housekeeping_task(
# Keep room as cleaning if there are other pending tasks
room.status = RoomStatus.cleaning
else:
# No pending tasks and no maintenance - room is ready
room.status = RoomStatus.available
# No pending tasks and no maintenance - room is ready for check-in
# Check if there are any upcoming bookings for this room
from ...bookings.models.booking import Booking, BookingStatus
upcoming_booking = db.query(Booking).filter(
and_(
Booking.room_id == room.id,
Booking.status == BookingStatus.confirmed,
Booking.check_in_date <= datetime.utcnow() + timedelta(days=1)
)
).first()
if upcoming_booking:
# Room has upcoming booking, keep as available (ready for check-in)
room.status = RoomStatus.available
else:
# No upcoming bookings, room is available
room.status = RoomStatus.available
if 'checklist_items' in task_data:
task.checklist_items = task_data['checklist_items']
@@ -591,7 +716,7 @@ async def update_housekeeping_task(
# Send notification if assignment changed
if assigned_to_changed and task.assigned_to:
try:
from ..routes.chat_routes import manager
from ...notifications.routes.notification_routes import notification_manager
room = db.query(Room).filter(Room.id == task.room_id).first()
task_data_notification = {
'id': task.id,
@@ -608,13 +733,9 @@ async def update_housekeeping_task(
'data': task_data_notification
}
# Send notification to the newly assigned staff member
if task.assigned_to in manager.staff_connections:
try:
await manager.staff_connections[task.assigned_to].send_json(notification_data)
except Exception as e:
logger.error(f'Error sending housekeeping task notification to staff {task.assigned_to}: {str(e)}', exc_info=True, extra={'staff_id': task.assigned_to})
await notification_manager.send_to_user(task.assigned_to, notification_data)
except Exception as e:
logger.error(f'Error setting up housekeeping task notification: {str(e)}', exc_info=True)
logger.error(f'Error sending housekeeping task notification: {str(e)}', exc_info=True)
return {
'status': 'success',