Automated Action 1d312e5ff6 Implement Small Business Inventory Management System
- Created FastAPI application with SQLite database
- Implemented models for inventory items, categories, suppliers, and transactions
- Added authentication system with JWT tokens
- Implemented CRUD operations for all models
- Set up Alembic for database migrations
- Added comprehensive API documentation
- Configured Ruff for code linting
2025-06-06 20:27:41 +00:00

176 lines
5.7 KiB
Python

from datetime import datetime, timedelta
from typing import Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from app import crud
from app.api import deps
from app.models.transaction import TransactionType
from app.models.user import User
from app.schemas.transaction import Transaction, TransactionCreate, TransactionUpdate
router = APIRouter()
@router.get("/", response_model=List[Transaction])
def read_transactions(
db: Session = Depends(deps.get_db),
skip: int = 0,
limit: int = 100,
item_id: Optional[int] = None,
transaction_type: Optional[TransactionType] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Retrieve transactions with optional filtering.
"""
if item_id:
transactions = crud.transaction.get_by_item(db, item_id=item_id, skip=skip, limit=limit)
elif transaction_type:
transactions = crud.transaction.get_by_type(db, transaction_type=transaction_type, skip=skip, limit=limit)
elif start_date and end_date:
transactions = crud.transaction.get_by_date_range(
db, start_date=start_date, end_date=end_date, skip=skip, limit=limit
)
else:
transactions = crud.transaction.get_multi(db, skip=skip, limit=limit)
return transactions
@router.post("/", response_model=Transaction, status_code=status.HTTP_201_CREATED)
def create_transaction(
*,
db: Session = Depends(deps.get_db),
transaction_in: TransactionCreate,
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Create new transaction (stock in or stock out).
"""
# Check if item exists
item = crud.item.get(db, id=transaction_in.item_id)
if not item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
# For stock out transactions, check if there's enough stock
if transaction_in.transaction_type == TransactionType.STOCK_OUT and item.quantity < transaction_in.quantity:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Insufficient stock. Available: {item.quantity}, Requested: {transaction_in.quantity}",
)
# Create transaction with item quantity update
transaction = crud.transaction.create_with_item_update(
db, obj_in=transaction_in, user_id=current_user.id
)
return transaction
@router.get("/{transaction_id}", response_model=Transaction)
def read_transaction(
*,
db: Session = Depends(deps.get_db),
transaction_id: int,
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Get transaction by ID.
"""
transaction = crud.transaction.get(db, id=transaction_id)
if not transaction:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Transaction not found",
)
return transaction
@router.put("/{transaction_id}", response_model=Transaction)
def update_transaction(
*,
db: Session = Depends(deps.get_db),
transaction_id: int,
transaction_in: TransactionUpdate,
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Update a transaction's metadata (notes, reference) only.
Quantity and item changes are not allowed to maintain inventory integrity.
"""
transaction = crud.transaction.get(db, id=transaction_id)
if not transaction:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Transaction not found",
)
# Only allow updating certain fields (reference, notes)
# Do not allow changing quantity, unit price to maintain inventory integrity
update_data = transaction_in.dict(exclude_unset=True)
for field in ["quantity", "unit_price"]:
if field in update_data:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Cannot update {field} after transaction is created",
)
# Calculate total_price if unit_price is updated
if "unit_price" in update_data:
update_data["total_price"] = transaction.quantity * update_data["unit_price"]
transaction = crud.transaction.update(db, db_obj=transaction, obj_in=update_data)
return transaction
@router.get("/by-item/{item_id}", response_model=List[Transaction])
def read_item_transactions(
*,
db: Session = Depends(deps.get_db),
item_id: int,
skip: int = 0,
limit: int = 100,
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Get all transactions for a specific 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.transaction.get_by_item(db, item_id=item_id, skip=skip, limit=limit)
return transactions
@router.get("/report/daily/", response_model=List[Transaction])
def get_daily_transactions(
*,
db: Session = Depends(deps.get_db),
date: Optional[datetime] = Query(None),
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Get all transactions for a specific day.
"""
# Default to today if no date is provided
if not date:
date = datetime.now()
start_date = datetime(date.year, date.month, date.day, 0, 0, 0)
end_date = start_date + timedelta(days=1) - timedelta(microseconds=1)
transactions = crud.transaction.get_by_date_range(
db, start_date=start_date, end_date=end_date
)
return transactions