Merge remote-tracking branch 'origin/develop'

This commit is contained in:
snipe 2023-11-29 09:41:05 +00:00
commit df2b52c537
11 changed files with 202 additions and 116 deletions

View file

@ -7,6 +7,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\Setting; use App\Models\Setting;
use App\View\Label; use App\View\Label;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -23,6 +25,13 @@ class BulkAssetsController extends Controller
/** /**
* Display the bulk edit page. * Display the bulk edit page.
* *
* This method is super weird because it's kinda of like a controller within a controller.
* It's main function is to determine what the bulk action in, and then return a view with
* the information that view needs, be it bulk delete, bulk edit, restore, etc.
*
* This is something that made sense at the time, but sort of doesn't make sense now. A JS front-end to determine form
* action would make a lot more sense here and make things a lot more clear.
*
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @return View * @return View
* @internal param int $assetId * @internal param int $assetId
@ -33,6 +42,9 @@ class BulkAssetsController extends Controller
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
/**
* No asset IDs were passed
*/
if (! $request->filled('ids')) { if (! $request->filled('ids')) {
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected')); return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
} }
@ -41,40 +53,33 @@ class BulkAssetsController extends Controller
$bulk_back_url = request()->headers->get('referer'); $bulk_back_url = request()->headers->get('referer');
session(['bulk_back_url' => $bulk_back_url]); session(['bulk_back_url' => $bulk_back_url]);
$asset_ids = array_values(array_unique($request->input('ids')));
//custom fields logic $asset_ids = $request->input('ids');
$asset_custom_field = Asset::with(['model.fieldset.fields', 'model'])->whereIn('id', $asset_ids)->whereHas('model', function ($query) { $assets = Asset::with('assignedTo', 'location', 'model')->find($asset_ids);
return $query->where('fieldset_id', '!=', null);
})->get();
$models = $asset_custom_field->unique('model_id'); $models = $assets->unique('model_id');
$modelNames = []; $modelNames = [];
foreach($models as $model) { foreach($models as $model) {
$modelNames[] = $model->model->name; $modelNames[] = $model->model->name;
} }
if ($request->filled('bulk_actions')) { if ($request->filled('bulk_actions')) {
switch ($request->input('bulk_actions')) { switch ($request->input('bulk_actions')) {
case 'labels': case 'labels':
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$assets_found = Asset::find($asset_ids);
if ($assets_found->isEmpty()){
return redirect()->back();
}
return (new Label) return (new Label)
->with('assets', $assets_found) ->with('assets', $assets)
->with('settings', Setting::getSettings()) ->with('settings', Setting::getSettings())
->with('bulkedit', true) ->with('bulkedit', true)
->with('count', 0); ->with('count', 0);
case 'delete': case 'delete':
$this->authorize('delete', Asset::class); $this->authorize('delete', Asset::class);
$assets = Asset::with('assignedTo', 'location')->find($asset_ids); $assets->each(function ($assets) {
$assets->each(function ($asset) { $this->authorize('delete', $assets);
$this->authorize('delete', $asset);
}); });
return view('hardware/bulk-delete')->with('assets', $assets); return view('hardware/bulk-delete')->with('assets', $assets);
@ -85,11 +90,11 @@ class BulkAssetsController extends Controller
$assets->each(function ($asset) { $assets->each(function ($asset) {
$this->authorize('delete', $asset); $this->authorize('delete', $asset);
}); });
return view('hardware/bulk-restore')->with('assets', $assets); return view('hardware/bulk-restore')->with('assets', $assets);
case 'edit': case 'edit':
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
return view('hardware/bulk') return view('hardware/bulk')
->with('assets', $asset_ids) ->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_list', Helper::statusLabelList())
@ -117,25 +122,31 @@ class BulkAssetsController extends Controller
// Get the back url from the session and then destroy the session // Get the back url from the session and then destroy the session
$bulk_back_url = route('hardware.index'); $bulk_back_url = route('hardware.index');
if ($request->session()->has('bulk_back_url')) { if ($request->session()->has('bulk_back_url')) {
$bulk_back_url = $request->session()->pull('bulk_back_url'); $bulk_back_url = $request->session()->pull('bulk_back_url');
} }
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray(); $custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
if (Session::exists('ids')) {
$assets = Session::get('ids'); if (! $request->filled('ids') || count($request->input('ids')) == 0) {
} elseif (! $request->filled('ids') || count($request->input('ids')) <= 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected')); return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
} }
$assets = array_keys($request->input('ids'));
if ($request->anyFilled($custom_field_columns)) { $assets = Asset::whereIn('id', array_keys($request->input('ids')))->get();
$custom_fields_present = true;
} else {
$custom_fields_present = false;
} /**
* If ANY of these are filled, prepare to update the values on the assets.
*
* Additional checks will be needed for some of them to make sure the values
* make sense (for example, changing the status ID to something incompatible with
* its checkout status.
*/
if (($request->filled('purchase_date')) if (($request->filled('purchase_date'))
|| ($request->filled('expected_checkin')) || ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost')) || ($request->filled('purchase_cost'))
@ -154,15 +165,22 @@ class BulkAssetsController extends Controller
|| ($request->anyFilled($custom_field_columns)) || ($request->anyFilled($custom_field_columns))
) { ) {
foreach ($assets as $assetId) { // Let's loop through those assets and build an update array
foreach ($assets as $asset) {
$this->update_array = []; $this->update_array = [];
/**
* Leave out model_id and status here because we do math on that later. We have to do some extra
* validation and checks on those two.
*
* It's tempting to make these match the request check above, but some of these values require
* extra work to make sure the data makes sense.
*/
$this->conditionallyAddItem('purchase_date') $this->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin') ->conditionallyAddItem('expected_checkin')
->conditionallyAddItem('order_number') ->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable') ->conditionallyAddItem('requestable')
->conditionallyAddItem('status_id')
->conditionallyAddItem('supplier_id') ->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months') ->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date'); ->conditionallyAddItem('next_audit_date');
@ -170,6 +188,9 @@ class BulkAssetsController extends Controller
$this->conditionallyAddItem($custom_field_column); $this->conditionallyAddItem($custom_field_column);
} }
/**
* Blank out fields that were requested to be blanked out via checkbox
*/
if ($request->input('null_purchase_date')=='1') { if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null; $this->update_array['purchase_date'] = null;
} }
@ -186,7 +207,6 @@ class BulkAssetsController extends Controller
$this->update_array['purchase_cost'] = $request->input('purchase_cost'); $this->update_array['purchase_cost'] = $request->input('purchase_cost');
} }
if ($request->filled('company_id')) { if ($request->filled('company_id')) {
$this->update_array['company_id'] = $request->input('company_id'); $this->update_array['company_id'] = $request->input('company_id');
if ($request->input('company_id') == 'clear') { if ($request->input('company_id') == 'clear') {
@ -194,48 +214,92 @@ class BulkAssetsController extends Controller
} }
} }
/**
* We're trying to change the model ID - we need to do some extra checks here to make sure
* the custom field values work for the custom fieldset rules around this asset. Uniqueness
* and requiredness across the fieldset is particularly important, since those are
* fieldset-specific attributes.
*/
if ($request->filled('model_id')) {
$this->update_array['model_id'] = AssetModel::find($request->input('model_id'))->id;
}
/**
* We're trying to change the status ID - we need to do some extra checks here to
* make sure the status label type is one that makes sense for the state of the asset,
* for example, we shouldn't be able to make an asset archived if it's currently assigned
* to someone/something.
*/
if ($request->filled('status_id')) {
$updated_status = Statuslabel::find($request->input('status_id'));
// We cannot assign a non-deployable status type if the asset is already assigned.
// This could probably be added to a form request.
// If the asset isn't assigned, we don't care what the status is.
// Otherwise we need to make sure the status type is still a deployable one.
if (
($asset->assigned_to == '')
|| ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1')
) {
$this->update_array['status_id'] = $updated_status->id;
}
}
/**
* We're changing the location ID - figure out which location we should apply
* this change to:
*
* 0 - RTD location only
* 1 - location ID and RTD location ID
* 2 - location ID only
*
* Note: this is kinda dumb and we should just use human-readable values IMHO. - snipe
*/
if ($request->filled('rtd_location_id')) { if ($request->filled('rtd_location_id')) {
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '0')) { if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '0')) {
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id'); $this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
} }
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) { if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
$this->update_array['location_id'] = $request->input('rtd_location_id'); $this->update_array['location_id'] = $request->input('rtd_location_id');
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id'); $this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
} }
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '2')) { if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '2')) {
$this->update_array['location_id'] = $request->input('rtd_location_id'); $this->update_array['location_id'] = $request->input('rtd_location_id');
} }
} }
/**
* ------------------------------------------------------------------------------
* ANYTHING that happens past this foreach
* WILL NOT BE logged in the edit log_meta data
* ------------------------------------------------------------------------------
*/
$changed = []; $changed = [];
$asset = Asset::find($assetId);
foreach ($this->update_array as $key => $value) { foreach ($this->update_array as $key => $value) {
if ($this->update_array[$key] != $asset->{$key}) { if ($this->update_array[$key] != $asset->{$key}) {
$changed[$key]['old'] = $asset->{$key}; $changed[$key]['old'] = $asset->{$key};
$changed[$key]['new'] = $this->update_array[$key]; $changed[$key]['new'] = $this->update_array[$key];
} }
} }
if ($custom_fields_present) { /**
* Start all the custom fields shenanigans
*/
$model = $asset->model()->first(); // Does the model have a fieldset?
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
// Use the rules of the new model fieldsets if the model changed if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
if ($request->filled('model_id')) {
$this->update_array['model_id'] = $request->input('model_id');
$model = \App\Models\AssetModel::find($request->input('model_id'));
}
// Make sure this model is valid
$assetCustomFields = ($model) ? $model->fieldset : null;
if ($assetCustomFields && $assetCustomFields->fields) {
foreach ($assetCustomFields->fields as $field) {
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted=='1')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
/* /*
@ -260,7 +324,6 @@ class BulkAssetsController extends Controller
*/ */
} else { } else {
if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) { if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) {
// Check if this is an array, and if so, flatten it // Check if this is an array, and if so, flatten it
@ -273,9 +336,7 @@ class BulkAssetsController extends Controller
} }
} // endforeach } // endforeach
} // end custom field check }
} // end custom fields handler
// Check if it passes validation, and then try to save // Check if it passes validation, and then try to save

View file

@ -266,7 +266,7 @@ class Asset extends Depreciable
/** /**
* Determines if an asset is available for checkout. * Determines if an asset is available for checkout.
* This checks to see if the it's checked out to an invalid (deleted) user * This checks to see if it's checked out to an invalid (deleted) user
* OR if the assigned_to and deleted_at fields on the asset are empty AND * OR if the assigned_to and deleted_at fields on the asset are empty AND
* that the status is deployable * that the status is deployable
* *
@ -753,7 +753,7 @@ class Asset extends Depreciable
} }
/** /**
* Establishes the asset -> status relationship * Establishes the asset -> license seats relationship
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]

View file

@ -3,6 +3,7 @@
use App\Models\Asset; use App\Models\Asset;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@ -50,7 +51,7 @@ class DenormalizedEolAndAddColumnForExplicitDateToAssets extends Migration
->whereNotNull('model_id') ->whereNotNull('model_id')
->join('models', 'assets.model_id', '=', 'models.id') ->join('models', 'assets.model_id', '=', 'models.id')
->update([ ->update([
'asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . DB::getTablePrefix() . 'models.eol MONTH)') 'asset_eol_date' => $this->eolUpdateExpression(),
]); ]);
} }
@ -66,4 +67,22 @@ class DenormalizedEolAndAddColumnForExplicitDateToAssets extends Migration
$table->dropColumn('eol_explicit'); $table->dropColumn('eol_explicit');
}); });
} }
/**
* This method returns the correct database expression for either
* mysql, postgres, or sqlite depending on the driver being used.
*/
private function eolUpdateExpression(): Expression
{
if (DB::getDriverName() === 'sqlite') {
return DB::raw("DATE(purchase_date, '+' || (SELECT eol FROM " . DB::getTablePrefix() . "models WHERE models.id = assets.model_id) || ' months')");
}
if (DB::getDriverName() === 'pgsql') {
return DB::raw("date(purchase_date + interval '1 month' * (SELECT eol FROM " . DB::getTablePrefix() . "models WHERE models.id = assets.model_id))");
}
// Default to MySQL's method
return DB::raw('DATE_ADD(purchase_date, INTERVAL ' . DB::getTablePrefix() . 'models.eol MONTH)');
}
} }

View file

@ -9,6 +9,7 @@ return [
'ad_append_domain_help' => 'User isn\'t required to write "username@domain.local", they can just type "username".', 'ad_append_domain_help' => 'User isn\'t required to write "username@domain.local", they can just type "username".',
'admin_cc_email' => 'CC Email', 'admin_cc_email' => 'CC Email',
'admin_cc_email_help' => 'If you would like to send a copy of checkin/checkout emails that are sent to users to an additional email account, enter it here. Otherwise leave this field blank.', 'admin_cc_email_help' => 'If you would like to send a copy of checkin/checkout emails that are sent to users to an additional email account, enter it here. Otherwise leave this field blank.',
'admin_settings' => 'Admin Settings',
'is_ad' => 'This is an Active Directory server', 'is_ad' => 'This is an Active Directory server',
'alerts' => 'Alerts', 'alerts' => 'Alerts',
'alert_title' => 'Update Notification Settings', 'alert_title' => 'Update Notification Settings',

View file

@ -21,6 +21,7 @@ return array(
'manager' => 'Manager', 'manager' => 'Manager',
'managed_locations' => 'Managed Locations', 'managed_locations' => 'Managed Locations',
'name' => 'Name', 'name' => 'Name',
'nogroup' => 'No groups have been created yet. To add one, visit: ',
'notes' => 'Notes', 'notes' => 'Notes',
'password_confirm' => 'Confirm Password', 'password_confirm' => 'Confirm Password',
'password' => 'Password', 'password' => 'Password',

View file

@ -491,5 +491,6 @@ return [
'upload_error' => 'Error uploading file. Please check that there are no empty rows and that no column names are duplicated.', 'upload_error' => 'Error uploading file. Please check that there are no empty rows and that no column names are duplicated.',
'copy_to_clipboard' => 'Copy to Clipboard', 'copy_to_clipboard' => 'Copy to Clipboard',
'copied' => 'Copied!', 'copied' => 'Copied!',
'status_compatibility' => 'If assets are already assigned, they cannot be changed to a non-deployable status type and this value change will be skipped.',
]; ];

View file

@ -30,7 +30,7 @@
<tr> <tr>
<td></td> <td></td>
<td>{{ trans('admin/hardware/table.id') }}</td> <td>{{ trans('admin/hardware/table.id') }}</td>
<td>{{ trans('admin/hardware/table.name') }}</td> <td>{{ trans('admin/hardware/form.name') }}</td>
<td>{{ trans('admin/hardware/table.location')}}</td> <td>{{ trans('admin/hardware/table.location')}}</td>
</tr> </tr>
</thead> </thead>

View file

@ -19,18 +19,22 @@
<p>{{ trans('admin/hardware/form.bulk_update_help') }}</p> <p>{{ trans('admin/hardware/form.bulk_update_help') }}</p>
<div class="callout callout-warning">
<i class="fas fa-exclamation-triangle"></i> {{ trans_choice('admin/hardware/form.bulk_update_warn', count($assets), ['asset_count' => count($assets)]) }}
@if (count($models) > 0)
{{ trans_choice('admin/hardware/form.bulk_update_with_custom_field', count($models), ['asset_model_count' => count($models)]) }}
@endif
</div>
<form class="form-horizontal" method="post" action="{{ route('hardware/bulksave') }}" autocomplete="off" role="form"> <form class="form-horizontal" method="post" action="{{ route('hardware/bulksave') }}" autocomplete="off" role="form">
{{ csrf_field() }} {{ csrf_field() }}
<div class="box box-default"> <div class="box box-default">
<div class="box-body"> <div class="box-body">
<div class="callout callout-warning">
<i class="fas fa-exclamation-triangle"></i> {{ trans_choice('admin/hardware/form.bulk_update_warn', count($assets), ['asset_count' => count($assets)]) }}
@if (count($models) > 0)
{{ trans_choice('admin/hardware/form.bulk_update_with_custom_field', count($models), ['asset_model_count' => count($models)]) }}
@endif
</div>
<!-- Purchase Date --> <!-- Purchase Date -->
<div class="form-group {{ $errors->has('purchase_date') ? ' has-error' : '' }}"> <div class="form-group {{ $errors->has('purchase_date') ? ' has-error' : '' }}">
<label for="purchase_date" class="col-md-3 control-label">{{ trans('admin/hardware/form.date') }}</label> <label for="purchase_date" class="col-md-3 control-label">{{ trans('admin/hardware/form.date') }}</label>
@ -76,6 +80,7 @@
</label> </label>
<div class="col-md-7"> <div class="col-md-7">
{{ Form::select('status_id', $statuslabel_list , old('status_id'), array('class'=>'select2', 'style'=>'width:100%', 'aria-label'=>'status_id')) }} {{ Form::select('status_id', $statuslabel_list , old('status_id'), array('class'=>'select2', 'style'=>'width:100%', 'aria-label'=>'status_id')) }}
<p class="help-block">{{ trans('general.status_compatibility') }}</p>
{!! $errors->first('status_id', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!} {!! $errors->first('status_id', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div> </div>
</div> </div>

View file

@ -1206,15 +1206,15 @@
<thead> <thead>
<tr> <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="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 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 data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
<th class="col-sm-1" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th> <th 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 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> <th data-visible="true" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th>
<th class="col-sm-2" data-field="note">{{ trans('general.notes') }}</th> <th data-field="note">{{ trans('general.notes') }}</th>
<th class="col-md-3" data-field="signature_file" data-visible="false" data-formatter="imageFormatter">{{ trans('general.signature') }}</th> <th data-field="signature_file" data-visible="false" data-formatter="imageFormatter">{{ trans('general.signature') }}</th>
<th class="col-md-3" data-visible="false" data-field="file" data-visible="false" data-formatter="fileUploadFormatter">{{ trans('general.download') }}</th> <th data-visible="false" data-field="file" data-visible="false" data-formatter="fileUploadFormatter">{{ trans('general.download') }}</th>
<th class="col-sm-2" data-field="log_meta" data-visible="true" data-formatter="changeLogFormatter">{{ trans('admin/hardware/table.changed')}}</th> <th data-field="log_meta" data-visible="true" data-formatter="changeLogFormatter">{{ trans('admin/hardware/table.changed')}}</th>
</tr> </tr>
</thead> </thead>
</table> </table>

View file

@ -553,7 +553,7 @@
</div> </div>
@endif @endif
@else @else
<p>No groups have been created yet. Visit <code>Admin Settings > Permission Groups</code> to add one.</p> <p>{{ trans('admin/users/table.nogroup') }} <code>{{ trans('admin/settings/general.admin_settings') }} <i class="fa fa-cogs"></i> > {{ trans('general.groups') }} <i class="fas fa-user-friends"></i></code> </p>
@endif @endif
</div> </div>

View file

@ -638,13 +638,14 @@
</div> </div>
@endif @endif
@if($user->getUserTotalCost()->total_user_cost > 0)
<div class="row"> <div class="row">
<div class="col-md-3"> <div class="col-md-3">
{{ trans('admin/users/table.total_assets_cost') }} {{ trans('admin/users/table.total_assets_cost') }}
</div> </div>
<div class="col-md-9"> <div class="col-md-9">
{{Helper::formatCurrencyOutput($user->getUserTotalCost()->total_user_cost)}} {{Helper::formatCurrencyOutput($user->getUserTotalCost()->total_user_cost)}}
<a id="optional_info" class="text-primary"> <a id="optional_info" class="text-primary">
<i class="fa fa-caret-right fa-2x" id="optional_info_icon"></i> <i class="fa fa-caret-right fa-2x" id="optional_info_icon"></i>
<strong>{{ trans('admin/hardware/form.optional_infos') }}</strong> <strong>{{ trans('admin/hardware/form.optional_infos') }}</strong>
@ -658,13 +659,10 @@
{{trans('general.accessories').': '.Helper::formatCurrencyOutput($user->getUserTotalCost()->accessory_cost)}}<br> {{trans('general.accessories').': '.Helper::formatCurrencyOutput($user->getUserTotalCost()->accessory_cost)}}<br>
</div> </div>
</div> </div>
</div> </div><!--/.row-->
@endif
</div> <!--/end striped container--> </div> <!--/end striped container-->
</div> <!-- end col-md-9 --> </div> <!-- end col-md-9 -->
</div> <!--/.row--> </div> <!--/.row-->
</div><!-- /.tab-pane --> </div><!-- /.tab-pane -->