Implement a new Filament page to provide a comprehensive overview of customer financial data, including new page class and blade template.
230 lines
8.6 KiB
PHP
230 lines
8.6 KiB
PHP
<?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';
|
||
}
|
||
} |