Merge pull request #16297 from snipe/add_field_to_checkin_checkout

Fixed #6188 - Added custom fields to checkin/checkout screens
This commit is contained in:
snipe 2025-02-22 19:09:51 +00:00 committed by GitHub
commit 9b44dfd9b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 191 additions and 48 deletions

View file

@ -27,18 +27,12 @@ class AssetCheckinController extends Controller
* @param string $backto
* @since [v1.0]
*/
public function create($assetId, $backto = null) : View | RedirectResponse
public function create(Asset $asset, $backto = null) : View | RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('checkin', $asset);
// This asset is already checked in, redirect
if (is_null($asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
@ -47,7 +41,11 @@ class AssetCheckinController extends Controller
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto)->with('table_name', 'Assets');
return view('hardware/checkin', compact('asset'))
->with('item', $asset)
->with('statusLabel_list', Helper::statusLabelList())
->with('backto', $backto)
->with('table_name', 'Assets');
}
/**
@ -91,6 +89,9 @@ class AssetCheckinController extends Controller
$asset->status_id = e($request->get('status_id'));
}
// Add any custom fields that should be included in the checkout
$asset->customFieldsForCheckinCheckout('display_checkin');
$this->migrateLegacyLocations($asset);
$asset->location_id = $asset->rtd_location_id;
@ -128,6 +129,9 @@ class AssetCheckinController extends Controller
session()->put('redirect_option', $request->get('redirect_option'));
// Add any custom fields that should be included in the checkout
$asset->customFieldsForCheckinCheckout('display_checkin');
if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));

View file

@ -39,7 +39,8 @@ class AssetCheckoutController extends Controller
if ($asset->availableForCheckout()) {
return view('hardware/checkout', compact('asset'))
->with('statusLabel_list', Helper::deployableStatusLabelList())
->with('table_name', 'Assets');
->with('table_name', 'Assets')
->with('item', $asset);
}
return redirect()->route('hardware.index')
@ -88,6 +89,7 @@ class AssetCheckoutController extends Controller
$asset->status_id = $request->get('status_id');
}
if(!empty($asset->licenseseats->all())){
if(request('checkout_to_type') == 'user') {
foreach ($asset->licenseseats as $seat){
@ -97,23 +99,26 @@ class AssetCheckoutController extends Controller
}
}
// Add any custom fields that should be included in the checkout
$asset->customFieldsForCheckinCheckout('display_checkout');
$settings = \App\Models\Setting::getSettings();
// We have to check whether $target->company_id is null here since locations don't have a company yet
if (($settings->full_multiple_companies_support) && ((!is_null($target->company_id)) && (!is_null($asset->company_id)))) {
if ($target->company_id != $asset->company_id){
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
return redirect()->route('hardware.checkout.create', $asset)->with('error', trans('general.error_user_company'));
}
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success', trans('admin/hardware/message.checkout.success'));
}
// Redirect to the asset management page with error
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
return redirect()->route("hardware.checkout.create", $asset)->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
} catch (ModelNotFoundException $e) {
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
} catch (CheckoutNotAllowed $e) {

View file

@ -104,6 +104,8 @@ class CustomFieldsController extends Controller
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0),
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
"display_checkin" => $request->get("display_checkin", 0),
"display_checkout" => $request->get("display_checkout", 0),
"created_by" => auth()->id()
]);
@ -246,6 +248,8 @@ class CustomFieldsController extends Controller
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->get("show_in_listview", 0);
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
$field->display_checkin = $request->get("display_checkin", 0);
$field->display_checkout = $request->get("display_checkout", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format'));

View file

@ -413,6 +413,17 @@ class Asset extends Depreciable
return $this->rules;
}
public function customFieldsForCheckinCheckout($checkin_checkout) {
// Check to see if any of the custom fields were included on the form and if they have any values
if (($this->model) && ($this->model->fieldset) && ($this->model->fieldset->fields)) {
foreach ($this->model->fieldset->fields as $field) {
if (($field->{$checkin_checkout} == 1) && (request()->has($field->db_column))){
$this->{$field->db_column} = request()->get($field->db_column);
}
}
}
}
/**
* Establishes the asset -> depreciation relationship

View file

@ -34,7 +34,11 @@ trait Loggable
*/
public function logCheckout($note, $target, $action_date = null, $originalValues = [])
{
$log = new Actionlog;
$fields_array = [];
$log = $this->determineLogItemType($log);
if (auth()->user()) {
$log->created_by = auth()->id();
@ -55,6 +59,7 @@ trait Loggable
$log->target_type = get_class($target);
$log->target_id = $target->id;
// Figure out what the target is
if ($log->target_type == Location::class) {
$log->location_id = $target->id;
@ -64,6 +69,21 @@ trait Loggable
$log->location_id = $target->location_id;
}
if (static::class == Asset::class) {
if ($asset = Asset::find($log->item_id)) {
// add the custom fields that were changed
if ($asset->model->fieldset) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
if ($field->display_checkout == 1) {
$fields_array[$field->db_column] = $asset->{$field->db_column};
}
}
}
}
}
$log->note = $note;
$log->action_date = $action_date;
@ -72,7 +92,10 @@ trait Loggable
}
$changed = [];
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','expected_checkin']));
$array_to_flip = array_keys($fields_array);
$array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']);
$originalValues = array_intersect_key($originalValues, array_flip($array_to_flip));
foreach ($originalValues as $key => $value) {
if ($key == 'action_date' && $value != $action_date) {
@ -119,6 +142,8 @@ trait Loggable
{
$log = new Actionlog;
$fields_array = [];
if($target != null){
$log->target_type = get_class($target);
$log->target_id = $target->id;
@ -135,6 +160,16 @@ trait Loggable
if (static::class == Asset::class) {
if ($asset = Asset::find($log->item_id)) {
$asset->increment('checkin_counter', 1);
// add the custom fields that were changed
if ($asset->model->fieldset) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
if ($field->display_checkin == 1) {
$fields_array[$field->db_column] = $asset->{$field->db_column};
}
}
}
}
}
}
@ -152,9 +187,14 @@ trait Loggable
}
$changed = [];
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','rtd_location_id','expected_checkin']));
$array_to_flip = array_keys($fields_array);
$array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']);
$originalValues = array_intersect_key($originalValues, array_flip($array_to_flip));
foreach ($originalValues as $key => $value) {
if ($key == 'action_date' && $value != $action_date) {
$changed[$key]['old'] = $value;
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');

View file

@ -0,0 +1,30 @@
<?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('custom_fields', function (Blueprint $table) {
$table->boolean('display_checkin')->default(0);
$table->boolean('display_checkout')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('custom_fields', function (Blueprint $table) {
$table->dropColumn('display_checkin');
$table->dropColumn('display_checkout');
});
}
};

View file

@ -57,5 +57,7 @@ return [
'show_in_requestable_list_short' => 'Show in requestable assets list',
'show_in_requestable_list' => 'Show value in requestable assets list. Encrypted fields will not be shown',
'encrypted_options' => 'This field is encrypted, so some display options will not be available.',
'display_checkin' => 'Display in checkin forms',
'display_checkout' => 'Display in checkout forms',
];

View file

@ -157,7 +157,7 @@
<!-- Auto-Add to Future Fieldsets -->
<div class="col-md-9 col-md-offset-3">
<div class="col-md-9 col-md-offset-3" style="padding-bottom: 10px;">
<label class="form-control">
<input type="checkbox" name="auto_add_to_fieldsets" aria-label="auto_add_to_fieldsets" value="1"{{ (old('auto_add_to_fieldsets') || $field->auto_add_to_fieldsets) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.auto_add_to_fieldsets') }}
@ -165,7 +165,7 @@
</div>
<!-- Show in list view -->
<div class="col-md-9 col-md-offset-3">
<div class="col-md-9 col-md-offset-3" style="padding-bottom: 10px;">
<label class="form-control">
<input type="checkbox" name="show_in_listview" aria-label="show_in_listview" value="1"{{ (old('show_in_listview') || $field->show_in_listview) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.show_in_listview') }}
@ -176,7 +176,7 @@
@if ((!$field->id) || ($field->field_encrypted=='0'))
<!-- Show in requestable list view -->
<div class="col-md-9 col-md-offset-3" id="show_in_requestable_list">
<div class="col-md-9 col-md-offset-3" id="show_in_requestable_list" style="padding-bottom: 10px;">
<label class="form-control">
<input type="checkbox" name="show_in_requestable_list" aria-label="show_in_requestable_list" value="1"{{ (old('show_in_requestable_list') || $field->show_in_requestable_list) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.show_in_requestable_list') }}
@ -184,7 +184,7 @@
</div>
<!-- Show in Email -->
<div class="col-md-9 col-md-offset-3" id="show_in_email">
<div class="col-md-9 col-md-offset-3" id="show_in_email" style="padding-bottom: 10px;">
<label class="form-control">
<input type="checkbox" name="show_in_email" aria-label="show_in_email" value="1"{{ (old('show_in_email') || $field->show_in_email) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.show_in_email') }}
@ -192,7 +192,7 @@
</div>
<!-- Value Must be Unique -->
<div class="col-md-9 col-md-offset-3" id="is_unique">
<div class="col-md-9 col-md-offset-3" id="is_unique" style="padding-bottom: 10px;">
<label class="form-control">
<input type="checkbox" name="is_unique" aria-label="is_unique" value="1"{{ (old('is_unique') || $field->is_unique) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.is_unique') }}
@ -200,7 +200,25 @@
</div>
@endif
<!-- Show in View All Assets profile view -->
<!-- Show in Checkout Form -->
<div class="col-md-9 col-md-offset-3" id="display_checkout" style="padding-bottom: 10px;">
<label class="form-control">
<input type="checkbox" name="display_checkout" aria-label="display_checkout" value="1" {{ (old('display_checkout') || $field->display_checkout) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.display_checkout') }}
</label>
</div>
<!-- Show in Checkin Form -->
<div class="col-md-9 col-md-offset-3" id="display_checkin" style="padding-bottom: 10px;">
<label class="form-control">
<input type="checkbox" name="display_checkin" aria-label="display_checkin" value="1" {{ (old('display_checkin') || $field->display_checkin) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.display_checkin') }}
</label>
</div>
<!-- Show in View All Assets profile view -->
<div class="col-md-9 col-md-offset-3" id="display_in_user_view">
<label class="form-control">
<input type="checkbox" name="display_in_user_view" aria-label="display_in_user_view" value="1" {{ (old('display_in_user_view') || $field->display_in_user_view) ? ' checked="checked"' : '' }}>

View file

@ -53,9 +53,9 @@
{{ $asset->model->name }}
@else
<span class="text-danger text-bold">
<x-icon type="warning" />
{{ trans('admin/hardware/general.model_invalid')}}
</span>
<x-icon type="warning" />
{{ trans('admin/hardware/general.model_invalid')}}
</span>
{{ trans('admin/hardware/general.model_invalid_fix')}}
<a href="{{ route('hardware.edit', $asset->id) }}">
<strong>{{ trans('admin/hardware/general.edit') }}</strong>
@ -113,6 +113,18 @@
</div>
</div>
<!-- Custom fields -->
@include("models/custom_fields_form", [
'model' => $asset->model,
'show_display_checkin_fields' => 'true'
])
<!-- Note -->
<div class="form-group {{ $errors->has('note') ? 'error' : '' }}">
<label for="note" class="col-md-3 control-label">

View file

@ -136,6 +136,12 @@
</div>
</div>
<!-- Custom fields -->
@include("models/custom_fields_form", [
'model' => $asset->model,
'show_display_checkout_fields' => 'true'
])
<!-- Note -->
<div class="form-group {{ $errors->has('note') ? 'error' : '' }}">
<label for="note" class="col-md-3 control-label">

View file

@ -830,7 +830,7 @@ dir="{{ Helper::determineLanguageDirection() }}">
<div class="row">
<div class="col-md-12">
<div class="col-md-12" style="margin-bottom: 0px;">
<style>
.breadcrumb-item {
@ -839,7 +839,7 @@ dir="{{ Helper::determineLanguageDirection() }}">
}
</style>
<h1 class="pull-left pagetitle" style="font-size: 22px; margin-top: 10px;">
<h1 class="pull-left pagetitle" style="font-size: 22px; margin-top: 5px;">
@if (Breadcrumbs::has() && (Breadcrumbs::current()->count() > 1))
<ul style="padding-left: 0;">
@ -891,7 +891,7 @@ dir="{{ Helper::determineLanguageDirection() }}">
</section>
<section class="content" id="main" tabindex="-1">
<section class="content" id="main" tabindex="-1" style="padding-top: 0px;">
<!-- Notifications -->
<div class="row">

View file

@ -1,10 +1,18 @@
@if (($model) && ($model->fieldset))
@foreach($model->fieldset->fields AS $field)
@if (
((!isset($show_display_checkin_fields))
|| (($field->display_checkin == '1')
&& ($show_display_checkin_fields =='true'))) &&
((!isset($show_display_checkout_fields))
|| (($field->display_checkout == '1')
&& ($show_display_checkout_fields =='true')))
)
<div class="form-group{{ $errors->has($field->db_column_name()) ? ' has-error' : '' }}">
<label for="{{ $field->db_column_name() }}" class="col-md-3 control-label">{{ $field->name }} </label>
<div class="col-md-7 col-sm-12">
@if ($field->element!='text')
@if ($field->element=='listbox')
@ -83,6 +91,7 @@
</div>
@endif
@endforeach
@endif

View file

@ -1,4 +1,4 @@
<a style="padding-left: 5px; font-size: 15px;" class="text-dark-gray hidden-print" data-trigger="focus" tabindex="0" role="button" data-toggle="popover" title="{{ trans('general.more_info') }}" data-placement="right" data-html="true" data-content="{{ (isset($helpText)) ? $helpText : 'Help Info Missing' }}">
<x-icon type="more-info" style="padding-top: 14px;" />
<x-icon type="more-info" style="padding-top: 9px;" />
<span class="sr-only">{{ trans('general.moreinfo') }}</span>
</a>

View file

@ -101,19 +101,24 @@ Route::group(
[AssetsController::class, 'getLabel']
)->name('label/hardware');
Route::get('{asset}/checkout', [AssetCheckoutController::class, 'create'])->name('hardware.checkout.create')
Route::get('{asset}/checkout', [AssetCheckoutController::class, 'create'])
->name('hardware.checkout.create')
->breadcrumbs(fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.show', $asset)
->push(trans('admin/hardware/general.bulk_checkout'), route('hardware.index'))
->push(trans('admin/hardware/general.checkout'), route('hardware.index'))
);
Route::post('{assetId}/checkout',
[AssetCheckoutController::class, 'store']
)->name('hardware.checkout.store');
Route::get('{assetId}/checkin/{backto?}',
Route::get('{asset}/checkin/{backto?}',
[AssetCheckinController::class, 'create']
)->name('hardware.checkin.create');
)->name('hardware.checkin.create')
->breadcrumbs(fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.show', $asset)
->push(trans('admin/hardware/general.checkin'), route('hardware.index'))
);
Route::post('{assetId}/checkin/{backto?}',
[AssetCheckinController::class, 'store']

View file

@ -18,16 +18,14 @@ class AssetCheckinTest extends TestCase
public function testCheckingInAssetRequiresCorrectPermission()
{
$this->actingAs(User::factory()->create())
->post(route('hardware.checkin.store', [
'assetId' => Asset::factory()->assignedToUser()->create()->id,
]))
->post(route('hardware.checkin.store', [Asset::factory()->assignedToUser()->create()]))
->assertForbidden();
}
public function testCannotCheckInAssetThatIsNotCheckedOut()
{
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => Asset::factory()->create()->id]))
->post(route('hardware.checkin.store', [Asset::factory()->create()]))
->assertStatus(302)
->assertSessionHas('error')
->assertRedirect(route('hardware.index'));
@ -36,7 +34,7 @@ class AssetCheckinTest extends TestCase
public function testCannotStoreAssetCheckinThatIsNotCheckedOut()
{
$this->actingAs(User::factory()->checkinAssets()->create())
->get(route('hardware.checkin.store', ['assetId' => Asset::factory()->create()->id]))
->get(route('hardware.checkin.store', [Asset::factory()->create()]))
->assertStatus(302)
->assertSessionHas('error')
->assertRedirect(route('hardware.index'));
@ -68,7 +66,7 @@ class AssetCheckinTest extends TestCase
$this->actingAs(User::factory()->checkinAssets()->create())
->post(
route('hardware.checkin.store', ['assetId' => $asset->id]),
route('hardware.checkin.store', [$asset]),
[
'name' => 'Changed Name',
'status_id' => $status->id,
@ -101,7 +99,7 @@ class AssetCheckinTest extends TestCase
]);
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
->post(route('hardware.checkin.store', [$asset]));
$this->assertTrue($asset->refresh()->location()->is($rtdLocation));
}
@ -112,7 +110,7 @@ class AssetCheckinTest extends TestCase
$asset = Asset::factory()->assignedToUser()->create();
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]), [
->post(route('hardware.checkin.store', [$asset]), [
'location_id' => $location->id,
'update_default_location' => 0
]);
@ -128,7 +126,7 @@ class AssetCheckinTest extends TestCase
$this->assertNotNull($asset->licenseseats->first()->assigned_to);
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
->post(route('hardware.checkin.store', [$asset]));
$this->assertNull($asset->refresh()->licenseseats->first()->assigned_to);
}
@ -141,7 +139,7 @@ class AssetCheckinTest extends TestCase
]);
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
->post(route('hardware.checkin.store', [$asset]));
$this->assertNull($asset->refresh()->rtd_location_id);
$this->assertEquals($asset->location_id, $asset->rtd_location_id);
@ -154,7 +152,7 @@ class AssetCheckinTest extends TestCase
$acceptance = CheckoutAcceptance::factory()->for($asset, 'checkoutable')->pending()->create();
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
->post(route('hardware.checkin.store', [$asset]));
$this->assertFalse($acceptance->exists(), 'Acceptance was not deleted');
}
@ -165,8 +163,7 @@ class AssetCheckinTest extends TestCase
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route(
'hardware.checkin.store',
['assetId' => Asset::factory()->assignedToUser()->create()->id]
'hardware.checkin.store', [Asset::factory()->assignedToUser()->create()]
), [
'checkin_at' => '2023-01-02',
'note' => 'hello'
@ -185,10 +182,10 @@ class AssetCheckinTest extends TestCase
$asset->forceSave();
$this->actingAs(User::factory()->admin()->create())
->get(route('hardware.checkin.create', ['assetId' => $asset->id]))
->get(route('hardware.checkin.create', [$asset]))
->assertStatus(302)
->assertSessionHas('error')
->assertRedirect(route('hardware.show', $asset->id));
->assertRedirect(route('hardware.show', $asset));
}
public function testAssetCheckinPagePostIsRedirectedIfModelIsInvalid()
@ -198,7 +195,7 @@ class AssetCheckinTest extends TestCase
$asset->forceSave();
$this->actingAs(User::factory()->admin()->create())
->post(route('hardware.checkin.store', ['assetId' => $asset->id]))
->post(route('hardware.checkin.store', $asset))
->assertStatus(302)
->assertSessionHas('error')
->assertRedirect(route('hardware.show', $asset));