update to python fastpi
This commit is contained in:
437
Backend/src/routes/booking_routes.py
Normal file
437
Backend/src/routes/booking_routes.py
Normal file
@@ -0,0 +1,437 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import random
|
||||
|
||||
from ..config.database import get_db
|
||||
from ..middleware.auth import get_current_user, authorize_roles
|
||||
from ..models.user import User
|
||||
from ..models.booking import Booking, BookingStatus
|
||||
from ..models.room import Room
|
||||
from ..models.room_type import RoomType
|
||||
from ..models.payment import Payment, PaymentMethod, PaymentType, PaymentStatus
|
||||
|
||||
router = APIRouter(prefix="/bookings", tags=["bookings"])
|
||||
|
||||
|
||||
def generate_booking_number() -> str:
|
||||
"""Generate unique booking number"""
|
||||
prefix = "BK"
|
||||
ts = int(datetime.utcnow().timestamp() * 1000)
|
||||
rand = random.randint(1000, 9999)
|
||||
return f"{prefix}-{ts}-{rand}"
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def get_all_bookings(
|
||||
search: Optional[str] = Query(None),
|
||||
status_filter: Optional[str] = Query(None, alias="status"),
|
||||
startDate: Optional[str] = Query(None),
|
||||
endDate: Optional[str] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
limit: int = Query(10, ge=1, le=100),
|
||||
current_user: User = Depends(authorize_roles("admin", "staff")),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all bookings (Admin/Staff only)"""
|
||||
try:
|
||||
query = db.query(Booking)
|
||||
|
||||
# Filter by search (booking_number)
|
||||
if search:
|
||||
query = query.filter(Booking.booking_number.like(f"%{search}%"))
|
||||
|
||||
# Filter by status
|
||||
if status_filter:
|
||||
try:
|
||||
query = query.filter(Booking.status == BookingStatus(status_filter))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Filter by date range
|
||||
if startDate:
|
||||
start = datetime.fromisoformat(startDate.replace('Z', '+00:00'))
|
||||
query = query.filter(Booking.check_in_date >= start)
|
||||
|
||||
if endDate:
|
||||
end = datetime.fromisoformat(endDate.replace('Z', '+00:00'))
|
||||
query = query.filter(Booking.check_in_date <= end)
|
||||
|
||||
# Get total count
|
||||
total = query.count()
|
||||
|
||||
# Apply pagination
|
||||
offset = (page - 1) * limit
|
||||
bookings = query.order_by(Booking.created_at.desc()).offset(offset).limit(limit).all()
|
||||
|
||||
# Include related data
|
||||
result = []
|
||||
for booking in bookings:
|
||||
booking_dict = {
|
||||
"id": booking.id,
|
||||
"booking_number": booking.booking_number,
|
||||
"user_id": booking.user_id,
|
||||
"room_id": booking.room_id,
|
||||
"check_in_date": booking.check_in_date.isoformat() if booking.check_in_date else None,
|
||||
"check_out_date": booking.check_out_date.isoformat() if booking.check_out_date else None,
|
||||
"num_guests": booking.num_guests,
|
||||
"total_price": float(booking.total_price) if booking.total_price else 0.0,
|
||||
"status": booking.status.value if isinstance(booking.status, BookingStatus) else booking.status,
|
||||
"deposit_paid": booking.deposit_paid,
|
||||
"requires_deposit": booking.requires_deposit,
|
||||
"special_requests": booking.special_requests,
|
||||
"created_at": booking.created_at.isoformat() if booking.created_at else None,
|
||||
}
|
||||
|
||||
# Add user info
|
||||
if booking.user:
|
||||
booking_dict["user"] = {
|
||||
"id": booking.user.id,
|
||||
"full_name": booking.user.full_name,
|
||||
"email": booking.user.email,
|
||||
"phone": booking.user.phone,
|
||||
}
|
||||
|
||||
# Add room info
|
||||
if booking.room:
|
||||
booking_dict["room"] = {
|
||||
"id": booking.room.id,
|
||||
"room_number": booking.room.room_number,
|
||||
"floor": booking.room.floor,
|
||||
}
|
||||
|
||||
result.append(booking_dict)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"data": {
|
||||
"bookings": result,
|
||||
"pagination": {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"totalPages": (total + limit - 1) // limit,
|
||||
},
|
||||
},
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
async def get_my_bookings(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get current user's bookings"""
|
||||
try:
|
||||
bookings = db.query(Booking).filter(
|
||||
Booking.user_id == current_user.id
|
||||
).order_by(Booking.created_at.desc()).all()
|
||||
|
||||
result = []
|
||||
for booking in bookings:
|
||||
booking_dict = {
|
||||
"id": booking.id,
|
||||
"booking_number": booking.booking_number,
|
||||
"room_id": booking.room_id,
|
||||
"check_in_date": booking.check_in_date.isoformat() if booking.check_in_date else None,
|
||||
"check_out_date": booking.check_out_date.isoformat() if booking.check_out_date else None,
|
||||
"num_guests": booking.num_guests,
|
||||
"total_price": float(booking.total_price) if booking.total_price else 0.0,
|
||||
"status": booking.status.value if isinstance(booking.status, BookingStatus) else booking.status,
|
||||
"deposit_paid": booking.deposit_paid,
|
||||
"requires_deposit": booking.requires_deposit,
|
||||
"special_requests": booking.special_requests,
|
||||
"created_at": booking.created_at.isoformat() if booking.created_at else None,
|
||||
}
|
||||
|
||||
# Add room info
|
||||
if booking.room and booking.room.room_type:
|
||||
booking_dict["room"] = {
|
||||
"id": booking.room.id,
|
||||
"room_number": booking.room.room_number,
|
||||
"room_type": {
|
||||
"name": booking.room.room_type.name,
|
||||
}
|
||||
}
|
||||
|
||||
result.append(booking_dict)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"bookings": result}
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/")
|
||||
async def create_booking(
|
||||
booking_data: dict,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create new booking"""
|
||||
try:
|
||||
room_id = booking_data.get("room_id")
|
||||
check_in_date = booking_data.get("check_in_date")
|
||||
check_out_date = booking_data.get("check_out_date")
|
||||
total_price = booking_data.get("total_price")
|
||||
guest_count = booking_data.get("guest_count", 1)
|
||||
notes = booking_data.get("notes")
|
||||
payment_method = booking_data.get("payment_method", "cash")
|
||||
|
||||
if not all([room_id, check_in_date, check_out_date, total_price]):
|
||||
raise HTTPException(status_code=400, detail="Missing required booking fields")
|
||||
|
||||
# Check if room exists
|
||||
room = db.query(Room).filter(Room.id == room_id).first()
|
||||
if not room:
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
|
||||
check_in = datetime.fromisoformat(check_in_date.replace('Z', '+00:00'))
|
||||
check_out = datetime.fromisoformat(check_out_date.replace('Z', '+00:00'))
|
||||
|
||||
# Check for overlapping bookings
|
||||
overlapping = db.query(Booking).filter(
|
||||
and_(
|
||||
Booking.room_id == room_id,
|
||||
Booking.status != BookingStatus.cancelled,
|
||||
Booking.check_in_date < check_out,
|
||||
Booking.check_out_date > check_in
|
||||
)
|
||||
).first()
|
||||
|
||||
if overlapping:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Room already booked for the selected dates"
|
||||
)
|
||||
|
||||
booking_number = generate_booking_number()
|
||||
|
||||
# Determine if deposit is required
|
||||
requires_deposit = payment_method == "cash"
|
||||
deposit_percentage = 20 if requires_deposit else 0
|
||||
deposit_amount = (float(total_price) * deposit_percentage) / 100 if requires_deposit else 0
|
||||
|
||||
# Create booking
|
||||
booking = Booking(
|
||||
booking_number=booking_number,
|
||||
user_id=current_user.id,
|
||||
room_id=room_id,
|
||||
check_in_date=check_in,
|
||||
check_out_date=check_out,
|
||||
num_guests=guest_count,
|
||||
total_price=total_price,
|
||||
special_requests=notes,
|
||||
status=BookingStatus.pending,
|
||||
requires_deposit=requires_deposit,
|
||||
deposit_paid=False,
|
||||
)
|
||||
|
||||
db.add(booking)
|
||||
db.flush()
|
||||
|
||||
# Create deposit payment if required
|
||||
if requires_deposit:
|
||||
payment = Payment(
|
||||
booking_id=booking.id,
|
||||
amount=deposit_amount,
|
||||
payment_method=PaymentMethod.bank_transfer,
|
||||
payment_type=PaymentType.deposit,
|
||||
deposit_percentage=deposit_percentage,
|
||||
payment_status=PaymentStatus.pending,
|
||||
notes=f"Deposit payment ({deposit_percentage}%) for booking {booking_number}",
|
||||
)
|
||||
db.add(payment)
|
||||
|
||||
db.commit()
|
||||
db.refresh(booking)
|
||||
|
||||
# Fetch with relations
|
||||
booking = db.query(Booking).filter(Booking.id == booking.id).first()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"booking": booking},
|
||||
"message": f"Booking created. Please pay {deposit_percentage}% deposit to confirm." if requires_deposit else "Booking created successfully"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
async def get_booking_by_id(
|
||||
id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get booking by ID"""
|
||||
try:
|
||||
booking = db.query(Booking).filter(Booking.id == id).first()
|
||||
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail="Booking not found")
|
||||
|
||||
# Check access
|
||||
if current_user.role_id != 1 and booking.user_id != current_user.id: # Not admin
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
|
||||
booking_dict = {
|
||||
"id": booking.id,
|
||||
"booking_number": booking.booking_number,
|
||||
"user_id": booking.user_id,
|
||||
"room_id": booking.room_id,
|
||||
"check_in_date": booking.check_in_date.isoformat() if booking.check_in_date else None,
|
||||
"check_out_date": booking.check_out_date.isoformat() if booking.check_out_date else None,
|
||||
"num_guests": booking.num_guests,
|
||||
"total_price": float(booking.total_price) if booking.total_price else 0.0,
|
||||
"status": booking.status.value if isinstance(booking.status, BookingStatus) else booking.status,
|
||||
"deposit_paid": booking.deposit_paid,
|
||||
"requires_deposit": booking.requires_deposit,
|
||||
"special_requests": booking.special_requests,
|
||||
"created_at": booking.created_at.isoformat() if booking.created_at else None,
|
||||
}
|
||||
|
||||
# Add relations
|
||||
if booking.room:
|
||||
booking_dict["room"] = {
|
||||
"id": booking.room.id,
|
||||
"room_number": booking.room.room_number,
|
||||
}
|
||||
if booking.room.room_type:
|
||||
booking_dict["room"]["room_type"] = {
|
||||
"name": booking.room.room_type.name,
|
||||
}
|
||||
|
||||
if booking.payments:
|
||||
booking_dict["payments"] = [
|
||||
{
|
||||
"id": p.id,
|
||||
"amount": float(p.amount) if p.amount else 0.0,
|
||||
"payment_method": p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method,
|
||||
"payment_status": p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status,
|
||||
}
|
||||
for p in booking.payments
|
||||
]
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"booking": booking_dict}
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.patch("/{id}/cancel")
|
||||
async def cancel_booking(
|
||||
id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Cancel a booking"""
|
||||
try:
|
||||
booking = db.query(Booking).filter(Booking.id == 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="Forbidden")
|
||||
|
||||
if booking.status == BookingStatus.cancelled:
|
||||
raise HTTPException(status_code=400, detail="Booking already cancelled")
|
||||
|
||||
booking.status = BookingStatus.cancelled
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"booking": booking}
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.put("/{id}", dependencies=[Depends(authorize_roles("admin"))])
|
||||
async def update_booking(
|
||||
id: int,
|
||||
booking_data: dict,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update booking status (Admin only)"""
|
||||
try:
|
||||
booking = db.query(Booking).filter(Booking.id == id).first()
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail="Booking not found")
|
||||
|
||||
status_value = booking_data.get("status")
|
||||
if status_value:
|
||||
try:
|
||||
booking.status = BookingStatus(status_value)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid status")
|
||||
|
||||
db.commit()
|
||||
db.refresh(booking)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Booking updated successfully",
|
||||
"data": {"booking": booking}
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/check/{booking_number}")
|
||||
async def check_booking_by_number(
|
||||
booking_number: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Check booking by booking number"""
|
||||
try:
|
||||
booking = db.query(Booking).filter(Booking.booking_number == booking_number).first()
|
||||
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail="Booking not found")
|
||||
|
||||
booking_dict = {
|
||||
"id": booking.id,
|
||||
"booking_number": booking.booking_number,
|
||||
"room_id": booking.room_id,
|
||||
"check_in_date": booking.check_in_date.isoformat() if booking.check_in_date else None,
|
||||
"check_out_date": booking.check_out_date.isoformat() if booking.check_out_date else None,
|
||||
"status": booking.status.value if isinstance(booking.status, BookingStatus) else booking.status,
|
||||
}
|
||||
|
||||
if booking.room:
|
||||
booking_dict["room"] = {
|
||||
"id": booking.room.id,
|
||||
"room_number": booking.room.room_number,
|
||||
}
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"data": {"booking": booking_dict}
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user