Skip to content

Commit

Permalink
Merge pull request #375 from liberu-accounting/sweep/Implement-Advanc…
Browse files Browse the repository at this point in the history
…ed-Inventory-Valuation-Methods-and-Cost-Tracking

Implement Advanced Inventory Valuation Methods and Cost Tracking
  • Loading branch information
curtisdelicata authored Dec 24, 2024
2 parents d4d5e05 + ac54bca commit 5637c27
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 1 deletion.
45 changes: 44 additions & 1 deletion app/Services/InventoryService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@
use App\Models\InventoryItem;
use App\Models\Transaction;
use App\Models\InventoryTransaction;
use App\Models\InventoryCostLayer;

class InventoryService
{
protected $valuationService;

public function __construct(InventoryValuationService $valuationService)
{
$this->valuationService = $valuationService;
}

public function createInventoryTransaction(
Transaction $transaction,
InventoryItem $item,
Expand All @@ -25,6 +33,22 @@ public function createInventoryTransaction(
'transaction_type' => $type
]);

if ($type === 'purchase') {
InventoryCostLayer::create([
'inventory_item_id' => $item->id,
'quantity' => $quantity,
'unit_cost' => $unitPrice,
'purchase_date' => now()
]);

if ($item->valuation_method === 'average') {
$this->valuationService->updateAverageCost($item, $quantity, $unitPrice);
}
} else if ($type === 'sale') {
$costOfGoodsSold = $this->valuationService->calculateCostOfGoodsSold($inventoryTx);
$inventoryTx->update(['cost_of_goods_sold' => $costOfGoodsSold]);
}

$change = $type === 'sale' ? -$quantity : $quantity;
$item->updateQuantity($change);

Expand All @@ -41,6 +65,25 @@ public function checkLowStock()
public function getInventoryValue()
{
return InventoryItem::where('is_active', true)
->sum(\DB::raw('unit_price * current_quantity'));
->get()
->sum(function ($item) {
return $this->valuationService->getInventoryValuation($item);
});
}

public function getInventoryReport()
{
return InventoryItem::where('is_active', true)
->get()
->map(function ($item) {
return [
'id' => $item->id,
'name' => $item->name,
'sku' => $item->sku,
'quantity' => $item->current_quantity,
'valuation_method' => $item->valuation_method,
'total_value' => $this->valuationService->getInventoryValuation($item)
];
});
}
}
102 changes: 102 additions & 0 deletions app/Services/InventoryValuationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@


<?php

namespace App\Services;

use App\Models\InventoryItem;
use App\Models\InventoryTransaction;
use App\Models\InventoryCostLayer;
use Illuminate\Support\Facades\DB;

class InventoryValuationService
{
public function calculateCostOfGoodsSold(InventoryTransaction $transaction)
{
$item = $transaction->inventoryItem;

switch ($item->valuation_method) {
case 'fifo':
return $this->calculateFIFOCost($transaction);
case 'lifo':
return $this->calculateLIFOCost($transaction);
case 'average':
return $this->calculateAverageCost($transaction);
}
}

private function calculateFIFOCost(InventoryTransaction $transaction)
{
$remainingQty = $transaction->quantity;
$totalCost = 0;

$costLayers = InventoryCostLayer::where('inventory_item_id', $transaction->inventory_item_id)
->where('quantity', '>', 0)
->orderBy('purchase_date')
->get();

foreach ($costLayers as $layer) {
$qtyFromLayer = min($remainingQty, $layer->quantity);
$totalCost += $qtyFromLayer * $layer->unit_cost;
$layer->quantity -= $qtyFromLayer;
$layer->save();

$remainingQty -= $qtyFromLayer;
if ($remainingQty <= 0) break;
}

return $totalCost;
}

private function calculateLIFOCost(InventoryTransaction $transaction)
{
$remainingQty = $transaction->quantity;
$totalCost = 0;

$costLayers = InventoryCostLayer::where('inventory_item_id', $transaction->inventory_item_id)
->where('quantity', '>', 0)
->orderBy('purchase_date', 'desc')
->get();

foreach ($costLayers as $layer) {
$qtyFromLayer = min($remainingQty, $layer->quantity);
$totalCost += $qtyFromLayer * $layer->unit_cost;
$layer->quantity -= $qtyFromLayer;
$layer->save();

$remainingQty -= $qtyFromLayer;
if ($remainingQty <= 0) break;
}

return $totalCost;
}

private function calculateAverageCost(InventoryTransaction $transaction)
{
$item = $transaction->inventoryItem;
return $transaction->quantity * $item->average_cost;
}

public function updateAverageCost(InventoryItem $item, $purchaseQty, $purchaseCost)
{
$totalValue = ($item->current_quantity * $item->average_cost) +
($purchaseQty * $purchaseCost);
$totalQty = $item->current_quantity + $purchaseQty;

$item->average_cost = $totalValue / $totalQty;
$item->save();
}

public function getInventoryValuation(InventoryItem $item)
{
switch ($item->valuation_method) {
case 'fifo':
case 'lifo':
return InventoryCostLayer::where('inventory_item_id', $item->id)
->where('quantity', '>', 0)
->sum(DB::raw('quantity * unit_cost'));
case 'average':
return $item->current_quantity * $item->average_cost;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public function up()
$table->integer('reorder_point')->default(0);
$table->foreignId('account_id')->constrained('accounts');
$table->foreignId('category_id')->nullable()->constrained('categories');
$table->enum('valuation_method', ['fifo', 'lifo', 'average'])->default('fifo');
$table->decimal('average_cost', 15, 2)->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::create('inventory_cost_layers', function (Blueprint $table) {
$table->id();
$table->foreignId('inventory_item_id')->constrained('inventory_items');
$table->integer('quantity');
$table->decimal('unit_cost', 15, 2);
$table->date('purchase_date');
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('inventory_cost_layers');
}
};

0 comments on commit 5637c27

Please sign in to comment.