Merge pull request #14800 from snipe/fixes/importer_tweaks

Importer tweaks
This commit is contained in:
snipe 2024-06-12 12:51:11 +01:00 committed by GitHub
commit cacb5d62dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 185 additions and 77 deletions

View file

@ -8,7 +8,6 @@ use Livewire\Component;
use App\Models\Import;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@ -119,8 +118,7 @@ class Importer extends Component
public function updating($name, $new_import_type)
{
if ($name == "activeFile.import_type") {
Log::debug("WE ARE CHANGING THE import_type!!!!! TO: " . $new_import_type);
Log::debug("so, what's \$this->>field_map at?: " . print_r($this->field_map, true));
// go through each header, find a matching field to try and map it to.
foreach ($this->activeFile->header_row as $i => $header) {
// do we have something mapped already?
@ -237,6 +235,15 @@ class Importer extends Component
'email' => trans('general.importer.checked_out_to_email'),
'username' => trans('general.importer.checked_out_to_username'),
'checkout_location' => trans('general.importer.checkout_location'),
/**
* These are here so users can import history, to replace the dinosaur that
* was the history importer
*/
'last_checkin' => trans('admin/hardware/table.last_checkin_date'),
'last_checkout' => trans('admin/hardware/table.checkout_date'),
'expected_checkin' => trans('admin/hardware/form.expected_checkin'),
'last_audit_date' => trans('general.last_audit'),
'next_audit_date' => trans('general.next_audit_date'),
];
$this->consumables_fields = [
@ -380,6 +387,12 @@ class Importer extends Component
'job title for user',
'job title',
],
'full_name' =>
[
'full name',
'fullname',
trans('general.importer.checked_out_to_fullname')
],
'username' =>
[
'user name',
@ -412,6 +425,7 @@ class Importer extends Component
'telephone',
'tel.',
],
'serial' =>
[
'serial number',
@ -456,6 +470,12 @@ class Importer extends Component
[
'Next Audit',
],
'last_checkout' =>
[
'Last Checkout',
'Last Checkout Date',
'Checkout Date',
],
'address2' =>
[
'Address 2',
@ -523,9 +543,8 @@ class Importer extends Component
{
// TODO: why don't we just do File::find($id)? This seems dumb.
foreach($this->files as $file) {
Log::debug("File id is: ".$file->id);
if($id == $file->id) {
if(Storage::delete('private_uploads/imports/'.$file->file_path)) {
if ($id == $file->id) {
if (Storage::delete('private_uploads/imports/'.$file->file_path)) {
$file->delete();
$this->message = trans('admin/hardware/message.import.file_delete_success');

View file

@ -7,8 +7,10 @@ use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\User;
use App\Events\CheckoutableCheckedIn;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class AssetImporter extends ItemImporter
{
@ -19,7 +21,7 @@ class AssetImporter extends ItemImporter
parent::__construct($filename);
if (!is_null(Statuslabel::first())) {
$this->defaultStatusLabelId = Statuslabel::first()->id;
$this->defaultStatusLabelId = Statuslabel::deployable()->first()->id;
}
}
@ -64,16 +66,14 @@ class AssetImporter extends ItemImporter
$editingAsset = false;
$asset_tag = $this->findCsvMatch($row, 'asset_tag');
if(empty($asset_tag)){
if (empty($asset_tag)){
$asset_tag = Asset::autoincrement_asset();
}
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
if ($asset) {
if (! $this->updating) {
$this->log('A matching Asset '.$asset_tag.' already exists');
return;
}
@ -83,6 +83,13 @@ class AssetImporter extends ItemImporter
$this->log('No Matching Asset, Creating a new one');
$asset = new Asset;
}
// If no status ID is found
if (! array_key_exists('status_id', $this->item) && ! $editingAsset) {
$this->log('No status ID field found, defaulting to first deployable status label.');
$this->item['status_id'] = $this->defaultStatusLabelId;
}
$this->item['notes'] = trim($this->findCsvMatch($row, 'asset_notes'));
$this->item['image'] = trim($this->findCsvMatch($row, 'image'));
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? '1' : 0;
@ -90,14 +97,12 @@ class AssetImporter extends ItemImporter
$this->item['warranty_months'] = intval(trim($this->findCsvMatch($row, 'warranty_months')));
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
$this->item['byod'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'byod'))) == 1) ? '1' : 0;
// If no status ID is found
if (! array_key_exists('status_id', $this->item) && ! $editingAsset) {
$this->log('No status field found, defaulting to first status.');
$this->item['status_id'] = $this->defaultStatusLabelId;
}
$this->item['last_checkin'] = trim($this->findCsvMatch($row, 'last_checkin'));
$this->item['last_checkout'] = trim($this->findCsvMatch($row, 'last_checkout'));
$this->item['expected_checkin'] = trim($this->findCsvMatch($row, 'expected_checkin'));
$this->item['last_audit_date'] = trim($this->findCsvMatch($row, 'last_audit_date'));
$this->item['next_audit_date'] = trim($this->findCsvMatch($row, 'next_audit_date'));
$this->item['asset_eol_date'] = trim($this->findCsvMatch($row, 'next_audit_date'));
$this->item['asset_tag'] = $asset_tag;
// We need to save the user if it exists so that we can checkout to user later.
@ -105,7 +110,9 @@ class AssetImporter extends ItemImporter
if (array_key_exists('checkout_target', $this->item)) {
$target = $this->item['checkout_target'];
}
$item = $this->sanitizeItemForStoring($asset, $editingAsset);
// The location id fetched by the csv reader is actually the rtd_location_id.
// This will also set location_id, but then that will be overridden by the
// checkout method if necessary below.
@ -113,16 +120,42 @@ class AssetImporter extends ItemImporter
$item['rtd_location_id'] = $this->item['location_id'];
}
$item['last_audit_date'] = null;
if (isset($this->item['last_audit_date'])) {
$item['last_audit_date'] = $this->item['last_audit_date'];
/**
* We use this to backdate the checkin action further down
*/
$checkin_date = date('Y-m-d H:i:s');
if ($this->item['last_checkin']!='') {
$item['last_checkin'] = $this->parseOrNullDate('last_checkin', 'datetime');
$checkout_date = $this->item['last_checkin'];
}
$item['next_audit_date'] = null;
if (isset($this->item['next_audit_date'])) {
$item['next_audit_date'] = $this->item['next_audit_date'];
/**
* We use this to backdate the checkout action further down
*/
$checkout_date = date('Y-m-d H:i:s');
if ($this->item['last_checkout']!='') {
$item['last_checkout'] = $this->parseOrNullDate('last_checkout', 'datetime');
$checkout_date = $this->item['last_checkout'];
}
if ($this->item['expected_checkin']!='') {
$item['expected_checkin'] = $this->parseOrNullDate('expected_checkin');
}
if ($this->item['last_audit_date']!='') {
$item['last_audit_date'] = $this->parseOrNullDate('last_audit_date');
}
if ($this->item['next_audit_date']!='') {
$item['next_audit_date'] = $this->parseOrNullDate('next_audit_date');
}
if ($this->item['asset_eol_date']!='') {
$item['asset_eol_date'] = $this->parseOrNullDate('asset_eol_date');
}
if ($editingAsset) {
$asset->update($item);
} else {
@ -135,27 +168,31 @@ class AssetImporter extends ItemImporter
$asset->{$custom_field} = $val;
}
}
// This sets an attribute on the Loggable trait for the action log
$asset->setImported(true);
if ($asset->save()) {
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
// If we have a target to checkout to, lets do so.
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's set by
//-- the class that needs to use it (command importer or GUI importer inside the project).
if (isset($target) && ($target !== false)) {
if (!is_null($asset->assigned_to)){
if ($asset->assigned_to != $target->id){
event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), $asset->notes, date('Y-m-d H:i:s')));
if ($asset->assigned_to != $target->id) {
event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), 'Checkin from CSV Importer', $checkin_date));
}
}
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
$asset->fresh()->checkOut($target, $this->user_id, $checkout_date, null, 'Checkout from CSV Importer', $asset->name);
}
return;
}
$this->logError($asset, 'Asset "'.$this->item['name'].'"');
}
}

View file

@ -6,6 +6,7 @@ use App\Models\CustomField;
use App\Models\Department;
use App\Models\Setting;
use App\Models\User;
use Carbon\CarbonImmutable;
use ForceUTF8\Encoding;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
@ -551,4 +552,35 @@ abstract class Importer
return null;
}
/**
* Parse a date or return null
*
* @author A. Gianotto
* @since 7.0.0
* @param $field
* @param $format
* @return string|null
*/
public function parseOrNullDate($field, $format = 'date') {
$date_format = 'Y-m-d';
if ($format == 'datetime') {
$date_format = 'Y-m-d H:i:s';
}
if (array_key_exists($field, $this->item) && $this->item[$field] != '') {
try {
$value = CarbonImmutable::parse($this->item[$field])->format($date_format);
return $value;
} catch (\Exception $e) {
$this->log('Unable to parse date: ' . $this->item[$field]);
return null;
}
}
return null;
}
}

