schema([ Forms\Components\Section::make('Route & Status') ->schema([ Forms\Components\TextInput::make('awb_no') ->required() ->unique(ignoreRecord: true) ->label('AWB No.'), Forms\Components\Select::make('direction') ->options([ 'export' => 'Export', 'import' => 'Import' ]) ->required(), Forms\Components\Select::make('type') ->options([ 'DOC_NORMAL' => 'Doc Normal', 'DOC_ECONOMY' => 'Doc Economy', 'PARCEL' => 'Parcel', ]) ->required() ->live(), Forms\Components\Select::make('status') ->options(collect(ShipmentStatus::cases())->mapWithKeys(fn ($case) => [ $case->value => $case->label(), ])) ->default('processed') ->required(), Forms\Components\TextInput::make('reason_for_export') ->nullable() ->label('Reason for Export'), Forms\Components\Select::make('from_country_id') ->relationship('fromCountry', 'name') ->searchable() ->preload() ->label('From Country'), Forms\Components\Select::make('to_country_id') ->relationship('toCountry', 'name') ->searchable() ->preload() ->label('To Country'), Forms\Components\TextInput::make('forwarder') ->nullable() ->label('Forwarder'), ])->columns(3), Forms\Components\Section::make('Weight & Dimensions') ->schema([ Forms\Components\TextInput::make('weight') ->numeric() ->nullable() ->suffix('kg') ->label('Weight'), Forms\Components\TextInput::make('volumetric_weight') ->numeric() ->nullable() ->suffix('kg') ->label('Volumetric Weight'), Forms\Components\TextInput::make('chargeable_weight') ->numeric() ->nullable() ->suffix('kg') ->label('Chargeable Weight'), Forms\Components\TextInput::make('dimensions') ->nullable() ->label('Dimensions (WxLxH)'), Forms\Components\TextInput::make('declared_value') ->numeric() ->nullable() ->prefix('USD') ->label('Declared Value'), Forms\Components\TextInput::make('content_description') ->nullable() ->label('Content Description'), ])->columns(4), Forms\Components\Section::make('Packages') ->description('برای محموله‌های چند بسته‌ای، اطلاعات هر بسته را وارد کنید.') ->schema([ Forms\Components\Repeater::make('packages') ->relationship() ->schema([ Forms\Components\TextInput::make('package_no') ->required() ->numeric() ->minValue(1) ->maxValue(99) ->label('#'), Forms\Components\TextInput::make('weight') ->numeric() ->nullable() ->suffix('kg') ->label('Weight'), Forms\Components\TextInput::make('volumetric_weight') ->numeric() ->nullable() ->suffix('kg') ->label('Volumetric Wt'), Forms\Components\TextInput::make('chargeable_weight') ->numeric() ->nullable() ->suffix('kg') ->label('Chargeable Wt'), Forms\Components\TextInput::make('dimensions') ->nullable() ->label('WxLxH (cm)'), Forms\Components\TextInput::make('declared_value') ->numeric() ->nullable() ->prefix('USD') ->label('Value'), Forms\Components\TextInput::make('content_description') ->nullable() ->label('Content'), ]) ->columns(4) ->defaultItems(1) ->reorderable() ->label('بسته‌ها'), ])->collapsible(), Forms\Components\Section::make('Financial Info') ->schema([ Forms\Components\TextInput::make('shipping_price') ->numeric() ->nullable() ->prefix('درهم') ->label('Shipping Price'), Forms\Components\TextInput::make('extra_service') ->numeric() ->default(0) ->prefix('درهم') ->label('Extra Service'), Forms\Components\TextInput::make('packing_cost') ->numeric() ->default(0) ->prefix('ریال') ->label('Packing Cost'), Forms\Components\TextInput::make('domestic_pickup') ->numeric() ->default(0) ->prefix('ریال') ->label('Domestic Pickup'), Forms\Components\TextInput::make('domestic_delivery') ->numeric() ->default(0) ->prefix('ریال') ->label('Domestic Delivery'), Forms\Components\TextInput::make('warehousing_cost') ->numeric() ->default(0) ->prefix('ریال') ->label('Warehousing Cost'), Forms\Components\TextInput::make('vat_amount') ->numeric() ->default(0) ->prefix('ریال') ->label('VAT Amount'), Forms\Components\TextInput::make('discount') ->numeric() ->default(0) ->prefix('ریال') ->label('Discount'), Forms\Components\TextInput::make('total_fee') ->numeric() ->nullable() ->prefix('ریال') ->label('Total Fee'), Forms\Components\TextInput::make('net_dirham') ->numeric() ->nullable() ->prefix('درهم') ->label('Net Dirham'), Forms\Components\TextInput::make('net_rial') ->numeric() ->nullable() ->prefix('ریال') ->label('Net Rial'), Forms\Components\TextInput::make('cod_amount') ->numeric() ->default(0) ->prefix('AED') ->label('Cash on Delivery'), ])->columns(4), Forms\Components\Section::make('Import Invoice Fields (Service Sales Invoice)') ->schema([ Forms\Components\TextInput::make('brand_fee') ->numeric() ->default(0) ->prefix('ریال') ->label('Brand Fee'), Forms\Components\TextInput::make('report_fee') ->numeric() ->default(0) ->prefix('ریال') ->label('Report Fee'), Forms\Components\TextInput::make('customs_clearance_cost') ->numeric() ->default(0) ->prefix('ریال') ->label('Customs Clearance Cost'), Forms\Components\TextInput::make('order_registration_fee') ->numeric() ->default(0) ->prefix('ریال') ->label('Order Registration Fee'), Forms\Components\TextInput::make('other_clearance_charges') ->numeric() ->default(0) ->prefix('ریال') ->label('Other Clearance Charges'), Forms\Components\TextInput::make('exchange_rate') ->numeric() ->default(0) ->label('Exchange Rate'), Forms\Components\TextInput::make('goods_nature') ->nullable() ->label('Goods Nature'), Forms\Components\Select::make('invoice_currency') ->options([ 'USD' => 'USD', 'EUR' => 'EUR', 'AED' => 'AED', 'IRR' => 'IRR', ]) ->default('USD') ->label('Invoice Currency'), ])->columns(4), Forms\Components\Section::make('Sender Info') ->schema([ Forms\Components\TextInput::make('sender_name') ->nullable() ->label('Name'), Forms\Components\TextInput::make('sender_company') ->nullable() ->label('Company'), Forms\Components\TextInput::make('sender_phone') ->tel() ->nullable() ->label('Phone'), Forms\Components\TextInput::make('sender_email') ->email() ->nullable() ->label('Email'), Forms\Components\TextInput::make('sender_address') ->nullable() ->label('Address'), Forms\Components\TextInput::make('sender_city') ->nullable() ->label('City'), Forms\Components\TextInput::make('sender_state') ->nullable() ->label('State/Province'), Forms\Components\TextInput::make('sender_zip') ->nullable() ->label('ZIP'), Forms\Components\TextInput::make('sender_id_number') ->nullable() ->label('ID Number'), ])->columns(4), Forms\Components\Section::make('Receiver Info') ->schema([ Forms\Components\TextInput::make('receiver_name') ->nullable() ->label('Name'), Forms\Components\TextInput::make('receiver_company') ->nullable() ->label('Company'), Forms\Components\TextInput::make('receiver_phone') ->tel() ->nullable() ->label('Phone'), Forms\Components\TextInput::make('receiver_email') ->email() ->nullable() ->label('Email'), Forms\Components\TextInput::make('receiver_address') ->nullable() ->label('Address'), Forms\Components\TextInput::make('receiver_city') ->nullable() ->label('City'), Forms\Components\TextInput::make('receiver_state') ->nullable() ->label('State/Province'), Forms\Components\TextInput::make('receiver_zip') ->nullable() ->label('ZIP'), Forms\Components\TextInput::make('receiver_id_number') ->nullable() ->label('ID Number'), ])->columns(4), Forms\Components\Section::make('Customs Items') ->schema([ Forms\Components\Repeater::make('items') ->relationship() ->schema([ Forms\Components\TextInput::make('row_number') ->required() ->numeric() ->minValue(1) ->maxValue(9) ->label('Row'), Forms\Components\TextInput::make('description') ->required() ->columnSpan(2) ->label('Description'), Forms\Components\TextInput::make('hs_code') ->required() ->label('HS Code'), Forms\Components\TextInput::make('quantity') ->required() ->numeric() ->label('Qty'), Forms\Components\TextInput::make('unit_price') ->required() ->numeric() ->prefix('USD') ->label('Unit Price'), Forms\Components\TextInput::make('total_usd') ->required() ->numeric() ->prefix('USD') ->label('Total'), ]) ->columns(6) ->maxItems(9) ->defaultItems(1) ->reorderable() ->label('Items'), ]), Forms\Components\Section::make('Customer Notes')->schema([ Forms\Components\Textarea::make('customer_notes')->nullable()->rows(3)->label('Customer Notes'), ]), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('awb_no') ->searchable()->label('AWB No.')->sortable(), Tables\Columns\TextColumn::make('direction') ->badge() ->color(fn (\App\Enums\ShipmentDirection $state): string => match ($state->value) { 'import' => 'success', 'export' => 'info', default => 'gray', }) ->label('Direction'), Tables\Columns\TextColumn::make('type') ->badge()->label('Type'), Tables\Columns\TextColumn::make('status') ->badge() ->color(fn (ShipmentStatus $state): string => $state->color()) ->formatStateUsing(fn (ShipmentStatus $state): string => $state->label()) ->searchable()->label('Status'), Tables\Columns\TextColumn::make('sender_name') ->searchable()->toggleable()->label('Sender'), Tables\Columns\TextColumn::make('receiver_name') ->searchable()->toggleable()->label('Receiver'), Tables\Columns\TextColumn::make('fromCountry.name') ->label('From') ->searchable() ->toggleable() ->sortable(), Tables\Columns\TextColumn::make('toCountry.name') ->label('To') ->searchable() ->toggleable() ->sortable(), Tables\Columns\TextColumn::make('route') ->label('Route') ->state(fn ($record) => ( ($record->fromCountry?->name ?? '?') . ' → ' . ($record->toCountry?->name ?? '?') )) ->searchable() ->toggleable(), Tables\Columns\TextColumn::make('sender_phone') ->label('Sender Phone') ->searchable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('receiver_phone') ->label('Receiver Phone') ->searchable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('cod_amount') ->label('COD (AED)') ->money('AED') ->sortable() ->toggleable(), Tables\Columns\TextColumn::make('total_fee') ->money('IRR')->sortable()->label('Total Fee'), Tables\Columns\TextColumn::make('created_at') ->dateTime()->sortable()->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Tables\Filters\SelectFilter::make('status') ->options(collect(ShipmentStatus::cases())->mapWithKeys(fn ($case) => [ $case->value => $case->label(), ])), Tables\Filters\SelectFilter::make('type') ->options([ 'DOC_NORMAL' => 'Doc Normal', 'DOC_ECONOMY' => 'Doc Economy', 'PARCEL' => 'Parcel', ]), Tables\Filters\SelectFilter::make('direction') ->options([ 'export' => 'Export', 'import' => 'Import' ]), ]) ->actions([ Tables\Actions\ViewAction::make(), Tables\Actions\EditAction::make(), Action::make('approve') ->label('تأیید') ->icon('heroicon-o-check-circle') ->color('success') ->requiresConfirmation() ->modalHeading('تأیید سفارش') ->modalDescription('آیا از تأیید این سفارش اطمینان دارید؟ پس از تأیید، مشتری می‌تواند پرداخت را انجام دهد.') ->modalSubmitActionLabel('بله، تأیید کن') ->visible(fn ($record) => $record->status === \App\Enums\ShipmentStatus::PendingApproval) ->action(function ($record) { $oldStatus = $record->status; $record->update(['status' => \App\Enums\ShipmentStatus::Approved]); \App\Models\ShipmentStatusHistory::create([ 'shipment_id' => $record->id, 'from_status' => $oldStatus->value, 'to_status' => \App\Enums\ShipmentStatus::Approved->value, 'reason' => 'تأیید توسط مدیر از پنل Filament', 'changed_by' => auth()->id(), ]); \Filament\Notifications\Notification::make() ->title('سفارش با موفقیت تأیید شد') ->success() ->send(); }), ]) ->headerActions([ Action::make('exportCsv') ->label('خروجی CSV') ->icon('heroicon-o-arrow-down-tray') ->color('success') ->action(function () { return response()->streamDownload(function () { $csv = fopen('php://output', 'w'); // BOM برای پشتیبانی از فارسی در Excel fwrite($csv, "\xEF\xBB\xBF"); // Header fputcsv($csv, [ 'AWB No', 'Direction', 'Type', 'Status', 'From Country', 'To Country', 'Weight (kg)', 'Chargeable Weight (kg)', 'Shipping Price (AED)', 'Extra Service (AED)', 'Packing Cost (IRR)', 'Total Fee (IRR)', 'Net Rial (IRR)', 'Sender Name', 'Sender Phone', 'Receiver Name', 'Receiver Phone', 'Created At' ]); // Data Shipment::query() ->with(['fromCountry', 'toCountry']) ->orderBy('created_at', 'desc') ->chunk(500, function ($shipments) use ($csv) { foreach ($shipments as $s) { fputcsv($csv, [ $s->awb_no, $s->direction?->value ?? '', $s->type?->value ?? '', $s->status?->label() ?? '', $s->fromCountry?->name ?? '', $s->toCountry?->name ?? '', $s->weight, $s->chargeable_weight, $s->shipping_price, $s->extra_service, $s->packing_cost, $s->total_fee, $s->net_rial, $s->sender_name, $s->sender_phone, $s->receiver_name, $s->receiver_phone, $s->created_at?->format('Y-m-d H:i'), ]); } }); fclose($csv); }, 'shipments_' . now()->format('Y-m-d_H-i') . '.csv', [ 'Content-Type' => 'text/csv; charset=UTF-8', ]); }), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), Action::make('changeStatus') ->label('تغییر وضعیت') ->icon('heroicon-o-arrow-path') ->color('warning') ->form([ Forms\Components\Select::make('new_status') ->label('وضعیت جدید') ->required() ->options(collect(ShipmentStatus::cases())->mapWithKeys(fn ($case) => [ $case->value => $case->label(), ])), Forms\Components\Textarea::make('reason') ->label('دلیل تغییر') ->rows(2) ->maxLength(500), ]) ->action(function (array $data, \Illuminate\Support\Collection $records) { foreach ($records as $shipment) { \App\Models\ShipmentStatusHistory::create([ 'shipment_id' => $shipment->id, 'from_status' => $shipment->status?->value, 'to_status' => $data['new_status'], 'reason' => $data['reason'] ?? null, 'changed_by' => auth()->id(), ]); $shipment->update(['status' => $data['new_status']]); } \Filament\Notifications\Notification::make() ->title(count($records) . ' محموله بروزرسانی شد') ->success() ->send(); }) ->deselectRecordsAfterCompletion(), ]), ]); } public static function afterSave(\Filament\Resources\Pages\Page $page, \Illuminate\Database\Eloquent\Model $record): void { if ($page instanceof \Filament\Resources\Pages\EditRecord) { ShipmentUpdatedNotification::notify($record); } } public static function getRelations(): array { return [ RelationManagers\CarrierMappingsRelationManager::class, RelationManagers\TrackingEventsRelationManager::class, ]; } public static function getPages(): array { return [ 'index' => Pages\ListShipments::route('/'), 'create' => Pages\CreateShipment::route('/create'), 'view' => Pages\ViewShipment::route('/{record}'), 'edit' => Pages\EditShipment::route('/{record}/edit'), ]; } }