Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net> # Conflicts: # public/css/build/app.css # public/css/build/overrides.css # public/css/dist/all.css # public/mix-manifest.json
This commit is contained in:
commit
f1d006c236
40 changed files with 596 additions and 150 deletions
|
@ -34,6 +34,7 @@ use Illuminate\Support\Facades\Log;
|
|||
use Illuminate\Support\Facades\Route;
|
||||
use App\View\Label;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1064,7 +1065,7 @@ class AssetsController extends Controller
|
|||
* @param int $id
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function audit(Request $request): JsonResponse
|
||||
public function audit(Request $request, Asset $asset): JsonResponse
|
||||
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
|
@ -1072,36 +1073,15 @@ class AssetsController extends Controller
|
|||
$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);
|
||||
// Allow the asset tag to be passed in the payload (legacy method)
|
||||
if ($request->filled('asset_tag')) {
|
||||
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
|
||||
}
|
||||
|
||||
|
||||
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
|
||||
|
||||
|
||||
if ($asset) {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
$asset->next_audit_date = $dt;
|
||||
|
||||
if ($request->filled('next_audit_date')) {
|
||||
|
@ -1116,33 +1096,89 @@ class AssetsController extends Controller
|
|||
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
// Set up the payload for re-display in the API response
|
||||
$payload = [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => $request->input('note'),
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 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 (($asset->model) && ($asset->model->fieldset)) {
|
||||
$payload['custom_fields'] = [];
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
|
||||
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} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
$payload['custom_fields'][$field->db_column] = $request->input($field->db_column);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Validate custom fields
|
||||
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
|
||||
|
||||
// Validate the rest of the data before we turn off the event dispatcher
|
||||
if ($asset->isInvalid()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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()
|
||||
* @see \App\Models\Asset::save()
|
||||
*/
|
||||
|
||||
$asset->unsetEventDispatcher();
|
||||
|
||||
|
||||
/**
|
||||
* 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),
|
||||
'note' => e($request->input('note')),
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
||||
], trans('admin/hardware/message.audit.success')));
|
||||
$asset->logAudit(request('note'), request('location_id'), null, $originalValues);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $payload, 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);
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Events\CheckoutableCheckedIn;
|
|||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Requests\UpdateAssetRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
@ -390,26 +391,26 @@ class AssetsController extends Controller
|
|||
$asset = $request->handleImages($asset);
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handlded through the AssetRequest form request
|
||||
// FIXME: No idea why this is returning a Builder error on db_column_name.
|
||||
// Need to investigate and fix. Using static method for now.
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
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));
|
||||
if ($request->has($field->db_column)) {
|
||||
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} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -865,13 +866,6 @@ class AssetsController extends Controller
|
|||
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
|
||||
}
|
||||
|
||||
public function audit(Asset $asset)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$this->authorize('audit', Asset::class);
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
|
||||
}
|
||||
|
||||
public function dueForAudit()
|
||||
{
|
||||
|
@ -888,19 +882,59 @@ class AssetsController extends Controller
|
|||
}
|
||||
|
||||
|
||||
public function audit(Asset $asset)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$this->authorize('audit', Asset::class);
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list');
|
||||
}
|
||||
|
||||
public function auditStore(UploadFileRequest $request, Asset $asset)
|
||||
{
|
||||
|
||||
$this->authorize('audit', Asset::class);
|
||||
|
||||
$rules = [
|
||||
'location_id' => 'exists:locations,id|nullable|numeric',
|
||||
'next_audit_date' => 'date|nullable',
|
||||
];
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
|
||||
// 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') {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
// Update custom fields in the database
|
||||
if (($asset->model) && ($asset->model->fieldset)) {
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
|
||||
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} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate custom fields
|
||||
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
|
||||
|
||||
// Validate the rest of the data before we turn off the event dispatcher
|
||||
if ($asset->isInvalid()) {
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -917,18 +951,11 @@ class AssetsController extends Controller
|
|||
* which manually invokes Watson Validating to make sure the asset's model is valid.
|
||||
*
|
||||
* @see \App\Observers\AssetObserver::updating()
|
||||
* @see \App\Models\Asset::save()
|
||||
*/
|
||||
|
||||
$asset->unsetEventDispatcher();
|
||||
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
// 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') {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
|
||||
|
@ -942,7 +969,7 @@ class AssetsController extends Controller
|
|||
$file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image'));
|
||||
}
|
||||
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
|
||||
return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
|
||||
}
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@ class CustomFieldsController extends Controller
|
|||
"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),
|
||||
"display_audit" => $request->get("display_audit", 0),
|
||||
"created_by" => auth()->id()
|
||||
]);
|
||||
|
||||
|
@ -250,6 +251,7 @@ class CustomFieldsController extends Controller
|
|||
$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);
|
||||
$field->display_audit = $request->get("display_audit", 0);
|
||||
|
||||
if ($request->get('format') == 'CUSTOM REGEX') {
|
||||
$field->format = e($request->get('custom_format'));
|
||||
|
|
|
@ -243,7 +243,7 @@ class ReportsController extends Controller
|
|||
|
||||
$header = [
|
||||
trans('general.date'),
|
||||
trans('general.admin'),
|
||||
trans('general.created_by'),
|
||||
trans('general.action'),
|
||||
trans('general.type'),
|
||||
trans('general.item'),
|
||||
|
|
|
@ -10,19 +10,36 @@ trait MayContainCustomFields
|
|||
// this gets called automatically on a form request
|
||||
public function withValidator($validator)
|
||||
{
|
||||
// find the model
|
||||
if ($this->method() == 'POST') {
|
||||
$asset_model = AssetModel::find($this->model_id);
|
||||
}
|
||||
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
|
||||
$asset_model = $this->asset->model;
|
||||
|
||||
// In case the model is being changed via form
|
||||
if (request()->has('model_id')!='') {
|
||||
|
||||
$asset_model = AssetModel::find(request()->input('model_id'));
|
||||
|
||||
// or if we have it available to route-model-binding
|
||||
} elseif ((request()->route('asset') && (request()->route('asset')->model_id))) {
|
||||
|
||||
$asset_model = AssetModel::find(request()->route('asset')->model_id);
|
||||
|
||||
} else {
|
||||
|
||||
if ($this->method() == 'POST') {
|
||||
$asset_model = AssetModel::find($this->model_id);
|
||||
}
|
||||
|
||||
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
|
||||
$asset_model = $this->asset->model;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// collect the custom fields in the request
|
||||
$validator->after(function ($validator) use ($asset_model) {
|
||||
$request_fields = $this->collect()->keys()->filter(function ($attributes) {
|
||||
return str_starts_with($attributes, '_snipeit_');
|
||||
});
|
||||
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
|
||||
|
||||
// if there are custom fields, find the ones that don't exist on the model's fieldset and add an error to the validator's error bag
|
||||
if (count($request_fields) > 0 && $validator->errors()->isEmpty()) {
|
||||
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
|
||||
->each(function ($request_field_name) use ($request_fields, $validator) {
|
||||
|
|
|
@ -50,6 +50,9 @@ class CustomFieldsTransformer
|
|||
'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false,
|
||||
'auto_add_to_fieldsets' => ($field->auto_add_to_fieldsets == '1') ? true : false,
|
||||
'show_in_listview' => ($field->show_in_listview == '1') ? true : false,
|
||||
'display_checkin' => ($field->display_checkin == '1') ? true : false,
|
||||
'display_checkout' => ($field->display_checkout == '1') ? true : false,
|
||||
'display_audit' => ($field->display_audit == '1') ? true : false,
|
||||
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
|
||||
];
|
||||
|
|
|
@ -57,7 +57,9 @@ class Actionlog extends SnipeModel
|
|||
'user_agent',
|
||||
'item_type',
|
||||
'target_type',
|
||||
'action_source'
|
||||
'action_source',
|
||||
'created_at',
|
||||
'action_date',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -69,7 +71,25 @@ class Actionlog extends SnipeModel
|
|||
'company' => ['name'],
|
||||
'adminuser' => ['first_name','last_name','username', 'email'],
|
||||
'user' => ['first_name','last_name','username', 'email'],
|
||||
'assets' => ['asset_tag','name', 'serial'],
|
||||
'assets' => ['asset_tag','name', 'serial', 'order_number', 'notes', 'purchase_date'],
|
||||
'assets.model' => ['name', 'model_number', 'eol', 'notes'],
|
||||
'assets.model.category' => ['name', 'notes'],
|
||||
'assets.model.manufacturer' => ['name', 'notes'],
|
||||
'licenses' => ['name', 'serial', 'notes', 'order_number', 'license_email', 'license_name', 'purchase_order', 'purchase_date'],
|
||||
'licenses.category' => ['name', 'notes'],
|
||||
'licenses.supplier' => ['name'],
|
||||
'consumables' => ['name', 'notes', 'order_number', 'model_number', 'item_no', 'purchase_date'],
|
||||
'consumables.category' => ['name', 'notes'],
|
||||
'consumables.location' => ['name', 'notes'],
|
||||
'consumables.supplier' => ['name', 'notes'],
|
||||
'components' => ['name', 'notes', 'purchase_date'],
|
||||
'components.category' => ['name', 'notes'],
|
||||
'components.location' => ['name', 'notes'],
|
||||
'components.supplier' => ['name', 'notes'],
|
||||
'accessories' => ['name', 'purchase_date'],
|
||||
'accessories.category' => ['name'],
|
||||
'accessories.location' => ['name', 'notes'],
|
||||
'accessories.supplier' => ['name', 'notes'],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -134,6 +154,54 @@ class Actionlog extends SnipeModel
|
|||
return $this->hasMany(\App\Models\Asset::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> license relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->hasMany(\App\Models\License::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> consumable relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Consumable::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> consumable relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Accessory::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> components relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function components()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Component::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> item type relationship
|
||||
*
|
||||
|
|
|
@ -213,6 +213,31 @@ class Asset extends Depreciable
|
|||
$this->attributes['expected_checkin'] = $value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function customFieldValidationRules()
|
||||
{
|
||||
|
||||
$customFieldValidationRules = [];
|
||||
|
||||
if (($this->model) && ($this->model->fieldset)) {
|
||||
|
||||
foreach ($this->model->fieldset->fields as $field) {
|
||||
|
||||
if ($field->format == 'BOOLEAN'){
|
||||
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
||||
$customFieldValidationRules += $this->model->fieldset->validation_rules();
|
||||
}
|
||||
|
||||
return $customFieldValidationRules;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This handles the custom field validation for assets
|
||||
*
|
||||
|
@ -220,29 +245,7 @@ class Asset extends Depreciable
|
|||
*/
|
||||
public function save(array $params = [])
|
||||
{
|
||||
if ($this->model_id != '') {
|
||||
$model = AssetModel::find($this->model_id);
|
||||
|
||||
if (($model) && ($model->fieldset)) {
|
||||
|
||||
foreach ($model->fieldset->fields as $field){
|
||||
if($field->format == 'BOOLEAN'){
|
||||
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
||||
$this->rules += $model->fieldset->validation_rules();
|
||||
|
||||
if ($this->model->fieldset){
|
||||
foreach ($this->model->fieldset->fields as $field){
|
||||
if($field->format == 'BOOLEAN'){
|
||||
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->rules += $this->customFieldValidationRules();
|
||||
return parent::save($params);
|
||||
}
|
||||
|
||||
|
@ -254,7 +257,7 @@ class Asset extends Depreciable
|
|||
|
||||
/**
|
||||
* Returns the warranty expiration date as Carbon object
|
||||
* @return \Carbon|null
|
||||
* @return \Carbon\Carbon|null
|
||||
*/
|
||||
public function getWarrantyExpiresAttribute()
|
||||
{
|
||||
|
@ -687,6 +690,21 @@ class Asset extends Depreciable
|
|||
->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of audits for this asset
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function audits()
|
||||
{
|
||||
return $this->assetlog()->where('action_type', '=', 'audit')
|
||||
->orderBy('created_at', 'desc')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of checkins for this asset
|
||||
*
|
||||
|
|
|
@ -220,9 +220,41 @@ trait Loggable
|
|||
* @since [v4.0]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logAudit($note, $location_id, $filename = null)
|
||||
public function logAudit($note, $location_id, $filename = null, $originalValues = [])
|
||||
{
|
||||
|
||||
$log = new Actionlog;
|
||||
|
||||
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_audit == 1) {
|
||||
$fields_array[$field->db_column] = $asset->{$field->db_column};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$changed = [];
|
||||
|
||||
unset($originalValues['updated_at'], $originalValues['last_audit_date']);
|
||||
foreach ($originalValues as $key => $value) {
|
||||
|
||||
if ($value != $this->getAttributes()[$key]) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = $this->getAttributes()[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changed)){
|
||||
$log->log_meta = json_encode($changed);
|
||||
}
|
||||
|
||||
|
||||
$location = Location::find($location_id);
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
|
@ -235,6 +267,7 @@ trait Loggable
|
|||
$log->note = $note;
|
||||
$log->created_by = auth()->id();
|
||||
$log->filename = $filename;
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
$log->logaction('audit');
|
||||
|
||||
$params = [
|
||||
|
@ -276,6 +309,7 @@ trait Loggable
|
|||
$log->item_id = $this->id;
|
||||
}
|
||||
$log->location_id = null;
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
$log->note = $note;
|
||||
$log->created_by = $created_by;
|
||||
$log->logaction('create');
|
||||
|
@ -303,6 +337,7 @@ trait Loggable
|
|||
$log->note = $note;
|
||||
$log->target_id = null;
|
||||
$log->created_at = date('Y-m-d H:i:s');
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
$log->filename = $filename;
|
||||
$log->logaction('uploaded');
|
||||
|
||||
|
|
|
@ -229,7 +229,7 @@ class AccessoryPresenter extends Presenter
|
|||
'field' => 'created_by',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => trans('general.admin'),
|
||||
'title' => trans('general.created_by'),
|
||||
'visible' => false,
|
||||
'formatter' => 'usersLinkObjFormatter',
|
||||
],
|
||||
|
|
|
@ -102,6 +102,10 @@ class ActionlogPresenter extends Presenter
|
|||
return 'fas fa-sticky-note';
|
||||
}
|
||||
|
||||
if ($this->action_type == 'audit') {
|
||||
return 'fas fa-clipboard-check';
|
||||
}
|
||||
|
||||
return 'fa-solid fa-rotate-right';
|
||||
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ class LocationPresenter extends Presenter
|
|||
'field' => 'created_by',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => trans('general.admin'),
|
||||
'title' => trans('general.created_by'),
|
||||
'visible' => false,
|
||||
'formatter' => 'usersLinkObjFormatter',
|
||||
],
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?php
|
||||
return array (
|
||||
'app_version' => 'v8.0.4',
|
||||
'full_app_version' => 'v8.0.4 - build 17333-gaf408bb45',
|
||||
'build_version' => '17333',
|
||||
'full_app_version' => 'v8.0.4 - build 17400-gb0b5a9669',
|
||||
'build_version' => '17400',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'gaf408bb45',
|
||||
'full_hash' => 'v8.0.4-135-gaf408bb45',
|
||||
'hash_version' => 'gb0b5a9669',
|
||||
'full_hash' => 'v8.0.4-202-gb0b5a9669',
|
||||
'branch' => 'master',
|
||||
);
|
|
@ -385,6 +385,12 @@ class UserFactory extends Factory
|
|||
return $this->appendPermission(['suppliers.delete' => '1']);
|
||||
}
|
||||
|
||||
public function auditAssets()
|
||||
{
|
||||
return $this->appendPermission(['assets.audit' => '1']);
|
||||
}
|
||||
|
||||
|
||||
private function appendPermission(array $permission)
|
||||
{
|
||||
return $this->state(function ($currentState) use ($permission) {
|
||||
|
|
|
@ -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('custom_fields', function (Blueprint $table) {
|
||||
$table->boolean('display_audit')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('custom_fields', function (Blueprint $table) {
|
||||
$table->dropColumn('display_audit');
|
||||
});
|
||||
}
|
||||
};
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/css/dist/all.css
vendored
2
public/css/dist/all.css
vendored
File diff suppressed because one or more lines are too long
|
@ -2,8 +2,8 @@
|
|||
"/js/build/app.js": "/js/build/app.js?id=970945c192cb3217d5f371a1931d7d77",
|
||||
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=d34ae2483cbe2c77478c45f4006eba55",
|
||||
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=6bf62cdec2477f3176df196fd0c99662",
|
||||
"/css/build/overrides.css": "/css/build/overrides.css?id=3ca26335f461f9d198f3c9869dc0ce52",
|
||||
"/css/build/app.css": "/css/build/app.css?id=35c2aaddd23e600b56f77c95b04523ac",
|
||||
"/css/build/overrides.css": "/css/build/overrides.css?id=12c469a76750af9fee1c92722c563e7e",
|
||||
"/css/build/app.css": "/css/build/app.css?id=1aed878e9211529befa4fdeed11ac52e",
|
||||
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=a67bd93bed52e6a29967fe472de66d6c",
|
||||
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=fc7adb943668ac69fe4b646625a7571f",
|
||||
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=53edc92eb2d272744bc7404ec259930e",
|
||||
|
@ -19,7 +19,7 @@
|
|||
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=091d9625203be910caca3e229afe438f",
|
||||
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=18787b3f00a3be7be38ee4e26cbd2a07",
|
||||
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=119a35ca4c300a14baefbdcbb203d08e",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=d092c5407b7e50f6a01e5e918ec737ee",
|
||||
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
|
||||
|
|
|
@ -886,7 +886,7 @@ th.css-history > .th-inner::before {
|
|||
height: 34px;
|
||||
}
|
||||
|
||||
.form-group.has-error label {
|
||||
.form-group.has-error label, .form-group.has-error .help-block {
|
||||
color: #a94442;
|
||||
}
|
||||
|
||||
|
|
|
@ -59,5 +59,6 @@ return [
|
|||
'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',
|
||||
'display_audit' => 'Display in audit forms',
|
||||
|
||||
];
|
||||
|
|
|
@ -31,6 +31,7 @@ return [
|
|||
'accept_assets_menu' => 'Accept Assets',
|
||||
'accept_item' => 'Accept Item',
|
||||
'audit' => 'Audit',
|
||||
'audits' => 'Audits',
|
||||
'audit_report' => 'Audit Log',
|
||||
'assets' => 'Assets',
|
||||
'assets_audited' => 'assets audited',
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="col-sm-2" data-visible="false" data-sortable="true" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.record_created') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -421,7 +421,7 @@
|
|||
{{ trans('general.date') }}
|
||||
</th>
|
||||
<th data-searchable="false" data-sortable="false" data-field="note">{{ trans('general.notes') }}</th>
|
||||
<th data-searchable="false" data-sortable="false" data-field="admin">{{ trans('general.admin') }}</th>
|
||||
<th data-searchable="false" data-sortable="false" data-field="admin">{{ trans('general.created_by') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
@ -472,7 +472,7 @@
|
|||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th data-visible="true" data-field="action_date" data-sortable="true" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -224,6 +224,14 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Show in Audit Form -->
|
||||
<div class="col-md-9 col-md-offset-3" id="display_audit" style="padding-bottom: 10px;">
|
||||
<label class="form-control">
|
||||
<input type="checkbox" name="display_audit" aria-label="display_audit" value="1" {{ (old('display_audit') || $field->display_audit) ? ' checked="checked"' : '' }}>
|
||||
{{ trans('admin/custom_fields/general.display_audit') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Show in View All Assets profile view -->
|
||||
<div class="col-md-9 col-md-offset-3" id="display_in_user_view">
|
||||
|
|
|
@ -196,6 +196,14 @@
|
|||
</span>
|
||||
</th>
|
||||
|
||||
<th data-sortable="true" data-visible="false" data-searchable="false" class="text-center"
|
||||
data-tooltip="{{ trans('admin/custom_fields/general.display_audit') }}">
|
||||
<x-icon type="due" />
|
||||
<span class="sr-only">
|
||||
{{ trans('admin/custom_fields/general.display_audit') }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
|
||||
<th data-sortable="true" data-searchable="true" class="text-center">{{ trans('admin/custom_fields/general.field_element_short') }}</th>
|
||||
|
||||
|
@ -227,6 +235,7 @@
|
|||
<td class="text-center">{!! ($field->is_unique=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->display_checkin=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->display_checkout=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->display_audit=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td>{{ $field->element }}</td>
|
||||
<td>
|
||||
@foreach($field->fieldset as $fieldset)
|
||||
|
|
|
@ -115,6 +115,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') ? ' has-error' : '' }}">
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
var formData = $('#audit-form').serializeArray();
|
||||
|
||||
$.ajax({
|
||||
url: "{{ route('api.asset.audit') }}",
|
||||
url: "{{ route('api.asset.audit.legacy') }}",
|
||||
type : 'POST',
|
||||
headers: {
|
||||
"X-Requested-With": 'XMLHttpRequest',
|
||||
|
|
|
@ -111,6 +111,22 @@
|
|||
@endif
|
||||
|
||||
|
||||
@if ($asset->audits->count() > 0)
|
||||
<li>
|
||||
<a href="#audits" data-toggle="tab" data-tooltip="true">
|
||||
|
||||
<span class="hidden-lg hidden-md">
|
||||
<i class="fas fa-clipboard-check fa-2x"></i>
|
||||
</span>
|
||||
<span class="hidden-xs hidden-sm">
|
||||
{{ trans('general.audits') }}
|
||||
{!! ($asset->audits()->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->audits()->count()).'</span>' : '' !!}
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li>
|
||||
<a href="#history" data-toggle="tab">
|
||||
<span class="hidden-lg hidden-md">
|
||||
|
@ -1390,7 +1406,53 @@
|
|||
</div> <!-- /.row -->
|
||||
</div> <!-- /.tab-pane maintenances -->
|
||||
|
||||
<div class="tab-pane fade" id="history">
|
||||
|
||||
<div class="tab-pane fade" id="audits">
|
||||
<!-- checked out assets table -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table
|
||||
class="table table-striped snipe-table"
|
||||
id="asseAuditHistory"
|
||||
data-pagination="true"
|
||||
data-id-table="asseAuditHistory"
|
||||
data-search="true"
|
||||
data-side-pagination="server"
|
||||
data-show-columns="true"
|
||||
data-show-fullscreen="true"
|
||||
data-show-refresh="true"
|
||||
data-sort-order="desc"
|
||||
data-sort-name="created_at"
|
||||
data-show-export="true"
|
||||
data-export-options='{
|
||||
"fileName": "export-asset-{{ $asset->id }}-audits",
|
||||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'
|
||||
|
||||
data-url="{{ route('api.activity.index', ['item_id' => $asset->id, 'item_type' => 'asset', 'action_type' => 'audit']) }}"
|
||||
data-cookie-id-table="assetHistory"
|
||||
data-cookie="true">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th data-visible="true" data-field="created_at" data-sortable="true" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-sortable="true" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th data-field="note">{{ trans('general.notes') }}</th>
|
||||
<th data-visible="false" data-field="file" data-visible="false" data-formatter="fileUploadFormatter">{{ trans('general.download') }}</th>
|
||||
<th data-field="log_meta" data-visible="true" data-formatter="changeLogFormatter">{{ trans('admin/hardware/table.changed')}}</th>
|
||||
<th data-field="remote_ip" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_ip') }}</th>
|
||||
<th data-field="user_agent" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_user_agent') }}</th>
|
||||
<th data-field="action_source" data-visible="false" data-sortable="true">{{ trans('general.action_source') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div> <!-- /.row -->
|
||||
</div> <!-- /.tab-pane history -->
|
||||
|
||||
|
||||
<div class="tab-pane fade" id="history">
|
||||
<!-- checked out assets table -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
@ -1419,7 +1481,7 @@
|
|||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th data-visible="true" data-field="action_date" data-sortable="true" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -526,7 +526,7 @@ dir="{{ Helper::determineLanguageDirection() }}">
|
|||
@can('audit', \App\Models\Asset::class)
|
||||
<li{!! (Request::is('hardware/audit/due') ? ' class="active"' : '') !!}>
|
||||
<a href="{{ route('assets.audit.due') }}">
|
||||
<x-icon type="due" class="text-yellow fa-fw"/>
|
||||
<x-icon type="audit" class="text-yellow fa-fw"/>
|
||||
{{ trans('general.audit_due') }}
|
||||
<span class="badge">{{ (isset($total_due_and_overdue_for_audit)) ? $total_due_and_overdue_for_audit : '' }}</span>
|
||||
</a>
|
||||
|
|
|
@ -498,7 +498,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="col-sm-2" data-visible="false" data-sortable="true" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.record_created') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -434,7 +434,7 @@
|
|||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th class="col-sm-2" data-visible="true" data-field="action_date" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th class="col-sm-1" data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th class="col-sm-1" data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-1" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
<th class="col-sm-2" data-visible="true" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th>
|
||||
|
|
|
@ -101,10 +101,4 @@
|
|||
@endif
|
||||
|
||||
|
||||
<script nonce="{{ csrf_token() }}">
|
||||
// We have to re-call the tooltip since this is pulled in after the DOM has loaded
|
||||
$('[data-tooltip="true"]').tooltip({
|
||||
container: 'body',
|
||||
animation: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -341,7 +341,7 @@
|
|||
|
||||
|
||||
function hardwareAuditFormatter(value, row) {
|
||||
return '<a href="{{ config('app.url') }}/hardware/audit/' + row.id + '/" class="btn btn-sm bg-yellow" data-tooltip="true" title="Audit this item">{{ trans('general.audit') }}</a>';
|
||||
return '<a href="{{ config('app.url') }}/hardware/' + row.id + '/audit" class="btn btn-sm bg-yellow" data-tooltip="true" title="Audit this item">{{ trans('general.audit') }}</a>';
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<th data-sortable="true" data-field="location" data-formatter="deployedLocationFormatter" data-visible="false">{{ trans('general.location') }}</th>
|
||||
<th data-sortable="true" data-field="rtd_location" data-formatter="deployedLocationFormatter" data-visible="false">{{ trans('admin/hardware/form.default_location') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="is_warranty" data-formatter="trueFalseFormatter">{{ trans('admin/asset_maintenances/table.is_warranty') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="user_id" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="user_id" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="notes" data-visible="false">{{ trans('admin/asset_maintenances/form.notes') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -1003,7 +1003,7 @@
|
|||
<th data-field="signature_file" data-visible="false" data-formatter="imageFormatter">{{ trans('general.signature') }}</th>
|
||||
@endif
|
||||
<th data-field="item.serial" data-visible="false">{{ trans('admin/hardware/table.serial') }}</th>
|
||||
<th data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-field="remote_ip" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_ip') }}</th>
|
||||
<th data-field="user_agent" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_user_agent') }}</th>
|
||||
<th data-field="action_source" data-visible="false" data-sortable="true">{{ trans('general.action_source') }}</th>
|
||||
|
|
|
@ -511,8 +511,17 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
|
|||
->where(['action' => 'audit|audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']);
|
||||
|
||||
|
||||
// Legacy URL for audit
|
||||
Route::post('audit',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
'audit'
|
||||
]
|
||||
)->name('api.asset.audit.legacy');
|
||||
|
||||
Route::post('audit',
|
||||
|
||||
// Newer url for audit
|
||||
Route::post('{asset}/audit',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
'audit'
|
||||
|
|
|
@ -63,14 +63,14 @@ Route::group(
|
|||
->push(trans_choice('general.checkin_due_days', Setting::getSettings()->due_checkin_days, ['days' => Setting::getSettings()->due_checkin_days]), route('assets.audit.due'))
|
||||
);
|
||||
|
||||
Route::get('audit/{asset}', [AssetsController::class, 'audit'])
|
||||
Route::get('{asset}/audit', [AssetsController::class, 'audit'])
|
||||
->name('asset.audit.create')
|
||||
->breadcrumbs(fn (Trail $trail, Asset $asset) =>
|
||||
$trail->parent('hardware.show', $asset)
|
||||
->push(trans('general.audit'))
|
||||
);
|
||||
|
||||
Route::post('audit/{asset}',
|
||||
Route::post('{asset}/audit',
|
||||
[AssetsController::class, 'auditStore']
|
||||
)->name('asset.audit.store');
|
||||
|
||||
|
|
78
tests/Feature/Assets/Api/AuditAssetTest.php
Normal file
78
tests/Feature/Assets/Api/AuditAssetTest.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Assets\Api;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\Location;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuditAssetTest extends TestCase
|
||||
{
|
||||
public function testThatANonExistentAssetIdReturnsError()
|
||||
{
|
||||
$this->actingAsForApi(User::factory()->auditAssets()->create())
|
||||
->postJson(route('api.asset.audit', 123456789))
|
||||
->assertStatusMessageIs('error');
|
||||
}
|
||||
|
||||
public function testRequiresPermissionToAuditAsset()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$this->actingAsForApi(User::factory()->create())
|
||||
->postJson(route('api.asset.audit', $asset))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function testLegacyAssetAuditIsSaved()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$this->actingAsForApi(User::factory()->auditAssets()->create())
|
||||
->postJson(route('api.asset.audit.legacy'), [
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => 'test',
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertJson(
|
||||
[
|
||||
'messages' =>trans('admin/hardware/message.audit.success'),
|
||||
'payload' => [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => 'test'
|
||||
],
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testAssetAuditIsSaved()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$this->actingAsForApi(User::factory()->auditAssets()->create())
|
||||
->postJson(route('api.asset.audit', $asset), [
|
||||
'note' => 'test'
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertJson(
|
||||
[
|
||||
'messages' =>trans('admin/hardware/message.audit.success'),
|
||||
'payload' => [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => 'test'
|
||||
],
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
34
tests/Feature/Assets/Ui/AuditAssetTest.php
Normal file
34
tests/Feature/Assets/Ui/AuditAssetTest.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Assets\Ui;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuditAssetTest extends TestCase
|
||||
{
|
||||
public function testPermissionRequiredToCreateAssetModel()
|
||||
{
|
||||
$this->actingAs(User::factory()->create())
|
||||
->get(route('clone/hardware', Asset::factory()->create()))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function testPageCanBeAccessed(): void
|
||||
{
|
||||
$this->actingAs(User::factory()->auditAssets()->create())
|
||||
->get(route('asset.audit.create', Asset::factory()->create()))
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testAssetCanBeAudited()
|
||||
{
|
||||
$response = $this->actingAs(User::factory()->auditAssets()->create())
|
||||
->post(route('asset.audit.store', Asset::factory()->create()))
|
||||
->assertStatus(302)
|
||||
->assertRedirect(route('assets.audit.due'));
|
||||
|
||||
$this->followRedirects($response)->assertSee('success');
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue