Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 104 additions & 89 deletions app/Http/Controllers/Assets/AssetsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\CreateMultipleAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
Expand Down Expand Up @@ -98,7 +99,7 @@ public function create(Request $request) : View
* @author [A. Gianotto] [<[email protected]>]
* @since [v1.0]
*/
public function store(ImageUploadRequest $request) : RedirectResponse
public function store(CreateMultipleAssetRequest $request): RedirectResponse
{
$this->authorize(Asset::class);

Expand Down Expand Up @@ -135,122 +136,136 @@ public function store(ImageUploadRequest $request) : RedirectResponse
$successes = [];
$failures = [];

for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
$asset = new Asset();
try {
DB::beginTransaction();
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
$asset = new Asset();

$asset->model()->associate($model);
$asset->name = $request->input('name');
$asset->model()->associate($model);
$asset->name = $request->input('name');

// Check for a corresponding serial
if (($serials) && (array_key_exists($a, $serials))) {
$asset->serial = $serials[$a];
}

if (($asset_tags) && (array_key_exists($a, $asset_tags))) {
$asset->asset_tag = $asset_tags[$a];
}
// Check for a corresponding serial
if (($serials) && (array_key_exists($a, $serials))) {
$asset->serial = $serials[$a];
}

$asset->company_id = $companyId;
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->created_by = auth()->id();
$asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
$asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
$asset->byod = request('byod', 0);

if (! empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString();
}
if (($asset_tags) && (array_key_exists($a, $asset_tags))) {
$asset->asset_tag = $asset_tags[$a];
}

// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) {
$asset->location_id = $request->input('rtd_location_id', null);
}
$asset->company_id = $companyId;
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->created_by = auth()->id();
$asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
$asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
$asset->byod = request('byod', 0);

if (!empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths((int)$settings->audit_interval)->toDateString();
}

if ($request->has('use_cloned_image')) {
$cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'assets/'.$new_image_name;
Storage::disk('public')->copy('assets/'.$cloned_model_img->image, $new_image);
$asset->image = $new_image_name;
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) {
$asset->location_id = $request->input('rtd_location_id', null);
}

} else {
$asset = $request->handleImages($asset);
}
if ($request->has('use_cloned_image')) {
$cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-' . date('U') . '-' . $cloned_model_img->image;
$new_image = 'assets/' . $new_image_name;
Storage::disk('public')->copy('assets/' . $cloned_model_img->image, $new_image);
$asset->image = $new_image_name;
}

// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
} else {
$asset = $request->handleImages($asset);
}

if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request

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));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
$asset->{$field->db_column} = $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 the asset before saving
if ($asset->isValid() && $asset->save()) {
$target = null;
$location = null;
// Validate the asset before saving
// Note - it can be tempting to instead want to call saveOrFail(), to automatically throw when an object
// is invalid (and can't save). But this won't work, because Custom Fields _overrides_ the save() method
// to inject the Custom Field Rules into the $rules property right before invoking the _real_ save.
// so, instead, we have to catch failures on the 'else' clause and throw there.
if ($asset->isValid() && $asset->save()) {
$target = null;
$location = null;

if ($userId = request('assigned_user')) {
$target = User::find($userId);
if ($userId = request('assigned_user')) {
$target = User::find($userId);

if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user'));
}
$location = $target->location_id;
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user'));
}
$location = $target->location_id;

} elseif ($assetId = request('assigned_asset')) {
$target = Asset::find($assetId);
} elseif ($assetId = request('assigned_asset')) {
$target = Asset::find($assetId);

if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset'));
}
$location = $target->location_id;
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset'));
}
$location = $target->location_id;

} elseif ($locationId = request('assigned_location')) {
$target = Location::find($locationId);
} elseif ($locationId = request('assigned_location')) {
$target = Location::find($locationId);

if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location'));
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location'));
}
$location = $target->id;
}
$location = $target->id;
}

if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
}
if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
}

$successes[] = "<a href='" . route('hardware.show', $asset) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
$successes[] = "<a href='" . route('hardware.show', $asset) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";

} else {
$failures[] = join(",", $asset->getErrors()->all());
} else {
$asset->throwValidationException(); // we have to do this for the reason listed above - can't use saveOrFail()
$failures[] = join(",", $asset->getErrors()->all()); //TODO - this can probably go away soon
}
}
} catch (\Throwable $e) {
\Log::debug("Caught exception in multi-create - rolling back: " . $e->getMessage());
DB::rollBack();
throw $e;
}
DB::commit();

if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
Expand Down
68 changes: 68 additions & 0 deletions app/Http/Requests/CreateMultipleAssetRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace App\Http\Requests;

use App\Http\Requests\Traits\MayContainCustomFields;
use App\Models\Asset;
use Illuminate\Foundation\Http\FormRequest;
use App\Helpers\Helper;
use App\Models\Setting;
use App\Models\AssetModel;
use App\Rules\UniqueUndeleted;
use Illuminate\Support\Str;

class CreateMultipleAssetRequest extends ImageUploadRequest //should I extend from StoreAssetRequest? FIXME OR TODO OR THINKME
{
use MayContainCustomFields;

/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; //TODO - should I do the auth check here?
}

/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
//grab the rules for serials and asset_tags, and tweak them into an array context for multi-create usage
$modelRules = (new Asset)->getRules();
unset($modelRules['serial']);

$asset_tag_rules = $modelRules['asset_tag'];
unset($modelRules['asset_tag']);
// now we replace the 'not_array' rule with 'distinct'
array_splice($asset_tag_rules, array_search('not_array', $asset_tag_rules), 1, 'distinct');
// and replace the 'unique_undeleted' rule with the Rule object
foreach ($asset_tag_rules as $i => $asset_tag_rule) {
if (Str::startsWith($asset_tag_rule, 'unique_undeleted')) {
$asset_tag_rules[$i] = new UniqueUndeleted('assets', 'asset_tag');
}
}

$serials_unique = Setting::getSettings()['unique_serial'];
$serials_required = AssetModel::find($this?->model_id)?->require_serial;

$serial_rules = ['string'];
if ($serials_unique) {
// $serial_rules[] = 'unique_undeleted:assets,serial';
$serial_rules[] = new UniqueUndeleted('assets', 'serial');
$serial_rules[] = 'distinct';
}
if ($serials_required) {
$serial_rules[] = 'required';
} else {
$serial_rules[] = 'nullable';
}

return array_merge($modelRules, [
'serials.*' => $serial_rules,
'asset_tags.*' => $asset_tag_rules,
]);
}
}
56 changes: 56 additions & 0 deletions app/Rules/UniqueUndeleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Validation\Validator;
use Illuminate\Support\Facades\DB;
use Illuminate\Contracts\Validation\DataAwareRule;

class UniqueUndeleted implements ValidationRule, ValidatorAwareRule
{
protected ?Validator $validator = null;
protected array $columns = [];
protected $data = [];

public function __construct(
public string $table,
string ...$columns,
)
{
$this->columns = $columns;
}

public function setValidator(Validator $validator): static
{
$this->validator = $validator;
$this->data = $validator->getData();
//TODO - can we somehow grab the ID of the route-model-bound object, and omit its ID?
// to do that, we'd have to know _which_ parameter in the validator is actually the R-M-B'ed
// parameter. Or maybe we just change the function signature to let you specify it.

return $this;
}

/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$query = DB::table($this->table)->whereNull('deleted_at');
$query->where($this->columns[0], '=', $value); //the first column to check
$translation_string = 'validation.unique_undeleted'; //the normal validation string for a single-column check
foreach (array_slice($this->columns, 1) as $column) {
$translation_string = 'validation.two_column_unique_undeleted';
$query->where($column, '=', $this->data[$column]);
}

if ($query->count() > 0) {
$fail($translation_string)->translate();
}
}
}
5 changes: 4 additions & 1 deletion resources/lang/en-US/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@
|
*/

'attributes' => [],
'attributes' => [
'serials.*' => 'Serial Number',
'asset_tags.*' => 'Asset Tag',
],

/*
|--------------------------------------------------------------------------
Expand Down
Loading
Loading