help with repeaters and updating data pls

Hello. I've got a Purchase Order resource with a repeater field on it. The repeater field is for items, and each item can have a name, quantity, and received quantity. When creating a purchase order, a user can add as many items as they like and set a quantity. The received quantity defaults to 0. The user can then upload invoices associated with the purchase order. In so doing, here's what I want to happen: 1. The user should be able to click an "Update Inventory" button on the invoice record to open a dialog with all of the existing items on that purchase order. 2. The items will be shown with their name and quantity disabled and unable to be edited, but the received quantity will be editable. 3. The user can edit the received quantity ONLY to a max of the quantity. The quantity is the upper limit. 4. On submit, it should increment or decrement the item's quantity in the repeater field and save the updated record. 5. On submit, it should also find each of the items in the Products table and increment or decrement their quantity accordingly. I've been losing my mind over this for the past hour or so because I can't figure out how to do it. I've got this so far:
DB::transaction(function () use ($record, $data) {
// Get the updated items from the modal
$updatedItems = $data['items'];
// Get the original items from the purchase order
$originalItems = $record->purchaseOrder->items;

// Initialize an array to hold the updated items for saving
$updatedItemsArray = [];

// Process the received quantities
foreach ($updatedItems as $updatedItem) {
// Find the matching original item in the purchase order
$matchingItem = collect($originalItems)->firstWhere('name', $updatedItem['name']);
if ($matchingItem) {
// Calculate the difference in received quantity
$previousReceivedQuantity = $matchingItem['received_quantity'];
$newReceivedQuantity = $updatedItem['received_quantity'];
$difference = $newReceivedQuantity - $previousReceivedQuantity;

// Update the received quantity in the original item
$matchingItem['received_quantity'] = $newReceivedQuantity;

// Increment or decrement inventory based on the difference
$product = Product::where('name', $updatedItem['name'])->first();
if ($product) {
if ($difference > 0) {
// If the received quantity increased, increment the inventory
$product->increment('inventory', $difference);
} elseif ($difference < 0) {
// If the received quantity decreased, decrement the inventory
$product->decrement('inventory', abs($difference));
}
}

// Add the updated item to the array
$updatedItemsArray[] = $matchingItem;
}
}

// Step 4: Save the updated items back to the purchase order
$record->purchaseOrder->items = $updatedItemsArray;
$record->purchaseOrder->save();
});
DB::transaction(function () use ($record, $data) {
// Get the updated items from the modal
$updatedItems = $data['items'];
// Get the original items from the purchase order
$originalItems = $record->purchaseOrder->items;

// Initialize an array to hold the updated items for saving
$updatedItemsArray = [];

// Process the received quantities
foreach ($updatedItems as $updatedItem) {
// Find the matching original item in the purchase order
$matchingItem = collect($originalItems)->firstWhere('name', $updatedItem['name']);
if ($matchingItem) {
// Calculate the difference in received quantity
$previousReceivedQuantity = $matchingItem['received_quantity'];
$newReceivedQuantity = $updatedItem['received_quantity'];
$difference = $newReceivedQuantity - $previousReceivedQuantity;

// Update the received quantity in the original item
$matchingItem['received_quantity'] = $newReceivedQuantity;

// Increment or decrement inventory based on the difference
$product = Product::where('name', $updatedItem['name'])->first();
if ($product) {
if ($difference > 0) {
// If the received quantity increased, increment the inventory
$product->increment('inventory', $difference);
} elseif ($difference < 0) {
// If the received quantity decreased, decrement the inventory
$product->decrement('inventory', abs($difference));
}
}

// Add the updated item to the array
$updatedItemsArray[] = $matchingItem;
}
}

// Step 4: Save the updated items back to the purchase order
$record->purchaseOrder->items = $updatedItemsArray;
$record->purchaseOrder->save();
});
This works except I need to make the name and quantity fields disabled and dehydrated(false) because otherwise the user can edit them. However, when I do that, they're not in the $data array and I'm not able to match it.
Solution:
So you want ->disabled() but ->dehydrated(true) I believe
Jump to solution
17 Replies
Solution
toeknee
toeknee2d ago
So you want ->disabled() but ->dehydrated(true) I believe
Raven
RavenOP2d ago
Wouldn't that allow users to possibly edit the input in the developer tools still? That's why I want dehydrated(false).
toeknee
toeknee2d ago
I don't believe so, I am fairly sure it gets it from the injection state, but the saving state. i.e. on the backend level not from the input. You can test it if it's a highly level of security concern.
Raven
RavenOP2d ago
https://filamentphp.com/docs/3.x/forms/advanced#field-dehydration
Dehydration is the process that gets data from the fields in your forms, and transforms it.
Yea, I'm going to test it in a bit but I think it would have a little bit of a vulnerability there. It doesn't really pose much of an security issue, per se. Just user experience.
toeknee
toeknee2d ago
I've just tested it and it's not because we get it from the model state, not the input state. since the input is disabled we don't take the input and livewire doesn't update the state in the request
Raven
RavenOP2d ago
Oh, perfect then, thank you! Also, do you know how to make it so that one of the fields (received quantity) can only have a max of the quantity number? So, I've got multiple items in the repeater, and for one of the items the quantity is 10 for example, the received quantity cannot be higher than 10. I don't know how to do that.
toeknee
toeknee2d ago
maxValue(10)
Raven
RavenOP2d ago
Well, I don't know the quantity in the code. I need to grab the max value from the quantity field in the repeater for that respective item.
toeknee
toeknee2d ago
where does the max value come from? The item? / product quanity that's left?
->maxValue(fn($record) => $record->qty)
->maxValue(fn($record) => $record->qty)
Like that?
Raven
RavenOP2d ago
No. I've got a PurchaseOrder model. It has an items field on it that's a JSON field cast as an array, which in Filament is used for a repeater. The repeater looks like this:
Repeater::make('items')->schema([
TextInput::make('name')->required()->label('Name'),
TextInput::make('quantity')->required()->numeric()->label('Quantity'),
TextInput::make('received_quantity')->required()->numeric()->default(0)->label('Received Quantity'),
MoneyInput::make('unit_cost')->required()->numeric()->label('Unit Cost'),
TextInput::make('tax_rate')->required()->numeric()->label('Tax Rate')->suffix('%'),
])->required()->columns()->columnSpanFull()
->itemLabel(fn (array $state): ?string => $state['name'] ?? null),
Repeater::make('items')->schema([
TextInput::make('name')->required()->label('Name'),
TextInput::make('quantity')->required()->numeric()->label('Quantity'),
TextInput::make('received_quantity')->required()->numeric()->default(0)->label('Received Quantity'),
MoneyInput::make('unit_cost')->required()->numeric()->label('Unit Cost'),
TextInput::make('tax_rate')->required()->numeric()->label('Tax Rate')->suffix('%'),
])->required()->columns()->columnSpanFull()
->itemLabel(fn (array $state): ?string => $state['name'] ?? null),
No description
Raven
RavenOP2d ago
[{"name":"Box, Medium","quantity":"10","unit_cost":"200","tax_rate":"5","received_quantity":"6"},{"name":"Packing Tape","quantity":"2","unit_cost":"500","tax_rate":"5","received_quantity":"0"}]
[{"name":"Box, Medium","quantity":"10","unit_cost":"200","tax_rate":"5","received_quantity":"6"},{"name":"Packing Tape","quantity":"2","unit_cost":"500","tax_rate":"5","received_quantity":"0"}]
This is what that looks like in the database. The received quantity for "Box, Medium" CANNOT be higher than its quantity, which is 10. So, if I put 11, it should error. For the packing tape, it cannot be 3, since the max quantity for that one is 2.
TextInput::make('received_quantity')->required()->numeric()->minValue(0)->maxValue(fn (
VendorInvoice $record,
Get $get
) => collect($record->purchaseOrder->items)->firstWhere('name',
$get('name'))['quantity'])->label('Received Quantity')
TextInput::make('received_quantity')->required()->numeric()->minValue(0)->maxValue(fn (
VendorInvoice $record,
Get $get
) => collect($record->purchaseOrder->items)->firstWhere('name',
$get('name'))['quantity'])->label('Received Quantity')
@toeknee I've got this and it works. However, using $get I find is a bit icky because someone can change the value of name in developer tools to be ANOTHER item's name and it'd update the max value allowed for received quantity, no?
toeknee
toeknee2d ago
No, I don't believe so. I believe it's re-validated in the state request, you just have the basic native browser function first. That generally how livewire works. Is the max value disabled too? if so the max value for the $get is from the model state and not the request state
Raven
RavenOP2d ago
The max value field is quantity, so it's disabled as well, yes.
Raven
RavenOP2d ago
No description
toeknee
toeknee2d ago
So there any attempt to change a disabled field will result in getting the original value since disabled is dehydrated.
Raven
RavenOP2d ago
Gotcha. Thank you!
toeknee
toeknee2d ago
Its the same reason why models can be unguarded in filamentphp, because we validate them all securely and only allow data to be retained from the backend, the frontend is mearly input never truly a validator.
Want results from more Discord servers?
Add your server