ifnex/04_Laravel/app/Filament/Pages/CustomerFinancialOverview.php
Kazem Alghasi ab972fe3e8 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.
2026-10-03 21:27:21 +03:30

230 lines
8.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Filament\Pages;
use App\Enums\ShipmentStatus;
use App\Models\CustomerCredit;
use App\Models\Shipment;
use App\Models\User;
use App\Models\WalletTransaction;
use App\Services\CustomerCreditService;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Illuminate\Support\Facades\DB;
/**
* صفحهٔ «وضعیت مالی مشتری» — نمای یکپارچه مالی per-customer در پنل ادمین.
*
* مدیر/کارمند مشتری را انتخاب می‌کند و در یک نگاه می‌بیند:
* - موجودی کیف پول
* - کل بدهی (سفارشات تأییدشده پرداخت‌نشده)
* - کل پرداختی (تاریخچه)
* - تعداد کل سفارشات
* - اعتبارات چندارزی فعال
* - سفارشات اخیر
* - تراکنش‌های اخیر
*/
class CustomerFinancialOverview extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-banknotes';
protected static ?string $navigationLabel = 'وضعیت مالی مشتری';
protected static ?string $navigationGroup = 'مالی';
protected static ?int $navigationSort = 5;
protected static ?string $slug = 'customer-financial';
protected static string $view = 'filament.pages.customer-financial-overview';
/** @var array فرم انتخاب مشتری */
public ?array $data = [];
/** @var array|null داده‌های مالی پس از انتخاب مشتری */
public ?array $financialData = null;
public function mount(): void
{
$this->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';
}
}