Feature: Wire up carrier-agnostic shipping admin UI

Registers ShippingServiceProvider (carrier config, driver/fulfillment bindings, Rates page override) and wires the shipping admin surface into CorePlugin: dynamic carrier dropdown on Shipping Method create/edit, a "Create Shipment" order action resolved generically by carrier, and a Pickup Manifests page for carriers that support manifest batching.
This commit is contained in:
2026-07-19 00:52:01 +03:00
parent 37c2e1194c
commit 4acabe4185
8 changed files with 452 additions and 2 deletions
@@ -0,0 +1,106 @@
<?php
namespace Modules\Core\Shipping\Filament\Pages;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Lunar\Shipping\Facades\Shipping;
use Modules\Core\Shipping\Contracts\CarrierFulfillmentInterface;
use Modules\Core\Shipping\Contracts\SupportsManifestBatching;
use Modules\Core\Shipping\Models\Shipment;
class ManagePickupManifests extends Page implements HasTable
{
use InteractsWithTable;
protected static ?string $navigationIcon = 'heroicon-o-truck';
protected static ?string $navigationLabel = 'Pickup Manifests';
protected static string $view = 'core::shipping.filament.pages.manage-pickup-manifests';
public function table(Table $table): Table
{
return $table
->query($this->pendingQuery())
->columns([
TextColumn::make('carrier')->badge(),
TextColumn::make('tracking_reference')->label('Tracking #'),
TextColumn::make('order.reference')->label('Order'),
TextColumn::make('label_printed_at')->label('Printed')->dateTime()->placeholder('Not printed'),
])
->actions([
Action::make('print')
->label('Print')
->icon('heroicon-o-printer')
->action(fn (Shipment $record) => $this->printShipment($record)),
])
->bulkActions([
BulkAction::make('print_selected')
->label('Print selected')
->icon('heroicon-o-printer')
->action(fn (Collection $records) => $records->each(fn (Shipment $shipment) => $this->printShipment($shipment))),
BulkAction::make('issue_manifest')
->label('Issue Manifest')
->icon('heroicon-o-check-circle')
->action(fn (Collection $records) => $this->issueManifest($records)),
]);
}
private function pendingQuery(): Builder
{
$carriers = collect(Shipping::getSupportedDrivers())->keys()->filter(
fn (string $carrier) => $this->fulfillmentService($carrier) instanceof SupportsManifestBatching
);
return Shipment::query()
->whereIn('carrier', $carriers)
->whereNull('manifest_reference')
->whereNull('cancelled_at');
}
private function printShipment(Shipment $shipment): void
{
$this->fulfillmentService($shipment->carrier)?->printLabel($shipment);
}
private function issueManifest(Collection $shipments): void
{
$shipments->groupBy('carrier')->each(function (Collection $group, string $carrier) {
$service = $this->fulfillmentService($carrier);
if (! $service instanceof SupportsManifestBatching) {
return;
}
$result = $service->issueManifest($group);
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();
});
}
private function fulfillmentService(string $carrier): ?CarrierFulfillmentInterface
{
return app(CarrierFulfillmentInterface::class, ['carrier' => $carrier]);
}
}