Files
Hotel-Booking/Backend/src/routes/promotion_routes.py
2025-11-16 15:59:05 +02:00

335 lines
13 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from typing import Optional
from datetime import datetime
from ..config.database import get_db
from ..middleware.auth import get_current_user, authorize_roles
from ..models.user import User
from ..models.promotion import Promotion, DiscountType
router = APIRouter(prefix="/promotions", tags=["promotions"])
@router.get("/")
async def get_promotions(
search: Optional[str] = Query(None),
status_filter: Optional[str] = Query(None, alias="status"),
type: Optional[str] = Query(None),
page: int = Query(1, ge=1),
limit: int = Query(10, ge=1, le=100),
db: Session = Depends(get_db)
):
"""Get all promotions with filters"""
try:
query = db.query(Promotion)
# Filter by search (code or name)
if search:
query = query.filter(
or_(
Promotion.code.like(f"%{search}%"),
Promotion.name.like(f"%{search}%")
)
)
# Filter by status (is_active)
if status_filter:
is_active = status_filter == "active"
query = query.filter(Promotion.is_active == is_active)
# Filter by discount type
if type:
try:
query = query.filter(Promotion.discount_type == DiscountType(type))
except ValueError:
pass
total = query.count()
offset = (page - 1) * limit
promotions = query.order_by(Promotion.created_at.desc()).offset(offset).limit(limit).all()
result = []
for promo in promotions:
promo_dict = {
"id": promo.id,
"code": promo.code,
"name": promo.name,
"description": promo.description,
"discount_type": promo.discount_type.value if isinstance(promo.discount_type, DiscountType) else promo.discount_type,
"discount_value": float(promo.discount_value) if promo.discount_value else 0.0,
"min_booking_amount": float(promo.min_booking_amount) if promo.min_booking_amount else None,
"max_discount_amount": float(promo.max_discount_amount) if promo.max_discount_amount else None,
"start_date": promo.start_date.isoformat() if promo.start_date else None,
"end_date": promo.end_date.isoformat() if promo.end_date else None,
"usage_limit": promo.usage_limit,
"used_count": promo.used_count,
"is_active": promo.is_active,
"created_at": promo.created_at.isoformat() if promo.created_at else None,
}
result.append(promo_dict)
return {
"status": "success",
"data": {
"promotions": 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("/{code}")
async def get_promotion_by_code(code: str, db: Session = Depends(get_db)):
"""Get promotion by code"""
try:
promotion = db.query(Promotion).filter(Promotion.code == code).first()
if not promotion:
raise HTTPException(status_code=404, detail="Promotion not found")
promo_dict = {
"id": promotion.id,
"code": promotion.code,
"name": promotion.name,
"description": promotion.description,
"discount_type": promotion.discount_type.value if isinstance(promotion.discount_type, DiscountType) else promotion.discount_type,
"discount_value": float(promotion.discount_value) if promotion.discount_value else 0.0,
"min_booking_amount": float(promotion.min_booking_amount) if promotion.min_booking_amount else None,
"max_discount_amount": float(promotion.max_discount_amount) if promotion.max_discount_amount else None,
"start_date": promotion.start_date.isoformat() if promotion.start_date else None,
"end_date": promotion.end_date.isoformat() if promotion.end_date else None,
"usage_limit": promotion.usage_limit,
"used_count": promotion.used_count,
"is_active": promotion.is_active,
}
return {
"status": "success",
"data": {"promotion": promo_dict}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/validate")
async def validate_promotion(
validation_data: dict,
db: Session = Depends(get_db)
):
"""Validate and apply promotion"""
try:
code = validation_data.get("code")
booking_amount = float(validation_data.get("booking_amount", 0))
promotion = db.query(Promotion).filter(Promotion.code == code).first()
if not promotion:
raise HTTPException(status_code=404, detail="Promotion code not found")
# Check if promotion is active
if not promotion.is_active:
raise HTTPException(status_code=400, detail="Promotion is not active")
# Check date validity
now = datetime.utcnow()
if promotion.start_date and now < promotion.start_date:
raise HTTPException(status_code=400, detail="Promotion is not valid at this time")
if promotion.end_date and now > promotion.end_date:
raise HTTPException(status_code=400, detail="Promotion is not valid at this time")
# Check usage limit
if promotion.usage_limit and promotion.used_count >= promotion.usage_limit:
raise HTTPException(status_code=400, detail="Promotion usage limit reached")
# Check minimum booking amount
if promotion.min_booking_amount and booking_amount < float(promotion.min_booking_amount):
raise HTTPException(
status_code=400,
detail=f"Minimum booking amount is {promotion.min_booking_amount}"
)
# Calculate discount
discount_amount = promotion.calculate_discount(booking_amount)
final_amount = booking_amount - discount_amount
return {
"status": "success",
"data": {
"promotion": {
"id": promotion.id,
"code": promotion.code,
"name": promotion.name,
},
"original_amount": booking_amount,
"discount_amount": discount_amount,
"final_amount": final_amount,
}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/", dependencies=[Depends(authorize_roles("admin"))])
async def create_promotion(
promotion_data: dict,
current_user: User = Depends(authorize_roles("admin")),
db: Session = Depends(get_db)
):
"""Create new promotion (Admin only)"""
try:
code = promotion_data.get("code")
# Check if code exists
existing = db.query(Promotion).filter(Promotion.code == code).first()
if existing:
raise HTTPException(status_code=400, detail="Promotion code already exists")
discount_type = promotion_data.get("discount_type")
discount_value = float(promotion_data.get("discount_value", 0))
# Validate discount value
if discount_type == "percentage" and discount_value > 100:
raise HTTPException(
status_code=400,
detail="Percentage discount cannot exceed 100%"
)
promotion = Promotion(
code=code,
name=promotion_data.get("name"),
description=promotion_data.get("description"),
discount_type=DiscountType(discount_type),
discount_value=discount_value,
min_booking_amount=float(promotion_data["min_booking_amount"]) if promotion_data.get("min_booking_amount") else None,
max_discount_amount=float(promotion_data["max_discount_amount"]) if promotion_data.get("max_discount_amount") else None,
start_date=datetime.fromisoformat(promotion_data["start_date"].replace('Z', '+00:00')) if promotion_data.get("start_date") else None,
end_date=datetime.fromisoformat(promotion_data["end_date"].replace('Z', '+00:00')) if promotion_data.get("end_date") else None,
usage_limit=promotion_data.get("usage_limit"),
used_count=0,
is_active=promotion_data.get("status") == "active" if promotion_data.get("status") else True,
)
db.add(promotion)
db.commit()
db.refresh(promotion)
return {
"status": "success",
"message": "Promotion created successfully",
"data": {"promotion": promotion}
}
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_promotion(
id: int,
promotion_data: dict,
current_user: User = Depends(authorize_roles("admin")),
db: Session = Depends(get_db)
):
"""Update promotion (Admin only)"""
try:
promotion = db.query(Promotion).filter(Promotion.id == id).first()
if not promotion:
raise HTTPException(status_code=404, detail="Promotion not found")
# Check if new code exists (excluding current)
code = promotion_data.get("code")
if code and code != promotion.code:
existing = db.query(Promotion).filter(
Promotion.code == code,
Promotion.id != id
).first()
if existing:
raise HTTPException(status_code=400, detail="Promotion code already exists")
# Validate discount value
discount_type = promotion_data.get("discount_type", promotion.discount_type.value if isinstance(promotion.discount_type, DiscountType) else promotion.discount_type)
discount_value = promotion_data.get("discount_value")
if discount_value is not None:
discount_value = float(discount_value)
if discount_type == "percentage" and discount_value > 100:
raise HTTPException(
status_code=400,
detail="Percentage discount cannot exceed 100%"
)
# Update fields
if "code" in promotion_data:
promotion.code = promotion_data["code"]
if "name" in promotion_data:
promotion.name = promotion_data["name"]
if "description" in promotion_data:
promotion.description = promotion_data["description"]
if "discount_type" in promotion_data:
promotion.discount_type = DiscountType(promotion_data["discount_type"])
if "discount_value" in promotion_data:
promotion.discount_value = discount_value
if "min_booking_amount" in promotion_data:
promotion.min_booking_amount = float(promotion_data["min_booking_amount"]) if promotion_data["min_booking_amount"] else None
if "max_discount_amount" in promotion_data:
promotion.max_discount_amount = float(promotion_data["max_discount_amount"]) if promotion_data["max_discount_amount"] else None
if "start_date" in promotion_data:
promotion.start_date = datetime.fromisoformat(promotion_data["start_date"].replace('Z', '+00:00')) if promotion_data["start_date"] else None
if "end_date" in promotion_data:
promotion.end_date = datetime.fromisoformat(promotion_data["end_date"].replace('Z', '+00:00')) if promotion_data["end_date"] else None
if "usage_limit" in promotion_data:
promotion.usage_limit = promotion_data["usage_limit"]
if "status" in promotion_data:
promotion.is_active = promotion_data["status"] == "active"
db.commit()
db.refresh(promotion)
return {
"status": "success",
"message": "Promotion updated successfully",
"data": {"promotion": promotion}
}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{id}", dependencies=[Depends(authorize_roles("admin"))])
async def delete_promotion(
id: int,
current_user: User = Depends(authorize_roles("admin")),
db: Session = Depends(get_db)
):
"""Delete promotion (Admin only)"""
try:
promotion = db.query(Promotion).filter(Promotion.id == id).first()
if not promotion:
raise HTTPException(status_code=404, detail="Promotion not found")
db.delete(promotion)
db.commit()
return {
"status": "success",
"message": "Promotion deleted successfully"
}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))