from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app import crud, schemas from app.db.session import get_db from app.models.inventory_movement import MovementType router = APIRouter() @router.get("/movements/", response_model=List[schemas.InventoryMovementWithProduct]) def read_inventory_movements( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, product_id: Optional[str] = None, movement_type: Optional[schemas.MovementTypeEnum] = None, ) -> Any: """ Retrieve inventory movements with optional filtering. """ if movement_type: db_movement_type = MovementType(movement_type) else: db_movement_type = None movements = crud.inventory.get_multi( db, skip=skip, limit=limit, product_id=product_id, movement_type=db_movement_type ) return movements @router.post( "/movements/", response_model=schemas.InventoryMovement, status_code=status.HTTP_201_CREATED ) def create_inventory_movement( *, db: Session = Depends(get_db), movement_in: schemas.InventoryMovementCreate, ) -> Any: """ Create a new inventory movement (stock in, stock out, adjustment, return). """ # Check if product exists product = crud.product.get(db, product_id=movement_in.product_id) if not product: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Product not found", ) # For stock out, check if there is enough stock if ( movement_in.type == schemas.MovementTypeEnum.STOCK_OUT and product.current_stock < movement_in.quantity ): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Insufficient stock for product {product.name}. Available: {product.current_stock}, Requested: {movement_in.quantity}", ) try: movement = crud.inventory.create(db, obj_in=movement_in) return movement except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) from e @router.get("/movements/{movement_id}", response_model=schemas.InventoryMovementWithProduct) def read_inventory_movement( *, db: Session = Depends(get_db), movement_id: str, ) -> Any: """ Get a specific inventory movement by ID. """ movement = crud.inventory.get(db, movement_id=movement_id) if not movement: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Inventory movement not found", ) return movement @router.delete( "/movements/{movement_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None ) def delete_inventory_movement( *, db: Session = Depends(get_db), movement_id: str, ) -> Any: """ Delete an inventory movement and revert its effect on stock. Warning: This will alter inventory history and may cause inconsistencies. Consider using adjustments instead for corrections. """ movement = crud.inventory.get(db, movement_id=movement_id) if not movement: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Inventory movement not found", ) try: crud.inventory.remove(db, movement_id=movement_id) return None except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) from e