Files
Hotel-Booking/Backend/src/routes/service_booking_routes.py
Iliyan Angelov 4488e3a795 updates
2025-11-21 08:55:55 +02:00

408 lines
14 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session, joinedload
from typing import Optional
from datetime import datetime
import random
from ..config.database import get_db
from ..middleware.auth import get_current_user
from ..models.user import User
from ..models.service import Service
from ..models.service_booking import (
ServiceBooking,
ServiceBookingItem,
ServicePayment,
ServiceBookingStatus,
ServicePaymentStatus,
ServicePaymentMethod
)
from ..services.stripe_service import StripeService, get_stripe_secret_key, get_stripe_publishable_key
from ..config.settings import settings
router = APIRouter(prefix="/service-bookings", tags=["service-bookings"])
def generate_service_booking_number() -> str:
prefix = "SB"
timestamp = datetime.utcnow().strftime("%Y%m%d")
random_suffix = random.randint(1000, 9999)
return f"{prefix}{timestamp}{random_suffix}"
@router.post("/")
async def create_service_booking(
booking_data: dict,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
services = booking_data.get("services", [])
total_amount = float(booking_data.get("total_amount", 0))
notes = booking_data.get("notes")
if not services or len(services) == 0:
raise HTTPException(status_code=400, detail="At least one service is required")
if total_amount <= 0:
raise HTTPException(status_code=400, detail="Total amount must be greater than 0")
calculated_total = 0
service_items_data = []
for service_item in services:
service_id = service_item.get("service_id")
quantity = service_item.get("quantity", 1)
if not service_id:
raise HTTPException(status_code=400, detail="Service ID is required for each item")
service = db.query(Service).filter(Service.id == service_id).first()
if not service:
raise HTTPException(status_code=404, detail=f"Service with ID {service_id} not found")
if not service.is_active:
raise HTTPException(status_code=400, detail=f"Service {service.name} is not active")
unit_price = float(service.price)
item_total = unit_price * quantity
calculated_total += item_total
service_items_data.append({
"service": service,
"quantity": quantity,
"unit_price": unit_price,
"total_price": item_total
})
if abs(calculated_total - total_amount) > 0.01:
raise HTTPException(
status_code=400,
detail=f"Total amount mismatch. Calculated: {calculated_total}, Provided: {total_amount}"
)
booking_number = generate_service_booking_number()
service_booking = ServiceBooking(
booking_number=booking_number,
user_id=current_user.id,
total_amount=total_amount,
status=ServiceBookingStatus.pending,
notes=notes
)
db.add(service_booking)
db.flush()
for item_data in service_items_data:
booking_item = ServiceBookingItem(
service_booking_id=service_booking.id,
service_id=item_data["service"].id,
quantity=item_data["quantity"],
unit_price=item_data["unit_price"],
total_price=item_data["total_price"]
)
db.add(booking_item)
db.commit()
db.refresh(service_booking)
service_booking = db.query(ServiceBooking).options(
joinedload(ServiceBooking.service_items).joinedload(ServiceBookingItem.service)
).filter(ServiceBooking.id == service_booking.id).first()
booking_dict = {
"id": service_booking.id,
"booking_number": service_booking.booking_number,
"user_id": service_booking.user_id,
"total_amount": float(service_booking.total_amount),
"status": service_booking.status.value,
"notes": service_booking.notes,
"created_at": service_booking.created_at.isoformat() if service_booking.created_at else None,
"service_items": [
{
"id": item.id,
"service_id": item.service_id,
"quantity": item.quantity,
"unit_price": float(item.unit_price),
"total_price": float(item.total_price),
"service": {
"id": item.service.id,
"name": item.service.name,
"description": item.service.description,
"price": float(item.service.price),
}
}
for item in service_booking.service_items
]
}
return {
"status": "success",
"message": "Service booking created successfully",
"data": {"service_booking": booking_dict}
}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
@router.get("/me")
async def get_my_service_bookings(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
bookings = db.query(ServiceBooking).options(
joinedload(ServiceBooking.service_items).joinedload(ServiceBookingItem.service)
).filter(ServiceBooking.user_id == current_user.id).order_by(ServiceBooking.created_at.desc()).all()
result = []
for booking in bookings:
booking_dict = {
"id": booking.id,
"booking_number": booking.booking_number,
"total_amount": float(booking.total_amount),
"status": booking.status.value,
"notes": booking.notes,
"created_at": booking.created_at.isoformat() if booking.created_at else None,
"service_items": [
{
"id": item.id,
"service_id": item.service_id,
"quantity": item.quantity,
"unit_price": float(item.unit_price),
"total_price": float(item.total_price),
"service": {
"id": item.service.id,
"name": item.service.name,
"description": item.service.description,
"price": float(item.service.price),
}
}
for item in booking.service_items
]
}
result.append(booking_dict)
return {
"status": "success",
"data": {"service_bookings": result}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{id}")
async def get_service_booking_by_id(
id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
booking = db.query(ServiceBooking).options(
joinedload(ServiceBooking.service_items).joinedload(ServiceBookingItem.service)
).filter(ServiceBooking.id == id).first()
if not booking:
raise HTTPException(status_code=404, detail="Service booking not found")
if booking.user_id != current_user.id and current_user.role_id != 1:
raise HTTPException(status_code=403, detail="Forbidden")
booking_dict = {
"id": booking.id,
"booking_number": booking.booking_number,
"user_id": booking.user_id,
"total_amount": float(booking.total_amount),
"status": booking.status.value,
"notes": booking.notes,
"created_at": booking.created_at.isoformat() if booking.created_at else None,
"service_items": [
{
"id": item.id,
"service_id": item.service_id,
"quantity": item.quantity,
"unit_price": float(item.unit_price),
"total_price": float(item.total_price),
"service": {
"id": item.service.id,
"name": item.service.name,
"description": item.service.description,
"price": float(item.service.price),
}
}
for item in booking.service_items
]
}
return {
"status": "success",
"data": {"service_booking": booking_dict}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{id}/payment/stripe/create-intent")
async def create_service_stripe_payment_intent(
id: int,
intent_data: dict,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
secret_key = get_stripe_secret_key(db)
if not secret_key:
secret_key = settings.STRIPE_SECRET_KEY
if not secret_key:
raise HTTPException(
status_code=500,
detail="Stripe is not configured. Please configure Stripe settings in Admin Panel."
)
amount = float(intent_data.get("amount", 0))
currency = intent_data.get("currency", "usd")
if amount <= 0:
raise HTTPException(status_code=400, detail="Amount must be greater than 0")
booking = db.query(ServiceBooking).filter(ServiceBooking.id == id).first()
if not booking:
raise HTTPException(status_code=404, detail="Service booking not found")
if booking.user_id != current_user.id and current_user.role_id != 1:
raise HTTPException(status_code=403, detail="Forbidden")
if abs(float(booking.total_amount) - amount) > 0.01:
raise HTTPException(
status_code=400,
detail=f"Amount mismatch. Booking total: {booking.total_amount}, Provided: {amount}"
)
intent = StripeService.create_payment_intent(
amount=amount,
currency=currency,
description=f"Service Booking #{booking.id}",
db=db
)
publishable_key = get_stripe_publishable_key(db)
if not publishable_key:
publishable_key = settings.STRIPE_PUBLISHABLE_KEY
if not publishable_key:
raise HTTPException(
status_code=500,
detail="Stripe publishable key is not configured."
)
return {
"status": "success",
"data": {
"client_secret": intent["client_secret"],
"payment_intent_id": intent["id"],
"publishable_key": publishable_key
}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{id}/payment/stripe/confirm")
async def confirm_service_stripe_payment(
id: int,
payment_data: dict,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
try:
payment_intent_id = payment_data.get("payment_intent_id")
if not payment_intent_id:
raise HTTPException(status_code=400, detail="payment_intent_id is required")
booking = db.query(ServiceBooking).filter(ServiceBooking.id == id).first()
if not booking:
raise HTTPException(status_code=404, detail="Service booking not found")
if booking.user_id != current_user.id and current_user.role_id != 1:
raise HTTPException(status_code=403, detail="Forbidden")
intent_data = StripeService.retrieve_payment_intent(payment_intent_id, db)
if intent_data["status"] != "succeeded":
raise HTTPException(
status_code=400,
detail=f"Payment intent status is {intent_data['status']}, expected 'succeeded'"
)
amount_paid = intent_data["amount"] / 100
if abs(float(booking.total_amount) - amount_paid) > 0.01:
raise HTTPException(
status_code=400,
detail="Payment amount does not match booking total"
)
payment = ServicePayment(
service_booking_id=booking.id,
amount=booking.total_amount,
payment_method=ServicePaymentMethod.stripe,
payment_status=ServicePaymentStatus.completed,
transaction_id=payment_intent_id,
payment_date=datetime.utcnow(),
notes=f"Stripe payment - Intent: {payment_intent_id}"
)
db.add(payment)
booking.status = ServiceBookingStatus.confirmed
db.commit()
db.refresh(payment)
db.refresh(booking)
return {
"status": "success",
"message": "Payment confirmed successfully",
"data": {
"payment": {
"id": payment.id,
"amount": float(payment.amount),
"payment_method": payment.payment_method.value,
"payment_status": payment.payment_status.value,
"transaction_id": payment.transaction_id,
},
"service_booking": {
"id": booking.id,
"booking_number": booking.booking_number,
"status": booking.status.value,
}
}
}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))