Skip to content

Commit bde0b21

Browse files
authored
Fix Price object cast error when editing tier and customer group prices (lunarphp#2458)
## Summary Fixes a regression in 1.5.0-beta.2 / beta.3 where opening the edit modal for a tier price or customer group price throws: > ErrorException: Object of class Lunar\DataTypes\Price could not be converted to float > at vendor/filament/schemas/src/Components/StateCasts/NumberStateCast.php:28 This is the same ordering bug as lunarphp#2441 — Filament v4's `NumberStateCast` (registered by `->numeric()`) runs `floatval()` on the raw state during hydration, before `formatStateUsing` is given a chance to unwrap the value. The two relation managers that bind a `Lunar\DataTypes\Price` object straight to a numeric `TextInput` were not covered by that fix. PR lunarphp#2441 solved the equivalent problem for `FieldType` attribute values by overriding `hydrateState`. For prices, the values arrive through plain record attributes, so `EditAction::mutateRecordDataUsing()` runs early enough to convert the `Price` object into its decimal value before child component hydration — which means the `formatStateUsing` calls can be removed entirely. ## Changes - `PriceRelationManager` and `CustomerGroupPricingRelationManager`: add `mutateRecordDataUsing` on the `EditAction` to convert `Price` objects to their decimal value via a small `unwrapPriceData` helper. - Drop the now-redundant `formatStateUsing` callbacks on `price` / `compare_price` text inputs. - Add Pest feature tests that mount the edit action against a stored price and verify the form receives the expected decimal values (these tests fail on `1.x` without the fix with the original exception). ## Test plan - [x] `vendor/bin/pest --testsuite=admin --group=resource.product` - [x] `vendor/bin/pest --testsuite=admin --filter='customer group price|tier price'` - [x] `vendor/bin/pint --format agent` on touched files - [x] Confirmed the new tests fail on `1.x` without the fix (reproducing the original `Object of class Lunar\DataTypes\Price could not be converted to float` exception)
1 parent 453ac3d commit bde0b21

4 files changed

Lines changed: 156 additions & 32 deletions

File tree

packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupPricingRelationManager.php

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Illuminate\Validation\Rules\Unique;
1919
use Lunar\Admin\Events\ProductPricingUpdated;
2020
use Lunar\Admin\Support\RelationManagers\BaseRelationManager;
21+
use Lunar\DataTypes\Price as PriceDataType;
2122
use Lunar\Facades\DB;
2223
use Lunar\Models\Currency;
2324
use Lunar\Models\CustomerGroup;
@@ -76,14 +77,10 @@ public function getDefaultForm(Schema $schema): Schema
7677
])->columns(2),
7778

7879
Group::make([
79-
TextInput::make('price')->formatStateUsing(
80-
fn ($state) => $state?->decimal(rounding: false)
81-
)->numeric()->helperText(
80+
TextInput::make('price')->numeric()->helperText(
8281
__('lunarpanel::relationmanagers.pricing.form.price.helper_text')
8382
)->required(),
84-
TextInput::make('compare_price')->formatStateUsing(
85-
fn ($state) => $state?->decimal(rounding: false)
86-
)->label(
83+
TextInput::make('compare_price')->label(
8784
__('lunarpanel::relationmanagers.pricing.form.compare_price.label')
8885
)->helperText(
8986
__('lunarpanel::relationmanagers.pricing.form.compare_price.helper_text')
@@ -152,18 +149,31 @@ public function getDefaultTable(Table $table): Table
152149
),
153150
])
154151
->recordActions([
155-
EditAction::make()->mutateDataUsing(function (array $data): array {
156-
$currencyModel = Currency::find($data['currency_id']);
152+
EditAction::make()
153+
->mutateRecordDataUsing(fn (array $data): array => $this->unwrapPriceData($data))
154+
->mutateDataUsing(function (array $data): array {
155+
$currencyModel = Currency::find($data['currency_id']);
157156

158-
$data['min_quantity'] = 1;
159-
$data['price'] = (int) ($data['price'] * $currencyModel->factor);
160-
$data['compare_price'] = (int) ($data['compare_price'] * $currencyModel->factor);
157+
$data['min_quantity'] = 1;
158+
$data['price'] = (int) ($data['price'] * $currencyModel->factor);
159+
$data['compare_price'] = (int) ($data['compare_price'] * $currencyModel->factor);
161160

162-
return $data;
163-
})->after(
164-
fn () => ProductPricingUpdated::dispatch($this->getOwnerRecord())
165-
),
161+
return $data;
162+
})->after(
163+
fn () => ProductPricingUpdated::dispatch($this->getOwnerRecord())
164+
),
166165
DeleteAction::make(),
167166
]);
168167
}
168+
169+
protected function unwrapPriceData(array $data): array
170+
{
171+
foreach (['price', 'compare_price'] as $key) {
172+
if (($data[$key] ?? null) instanceof PriceDataType) {
173+
$data[$key] = $data[$key]->decimal(rounding: false);
174+
}
175+
}
176+
177+
return $data;
178+
}
169179
}

