I’m working on a Laravel project using Filament v3. I’m creating an order management system where each order can have multiple order items. I’m using the Filament Repeater component to handle the multiple order items in the order form. Each order item has a product, quantity, price, and total amount. The total price of the order should be the sum of the total amounts of all order items. However, I’m encountering an issue where the total price of the order remains 0 unless I add a new repeater item during the edit process.
Technologies Used:
Laravel: v8
Filament: v3
Database: MySQL
Current Implementation:
ORDER MODEL
<?php
namespace AppModels;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
class Order extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'status', 'shipping_address', 'total_price'];
protected static function booted()
{
static::saving(function ($order) {
$total = $order->orderItems->sum('total');
$order->total_price = $total;
});
}
public function orderItems()
{
return $this->hasMany(OrderItem::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
OrderItem Model:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
class OrderItem extends Model
{
use HasFactory;
protected $fillable = ['order_id', 'product_id', 'quantity', 'price', 'total'];
protected static function booted()
{
static::saving(function ($orderItem) {
$product = Product::find($orderItem->product_id);
$orderItem->price = $product ? $product->price : 0;
$orderItem->total = $orderItem->price * $orderItem->quantity;
});
}
public function order()
{
return $this->belongsTo(Order::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
}
OrderResource:
<?php
namespace AppFilamentResources;
use AppFilamentResourcesOrderResourcePages;
use AppModelsOrder;
use AppModelsProduct;
use FilamentForms;
use FilamentResourcesResource;
use FilamentTables;
use FilamentFormsComponentsPlaceholder;
use FilamentFormsForm;
use FilamentTablesColumnsTextColumn;
use FilamentTablesColumnsSelectColumn;
use FilamentTablesTable;
use FilamentFormsComponentsTextInput;
use FilamentFormsComponentsRepeater;
use FilamentFormsGet;
use FilamentFormsSet;
use FilamentTablesActionsActionGroup;
use FilamentTablesActionsEditAction;
use FilamentTablesActionsViewAction;
use FilamentTablesActionsDeleteAction;
class OrderResource extends Resource
{
protected static ?string $model = Order::class;
protected static ?string $navigationIcon = 'heroicon-o-shopping-cart';
public static function form(Form $form): Form
{
return $form
->schema([
FormsComponentsSelect::make('user_id')
->relationship('user', 'name')
->required(),
Repeater::make('orderItems')
->relationship('orderItems')
->schema([
FormsComponentsSelect::make('product_id')
->relationship('product', 'name')
->required()
->reactive()
->afterStateUpdated(function (callable $set, callable $get, $state) {
$product = Product::find($state);
$price = $product ? $product->price : 0;
$quantity = $get('quantity') ?? 1;
$set('price', $price);
$set('total', $price * $quantity);
self::updateTotalPrice($set, $get);
}),
FormsComponentsTextInput::make('quantity')
->numeric()
->required()
->default(1)
->minValue(1)
->reactive()
->afterStateUpdated(function (callable $set, callable $get, $state) {
$price = $get('price') ?? 0;
$set('total', $price * $state);
self::updateTotalPrice($set, $get);
}),
FormsComponentsTextInput::make('price')
->numeric()
->required()
->dehydrated()
->disabled(),
FormsComponentsTextInput::make('total')
->numeric()
->required()
->dehydrated()
->disabled(),
])
->createItemButtonLabel('Add Product')
->columns(4)
->afterStateUpdated(function (callable $set, callable $get) {
self::updateTotalPrice($set, $get);
}),
Placeholder::make('grand_total_placeholder')
->label('Grand Total')
->content(function (Get $get, Set $set) {
$total = collect($get('orderItems'))->sum('total');
$set('total_price', $total);
return number_format($total, 2);
}),
TextInput::make('total_price')
->numeric()
->required()
->dehydrated()
->hidden(),
FormsComponentsSelect::make('status')
->options([
'pending' => 'Pending',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
])
->required(),
FormsComponentsTextarea::make('shipping_address')
->maxLength(65535)
->required(),
]);
}
private static function updateTotalPrice(callable $set, callable $get)
{
$total = collect($get('orderItems'))->sum('total');
$set('total_price', $total);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('user.name')->label('Customer')->sortable(),
TextColumn::make('total_price')->label('Grand Total')->sortable()->formatStateUsing(fn ($state) => '$' . number_format($state, 2)),
TextColumn::make('shipping_address')->label('Shipping Address')->limit(50),
SelectColumn::make('status')
->label('Status')
->options([
'pending' => 'Pending',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
])
->sortable()
->afterStateUpdated(fn ($state, $record) => $record->update(['status' => $state])),
])
->actions([
ActionGroup::make([
EditAction::make(),
ViewAction::make(),
DeleteAction::make(),
]),
]);
}
public static function getPages(): array
{
return [
'index' => PagesListOrders::route('/'),
'create' => PagesCreateOrder::route('/create'),
'edit' => PagesEditOrder::route('/{record}/edit'),
];
}
}
Issue:
The total_price in the orders table remains 0 unless I add a new repeater item during the edit process. When I create a new order, the total_price should be calculated based on the total of all order items and stored in the orders table. However, it seems the calculation only happens correctly when adding a new repeater item in edit mode.
createorder
but when i edit the order this total price is apply and send the db and save.
Expected Behavior:
The total_price should be correctly calculated and stored in the orders table both when creating a new order and when editing an existing order without having to add a new repeater item.
What I’ve Tried:
Implemented booted method in the Order and OrderItem models to ensure the calculations are done before saving.
Added logic to calculate total_price in the Filament Repeater component.
Attempted to trigger the total calculation on various events within the Filament form components.
Looking for:Suggestions on ensuring the total_price field in the orders table gets updated correctly when saving a new or edited order.Any alternative approaches or best practices for handling this scenario using Filament v3 Repeater and Laravel.
Thank you for your assistance!
Rafael Gomez is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.