View file

@ -79,26 +79,18 @@ class ItemImporter extends Importer
$this->item['purchase_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'purchase_date')));
}
$this->item['last_audit_date'] = null;
if ($this->findCsvMatch($row, 'last_audit_date') != '') {
$this->item['last_audit_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'last_audit_date')));
}
// $this->item['asset_eol_date'] = null;
// if ($this->findCsvMatch($row, 'asset_eol_date') != '') {
// $csvMatch = $this->findCsvMatch($row, 'asset_eol_date');
// \Log::warning('EOL Date for $csvMatch is '.$csvMatch);
// try {
// $this->item['asset_eol_date'] = CarbonImmutable::parse($csvMatch)->format('Y-m-d');
// } catch (\Exception $e) {
// Log::info($e->getMessage());
// $this->log('Unable to parse date: '.$csvMatch);
// }
// }
$this->item['next_audit_date'] = null;
if ($this->findCsvMatch($row, 'next_audit_date') != '') {
$this->item['next_audit_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'next_audit_date')));
}
$this->item['asset_eol_date'] = null;
if($this->findCsvMatch($row, 'asset_eol_date') != '') {
$csvMatch = $this->findCsvMatch($row, 'asset_eol_date');
try {
$this->item['asset_eol_date'] = CarbonImmutable::parse($csvMatch)->format('Y-m-d');
} catch (\Exception $e) {
Log::info($e->getMessage());
$this->log('Unable to parse date: '.$csvMatch);
}
}
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
@ -389,7 +381,6 @@ class ItemImporter extends Importer
if ($status->save()) {
$this->log('Status '.$asset_statuslabel_name.' was created');
return $status->id;
}
@ -509,4 +500,5 @@ class ItemImporter extends Importer
return null;
}
}

