Fix: Updates to OrderFullfilmentServices and box now clients, order views and checkout services

This commit is contained in:
2026-09-18 00:54:43 +03:00
parent dcdc998eee
commit 12aaa43f10
15 changed files with 450 additions and 18 deletions
@@ -5,6 +5,8 @@ namespace Modules\Core\Shipping\Extensions;
use Filament\Actions\Action;
use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\TextEntry;
use Illuminate\Support\Collection;
use Modules\Core\Shipping\Models\ShipmentInfo;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Section;
use Illuminate\Support\Facades\URL;
@@ -112,10 +114,34 @@ class OrderShipmentsExtension extends ViewPageExtension
->action(fn (Shipment $record) => $this->cancel($record))
->visible(fn (Shipment $record) => ! $record->cancelled_at),
]),
RepeatableEntry::make('shipmentInfo')
->label('Tracking history')
->state(fn (Shipment $record) => $this->orderedCheckpoints($record))
->hidden(fn (Shipment $record) => $record->shipmentInfo->isEmpty())
->schema([
TextEntry::make('status')
->label(fn (ShipmentInfo $record) => $record->occurred_at->format('Y-m-d H:i'))
->inlineLabel()
->state(fn (ShipmentInfo $record) => (string) str($record->status->value)->replace('_', ' ')->title())
->helperText(fn (ShipmentInfo $record) => $record->location),
]),
]),
]);
}
/**
* Oldest first — a delivery journey (Collected → In Transit →
* Delivered) reads naturally top-to-bottom in that order, unlike
* statusLabel()/statusColor() above which only ever need the single
* latest checkpoint and so use latestShipmentInfo() directly instead.
*
* @return Collection<int, ShipmentInfo>
*/
private function orderedCheckpoints(Shipment $record): Collection
{
return $record->shipmentInfo->sortBy('occurred_at')->values();
}
private function carrierLabel(Shipment $record): string
{
return match ($record->carrier) {
+23 -12
View File
@@ -56,10 +56,11 @@ use Modules\Core\Shipping\Support\WeightCalculator;
* each with its own S/M/L size) instead — see
* Modules\Core\Shipping\Carriers\BoxNow\BoxNowFulfillmentService for how
* multiple boxes become multiple Shipment rows from one delivery request.
* Box Now's locker is locked to read-only once the shopper's own checkout
* selection ($order->shippingAddress->meta['box_now_locker']) is present
* — staff can only fill it in manually for the (current, checkout-UI-less)
* case where nothing set it yet.
* Box Now's locker field defaults from the shopper's own checkout
* selection ($order->shippingAddress->meta['box_now_locker']) when present,
* but stays editable — staff can override to a different locker (e.g. the
* customer's choice turns out to be unavailable) or fill it in manually for
* an order placed before the checkout locker picker existed.
*
* "Mark Paid" is a third, separate header action — Order::paid is
* independent of `status` (see OrderStatusFlow's own docblock), so it
@@ -124,16 +125,9 @@ class OrderViewExtension extends ViewPageExtension
TextInput::make('destination_location_id')
->label('Box Now locker ID')
->default($lockerId)
// Locked once the shopper's own checkout selection is
// known — staff should not be able to redirect a
// parcel to a different locker than the one the
// customer picked. Only editable for the (current,
// checkout-UI-less) case where nothing set it yet.
->disabled(filled($lockerId))
->dehydrated()
->required()
->helperText($lockerId
? 'Set by the customer at checkout.'
? 'Set by the customer at checkout — override if the parcel needs to go to a different locker.'
: 'No locker was selected at checkout — enter it manually.'),
Repeater::make('boxes')
->label('Boxes')
@@ -152,12 +146,29 @@ class OrderViewExtension extends ViewPageExtension
];
})
->action(function (Order $record, array $data, Action $action) {
// Derived from the order itself, never from staff input —
// whether a shipment collects cash on delivery is a fact
// about the order (which PaymentMethod it was placed
// against), not a choice to make again at dispatch time.
// Previously this was never set at all (defaulted to
// ShipmentRequest::$paymentMode's own null), which silently
// made AcsFulfillmentService::createShipment()'s
// `$request->paymentMode === 'cod'` branch (sends
// Cod_Ammount/Cod_Payment_Way to ACS) permanently
// unreachable, and BoxNowFulfillmentService::createShipment()
// always ship 'prepaid' with amountToBeCollected '0.00' —
// a real COD order would arrive with the carrier believing
// full payment was already settled, and never collect it.
$isCod = app(OrderStatusFlow::class)->isCod($record);
$result = $this->service()->createShipmentAndDispatch(
$record,
new ShipmentRequest(
weight: filled($data['weight'] ?? null) ? (float) $data['weight'] : null,
packageCount: (int) ($data['package_count'] ?? 1),
destinationLocationId: $data['destination_location_id'] ?? null,
paymentMode: $isCod ? 'cod' : 'prepaid',
amountToCollect: $isCod ? $record->total->decimal : null,
boxes: collect($data['boxes'] ?? [])->pluck('size')->all(),
),
);