From ce5042bc22ee3bc3d1e3ec4e91396dc659a0da73 Mon Sep 17 00:00:00 2001 From: Bart Potmalnik <20015942+bpotmalnik@users.noreply.github.com> Date: Fri, 29 May 2026 14:37:08 +0200 Subject: [PATCH] fix(order-lines): resolve purchasable relationship for ShippingOption lines Fixes #2294 Accessing $orderLine->purchasable on a shipping line threw a fatal error because Eloquent tried to instantiate ShippingOption as a model with no arguments, but it is a plain DTO with required constructor parameters. Add a getPurchasableAttribute() accessor that detects purchasable_type of ShippingOption and reconstructs the DTO from the snapshotted order line data (description, identifier, unit_price, option, meta) instead of querying the database. All other purchasable types fall through to the normal morphTo relationship unchanged. --- packages/core/src/Models/OrderLine.php | 19 ++++++++++++++ tests/core/Unit/Models/OrderLineTest.php | 32 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/packages/core/src/Models/OrderLine.php b/packages/core/src/Models/OrderLine.php index 2233284593..0bf2e473ba 100644 --- a/packages/core/src/Models/OrderLine.php +++ b/packages/core/src/Models/OrderLine.php @@ -14,6 +14,8 @@ use Lunar\Base\Traits\HasMacros; use Lunar\Base\Traits\LogsActivity; use Lunar\Database\Factories\OrderLineFactory; +use Lunar\DataTypes\ShippingOption; +use Lunar\Models\TaxClass; /** * @property int $id @@ -81,6 +83,23 @@ public function order(): BelongsTo return $this->belongsTo(Order::modelClass()); } + public function getPurchasableAttribute(): mixed + { + if ($this->purchasable_type === ShippingOption::class) { + return new ShippingOption( + name: (string) $this->description, + description: (string) $this->description, + identifier: (string) $this->identifier, + price: $this->unit_price, + taxClass: TaxClass::getDefault(), + option: $this->option, + meta: $this->meta ? (array) $this->meta : null, + ); + } + + return $this->getRelationValue('purchasable'); + } + public function purchasable(): MorphTo { return $this->morphTo(); diff --git a/tests/core/Unit/Models/OrderLineTest.php b/tests/core/Unit/Models/OrderLineTest.php index 6980ae5e86..a2d05a58e7 100644 --- a/tests/core/Unit/Models/OrderLineTest.php +++ b/tests/core/Unit/Models/OrderLineTest.php @@ -159,3 +159,35 @@ ->and($orderLine->unit_price->unitDecimal) ->toEqual(6.5); }); + +test('can access purchasable on a shipping order line', function () { + $order = Order::factory()->create(); + + Currency::factory()->create([ + 'default' => true, + ]); + + TaxClass::factory()->create([ + 'default' => true, + ]); + + $orderLine = OrderLine::factory()->create([ + 'order_id' => $order->id, + 'quantity' => 1, + 'type' => 'shipping', + 'description' => 'Basic Delivery', + 'identifier' => 'BASDEL', + 'option' => 'standard', + 'purchasable_type' => ShippingOption::class, + 'purchasable_id' => 1, + 'unit_price' => 500, + 'unit_quantity' => 1, + ]); + + $purchasable = $orderLine->purchasable; + + expect($purchasable)->toBeInstanceOf(ShippingOption::class) + ->and($purchasable->getIdentifier())->toBe('BASDEL') + ->and($purchasable->getName())->toBe('Basic Delivery') + ->and($purchasable->getOption())->toBe('standard'); +});