diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index b234e9cbf1..5f901ef490 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -1032,25 +1032,39 @@ class AssetsController extends Controller { $this->authorize('audit', Asset::class); - $rules = [ - 'asset_tag' => 'required', - 'location_id' => 'exists:locations,id|nullable|numeric', - 'next_audit_date' => 'date|nullable', - ]; - - $validator = Validator::make($request->all(), $rules); - if ($validator->fails()) { - return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all())); - } $settings = Setting::getSettings(); $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); + // No tag passed - return an error + if (!$request->filled('asset_tag')) { + return response()->json(Helper::formatStandardApiResponse('error', [ + 'asset_tag'=> '', + 'error'=> trans('admin/hardware/message.no_tag'), + ], trans('admin/hardware/message.no_tag')), 200); + } + + $asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first(); if ($asset) { - // We don't want to log this as a normal update, so let's bypass that + + /** + * Even though we do a save() further down, we don't want to log this as a "normal" asset update, + * which would trigger the Asset Observer and would log an asset *update* log entry (because the + * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to + * the audit log entry we're creating through this controller. + * + * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass + * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher() + * will bypass normal model-level validation that's usually handled at the observer ) + * + * We handle validation on the save() by checking if the asset is valid via the ->isValid() method, + * which manually invokes Watson Validating to make sure the asset's model is valid. + * + * @see \App\Observers\AssetObserver::updating() + */ $asset->unsetEventDispatcher(); $asset->next_audit_date = $dt; @@ -1066,8 +1080,12 @@ class AssetsController extends Controller $asset->last_audit_date = date('Y-m-d H:i:s'); - if ($asset->save()) { - $log = $asset->logAudit(request('note'), request('location_id')); + /** + * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly. + * We have to invoke this manually because of the unsetEventDispatcher() above.) + */ + if ($asset->isValid() && $asset->save()) { + $asset->logAudit(request('note'), request('location_id')); return response()->json(Helper::formatStandardApiResponse('success', [ 'asset_tag'=> e($asset->asset_tag), @@ -1075,9 +1093,23 @@ class AssetsController extends Controller 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date), ], trans('admin/hardware/message.audit.success'))); } + + // Asset failed validation or was not able to be saved + return response()->json(Helper::formatStandardApiResponse('error', [ + 'asset_tag'=> e($asset->asset_tag), + 'error'=> $asset->getErrors()->first(), + ], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200); + } - return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.e($request->input('asset_tag')).' not found')); + + // No matching asset for the asset tag that was passed. + return response()->json(Helper::formatStandardApiResponse('error', [ + 'asset_tag'=> e($request->input('asset_tag')), + 'error'=> trans('admin/hardware/message.audit.error'), + ], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200); + + } diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 20a5564a44..e817e72410 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\Models\Actionlog; -use App\Models\Manufacturer; +use App\Http\Requests\UploadFileRequest; use Illuminate\Support\Facades\Log; use App\Models\Asset; use App\Models\AssetModel; @@ -308,7 +308,8 @@ class AssetsController extends Controller $asset->status_id = $request->input('status_id', null); $asset->warranty_months = $request->input('warranty_months', null); $asset->purchase_cost = $request->input('purchase_cost', null); - $asset->purchase_date = $request->input('purchase_date', null); + $asset->purchase_date = $request->input('purchase_date', null); + $asset->next_audit_date = $request->input('next_audit_date', null); if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) { $asset->purchase_date = $request->input('purchase_date', null); $asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d'); @@ -862,7 +863,7 @@ class AssetsController extends Controller } - public function auditStore(Request $request, $id) + public function auditStore(UploadFileRequest $request, $id) { $this->authorize('audit', Asset::class); @@ -879,7 +880,21 @@ class AssetsController extends Controller $asset = Asset::findOrFail($id); - // We don't want to log this as a normal update, so let's bypass that + /** + * Even though we do a save() further down, we don't want to log this as a "normal" asset update, + * which would trigger the Asset Observer and would log an asset *update* log entry (because the + * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to + * the audit log entry we're creating through this controller. + * + * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass + * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher() + * will bypass normal model-level validation that's usually handled at the observer ) + * + * We handle validation on the save() by checking if the asset is valid via the ->isValid() method, + * which manually invokes Watson Validating to make sure the asset's model is valid. + * + * @see \App\Observers\AssetObserver::updating() + */ $asset->unsetEventDispatcher(); $asset->next_audit_date = $request->input('next_audit_date'); @@ -888,29 +903,26 @@ class AssetsController extends Controller // Check to see if they checked the box to update the physical location, // not just note it in the audit notes if ($request->input('update_location') == '1') { - Log::debug('update location in audit'); $asset->location_id = $request->input('location_id'); } - if ($asset->save()) { - $file_name = ''; - // Upload an image, if attached - if ($request->hasFile('image')) { - $path = 'private_uploads/audits'; - if (! Storage::exists($path)) { - Storage::makeDirectory($path, 775); - } - $upload = $image = $request->file('image'); - $ext = $image->getClientOriginalExtension(); - $file_name = 'audit-'.str_random(18).'.'.$ext; - Storage::putFileAs($path, $upload, $file_name); - } + /** + * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly. + * We have to invoke this manually because of the unsetEventDispatcher() above.) + */ + if ($asset->isValid() && $asset->save()) { + // Create the image (if one was chosen.) + if ($request->hasFile('image')) { + $file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image')); + } $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name); return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success')); } + + return redirect()->back()->withInput()->withErrors($asset->getErrors()); } public function getRequestedIndex($user_id = null) diff --git a/app/Models/Asset.php b/app/Models/Asset.php index c5373a04e2..f31b872841 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -99,7 +99,8 @@ class Asset extends Depreciable 'last_checkin' => 'nullable|date_format:Y-m-d H:i:s', 'expected_checkin' => 'nullable|date', 'last_audit_date' => 'nullable|date_format:Y-m-d H:i:s', - 'next_audit_date' => 'nullable|date|after:last_audit_date', + // 'next_audit_date' => 'nullable|date|after:last_audit_date', + 'next_audit_date' => 'nullable|date', 'location_id' => 'nullable|exists:locations,id', 'rtd_location_id' => 'nullable|exists:locations,id', 'purchase_date' => 'nullable|date|date_format:Y-m-d', @@ -907,6 +908,23 @@ class Asset extends Depreciable } + + /** + * Determine whether this asset's next audit date is before the last audit date + * + * @return bool + * @since [v6.4.1] + * @author [A. Gianotto] [] + * */ + public function checkInvalidNextAuditDate() + { + if (($this->last_audit_date) && ($this->next_audit_date) && ($this->last_audit_date > $this->next_audit_date)) { + return true; + } + return false; + } + + /** * Checks for a category-specific EULA, and if that doesn't exist, * checks for a settings level EULA @@ -944,6 +962,25 @@ class Asset extends Depreciable * ----------------------------------------------- **/ + /** + * Make sure the next_audit_date is formatted as Y-m-d. + * + * This is kind of dumb and confusing, since we already cast it that way AND it's a date field + * in the database, but here we are. + * + * @param $value + * @return void + */ + public function getNextAuditDateAttribute($value) + { + return $this->attributes['next_audit_date'] = $value ? Carbon::parse($value)->format('Y-m-d') : null; + } + + public function setNextAuditDateAttribute($value) + { + $this->attributes['next_audit_date'] = $value ? Carbon::parse($value)->format('Y-m-d') : null; + } + /** * This sets the requestable to a boolean 0 or 1. This accounts for forms or API calls that * explicitly pass the requestable field but it has a null or empty value. diff --git a/resources/lang/en-US/admin/hardware/message.php b/resources/lang/en-US/admin/hardware/message.php index 96b4b6c949..32698b1c07 100644 --- a/resources/lang/en-US/admin/hardware/message.php +++ b/resources/lang/en-US/admin/hardware/message.php @@ -5,8 +5,11 @@ return [ 'undeployable' => 'Warning: This asset has been marked as currently undeployable. If this status has changed, please update the asset status.', 'does_not_exist' => 'Asset does not exist.', + 'does_not_exist_var'=> 'Asset with tag :asset_tag not found.', + 'no_tag' => 'No asset tag provided.', 'does_not_exist_or_not_requestable' => 'That asset does not exist or is not requestable.', 'assoc_users' => 'This asset is currently checked out to a user and cannot be deleted. Please check the asset in first, and then try deleting again. ', + 'warning_audit_date_mismatch' => 'This asset\'s next audit date (:next_audit_date) is before the last audit date (:last_audit_date). Please update the next audit date.', 'create' => [ 'error' => 'Asset was not created, please try again. :(', @@ -31,7 +34,7 @@ return [ ], 'audit' => [ - 'error' => 'Asset audit was unsuccessful. Please try again.', + 'error' => 'Asset audit unsuccessful: :error ', 'success' => 'Asset audit successfully logged.', ], diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index 9510921b1f..7ee1557c51 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -202,6 +202,8 @@ return [ 'new_password' => 'New Password', 'next' => 'Next', 'next_audit_date' => 'Next Audit Date', + 'next_audit_date_help' => 'If you use auditing in your organization, this is usually automatically calculated based on the asset's last audit date and audit frequency (in Admin Settings > Alerts) and you can leave this blank. You can manually set this date here if you need to, but it must be later than the last audit date. ', + 'audit_images_help' => 'You can find audit images in the asset\'s history tab.', 'no_email' => 'No email address associated with this user', 'last_audit' => 'Last Audit', 'new' => 'new!', diff --git a/resources/views/hardware/audit.blade.php b/resources/views/hardware/audit.blade.php index 8b859a40a3..9713b6fc1e 100644 --- a/resources/views/hardware/audit.blade.php +++ b/resources/views/hardware/audit.blade.php @@ -34,7 +34,7 @@ {{csrf_field()}} @if ($asset->model->name) -
+
{{ Form::label('name', trans('admin/hardware/form.model'), array('class' => 'col-md-3 control-label')) }}

