Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
58eac61
add checkoutable class, rework blade for unaccepted items
Godmartinz Sep 16, 2025
dcbb09b
added checkoutable class
Godmartinz Sep 16, 2025
51ce570
attempt to sort chronologically, can not resort still
Godmartinz Sep 18, 2025
a6cb75c
remove sorter, didnt work
Godmartinz Sep 22, 2025
ab30df1
remove sorter from blade
Godmartinz Sep 22, 2025
7077faa
updated the postassetAcceptanceReport query and rows
Godmartinz Sep 23, 2025
ca8eae4
updated the csv values to be plain
Godmartinz Sep 23, 2025
20adad3
fix company name link, clean up query
Godmartinz Sep 23, 2025
5af85bf
further clean up
Godmartinz Sep 23, 2025
6f3323c
fix data in view model
Godmartinz Sep 23, 2025
2b5254e
fixed, eager loading, variable names, clean up
Godmartinz Sep 23, 2025
3527c35
whoops
Godmartinz Sep 23, 2025
d31d99b
remove else and blank lines
Godmartinz Sep 24, 2025
0ce20c1
fix send reminder method to handle other types
Godmartinz Sep 24, 2025
9ac2ea2
fix test to check all mailable types for reminders
Godmartinz Sep 24, 2025
bf6964e
tests passing, CheckoutAcceptance Factory needs eyes though
Godmartinz Sep 24, 2025
6ca0e19
uncomment code
Godmartinz Sep 24, 2025
6f990dd
adds an option to disable Auto assigned an actionlogs in factories
Godmartinz Sep 24, 2025
533d82d
remove unnecessary changes
Godmartinz Sep 24, 2025
82bdd43
renamed variable
Godmartinz Sep 24, 2025
fea0189
Merge branch 'fix-factory-auto-gen-action-logs' into add_types_to_una…
Godmartinz Sep 24, 2025
881c789
add usuage of withoutactionlog
Godmartinz Sep 24, 2025
3ae7a77
update the snipeit reminder command
Godmartinz Sep 25, 2025
ca44ee9
Merge pull request #28 from Godmartinz/add_types_to_command
Godmartinz Sep 25, 2025
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
79 changes: 45 additions & 34 deletions app/Http/Controllers/ReportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
use App\Helpers\Helper;
use App\Mail\CheckoutAssetMail;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Checkoutable;
use App\Models\Component;
use App\Models\LicenseSeat;
use App\Models\Maintenance;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
Expand Down Expand Up @@ -1111,33 +1115,28 @@ public function getAssetAcceptanceReport($deleted = false) : View
$showDeleted = $deleted == 'deleted';

$query = CheckoutAcceptance::pending()
->where('checkoutable_type', 'App\Models\Asset')
->with([
'checkoutable' => function (MorphTo $query) {
$query->morphWith([
AssetModel::class => ['model'],
Company::class => ['company'],
Asset::class => ['assignedTo'],
])->with('model.category');
Asset::class => ['model.category', 'assignedTo', 'company'],
Accessory::class => ['category','checkouts', 'company'],
LicenseSeat::class => ['user', 'license'],
Component::class => ['assignedTo', 'company'],
]);
},
'assignedTo' => function($query){
$query->withTrashed();
}
]);
])->orderByDesc('checkout_acceptances.created_at');


if ($showDeleted) {
$query->withTrashed();
}

$assetsForReport = $query->get()
->map(function ($acceptance) {
return [
'assetItem' => $acceptance->checkoutable,
'acceptance' => $acceptance,
];
});
$itemsForReport = $query->get()->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));

return view('reports/unaccepted_assets', compact('assetsForReport','showDeleted' ));
return view('reports/unaccepted_assets', compact('itemsForReport','showDeleted' ));
}

/**
Expand Down Expand Up @@ -1215,28 +1214,37 @@ public function deleteAssetAcceptance($acceptanceId = null) : RedirectResponse
public function postAssetAcceptanceReport($deleted = false) : Response
{
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
$showDeleted = request('deleted') === 'deleted';;

/**
* Get all assets with pending checkout acceptances
*/
if($showDeleted) {
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
} else {
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
}

$assetsForReport = $acceptances
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
});
$acceptances = CheckoutAcceptance::pending()
->with([
'checkoutable' => function (MorphTo $acceptance) {
$acceptance->morphWith([
Asset::class => ['model.category', 'assignedTo', 'company'],
Accessory::class => ['category','checkouts', 'company'],
LicenseSeat::class => ['user', 'license'],
Component::class => ['assignedTo', 'company'],
]);
},
'assignedTo',
])->orderByDesc('checkout_acceptances.created_at');

if ($showDeleted) {
$acceptances->withTrashed();
}

$itemsForReport = $acceptances->get()->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));

$rows = [];

