335 lines
13 KiB
Python
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))
|