{{ $asset->model->name }}

@@ -43,7 +43,7 @@ @endif -
+
{{ Form::label('name', trans('admin/hardware/form.name'), array('class' => 'col-md-3 control-label')) }}

{{ $asset->name }}

@@ -66,21 +66,40 @@
+ +
+ +
+ +

+ @if ($asset->last_audit_date) + {{ Helper::getFormattedDateObject($asset->last_audit_date, 'datetime', false) }} + @else + {{ trans('admin/settings/general.none') }} + @endif +

+
+
+ + -
+
{{ Form::label('name', trans('general.next_audit_date'), array('class' => 'col-md-3 control-label')) }} -
+
{!! $errors->first('next_audit_date', '') !!} +

{!! trans('general.next_audit_date_help') !!}

-
+
{{ Form::label('note', trans('admin/hardware/form.notes'), array('class' => 'col-md-3 control-label')) }}
@@ -88,13 +107,8 @@
- - - @include ('partials.forms.edit.image-upload') - - - - + + @include ('partials.forms.edit.image-upload', ['help_text' => trans('general.audit_images_help')])
diff --git a/resources/views/hardware/bulk.blade.php b/resources/views/hardware/bulk.blade.php index d4f0544851..371f0c2549 100755 --- a/resources/views/hardware/bulk.blade.php +++ b/resources/views/hardware/bulk.blade.php @@ -164,6 +164,8 @@ {!! $errors->first('next_audit_date', '') !!} + +
+
+

