- 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
322 lines
14 KiB
PHP
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;
|
|
}
|
|
} |