diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index 954da30870eb..ad1ddf6543bc 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -50,6 +50,7 @@ public function index(Request $request) : JsonResponse | array 'fieldset', 'deleted_at', 'updated_at', + 'require_serial', ]; $assetmodels = AssetModel::select([ @@ -69,6 +70,7 @@ public function index(Request $request) : JsonResponse | array 'models.fieldset_id', 'models.deleted_at', 'models.updated_at', + 'models.require_serial' ]) ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser') ->withCount('assets as assets_count'); diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 079558877677..0bbe19bdc6e5 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -82,6 +82,7 @@ public function store(StoreAssetModelRequest $request) : RedirectResponse $model->notes = $request->input('notes'); $model->created_by = auth()->id(); $model->requestable = $request->has('requestable'); + $model->require_serial = $request->input('require_serial', 0); if ($request->input('fieldset_id') != '') { $model->fieldset_id = $request->input('fieldset_id'); @@ -142,7 +143,7 @@ public function update(StoreAssetModelRequest $request, AssetModel $model) : Red $model->category_id = $request->input('category_id'); $model->notes = $request->input('notes'); $model->requestable = $request->input('requestable', '0'); - + $model->require_serial = $request->input('require_serial', 0); $model->fieldset_id = $request->input('fieldset_id'); if ($model->save()) { diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 12cb93ff3ec6..7f10f6ff6275 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -110,17 +110,35 @@ public function store(ImageUploadRequest $request) : RedirectResponse // This is only necessary on create, not update, since bulk editing is handled // differently $asset_tags = $request->input('asset_tags'); + $model = AssetModel::find($request->input('model_id')); + $serial_errors = []; + $serials = $request->input('serials'); $settings = Setting::getSettings(); + //Validate required serial based on model setting + for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { + if ($model && $model->require_serial === 1 && empty($serials[$a])) { + $serial_errors["serials.$a"] = trans('admin/hardware/form.serial_required', ['number' => $a]); + } + + } + + if (!empty($serial_errors)) { + return redirect()->back() + ->withInput() + ->withErrors($serial_errors); + } + + $asset = null; + $companyId = Company::getIdForCurrentUser($request->input('company_id')); $successes = []; $failures = []; - $serials = $request->input('serials'); - $asset = null; - for ($a = 1; $a <= count($asset_tags); $a++) { + for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { $asset = new Asset(); - $asset->model()->associate(AssetModel::find($request->input('model_id'))); + + $asset->model()->associate($model); $asset->name = $request->input('name'); // Check for a corresponding serial @@ -132,7 +150,7 @@ public function store(ImageUploadRequest $request) : RedirectResponse $asset->asset_tag = $asset_tags[$a]; } - $asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); + $asset->company_id = $companyId; $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); $asset->notes = $request->input('notes'); @@ -164,7 +182,6 @@ public function store(ImageUploadRequest $request) : RedirectResponse // Update custom fields in the database. // Validation for these fields is handled through the AssetRequest form request - $model = AssetModel::find($request->get('model_id')); if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { @@ -413,6 +430,13 @@ public function update(ImageUploadRequest $request, Asset $asset) : RedirectResp session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + //Validate required serial based on model setting + if ($model && $model->require_serial === 1 && empty($serial[1])) { + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('warning', trans('admin/hardware/form.serial_required_post_model_update', [ + 'asset_model' => $model->name + ])); + } if ($asset->save()) { return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) ->with('success', trans('admin/hardware/message.update.success')); diff --git a/app/Http/Controllers/BulkAssetModelsController.php b/app/Http/Controllers/BulkAssetModelsController.php index 5f64ea083868..c1ecf309fbc7 100644 --- a/app/Http/Controllers/BulkAssetModelsController.php +++ b/app/Http/Controllers/BulkAssetModelsController.php @@ -92,7 +92,9 @@ public function update(Request $request): View | RedirectResponse $update_array['min_amt'] = $request->input('min_amt'); } - + if ($request->filled('require_serial')) { + $update_array['require_serial'] = $request->input('require_serial'); + } if (count($update_array) > 0) { AssetModel::whereIn('id', $models_raw_array)->update($update_array); diff --git a/app/Http/Transformers/AssetModelsTransformer.php b/app/Http/Transformers/AssetModelsTransformer.php index 2d47ca47dbd4..9c21d724220c 100644 --- a/app/Http/Transformers/AssetModelsTransformer.php +++ b/app/Http/Transformers/AssetModelsTransformer.php @@ -65,6 +65,7 @@ public function transformAssetModel(AssetModel $assetmodel) 'default_fieldset_values' => $default_field_values, 'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None', 'requestable' => ($assetmodel->requestable == '1') ? true : false, + 'require_serial' => $assetmodel->require_serial, 'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes), 'created_by' => ($assetmodel->adminuser) ? [ 'id' => (int) $assetmodel->adminuser->id, diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index 8d60474b9622..cd1376c475ba 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -69,6 +69,7 @@ class AssetModel extends SnipeModel 'name', 'notes', 'requestable', + 'require_serial' ]; use Searchable; diff --git a/app/Presenters/AssetModelPresenter.php b/app/Presenters/AssetModelPresenter.php index 2ed019eaea4f..601a59fabe90 100644 --- a/app/Presenters/AssetModelPresenter.php +++ b/app/Presenters/AssetModelPresenter.php @@ -142,6 +142,14 @@ public static function dataTableLayout() 'title' => trans('admin/hardware/general.requestable'), 'formatter' => 'trueFalseFormatter', ], + [ + 'field' => 'require_serial', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('admin/hardware/general.require_serial'), + 'formatter' => 'trueFalseFormatter', + ], [ 'field' => 'notes', 'searchable' => true, diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index 3adb933e3e42..ac31b57984aa 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -33,6 +33,7 @@ public function definition() 'category_id' => Category::factory(), 'model_number' => $this->faker->creditCardNumber(), 'notes' => 'Created by demo seeder', + 'require_serial' => 0, ]; } diff --git a/database/migrations/2025_05_12_183803_add_req_serial_bool_to_models_table.php b/database/migrations/2025_05_12_183803_add_req_serial_bool_to_models_table.php new file mode 100644 index 000000000000..ee0e37a62812 --- /dev/null +++ b/database/migrations/2025_05_12_183803_add_req_serial_bool_to_models_table.php @@ -0,0 +1,28 @@ +boolean( 'require_serial')->after('category_id')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('models', function (Blueprint $table) { + $table->dropColumn('require_serial'); + }); + } +}; diff --git a/resources/lang/en-US/admin/hardware/form.php b/resources/lang/en-US/admin/hardware/form.php index 8fbd0b4e8738..dc4754e71a9d 100644 --- a/resources/lang/en-US/admin/hardware/form.php +++ b/resources/lang/en-US/admin/hardware/form.php @@ -44,6 +44,8 @@ 'redirect_to_checked_out_to' => 'Go to Checked Out to', 'select_statustype' => 'Select Status Type', 'serial' => 'Serial', + 'serial_required' => 'Asset :number requires a serial number', + 'serial_required_post_model_update' => ':asset_model have been updated to require a serial number. Please add a serial number for this asset.', 'status' => 'Status', 'tag' => 'Asset Tag', 'update' => 'Asset Update', diff --git a/resources/lang/en-US/admin/hardware/general.php b/resources/lang/en-US/admin/hardware/general.php index 6740476574a3..235e1a3dc58b 100644 --- a/resources/lang/en-US/admin/hardware/general.php +++ b/resources/lang/en-US/admin/hardware/general.php @@ -21,6 +21,8 @@ 'requested' => 'Requested', 'not_requestable' => 'Not Requestable', 'requestable_status_warning' => 'Do not change requestable status', + 'require_serial' => 'Require Serial Number', + 'require_serial_help' => 'A serial number will be required when creating a new asset of this model.', 'restore' => 'Restore Asset', 'pending' => 'Pending', 'undeployable' => 'Undeployable', diff --git a/resources/views/models/bulk-edit.blade.php b/resources/views/models/bulk-edit.blade.php index 8131a470bcbb..2e8d4925b422 100644 --- a/resources/views/models/bulk-edit.blade.php +++ b/resources/views/models/bulk-edit.blade.php @@ -91,7 +91,27 @@ class="js-fieldset-field" @include ('partials.forms.edit.minimum_quantity') + +