{!! trans('general.next_audit_date_help') !!}

+
diff --git a/resources/views/hardware/edit.blade.php b/resources/views/hardware/edit.blade.php index 4c1c1457b6..b585db2135 100755 --- a/resources/views/hardware/edit.blade.php +++ b/resources/views/hardware/edit.blade.php @@ -109,6 +109,29 @@ @include ('partials.forms.edit.name', ['translated_name' => trans('admin/hardware/form.name')]) @include ('partials.forms.edit.warranty') + +
+ + + +
+
+ + +
+
+
+ {!! $errors->first('next_audit_date', '') !!} +

{!! trans('general.next_audit_date_help') !!}

+
+ +
+ + + +
diff --git a/resources/views/hardware/quickscan.blade.php b/resources/views/hardware/quickscan.blade.php index 1745d74f38..4ba4321c63 100644 --- a/resources/views/hardware/quickscan.blade.php +++ b/resources/views/hardware/quickscan.blade.php @@ -23,9 +23,7 @@
-
-

{{ trans('general.bulkaudit') }}

-
+
{{csrf_field()}} @@ -185,7 +183,7 @@ } else { var messages = ''; } - $('#audited tbody').prepend("" + asset_tag + "" + messages + ""); + $('#audited tbody').prepend("" + data.payload.asset_tag + "" + messages + ""); } function incrementOnSuccess() { diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index dfb1513936..589803d96c 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -19,6 +19,23 @@
@endif + @if ($asset->checkInvalidNextAuditDate()) +
+
+

{{ trans('general.warning', + [ + 'warning' => trans('admin/hardware/message.warning_audit_date_mismatch', + [ + 'last_audit_date' => Helper::getFormattedDateObject($asset->last_audit_date, 'date', false), + 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date', false) + ] + ) + ] + ) }}

+
+
+ @endif + @if ($asset->deleted_at!='')
@@ -237,7 +254,8 @@
- {{ \App\Helpers\Helper::getFormattedDateObject($audit_log->created_at, 'date', false) }} + {!! $asset->checkInvalidNextAuditDate() ? '' : '' !!} + {{ Helper::getFormattedDateObject($audit_log->created_at, 'date', false) }} @if ($audit_log->user) (by {{ link_to_route('users.show', $audit_log->user->present()->fullname(), [$audit_log->user->id]) }}) @endif @@ -254,6 +272,7 @@
+ {!! $asset->checkInvalidNextAuditDate() ? '' : '' !!} {{ Helper::getFormattedDateObject($asset->next_audit_date, 'date', false) }}
diff --git a/resources/views/partials/forms/edit/image-upload.blade.php b/resources/views/partials/forms/edit/image-upload.blade.php index 2e9ac38558..fca1068844 100644 --- a/resources/views/partials/forms/edit/image-upload.blade.php +++ b/resources/views/partials/forms/edit/image-upload.blade.php @@ -32,7 +32,10 @@ -

{{ trans('general.image_filetypes_help', ['size' => Helper::file_upload_max_size_readable()]) }}

+

{{ trans('general.image_filetypes_help', ['size' => Helper::file_upload_max_size_readable()]) }} {{ $help_text ?? '' }}

+ + + {!! $errors->first('image', '') !!}