View file

@ -10,7 +10,7 @@ use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use AssetPresenter;
use App\Presenters\AssetPresenter;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
@ -19,6 +19,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
/**
* Model for Assets.
@ -28,7 +30,7 @@ use Watson\Validating\ValidatingTrait;
class Asset extends Depreciable
{
protected $presenter = \App\Presenters\AssetPresenter::class;
protected $presenter = AssetPresenter::class;
use CompanyableTrait;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
@ -149,6 +151,8 @@ class Asset extends Depreciable
'last_audit_date',
'next_audit_date',
'asset_eol_date',
'last_checkin',
'last_checkout',
];
use Searchable;
@ -971,14 +975,37 @@ class Asset extends Depreciable
* @param $value
* @return void
*/
public function getNextAuditDateAttribute($value)
protected function nextAuditDate(): Attribute
{
return $this->attributes['next_audit_date'] = $value ? Carbon::parse($value)->format('Y-m-d') : null;
return Attribute::make(
get: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d') : null,
set: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d') : null,
);
}
public function setNextAuditDateAttribute($value)
protected function lastCheckout(): Attribute
{
$this->attributes['next_audit_date'] = $value ? Carbon::parse($value)->format('Y-m-d') : null;
return Attribute::make(
get: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d H:i:s') : null,
set: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d H:i:s') : null,
);
}
protected function lastCheckin(): Attribute
{
return Attribute::make(
get: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d H:i:s') : null,
set: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d H:i:s') : null,
);
}
protected function assetEolDate(): Attribute
{
return Attribute::make(
get: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d') : null,
set: fn ($value) => $value ? Carbon::parse($value)->format('Y-m-d') : null,
);
}
/**
@ -990,9 +1017,13 @@ class Asset extends Depreciable
* @param $value
* @return void
*/
public function setRequestableAttribute($value)
protected function requestable(): Attribute
{
$this->attributes['requestable'] = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
return Attribute::make(
get: fn ($value) => (int) filter_var($value, FILTER_VALIDATE_BOOLEAN),
set: fn ($value) => (int) filter_var($value, FILTER_VALIDATE_BOOLEAN),
);
}

