from typing import Any, List, Optional from decimal import Decimal from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app.api.deps import get_db, get_current_active_user, get_current_admin_user from app.crud.crud_sale import sale from app.crud.crud_product import product from app.crud.crud_inventory import inventory from app.models.user import User as UserModel from app.schemas.sale import Sale, SaleCreate, SaleUpdate router = APIRouter() @router.get("/", response_model=List[Sale]) def read_sales( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, status: Optional[str] = None, current_user: UserModel = Depends(get_current_active_user), ) -> Any: """ Retrieve sales with their items. Filter by status if provided. """ sales = sale.get_multi_with_items(db, skip=skip, limit=limit) # Apply status filter if status: sales = [s for s in sales if s.status == status] # Calculate total amount for each sale for s in sales: s.total_amount = sale.get_total_amount(db, id=s.id) return sales @router.post("/", response_model=Sale) def create_sale( *, db: Session = Depends(get_db), sale_in: SaleCreate, current_user: UserModel = Depends(get_current_active_user), ) -> Any: """ Create new sale with items and update inventory. """ # Verify all products exist and have enough inventory for item in sale_in.items: # Check if product exists prod = product.get(db, id=item.product_id) if not prod: raise HTTPException( status_code=404, detail=f"Product with id {item.product_id} not found" ) # Check if enough inventory is available available = inventory.get_total_product_quantity(db, product_id=item.product_id) if available < item.quantity: raise HTTPException( status_code=400, detail=f"Not enough inventory for product {prod.name}. Available: {available}, Requested: {item.quantity}" ) # Create sale with items new_sale = sale.create_with_items( db, obj_in=sale_in, user_id=current_user.id ) if not new_sale: raise HTTPException( status_code=400, detail="Failed to create sale, likely due to insufficient inventory" ) # Calculate total amount new_sale.total_amount = sale.get_total_amount(db, id=new_sale.id) return new_sale @router.get("/{id}", response_model=Sale) def read_sale( *, db: Session = Depends(get_db), id: int, current_user: UserModel = Depends(get_current_active_user), ) -> Any: """ Get sale by ID with its items. """ sale_item = sale.get_with_items(db, id=id) if not sale_item: raise HTTPException(status_code=404, detail="Sale not found") # Calculate total amount sale_item.total_amount = sale.get_total_amount(db, id=sale_item.id) return sale_item @router.put("/{id}", response_model=Sale) def update_sale( *, db: Session = Depends(get_db), id: int, sale_in: SaleUpdate, current_user: UserModel = Depends(get_current_active_user), ) -> Any: """ Update a sale (but not its items). Can only update completed sales that haven't been cancelled or returned. """ sale_item = sale.get(db, id=id) if not sale_item: raise HTTPException(status_code=404, detail="Sale not found") # Only allow updates to completed sales if sale_item.status != "completed": raise HTTPException( status_code=400, detail=f"Cannot update sale with status {sale_item.status}. Only completed sales can be updated." ) updated_sale = sale.update(db, db_obj=sale_item, obj_in=sale_in) # Get full sale with items for response result = sale.get_with_items(db, id=updated_sale.id) # Calculate total amount result.total_amount = sale.get_total_amount(db, id=result.id) return result @router.post("/{id}/cancel", response_model=Sale) def cancel_sale( *, db: Session = Depends(get_db), id: int, current_user: UserModel = Depends(get_current_active_user), ) -> Any: """ Cancel a sale and return items to inventory. """ sale_item = sale.get(db, id=id) if not sale_item: raise HTTPException(status_code=404, detail="Sale not found") # Only allow cancelling completed sales if sale_item.status != "completed": raise HTTPException( status_code=400, detail=f"Cannot cancel sale with status {sale_item.status}. Only completed sales can be cancelled." ) # Update status and return to inventory cancelled_sale = sale.cancel_sale(db, id=id) # Calculate total amount cancelled_sale.total_amount = sale.get_total_amount(db, id=cancelled_sale.id) return cancelled_sale