Skip to main content

Overview

The inventory management system consists of two main models: InventoryStock for tracking current stock levels and InventoryMovement for recording all inventory transactions.

InventoryStock Model

Tracks current stock quantities for each product in each warehouse. Namespace: App\Models\Inventory\InventoryStock Database Table: inventory_stocks Traits: HasFactory

Properties

warehouse_id
integer
required
Foreign key to the Warehouse model
product_id
integer
required
Foreign key to the Product model
quantity
integer
required
Current quantity in stock
min_stock
integer
Minimum stock level threshold for low stock alerts

Relationships

warehouse()

warehouse
BelongsTo
Returns the warehouse where this stock is located
$stock->warehouse; // Warehouse object
$stock->warehouse->name; // Warehouse name

product()

product
BelongsTo
Returns the product this stock record represents
$stock->product; // Product object
$stock->product->name; // Product name
$stock->product->sku; // Product SKU

Usage Examples

Checking Stock Levels

use App\Models\Inventory\InventoryStock;

// Get stock for a product in a specific warehouse
$stock = InventoryStock::where('product_id', $productId)
    ->where('warehouse_id', $warehouseId)
    ->first();

if ($stock && $stock->quantity > 0) {
    echo "Available: {$stock->quantity}";
} else {
    echo "Out of stock";
}

Low Stock Alerts

// Get all low stock items
$lowStockItems = InventoryStock::whereRaw('quantity <= min_stock')
    ->with(['product', 'warehouse'])
    ->get();

foreach ($lowStockItems as $item) {
    echo "{$item->product->name} is low in {$item->warehouse->name}";
    echo "Current: {$item->quantity}, Minimum: {$item->min_stock}";
}

Stock by Warehouse

// Get all products in a warehouse
$warehouseStock = InventoryStock::where('warehouse_id', $warehouseId)
    ->with('product')
    ->where('quantity', '>', 0)
    ->get();

// Calculate total inventory value
$totalValue = $warehouseStock->sum(function ($stock) {
    return $stock->quantity * $stock->product->cost;
});

InventoryMovement Model

Records all inventory transactions including inputs, outputs, adjustments, and transfers. Namespace: App\Models\Inventory\InventoryMovement Database Table: inventory_movements Traits: SoftDeletes

Properties

warehouse_id
integer
required
Foreign key to the source warehouse
to_warehouse_id
integer
Foreign key to destination warehouse (for transfers only)
product_id
integer
required
Foreign key to the Product model
user_id
integer
required
Foreign key to the User who created the movement
quantity
integer
required
Quantity moved (positive for inputs, negative for outputs)
type
string
required
Movement type: ‘input’, ‘output’, ‘adjustment’, or ‘transfer’
previous_stock
integer
Stock level before this movement (audit trail)
current_stock
integer
Stock level after this movement (audit trail)
description
text
Description or reason for the movement
reference_type
string
Polymorphic type for the related document (Sale, Purchase, etc.)
reference_id
integer
Polymorphic ID for the related document

Constants

const TYPE_INPUT = 'input';
const TYPE_OUTPUT = 'output';
const TYPE_ADJUSTMENT = 'adjustment';
const TYPE_TRANSFER = 'transfer';

Relationships

warehouse()

warehouse
BelongsTo
Returns the source warehouse
$movement->warehouse; // Source Warehouse object

toWarehouse()

toWarehouse
BelongsTo
Returns the destination warehouse (for transfers)
$movement->toWarehouse; // Destination Warehouse object (or null)

user()

user
BelongsTo
Returns the user who created the movement
$movement->user; // User object
$movement->user->name; // User name

product()

product
BelongsTo
Returns the product being moved
$movement->product; // Product object

reference()

reference
MorphTo
Returns the related document (Sale, Purchase, etc.)
$movement->reference; // Could be Sale, Purchase, or other model

Accessors

typeLabel

Returns human-readable label for the movement type.
$movement->typeLabel; // 'Entrada', 'Salida', 'Ajuste', or 'Transferencia'

Scopes

withIndexRelations()

Eager loads all relationships for list views.
InventoryMovement::withIndexRelations()->get();
This scope loads:
  • warehouse
  • toWarehouse
  • user
  • product
  • reference

Static Methods

getTypes()

Returns array of movement types with labels.
InventoryMovement::getTypes();
// Returns:
// [
//   'input' => 'Entrada',
//   'output' => 'Salida',
//   'adjustment' => 'Ajuste',
//   'transfer' => 'Transferencia'
// ]

Usage Examples

Recording an Input

use App\Models\Inventory\InventoryMovement;

$movement = InventoryMovement::create([
    'warehouse_id' => $warehouseId,
    'product_id' => $productId,
    'user_id' => auth()->id(),
    'quantity' => 100,
    'type' => InventoryMovement::TYPE_INPUT,
    'previous_stock' => $currentStock,
    'current_stock' => $currentStock + 100,
    'description' => 'Purchase order receipt',
    'reference_type' => 'App\\Models\\Purchases\\Purchase',
    'reference_id' => $purchase->id
]);

Recording an Output (Sale)

$movement = InventoryMovement::create([
    'warehouse_id' => $warehouseId,
    'product_id' => $productId,
    'user_id' => auth()->id(),
    'quantity' => -10, // Negative for outputs
    'type' => InventoryMovement::TYPE_OUTPUT,
    'previous_stock' => $currentStock,
    'current_stock' => $currentStock - 10,
    'description' => 'Sale to customer',
    'reference_type' => 'App\\Models\\Sales\\Sale',
    'reference_id' => $sale->id
]);