packages/admin/src/Support/RelationManagers/PriceRelationManager.php

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Illuminate\Contracts\Support\Htmlable;
1818
use Illuminate\Database\Eloquent\Model;
1919
use Lunar\Admin\Events\ModelPricesUpdated;
20+
use Lunar\DataTypes\Price as PriceDataType;
2021
use Lunar\Facades\DB;
2122
use Lunar\Models\Currency;
2223
use Lunar\Models\CustomerGroup;
@@ -93,14 +94,10 @@ public function form(Schema $schema): Schema
9394
])->columns(3),
9495

9596
Group::make([
96-
TextInput::make('price')->formatStateUsing(
97-
fn ($state) => $state?->decimal(rounding: false)
98-
)->numeric()->helperText(
97+
TextInput::make('price')->numeric()->helperText(
9998
__('lunarpanel::relationmanagers.pricing.form.price.helper_text')
10099
)->required(),
101-
TextInput::make('compare_price')->formatStateUsing(
102-
fn ($state) => $state?->decimal(rounding: false)
103-
)->label(
100+
TextInput::make('compare_price')->label(
104101
__('lunarpanel::relationmanagers.pricing.form.compare_price.label')
105102
)->helperText(
106103
__('lunarpanel::relationmanagers.pricing.form.compare_price.helper_text')
@@ -179,22 +176,35 @@ public function table(Table $table): Table
179176
),
180177
])
181178
->recordActions([
182-
EditAction::make()->mutateDataUsing(function (array $data): array {
183-
$currencyModel = Currency::find($data['currency_id']);
184-
185-
$data['price'] = (int) ($data['price'] * $currencyModel->factor);
186-
187-
return $data;
188-
})->after(
189-
fn () => ModelPricesUpdated::dispatch(
190-
$this->getOwnerRecord()
191-
)
192-
),
179+
EditAction::make()
180+
->mutateRecordDataUsing(fn (array $data): array => $this->unwrapPriceData($data))
181+
->mutateDataUsing(function (array $data): array {
182+
$currencyModel = Currency::find($data['currency_id']);
183+
184+
$data['price'] = (int) ($data['price'] * $currencyModel->factor);
185+
186+
return $data;
187+
})->after(
188+
fn () => ModelPricesUpdated::dispatch(
189+
$this->getOwnerRecord()
190+
)
191+
),
193192
DeleteAction::make()->after(
194193
fn () => ModelPricesUpdated::dispatch(
195194
$this->getOwnerRecord()
196195
)
197196
),
198197
]);
199198
}
199+
200+
protected function unwrapPriceData(array $data): array
201+
{
202+
foreach (['price', 'compare_price'] as $key) {
203+
if (($data[$key] ?? null) instanceof PriceDataType) {
204+
$data[$key] = $data[$key]->decimal(rounding: false);
205+
}
206+
}
207+
208+
return $data;
209+
}
200210
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
use Filament\Actions\EditAction;
4+
use Livewire\Livewire;
5+
use Lunar\Admin\Filament\Resources\ProductResource\RelationManagers\CustomerGroupPricingRelationManager;
6+
use Lunar\Models\Currency;
7+
use Lunar\Models\CustomerGroup;
8+
use Lunar\Models\Language;
9+
use Lunar\Models\Price;
10+
use Lunar\Models\Product;
11+
use Lunar\Models\ProductVariant;
12+
use Lunar\Tests\Admin\Feature\Filament\TestCase;
13+
14+
uses(TestCase::class)
15+
->group('resource.product');
16+
17+
it('can mount the edit action on a customer group price without a Price object cast error', function () {
18+
Language::factory()->create([
19+
'default' => true,
20+
]);
21+
22+
$currency = Currency::factory()->create([
23+
'default' => true,
24+
'decimal_places' => 2,
25+
]);
26+
27+
$customerGroup = CustomerGroup::factory()->create([
28+
'default' => true,
29+
]);
30+
31+
$product = Product::factory()->create();
32+
$variant = ProductVariant::factory()->create([
33+
'product_id' => $product->id,
34+
]);
35+
36+
$price = Price::factory()->create([
37+
'priceable_type' => $variant->getMorphClass(),
38+
'priceable_id' => $variant->id,
39+
'currency_id' => $currency->id,
40+
'customer_group_id' => $customerGroup->id,
41+
'min_quantity' => 1,
42+
'price' => 1099,
43+
'compare_price' => 1299,
44+
]);
45+
46+
$this->asStaff(admin: true);
47+
48+
Livewire::test(CustomerGroupPricingRelationManager::class, [
49+
'ownerRecord' => $product,
50+
'pageClass' => 'productEdit',
51+
])
52+
->mountTableAction(EditAction::class, $price)
53+
->assertTableActionDataSet([
54+
'price' => 10.99,
55+
'compare_price' => 12.99,
56+
'customer_group_id' => $customerGroup->id,
57+
'currency_id' => $currency->id,
58+
])
59+
->assertHasNoErrors();
60+
});

tests/admin/Feature/Support/RelationManagers/PriceRelationManagerTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
<?php
22

3+
use Filament\Actions\EditAction;
34
use Livewire\Livewire;
45
use Lunar\Admin\Filament\Resources\ProductResource\Pages\ManageProductPricing;
56
use Lunar\Admin\Support\RelationManagers\PriceRelationManager;
7+
use Lunar\Models\Currency;
68
use Lunar\Models\Language;
9+
use Lunar\Models\Price;
710
use Lunar\Models\Product;
11+
use Lunar\Models\ProductVariant;
812
use Lunar\Tests\Admin\Feature\Filament\TestCase;
913

1014
uses(TestCase::class)
@@ -26,3 +30,43 @@
2630
})->with([
2731
[Product::class, ManageProductPricing::class],
2832
]);
33+
34+
it('can mount the edit action on a tier price without a Price object cast error', function () {
35+
Language::factory()->create([
36+
'default' => true,
37+
]);
38+
39+
$currency = Currency::factory()->create([
40+
'default' => true,
41+
'decimal_places' => 2,
42+
]);
43+
44+
$product = Product::factory()->create();
45+
$variant = ProductVariant::factory()->create([
46+
'product_id' => $product->id,
47+
]);
48+
49+
$price = Price::factory()->create([
50+
'priceable_type' => $variant->getMorphClass(),
51+
'priceable_id' => $variant->id,
52+
'currency_id' => $currency->id,
53+
'min_quantity' => 5,
54+
'price' => 1099,
55+
'compare_price' => 1299,
56+
]);
57+
58+
$this->asStaff(admin: true);
59+
60+
Livewire::test(PriceRelationManager::class, [
61+
'ownerRecord' => $product,
62+
'pageClass' => ManageProductPricing::class,
63+
])
64+
->mountTableAction(EditAction::class, $price)
65+
->assertTableActionDataSet([
66+
'price' => 10.99,
67+
'compare_price' => 12.99,
68+
'min_quantity' => 5,
69+
'currency_id' => $currency->id,
70+
])
71+
->assertHasNoErrors();
72+
});

0 commit comments

Comments
 (0)