ifnex/04_Laravel/app/Filament/Resources/CustomerCreditResource.php
Kazem Alghasi 3848f0b399 feat(finance): rate approval + multi-currency credit system (درخواست ۹)
- rate approval: API creates pending proposals, manager approves
- customer_credits + credit_settlements tables
- CustomerCreditService (grant/settle via wallet)
- rebuilt CustomerCreditResource
- CustomerFinancialController: real foreign-currency debt
- WP login redirect + custom login page wiring
2026-09-30 05:14:31 +03:30

322 lines
14 KiB
PHP

<?php
namespace App\Filament\Resources;
use App\Filament\Resources\CustomerCreditResource\Pages;
use App\Models\CustomerCredit;
use App\Services\CustomerCreditService;
use App\Services\ExchangeRateService;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Tables\Actions\Action;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\Auth;
/**
* اعتبار چندارزی مشتری (بدهی به ارز خارجی + تسویه ریالی).
* - ایجاد: فقط super_admin (canCreate).
* - تسویه: super_admin/admin/staff (اکشن روی ردیف).
* - کیف پول = مرجع؛ تسویه از کیف پول یک WalletTransaction (type=credit_settlement) می‌سازد.
*/
class CustomerCreditResource extends Resource
{
protected static ?string $model = CustomerCredit::class;
protected static ?string $navigationIcon = 'heroicon-o-banknotes';
protected static ?string $navigationGroup = 'مالی';
protected static ?string $navigationLabel = 'اعتبار مشتریان';
protected static ?string $modelLabel = 'اعتبار مشتری';
protected static ?string $pluralModelLabel = 'اعتبار مشتریان';
protected static ?int $navigationSort = 4;
public static function form(Form $form): Form
{
$isCreate = $form->getOperation() === 'create';
return $form->schema([
Forms\Components\Section::make('اطلاعات اعتبار')->schema([
Forms\Components\Select::make('user_id')
->label('مشتری')
->relationship('user', 'name', fn (\Illuminate\Database\Eloquent\Builder $query) => $query->role('customer')->orderBy('name'))
->searchable()
->preload()
->required()
->disabled(! $isCreate),
Forms\Components\TextInput::make('currency_code')
->label('کد ارز')
->placeholder('EUR, USD, AED, CNY')
->required()
->maxLength(10)
->dehydrateStateUsing(fn ($state) => strtoupper(trim($state)))
->disabled(! $isCreate),
Forms\Components\TextInput::make('amount')
->label('مبلغ اعتبار (به ارز خارجی)')
->numeric()
->step(0.01)
->minValue(0.01)
->required()
->disabled(! $isCreate),
Forms\Components\TextInput::make('reference')
->label('مرجع (مثلاً شماره AWB)')
->maxLength(100),
Forms\Components\Select::make('shipment_id')
->label('مرسوله مرتبط (اختیاری)')
->relationship('shipment', 'awb_no')
->searchable()
->preload(),
Forms\Components\Textarea::make('reason')
->label('دلیل/توضیحات')
->rows(3)
->columnSpanFull(),
])->columns(2),
// فقط در حالت view نمایش داده می‌شوند
Forms\Components\Section::make('وضعیت تسویه')
->schema([
Forms\Components\Placeholder::make('settled_amount_display')
->label('تسویه‌شده')
->content(fn ($record) => $record
? number_format((float) $record->settled_amount, 2) . ' ' . $record->currency_code
: '—'),
Forms\Components\Placeholder::make('balance_display')
->label('مانده بدهی')
->content(fn ($record) => $record
? number_format($record->balance, 2) . ' ' . $record->currency_code
: '—'),
Forms\Components\Placeholder::make('status_display')
->label('وضعیت')
->content(fn ($record) => match ($record?->status) {
'open' => 'باز',
'partially_settled' => 'نیمه‌تسویه',
'settled' => 'تسویه‌شده',
default => '—',
}),
Forms\Components\Placeholder::make('granted_by_display')
->label('اعطاکننده')
->content(fn ($record) => $record?->grantedBy?->name ?? '—'),
Forms\Components\Placeholder::make('granted_at_display')
->label('تاریخ اعطا')
->content(fn ($record) => $record?->granted_at?->format('Y/m/d H:i') ?? '—'),
])
->columns(3)
->hidden($isCreate),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('user.name')
->label('مشتری')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('currency_code')
->label('ارز')
->badge()
->color('info'),
Tables\Columns\TextColumn::make('amount')
->label('مبلغ')
->numeric(2)
->sortable(),
Tables\Columns\TextColumn::make('settled_amount')
->label('تسویه‌شده')
->numeric(2)
->color('success'),
Tables\Columns\TextColumn::make('balance')
->label('مانده')
->numeric(2)
->badge()
->color(fn ($record) => $record->balance > 0 ? 'danger' : 'gray'),
Tables\Columns\TextColumn::make('status')
->label('وضعیت')
->badge()
->formatStateUsing(fn (string $state) => match ($state) {
'open' => 'باز',
'partially_settled' => 'نیمه‌تسویه',
'settled' => 'تسویه‌شده',
default => $state,
})
->color(fn (string $state) => match ($state) {
'open' => 'danger',
'partially_settled' => 'warning',
'settled' => 'success',
default => 'gray',
}),
Tables\Columns\TextColumn::make('reference')
->label('مرجع')
->toggleable(),
Tables\Columns\TextColumn::make('grantedBy.name')
->label('اعطاکننده')
->toggleable(),
Tables\Columns\TextColumn::make('granted_at')
->label('تاریخ اعطا')
->dateTime('Y/m/d H:i')
->sortable(),
])
->filters([
Tables\Filters\SelectFilter::make('status')
->label('وضعیت')
->options([
'open' => 'باز',
'partially_settled' => 'نیمه‌تسویه',
'settled' => 'تسویه‌شده',
]),
Tables\Filters\SelectFilter::make('currency_code')
->label('ارز')
->options(
fn () => CustomerCredit::select('currency_code')
->distinct()
->orderBy('currency_code')
->pluck('currency_code', 'currency_code')
->toArray()
),
])
->actions([
Tables\Actions\ViewAction::make(),
Action::make('settle')
->label('ثبت تسویه')
->icon('heroicon-o-banknotes')
->color('success')
->visible(fn ($record) => $record->isOpen()
&& auth()->user()?->hasAnyRole(['super_admin', 'admin', 'staff']))
->form([
Forms\Components\Placeholder::make('balance_info')
->label('مانده بدهی')
->content(fn ($record) => $record
? number_format($record->balance, 2) . ' ' . $record->currency_code
: '—'),
Forms\Components\TextInput::make('amount_foreign')
->label(fn ($record) => "مبلغ تسویه ({$record->currency_code})")
->numeric()
->step(0.01)
->minValue(0.01)
->required()
->helperText(fn ($record) => 'حداکثر: '
. number_format($record->balance, 2) . ' ' . $record->currency_code),
Forms\Components\TextInput::make('exchange_rate')
->label('نرخ روز (ریال به یک واحد ارز)')
->numeric()
->step(0.0001)
->minValue(0.0001)
->required()
->helperText(function ($record) {
$rate = app(ExchangeRateService::class)
->getCurrentRate($record->currency_code, 'IRR');
return $rate > 0
? 'نرخ فعلی: ' . number_format($rate) . ' ریال'
: 'نرخ فعلی ثبت نشده — دستی وارد کنید.';
}),
Forms\Components\DatePicker::make('rate_date')
->label('تاریخ نرخ')
->default(now())
->required(),
Forms\Components\Toggle::make('from_wallet')
->label('کسر از کیف پول (ریال)')
->default(true)
->helperText('اگر خاموش باشد، ثبت به‌عنوان پرداخت خارجی (نقدی/کارت) بدون تأثیر روی کیف پول.'),
Forms\Components\Textarea::make('reason')
->label('دلیل/توضیحات')
->rows(2),
])
->action(function ($record, array $data) {
try {
$settlement = app(CustomerCreditService::class)->settle(
$record,
(float) $data['amount_foreign'],
(float) $data['exchange_rate'],
$data['rate_date'],
(bool) $data['from_wallet'],
Auth::id(),
$data['reason'] ?? null,
);
Notification::make()
->title('تسویه ثبت شد')
->body(number_format($data['amount_foreign'], 2)
. ' ' . $record->currency_code
. ' → ' . number_format($settlement->settlement_amount_irr) . ' ریال')
->success()
->send();
} catch (\Throwable $e) {
Notification::make()
->title('خطا در تسویه')
->body($e->getMessage())
->danger()
->send();
}
}),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make()
->visible(fn () => auth()->user()?->hasRole('super_admin')),
]),
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [];
}
public static function getPages(): array
{
return [
'index' => Pages\ListCustomerCredits::route('/'),
'create' => Pages\CreateCustomerCredit::route('/create'),
'view' => Pages\ViewCustomerCredit::route('/{record}'),
];
}
// ─── Authorization ────────────────────────────────
public static function canViewAny(): bool
{
return auth()->user()?->hasAnyRole(['super_admin', 'admin', 'staff']) ?? false;
}
public static function canCreate(): bool
{
// فقط super_admin می‌تواند اعتبار جدید بدهد
return auth()->user()?->hasRole('super_admin') ?? false;
}
public static function canEdit($record): bool
{
// اعتبار بعد از ایجاد غیرقابل‌ویرایش است (فقط از طریق تسویه)
return false;
}
public static function canDelete($record): bool
{
return auth()->user()?->hasRole('super_admin') ?? false;
}
}