Recording a Transfer

$movement = InventoryMovement::create([
    'warehouse_id' => $fromWarehouseId,
    'to_warehouse_id' => $toWarehouseId,
    'product_id' => $productId,
    'user_id' => auth()->id(),
    'quantity' => 50,
    'type' => InventoryMovement::TYPE_TRANSFER,
    'previous_stock' => $currentStock,
    'current_stock' => $currentStock - 50,
    'description' => 'Transfer to branch warehouse'
]);

Recording an Adjustment

// Stock count revealed discrepancy
$expectedStock = 100;
$actualStock = 95;
$adjustment = $actualStock - $expectedStock; // -5

$movement = InventoryMovement::create([
    'warehouse_id' => $warehouseId,
    'product_id' => $productId,
    'user_id' => auth()->id(),
    'quantity' => $adjustment,
    'type' => InventoryMovement::TYPE_ADJUSTMENT,
    'previous_stock' => $expectedStock,
    'current_stock' => $actualStock,
    'description' => 'Physical inventory adjustment - damaged units'
]);

Querying Movement History

// Get all movements for a product
$productHistory = InventoryMovement::where('product_id', $productId)
    ->withIndexRelations()
    ->orderBy('created_at', 'desc')
    ->get();

foreach ($productHistory as $movement) {
    echo $movement->typeLabel;
    echo $movement->quantity;
    echo $movement->warehouse->name;
    echo $movement->user->name;
}

Filtering by Movement Type

// Get all inputs this month
$inputs = InventoryMovement::where('type', InventoryMovement::TYPE_INPUT)
    ->whereMonth('created_at', now()->month)
    ->with('product', 'warehouse')
    ->get();

// Get all transfers between specific warehouses
$transfers = InventoryMovement::where('type', InventoryMovement::TYPE_TRANSFER)
    ->where('warehouse_id', $fromWarehouse)
    ->where('to_warehouse_id', $toWarehouse)
    ->get();

Movement Audit Trail

// Get movement history with audit data
$movements = InventoryMovement::where('product_id', $productId)
    ->where('warehouse_id', $warehouseId)
    ->orderBy('created_at', 'desc')
    ->get();

foreach ($movements as $movement) {
    echo "Previous: {$movement->previous_stock}";
    echo "Change: {$movement->quantity}";
    echo "Current: {$movement->current_stock}";
    echo "By: {$movement->user->name}";
    echo "Date: {$movement->created_at}";
}

Movement Reports

// Daily movement summary
$dailySummary = InventoryMovement::whereDate('created_at', today())
    ->selectRaw('type, COUNT(*) as count, SUM(ABS(quantity)) as total_quantity')
    ->groupBy('type')
    ->get();

// Product movement by warehouse
$warehouseMovements = InventoryMovement::where('product_id', $productId)
    ->selectRaw('warehouse_id, type, SUM(quantity) as total')
    ->groupBy('warehouse_id', 'type')
    ->with('warehouse')
    ->get();
// Access the related sale or purchase
if ($movement->reference_type === 'App\\Models\\Sales\\Sale') {
    $sale = $movement->reference; // Polymorphic relationship
    echo "Related to Sale: {$sale->number}";
}

Complete Inventory Flow Example

Receiving Products (Input)

use App\Models\Inventory\InventoryStock;
use App\Models\Inventory\InventoryMovement;
use Illuminate\Support\Facades\DB;

DB::transaction(function () use ($warehouseId, $productId, $quantity, $purchaseId) {
    // Get or create stock record
    $stock = InventoryStock::firstOrCreate(
        [
            'warehouse_id' => $warehouseId,
            'product_id' => $productId
        ],
        ['quantity' => 0, 'min_stock' => 10]
    );
    
    $previousStock = $stock->quantity;
    $stock->quantity += $quantity;
    $stock->save();
    
    // Record the movement
    InventoryMovement::create([
        'warehouse_id' => $warehouseId,
        'product_id' => $productId,
        'user_id' => auth()->id(),
        'quantity' => $quantity,
        'type' => InventoryMovement::TYPE_INPUT,
        'previous_stock' => $previousStock,
        'current_stock' => $stock->quantity,
        'description' => 'Purchase receipt',
        'reference_type' => 'App\\Models\\Purchases\\Purchase',
        'reference_id' => $purchaseId
    ]);
});

Processing a Sale (Output)

DB::transaction(function () use ($warehouseId, $productId, $quantity, $saleId) {
    $stock = InventoryStock::where('warehouse_id', $warehouseId)
        ->where('product_id', $productId)
        ->firstOrFail();
    
    if ($stock->quantity < $quantity) {
        throw new \Exception('Insufficient stock');
    }
    
    $previousStock = $stock->quantity;
    $stock->quantity -= $quantity;
    $stock->save();
    
    InventoryMovement::create([
        'warehouse_id' => $warehouseId,
        'product_id' => $productId,
        'user_id' => auth()->id(),
        'quantity' => -$quantity,
        'type' => InventoryMovement::TYPE_OUTPUT,
        'previous_stock' => $previousStock,
        'current_stock' => $stock->quantity,
        'description' => 'Sale to customer',
        'reference_type' => 'App\\Models\\Sales\\Sale',
        'reference_id' => $saleId
    ]);
});
  • Product - Products being tracked
  • Warehouse - Storage locations
  • Sale - Sales that reduce inventory
  • User - Users making movements

Build docs developers (and LLMs) love