from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select, delete from geopy.geocoders import Nominatim from geopy.distance import geodesic from backend.database import get_session from backend.routes.auth import get_current_user from backend.models.models import Order, OrderItem, User, Product, Shop from backend.schemas.order import OrderCreate, OrderRead, OrderUpdate router = APIRouter() @router.post("/", response_model=OrderRead) def create_order( order_data: OrderCreate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user), ): # Fetch the shop details shop = session.get(Shop, order_data.shop_id) if not shop: raise HTTPException(status_code=404, detail="Shop not found") # Geocode the delivery address geolocator = Nominatim(user_agent="order_locator") delivery_location = geolocator.geocode(order_data.delivery_address) if not delivery_location: raise HTTPException(status_code=400, detail="Invalid delivery address provided") # Calculate the distance between the shop and the delivery location shop_location = (shop.latitude, shop.longitude) delivery_coordinates = (delivery_location.latitude, delivery_location.longitude) distance_km = geodesic(shop_location, delivery_coordinates).kilometers # Calculate the shipping price ($1 per km) shipping_price = distance_km * 1.0 total_price = shipping_price for item in order_data.items: product = session.get(Product, item.product_id) if not product or product.stock < item.quantity: raise HTTPException( status_code=400, detail=f"Product {item.product_id} is out of stock" ) total_price += item.quantity * product.price # Create the order new_order = Order( user_id=current_user.id, shop_id=order_data.shop_id, total_price=total_price, shipping_price=shipping_price, status="pending", delivery_address=order_data.delivery_address, delivery_latitude=delivery_location.latitude, delivery_longitude=delivery_location.longitude, ) session.add(new_order) session.commit() session.refresh(new_order) # Create order items for item in order_data.items: order_item = OrderItem( order_id=new_order.id, product_id=item.product_id, quantity=item.quantity, price=product.price, ) session.add(order_item) product.stock -= item.quantity session.commit() return new_order # list all orders associated with the current user @router.get("/list", response_model=list[OrderRead]) def list_orders( session: Session = Depends(get_session), current_user: User = Depends(get_current_user), ): orders = session.exec(select(Order).where(Order.user_id == current_user.id)).all() return orders # retrieve a specific order, only the owner can access @router.get("/{order_id}", response_model=OrderRead) def get_order( order_id: int, session: Session = Depends(get_session), current_user: User = Depends(get_current_user), ): order = session.get(Order, order_id) if not order or order.user_id != current_user.id: raise HTTPException(status_code=404, detail="Order not found") return order # update order status @router.put("/status/{order_id}", response_model=OrderRead) def update_order( order_id: int, order_update: OrderUpdate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user), ): order = session.get(Order, order_id) if not order or order.user_id != current_user.id: raise HTTPException(status_code=404, detail="Order not found or unauthorized") # Update the delivery address if provided if order_update.delivery_address: geolocator = Nominatim(user_agent="order_locator") delivery_location = geolocator.geocode(order_update.delivery_address) if not delivery_location: raise HTTPException(status_code=400, detail="Invalid delivery address") order.delivery_address = order_update.delivery_address order.delivery_latitude = delivery_location.latitude order.delivery_longitude = delivery_location.longitude # Recalculate the shipping price shop = session.get(Shop, order.shop_id) if not shop: raise HTTPException(status_code=404, detail="Shop not found") shop_location = (shop.latitude, shop.longitude) delivery_coordinates = (delivery_location.latitude, delivery_location.longitude) distance_km = geodesic(shop_location, delivery_coordinates).kilometers shipping_price = distance_km * 1.0 # Update the total price product_total = sum( item.quantity * session.get(Product, item.product_id).price for item in order.order_items ) order.shipping_price = shipping_price order.total_price = product_total + shipping_price # Update the order status if provided if order_update.status: if order.status != "pending" or order_update.status != "completed": raise HTTPException( status_code=400, detail="Order can only be updated from pending to completed", ) order.status = order_update.status session.commit() session.refresh(order) return order # Delete order @router.delete("/{order_id}") def delete_order( order_id: int, session: Session = Depends(get_session), current_user: User = Depends(get_current_user), ): order = session.get(Order, order_id) if not order or ( order.user_id != current_user.id and current_user.role != "shop_owner" ): raise HTTPException(status_code=404, detail="Order not found or unauthorized") # Delete related order items first session.exec(delete(OrderItem).where(OrderItem.order_id == order.id)) # Now delete the order session.delete(order) session.commit() return {"message": "Order deleted successfully"}