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)