panchodp / laravel-fifo
Simple FIFO inventory system
Requires
- php: ^8.4.0
Requires (Dev)
- larastan/larastan: ^3.6.1
- laravel/pint: ^1.24
- orchestra/testbench: ^10.6
- pestphp/pest: ^4.0.3
- rector/rector: ^2.1
- spatie/ray: ^1.42
This package is not auto-updated.
Last update: 2025-09-05 11:10:20 UTC
README
Laravel Fifo
Laravel package for inventory management using FIFO (First In, First Out) methodology.
Important
Caution: This package is a work in progress and may not be production-ready. Use at your own risk.
What is FIFO?
FIFO (First In, First Out) is an inventory valuation method where the first items purchased are the first items sold. This method is essential for:
- Accurate cost calculation: Ensures older inventory costs are used first
- Financial reporting: Provides realistic inventory valuations
- Perishable goods: Natural fit for items with expiration dates
- Inflation periods: Shows current market prices in cost of goods sold
How FIFO Works
When you sell inventory, FIFO calculates the cost using the oldest purchases first:
- Purchase: Record items with their unit costs
- Sale: Calculate cost using oldest inventory first
- Valuation: Remaining inventory reflects recent purchase prices
Example:
- Buy 100 units at $10 each (Total: $1,000)
- Buy 50 units at $12 each (Total: $600)
- Sell 120 units → Cost: (100 × $10) + (20 × $12) = $1,240
- Remaining: 30 units at $12 each = $360
Installation
You can install the package via composer:
composer require panchodp/laravel-fifo
Install the package (publishes config and migrations):
php artisan fifo:install
Run the migrations:
php artisan migrate
Alternative Installation
You can also publish files individually:
# Publish the config file php artisan vendor:publish --provider="LaravelFifo\Providers\LaravelFifoServiceProvider" --tag="laravel-fifo-config" # Publish the migrations php artisan vendor:publish --provider="LaravelFifo\Providers\LaravelFifoServiceProvider" --tag="laravel-fifo-migrations"
Configuration
Product Model
You must configure the Product model that will be used by the FIFO system. In your config/fifo.php
file:
'product_model' => App\Models\Product::class,
Or set it in your .env
file:
FIFO_PRODUCT_MODEL=App\Models\Product
Product Model Requirements
Your Product model must:
- Have an
id
column (primary key) - Extend
Illuminate\Database\Eloquent\Model
Adding Relationship to Your Product Model
Add the following relationship to your Product model:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use LaravelFifo\Models\FifoTransaction; class Product extends Model { // Your existing code... /** * Get the FIFO transactions for this product. */ public function fifoTransactions(): HasMany { return $this->hasMany(FifoTransaction::class); } }
Understanding the Data Structure
FifoTransaction Model
The FifoTransaction
model represents each inventory movement with the following fields:
Field | Type | Description |
---|---|---|
id |
integer | Primary key |
product_id |
integer | Foreign key to your Product model |
type |
enum | Transaction type: 'in' (purchase) or 'out' (sale) |
quantity |
decimal | Number of units |
unit_price |
decimal | Important: See semantics below |
total_amount |
decimal | quantity × unit_price |
transaction_date |
timestamp | When the transaction occurred |
reference |
string | Optional reference (PO number, invoice, etc.) |
unit_price Semantics
The unit_price
field has different meanings depending on the transaction type:
- Inbound transactions (
type = 'in'
): Represents the unit cost of purchasing the item - Outbound transactions (
type = 'out'
): Represents the FIFO-calculated weighted average cost of the items being sold
This design maintains consistency while providing accurate cost tracking through the FIFO methodology.
When to Use Laravel FIFO
Ideal Use Cases
- E-commerce: Track product costs across multiple purchase orders
- Manufacturing: Calculate raw material costs using oldest inventory first
Benefits
- Accurate Financial Reporting: Provides realistic cost of goods sold
- Inflation Management: Current inventory reflects recent purchase prices
- Automated Calculations: No manual cost tracking required
- Audit Trail: Complete transaction history for each product
Usage
Using the Fifo Facade
Register Inbound Transaction (Stock In)
use LaravelFifo\Facades\Fifo; $success = Fifo::registerInbound( productId: 1, quantity: 100.0, unitPrice: 10.50, reference: 'PO-001' // optional ); if ($success) { echo "Inbound transaction registered successfully"; } else { echo "Failed to register inbound transaction"; }
Register Outbound Transaction (Stock Out)
use LaravelFifo\Facades\Fifo; $result = Fifo::registerOutbound( productId: 1, quantity: 50.0, reference: 'SO-001' // optional ); if ($result['success']) { echo "Transaction ID: " . $result['transaction_id']; echo "FIFO Price: $" . $result['fifo_price']; } else { echo "Error: " . $result['error']; }
Complete Example: Purchase → Sale → Cost Calculation
Here's a complete workflow demonstrating FIFO in action:
use LaravelFifo\Facades\Fifo; // Step 1: Record purchases (inbound transactions) Fifo::registerInbound(productId: 1, quantity: 100, unitPrice: 10.00, reference: 'PO-001'); Fifo::registerInbound(productId: 1, quantity: 50, unitPrice: 12.00, reference: 'PO-002'); // Current inventory: 150 units // - 100 units at $10.00 each // - 50 units at $12.00 each // Total value: $1,600 echo "Available stock: " . Fifo::getAvailableStock(1); // 150.00 echo "Inventory value: $" . Fifo::getCurrentInventoryValue(1); // 1600.00 // Step 2: Make a sale (outbound transaction) $sale = Fifo::registerOutbound(productId: 1, quantity: 120, reference: 'SO-001'); if ($sale['success']) { echo "Sale completed:"; echo "- Units sold: 120"; echo "- FIFO cost per unit: $" . $sale['fifo_price']; // 10.33 echo "- Total cost: $" . (120 * floatval($sale['fifo_price'])); // 1,240.00 } // Step 3: Check remaining inventory echo "Remaining stock: " . Fifo::getAvailableStock(1); // 30.00 echo "Remaining value: $" . Fifo::getCurrentInventoryValue(1); // 360.00 // FIFO Calculation explanation: // Sale of 120 units uses: // - 100 units at $10.00 = $1,000 // - 20 units at $12.00 = $240 // - Total cost: $1,240 // - Average cost: $1,240 ÷ 120 = $10.33 per unit // // Remaining inventory: // - 30 units at $12.00 = $360
Stock Information Methods
use LaravelFifo\Facades\Fifo; // Get available stock $stock = Fifo::getAvailableStock(1); echo "Available stock: {$stock}"; // Get FIFO price for specific quantity $price = Fifo::fifoPrice(1, 25.0); echo "FIFO price for 25 units: ${price}"; // Get current inventory value $value = Fifo::getCurrentInventoryValue(1); echo "Current inventory value: ${value}"; // Get detailed stock by batch $batches = Fifo::calculateStockByBatch(1); foreach ($batches as $batch) { echo "Batch {$batch['transaction_id']}: {$batch['available_quantity']} units at ${batch['unit_price']}"; }
Understanding calculateStockByBatch() Return Value
The calculateStockByBatch()
method returns an array of available inventory batches, ordered by FIFO (oldest first). Each batch contains:
[ 'transaction_id' => 123, // ID of the original purchase transaction 'unit_price' => 10.50, // Unit cost from the original purchase 'original_quantity' => 100.0, // Original purchased quantity 'available_quantity' => 75.0, // Remaining quantity after sales 'transaction_date' => Carbon // Purchase date/time ]
Example with Multiple Batches:
// After some purchases and sales: $batches = Fifo::calculateStockByBatch(1); // Returns: // [ // [ // 'transaction_id' => 1, // 'unit_price' => 10.00, // 'original_quantity' => 100.0, // 'available_quantity' => 25.0, // 75 units were sold // 'transaction_date' => '2024-01-01 10:00:00' // ], // [ // 'transaction_id' => 2, // 'unit_price' => 12.00, // 'original_quantity' => 50.0, // 'available_quantity' => 50.0, // No units sold from this batch yet // 'transaction_date' => '2024-01-02 14:30:00' // ] // ] // This tells us: // - Oldest batch (ID 1): 25 units left at $10.00 each // - Newest batch (ID 2): 50 units left at $12.00 each // - Total available: 75 units // - Next sale will use the remaining 25 units from batch 1 first
Product Management
use LaravelFifo\Facades\Fifo; // Delete product (only if no transactions) $result = Fifo::deleteProduct(1); if (!$result['success']) { echo $result['error']; } // Force delete product and all transactions $result = Fifo::forceDeleteProduct(1); if ($result['success']) { echo "Deleted {$result['deleted_transactions']} transactions"; }