
- Set up project structure with FastAPI - Implement SQLAlchemy models for inventory items and categories - Create database connection with SQLite - Add CRUD operations for inventory management - Implement API endpoints for categories, items, and inventory transactions - Add health check endpoint - Set up Alembic for database migrations - Update README with documentation
227 lines
6.4 KiB
Python
227 lines
6.4 KiB
Python
from typing import Any, List, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app import crud
|
|
from app.db.session import get_db
|
|
from app.schemas.item import (
|
|
Item, ItemCreate, ItemUpdate,
|
|
InventoryTransaction, InventoryTransactionCreate
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=List[Item])
|
|
def read_items(
|
|
db: Session = Depends(get_db),
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
category_id: Optional[int] = None,
|
|
search: Optional[str] = None,
|
|
low_stock: Optional[bool] = False,
|
|
low_stock_threshold: Optional[int] = 10,
|
|
) -> Any:
|
|
"""
|
|
Retrieve items with filtering options.
|
|
"""
|
|
if category_id:
|
|
# Filter by category
|
|
items = crud.item.get_by_category(db, category_id=category_id, skip=skip, limit=limit)
|
|
elif search:
|
|
# Search by name or description
|
|
items = crud.item.search_items(db, query=search, skip=skip, limit=limit)
|
|
elif low_stock:
|
|
# Get low stock items
|
|
items = crud.item.get_low_stock_items(db, threshold=low_stock_threshold, skip=skip, limit=limit)
|
|
else:
|
|
# Get all items
|
|
items = crud.item.get_multi(db, skip=skip, limit=limit)
|
|
return items
|
|
|
|
|
|
@router.post("/", response_model=Item)
|
|
def create_item(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
item_in: ItemCreate,
|
|
) -> Any:
|
|
"""
|
|
Create new item.
|
|
"""
|
|
# Check if SKU exists if provided
|
|
if item_in.sku:
|
|
existing_item = crud.item.get_by_sku(db, sku=item_in.sku)
|
|
if existing_item:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Item with this SKU already exists",
|
|
)
|
|
|
|
# Check if category exists if provided
|
|
if item_in.category_id:
|
|
category = crud.category.get(db, id=item_in.category_id)
|
|
if not category:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Category not found",
|
|
)
|
|
|
|
# Create item with transaction if initial quantity > 0
|
|
item = crud.item.create_with_transaction(db, obj_in=item_in)
|
|
return item
|
|
|
|
|
|
@router.get("/{item_id}", response_model=Item)
|
|
def read_item(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
item_id: int,
|
|
) -> Any:
|
|
"""
|
|
Get item by ID.
|
|
"""
|
|
item = crud.item.get(db, id=item_id)
|
|
if not item:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Item not found",
|
|
)
|
|
return item
|
|
|
|
|
|
@router.put("/{item_id}", response_model=Item)
|
|
def update_item(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
item_id: int,
|
|
item_in: ItemUpdate,
|
|
) -> Any:
|
|
"""
|
|
Update an item.
|
|
"""
|
|
item = crud.item.get(db, id=item_id)
|
|
if not item:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Item not found",
|
|
)
|
|
|
|
# Check if SKU is being updated and if it already exists
|
|
if item_in.sku and item_in.sku != item.sku:
|
|
existing_item = crud.item.get_by_sku(db, sku=item_in.sku)
|
|
if existing_item:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Item with this SKU already exists",
|
|
)
|
|
|
|
# Check if category exists if being updated
|
|
if item_in.category_id and item_in.category_id != item.category_id:
|
|
category = crud.category.get(db, id=item_in.category_id)
|
|
if not category:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Category not found",
|
|
)
|
|
|
|
# If quantity is being updated, create a transaction
|
|
if item_in.quantity is not None and item_in.quantity != item.quantity:
|
|
quantity_change = item_in.quantity - item.quantity
|
|
if item.quantity + quantity_change < 0:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Cannot reduce quantity below zero",
|
|
)
|
|
|
|
# Update quantity with transaction
|
|
return crud.item.update_quantity(
|
|
db,
|
|
db_obj=item,
|
|
quantity_change=quantity_change,
|
|
transaction_type="adjustment",
|
|
notes="Quantity updated via API"
|
|
)
|
|
else:
|
|
# Regular update without quantity change
|
|
return crud.item.update(db, db_obj=item, obj_in=item_in)
|
|
|
|
|
|
@router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
|
def delete_item(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
item_id: int,
|
|
) -> None:
|
|
"""
|
|
Delete an item.
|
|
"""
|
|
item = crud.item.get(db, id=item_id)
|
|
if not item:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Item not found",
|
|
)
|
|
|
|
crud.item.remove(db, id=item_id)
|
|
return None
|
|
|
|
|
|
@router.post("/{item_id}/transactions", response_model=Item)
|
|
def add_inventory_transaction(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
item_id: int,
|
|
transaction_in: InventoryTransactionCreate,
|
|
) -> Any:
|
|
"""
|
|
Add inventory transaction (stock in/out).
|
|
"""
|
|
item = crud.item.get(db, id=item_id)
|
|
if not item:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Item not found",
|
|
)
|
|
|
|
# Update item quantity with transaction
|
|
try:
|
|
updated_item = crud.item.update_quantity(
|
|
db,
|
|
db_obj=item,
|
|
quantity_change=transaction_in.quantity_change,
|
|
transaction_type=transaction_in.transaction_type,
|
|
notes=transaction_in.notes
|
|
)
|
|
return updated_item
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e),
|
|
)
|
|
|
|
|
|
@router.get("/{item_id}/transactions", response_model=List[InventoryTransaction])
|
|
def get_item_transactions(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
item_id: int,
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
) -> Any:
|
|
"""
|
|
Get transaction history for an item.
|
|
"""
|
|
# Check if item exists
|
|
item = crud.item.get(db, id=item_id)
|
|
if not item:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Item not found",
|
|
)
|
|
|
|
transactions = crud.inventory_transaction.get_by_item(
|
|
db, item_id=item_id, skip=skip, limit=limit
|
|
)
|
|
return transactions |