mirror of
https://github.com/grokability/snipe-it.git
synced 2026-03-12 17:52:00 +08:00
Merge pull request #16947 from Godmartinz/add-require-serial-to-models
Adds require serial as Asset Model option
This commit is contained in:
@@ -50,6 +50,7 @@ class AssetModelsController extends Controller
|
||||
'fieldset',
|
||||
'deleted_at',
|
||||
'updated_at',
|
||||
'require_serial',
|
||||
];
|
||||
|
||||
$assetmodels = AssetModel::select([
|
||||
@@ -69,6 +70,7 @@ class AssetModelsController extends Controller
|
||||
'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');
|
||||
|
||||
@@ -82,6 +82,7 @@ class AssetModelsController extends Controller
|
||||
$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');
|
||||
@@ -155,7 +156,7 @@ class AssetModelsController extends Controller
|
||||
$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()) {
|
||||
|
||||
@@ -110,17 +110,35 @@ class AssetsController extends Controller
|
||||
// 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 @@ class AssetsController extends Controller
|
||||
$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');
|
||||
@@ -172,7 +190,6 @@ class AssetsController extends Controller
|
||||
|
||||
// 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) {
|
||||
@@ -453,6 +470,13 @@ class AssetsController extends Controller
|
||||
]);
|
||||
|
||||
|
||||
//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 Helper::getRedirectOption($request, $asset->id, 'Assets')
|
||||
->with('success', trans('admin/hardware/message.update.success'));
|
||||
|
||||
@@ -92,7 +92,9 @@ class BulkAssetModelsController extends Controller
|
||||
$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);
|
||||
|
||||
@@ -65,6 +65,7 @@ class AssetModelsTransformer
|
||||
'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,
|
||||
|
||||
@@ -71,6 +71,7 @@ class AssetModel extends SnipeModel
|
||||
'name',
|
||||
'notes',
|
||||
'requestable',
|
||||
'require_serial'
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
@@ -143,6 +143,14 @@ class AssetModelPresenter extends Presenter
|
||||
'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,
|
||||
|
||||
@@ -33,6 +33,7 @@ class AssetModelFactory extends Factory
|
||||
'category_id' => Category::factory(),
|
||||
'model_number' => $this->faker->creditCardNumber(),
|
||||
'notes' => 'Created by demo seeder',
|
||||
'require_serial' => 0,
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('models', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -44,6 +44,8 @@ return [
|
||||
'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',
|
||||
|
||||
@@ -22,6 +22,8 @@ return [
|
||||
'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',
|
||||
|
||||
@@ -91,7 +91,27 @@
|
||||
</div>
|
||||
|
||||
@include ('partials.forms.edit.minimum_quantity')
|
||||
<!-- require serial boolean -->
|
||||
<div class="form-group">
|
||||
<label for="require_serial" class="col-md-3 control-label">
|
||||
{{ trans('admin/hardware/general.require_serial') }}
|
||||
</label>
|
||||
|
||||
<div class="col-md-9">
|
||||
<div class="form-inline" style="display: flex; align-items: center; gap: 8px;">
|
||||
<input type="checkbox" name="require_serial" value="1" id="require_serial" aria-label="require_serial" />
|
||||
<a
|
||||
href="#"
|
||||
data-tooltip="true"
|
||||
title="{{ trans('admin/hardware/general.require_serial_help') }}"
|
||||
style="display: inline-flex; align-items: center;"
|
||||
>
|
||||
<x-icon type="info-circle" />
|
||||
<span class="sr-only">{{ trans('admin/hardware/general.require_serial_help') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- requestable -->
|
||||
<div class="form-group{{ $errors->has('requestable') ? ' has-error' : '' }}">
|
||||
|
||||
@@ -16,6 +16,27 @@
|
||||
@include ('partials.forms.edit.depreciation')
|
||||
@include ('partials.forms.edit.minimum_quantity')
|
||||
|
||||
<!-- require serial boolean -->
|
||||
<div class="form-group">
|
||||
<label for="require_serial" class="col-md-3 control-label">
|
||||
{{ trans('admin/hardware/general.require_serial') }}
|
||||
</label>
|
||||
|
||||
<div class="col-md-9">
|
||||
<div class="form-inline" style="display: flex; align-items: center; gap: 8px;">
|
||||
<input type="checkbox" name="require_serial" value="1" @checked(old('require_serial', $item->require_serial)) id="require_serial" aria-label="require_serial" />
|
||||
<a
|
||||
href="#"
|
||||
data-tooltip="true"
|
||||
title="{{ trans('admin/hardware/general.require_serial_help') }}"
|
||||
style="display: inline-flex; align-items: center;"
|
||||
>
|
||||
<x-icon type="info-circle" />
|
||||
<span class="sr-only">{{ trans('admin/hardware/general.require_serial_help') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- EOL -->
|
||||
|
||||
<div class="form-group {{ $errors->has('eol') ? ' has-error' : '' }}">
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
<div class="form-group {{ $errors->has('serial') ? ' has-error' : '' }}">
|
||||
<label for="{{ $fieldname }}" class="col-md-3 control-label">{{ trans('admin/hardware/form.serial') }} </label>
|
||||
<div class="col-md-7 col-sm-12">
|
||||
<input class="form-control" type="text" name="{{ $fieldname }}" id="{{ $fieldname }}" value="{{ old((isset($old_val_name) ? $old_val_name : $fieldname), $item->serial) }}"{{ (Helper::checkIfRequired($item, 'serial')) ? ' required' : '' }} maxlength="191" />
|
||||
{!! $errors->first('serial', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
|
||||
<input class="form-control" type="text" name="{{ $fieldname }}" id="{{ $fieldname }}" value="{{ old((isset($old_val_name) ? $old_val_name : $fieldname), $item->serial) }}" {{ (Helper::checkIfRequired($item, 'serial') || ($item->model && $item->model->require_serial)) ? ' required' : '' }} maxlength="191" />
|
||||
@error($old_val_name ?? $fieldname)
|
||||
<span class="alert-msg" aria-hidden="true">
|
||||
<i class="fas fa-times" aria-hidden="true"></i> {{ $message }}
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Tests\Feature\Assets\Ui;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -13,4 +15,63 @@ class StoreAssetsTest extends TestCase
|
||||
->get(route('hardware.create'))
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
public function testAssetCanBeStoredWithSerialRequiredAndSerialProvided()
|
||||
{
|
||||
$user = User::factory()->superuser()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$model = AssetModel::factory()->create([
|
||||
'require_serial' => 1,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('hardware.store'), [
|
||||
'model_id' => $model->id,
|
||||
'serials' => [1 => 'ABC123'],
|
||||
'asset_tags' =>[1 => '1234'],
|
||||
'status_id' => 1,
|
||||
// other required fields...
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$response->assertSessionHas('success-unescaped');
|
||||
$this->assertNotEquals(
|
||||
trans('admin/hardware/form.serial_required'),
|
||||
session('error')
|
||||
);
|
||||
$this->assertDatabaseHas('assets', [
|
||||
'model_id' => $model->id,
|
||||
'serial' => 'ABC123',
|
||||
'asset_tag' => '1234',
|
||||
]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testAssetCannotBeStoredIfSerialRequiredAndMissing()
|
||||
{
|
||||
$user = User::factory()->superuser()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$model = AssetModel::factory()->create([
|
||||
'require_serial' => 1,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('hardware.store'), [
|
||||
'model_id' => $model->id,
|
||||
'serials' => [], // ← serial missing
|
||||
'asset_tags' => [1 => '1234'],
|
||||
'status_id' => 1,
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$response->assertSessionHasErrors(['serials.1']);
|
||||
|
||||
$this->assertDatabaseMissing('assets', [
|
||||
'model_id' => $model->id,
|
||||
'asset_tag' => '1234',
|
||||
]);
|
||||
|
||||
$response->assertSessionMissing('success-unescaped');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user