This commit is contained in:
Iliyan Angelov
2025-12-01 15:34:45 +02:00
parent 49181cf48c
commit f7d6f24e49
28 changed files with 2121 additions and 832 deletions

View File

@@ -346,19 +346,20 @@ async def get_housekeeping_tasks(
date: 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')),
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
db: Session = Depends(get_db)
):
"""Get housekeeping tasks with filtering"""
try:
# Check if user is staff (not admin) - staff should only see their assigned tasks
# 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()
is_staff = role and role.name == 'staff'
is_admin = role and role.name == 'admin'
is_housekeeping_or_staff = role and role.name in ('housekeeping', 'staff')
query = db.query(HousekeepingTask)
# Filter by assigned_to for staff users
if is_staff:
# Filter by assigned_to for housekeeping and staff users (not admin)
if is_housekeeping_or_staff:
query = query.filter(HousekeepingTask.assigned_to == current_user.id)
if room_id:
@@ -488,7 +489,7 @@ async def create_housekeeping_task(
async def update_housekeeping_task(
task_id: int,
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)
):
"""Update a housekeeping task"""
@@ -497,22 +498,23 @@ async def update_housekeeping_task(
if not task:
raise HTTPException(status_code=404, detail='Housekeeping task not found')
# Check if user is staff (not admin) - staff can only update their own assigned tasks
# Check user role - housekeeping and staff users can only update their own assigned tasks
role = db.query(Role).filter(Role.id == current_user.role_id).first()
is_staff = role and role.name == 'staff'
is_admin = role and role.name == 'admin'
is_housekeeping_or_staff = role and role.name in ('housekeeping', 'staff')
if is_staff:
# Staff can only update tasks assigned to them
if is_housekeeping_or_staff:
# Housekeeping and staff can only update tasks assigned to them
if task.assigned_to != current_user.id:
raise HTTPException(status_code=403, detail='You can only update tasks assigned to you')
# Staff cannot change assignment
# Housekeeping and staff cannot change assignment
if 'assigned_to' in task_data 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 not is_staff:
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
@@ -537,6 +539,37 @@ async def update_housekeeping_task(
if task.started_at:
duration = (task.completed_at - task.started_at).total_seconds() / 60
task.actual_duration_minutes = int(duration)
# Update room status when housekeeping task is completed
room = db.query(Room).filter(Room.id == task.room_id).first()
if room:
# Check if there are other pending housekeeping tasks for this room
pending_tasks = db.query(HousekeepingTask).filter(
and_(
HousekeepingTask.room_id == room.id,
HousekeepingTask.id != task.id,
HousekeepingTask.status.in_([HousekeepingStatus.pending, HousekeepingStatus.in_progress])
)
).count()
# Check if there's active maintenance
from ...rooms.models.room_maintenance import RoomMaintenance, MaintenanceStatus
active_maintenance = db.query(RoomMaintenance).filter(
and_(
RoomMaintenance.room_id == room.id,
RoomMaintenance.blocks_room == True,
RoomMaintenance.status.in_([MaintenanceStatus.scheduled, MaintenanceStatus.in_progress])
)
).first()
if active_maintenance:
room.status = RoomStatus.maintenance
elif pending_tasks > 0:
# 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
if 'checklist_items' in task_data:
task.checklist_items = task_data['checklist_items']

View File

@@ -72,6 +72,26 @@ async def get_amenities(db: Session=Depends(get_db)):
logger.error(f'Error fetching amenities: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get('/room-types')
async def get_room_types(db: Session=Depends(get_db)):
"""Get all room types for dropdowns and forms."""
try:
room_types = db.query(RoomType).order_by(RoomType.name).all()
room_types_list = [
{
'id': rt.id,
'name': rt.name,
'description': rt.description,
'base_price': float(rt.base_price) if rt.base_price else 0.0,
'capacity': rt.capacity,
}
for rt in room_types
]
return {'status': 'success', 'data': {'room_types': room_types_list}}
except Exception as e:
logger.error(f'Error fetching room types: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get('/available')
async def search_available_rooms(request: Request, from_date: str=Query(..., alias='from'), to_date: str=Query(..., alias='to'), roomId: Optional[int]=Query(None, alias='roomId'), type: Optional[str]=Query(None), capacity: Optional[int]=Query(None), page: int=Query(1, ge=1), limit: int=Query(12, ge=1, le=100), db: Session=Depends(get_db)):
try:
@@ -215,19 +235,17 @@ async def get_room_by_number(room_number: str, request: Request, db: Session=Dep
@router.post('/', dependencies=[Depends(authorize_roles('admin'))])
async def create_room(room_data: CreateRoomRequest, request: Request, current_user: User=Depends(authorize_roles('admin', 'staff')), db: Session=Depends(get_db)):
"""Create a new room with validated input using Pydantic schema."""
# Start transaction
transaction = db.begin()
try:
# Lock room type to prevent race conditions
room_type = db.query(RoomType).filter(RoomType.id == room_data.room_type_id).with_for_update().first()
if not room_type:
transaction.rollback()
db.rollback()
raise HTTPException(status_code=404, detail='Room type not found')
# Check for duplicate room number with locking
existing = db.query(Room).filter(Room.room_number == room_data.room_number).with_for_update().first()
if existing:
transaction.rollback()
db.rollback()
raise HTTPException(status_code=400, detail='Room number already exists')
# Use price from request or default to room type base price
@@ -250,7 +268,7 @@ async def create_room(room_data: CreateRoomRequest, request: Request, current_us
db.flush()
# Commit transaction
transaction.commit()
db.commit()
db.refresh(room)
base_url = get_base_url(request)
room_dict = {'id': room.id, 'room_type_id': room.room_type_id, 'room_number': room.room_number, 'floor': room.floor, 'status': room.status.value if isinstance(room.status, RoomStatus) else room.status, 'price': float(room.price) if room.price is not None and room.price > 0 else None, 'featured': room.featured, 'description': room.description, 'capacity': room.capacity, 'room_size': room.room_size, 'view': room.view, 'amenities': room.amenities if room.amenities else [], 'created_at': room.created_at.isoformat() if room.created_at else None, 'updated_at': room.updated_at.isoformat() if room.updated_at else None}
@@ -262,36 +280,31 @@ async def create_room(room_data: CreateRoomRequest, request: Request, current_us
room_dict['room_type'] = {'id': room.room_type.id, 'name': room.room_type.name, 'description': room.room_type.description, 'base_price': float(room.room_type.base_price) if room.room_type.base_price else 0.0, 'capacity': room.room_type.capacity, 'amenities': room.room_type.amenities if room.room_type.amenities else [], 'images': []}
return success_response(data={'room': room_dict}, message='Room created successfully')
except HTTPException:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
raise
except IntegrityError as e:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
logger.error(f'Database integrity error during room creation: {str(e)}')
raise HTTPException(status_code=409, detail='Room conflict detected. Please check room number.')
except Exception as e:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
logger.error(f'Error creating room: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail='An error occurred while creating the room')
@router.put('/{id}', dependencies=[Depends(authorize_roles('admin'))])
async def update_room(id: int, room_data: UpdateRoomRequest, request: Request, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
"""Update a room with validated input using Pydantic schema."""
# Start transaction
transaction = db.begin()
try:
# Lock room row to prevent race conditions
room = db.query(Room).filter(Room.id == id).with_for_update().first()
if not room:
transaction.rollback()
db.rollback()
raise HTTPException(status_code=404, detail='Room not found')
if room_data.room_type_id:
room_type = db.query(RoomType).filter(RoomType.id == room_data.room_type_id).first()
if not room_type:
transaction.rollback()
db.rollback()
raise HTTPException(status_code=404, detail='Room type not found')
room.room_type_id = room_data.room_type_id
@@ -299,7 +312,7 @@ async def update_room(id: int, room_data: UpdateRoomRequest, request: Request, c
# Check for duplicate room number
existing = db.query(Room).filter(Room.room_number == room_data.room_number, Room.id != id).first()
if existing:
transaction.rollback()
db.rollback()
raise HTTPException(status_code=400, detail='Room number already exists')
room.room_number = room_data.room_number
@@ -323,7 +336,7 @@ async def update_room(id: int, room_data: UpdateRoomRequest, request: Request, c
room.amenities = room_data.amenities or []
# Commit transaction
transaction.commit()
db.commit()
db.refresh(room)
base_url = get_base_url(request)
@@ -359,17 +372,14 @@ async def update_room(id: int, room_data: UpdateRoomRequest, request: Request, c
}
return success_response(data={'room': room_dict}, message='Room updated successfully')
except HTTPException:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
raise
except IntegrityError as e:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
logger.error(f'Database integrity error during room update: {str(e)}')
raise HTTPException(status_code=409, detail='Room conflict detected. Please check room number.')
except Exception as e:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
logger.error(f'Error updating room: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail='An error occurred while updating the room')
@@ -391,8 +401,6 @@ async def delete_room(id: int, current_user: User=Depends(authorize_roles('admin
@router.post('/bulk-delete', dependencies=[Depends(authorize_roles('admin'))])
async def bulk_delete_rooms(room_ids: BulkDeleteRoomsRequest, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
"""Bulk delete rooms with validated input using Pydantic schema."""
# Start transaction
transaction = db.begin()
try:
ids = room_ids.room_ids
@@ -401,30 +409,27 @@ async def bulk_delete_rooms(room_ids: BulkDeleteRoomsRequest, current_user: User
found_ids = [room.id for room in rooms]
not_found_ids = [id for id in ids if id not in found_ids]
if not_found_ids:
transaction.rollback()
db.rollback()
raise HTTPException(status_code=404, detail=f'Rooms with IDs {not_found_ids} not found')
# Delete rooms
deleted_count = db.query(Room).filter(Room.id.in_(ids)).delete(synchronize_session=False)
# Commit transaction
transaction.commit()
db.commit()
return success_response(
data={'deleted_count': deleted_count, 'deleted_ids': ids},
message=f'Successfully deleted {deleted_count} room(s)'
)
except HTTPException:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
raise
except IntegrityError as e:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
logger.error(f'Database integrity error during bulk room deletion: {str(e)}')
raise HTTPException(status_code=409, detail='Cannot delete rooms due to existing relationships (bookings, etc.)')
except Exception as e:
if 'transaction' in locals():
transaction.rollback()
db.rollback()
logger.error(f'Error bulk deleting rooms: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail='An error occurred while deleting rooms')