AsArrayObject::class, 'label_printed_at' => 'datetime', 'cancelled_at' => 'datetime', ]; public function order(): BelongsTo { return $this->belongsTo(Order::class); } public function manifest(): BelongsTo { return $this->belongsTo(Manifest::class); } public function shipmentInfo(): HasMany { return $this->hasMany(ShipmentInfo::class); } public function latestShipmentInfo(): ?ShipmentInfo { return $this->shipmentInfo()->latest('occurred_at')->first(); } /** * The carrier's display name: a manual carrier's own name (snapshotted * from its shipping method), otherwise the shipping driver's name(). */ public function carrierLabel(): string { if (filled($this->meta['carrier_name'] ?? null)) { return $this->meta['carrier_name']; } $driver = collect(Shipping::getSupportedDrivers())->get($this->carrier); return $driver?->name() ?? ucwords(str_replace('-', ' ', $this->carrier)); } /** * A link to the carrier's own tracking page — manual carriers only. * Integrated carriers' history is synced, so customers follow it on * our own order page instead. */ public function trackingUrl(): ?string { $template = $this->meta['tracking_url'] ?? null; if ($this->source !== self::SOURCE_MANUAL || blank($template) || blank($this->tracking_reference)) { return null; } return str_replace('{number}', rawurlencode($this->tracking_reference), $template); } public function isCancelled(): bool { return $this->cancelled_at !== null; } /** * A return voucher (coming back to us), from a carrier's list. Its * checkpoints must never drive the order's own status. */ public function isReturn(): bool { return (bool) ($this->meta['is_return'] ?? false); } /** * Whether this shipment's carrier checkpoints should move its order's * status (dispatched / delivered / delivery failed). */ public function drivesOrderStatus(): bool { return $this->order_id !== null && ! $this->isReturn() && ! $this->isCancelled(); } /** * Only shipments created through the carrier's API have a label to * print — manual carriers, typed-in vouchers and synced ones don't. */ public function hasCarrierLabel(): bool { return $this->source === self::SOURCE_CREATED && ! $this->isCancelled(); } /** * Cancelling a manual carrier's shipment or a typed-in voucher is a * local record change only — there's nothing to cancel at the carrier * through the API. */ public function cancelsLocallyOnly(): bool { return in_array($this->source, [self::SOURCE_MANUAL, self::SOURCE_MANUAL_VOUCHER, self::SOURCE_SYNCED], true); } }