From ab972fe3e8657828f8693c7261cba74642564974 Mon Sep 17 00:00:00 2001 From: Kazem Alghasi Date: Sat, 3 Oct 2026 21:27:21 +0330 Subject: [PATCH] feat(admin): add customer financial overview page Implement a new Filament page to provide a comprehensive overview of customer financial data, including new page class and blade template. --- .../Pages/CustomerFinancialOverview.php | 230 ++++++++++ .../customer-financial-overview.blade.php | 392 ++++++++++++++++++ 2 files changed, 622 insertions(+) create mode 100644 04_Laravel/app/Filament/Pages/CustomerFinancialOverview.php create mode 100644 04_Laravel/resources/views/filament/pages/customer-financial-overview.blade.php diff --git a/04_Laravel/app/Filament/Pages/CustomerFinancialOverview.php b/04_Laravel/app/Filament/Pages/CustomerFinancialOverview.php new file mode 100644 index 0000000..3617026 --- /dev/null +++ b/04_Laravel/app/Filament/Pages/CustomerFinancialOverview.php @@ -0,0 +1,230 @@ +form->fill(); + } + + public function form(Form $form): Form + { + return $form + ->schema([ + Select::make('customer_id') + ->label('انتخاب مشتری') + ->placeholder('نام، ایمیل یا شماره موبایل را جستجو کنید…') + ->options(fn () => $this->getCustomerOptions()) + ->searchable() + ->reactive() + ->preload() + ->required(), + ]) + ->statePath('data'); + } + + /** + * لیست مشتریان برای Select — کاربرانی که مشتری هستند (role = customer یا بدون نقش). + */ + protected function getCustomerOptions(): array + { + return User::query() + ->where('role', 'customer') + ->orWhereNull('role') + ->where('is_active', true) + ->orderBy('name') + ->limit(500) + ->get() + ->mapWithKeys(fn ($user) => [ + $user->id => "{$user->name}" . ($user->phone ? " — {$user->phone}" : ''), + ]) + ->toArray(); + } + + /** + * Livewire lifecycle hook — هنگام تغییر فرم صدا زده می‌شود. + */ + public function updated($property, $value): void + { + if ($property === 'data.customer_id') { + if (!empty($value)) { + $this->loadFinancialData((int) $value); + } else { + $this->financialData = null; + } + } + } + + /** + * بارگذاری داده‌های مالی مشتری انتخاب‌شده. + */ + public function loadFinancialData(int $customerId): void + { + $customer = User::with(['wallet'])->find($customerId); + + if (!$customer) { + $this->financialData = null; + return; + } + + $creditService = app(CustomerCreditService::class); + + // ─── KPI محاسبات ─── + $walletBalance = (float) ($customer->wallet?->balance ?? 0); + + $totalPendingIrr = (float) Shipment::where('user_id', $customer->id) + ->where('status', ShipmentStatus::Approved) + ->sum('total_fee'); + + $pendingCount = Shipment::where('user_id', $customer->id) + ->where('status', ShipmentStatus::Approved) + ->count(); + + $totalOrders = Shipment::where('user_id', $customer->id)->count(); + + $totalPaid = (float) WalletTransaction::whereHas('wallet', fn ($q) => $q->where('user_id', $customer->id)) + ->where('type', 'order_payment') + ->where('status', 'completed') + ->where('amount', '>', 0) + ->sum('amount'); + + $totalDeposits = (float) WalletTransaction::whereHas('wallet', fn ($q) => $q->where('user_id', $customer->id)) + ->where('type', 'deposit') + ->where('status', 'completed') + ->where('amount', '>', 0) + ->sum('amount'); + + // بدهی‌های چندارزی (EU/AED/USD) + $foreignCredits = $creditService->getCustomerDebtsByCurrency($customer); + + // اعتبارات فعال (ریکوردهای قابل ویرایش) + $activeCredits = CustomerCredit::where('user_id', $customer->id) + ->whereIn('status', ['open', 'partially_settled']) + ->orderBy('created_at', 'desc') + ->get(); + + // سفارشات اخیر (۵ ردیف) + $recentOrders = Shipment::where('user_id', $customer->id) + ->with(['fromCountry', 'toCountry']) + ->orderBy('created_at', 'desc') + ->limit(5) + ->get(); + + // تراکنش‌های اخیر (۵ ردیف) + $recentTransactions = WalletTransaction::whereHas('wallet', fn ($q) => $q->where('user_id', $customer->id)) + ->with('wallet') + ->orderBy('created_at', 'desc') + ->limit(5) + ->get(); + + // مانده حساب = کیف پول منهای تعهدات ریالی + $accountBalance = $walletBalance - $totalPendingIrr; + + $this->financialData = [ + 'customer' => [ + 'id' => $customer->id, + 'name' => $customer->name, + 'email' => $customer->email, + 'phone' => $customer->phone, + 'created_at' => $customer->created_at?->format('Y/m/d'), + ], + 'kpis' => [ + [ + 'label' => 'موجودی کیف پول', + 'value' => ifnex_format_number($walletBalance), + 'desc' => $walletBalance > 0 ? 'موجودی مثبت' : 'صفر یا منفی', + 'icon' => 'heroicon-o-wallet', + 'color' => 'blue', + ], + [ + 'label' => 'بدهی فعلی (سفارشات پرداخت‌نشده)', + 'value' => ifnex_format_number($totalPendingIrr), + 'desc' => ifnex_persian_digits(number_format($pendingCount)) . ' سفارش تأییدشده', + 'icon' => 'heroicon-o-clock', + 'color' => $totalPendingIrr > 0 ? 'red' : 'gray', + ], + [ + 'label' => 'کل پرداختی (تاریخچه)', + 'value' => ifnex_format_number($totalPaid), + 'desc' => 'مجموع پرداخت‌های موفق', + 'icon' => 'heroicon-o-arrow-trending-up', + 'color' => 'green', + ], + [ + 'label' => 'مانده حساب', + 'value' => ifnex_format_number($accountBalance), + 'desc' => $accountBalance >= 0 ? 'موجودی‌ساز' : 'بدهکار — منفی', + 'icon' => 'heroicon-o-banknotes', + 'color' => $accountBalance >= 0 ? 'green' : 'red', + ], + ], + 'walletBalance' => $walletBalance, + 'totalPendingIrr' => $totalPendingIrr, + 'totalOrders' => $totalOrders, + 'totalDeposits' => $totalDeposits, + 'accountBalance' => $accountBalance, + 'foreignCredits' => $foreignCredits, + 'activeCredits' => $activeCredits, + 'recentOrders' => $recentOrders, + 'recentTransactions' => $recentTransactions, + ]; + } + + /** + * فقط مدیر و کارمند می‌توانند این صفحه را ببینند. + */ + public static function canAccess(): bool + { + $user = auth()->user(); + if (!$user) return false; + + // مدیر یا کارمند + return $user->hasRole('super_admin') + || $user->hasRole('admin') + || $user->hasRole('tracking_operator') + || $user->hasRole('data_entry') + || $user->role === 'super_admin' + || $user->role === 'admin' + || $user->role === 'tracking_operator' + || $user->role === 'data_entry'; + } +} \ No newline at end of file diff --git a/04_Laravel/resources/views/filament/pages/customer-financial-overview.blade.php b/04_Laravel/resources/views/filament/pages/customer-financial-overview.blade.php new file mode 100644 index 0000000..740b72f --- /dev/null +++ b/04_Laravel/resources/views/filament/pages/customer-financial-overview.blade.php @@ -0,0 +1,392 @@ +{{-- + IFNEX — Customer Financial Overview page. + Director/staff selects a customer, sees complete financial status: + KPIs + multi-currency credits + recent orders + recent transactions. +--}} + +
+ + + + {{-- ════════ Customer Selector ════════ --}} +
+

+ + جستجوی مشتری +

+ {{ $this->form }} +
+ + {{-- ════════ Financial Overview (only when customer selected) ════════ --}} + @if(!empty($financialData)) + + {{-- Customer Info + KPIs --}} +
+ + {{-- Customer header --}} +
+
+
+

+ {{ $financialData['customer']['name'] }} +

+
+ @if($financialData['customer']['phone']) + {{ $financialData['customer']['phone'] }} + @endif + @if($financialData['customer']['email']) + — {{ $financialData['customer']['email'] }} + @endif + — عضو از {{ $financialData['customer']['created_at'] }} +
+
+ +
+
+ + {{-- KPI Grid --}} +
+ @foreach($financialData['kpis'] as $kpi) +
+
+ +
+
+
{{ $kpi['label'] }}
+
{{ $kpi['value'] }}
+
{{ $kpi['desc'] }}
+
+
+ @endforeach +
+
+ + {{-- Multi-Currency Credits --}} +
+
+ + + +

بدهی‌های چندارزی

+
+ @if(empty($financialData['foreignCredits'])) +
+

بدهی چندارزی فعال ندارد ✅

+
+ @else +
+ + + + + + + + + + + + @foreach($financialData['foreignCredits'] as $credit) + + + + + + + + @endforeach + +
ارزمبلغ کلتسویه‌شدهماندهتعداد رکورد
+ {{ $credit['currency_code'] }} + {{ number_format($credit['total_amount'], 2) }}{{ number_format($credit['total_settled'], 2) }}{{ number_format($credit['balance'], 2) }}{{ ifnex_persian_digits(number_format($credit['count'])) }}
+
+ @endif +
+ + {{-- Recent Orders + Transactions (side by side) --}} +
+ + {{-- Recent Orders --}} +
+
+
+ + + +

سفارشات اخیر

+
+ {{ ifnex_persian_digits(number_format($financialData['totalOrders'])) }} سفارش کل +
+ @if($financialData['recentOrders']->isEmpty()) +
سفارشی ثبت نشده است
+ @else +
+ + + + + + + + + + + @foreach($financialData['recentOrders'] as $order) + + + + + + + @endforeach + +
AWBمسیروضعیتمبلغ
+ + {{ $order->awb_no }} + + + {{ $order->fromCountry?->name }} ← {{ $order->toCountry?->name }} + + @php + $stColor = match($order->status?->value) { + 'approved' => 'ifnex-badge-info', + 'delivered' => 'ifnex-badge-success', + 'cancelled', 'failed' => 'ifnex-badge-danger', + 'pending_approval', 'pending_payment' => 'ifnex-badge-warning', + default => 'ifnex-badge-gray', + }; + @endphp + + {{ $order->status?->label() ?? '—' }} + + + {{ $order->total_fee ? ifnex_format_number($order->total_fee) : '—' }} +
+
+ @endif +
+ + {{-- Recent Transactions --}} +
+
+
+ + + +

تراکنش‌های اخیر

+
+ + مشاهده همه ← + +
+ @if($financialData['recentTransactions']->isEmpty()) +
تراکنشی ثبت نشده است
+ @else +
+ + + + + + + + + + + @foreach($financialData['recentTransactions'] as $tx) + + + + + + + @endforeach + +
نوعمبلغتوضیحاتتاریخ
+ @php + $txColor = match($tx->type?->value) { + 'deposit' => 'ifnex-badge-success', + 'withdrawal' => 'ifnex-badge-danger', + 'order_payment' => 'ifnex-badge-warning', + 'refund' => 'ifnex-badge-info', + default => 'ifnex-badge-gray', + }; + @endphp + + {{ $tx->type?->label() ?? '—' }} + + + {{ $tx->amount >= 0 ? '+' : '' }}{{ ifnex_format_number(abs($tx->amount)) }} + + {{ \Illuminate\Support\Str::limit($tx->description ?? '—', 30) }} + + {{ \Morilog\Jalali\Jalalian::fromCarbon($tx->created_at)->format('Y/m/d H:i') }} +
+
+ @endif +
+
+ + @else + {{-- Empty state --}} +
+
+ +
+

+ مشتری را انتخاب کنید +

+

+ برای مشاهده وضعیت مالی کامل، از فرم بالا یک مشتری جستجو و انتخاب کنید. +

+
+ @endif + +
+
\ No newline at end of file