From fb1fde26ce47eefe1cde7eaf6bf1fece5ead0543 Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Tue, 11 Nov 2025 18:00:22 +0000 Subject: [PATCH 1/3] Use a FormRequest to better handle multiple-asset-creation by GUI --- .../Controllers/Assets/AssetsController.php | 4 +- .../Requests/StoreMultipleAssetRequest.php | 70 +++++++++++++++++++ app/Http/Traits/UniqueUndeletedTrait.php | 1 + app/Models/Asset.php | 2 +- app/Rules/UniqueUndeleted.php | 60 ++++++++++++++++ resources/lang/en-US/validation.php | 5 +- resources/views/groups/edit.blade.php | 18 ++++- resources/views/hardware/edit.blade.php | 62 ++++++++++++++-- .../partials/forms/edit/serial.blade.php | 2 +- 9 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 app/Http/Requests/StoreMultipleAssetRequest.php create mode 100644 app/Rules/UniqueUndeleted.php diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 83c7a08ea183..dfc01bdf9a0a 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -6,6 +6,7 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\ImageUploadRequest; +use App\Http\Requests\StoreMultipleAssetRequest; use App\Http\Requests\UpdateAssetRequest; use App\Models\Actionlog; use App\Http\Requests\UploadFileRequest; @@ -98,7 +99,7 @@ public function create(Request $request) : View * @author [A. Gianotto] [] * @since [v1.0] */ - public function store(ImageUploadRequest $request) : RedirectResponse + public function store(StoreMultipleAssetRequest $request): RedirectResponse { $this->authorize(Asset::class); @@ -211,6 +212,7 @@ public function store(ImageUploadRequest $request) : RedirectResponse } } + \Log::error("About to check validity and, possibly, save..."); // Validate the asset before saving if ($asset->isValid() && $asset->save()) { $target = null; diff --git a/app/Http/Requests/StoreMultipleAssetRequest.php b/app/Http/Requests/StoreMultipleAssetRequest.php new file mode 100644 index 000000000000..92404eaa0c8f --- /dev/null +++ b/app/Http/Requests/StoreMultipleAssetRequest.php @@ -0,0 +1,70 @@ +|string> + */ + public function rules(): array + { + //grab the rules for serials and asset_tags, and tweak them into an array context for multi-create usage + $modelRules = (new Asset)->getRules(); + unset($modelRules['serial']); + + $asset_tag_rules = $modelRules['asset_tag']; + unset($modelRules['asset_tag']); + // now we replace the 'not_array' rule with 'distinct' + array_splice($asset_tag_rules, array_search('not_array', $asset_tag_rules), 1, 'distinct'); + // and replace the 'unique_undeleted' rule with the Rule object + foreach ($asset_tag_rules as $i => $asset_tag_rule) { + if (Str::startsWith($asset_tag_rule, 'unique_undeleted')) { + $asset_tag_rules[$i] = new UniqueUndeleted('assets', 'asset_tag'); + } + } + + $serials_unique = Setting::getSettings()['unique_serial']; + $serials_required = AssetModel::find($this?->model_id)?->require_serial; + + $serial_rules = ['string']; + if ($serials_unique) { +// $serial_rules[] = 'unique_undeleted:assets,serial'; + $serial_rules[] = new UniqueUndeleted('assets', 'serial'); + $serial_rules[] = 'distinct'; + } + if ($serials_required) { + $serial_rules[] = 'required'; + } else { + $serial_rules[] = 'nullable'; + } + \Log::error("Serial Rules are: " . print_r($serial_rules, true)); + \Log::error("Asset Tag Rules are: " . print_r($asset_tag_rules, true)); + + return array_merge($modelRules, [ + 'serials.*' => $serial_rules, + 'asset_tags.*' => $asset_tag_rules, + ]); + } +} diff --git a/app/Http/Traits/UniqueUndeletedTrait.php b/app/Http/Traits/UniqueUndeletedTrait.php index d4c726bd7310..a21d074573c8 100644 --- a/app/Http/Traits/UniqueUndeletedTrait.php +++ b/app/Http/Traits/UniqueUndeletedTrait.php @@ -13,6 +13,7 @@ trait UniqueUndeletedTrait */ protected function prepareUniqueUndeletedRule($parameters, $field) { + \Log::error("Preparing unique undeleted rule for $field"); // Only perform a replacement if the model has been persisted. if ($this->exists) { return 'unique_undeleted:'.$this->table.','.$this->getKey(); diff --git a/app/Models/Asset.php b/app/Models/Asset.php index d588690ba4bd..fe7eaf00a0bf 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -115,7 +115,7 @@ public function declinedCheckout(User $declinedBy, $signature) 'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], 'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'], - 'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'], + 'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'], // FIXME - doesn't this require unique serials *ALWAYS*?!? 'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:99999999999999999.99'], 'supplier_id' => ['nullable', 'exists:suppliers,id'], 'asset_eol_date' => ['nullable', 'date'], diff --git a/app/Rules/UniqueUndeleted.php b/app/Rules/UniqueUndeleted.php new file mode 100644 index 000000000000..3f21f027a860 --- /dev/null +++ b/app/Rules/UniqueUndeleted.php @@ -0,0 +1,60 @@ +columns = $columns; + } + + public function setValidator(Validator $validator): static + { +// \Log::error("Validator has been SET and it is: ".print_r(get_object_vars($validator), true)); + $this->validator = $validator; + $this->data = $validator->getData(); + //TODO - can we somehow grab the ID of the route-model-bound object, and omit its ID? + // to do that, we'd have to know _which_ parameter in the validator is actually the R-M-B'ed + // parameter + + return $this; + } + + /** + * Run the validation rule. + * + * @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail + */ + public function validate(string $attribute, mixed $value, Closure $fail): void + { + \Log::error("Going to validate via RULE for attribute: $attribute which has value: " . print_r($value, true)); + // $this->validator->{$something} should maybe get the object? + // but, so far, we're only using this on creating a 'new thing' so there's just a simple check needed, I guess? + $query = DB::table($this->table)->whereNull('deleted_at'); + $query->where($this->columns[0], '!=', $value); //the first column to check + $translation_string = 'validation.unique_undeleted'; //the normal validation string for a single-column check + foreach (array_slice($this->columns, 1) as $column) { + $translation_string = 'validation.two_column_unique_undeleted'; + $query->where($column, '!=', $this->data[$column]); + } +// \Log::error("Here is what you get if you ask for the ID directly: " . $this->validator->id); + if ($query->count() > 0) { + $fail()->translate($translation_string); + } + } +} diff --git a/resources/lang/en-US/validation.php b/resources/lang/en-US/validation.php index 2d4af64fb430..7276b44586a4 100644 --- a/resources/lang/en-US/validation.php +++ b/resources/lang/en-US/validation.php @@ -230,7 +230,10 @@ | */ - 'attributes' => [], + 'attributes' => [ + 'serials.*' => 'Serial Number', + 'asset_tags.*' => 'Asset Tag', + ], /* |-------------------------------------------------------------------------- diff --git a/resources/views/groups/edit.blade.php b/resources/views/groups/edit.blade.php index 8f5d6b930efc..8f35bfce0cb4 100755 --- a/resources/views/groups/edit.blade.php +++ b/resources/views/groups/edit.blade.php @@ -51,9 +51,9 @@ + {!! $errors->first('associated_users', '
') !!} @@ -78,3 +79,16 @@ @stop +@section('moar_scripts') + +@endsection diff --git a/resources/views/hardware/edit.blade.php b/resources/views/hardware/edit.blade.php index c06a396ec1a9..47fff58358ac 100755 --- a/resources/views/hardware/edit.blade.php +++ b/resources/views/hardware/edit.blade.php @@ -23,7 +23,7 @@ -
+
@@ -39,8 +39,10 @@ @else
- - {!! $errors->first('asset_tags', ' :message') !!} + + {!! $errors->first('asset_tags.1', ' :message') !!} {!! $errors->first('asset_tag', ' :message') !!}
@@ -57,6 +59,54 @@ @include ('partials.forms.edit.serial', ['fieldname'=> 'serials[1]', 'old_val_name' => 'serials.1', 'translated_serial' => trans('admin/hardware/form.serial')])
+ {{-- If we're back on this screen for an error, *and* we are doing 'create multiple', then... --}} + @php + $old_tags = old('asset_tags',[]); + /** + okay, so old() comes back as: + ( + [1] => 1744410541 + [2] => 1744410542 + ) + */ + if(array_key_exists('1',$old_tags)) { + unset($old_tags[1]); //we already handled 'asset_tag.1' - so unset it + } + @endphp + @foreach(array_keys($old_tags) as $i) + {{-- This is mostly stolen from the HTML that we add via javascript on the 'add_field_button' handler in the embedded JS below --}} + {{-- @include ('partials.forms.edit.serial', ['fieldname'=> 'serials['.$loop->iteration.']', 'old_val_name' => 'serials.'.$loop->iteration, 'translated_serial' => trans('admin/hardware/form.serial')])--}} + @php + \Log::error("I is: $i"); + @endphp + +
+
+ + {!! $errors->first('asset_tags.'.$i, ' :message') !!} +
+
+ +
+
+ @include ('partials.forms.edit.serial', ['fieldname'=> 'serials['.$i.']', 'old_val_name' => 'serials.'.$i, 'translated_serial' => trans('admin/hardware/form.serial')]) + {{--
+
+ + @error('serials.'.$i ) + + @enderror +
+
--}} +
+ @endforeach
@include ('partials.forms.edit.model-select', ['translated_name' => trans('admin/hardware/form.model'), 'fieldname' => 'model_id', 'field_req' => true]) @@ -290,7 +340,7 @@ function user_add(status_id) { var max_fields = 100; //maximum input boxes allowed var wrapper = $(".input_fields_wrap"); //Fields wrapper var add_button = $(".add_field_button"); //Add button ID - var x = 1; //initial text box count + var x = {{ old('asset_tags') ? count(old('asset_tags')) : 1 /* If we have old() data, use that to determine the 'next' number for 'Asset Tag 2' etc. Otherwise, use 1 */ }}; //initial text box count @@ -314,6 +364,8 @@ function user_add(status_id) { x++; //text box increment + // NOTE - this is duplicated in the blade itself in order to re-display an attempt to insert multiple assets + // So if this changes, that needs to change too. box_html += ''; box_html += '
'; box_html += '
'; @@ -322,7 +374,7 @@ function user_add(status_id) { box_html += '
'; box_html += ''; box_html += '
'; - box_html += '
'; + // box_html += '
'; box_html += '
'; box_html += '
'; box_html += '
'; diff --git a/resources/views/partials/forms/edit/serial.blade.php b/resources/views/partials/forms/edit/serial.blade.php index 057fae0eeaf6..391dc17af6d9 100644 --- a/resources/views/partials/forms/edit/serial.blade.php +++ b/resources/views/partials/forms/edit/serial.blade.php @@ -1,5 +1,5 @@ -
+
model && $item->model->require_serial)) ? ' required' : '' }} maxlength="191" /> From 9b8768dbdd3f7ad1b8188434c4ebda15a67853e9 Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Tue, 25 Nov 2025 19:34:10 +0000 Subject: [PATCH 2/3] Tighten everything up, remove logging, fix last bits of functionality --- .../Controllers/Assets/AssetsController.php | 195 ++++++++++-------- ...est.php => CreateMultipleAssetRequest.php} | 6 +- app/Http/Traits/UniqueUndeletedTrait.php | 1 - app/Models/Asset.php | 2 +- app/Rules/UniqueUndeleted.php | 14 +- resources/views/hardware/edit.blade.php | 15 -- 6 files changed, 112 insertions(+), 121 deletions(-) rename app/Http/Requests/{StoreMultipleAssetRequest.php => CreateMultipleAssetRequest.php} (86%) diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index dfc01bdf9a0a..d485fafa01a6 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -6,7 +6,7 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\ImageUploadRequest; -use App\Http\Requests\StoreMultipleAssetRequest; +use App\Http\Requests\CreateMultipleAssetRequest; use App\Http\Requests\UpdateAssetRequest; use App\Models\Actionlog; use App\Http\Requests\UploadFileRequest; @@ -99,7 +99,7 @@ public function create(Request $request) : View * @author [A. Gianotto] [] * @since [v1.0] */ - public function store(StoreMultipleAssetRequest $request): RedirectResponse + public function store(CreateMultipleAssetRequest $request): RedirectResponse { $this->authorize(Asset::class); @@ -136,123 +136,136 @@ public function store(StoreMultipleAssetRequest $request): RedirectResponse $successes = []; $failures = []; - for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { - $asset = new Asset(); + try { + DB::beginTransaction(); + for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { + $asset = new Asset(); - $asset->model()->associate($model); - $asset->name = $request->input('name'); + $asset->model()->associate($model); + $asset->name = $request->input('name'); - // Check for a corresponding serial - if (($serials) && (array_key_exists($a, $serials))) { - $asset->serial = $serials[$a]; - } - - if (($asset_tags) && (array_key_exists($a, $asset_tags))) { - $asset->asset_tag = $asset_tags[$a]; - } + // Check for a corresponding serial + if (($serials) && (array_key_exists($a, $serials))) { + $asset->serial = $serials[$a]; + } - $asset->company_id = $companyId; - $asset->model_id = $request->input('model_id'); - $asset->order_number = $request->input('order_number'); - $asset->notes = $request->input('notes'); - $asset->created_by = auth()->id(); - $asset->status_id = request('status_id'); - $asset->warranty_months = request('warranty_months', null); - $asset->purchase_cost = request('purchase_cost'); - $asset->purchase_date = request('purchase_date', null); - $asset->asset_eol_date = request('asset_eol_date', null); - $asset->assigned_to = request('assigned_to', null); - $asset->supplier_id = request('supplier_id', null); - $asset->requestable = request('requestable', 0); - $asset->rtd_location_id = request('rtd_location_id', null); - $asset->byod = request('byod', 0); - - if (! empty($settings->audit_interval)) { - $asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString(); - } + if (($asset_tags) && (array_key_exists($a, $asset_tags))) { + $asset->asset_tag = $asset_tags[$a]; + } - // Set location_id to rtd_location_id ONLY if the asset isn't being checked out - if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) { - $asset->location_id = $request->input('rtd_location_id', null); - } + $asset->company_id = $companyId; + $asset->model_id = $request->input('model_id'); + $asset->order_number = $request->input('order_number'); + $asset->notes = $request->input('notes'); + $asset->created_by = auth()->id(); + $asset->status_id = request('status_id'); + $asset->warranty_months = request('warranty_months', null); + $asset->purchase_cost = request('purchase_cost'); + $asset->purchase_date = request('purchase_date', null); + $asset->asset_eol_date = request('asset_eol_date', null); + $asset->assigned_to = request('assigned_to', null); + $asset->supplier_id = request('supplier_id', null); + $asset->requestable = request('requestable', 0); + $asset->rtd_location_id = request('rtd_location_id', null); + $asset->byod = request('byod', 0); + + if (!empty($settings->audit_interval)) { + $asset->next_audit_date = Carbon::now()->addMonths((int)$settings->audit_interval)->toDateString(); + } - if ($request->has('use_cloned_image')) { - $cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id')); - if ($cloned_model_img) { - $new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image; - $new_image = 'assets/'.$new_image_name; - Storage::disk('public')->copy('assets/'.$cloned_model_img->image, $new_image); - $asset->image = $new_image_name; + // Set location_id to rtd_location_id ONLY if the asset isn't being checked out + if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) { + $asset->location_id = $request->input('rtd_location_id', null); } - } else { - $asset = $request->handleImages($asset); - } + if ($request->has('use_cloned_image')) { + $cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id')); + if ($cloned_model_img) { + $new_image_name = 'clone-' . date('U') . '-' . $cloned_model_img->image; + $new_image = 'assets/' . $new_image_name; + Storage::disk('public')->copy('assets/' . $cloned_model_img->image, $new_image); + $asset->image = $new_image_name; + } - // Update custom fields in the database. - // Validation for these fields is handled through the AssetRequest form request + } else { + $asset = $request->handleImages($asset); + } - if (($model) && ($model->fieldset)) { - foreach ($model->fieldset->fields as $field) { - if ($field->field_encrypted == '1') { - if (Gate::allows('assets.view.encrypted_custom_fields')) { + // Update custom fields in the database. + // Validation for these fields is handled through the AssetRequest form request + + if (($model) && ($model->fieldset)) { + foreach ($model->fieldset->fields as $field) { + if ($field->field_encrypted == '1') { + if (Gate::allows('assets.view.encrypted_custom_fields')) { + if (is_array($request->input($field->db_column))) { + $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); + } else { + $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); + } + } + } else { if (is_array($request->input($field->db_column))) { - $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); + $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); } else { - $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); + $asset->{$field->db_column} = $request->input($field->db_column); } } - } else { - if (is_array($request->input($field->db_column))) { - $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); - } else { - $asset->{$field->db_column} = $request->input($field->db_column); - } } } - } - \Log::error("About to check validity and, possibly, save..."); - // Validate the asset before saving - if ($asset->isValid() && $asset->save()) { - $target = null; - $location = null; + // Validate the asset before saving + // Note - it can be tempting to instead want to call saveOrFail(), to automatically throw when an object + // is invalid (and can't save). But this won't work, because Custom Fields _overrides_ the save() method + // to inject the Custom Field Rules into the $rules property right before invoking the _real_ save. + // so, instead, we have to catch failures on the 'else' clause and throw there. + if ($asset->isValid() && $asset->save()) { + $target = null; + $location = null; - if ($userId = request('assigned_user')) { - $target = User::find($userId); + if ($userId = request('assigned_user')) { + $target = User::find($userId); - if (!$target) { - return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user')); - } - $location = $target->location_id; + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user')); + } + $location = $target->location_id; - } elseif ($assetId = request('assigned_asset')) { - $target = Asset::find($assetId); + } elseif ($assetId = request('assigned_asset')) { + $target = Asset::find($assetId); - if (!$target) { - return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset')); - } - $location = $target->location_id; + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset')); + } + $location = $target->location_id; - } elseif ($locationId = request('assigned_location')) { - $target = Location::find($locationId); + } elseif ($locationId = request('assigned_location')) { + $target = Location::find($locationId); - if (!$target) { - return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location')); + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location')); + } + $location = $target->id; } - $location = $target->id; - } - if (isset($target)) { - $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location); - } + if (isset($target)) { + $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location); + } - $successes[] = "" . e($asset->asset_tag) . ""; + $successes[] = "" . e($asset->asset_tag) . ""; - } else { - $failures[] = join(",", $asset->getErrors()->all()); + } else { + $asset->throwValidationException(); // we have to do this for the reason listed above - can't use saveOrFail() + $failures[] = join(",", $asset->getErrors()->all()); //TODO - this can probably go away soon + } } + } catch (\Throwable $e) { + \Log::debug("Caught exception in multi-create - rolling back: " . $e->getMessage()); + DB::rollBack(); + throw $e; } + DB::commit(); + if($request->get('redirect_option') === 'back'){ session()->put(['redirect_option' => 'index']); } else { diff --git a/app/Http/Requests/StoreMultipleAssetRequest.php b/app/Http/Requests/CreateMultipleAssetRequest.php similarity index 86% rename from app/Http/Requests/StoreMultipleAssetRequest.php rename to app/Http/Requests/CreateMultipleAssetRequest.php index 92404eaa0c8f..0cdd2d24b74a 100644 --- a/app/Http/Requests/StoreMultipleAssetRequest.php +++ b/app/Http/Requests/CreateMultipleAssetRequest.php @@ -11,7 +11,7 @@ use App\Rules\UniqueUndeleted; use Illuminate\Support\Str; -class StoreMultipleAssetRequest extends ImageUploadRequest //should I extend from StoreAssetRequest? FIXME OR TODO OR THINKME +class CreateMultipleAssetRequest extends ImageUploadRequest //should I extend from StoreAssetRequest? FIXME OR TODO OR THINKME { use MayContainCustomFields; @@ -20,7 +20,7 @@ class StoreMultipleAssetRequest extends ImageUploadRequest //should I extend fro */ public function authorize(): bool { - return true; //FIXME - should I do the auth check here? + return true; //TODO - should I do the auth check here? } /** @@ -59,8 +59,6 @@ public function rules(): array } else { $serial_rules[] = 'nullable'; } - \Log::error("Serial Rules are: " . print_r($serial_rules, true)); - \Log::error("Asset Tag Rules are: " . print_r($asset_tag_rules, true)); return array_merge($modelRules, [ 'serials.*' => $serial_rules, diff --git a/app/Http/Traits/UniqueUndeletedTrait.php b/app/Http/Traits/UniqueUndeletedTrait.php index a21d074573c8..d4c726bd7310 100644 --- a/app/Http/Traits/UniqueUndeletedTrait.php +++ b/app/Http/Traits/UniqueUndeletedTrait.php @@ -13,7 +13,6 @@ trait UniqueUndeletedTrait */ protected function prepareUniqueUndeletedRule($parameters, $field) { - \Log::error("Preparing unique undeleted rule for $field"); // Only perform a replacement if the model has been persisted. if ($this->exists) { return 'unique_undeleted:'.$this->table.','.$this->getKey(); diff --git a/app/Models/Asset.php b/app/Models/Asset.php index a927b8eba178..46385c23304c 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -115,7 +115,7 @@ public function declinedCheckout(User $declinedBy, $signature) 'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], 'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'], - 'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'], // FIXME - doesn't this require unique serials *ALWAYS*?!? + 'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'], 'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:99999999999999999.99'], 'supplier_id' => ['nullable', 'exists:suppliers,id'], 'asset_eol_date' => ['nullable', 'date'], diff --git a/app/Rules/UniqueUndeleted.php b/app/Rules/UniqueUndeleted.php index 3f21f027a860..9aceae5a73cc 100644 --- a/app/Rules/UniqueUndeleted.php +++ b/app/Rules/UniqueUndeleted.php @@ -25,12 +25,11 @@ public function __construct( public function setValidator(Validator $validator): static { -// \Log::error("Validator has been SET and it is: ".print_r(get_object_vars($validator), true)); $this->validator = $validator; $this->data = $validator->getData(); //TODO - can we somehow grab the ID of the route-model-bound object, and omit its ID? // to do that, we'd have to know _which_ parameter in the validator is actually the R-M-B'ed - // parameter + // parameter. Or maybe we just change the function signature to let you specify it. return $this; } @@ -42,19 +41,16 @@ public function setValidator(Validator $validator): static */ public function validate(string $attribute, mixed $value, Closure $fail): void { - \Log::error("Going to validate via RULE for attribute: $attribute which has value: " . print_r($value, true)); - // $this->validator->{$something} should maybe get the object? - // but, so far, we're only using this on creating a 'new thing' so there's just a simple check needed, I guess? $query = DB::table($this->table)->whereNull('deleted_at'); - $query->where($this->columns[0], '!=', $value); //the first column to check + $query->where($this->columns[0], '==', $value); //the first column to check $translation_string = 'validation.unique_undeleted'; //the normal validation string for a single-column check foreach (array_slice($this->columns, 1) as $column) { $translation_string = 'validation.two_column_unique_undeleted'; - $query->where($column, '!=', $this->data[$column]); + $query->where($column, '==', $this->data[$column]); } -// \Log::error("Here is what you get if you ask for the ID directly: " . $this->validator->id); + if ($query->count() > 0) { - $fail()->translate($translation_string); + $fail($translation_string)->translate(); } } } diff --git a/resources/views/hardware/edit.blade.php b/resources/views/hardware/edit.blade.php index 47fff58358ac..74abb1d4d36c 100755 --- a/resources/views/hardware/edit.blade.php +++ b/resources/views/hardware/edit.blade.php @@ -76,9 +76,6 @@ @foreach(array_keys($old_tags) as $i) {{-- This is mostly stolen from the HTML that we add via javascript on the 'add_field_button' handler in the embedded JS below --}} {{-- @include ('partials.forms.edit.serial', ['fieldname'=> 'serials['.$loop->iteration.']', 'old_val_name' => 'serials.'.$loop->iteration, 'translated_serial' => trans('admin/hardware/form.serial')])--}} - @php - \Log::error("I is: $i"); - @endphp
@@ -93,18 +90,6 @@ class="col-md-3 control-label">{{ trans('admin/hardware/form.tag') }} {{ $i }}
@include ('partials.forms.edit.serial', ['fieldname'=> 'serials['.$i.']', 'old_val_name' => 'serials.'.$i, 'translated_serial' => trans('admin/hardware/form.serial')]) - {{--
-
- - @error('serials.'.$i ) - - @enderror -
-
--}}
@endforeach
From c6c0a14ee09fcbc7d230e5f71b6d9b0c6f7db3ef Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Tue, 25 Nov 2025 20:23:15 +0000 Subject: [PATCH 3/3] Whoops, used PHP's equal signs instead of MySQL's :/ --- app/Rules/UniqueUndeleted.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Rules/UniqueUndeleted.php b/app/Rules/UniqueUndeleted.php index 9aceae5a73cc..e53274ea8d8b 100644 --- a/app/Rules/UniqueUndeleted.php +++ b/app/Rules/UniqueUndeleted.php @@ -42,11 +42,11 @@ public function setValidator(Validator $validator): static public function validate(string $attribute, mixed $value, Closure $fail): void { $query = DB::table($this->table)->whereNull('deleted_at'); - $query->where($this->columns[0], '==', $value); //the first column to check + $query->where($this->columns[0], '=', $value); //the first column to check $translation_string = 'validation.unique_undeleted'; //the normal validation string for a single-column check foreach (array_slice($this->columns, 1) as $column) { $translation_string = 'validation.two_column_unique_undeleted'; - $query->where($column, '==', $this->data[$column]); + $query->where($column, '=', $this->data[$column]); } if ($query->count() > 0) {