407 lines
No EOL
15 KiB
PHP
407 lines
No EOL
15 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
/**
|
|
* DHL Shipment Model
|
|
*
|
|
* Represents a DHL shipment for a shopping order, including both outbound and return shipments.
|
|
*
|
|
* @property int $id
|
|
* @property int $shopping_order_id
|
|
* @property string|null $shipment_number DHL shipment number
|
|
* @property string|null $tracking_number DHL tracking number
|
|
* @property string $type Type: 'outbound' or 'return'
|
|
* @property int|null $related_shipment_id For returns: reference to original shipment
|
|
* @property float $weight Package weight in kg
|
|
* @property int|null $length Package length in cm
|
|
* @property int|null $width Package width in cm
|
|
* @property int|null $height Package height in cm
|
|
* @property string $product_code DHL product code (e.g., V01PAK)
|
|
* @property array|null $services Additional DHL services
|
|
* @property string|null $label_path Path to generated label file
|
|
* @property string $label_format Label format (PDF or ZPL)
|
|
* @property bool $label_printed Whether label has been printed
|
|
* @property string $status Shipment status
|
|
* @property string|null $tracking_status Current tracking status from DHL
|
|
* @property string|null $tracking_details Detailed tracking information (JSON)
|
|
* @property Carbon|null $last_tracked_at Last tracking update
|
|
* @property string $recipient_name Recipient name
|
|
* @property string|null $recipient_company Recipient company
|
|
* @property string $recipient_street Recipient street
|
|
* @property string $recipient_street_number Recipient street number
|
|
* @property string $recipient_postal_code Recipient postal code
|
|
* @property string $recipient_city Recipient city
|
|
* @property string|null $recipient_state Recipient state
|
|
* @property string $recipient_country Recipient country code
|
|
* @property string|null $recipient_email Recipient email
|
|
* @property string|null $recipient_phone Recipient phone
|
|
* @property array|null $api_request_data API request data for debugging
|
|
* @property array|null $api_response_data API response data for debugging
|
|
* @property string|null $api_errors API error messages
|
|
* @property float|null $shipping_cost Shipping cost
|
|
* @property string $currency Currency code
|
|
* @property string|null $notes Internal notes
|
|
* @property array|null $metadata Additional metadata
|
|
* @property Carbon|null $shipped_at When the package was shipped
|
|
* @property Carbon|null $delivered_at When the package was delivered
|
|
* @property Carbon|null $created_at
|
|
* @property Carbon|null $updated_at
|
|
* @property-read ShoppingOrder $shoppingOrder
|
|
* @property-read DhlShipment|null $relatedShipment
|
|
* @property-read DhlShipment|null $returnShipment
|
|
* @property-read string|null $dimensions
|
|
* @property-read string|null $label_url
|
|
* @property-read string $recipient_address
|
|
* @property-read string $status_label
|
|
* @property-read string $type_label
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment active()
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment newModelQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment newQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment outbound()
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment query()
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment returns()
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment trackable()
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereApiErrors($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereApiRequestData($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereApiResponseData($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereCreatedAt($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereCurrency($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereDeliveredAt($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereHeight($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLabelFormat($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLabelPath($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLabelPrinted($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLastTrackedAt($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLength($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereMetadata($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereNotes($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereProductCode($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientCity($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientCompany($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientCountry($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientEmail($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientName($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientPhone($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientPostalCode($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientState($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientStreet($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientStreetNumber($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRelatedShipmentId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereServices($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShipmentNumber($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShippedAt($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShippingCost($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShoppingOrderId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereStatus($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereTrackingDetails($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereTrackingNumber($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereTrackingStatus($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereType($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereUpdatedAt($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereWeight($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereWidth($value)
|
|
* @mixin \Eloquent
|
|
*/
|
|
class DhlShipment extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
/**
|
|
* The table associated with the model.
|
|
*/
|
|
protected $table = 'dhl_shipments';
|
|
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*/
|
|
protected $fillable = [
|
|
'shopping_order_id',
|
|
'shipment_number',
|
|
'tracking_number',
|
|
'type',
|
|
'related_shipment_id',
|
|
'weight',
|
|
'length',
|
|
'width',
|
|
'height',
|
|
'product_code',
|
|
'services',
|
|
'label_path',
|
|
'label_format',
|
|
'label_printed',
|
|
'status',
|
|
'tracking_status',
|
|
'tracking_details',
|
|
'last_tracked_at',
|
|
'recipient_name',
|
|
'recipient_company',
|
|
'recipient_street',
|
|
'recipient_street_number',
|
|
'recipient_postal_code',
|
|
'recipient_city',
|
|
'recipient_state',
|
|
'recipient_country',
|
|
'recipient_email',
|
|
'recipient_phone',
|
|
'api_request_data',
|
|
'api_response_data',
|
|
'api_errors',
|
|
'shipping_cost',
|
|
'currency',
|
|
'notes',
|
|
'metadata',
|
|
'shipped_at',
|
|
'delivered_at',
|
|
];
|
|
|
|
/**
|
|
* The attributes that should be cast.
|
|
*/
|
|
protected $casts = [
|
|
'weight' => 'float',
|
|
'length' => 'integer',
|
|
'width' => 'integer',
|
|
'height' => 'integer',
|
|
'services' => 'array',
|
|
'label_printed' => 'boolean',
|
|
'tracking_details' => 'array',
|
|
'last_tracked_at' => 'datetime',
|
|
'api_request_data' => 'array',
|
|
'api_response_data' => 'array',
|
|
'shipping_cost' => 'decimal:2',
|
|
'metadata' => 'array',
|
|
'shipped_at' => 'datetime',
|
|
'delivered_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* Shipment types
|
|
*/
|
|
public const TYPE_OUTBOUND = 'outbound';
|
|
public const TYPE_RETURN = 'return';
|
|
|
|
/**
|
|
* Shipment statuses
|
|
*/
|
|
public const STATUS_CREATED = 'created';
|
|
public const STATUS_SUBMITTED = 'submitted';
|
|
public const STATUS_IN_TRANSIT = 'in_transit';
|
|
public const STATUS_DELIVERED = 'delivered';
|
|
public const STATUS_RETURNED = 'returned';
|
|
public const STATUS_CANCELLED = 'cancelled';
|
|
public const STATUS_FAILED = 'failed';
|
|
|
|
/**
|
|
* Get the shopping order that owns the shipment
|
|
*/
|
|
public function shoppingOrder(): BelongsTo
|
|
{
|
|
return $this->belongsTo(ShoppingOrder::class, 'shopping_order_id');
|
|
}
|
|
|
|
/**
|
|
* Get the related shipment (for returns)
|
|
*/
|
|
public function relatedShipment(): BelongsTo
|
|
{
|
|
return $this->belongsTo(DhlShipment::class, 'related_shipment_id');
|
|
}
|
|
|
|
/**
|
|
* Get the return shipment (for outbound shipments)
|
|
*/
|
|
public function returnShipment(): HasOne
|
|
{
|
|
return $this->hasOne(DhlShipment::class, 'related_shipment_id');
|
|
}
|
|
|
|
/**
|
|
* Scope for outbound shipments
|
|
*/
|
|
public function scopeOutbound($query)
|
|
{
|
|
return $query->where('type', self::TYPE_OUTBOUND);
|
|
}
|
|
|
|
/**
|
|
* Scope for return shipments
|
|
*/
|
|
public function scopeReturns($query)
|
|
{
|
|
return $query->where('type', self::TYPE_RETURN);
|
|
}
|
|
|
|
/**
|
|
* Scope for active shipments (not cancelled or failed)
|
|
*/
|
|
public function scopeActive($query)
|
|
{
|
|
return $query->whereNotIn('status', [self::STATUS_CANCELLED, self::STATUS_FAILED]);
|
|
}
|
|
|
|
/**
|
|
* Scope for trackable shipments (have tracking number)
|
|
*/
|
|
public function scopeTrackable($query)
|
|
{
|
|
return $query->whereNotNull('tracking_number');
|
|
}
|
|
|
|
/**
|
|
* Check if shipment is outbound
|
|
*/
|
|
public function isOutbound(): bool
|
|
{
|
|
return $this->type === self::TYPE_OUTBOUND;
|
|
}
|
|
|
|
/**
|
|
* Check if shipment is return
|
|
*/
|
|
public function isReturn(): bool
|
|
{
|
|
return $this->type === self::TYPE_RETURN;
|
|
}
|
|
|
|
/**
|
|
* Check if shipment can be cancelled
|
|
*/
|
|
public function canBeCancelled(): bool
|
|
{
|
|
return in_array($this->status, [
|
|
self::STATUS_CREATED,
|
|
self::STATUS_SUBMITTED,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Check if shipment is delivered
|
|
*/
|
|
public function isDelivered(): bool
|
|
{
|
|
return $this->status === self::STATUS_DELIVERED;
|
|
}
|
|
|
|
/**
|
|
* Check if shipment has tracking information
|
|
*/
|
|
public function hasTracking(): bool
|
|
{
|
|
return !empty($this->tracking_number);
|
|
}
|
|
|
|
/**
|
|
* Check if label is available
|
|
*/
|
|
public function hasLabel(): bool
|
|
{
|
|
return !empty($this->label_path) && file_exists(storage_path('app/' . $this->label_path));
|
|
}
|
|
|
|
/**
|
|
* Get full recipient address as formatted string
|
|
*/
|
|
public function getRecipientAddressAttribute(): string
|
|
{
|
|
$address = $this->recipient_name;
|
|
|
|
if ($this->recipient_company) {
|
|
$address .= "\n" . $this->recipient_company;
|
|
}
|
|
|
|
$address .= "\n" . $this->recipient_street . ' ' . $this->recipient_street_number;
|
|
$address .= "\n" . $this->recipient_postal_code . ' ' . $this->recipient_city;
|
|
|
|
if ($this->recipient_state) {
|
|
$address .= "\n" . $this->recipient_state;
|
|
}
|
|
|
|
$address .= "\n" . $this->recipient_country;
|
|
|
|
return $address;
|
|
}
|
|
|
|
/**
|
|
* Get package dimensions as formatted string
|
|
*/
|
|
public function getDimensionsAttribute(): ?string
|
|
{
|
|
if (!$this->length || !$this->width || !$this->height) {
|
|
return null;
|
|
}
|
|
|
|
return $this->length . ' x ' . $this->width . ' x ' . $this->height . ' cm';
|
|
}
|
|
|
|
/**
|
|
* Get human-readable status
|
|
*/
|
|
public function getStatusLabelAttribute(): string
|
|
{
|
|
return match($this->status) {
|
|
self::STATUS_CREATED => 'Erstellt',
|
|
self::STATUS_SUBMITTED => 'Übertragen',
|
|
self::STATUS_IN_TRANSIT => 'Unterwegs',
|
|
self::STATUS_DELIVERED => 'Zugestellt',
|
|
self::STATUS_RETURNED => 'Zurückgeschickt',
|
|
self::STATUS_CANCELLED => 'Storniert',
|
|
self::STATUS_FAILED => 'Fehler',
|
|
default => 'Unbekannt',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get human-readable type
|
|
*/
|
|
public function getTypeLabelAttribute(): string
|
|
{
|
|
return match($this->type) {
|
|
self::TYPE_OUTBOUND => 'Versand',
|
|
self::TYPE_RETURN => 'Retoure',
|
|
default => 'Unbekannt',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get label file URL for download
|
|
*/
|
|
public function getLabelUrlAttribute(): ?string
|
|
{
|
|
if (!$this->hasLabel()) {
|
|
return null;
|
|
}
|
|
|
|
return route('admin.dhl.shipments.label', $this->id);
|
|
}
|
|
|
|
/**
|
|
* Boot the model
|
|
*/
|
|
protected static function boot()
|
|
{
|
|
parent::boot();
|
|
|
|
static::creating(function ($shipment) {
|
|
// Set default values
|
|
if (empty($shipment->currency)) {
|
|
$shipment->currency = config('dhl.defaults.currency', 'EUR');
|
|
}
|
|
|
|
if (empty($shipment->product_code)) {
|
|
$shipment->product_code = config('dhl.defaults.product', 'V01PAK');
|
|
}
|
|
|
|
if (empty($shipment->label_format)) {
|
|
$shipment->label_format = config('dhl.labels.format', 'PDF');
|
|
}
|
|
});
|
|
}
|
|
} |