View file

@ -705,18 +705,6 @@
</div>
@endif
@if ($asset->expected_checkin!='')
<div class="row">
<div class="col-md-2">
<strong>
{{ trans('admin/hardware/form.expected_checkin') }}
</strong>
</div>
<div class="col-md-6">
{{ Helper::getFormattedDateObject($asset->expected_checkin, 'date', false) }}
</div>
</div>
@endif
<div class="row">
<div class="col-md-2">
@ -804,6 +792,19 @@
</div>
</div>
@endif
@if ($asset->expected_checkin!='')
<div class="row">
<div class="col-md-2">
<strong>
{{ trans('admin/hardware/form.expected_checkin') }}
</strong>
</div>
<div class="col-md-6">
{{ Helper::getFormattedDateObject($asset->expected_checkin, 'date', false) }}
</div>
</div>
@endif
@if ($asset->last_checkin!='')
<div class="row">
<div class="col-md-2">

View file

@ -36,15 +36,11 @@
<th>{{ trans('general.error') }}</th>
</thead>
<tbody>
@php \Log::debug("import errors are: ".print_r($import_errors,true)); @endphp
@foreach($import_errors AS $key => $actual_import_errors)
@php \Log::debug("Key is: $key"); @endphp
@foreach($actual_import_errors AS $table => $error_bag)
@php \Log::debug("Table is: $table"); @endphp
@foreach($error_bag as $field => $error_list)
@php \Log::debug("Field is: $field"); @endphp
<tr>
<td>{{ $activeFile->file_path ?? "Unknown File" }}</td>
<td><b>{{ $key }}</b></td>
<td>
<b>{{ $field }}:</b>
<span>{{ implode(", ",$error_list) }}</span>

View file

@ -67,7 +67,7 @@ class StoreAssetTest extends TestCase
$this->assertEquals('random_string', $asset->asset_tag);
$this->assertEquals($userAssigned->id, $asset->assigned_to);
$this->assertTrue($asset->company->is($company));
$this->assertEquals('2023-09-03 00:00:00', $asset->last_audit_date->format('Y-m-d H:i:s'));
$this->assertEquals('2023-09-03 00:00:00', $asset->last_audit_date);
$this->assertTrue($asset->location->is($location));
$this->assertTrue($asset->model->is($model));
$this->assertEquals('A New Asset', $asset->name);
@ -87,7 +87,7 @@ class StoreAssetTest extends TestCase
{
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'last_audit_date' => '2023-09-03 12:23:45',
'last_audit_date' => '2023-09-03',
'asset_tag' => '1234',
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->create()->id,
@ -96,7 +96,7 @@ class StoreAssetTest extends TestCase
->assertStatusMessageIs('success');
$asset = Asset::find($response['payload']['id']);
$this->assertEquals('00:00:00', $asset->last_audit_date->format('H:i:s'));
$this->assertEquals('2023-09-03 00:00:00', $asset->last_audit_date);
}
public function testLastAuditDateCanBeNull()