where('source', Shipment::SOURCE_CREATED) ->whereNotNull('order_id') ->whereNull('manifest_id') ->whereNull('cancelled_at'); } public static function table(Table $table): Table { return $table ->defaultSort('created_at', 'desc') ->columns([ TextColumn::make('carrier') ->badge() ->formatStateUsing(fn (Shipment $record) => $record->carrierLabel()), TextColumn::make('tracking_reference') ->label('Tracking #') ->placeholder('Not issued yet') ->copyable(), TextColumn::make('order.reference')->label('Order'), TextColumn::make('created_at')->label('Created')->dateTime(), TextColumn::make('label_printed_at')->label('Printed')->dateTime()->placeholder('Not printed'), ]) ->recordActions([ Action::make('print') ->label('Print') ->icon('heroicon-o-printer') ->url(fn (Shipment $record) => self::labelUrl($record), shouldOpenInNewTab: true), Action::make('cancel') ->label('Cancel') ->icon('heroicon-o-x-circle') ->color('danger') ->requiresConfirmation() ->modalDescription('Cancels this shipment with the carrier. This cannot be undone.') ->action(fn (Shipment $record) => self::cancelShipment($record)), ]) ->toolbarActions([ BulkAction::make('print_selected') ->label('Print selected') ->icon('heroicon-o-printer') ->deselectRecordsAfterCompletion() ->action(fn (Collection $records, $livewire) => self::printSelected($records, $livewire)), BulkAction::make('issue_manifest') ->label('Issue Manifest') ->icon('heroicon-o-check-circle') ->visible(fn ($livewire) => filled($livewire->activeTab ?? null) && self::fulfillmentService($livewire->activeTab) instanceof SupportsManifestBatching) ->action(fn (Collection $records) => self::issueManifest($records)), ]); } public static function labelUrl(Shipment $shipment): string { return URL::temporarySignedRoute('shipments.label', now()->addMinutes(5), ['shipment' => $shipment->id]); } /** * One combined PDF for carriers that support it (opened in a new tab, * with a link in the notification in case the browser blocks the * pop-up); anything else has to be printed row by row. */ public static function printSelected(Collection $shipments, $livewire): void { $carriers = $shipments->pluck('carrier')->unique(); if ($carriers->count() !== 1) { Notification::make() ->title('Select shipments of a single carrier to print them together.') ->warning() ->send(); return; } $shipment = $shipments->first(); if (! self::fulfillmentService($shipment->carrier) instanceof SupportsBatchLabels) { Notification::make() ->title("{$shipment->carrierLabel()} labels can't be combined — print them individually.") ->warning() ->send(); return; } $url = URL::temporarySignedRoute('shipments.labels', now()->addMinutes(5), [ 'ids' => $shipments->pluck('id')->sort()->implode(','), ]); $livewire->js('window.open('.json_encode($url).', "_blank")'); Notification::make() ->title('Printing '.$shipments->count().' labels.') ->success() ->actions([ Action::make('open')->label('Open PDF')->url($url, shouldOpenInNewTab: true), ]) ->send(); } public static function cancelShipment(Shipment $shipment): void { if ($shipment->cancelsLocallyOnly()) { $shipment->update(['cancelled_at' => now()]); Notification::make() ->title('Shipment marked as cancelled. Void the voucher with the courier as well.') ->success() ->send(); return; } $service = self::fulfillmentService($shipment->carrier); try { if (! $service) { throw new \RuntimeException("No fulfillment integration configured for {$shipment->carrierLabel()}."); } $service->cancelShipment($shipment); } catch (Throwable $e) { report($e); Notification::make() ->title('Failed to cancel shipment: '.$e->getMessage()) ->danger() ->send(); return; } Notification::make() ->title('Shipment cancelled.') ->success() ->send(); } public static function issueManifest(Collection $shipments): void { $shipments->groupBy('carrier')->each(function (Collection $group, string $carrier) { $service = self::fulfillmentService($carrier); if (! $service instanceof SupportsManifestBatching) { return; } try { $result = $service->issueManifest($group); } catch (Throwable $e) { report($e); Notification::make() ->title("Failed to issue manifest for {$carrier}: {$e->getMessage()}") ->danger() ->send(); return; } if (! $result->success) { Notification::make() ->title("Manifest blocked for {$carrier}: {$result->reason}") ->danger() ->send(); return; } Notification::make() ->title("Manifest issued for {$carrier}: {$result->reference}") ->success() ->send(); }); } public static function fulfillmentService(string $carrier): ?CarrierFulfillmentInterface { return app(CarrierFulfillmentInterface::class, ['carrier' => $carrier]); } public static function getPages(): array { return [ 'index' => ListShipments::route('/'), ]; } }