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))