from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List, Optional from app.db.session import get_db from app.models import Order, OrderItem, Book, Inventory, OrderStatus from app.schemas import OrderCreate, OrderResponse, PaymentIntentCreate from app.services.stripe_service import StripeService router = APIRouter(prefix="/orders", tags=["orders"]) @router.post("/", response_model=OrderResponse) def create_order(order: OrderCreate, db: Session = Depends(get_db)): total_amount = 0 order_items_data = [] for item in order.items: book = db.query(Book).filter(Book.id == item.book_id, Book.is_active).first() if not book: raise HTTPException(status_code=404, detail=f"Book with ID {item.book_id} not found") inventory = db.query(Inventory).filter(Inventory.book_id == item.book_id).first() if not inventory: raise HTTPException(status_code=400, detail=f"No inventory found for book ID {item.book_id}") available = inventory.quantity - inventory.reserved_quantity if item.quantity > available: raise HTTPException(status_code=400, detail=f"Not enough stock for book '{book.title}'. Available: {available}") item_total = book.price * item.quantity total_amount += item_total order_items_data.append({ "book_id": item.book_id, "quantity": item.quantity, "price": book.price }) db_order = Order( customer_email=order.customer_email, customer_name=order.customer_name, customer_address=order.customer_address, total_amount=total_amount ) db.add(db_order) db.commit() db.refresh(db_order) for item_data in order_items_data: order_item = OrderItem(order_id=db_order.id, **item_data) db.add(order_item) inventory = db.query(Inventory).filter(Inventory.book_id == item_data["book_id"]).first() inventory.reserved_quantity += item_data["quantity"] db.commit() db.refresh(db_order) return db_order @router.get("/", response_model=List[OrderResponse]) def get_orders( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), status: Optional[OrderStatus] = None, customer_email: Optional[str] = None, db: Session = Depends(get_db) ): query = db.query(Order) if status: query = query.filter(Order.status == status) if customer_email: query = query.filter(Order.customer_email.ilike(f"%{customer_email}%")) return query.offset(skip).limit(limit).all() @router.get("/{order_id}", response_model=OrderResponse) def get_order(order_id: int, db: Session = Depends(get_db)): order = db.query(Order).filter(Order.id == order_id).first() if not order: raise HTTPException(status_code=404, detail="Order not found") return order @router.put("/{order_id}/status") def update_order_status(order_id: int, status: OrderStatus, db: Session = Depends(get_db)): order = db.query(Order).filter(Order.id == order_id).first() if not order: raise HTTPException(status_code=404, detail="Order not found") if order.status == OrderStatus.CANCELLED: raise HTTPException(status_code=400, detail="Cannot update status of cancelled order") if status == OrderStatus.CANCELLED: for item in order.items: inventory = db.query(Inventory).filter(Inventory.book_id == item.book_id).first() if inventory: inventory.reserved_quantity -= item.quantity if status == OrderStatus.DELIVERED and order.status != OrderStatus.SHIPPED: raise HTTPException(status_code=400, detail="Order must be shipped before it can be delivered") if status == OrderStatus.CONFIRMED and order.status == OrderStatus.PENDING: for item in order.items: inventory = db.query(Inventory).filter(Inventory.book_id == item.book_id).first() if inventory: inventory.quantity -= item.quantity inventory.reserved_quantity -= item.quantity order.status = status db.commit() return {"message": f"Order status updated to {status.value}"} @router.post("/payment-intent", response_model=dict) def create_payment_intent(payment_data: PaymentIntentCreate, db: Session = Depends(get_db)): order = db.query(Order).filter(Order.id == payment_data.order_id).first() if not order: raise HTTPException(status_code=404, detail="Order not found") if order.stripe_payment_intent_id: raise HTTPException(status_code=400, detail="Payment intent already exists for this order") stripe_service = StripeService() payment_intent = stripe_service.create_payment_intent( amount=order.total_amount, metadata={"order_id": str(order.id)} ) order.stripe_payment_intent_id = payment_intent["payment_intent_id"] db.commit() return payment_intent @router.post("/{order_id}/confirm-payment") def confirm_payment(order_id: int, db: Session = Depends(get_db)): order = db.query(Order).filter(Order.id == order_id).first() if not order: raise HTTPException(status_code=404, detail="Order not found") if not order.stripe_payment_intent_id: raise HTTPException(status_code=400, detail="No payment intent found for this order") stripe_service = StripeService() payment_status = stripe_service.confirm_payment_intent(order.stripe_payment_intent_id) if payment_status["status"] == "succeeded": order.status = OrderStatus.CONFIRMED db.commit() return {"message": "Payment confirmed and order updated"} else: return {"message": "Payment not yet completed", "status": payment_status["status"]} @router.delete("/{order_id}") def cancel_order(order_id: int, db: Session = Depends(get_db)): order = db.query(Order).filter(Order.id == order_id).first() if not order: raise HTTPException(status_code=404, detail="Order not found") if order.status in [OrderStatus.SHIPPED, OrderStatus.DELIVERED]: raise HTTPException(status_code=400, detail="Cannot cancel shipped or delivered order") for item in order.items: inventory = db.query(Inventory).filter(Inventory.book_id == item.book_id).first() if inventory: inventory.reserved_quantity -= item.quantity if order.stripe_payment_intent_id: stripe_service = StripeService() try: stripe_service.cancel_payment_intent(order.stripe_payment_intent_id) except HTTPException: pass order.status = OrderStatus.CANCELLED db.commit() return {"message": "Order cancelled successfully"}