
This commit includes: - Project structure setup with FastAPI and SQLite - Database models and schemas for inventory management - CRUD operations for all entities - API endpoints for product, category, supplier, and inventory management - User authentication with JWT tokens - Initial database migration - Comprehensive README with setup instructions
172 lines
6.2 KiB
Python
172 lines
6.2 KiB
Python
from typing import List, Optional, Dict, Any
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.crud.base import CRUDBase
|
|
from app.models.inventory import Inventory, InventoryTransaction
|
|
from app.models.product import Product
|
|
from app.schemas.inventory import (
|
|
InventoryCreate, InventoryUpdate,
|
|
InventoryTransactionCreate, InventoryTransactionUpdate
|
|
)
|
|
|
|
|
|
class CRUDInventory(CRUDBase[Inventory, InventoryCreate, InventoryUpdate]):
|
|
def get_by_product_id(self, db: Session, *, product_id: int) -> Optional[Inventory]:
|
|
return db.query(Inventory).filter(Inventory.product_id == product_id).first()
|
|
|
|
def get_by_location(
|
|
self, db: Session, *, location: str, skip: int = 0, limit: int = 100
|
|
) -> List[Inventory]:
|
|
return db.query(Inventory).filter(Inventory.location == location).offset(skip).limit(limit).all()
|
|
|
|
def get_low_stock(self, db: Session, *, skip: int = 0, limit: int = 100) -> List[Dict[str, Any]]:
|
|
"""Get products where inventory is below minimum stock level."""
|
|
query = (
|
|
db.query(
|
|
Inventory,
|
|
Product.name,
|
|
Product.sku,
|
|
Product.min_stock_level
|
|
)
|
|
.join(Product)
|
|
.filter(Inventory.quantity <= Product.min_stock_level)
|
|
.offset(skip)
|
|
.limit(limit)
|
|
)
|
|
|
|
result = []
|
|
for inventory, name, sku, min_stock_level in query.all():
|
|
result.append({
|
|
"product_id": inventory.product_id,
|
|
"product_name": name,
|
|
"sku": sku,
|
|
"current_stock": inventory.quantity,
|
|
"min_stock_level": min_stock_level,
|
|
"is_low_stock": True
|
|
})
|
|
|
|
return result
|
|
|
|
def get_inventory_summary(self, db: Session, *, skip: int = 0, limit: int = 100) -> List[Dict[str, Any]]:
|
|
"""Get inventory summary for all products."""
|
|
query = (
|
|
db.query(
|
|
Inventory,
|
|
Product.name,
|
|
Product.sku,
|
|
Product.min_stock_level
|
|
)
|
|
.join(Product)
|
|
.offset(skip)
|
|
.limit(limit)
|
|
)
|
|
|
|
result = []
|
|
for inventory, name, sku, min_stock_level in query.all():
|
|
result.append({
|
|
"product_id": inventory.product_id,
|
|
"product_name": name,
|
|
"sku": sku,
|
|
"current_stock": inventory.quantity,
|
|
"min_stock_level": min_stock_level,
|
|
"is_low_stock": inventory.quantity <= min_stock_level
|
|
})
|
|
|
|
return result
|
|
|
|
def update_stock(
|
|
self, db: Session, *, product_id: int, quantity_change: int, user_id: Optional[int] = None,
|
|
transaction_type: str = "adjustment", reference: Optional[str] = None,
|
|
unit_price: Optional[float] = None, notes: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Update stock level and create a transaction record."""
|
|
inventory = self.get_by_product_id(db=db, product_id=product_id)
|
|
|
|
if not inventory:
|
|
# Create new inventory record if it doesn't exist
|
|
inventory_in = InventoryCreate(
|
|
product_id=product_id,
|
|
quantity=quantity_change if quantity_change > 0 else 0, # Don't allow negative initial stock
|
|
last_counted_at=datetime.now()
|
|
)
|
|
inventory = super().create(db=db, obj_in=inventory_in)
|
|
else:
|
|
# Update existing inventory
|
|
new_quantity = inventory.quantity + quantity_change
|
|
if new_quantity < 0:
|
|
new_quantity = 0 # Don't allow negative stock
|
|
|
|
inventory_in = InventoryUpdate(
|
|
product_id=product_id,
|
|
quantity=new_quantity,
|
|
last_counted_at=datetime.now()
|
|
)
|
|
inventory = super().update(db=db, db_obj=inventory, obj_in=inventory_in)
|
|
|
|
# Create transaction record
|
|
transaction = InventoryTransaction(
|
|
product_id=product_id,
|
|
quantity=quantity_change,
|
|
transaction_type=transaction_type,
|
|
reference=reference,
|
|
unit_price=unit_price,
|
|
notes=notes,
|
|
transaction_date=datetime.now(),
|
|
user_id=user_id
|
|
)
|
|
db.add(transaction)
|
|
db.commit()
|
|
db.refresh(transaction)
|
|
|
|
return {
|
|
"inventory": inventory,
|
|
"transaction": transaction
|
|
}
|
|
|
|
|
|
class CRUDInventoryTransaction(CRUDBase[InventoryTransaction, InventoryTransactionCreate, InventoryTransactionUpdate]):
|
|
def get_by_product_id(
|
|
self, db: Session, *, product_id: int, skip: int = 0, limit: int = 100
|
|
) -> List[InventoryTransaction]:
|
|
return (
|
|
db.query(InventoryTransaction)
|
|
.filter(InventoryTransaction.product_id == product_id)
|
|
.order_by(InventoryTransaction.transaction_date.desc())
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
|
|
def get_by_type(
|
|
self, db: Session, *, transaction_type: str, skip: int = 0, limit: int = 100
|
|
) -> List[InventoryTransaction]:
|
|
return (
|
|
db.query(InventoryTransaction)
|
|
.filter(InventoryTransaction.transaction_type == transaction_type)
|
|
.order_by(InventoryTransaction.transaction_date.desc())
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
|
|
def get_by_date_range(
|
|
self, db: Session, *, start_date: datetime, end_date: datetime,
|
|
skip: int = 0, limit: int = 100
|
|
) -> List[InventoryTransaction]:
|
|
return (
|
|
db.query(InventoryTransaction)
|
|
.filter(
|
|
InventoryTransaction.transaction_date >= start_date,
|
|
InventoryTransaction.transaction_date <= end_date
|
|
)
|
|
.order_by(InventoryTransaction.transaction_date.desc())
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
|
|
|
|
inventory = CRUDInventory(Inventory)
|
|
inventory_transaction = CRUDInventoryTransaction(InventoryTransaction) |