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 @@
namespace App\Livewire\Inventory;
use App\Domain\Financial\Models\InvoiceItem;
use App\Domain\Inventory\Enums\MovementDirection;
use App\Domain\Inventory\Models\InventoryMovement;
use App\Domain\Inventory\Models\Product;
......@@ -35,27 +36,30 @@ public function render()
->limit(30)
->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)
->where('direction', MovementDirection::In->value)
->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;
if ($firstMovement) {
$monthsActive = max(1, (int) now()->diffInMonths($firstMovement));
if ($firstSale) {
$monthsActive = max(1, (int) now()->diffInMonths($firstSale));
}
$totalMovements = InventoryMovement::where('product_id', $this->product->id)->sum('quantity');
$avgMonthlyMovement = round($totalMovements / $monthsActive, 1);
$avgMonthlyMovement = round($totalUnitsSold / $monthsActive, 1);
// Profit margin
$margin = 0;
......@@ -70,7 +74,8 @@ public function render()
'totalAvailable' => $totalAvailable,
'movements' => $movements,
'totalIn' => $totalIn,
'totalSold' => $totalSold,
'totalSold' => $totalUnitsSold,
'totalRevenue' => $totalRevenue,
'avgMonthlyMovement' => $avgMonthlyMovement,
'margin' => $margin,
]);
......
......@@ -347,20 +347,20 @@ class="px-6 py-3 text-sm font-medium border-b-2 transition-colors">
{{-- Analytics Tab --}}
<div x-show="activeTab === 'analytics'" x-cloak>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6">
{{-- Total Received --}}
<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" />
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{{-- Total Revenue --}}
<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-emerald-100 mb-3">
<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="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>
</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>
<p class="text-xs font-medium text-emerald-700 mb-1">{{ __('إجمالي الإيرادات') }}</p>
<p class="text-xl font-bold text-emerald-800">{{ number_format($totalRevenue / 100, 2) }}</p>
<p class="text-xs text-emerald-600 mt-1">{{ __('ج.م') }}</p>
</div>
{{-- Total Sold --}}
{{-- Total Sold (units) --}}
<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">
<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">
<p class="text-xs text-gray-400 mt-1">{{ __('وحدة') }}</p>
</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 --}}
<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">
......
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