Commit 5b81069b authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix product analytics showing 0 — query InvoiceItem for revenue/units sold

Products with track_inventory=false never create InventoryMovements on sale,
so the old movement-based queries always returned 0. Revenue and units sold
now come from InvoiceItem (the authoritative source for what was actually
invoiced). Stock-in (إجمالي الوارد) still uses InventoryMovement since that
genuinely is a stock-receipt concept. Added إجمالي الإيرادات KPI card.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 11b7767c
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace App\Livewire\Inventory; namespace App\Livewire\Inventory;
use App\Domain\Financial\Models\InvoiceItem;
use App\Domain\Inventory\Enums\MovementDirection; use App\Domain\Inventory\Enums\MovementDirection;
use App\Domain\Inventory\Models\InventoryMovement; use App\Domain\Inventory\Models\InventoryMovement;
use App\Domain\Inventory\Models\Product; use App\Domain\Inventory\Models\Product;
...@@ -35,27 +36,30 @@ public function render() ...@@ -35,27 +36,30 @@ public function render()
->limit(30) ->limit(30)
->get(); ->get();
// Analytics // Analytics — revenue from InvoiceItem (works regardless of track_inventory)
$soldQuery = InvoiceItem::where('itemable_type', Product::class)
->where('itemable_id', $this->product->id)
->join('invoices', 'invoice_items.invoice_id', '=', 'invoices.id')
->whereNotIn('invoices.status', ['cancelled', 'refunded']);
$totalRevenue = (clone $soldQuery)->sum('invoice_items.total_amount');
$totalUnitsSold = (clone $soldQuery)->sum('invoice_items.quantity');
$firstSale = (clone $soldQuery)
->orderBy('invoice_items.created_at')
->value('invoice_items.created_at');
// Stock-in from movements (genuinely a stock concept)
$totalIn = InventoryMovement::where('product_id', $this->product->id) $totalIn = InventoryMovement::where('product_id', $this->product->id)
->where('direction', MovementDirection::In->value) ->where('direction', MovementDirection::In->value)
->sum('quantity'); ->sum('quantity');
$totalSold = InventoryMovement::where('product_id', $this->product->id)
->where('direction', MovementDirection::Out->value)
->where('movement_type', 'sale')
->sum('quantity');
$firstMovement = InventoryMovement::where('product_id', $this->product->id)
->orderBy('created_at')
->value('created_at');
$monthsActive = 1; $monthsActive = 1;
if ($firstMovement) { if ($firstSale) {
$monthsActive = max(1, (int) now()->diffInMonths($firstMovement)); $monthsActive = max(1, (int) now()->diffInMonths($firstSale));
} }
$totalMovements = InventoryMovement::where('product_id', $this->product->id)->sum('quantity'); $avgMonthlyMovement = round($totalUnitsSold / $monthsActive, 1);
$avgMonthlyMovement = round($totalMovements / $monthsActive, 1);
// Profit margin // Profit margin
$margin = 0; $margin = 0;
...@@ -70,7 +74,8 @@ public function render() ...@@ -70,7 +74,8 @@ public function render()
'totalAvailable' => $totalAvailable, 'totalAvailable' => $totalAvailable,
'movements' => $movements, 'movements' => $movements,
'totalIn' => $totalIn, 'totalIn' => $totalIn,
'totalSold' => $totalSold, 'totalSold' => $totalUnitsSold,
'totalRevenue' => $totalRevenue,
'avgMonthlyMovement' => $avgMonthlyMovement, 'avgMonthlyMovement' => $avgMonthlyMovement,
'margin' => $margin, 'margin' => $margin,
]); ]);
......
...@@ -347,20 +347,20 @@ class="px-6 py-3 text-sm font-medium border-b-2 transition-colors"> ...@@ -347,20 +347,20 @@ class="px-6 py-3 text-sm font-medium border-b-2 transition-colors">
{{-- Analytics Tab --}} {{-- Analytics Tab --}}
<div x-show="activeTab === 'analytics'" x-cloak> <div x-show="activeTab === 'analytics'" x-cloak>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{{-- Total Received --}} {{-- Total Revenue --}}
<div class="border border-gray-200 rounded-lg p-5 text-center"> <div class="border border-emerald-200 rounded-lg p-5 text-center bg-emerald-50">
<div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-100 mb-3"> <div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-emerald-100 mb-3">
<svg class="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="w-6 h-6 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 16l4 4 4-4m-4 4V4m14 0l-4-4-4 4m4-4v16" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
</div> </div>
<p class="text-xs font-medium text-gray-500 mb-1">{{ __('إجمالي الوارد') }}</p> <p class="text-xs font-medium text-emerald-700 mb-1">{{ __('إجمالي الإيرادات') }}</p>
<p class="text-2xl font-bold text-gray-800">{{ number_format($totalIn) }}</p> <p class="text-xl font-bold text-emerald-800">{{ number_format($totalRevenue / 100, 2) }}</p>
<p class="text-xs text-gray-400 mt-1">{{ __('وحدة') }}</p> <p class="text-xs text-emerald-600 mt-1">{{ __('ج.م') }}</p>
</div> </div>
{{-- Total Sold --}} {{-- Total Sold (units) --}}
<div class="border border-gray-200 rounded-lg p-5 text-center"> <div class="border border-gray-200 rounded-lg p-5 text-center">
<div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-blue-100 mb-3"> <div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-blue-100 mb-3">
<svg class="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
...@@ -372,6 +372,18 @@ class="px-6 py-3 text-sm font-medium border-b-2 transition-colors"> ...@@ -372,6 +372,18 @@ class="px-6 py-3 text-sm font-medium border-b-2 transition-colors">
<p class="text-xs text-gray-400 mt-1">{{ __('وحدة') }}</p> <p class="text-xs text-gray-400 mt-1">{{ __('وحدة') }}</p>
</div> </div>
{{-- Total Received (stock-in) --}}
<div class="border border-gray-200 rounded-lg p-5 text-center">
<div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-100 mb-3">
<svg class="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 16l4 4 4-4m-4 4V4m14 0l-4-4-4 4m4-4v16" />
</svg>
</div>
<p class="text-xs font-medium text-gray-500 mb-1">{{ __('إجمالي الوارد') }}</p>
<p class="text-2xl font-bold text-gray-800">{{ number_format($totalIn) }}</p>
<p class="text-xs text-gray-400 mt-1">{{ __('وحدة') }}</p>
</div>
{{-- Average Monthly --}} {{-- Average Monthly --}}
<div class="border border-gray-200 rounded-lg p-5 text-center"> <div class="border border-gray-200 rounded-lg p-5 text-center">
<div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-purple-100 mb-3"> <div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-purple-100 mb-3">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment