2026-07-19 00:52:01 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Modules\Core\Shipping\Extensions;
|
|
|
|
|
|
2026-08-31 13:16:13 +03:00
|
|
|
use Filament\Actions\Action;
|
|
|
|
|
use Filament\Forms\Components\TextInput;
|
|
|
|
|
use Filament\Forms\Components\Toggle;
|
|
|
|
|
use Closure;
|
|
|
|
|
use Throwable;
|
2026-07-19 00:52:01 +03:00
|
|
|
use Filament\Actions;
|
|
|
|
|
use Filament\Forms;
|
|
|
|
|
use Filament\Notifications\Notification;
|
|
|
|
|
use Lunar\Admin\Support\Extending\ViewPageExtension;
|
|
|
|
|
use Lunar\Models\Order;
|
|
|
|
|
use Lunar\Shipping\Models\ShippingMethod;
|
|
|
|
|
use Modules\Core\Shipping\Contracts\CarrierFulfillmentInterface;
|
2026-09-03 15:58:51 +03:00
|
|
|
use Modules\Core\Shipping\DTOs\ShipmentRequest;
|
2026-07-19 00:52:01 +03:00
|
|
|
|
|
|
|
|
class OrderViewExtension extends ViewPageExtension
|
|
|
|
|
{
|
|
|
|
|
public function headerActions(array $actions): array
|
|
|
|
|
{
|
|
|
|
|
$actions[] = $this->createShipmentAction();
|
2026-09-10 22:50:40 +03:00
|
|
|
$actions[] = $this->markPickedUpAction();
|
2026-07-19 00:52:01 +03:00
|
|
|
|
|
|
|
|
return $actions;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 13:16:13 +03:00
|
|
|
private function createShipmentAction(): Action
|
2026-07-19 00:52:01 +03:00
|
|
|
{
|
2026-08-31 13:16:13 +03:00
|
|
|
return Action::make('create_shipment')
|
2026-07-19 00:52:01 +03:00
|
|
|
->label('Create Shipment')
|
|
|
|
|
->icon('heroicon-o-truck')
|
|
|
|
|
->modalSubmitActionLabel('Create Shipment')
|
2026-08-31 13:16:13 +03:00
|
|
|
->schema([
|
|
|
|
|
TextInput::make('weight')
|
2026-07-19 18:27:24 +03:00
|
|
|
->label('Package weight (kg)')
|
|
|
|
|
->numeric()
|
|
|
|
|
->minValue(0)
|
|
|
|
|
->helperText('Leave blank to use the carrier\'s default.'),
|
2026-08-31 13:16:13 +03:00
|
|
|
TextInput::make('destination_location_id')
|
2026-07-19 18:27:24 +03:00
|
|
|
->label('Box Now locker ID')
|
|
|
|
|
->helperText('Only required for Box Now shipments.')
|
|
|
|
|
->default(fn (Order $record) => $record->shippingAddress?->meta['box_now_locker']['locationId'] ?? null),
|
2026-08-31 13:16:13 +03:00
|
|
|
Toggle::make('confirm')
|
2026-07-19 00:52:01 +03:00
|
|
|
->label('Confirm')
|
|
|
|
|
->helperText('This will create a real shipment with the carrier.')
|
|
|
|
|
->rules([
|
|
|
|
|
function () {
|
2026-08-31 13:16:13 +03:00
|
|
|
return function (string $attribute, $value, Closure $fail) {
|
2026-07-19 00:52:01 +03:00
|
|
|
if ($value !== true) {
|
|
|
|
|
$fail('Please confirm before creating the shipment.');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
]),
|
|
|
|
|
])
|
2026-08-31 13:16:13 +03:00
|
|
|
->action(function (Order $record, array $data, Action $action) {
|
2026-07-19 00:52:01 +03:00
|
|
|
$service = $this->resolveFulfillmentService($record);
|
|
|
|
|
|
|
|
|
|
if (! $service) {
|
|
|
|
|
Notification::make()
|
|
|
|
|
->title('No carrier fulfillment integration is configured for this order.')
|
|
|
|
|
->danger()
|
|
|
|
|
->send();
|
|
|
|
|
|
|
|
|
|
$action->halt();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 18:27:24 +03:00
|
|
|
$request = new ShipmentRequest(
|
|
|
|
|
weight: filled($data['weight'] ?? null) ? (float) $data['weight'] : null,
|
|
|
|
|
destinationLocationId: $data['destination_location_id'] ?? null,
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-19 00:52:01 +03:00
|
|
|
try {
|
2026-07-19 18:27:24 +03:00
|
|
|
$service->createShipment($record, $request);
|
2026-08-31 13:16:13 +03:00
|
|
|
} catch (Throwable $e) {
|
2026-07-19 00:52:01 +03:00
|
|
|
report($e);
|
|
|
|
|
|
|
|
|
|
Notification::make()
|
|
|
|
|
->title('Failed to create shipment: '.$e->getMessage())
|
|
|
|
|
->danger()
|
|
|
|
|
->send();
|
|
|
|
|
|
|
|
|
|
$action->halt();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Notification::make()
|
|
|
|
|
->title('Shipment created.')
|
|
|
|
|
->success()
|
|
|
|
|
->send();
|
|
|
|
|
})
|
2026-09-10 22:50:40 +03:00
|
|
|
->visible(fn (Order $record) => $record->status === 'ready-for-dispatch'
|
|
|
|
|
&& ! $record->isStorePickupOrder()
|
|
|
|
|
&& $record->shipments()->exists() === false
|
2026-07-19 00:52:01 +03:00
|
|
|
&& $this->resolveFulfillmentService($record) !== null);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-10 22:50:40 +03:00
|
|
|
/**
|
|
|
|
|
* The store-pickup mirror of createShipmentAction() — a store-pickup
|
|
|
|
|
* order never gets a Shipment record (no carrier is ever involved), so
|
|
|
|
|
* it needs its own way to close out of 'ready-for-pickup' once the
|
|
|
|
|
* customer has actually collected it. Sets status directly to
|
|
|
|
|
* 'completed', same terminal status DeriveOrderDeliveredFromShipment
|
|
|
|
|
* writes for a carrier order once tracking confirms delivery — see
|
|
|
|
|
* that listener's own docblock.
|
|
|
|
|
*/
|
|
|
|
|
private function markPickedUpAction(): Action
|
|
|
|
|
{
|
|
|
|
|
return Action::make('mark_picked_up')
|
|
|
|
|
->label('Mark Picked Up')
|
|
|
|
|
->icon('heroicon-o-check-circle')
|
|
|
|
|
->color('success')
|
|
|
|
|
->requiresConfirmation()
|
|
|
|
|
->modalDescription('Confirms the customer has collected this order in store.')
|
|
|
|
|
->action(function (Order $record) {
|
|
|
|
|
$record->update(['status' => 'completed']);
|
|
|
|
|
|
|
|
|
|
Notification::make()
|
|
|
|
|
->title('Order marked as picked up.')
|
|
|
|
|
->success()
|
|
|
|
|
->send();
|
|
|
|
|
})
|
|
|
|
|
->visible(fn (Order $record) => $record->status === 'ready-for-pickup'
|
|
|
|
|
&& $record->isStorePickupOrder());
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 00:52:01 +03:00
|
|
|
private function resolveCarrier(Order $record): ?string
|
|
|
|
|
{
|
|
|
|
|
$code = $record->shippingAddress?->shipping_option;
|
|
|
|
|
|
|
|
|
|
if (! $code) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ShippingMethod::where('code', $code)->value('driver');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveFulfillmentService(Order $record): ?CarrierFulfillmentInterface
|
|
|
|
|
{
|
|
|
|
|
$carrier = $this->resolveCarrier($record);
|
|
|
|
|
|
|
|
|
|
if (! $carrier) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return app(CarrierFulfillmentInterface::class, ['carrier' => $carrier]);
|
|
|
|
|
}
|
|
|
|
|
}
|