$header = [
trans('general.date'),
trans('general.type'),
trans('admin/companies/table.title'),
trans('general.category'),
trans('admin/hardware/form.model'),
trans('admin/hardware/form.name'),
Expand All @@ -1247,16 +1255,19 @@ public function postAssetAcceptanceReport($deleted = false) : Response
$header = array_map('trim', $header);
$rows[] = implode(',', $header);

foreach ($assetsForReport as $item) {
foreach ($itemsForReport as $item) {

if ($item['assetItem'] != null){
if ($item != null){

$row = [ ];
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
$row[] = str_replace(',', '', e($item['assetItem']->name));
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->display_name : trans('admin/reports/general.deleted_user')));
$row[] = str_replace(',', '', $item->acceptance->created_at);
$row[] = str_replace(',', '', $item->type);
$row[] = str_replace(',', '', $item->company_plain);
$row[] = str_replace(',', '', $item->category_plain);
$row[] = str_replace(',', '', $item->model_plain);
$row[] = str_replace(',', '', $item->name_plain);
$row[] = str_replace(',', '', $item->asset_tag);
$row[] = str_replace(',', '', ($item->acceptance->assignedto) ? $item->acceptance->assignedto->display_name : trans('admin/reports/general.deleted_user'));
$rows[] = implode(',', $row);
}
}
Expand Down
91 changes: 91 additions & 0 deletions app/Models/Checkoutable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

namespace App\Models;

use App\Helpers\Helper;


class Checkoutable
{
public function __construct(
public int $acceptance_id,
public string $company,
public string $category,
public string $model,
public string $asset_tag,
public string $name,
public string $type,
public object $acceptance,
public object $assignee,
public readonly string $category_plain,
public readonly string $model_plain,
public readonly string $name_plain,
public readonly string $company_plain,
){}

// public static function fromCheckoutable(Asset|Accessory|etc..)
// {
//
// }

public static function fromAcceptance(CheckoutAcceptance $unaccepted): self
{
$unaccepted_row = $unaccepted->checkoutable;
$acceptance = $unaccepted;

$assignee = $acceptance->assignedTo;
$company = optional($unaccepted_row->company)->present()?->nameUrl() ?? '';
$category = $model = $name = $tag = '';
$type = $acceptance->checkoutable_item_type ?? '';


if($unaccepted_row instanceof Asset){
$category = optional($unaccepted_row->model?->category?->present())->nameUrl() ?? '';
$model = optional($unaccepted_row->present())->modelUrl() ?? '';
$name = optional($unaccepted_row->present())->nameUrl() ?? '';
$tag = (string) ($unaccepted_row->asset_tag ?? '');
}
elseif($unaccepted_row instanceof Accessory){
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
$model = $unaccepted_row->model_number ?? '';
$name = optional($unaccepted_row->present())->nameUrl() ?? '';

}
if($unaccepted_row instanceof LicenseSeat){
$category = optional($unaccepted_row->license->category?->present())->nameUrl() ?? '';
$company = optional($unaccepted_row->license->company?->present())?->nameUrl() ?? '';
$model = '';
$name = $unaccepted_row->license->name ?? '';
}
if($unaccepted_row instanceof Consumable){
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
$model = $unaccepted_row->model_number ?? '';
$name = $unaccepted_row?->present()?->nameUrl() ?? '';

}
if($unaccepted_row instanceof Component){
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
$model = $unaccepted_row->model_number ?? '';
$name = $unaccepted_row?->present()?->nameUrl() ?? '';

}
$created = $acceptance->created_at;

return new self(
acceptance_id: $acceptance->id,
company: $company,
category: $category,
model: $model,
asset_tag: $tag,
name: $name,
type: $type,
acceptance: $acceptance,
assignee: $assignee,
//plain text for CSVs
category_plain: ($unaccepted_row->model?->category?->name ?? $unaccepted_row->license->category?->name ?? $unaccepted_row->category?->name ?? ''),
model_plain: ($unaccepted_row->model?->name ?? $unaccepted_row->model_number ?? ''),
name_plain: ($unaccepted_row->name ?? $unaccepted_row->license?->name ?? ''),
company_plain: ($unaccepted_row->company)->name ?? $unaccepted_row->license->company?->name ?? '',
);
}
}
102 changes: 65 additions & 37 deletions resources/views/reports/unaccepted_assets.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class="table table-striped snipe-table"
}'>
<thead>
<tr role="row">
<th class="col-sm-1" data-searchable="false" data-field="created_at" data-sortable="true">{{ trans('general.date') }}</th>
<th class="col-sm-1" data-field="created_at" data-searchable="false" data-sortable="true">{{ trans('general.date') }}</th>
<th class="col-sm-1" data-sortable="true" >{{ trans('general.type') }}</th>
<th class="col-sm-1" data-sortable="true" >{{ trans('admin/companies/table.title') }}</th>
<th class="col-sm-1" data-sortable="true" >{{ trans('general.category') }}</th>
<th class="col-sm-1" data-sortable="true" >{{ trans('admin/hardware/form.model') }}</th>
Expand All @@ -59,43 +60,70 @@ class="table table-striped snipe-table"
</tr>
</thead>
<tbody>
@if ($assetsForReport)
@foreach ($assetsForReport as $item)
@if ($item['assetItem'])
<tr @if($item['acceptance']->trashed()) style="text-decoration: line-through" @endif>
<td>{{ Helper::getFormattedDateObject($item['acceptance']->created_at, 'datetime', false) }}</td>
<td>{{ ($item['assetItem']->company) ? $item['assetItem']->company->name : '' }}</td>
<td>{!! $item['assetItem']->model->category->present()->nameUrl() !!}</td>
<td>{!! $item['assetItem']->present()->modelUrl() !!}</td>
<td>{!! $item['assetItem']->present()->nameUrl() !!}</td>
<td>{{ $item['assetItem']->asset_tag }}</td>
<td @if($item['acceptance']->assignedTo === null || $item['acceptance']->assignedTo->trashed()) style="text-decoration: line-through" @endif>{!! ($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->nameUrl() : trans('admin/reports/general.deleted_user') !!}</td>
<td class="white-space: nowrap;">
<nobr>
@if(!$item['acceptance']->trashed())
<form method="post" class="white-space: nowrap;" action="{{ route('reports/unaccepted_assets_sent_reminder') }}">
@if (($item['acceptance']->assignedTo) && ($item['acceptance']->assignedTo->email))
@csrf
<input type="hidden" name="acceptance_id" value="{{ $item['acceptance']->id }}">
<button class="btn btn-sm btn-warning" data-tooltip="true" data-title="{{ trans('admin/reports/general.send_reminder') }}">
<i class="fa fa-repeat" aria-hidden="true"></i>
</button>
@else
<span data-tooltip="true" data-title="{{ trans('admin/reports/general.cannot_send_reminder') }}">
<a class="btn btn-sm btn-warning disabled" href="#">
<i class="fa fa-repeat" aria-hidden="true"></i>
</a>
</span>
@endif
<a href="{{ route('reports/unaccepted_assets_delete', ['acceptanceId' => $item['acceptance']->id]) }}" class="btn btn-sm btn-danger delete-asset" data-tooltip="true" data-toggle="modal" data-content="{{ trans('general.delete_confirm', ['item' =>trans('admin/reports/general.acceptance_request')]) }}" data-title="{{ trans('general.delete') }}" onClick="return false;"><i class="fa fa-trash"></i></a>
</form>
@endif
@if ($itemsForReport)
@foreach ($itemsForReport as $item)
<tr @if($item->acceptance->trashed()) style="text-decoration: line-through" @endif>
{{-- Created date --}}
<td>
{{ Helper::getFormattedDateObject($item->acceptance->created_at, 'datetime', false) }}
</td>
{{-- Item Type --}}
<td>{{ $item->type }}</td>
{{-- Company name --}}
<td>{!! $item->company !!}</td>

{{-- Category --}}
<td>{!! $item->category !!}</td>

{{-- Model --}}
<td>{!! $item->model !!}</td>

{{-- Name --}}
<td>{!! $item->name !!}</td>

</nobr>
</td>
</tr>
@endif
@endforeach
{{-- Asset tag or blank --}}
<td>{{ $item->asset_tag }}</td>

{{-- Assigned To (with soft-delete strike if needed) --}}
<td @if(!$item->assignee || (method_exists($item->assignee, 'trashed') && $item->assignee->trashed())) style="text-decoration: line-through" @endif>
{!! $item->assignee
? optional($item->assignee->present())->nameUrl() ?? e($item->assignee->name)
: trans('admin/reports/general.deleted_user') !!}
</td>

{{-- Actions: send reminder / delete --}}
<td class="white-space: nowrap;">
<nobr>
@unless($item->acceptance->trashed())
<form method="post" class="white-space: nowrap;" action="{{ route('reports/unaccepted_assets_sent_reminder') }}">
@csrf
<input type="hidden" name="acceptance_id" value="{{ $item->acceptance_id }}">
@if ($item->assignee && $item->assignee->email)
<button class="btn btn-sm btn-warning" data-tooltip="true" data-title="{{ trans('admin/reports/general.send_reminder') }}">
<i class="fa fa-repeat" aria-hidden="true"></i>
</button>
@else
<span data-tooltip="true" data-title="{{ trans('admin/reports/general.cannot_send_reminder') }}">
<a class="btn btn-sm btn-warning disabled" href="#">
<i class="fa fa-repeat" aria-hidden="true"></i>
</a>
</span>
@endif
<a href="{{ route('reports/unaccepted_assets_delete', ['acceptanceId' => $item->acceptance_id]) }}"
class="btn btn-sm btn-danger delete-asset"
data-tooltip="true"
data-toggle="modal"
data-content="{{ trans('general.delete_confirm', ['item' => trans('admin/reports/general.acceptance_request')]) }}"
data-title="{{ trans('general.delete') }}"
onClick="return false;">
<i class="fa fa-trash"></i>
</a>
</form>
@endunless
</nobr>
</td>
</tr>
@endforeach
@endif
</tbody>
<tfoot>
Expand Down
Loading