Introduce a new currency management module including a dedicated resource, database migration, and a custom Filament page for importing exchange rates. Additionally, enhance the ShipmentResource table with improved column visibility, sorting, and route display, and integrate shipment update notifications.
463 lines
22 KiB
PHP
463 lines
22 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources;
|
|
|
|
use App\Enums\ShipmentStatus;
|
|
use App\Filament\Resources\ShipmentResource\Pages;
|
|
use App\Filament\Resources\ShipmentResource\RelationManagers;
|
|
use App\Models\Shipment;
|
|
use Filament\Forms;
|
|
use Filament\Forms\Form;
|
|
use Filament\Resources\Resource;
|
|
use Filament\Tables;
|
|
use Filament\Tables\Table;
|
|
use Filament\Tables\Actions\Action;
|
|
use App\Notifications\ShipmentUpdatedNotification;
|
|
|
|
class ShipmentResource extends Resource
|
|
{
|
|
protected static ?string $model = Shipment::class;
|
|
|
|
protected static ?string $navigationIcon = 'heroicon-o-truck';
|
|
|
|
protected static ?string $navigationLabel = 'مرسولات';
|
|
protected static ?string $navigationGroup = 'عملیات';
|
|
protected static ?int $navigationSort = 1;
|
|
|
|
public static function form(Form $form): Form
|
|
{
|
|
return $form
|
|
->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('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('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(),
|
|
])
|
|
->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(),
|
|
]),
|
|
]);
|
|
}
|
|
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'),
|
|
];
|
|
}
|
|
} |