updates
This commit is contained in:
401
Backend/src/hotel_services/routes/guest_request_routes.py
Normal file
401
Backend/src/hotel_services/routes/guest_request_routes.py
Normal file
@@ -0,0 +1,401 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy import and_, or_, desc
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
from ...shared.config.database import get_db
|
||||
from ...shared.config.logging_config import get_logger
|
||||
from ...security.middleware.auth import get_current_user, authorize_roles
|
||||
from ...auth.models.user import User
|
||||
from ...auth.models.role import Role
|
||||
from ..models.guest_request import GuestRequest, RequestType, RequestStatus, RequestPriority
|
||||
from ...bookings.models.booking import Booking, BookingStatus
|
||||
from ...rooms.models.room import Room
|
||||
from pydantic import BaseModel
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = APIRouter(prefix='/guest-requests', tags=['guest-requests'])
|
||||
|
||||
|
||||
# ==================== Pydantic Schemas ====================
|
||||
|
||||
class GuestRequestCreate(BaseModel):
|
||||
booking_id: int
|
||||
room_id: int
|
||||
request_type: str
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
priority: str = 'normal'
|
||||
guest_notes: Optional[str] = None
|
||||
|
||||
class GuestRequestUpdate(BaseModel):
|
||||
status: Optional[str] = None
|
||||
assigned_to: Optional[int] = None
|
||||
staff_notes: Optional[str] = None
|
||||
|
||||
|
||||
# ==================== Guest Requests ====================
|
||||
|
||||
@router.get('/')
|
||||
async def get_guest_requests(
|
||||
status: Optional[str] = Query(None),
|
||||
request_type: Optional[str] = Query(None),
|
||||
room_id: Optional[int] = Query(None),
|
||||
assigned_to: Optional[int] = Query(None),
|
||||
priority: Optional[str] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get guest requests with filtering"""
|
||||
try:
|
||||
query = db.query(GuestRequest).options(
|
||||
joinedload(GuestRequest.booking),
|
||||
joinedload(GuestRequest.room),
|
||||
joinedload(GuestRequest.guest)
|
||||
)
|
||||
|
||||
# Check if user is housekeeping - they can only see requests assigned to them or unassigned
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
is_housekeeping = role and role.name == 'housekeeping'
|
||||
|
||||
if is_housekeeping:
|
||||
query = query.filter(
|
||||
or_(
|
||||
GuestRequest.assigned_to == current_user.id,
|
||||
GuestRequest.assigned_to.is_(None)
|
||||
)
|
||||
)
|
||||
|
||||
if status:
|
||||
query = query.filter(GuestRequest.status == status)
|
||||
|
||||
if request_type:
|
||||
query = query.filter(GuestRequest.request_type == request_type)
|
||||
|
||||
if room_id:
|
||||
query = query.filter(GuestRequest.room_id == room_id)
|
||||
|
||||
if assigned_to:
|
||||
query = query.filter(GuestRequest.assigned_to == assigned_to)
|
||||
|
||||
if priority:
|
||||
query = query.filter(GuestRequest.priority == priority)
|
||||
|
||||
# Only show requests for checked-in bookings (guests must be in the room)
|
||||
query = query.join(Booking).filter(
|
||||
Booking.status == BookingStatus.checked_in
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
requests = query.order_by(
|
||||
desc(GuestRequest.priority == RequestPriority.urgent),
|
||||
desc(GuestRequest.priority == RequestPriority.high),
|
||||
desc(GuestRequest.requested_at)
|
||||
).offset((page - 1) * limit).limit(limit).all()
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'requests': [
|
||||
{
|
||||
'id': req.id,
|
||||
'booking_id': req.booking_id,
|
||||
'room_id': req.room_id,
|
||||
'room_number': req.room.room_number if req.room else None,
|
||||
'user_id': req.user_id,
|
||||
'guest_name': req.guest.full_name if req.guest else None,
|
||||
'request_type': req.request_type.value,
|
||||
'status': req.status.value,
|
||||
'priority': req.priority.value,
|
||||
'title': req.title,
|
||||
'description': req.description,
|
||||
'guest_notes': req.guest_notes,
|
||||
'staff_notes': req.staff_notes,
|
||||
'assigned_to': req.assigned_to,
|
||||
'assigned_staff_name': req.assigned_staff.full_name if req.assigned_staff else None,
|
||||
'fulfilled_by': req.fulfilled_by,
|
||||
'requested_at': req.requested_at.isoformat() if req.requested_at else None,
|
||||
'started_at': req.started_at.isoformat() if req.started_at else None,
|
||||
'fulfilled_at': req.fulfilled_at.isoformat() if req.fulfilled_at else None,
|
||||
'response_time_minutes': req.response_time_minutes,
|
||||
'fulfillment_time_minutes': req.fulfillment_time_minutes,
|
||||
}
|
||||
for req in requests
|
||||
],
|
||||
'pagination': {
|
||||
'total': total,
|
||||
'page': page,
|
||||
'limit': limit,
|
||||
'total_pages': (total + limit - 1) // limit
|
||||
}
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f'Error fetching guest requests: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail='Failed to fetch guest requests')
|
||||
|
||||
@router.post('/')
|
||||
async def create_guest_request(
|
||||
request_data: GuestRequestCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new guest request"""
|
||||
try:
|
||||
# Verify booking belongs to user
|
||||
booking = db.query(Booking).filter(Booking.id == request_data.booking_id).first()
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail='Booking not found')
|
||||
|
||||
if booking.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='You can only create requests for your own bookings')
|
||||
|
||||
# Guests can only create requests when they are checked in (in the room)
|
||||
if booking.status != BookingStatus.checked_in:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail='You can only create requests when you are checked in. Please check in first or contact reception.'
|
||||
)
|
||||
|
||||
# Verify room matches booking
|
||||
if booking.room_id != request_data.room_id:
|
||||
raise HTTPException(status_code=400, detail='Room ID does not match booking')
|
||||
|
||||
guest_request = GuestRequest(
|
||||
booking_id=request_data.booking_id,
|
||||
room_id=request_data.room_id,
|
||||
user_id=current_user.id,
|
||||
request_type=RequestType(request_data.request_type),
|
||||
priority=RequestPriority(request_data.priority),
|
||||
title=request_data.title,
|
||||
description=request_data.description,
|
||||
guest_notes=request_data.guest_notes,
|
||||
)
|
||||
|
||||
db.add(guest_request)
|
||||
db.commit()
|
||||
db.refresh(guest_request)
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'Guest request created successfully',
|
||||
'data': {'id': guest_request.id}
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=f'Invalid enum value: {str(e)}')
|
||||
except Exception as e:
|
||||
logger.error(f'Error creating guest request: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail='Failed to create guest request')
|
||||
|
||||
@router.put('/{request_id}')
|
||||
async def update_guest_request(
|
||||
request_id: int,
|
||||
request_data: GuestRequestUpdate,
|
||||
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a guest request (assign, update status, add notes)"""
|
||||
try:
|
||||
guest_request = db.query(GuestRequest).filter(GuestRequest.id == request_id).first()
|
||||
if not guest_request:
|
||||
raise HTTPException(status_code=404, detail='Guest request not found')
|
||||
|
||||
# Check permissions
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
is_housekeeping = role and role.name == 'housekeeping'
|
||||
|
||||
if is_housekeeping:
|
||||
# Housekeeping can only update requests assigned to them or unassigned
|
||||
if guest_request.assigned_to and guest_request.assigned_to != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='You can only update requests assigned to you')
|
||||
|
||||
update_data = request_data.dict(exclude_unset=True)
|
||||
|
||||
# Handle status changes
|
||||
if 'status' in update_data:
|
||||
new_status = RequestStatus(update_data['status'])
|
||||
old_status = guest_request.status
|
||||
|
||||
# Track timestamps
|
||||
if new_status == RequestStatus.in_progress and old_status == RequestStatus.pending:
|
||||
guest_request.started_at = datetime.utcnow()
|
||||
if guest_request.requested_at:
|
||||
delta = datetime.utcnow() - guest_request.requested_at
|
||||
guest_request.response_time_minutes = int(delta.total_seconds() / 60)
|
||||
# Auto-assign if not assigned
|
||||
if not guest_request.assigned_to:
|
||||
guest_request.assigned_to = current_user.id
|
||||
|
||||
elif new_status == RequestStatus.fulfilled and old_status != RequestStatus.fulfilled:
|
||||
guest_request.fulfilled_at = datetime.utcnow()
|
||||
guest_request.fulfilled_by = current_user.id
|
||||
if guest_request.started_at:
|
||||
delta = datetime.utcnow() - guest_request.started_at
|
||||
guest_request.fulfillment_time_minutes = int(delta.total_seconds() / 60)
|
||||
|
||||
update_data['status'] = new_status
|
||||
|
||||
if 'assigned_to' in update_data:
|
||||
update_data['assigned_to'] = update_data['assigned_to'] if update_data['assigned_to'] else None
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(guest_request, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(guest_request)
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'Guest request updated successfully'
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=f'Invalid enum value: {str(e)}')
|
||||
except Exception as e:
|
||||
logger.error(f'Error updating guest request: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail='Failed to update guest request')
|
||||
|
||||
@router.get('/{request_id}')
|
||||
async def get_guest_request(
|
||||
request_id: int,
|
||||
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a single guest request"""
|
||||
try:
|
||||
request = db.query(GuestRequest).options(
|
||||
joinedload(GuestRequest.booking),
|
||||
joinedload(GuestRequest.room),
|
||||
joinedload(GuestRequest.guest),
|
||||
joinedload(GuestRequest.assigned_staff),
|
||||
joinedload(GuestRequest.fulfilled_staff)
|
||||
).filter(GuestRequest.id == request_id).first()
|
||||
|
||||
if not request:
|
||||
raise HTTPException(status_code=404, detail='Guest request not found')
|
||||
|
||||
# Check permissions
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
is_housekeeping = role and role.name == 'housekeeping'
|
||||
|
||||
if is_housekeeping and request.assigned_to and request.assigned_to != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='You can only view requests assigned to you')
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'id': request.id,
|
||||
'booking_id': request.booking_id,
|
||||
'room_id': request.room_id,
|
||||
'room_number': request.room.room_number if request.room else None,
|
||||
'user_id': request.user_id,
|
||||
'guest_name': request.guest.full_name if request.guest else None,
|
||||
'guest_email': request.guest.email if request.guest else None,
|
||||
'request_type': request.request_type.value,
|
||||
'status': request.status.value,
|
||||
'priority': request.priority.value,
|
||||
'title': request.title,
|
||||
'description': request.description,
|
||||
'guest_notes': request.guest_notes,
|
||||
'staff_notes': request.staff_notes,
|
||||
'assigned_to': request.assigned_to,
|
||||
'assigned_staff_name': request.assigned_staff.full_name if request.assigned_staff else None,
|
||||
'fulfilled_by': request.fulfilled_by,
|
||||
'fulfilled_staff_name': request.fulfilled_staff.full_name if request.fulfilled_staff else None,
|
||||
'requested_at': request.requested_at.isoformat() if request.requested_at else None,
|
||||
'started_at': request.started_at.isoformat() if request.started_at else None,
|
||||
'fulfilled_at': request.fulfilled_at.isoformat() if request.fulfilled_at else None,
|
||||
'response_time_minutes': request.response_time_minutes,
|
||||
'fulfillment_time_minutes': request.fulfillment_time_minutes,
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f'Error fetching guest request: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail='Failed to fetch guest request')
|
||||
|
||||
@router.post('/{request_id}/assign')
|
||||
async def assign_request(
|
||||
request_id: int,
|
||||
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Assign a request to the current user (housekeeping)"""
|
||||
try:
|
||||
request = db.query(GuestRequest).filter(GuestRequest.id == request_id).first()
|
||||
if not request:
|
||||
raise HTTPException(status_code=404, detail='Guest request not found')
|
||||
|
||||
if request.status == RequestStatus.fulfilled:
|
||||
raise HTTPException(status_code=400, detail='Cannot assign a fulfilled request')
|
||||
|
||||
request.assigned_to = current_user.id
|
||||
if request.status == RequestStatus.pending:
|
||||
request.status = RequestStatus.in_progress
|
||||
request.started_at = datetime.utcnow()
|
||||
if request.requested_at:
|
||||
delta = datetime.utcnow() - request.requested_at
|
||||
request.response_time_minutes = int(delta.total_seconds() / 60)
|
||||
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'Request assigned successfully'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f'Error assigning request: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail='Failed to assign request')
|
||||
|
||||
@router.post('/{request_id}/fulfill')
|
||||
async def fulfill_request(
|
||||
request_id: int,
|
||||
staff_notes: Optional[str] = None,
|
||||
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Mark a request as fulfilled"""
|
||||
try:
|
||||
request = db.query(GuestRequest).filter(GuestRequest.id == request_id).first()
|
||||
if not request:
|
||||
raise HTTPException(status_code=404, detail='Guest request not found')
|
||||
|
||||
# Check permissions
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
is_housekeeping = role and role.name == 'housekeeping'
|
||||
|
||||
if is_housekeeping and request.assigned_to != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='You can only fulfill requests assigned to you')
|
||||
|
||||
if request.status == RequestStatus.fulfilled:
|
||||
raise HTTPException(status_code=400, detail='Request is already fulfilled')
|
||||
|
||||
request.status = RequestStatus.fulfilled
|
||||
request.fulfilled_by = current_user.id
|
||||
request.fulfilled_at = datetime.utcnow()
|
||||
|
||||
if staff_notes:
|
||||
request.staff_notes = (request.staff_notes or '') + f'\n{staff_notes}' if request.staff_notes else staff_notes
|
||||
|
||||
if request.started_at:
|
||||
delta = datetime.utcnow() - request.started_at
|
||||
request.fulfillment_time_minutes = int(delta.total_seconds() / 60)
|
||||
elif request.requested_at:
|
||||
# If never started, calculate from request time
|
||||
delta = datetime.utcnow() - request.requested_at
|
||||
request.fulfillment_time_minutes = int(delta.total_seconds() / 60)
|
||||
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'Request marked as fulfilled'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f'Error fulfilling request: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail='Failed to fulfill request')
|
||||
|
||||
Reference in New Issue
Block a user