
- Added comprehensive book management with CRUD operations - Implemented inventory tracking with stock management and reservations - Created order management system with status tracking - Integrated Stripe payment processing with payment intents - Added SQLite database with SQLAlchemy ORM and Alembic migrations - Implemented health check and API documentation endpoints - Added comprehensive error handling and validation - Configured CORS middleware for frontend integration
177 lines
6.8 KiB
Python
177 lines
6.8 KiB
Python
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"} |