Feat: Shipping functionalities redesign, elta courier integration, commenting out acs since no integration can happen, flow updates
This commit is contained in:
@@ -12,6 +12,8 @@ use Modules\Core\Payment\DTOs\PaymentResult;
|
||||
use Modules\Core\Payment\Enums\PaymentResultStatus;
|
||||
use Modules\Core\Shipping\Contracts\CarrierFulfillmentInterface;
|
||||
use Modules\Core\Shipping\DTOs\ShipmentRequest;
|
||||
use Modules\Core\Shipping\Enums\ExtraService;
|
||||
use Modules\Core\Shipping\Models\Shipment;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -56,7 +58,15 @@ class OrderFulfillmentService
|
||||
return OrderFulfillmentResult::success('Order marked ready.');
|
||||
}
|
||||
|
||||
public function createShipmentAndDispatch(Order $order, ShipmentRequest $request): OrderFulfillmentResult
|
||||
/**
|
||||
* Creates the shipment only — the order stays at ready_for_dispatch
|
||||
* until the carrier actually picks the parcel up. That move (and the
|
||||
* "on its way" email) happens in Modules\Core\Order\Listeners\
|
||||
* AdvanceFulfillmentOnCarrierCheckpoint, on the first InTransit /
|
||||
* CollectedFromSender checkpoint — synced from the carrier, or entered
|
||||
* by hand for a manual carrier.
|
||||
*/
|
||||
public function createShipment(Order $order, ShipmentRequest $request): OrderFulfillmentResult
|
||||
{
|
||||
if ($order->status !== 'ready_for_dispatch') {
|
||||
return OrderFulfillmentResult::failure('This order is not ready to be dispatched.');
|
||||
@@ -76,9 +86,48 @@ class OrderFulfillmentService
|
||||
return OrderFulfillmentResult::failure('Failed to create shipment: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$this->writer->write($order, 'dispatched', self::class.'::createShipmentAndDispatch');
|
||||
return OrderFulfillmentResult::success('Shipment created. The order moves to Dispatched when the carrier picks it up.');
|
||||
}
|
||||
|
||||
return OrderFulfillmentResult::success('Shipment created and order dispatched.');
|
||||
/**
|
||||
* Records an integrated carrier's voucher that wasn't created through
|
||||
* our API — the carrier's system was down and a pre-numbered paper
|
||||
* voucher was used, or the courier wrote his own at pickup. It's still
|
||||
* that carrier's voucher, so PollShipmentTrackingJob picks up its
|
||||
* history once the carrier's API has it.
|
||||
*
|
||||
* @param array<int, ExtraService> $services
|
||||
*/
|
||||
public function addManualVoucher(Order $order, string $carrier, string $voucherNumber, array $services = []): OrderFulfillmentResult
|
||||
{
|
||||
if (! $this->canAddManualVoucher($order)) {
|
||||
return OrderFulfillmentResult::failure('A voucher can only be added from Ready for Dispatch until the order is delivered.');
|
||||
}
|
||||
|
||||
$voucherNumber = trim($voucherNumber);
|
||||
|
||||
if (Shipment::where('tracking_reference', $voucherNumber)->exists()) {
|
||||
return OrderFulfillmentResult::failure("Voucher {$voucherNumber} is already recorded.");
|
||||
}
|
||||
|
||||
Shipment::create([
|
||||
'order_id' => $order->id,
|
||||
'carrier' => $carrier,
|
||||
'source' => Shipment::SOURCE_MANUAL_VOUCHER,
|
||||
'tracking_reference' => $voucherNumber,
|
||||
'meta' => [
|
||||
'services' => array_map(fn (ExtraService $service) => $service->value, $services),
|
||||
'cod_amount' => $this->flow->isCod($order) ? $order->total->decimal : null,
|
||||
],
|
||||
]);
|
||||
|
||||
return OrderFulfillmentResult::success("Voucher {$voucherNumber} added.");
|
||||
}
|
||||
|
||||
public function canAddManualVoucher(Order $order): bool
|
||||
{
|
||||
return in_array($order->status, ['ready_for_dispatch', 'dispatched', 'delivery_failed'], true)
|
||||
&& ! $order->isStorePickupOrder();
|
||||
}
|
||||
|
||||
public function markPickedUp(Order $order): OrderFulfillmentResult
|
||||
@@ -175,14 +224,28 @@ class OrderFulfillmentService
|
||||
return OrderFulfillmentResult::success('Order marked as paid.');
|
||||
}
|
||||
|
||||
/**
|
||||
* A cancelled shipment doesn't block creating a new one.
|
||||
*/
|
||||
public function canCreateShipment(Order $order): bool
|
||||
{
|
||||
return $order->status === 'ready_for_dispatch'
|
||||
&& ! $order->isStorePickupOrder()
|
||||
&& $order->shipments()->exists() === false
|
||||
&& $order->shipments()->whereNull('cancelled_at')->doesntExist()
|
||||
&& $this->resolveFulfillmentService($order) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The order's shipping method — manual carriers keep their name,
|
||||
* tracking URL template and cash-on-delivery setting in its `data`.
|
||||
*/
|
||||
public function shippingMethodFor(Order $order): ?ShippingMethod
|
||||
{
|
||||
$code = $order->shippingAddress?->shipping_option;
|
||||
|
||||
return $code ? ShippingMethod::where('code', $code)->first() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public wrapper around resolveCarrier() — Modules\Core\Shipping\
|
||||
* Extensions\OrderViewExtension needs to know which carrier an order
|
||||
|
||||
Reference in New Issue
Block a user