
- 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
176 lines
5.7 KiB
Python
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 |