From 58eac619eabc6f224a6147c66da6454cc37cca1d Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 16 Sep 2025 15:52:41 -0700 Subject: [PATCH 01/28] add checkoutable class, rework blade for unaccepted items --- app/Http/Controllers/ReportsController.php | 35 +++--- .../views/reports/unaccepted_assets.blade.php | 104 ++++++++++++------ 2 files changed, 90 insertions(+), 49 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 1eccf4886f..61d772809b 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -5,10 +5,14 @@ namespace App\Http\Controllers; 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; @@ -1111,14 +1115,14 @@ class ReportsController extends Controller $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(); @@ -1129,15 +1133,20 @@ class ReportsController extends Controller $query->withTrashed(); } - $assetsForReport = $query->get() - ->map(function ($acceptance) { - return [ - 'assetItem' => $acceptance->checkoutable, - 'acceptance' => $acceptance, - ]; - }); +// $assetsForReport = $query->get() +// ->map(function ($acceptance) { +// return [ +// 'assetItem' => $acceptance->checkoutable, +// 'acceptance' => $acceptance, +// ]; +// }); +// dd($assetsForReport); +// $assetsForReport = $query->get()->map(function ($unaccepted) {}) + $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' )); } /** diff --git a/resources/views/reports/unaccepted_assets.blade.php b/resources/views/reports/unaccepted_assets.blade.php index f0d93c0bed..ccffb10cf3 100644 --- a/resources/views/reports/unaccepted_assets.blade.php +++ b/resources/views/reports/unaccepted_assets.blade.php @@ -49,6 +49,7 @@ {{ trans('general.date') }} + {{ trans('general.type') }} {{ trans('admin/companies/table.title') }} {{ trans('general.category') }} {{ trans('admin/hardware/form.model') }} @@ -59,43 +60,74 @@ - @if ($assetsForReport) - @foreach ($assetsForReport as $item) - @if ($item['assetItem']) - trashed()) style="text-decoration: line-through" @endif> - {{ Helper::getFormattedDateObject($item['acceptance']->created_at, 'datetime', false) }} - {{ ($item['assetItem']->company) ? $item['assetItem']->company->name : '' }} - {!! $item['assetItem']->model->category->present()->nameUrl() !!} - {!! $item['assetItem']->present()->modelUrl() !!} - {!! $item['assetItem']->present()->nameUrl() !!} - {{ $item['assetItem']->asset_tag }} - 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') !!} - - - @if(!$item['acceptance']->trashed()) -
- @if (($item['acceptance']->assignedTo) && ($item['acceptance']->assignedTo->email)) - @csrf - - - @else - - - - - - @endif - -
- @endif + @if ($itemsForReport) + @foreach ($itemsForReport as $item) + acceptance->trashed()) style="text-decoration: line-through" @endif> + {{-- Created date (already formatted) --}} + + {{ Helper::getFormattedDateObject($item->acceptance->created_at, 'datetime', false) }} + + {{-- Item Type --}} + {{ $item->type }} + {{-- Company name --}} + {{ $item->company }} -
- - - @endif - @endforeach + {{-- Category --}} + {!! $item->category !!} + + {{-- Model --}} + {!! $item->model !!} + + {{-- Name --}} + {!! $item->name !!} + + {{-- Asset tag or blank --}} + {{ $item->asset_tag }} + + {{-- Assigned To (with soft-delete strike if needed) --}} + @php + $assignee = $item->acceptance->assignedTo; + $assigneeStruck = !$assignee || (method_exists($assignee, 'trashed') && $assignee->trashed()); + @endphp + + {!! $assignee + ? optional($assignee->present())->nameUrl() ?? e($assignee->name) + : trans('admin/reports/general.deleted_user') !!} + + + {{-- Actions: send reminder / delete --}} + + + @unless($item->acceptance->trashed()) +
+ @csrf + + @if ($assignee && $assignee->email) + + @else + + + + + + @endif + + + +
+ @endunless +
+ + + @endforeach @endif From dcbb09bbd7f22ab12ebe248e59444e3de2dce6d0 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 16 Sep 2025 15:59:53 -0700 Subject: [PATCH 02/28] added checkoutable class --- app/Http/Controllers/ReportsController.php | 11 ---- app/Models/Checkoutable.php | 73 ++++++++++++++++++++++ 2 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 app/Models/Checkoutable.php diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 61d772809b..5a350a0b9f 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1133,19 +1133,8 @@ class ReportsController extends Controller $query->withTrashed(); } -// $assetsForReport = $query->get() -// ->map(function ($acceptance) { -// return [ -// 'assetItem' => $acceptance->checkoutable, -// 'acceptance' => $acceptance, -// ]; -// }); -// dd($assetsForReport); -// $assetsForReport = $query->get()->map(function ($unaccepted) {}) $itemsForReport = $query->get()->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted)); - - return view('reports/unaccepted_assets', compact('itemsForReport','showDeleted' )); } diff --git a/app/Models/Checkoutable.php b/app/Models/Checkoutable.php new file mode 100644 index 0000000000..5c18a028aa --- /dev/null +++ b/app/Models/Checkoutable.php @@ -0,0 +1,73 @@ +checkoutable; + $acceptance = $unaccepted; + + $company = optional($unaccepted_row->company)->name ?? ''; + $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() ?? ''; + $tag = ''; + + } + if($unaccepted_row instanceof LicenseSeat){ + $category = ''; + $model = ''; + $name = $unaccepted_row->license->name ?? ''; + $tag = ''; + } + if($unaccepted_row instanceof Component){ + $category = optional($unaccepted_row->category?->present())->nameUrl() ?? ''; + $model = $unaccepted_row->model_number ?? ''; + $name = $unaccepted_row->present()->nameUrl() ?? ''; + $tag = ''; + } + return new self( + acceptance_id: $acceptance->id, + created_at: Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false), + company: $company, + category: $category, + model: $model, + asset_tag: $tag, + name: $name, + type: $type, + acceptance: $acceptance, + ); + } +} From 51ce570eb3b7c5ba714bdaa6e1f6edd505fc85be Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Thu, 18 Sep 2025 11:01:40 -0700 Subject: [PATCH 03/28] attempt to sort chronologically, can not resort still --- app/Http/Controllers/ReportsController.php | 3 ++- app/Models/Checkoutable.php | 11 +++++---- .../views/partials/bootstrap-table.blade.php | 11 +++++++++ .../views/reports/unaccepted_assets.blade.php | 24 ++++++++++--------- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 5a350a0b9f..946e810b8a 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1127,7 +1127,8 @@ class ReportsController extends Controller 'assignedTo' => function($query){ $query->withTrashed(); } - ]); + ])->orderByDesc('checkout_acceptances.created_at'); + if ($showDeleted) { $query->withTrashed(); diff --git a/app/Models/Checkoutable.php b/app/Models/Checkoutable.php index 5c18a028aa..1648b5cae7 100644 --- a/app/Models/Checkoutable.php +++ b/app/Models/Checkoutable.php @@ -9,7 +9,6 @@ class Checkoutable { public function __construct( public int $acceptance_id, - public string $created_at, public string $company, public string $category, public string $model, @@ -17,6 +16,7 @@ class Checkoutable public string $name, public string $type, public object $acceptance, + public object $assignee, ){} // public static function fromCheckoutable(Asset|Accessory|etc..) @@ -29,10 +29,12 @@ class Checkoutable $unaccepted_row = $unaccepted->checkoutable; $acceptance = $unaccepted; + $assignee = $acceptance->assignedTo; $company = optional($unaccepted_row->company)->name ?? ''; $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() ?? ''; @@ -43,24 +45,22 @@ class Checkoutable $category = optional($unaccepted_row->category?->present())->nameUrl() ?? ''; $model = $unaccepted_row->model_number ?? ''; $name = optional($unaccepted_row->present())->nameUrl() ?? ''; - $tag = ''; } if($unaccepted_row instanceof LicenseSeat){ $category = ''; $model = ''; $name = $unaccepted_row->license->name ?? ''; - $tag = ''; } if($unaccepted_row instanceof Component){ $category = optional($unaccepted_row->category?->present())->nameUrl() ?? ''; $model = $unaccepted_row->model_number ?? ''; $name = $unaccepted_row->present()->nameUrl() ?? ''; - $tag = ''; } + $created = $acceptance->created_at; + return new self( acceptance_id: $acceptance->id, - created_at: Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false), company: $company, category: $category, model: $model, @@ -68,6 +68,7 @@ class Checkoutable name: $name, type: $type, acceptance: $acceptance, + assignee: $assignee, ); } } diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 8ebbc9fb16..687927719c 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -870,6 +870,17 @@ return value.formatted; } } + function createdAtSorter(a, b, rowA, rowB) { + const CREATED_AT_COL_INDEX = 0; + const ts = row => Number(row?._$el?.find('td').eq(CREATED_AT_COL_INDEX).attr('data-timestamp') || 0); + return ts(rowA) - ts(rowB); + } + + function createdAtSorter(a, b, rowA, rowB) { + const ts = row => + Number(row?._$el?.find('td').eq(CREATED_AT_COL_INDEX).attr('data-timestamp') || 0); + return ts(rowA) - ts(rowB); + } function iconFormatter(value) { if (value) { diff --git a/resources/views/reports/unaccepted_assets.blade.php b/resources/views/reports/unaccepted_assets.blade.php index ccffb10cf3..01081ce5aa 100644 --- a/resources/views/reports/unaccepted_assets.blade.php +++ b/resources/views/reports/unaccepted_assets.blade.php @@ -48,7 +48,13 @@ }'> - {{ trans('general.date') }} + + {{ trans('general.date') }} + {{ trans('general.type') }} {{ trans('admin/companies/table.title') }} {{ trans('general.category') }} @@ -63,8 +69,8 @@ @if ($itemsForReport) @foreach ($itemsForReport as $item) acceptance->trashed()) style="text-decoration: line-through" @endif> - {{-- Created date (already formatted) --}} - + {{-- Created date --}} + {{ Helper::getFormattedDateObject($item->acceptance->created_at, 'datetime', false) }} {{-- Item Type --}} @@ -85,13 +91,9 @@ {{ $item->asset_tag }} {{-- Assigned To (with soft-delete strike if needed) --}} - @php - $assignee = $item->acceptance->assignedTo; - $assigneeStruck = !$assignee || (method_exists($assignee, 'trashed') && $assignee->trashed()); - @endphp - - {!! $assignee - ? optional($assignee->present())->nameUrl() ?? e($assignee->name) + 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') !!} @@ -102,7 +104,7 @@
@csrf - @if ($assignee && $assignee->email) + @if ($item->assignee && $item->assignee->email) From a6cb75c481c2a6e719c9a8af12856e15cbe20f20 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Mon, 22 Sep 2025 12:19:34 -0700 Subject: [PATCH 04/28] remove sorter, didnt work --- resources/views/partials/bootstrap-table.blade.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 687927719c..8ebbc9fb16 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -870,17 +870,6 @@ return value.formatted; } } - function createdAtSorter(a, b, rowA, rowB) { - const CREATED_AT_COL_INDEX = 0; - const ts = row => Number(row?._$el?.find('td').eq(CREATED_AT_COL_INDEX).attr('data-timestamp') || 0); - return ts(rowA) - ts(rowB); - } - - function createdAtSorter(a, b, rowA, rowB) { - const ts = row => - Number(row?._$el?.find('td').eq(CREATED_AT_COL_INDEX).attr('data-timestamp') || 0); - return ts(rowA) - ts(rowB); - } function iconFormatter(value) { if (value) { From ab30df10ff9f2d53b4400e718cd445a05df0a99d Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Mon, 22 Sep 2025 12:20:32 -0700 Subject: [PATCH 05/28] remove sorter from blade --- resources/views/reports/unaccepted_assets.blade.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/resources/views/reports/unaccepted_assets.blade.php b/resources/views/reports/unaccepted_assets.blade.php index 01081ce5aa..a70652855f 100644 --- a/resources/views/reports/unaccepted_assets.blade.php +++ b/resources/views/reports/unaccepted_assets.blade.php @@ -48,13 +48,7 @@ }'> - - {{ trans('general.date') }} - + {{ trans('general.date') }} {{ trans('general.type') }} {{ trans('admin/companies/table.title') }} {{ trans('general.category') }} From 7077faaf4ac42619e6e487e831e88e2cc1f5da7a Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 11:08:37 -0700 Subject: [PATCH 06/28] updated the postassetAcceptanceReport query and rows --- app/Http/Controllers/ReportsController.php | 57 ++++++++++++++++------ app/Models/Checkoutable.php | 6 +++ 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 946e810b8a..d36944e973 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1220,22 +1220,44 @@ class ReportsController extends Controller * 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(); + $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' => function($acceptance){ + $acceptance->withTrashed(); + } + ])->orderByDesc('checkout_acceptances.created_at'); } else { - $acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get(); + $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' => function($acceptances){ + } + ])->orderByDesc('checkout_acceptances.created_at'); } - $assetsForReport = $acceptances - ->filter(function($acceptance) { - return $acceptance->checkoutable_type == 'App\Models\Asset'; - }) - ->map(function($acceptance) { - return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance]; - }); + $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'), @@ -1246,16 +1268,19 @@ class ReportsController extends Controller $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); + $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); } } diff --git a/app/Models/Checkoutable.php b/app/Models/Checkoutable.php index 1648b5cae7..21420fdd86 100644 --- a/app/Models/Checkoutable.php +++ b/app/Models/Checkoutable.php @@ -17,6 +17,9 @@ class Checkoutable 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 static function fromCheckoutable(Asset|Accessory|etc..) @@ -69,6 +72,9 @@ class Checkoutable type: $type, acceptance: $acceptance, assignee: $assignee, + category_plain: optional($unaccepted_row->model?->category)->name ?? '', + model_plain: optional($unaccepted_row->model)->name ?? '', + name_plain: (string) ($unaccepted_row->name ?? ''), ); } } From ca8eae4064b2492d27e5447dcf6c7665e6d90798 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 11:40:29 -0700 Subject: [PATCH 07/28] updated the csv values to be plain --- app/Http/Controllers/ReportsController.php | 28 ++++++------------- app/Models/Checkoutable.php | 5 +++- .../views/reports/unaccepted_assets.blade.php | 2 +- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index d36944e973..689e5c38f3 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1219,25 +1219,13 @@ class ReportsController extends Controller /** * Get all assets with pending checkout acceptances */ - if($showDeleted) { + $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' => function($acceptance){ + 'checkoutable' => function (MorphTo $acceptance) use ($showDeleted) { + if ($showDeleted) { $acceptance->withTrashed(); } - ])->orderByDesc('checkout_acceptances.created_at'); - } else { - $acceptances = CheckoutAcceptance::pending() - ->with([ - 'checkoutable' => function (MorphTo $acceptance) { $acceptance->morphWith([ Asset::class => ['model.category', 'assignedTo', 'company'], Accessory::class => ['category','checkouts', 'company'], @@ -1245,10 +1233,12 @@ class ReportsController extends Controller Component::class => ['assignedTo', 'company'], ]); }, - 'assignedTo' => function($acceptances){ - } + 'assignedTo', ])->orderByDesc('checkout_acceptances.created_at'); - } + + if ($showDeleted) { + $acceptances->withTrashed(); + } $itemsForReport = $acceptances->get()->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted)); @@ -1275,7 +1265,7 @@ class ReportsController extends Controller $row = [ ]; $row[] = str_replace(',', '', $item->acceptance->created_at); $row[] = str_replace(',', '', $item->type); - $row[] = str_replace(',', '', $item->company); + $row[] = str_replace(',', '', $item->company_plain); $row[] = str_replace(',', '', $item->category_plain); $row[] = str_replace(',', '', $item->model_plain); $row[] = str_replace(',', '', $item->name_plain); diff --git a/app/Models/Checkoutable.php b/app/Models/Checkoutable.php index 21420fdd86..b629aa7d0d 100644 --- a/app/Models/Checkoutable.php +++ b/app/Models/Checkoutable.php @@ -20,6 +20,7 @@ class Checkoutable 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..) @@ -33,7 +34,7 @@ class Checkoutable $acceptance = $unaccepted; $assignee = $acceptance->assignedTo; - $company = optional($unaccepted_row->company)->name ?? ''; + $company = optional($unaccepted_row->company)->present()?->nameUrl() ?? ''; $category = $model = $name = $tag = ''; $type = $acceptance->checkoutable_item_type ?? ''; @@ -72,9 +73,11 @@ class Checkoutable type: $type, acceptance: $acceptance, assignee: $assignee, + //plain text for CSVs category_plain: optional($unaccepted_row->model?->category)->name ?? '', model_plain: optional($unaccepted_row->model)->name ?? '', name_plain: (string) ($unaccepted_row->name ?? ''), + company_plain: optional($unaccepted_row->company)->name ?? '', ); } } diff --git a/resources/views/reports/unaccepted_assets.blade.php b/resources/views/reports/unaccepted_assets.blade.php index a70652855f..2b78a756ef 100644 --- a/resources/views/reports/unaccepted_assets.blade.php +++ b/resources/views/reports/unaccepted_assets.blade.php @@ -70,7 +70,7 @@ {{-- Item Type --}} {{ $item->type }} {{-- Company name --}} - {{ $item->company }} + {!! $item->company !!} {{-- Category --}} {!! $item->category !!} From 20adad3c6b64e038dfa12b9ac96c0f88d007339b Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 12:09:21 -0700 Subject: [PATCH 08/28] fix company name link, clean up query --- app/Http/Controllers/ReportsController.php | 2 +- resources/views/reports/unaccepted_assets.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 689e5c38f3..6351121b51 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1214,7 +1214,7 @@ class ReportsController extends Controller public function postAssetAcceptanceReport($deleted = false) : Response { $this->authorize('reports.view'); - $showDeleted = $deleted == 'deleted'; + $showDeleted = request('deleted') === 'deleted';; /** * Get all assets with pending checkout acceptances diff --git a/resources/views/reports/unaccepted_assets.blade.php b/resources/views/reports/unaccepted_assets.blade.php index 2b78a756ef..47ad6908b6 100644 --- a/resources/views/reports/unaccepted_assets.blade.php +++ b/resources/views/reports/unaccepted_assets.blade.php @@ -70,7 +70,7 @@ {{-- Item Type --}} {{ $item->type }} {{-- Company name --}} - {!! $item->company !!} + {!! $item->company !!}/td> {{-- Category --}} {!! $item->category !!} From 5af85bfe7d11865959b4ab39e5930f7fa8f76b30 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 12:10:50 -0700 Subject: [PATCH 09/28] further clean up --- app/Http/Controllers/ReportsController.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 6351121b51..5bb6a7d0db 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1222,10 +1222,7 @@ class ReportsController extends Controller $acceptances = CheckoutAcceptance::pending() ->with([ - 'checkoutable' => function (MorphTo $acceptance) use ($showDeleted) { - if ($showDeleted) { - $acceptance->withTrashed(); - } + 'checkoutable' => function (MorphTo $acceptance) { $acceptance->morphWith([ Asset::class => ['model.category', 'assignedTo', 'company'], Accessory::class => ['category','checkouts', 'company'], From 6f3323c195e12dd35ab100b5d54a7e415e1e2905 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 12:50:26 -0700 Subject: [PATCH 10/28] fix data in view model --- app/Models/Checkoutable.php | 20 +++++++++++++------ .../views/reports/unaccepted_assets.blade.php | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/Models/Checkoutable.php b/app/Models/Checkoutable.php index b629aa7d0d..d73de4d4b3 100644 --- a/app/Models/Checkoutable.php +++ b/app/Models/Checkoutable.php @@ -52,14 +52,22 @@ class Checkoutable } if($unaccepted_row instanceof LicenseSeat){ - $category = ''; + $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() ?? ''; + $name = $unaccepted_row?->present()?->nameUrl() ?? ''; + } $created = $acceptance->created_at; @@ -74,10 +82,10 @@ class Checkoutable acceptance: $acceptance, assignee: $assignee, //plain text for CSVs - category_plain: optional($unaccepted_row->model?->category)->name ?? '', - model_plain: optional($unaccepted_row->model)->name ?? '', - name_plain: (string) ($unaccepted_row->name ?? ''), - company_plain: optional($unaccepted_row->company)->name ?? '', + 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 ?? '', ); } } diff --git a/resources/views/reports/unaccepted_assets.blade.php b/resources/views/reports/unaccepted_assets.blade.php index 47ad6908b6..2b78a756ef 100644 --- a/resources/views/reports/unaccepted_assets.blade.php +++ b/resources/views/reports/unaccepted_assets.blade.php @@ -70,7 +70,7 @@ {{-- Item Type --}} {{ $item->type }} {{-- Company name --}} - {!! $item->company !!}/td> + {!! $item->company !!} {{-- Category --}} {!! $item->category !!} From 2b5254e68f05173521b213780df87672520b0485 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 16:55:10 -0700 Subject: [PATCH 11/28] fixed, eager loading, variable names, clean up --- app/Http/Controllers/ReportsController.php | 13 +++++---- app/Models/Checkoutable.php | 27 +++++++------------ .../views/reports/unaccepted_assets.blade.php | 15 +++++------ 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 5bb6a7d0db..10c0a2e943 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -12,6 +12,7 @@ use App\Models\AssetModel; use App\Models\Category; use App\Models\Checkoutable; use App\Models\Component; +use App\Models\Consumable; use App\Models\LicenseSeat; use App\Models\Maintenance; use App\Models\CheckoutAcceptance; @@ -22,6 +23,7 @@ use App\Models\License; use App\Models\ReportTemplate; use App\Models\Setting; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -1228,6 +1230,7 @@ class ReportsController extends Controller Accessory::class => ['category','checkouts', 'company'], LicenseSeat::class => ['user', 'license'], Component::class => ['assignedTo', 'company'], + Consumable::class => ['company'], ]); }, 'assignedTo', @@ -1247,7 +1250,7 @@ class ReportsController extends Controller trans('admin/companies/table.title'), trans('general.category'), trans('admin/hardware/form.model'), - trans('admin/hardware/form.name'), + trans('general.name'), trans('admin/hardware/table.asset_tag'), trans('admin/hardware/table.checkoutto'), ]; @@ -1262,10 +1265,10 @@ class ReportsController extends Controller $row = [ ]; $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->plain_text_company); + $row[] = str_replace(',', '', $item->plain_text_category); + $row[] = str_replace(',', '', $item->plain_text_model); + $row[] = str_replace(',', '', $item->plain_text_name); $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); diff --git a/app/Models/Checkoutable.php b/app/Models/Checkoutable.php index d73de4d4b3..bf169eb048 100644 --- a/app/Models/Checkoutable.php +++ b/app/Models/Checkoutable.php @@ -2,9 +2,6 @@ namespace App\Models; -use App\Helpers\Helper; - - class Checkoutable { public function __construct( @@ -17,17 +14,12 @@ class Checkoutable 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 readonly string $plain_text_category, + public readonly string $plain_text_model, + public readonly string $plain_text_name, + public readonly string $plain_text_company, ){} -// public static function fromCheckoutable(Asset|Accessory|etc..) -// { -// -// } - public static function fromAcceptance(CheckoutAcceptance $unaccepted): self { $unaccepted_row = $unaccepted->checkoutable; @@ -55,7 +47,7 @@ class Checkoutable $category = optional($unaccepted_row->license->category?->present())->nameUrl() ?? ''; $company = optional($unaccepted_row->license->company?->present())?->nameUrl() ?? ''; $model = ''; - $name = $unaccepted_row->license->name ?? ''; + $name = $unaccepted_row->license->present()->nameUrl() ?? ''; } if($unaccepted_row instanceof Consumable){ $category = optional($unaccepted_row->category?->present())->nameUrl() ?? ''; @@ -69,7 +61,6 @@ class Checkoutable $name = $unaccepted_row?->present()?->nameUrl() ?? ''; } - $created = $acceptance->created_at; return new self( acceptance_id: $acceptance->id, @@ -82,10 +73,10 @@ class Checkoutable 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 ?? '', + plain_text_category: ($unaccepted_row->model?->category?->name ?? $unaccepted_row->license->category?->name ?? $unaccepted_row->category?->name ?? ''), + plain_text_model: ($unaccepted_row->model?->name ?? $unaccepted_row->model_number ?? ''), + plain_text_name: ($unaccepted_row->name ?? $unaccepted_row->license?->name ?? ''), + plain_text_company: ($unaccepted_row->company)->name ?? $unaccepted_row->license->company?->name ?? '', ); } } diff --git a/resources/views/reports/unaccepted_assets.blade.php b/resources/views/reports/unaccepted_assets.blade.php index 2b78a756ef..5dbefbc8e1 100644 --- a/resources/views/reports/unaccepted_assets.blade.php +++ b/resources/views/reports/unaccepted_assets.blade.php @@ -53,7 +53,7 @@ {{ trans('admin/companies/table.title') }} {{ trans('general.category') }} {{ trans('admin/hardware/form.model') }} - {{ trans('admin/hardware/form.name') }} + {{ trans('general.name') }} {{ trans('admin/hardware/table.asset_tag') }} {{ trans('admin/hardware/table.checkoutto') }} {{ trans('table.actions') }} @@ -92,8 +92,8 @@ {{-- Actions: send reminder / delete --}} - - + + @unless($item->acceptance->trashed()) @csrf @@ -104,10 +104,10 @@ @else - - - - + + + + @endif @endunless - @endforeach From 3527c357cc8e6a73b5941dbe3770039ebeca7485 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 16:57:06 -0700 Subject: [PATCH 12/28] whoops --- app/Http/Controllers/ReportsController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 10c0a2e943..be00c2c0e1 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1124,6 +1124,7 @@ class ReportsController extends Controller Accessory::class => ['category','checkouts', 'company'], LicenseSeat::class => ['user', 'license'], Component::class => ['assignedTo', 'company'], + Consumable::class => ['company'], ]); }, 'assignedTo' => function($query){ From d31d99b40f3dd56e697d2e285e94f0c88b6798e2 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 17:00:08 -0700 Subject: [PATCH 13/28] remove else and blank lines --- app/Models/Checkoutable.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Models/Checkoutable.php b/app/Models/Checkoutable.php index bf169eb048..9bf091c15f 100644 --- a/app/Models/Checkoutable.php +++ b/app/Models/Checkoutable.php @@ -37,11 +37,10 @@ class Checkoutable $name = optional($unaccepted_row->present())->nameUrl() ?? ''; $tag = (string) ($unaccepted_row->asset_tag ?? ''); } - elseif($unaccepted_row instanceof Accessory){ + if($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() ?? ''; @@ -53,13 +52,11 @@ class Checkoutable $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() ?? ''; - } return new self( From 0ce20c1eddff6e1f9f6a2f75aadbbc0d61971ed5 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 23 Sep 2025 18:40:03 -0700 Subject: [PATCH 14/28] fix send reminder method to handle other types --- app/Http/Controllers/ReportsController.php | 63 ++++++++++++++++++---- app/Models/Component.php | 14 ++++- app/Models/Consumable.php | 14 +++++ app/Models/License.php | 7 ++- app/Models/LicenseSeat.php | 24 +++++++++ 5 files changed, 109 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index be00c2c0e1..6d79f80893 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -3,7 +3,11 @@ namespace App\Http\Controllers; use App\Helpers\Helper; +use App\Mail\CheckoutAccessoryMail; use App\Mail\CheckoutAssetMail; +use App\Mail\CheckoutComponentMail; +use App\Mail\CheckoutConsumableMail; +use App\Mail\CheckoutLicenseMail; use App\Models\Accessory; use App\Models\AccessoryCheckout; use App\Models\Actionlog; @@ -27,6 +31,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Mail\Mailable; use Illuminate\Support\Facades\Mail; use \Illuminate\Contracts\View\View; use League\Csv\Reader; @@ -1151,41 +1156,77 @@ class ReportsController extends Controller public function sentAssetAcceptanceReminder(Request $request) : RedirectResponse { $this->authorize('reports.view'); - - if (!$acceptance = CheckoutAcceptance::pending()->find($request->input('acceptance_id'))) { + $id = $request->input('acceptance_id'); + $query = CheckoutAcceptance::query() + ->with([ + 'checkoutable' => function (MorphTo $query) { + $query->morphWith([ + Asset::class => ['model.category', 'assignedTo', 'company', 'checkouts'], + Accessory::class => ['category', 'company', 'checkouts'], + LicenseSeat::class => ['user', 'license', 'checkouts'], + Component::class => ['assignedTo', 'company', 'checkouts'], + Consumable::class => ['company', 'checkouts'], + ]); + }, + 'assignedTo' => fn ($q) => $q->withTrashed(), + ]) + ->pending(); + $acceptance = $query->find($id); + if (!$acceptance) { Log::debug('No pending acceptances'); // Redirect to the unaccepted assets report page with error return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data')); } + $item = $acceptance->checkoutable; + $assignee = $acceptance->assignedTo ?? $item->assignedTo ?? null; + $email = $assignee?->email; + $locale = $assignee?->locale; - $assetItem = $acceptance->checkoutable; - - Log::debug(print_r($assetItem, true)); + Log::debug(print_r($acceptance, true)); if (is_null($acceptance->created_at)){ Log::debug('No acceptance created_at'); return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data')); } else { - $logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get(); - + if($item instanceof LicenseSeat){ + $logItem_res = $item->license->checkouts()->where('created_at', '=', $acceptance->created_at)->get(); + } + else{ + $logItem_res = $item->checkouts()->where('created_at', '=', $acceptance->created_at)->get(); + } if ($logItem_res->isEmpty()){ Log::debug('Acceptance date mismatch'); return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data')); } $logItem = $logItem_res[0]; } - $email = $assetItem->assignedTo?->email; - $locale = $assetItem->assignedTo?->locale; if (is_null($email) || $email === '') { return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email')); } - - Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale)); + $mailable = $this->getCheckoutMailType($acceptance, $logItem); + Mail::to($email)->send($mailable->locale($locale)); return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent')); } + private function getCheckoutMailType(CheckoutAcceptance $acceptance, $logItem) : Mailable + { + $lookup = [ + Accessory::class => CheckoutAccessoryMail::class, + Asset::class => CheckoutAssetMail::class, + LicenseSeat::class => CheckoutLicenseMail::class, + Consumable::class => CheckoutConsumableMail::class, + Component::class => CheckoutComponentMail::class, + ]; + $mailable= $lookup[get_class($acceptance->checkoutable)]; + return new $mailable($acceptance->checkoutable, + $acceptance->checkedOutTo ?? $acceptance->assignedTo, + $logItem->adminuser, + $acceptance, + $acceptance->note); + + } /** * sentAssetAcceptanceReminder * diff --git a/app/Models/Component.php b/app/Models/Component.php index ddcb3ad575..2ce67c5ec7 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -274,7 +274,19 @@ class Component extends SnipeModel { return $this->category?->checkin_email; } - + /** + * Get the list of checkouts for this License + * + * @author [A. Gianotto] [] + * @since [v2.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function checkouts() + { + return $this->assetlog()->where('action_type', '=', 'checkout') + ->orderBy('created_at', 'desc') + ->withTrashed(); + } /** * Check how many items within a component are remaining diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index 8c6e43c490..27b7a5581f 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -313,6 +313,20 @@ class Consumable extends SnipeModel return $remaining; } + /** + * Get the list of checkouts for this consumable + * + * @author [A. Gianotto] [] + * @since [v2.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function checkouts() + { + return $this->assetlog()->where('action_type', '=', 'checkout') + ->orderBy('created_at', 'desc') + ->withTrashed(); + } + /** * ----------------------------------------------- * BEGIN MUTATORS diff --git a/app/Models/License.php b/app/Models/License.php index d84f42f0a1..2ee10cddba 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -391,7 +391,12 @@ class License extends Depreciable } return false; } - + public function checkouts() + { + return $this->assetlog()->where('action_type', '=', 'checkout') + ->orderBy('created_at', 'desc') + ->withTrashed(); + } /** * Determine whether the user should be required to accept the license * diff --git a/app/Models/LicenseSeat.php b/app/Models/LicenseSeat.php index 9ddd3fb431..9ad51901e7 100755 --- a/app/Models/LicenseSeat.php +++ b/app/Models/LicenseSeat.php @@ -118,7 +118,31 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild return false; } + /** + * Get the list of checkouts for this License + * + * @author [A. Gianotto] [] + * @since [v2.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function checkouts() + { + return $this->assetlog()->where('action_type', '=', 'checkout') + ->orderBy('created_at', 'desc') + ->withTrashed(); + } + /** + * Establishes the license -> action logs relationship + * + * @author [A. Gianotto] [] + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function assetlog() + { + return $this->hasMany(Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed(); + } /** * Query builder scope to order on department * From 9ac2ea2a52423c9524f332a4c59691eda0fd5328 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 24 Sep 2025 13:22:33 -0700 Subject: [PATCH 15/28] fix test to check all mailable types for reminders --- app/Http/Controllers/ReportsController.php | 4 +- .../Email/AssetAcceptanceReminderTest.php | 61 ++++++++++++++++--- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 6d79f80893..d06e779a40 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -1189,10 +1189,10 @@ class ReportsController extends Controller return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data')); } else { if($item instanceof LicenseSeat){ - $logItem_res = $item->license->checkouts()->where('created_at', '=', $acceptance->created_at)->get(); + $logItem_res = $item->license->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get(); } else{ - $logItem_res = $item->checkouts()->where('created_at', '=', $acceptance->created_at)->get(); + $logItem_res = $item->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get(); } if ($logItem_res->isEmpty()){ Log::debug('Acceptance date mismatch'); diff --git a/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php b/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php index deb3e07d2c..246d52366c 100644 --- a/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php +++ b/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php @@ -2,8 +2,19 @@ namespace Tests\Feature\Notifications\Email; +use App\Mail\CheckoutAccessoryMail; use App\Mail\CheckoutAssetMail; +use App\Mail\CheckoutComponentMail; +use App\Mail\CheckoutConsumableMail; +use App\Mail\CheckoutLicenseMail; +use App\Models\Accessory; +use App\Models\Actionlog; +use App\Models\Asset; use App\Models\CheckoutAcceptance; +use App\Models\Component; +use App\Models\Consumable; +use App\Models\License; +use App\Models\LicenseSeat; use App\Models\User; use Illuminate\Support\Facades\Mail; use PHPUnit\Framework\Attributes\DataProvider; @@ -86,16 +97,52 @@ class AssetAcceptanceReminderTest extends TestCase public function testReminderIsSentToUser() { - $checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create(); + $checkedOutBy = User::factory()->canViewReports()->create(); - $this->actingAs(User::factory()->canViewReports()->create()) - ->post($this->routeFor($checkoutAcceptance)) + $checkoutTypes = [ + Asset::class => CheckoutAssetMail::class, + Accessory::class => CheckoutAccessoryMail::class, + LicenseSeat::class => CheckoutLicenseMail::class, + Consumable::class => CheckoutConsumableMail::class, + //for the future its setup for components, but we dont send reminders for components at the moment. +// Component::class => CheckoutComponentMail::class, + ]; + + $assignee = User::factory()->create(['email' => 'test@example.com']); + foreach ($checkoutTypes as $modelClass => $mailable) { + + $item = $modelClass::factory()->create(); + $acceptance = CheckoutAcceptance::factory()->pending()->create([ + 'checkoutable_id' => $item->id, + 'checkoutable_type' => $modelClass, + 'assigned_to_id' => $assignee->id, + ]); + + if ($modelClass === LicenseSeat::class) { + $logType = License::class; + $logId = $item->license->id; + } else { + $logType = $modelClass; + $logId = $item->id; + } + + Actionlog::factory()->create([ + 'action_type' => 'checkout', + 'created_by' => $checkedOutBy->id, + 'target_id' => $assignee->id, + 'item_type' => $logType, + 'item_id' => $logId, + 'created_at' => $acceptance->created_at, + ]); + + $this->actingAs($checkedOutBy) + ->post($this->routeFor($acceptance)) ->assertRedirect(route('reports/unaccepted_assets')); + } - Mail::assertSent(CheckoutAssetMail::class, 1); - Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) use ($checkoutAcceptance) { - return $mail->hasTo($checkoutAcceptance->assignedTo->email) - && $mail->hasSubject(trans('mail.unaccepted_asset_reminder')); + Mail::assertSent($mailable, 1); + Mail::assertSent($mailable, function ($mail) use ($assignee) { + return $mail->hasTo($assignee->email); }); } From bf6964ee621185b5a704f229760a74f0339b3f4d Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 24 Sep 2025 13:41:04 -0700 Subject: [PATCH 16/28] tests passing, CheckoutAcceptance Factory needs eyes though --- .../factories/CheckoutAcceptanceFactory.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/database/factories/CheckoutAcceptanceFactory.php b/database/factories/CheckoutAcceptanceFactory.php index bb56ab2b4a..4f8f3fe469 100644 --- a/database/factories/CheckoutAcceptanceFactory.php +++ b/database/factories/CheckoutAcceptanceFactory.php @@ -27,16 +27,16 @@ class CheckoutAcceptanceFactory extends Factory public function configure(): static { return $this->afterCreating(function (CheckoutAcceptance $acceptance) { - if ($acceptance->checkoutable instanceof Asset) { - $this->createdAssociatedActionLogEntry($acceptance); - } - - if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) { - $acceptance->checkoutable->update([ - 'assigned_to' => $acceptance->assigned_to_id, - 'assigned_type' => get_class($acceptance->assignedTo), - ]); - } +// if ($acceptance->checkoutable instanceof Asset) { +// $this->createdAssociatedActionLogEntry($acceptance); +// } +// +// if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) { +// $acceptance->checkoutable->update([ +// 'assigned_to' => $acceptance->assigned_to_id, +// 'assigned_type' => get_class($acceptance->assignedTo), +// ]); +// } }); } From 6ca0e19819323407d8e57bea2d910804d57f52dc Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 24 Sep 2025 13:50:41 -0700 Subject: [PATCH 17/28] uncomment code --- .../factories/CheckoutAcceptanceFactory.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/database/factories/CheckoutAcceptanceFactory.php b/database/factories/CheckoutAcceptanceFactory.php index 4f8f3fe469..bb56ab2b4a 100644 --- a/database/factories/CheckoutAcceptanceFactory.php +++ b/database/factories/CheckoutAcceptanceFactory.php @@ -27,16 +27,16 @@ class CheckoutAcceptanceFactory extends Factory public function configure(): static { return $this->afterCreating(function (CheckoutAcceptance $acceptance) { -// if ($acceptance->checkoutable instanceof Asset) { -// $this->createdAssociatedActionLogEntry($acceptance); -// } -// -// if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) { -// $acceptance->checkoutable->update([ -// 'assigned_to' => $acceptance->assigned_to_id, -// 'assigned_type' => get_class($acceptance->assignedTo), -// ]); -// } + if ($acceptance->checkoutable instanceof Asset) { + $this->createdAssociatedActionLogEntry($acceptance); + } + + if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) { + $acceptance->checkoutable->update([ + 'assigned_to' => $acceptance->assigned_to_id, + 'assigned_type' => get_class($acceptance->assignedTo), + ]); + } }); } From 881c789a75e8d4313a272d8089bf71162a3021f6 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 24 Sep 2025 15:45:01 -0700 Subject: [PATCH 18/28] add usuage of withoutactionlog --- .../Feature/Notifications/Email/AssetAcceptanceReminderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php b/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php index 246d52366c..04081241c9 100644 --- a/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php +++ b/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php @@ -112,7 +112,7 @@ class AssetAcceptanceReminderTest extends TestCase foreach ($checkoutTypes as $modelClass => $mailable) { $item = $modelClass::factory()->create(); - $acceptance = CheckoutAcceptance::factory()->pending()->create([ + $acceptance = CheckoutAcceptance::factory()->withoutActionLog()->pending()->create([ 'checkoutable_id' => $item->id, 'checkoutable_type' => $modelClass, 'assigned_to_id' => $assignee->id, From 3ae7a7703260b1f8cdc15ddcfc8c7f4ecca7d113 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Thu, 25 Sep 2025 10:43:41 -0700 Subject: [PATCH 19/28] update the snipeit reminder command --- .../Commands/SendAcceptanceReminder.php | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/app/Console/Commands/SendAcceptanceReminder.php b/app/Console/Commands/SendAcceptanceReminder.php index 67efecbb34..4eb4a5114f 100644 --- a/app/Console/Commands/SendAcceptanceReminder.php +++ b/app/Console/Commands/SendAcceptanceReminder.php @@ -3,13 +3,18 @@ namespace App\Console\Commands; use App\Mail\UnacceptedAssetReminderMail; +use App\Models\Accessory; use App\Models\Asset; use App\Models\CheckoutAcceptance; +use App\Models\Component; +use App\Models\Consumable; +use App\Models\LicenseSeat; use App\Models\Setting; use App\Models\User; use App\Notifications\CheckoutAssetNotification; use App\Notifications\CurrentInventory; use Illuminate\Console\Command; +use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Support\Facades\Mail; class SendAcceptanceReminder extends Command @@ -45,19 +50,30 @@ class SendAcceptanceReminder extends Command */ public function handle() { - $pending = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset') - ->whereHas('checkoutable', function($query) { - $query->where('accepted_at', null) - ->where('declined_at', null); - }) - ->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser']) - ->get(); + $pending = CheckoutAcceptance::query() + ->with([ + 'checkoutable' => function (MorphTo $morph) { + $morph->morphWith([ + Asset::class => ['model.category', 'assignedTo', 'adminuser', 'company', 'checkouts'], + Accessory::class => ['category', 'company', 'checkouts'], + LicenseSeat::class => ['user', 'license', 'checkouts'], + Component::class => ['assignedTo', 'company', 'checkouts'], + Consumable::class => ['company', 'checkouts'], + ]); + }, + 'assignedTo', + ]) + ->whereHasMorph( + 'checkoutable', + [Asset::class, Accessory::class, LicenseSeat::class, Component::class, Consumable::class], + fn ($q) => $q->whereNull('accepted_at') + ->whereNull('declined_at') + ) + ->pending() + ->get(); $count = 0; $unacceptedAssetGroups = $pending - ->filter(function($acceptance) { - return $acceptance->checkoutable_type == 'App\Models\Asset'; - }) ->map(function($acceptance) { return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance]; }) From b96e2fb52c61fe2d961d16b939e849c1a399361a Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Thu, 23 Oct 2025 15:40:54 +0100 Subject: [PATCH 20/28] Added migration to fix incorrect action_type in action_log --- ..._144927_migrate_incorrect_action_types.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php diff --git a/database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php b/database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php new file mode 100644 index 0000000000..7eff84667a --- /dev/null +++ b/database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php @@ -0,0 +1,38 @@ +where('action_type', 'request_canceled')->update(['action_type' => 'request canceled']); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // no down migration for this one + } +}; From f7bc538fdf39f8fec0d9869974acb3c425ca6008 Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Mon, 3 Nov 2025 15:39:31 +0000 Subject: [PATCH 21/28] Intorduce ActionType enum and ensure all logactions are using it --- app/Enums/ActionType.php | 33 +++++++++++++++++++ app/Http/Controllers/ViewAssetsController.php | 3 +- app/Models/Actionlog.php | 8 +++-- app/Presenters/ActionlogPresenter.php | 2 +- 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 app/Enums/ActionType.php diff --git a/app/Enums/ActionType.php b/app/Enums/ActionType.php new file mode 100644 index 0000000000..6798a6b78e --- /dev/null +++ b/app/Enums/ActionType.php @@ -0,0 +1,33 @@ +isRequestedBy($user)) || $cancel_by_admin) { $item->cancelRequest($requestingUser); $data['item_quantity'] = ($item_request) ? $item_request->qty : 1; - $logaction->logaction('request_canceled'); + $logaction->logaction(ActionType::RequestCanceled); if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) { $settings->notify(new RequestAssetCancelation($data)); diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 786246778c..4a12835a9d 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -9,6 +9,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Str; +use App\Enums\ActionType; /** * Model for the Actionlog (the table that keeps a historical log of @@ -335,9 +336,12 @@ class Actionlog extends SnipeModel * @since [v3.0] * @return bool */ - public function logaction($actiontype) + public function logaction(string|ActionType $actiontype) { - $this->action_type = $actiontype; + if (is_string($actiontype)) { + $actiontype = ActionType::from($actiontype); + } + $this->action_type = $actiontype->value; $this->remote_ip = request()->ip(); $this->user_agent = request()->header('User-Agent'); $this->action_source = $this->determineActionSource(); diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php index 0f82d1f667..a2dc75113d 100644 --- a/app/Presenters/ActionlogPresenter.php +++ b/app/Presenters/ActionlogPresenter.php @@ -102,7 +102,7 @@ class ActionlogPresenter extends Presenter return 'fa-solid fa-rotate-right'; } - if ($this->action_type == 'note_added') { + if ($this->action_type == 'note added') { return 'fas fa-sticky-note'; } From 27d74494591ddc04d51c5cd5fc3aa06e06958930 Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Tue, 4 Nov 2025 14:04:37 +0000 Subject: [PATCH 22/28] Fix comment to properly reflext the current state of the database --- ..._10_22_144927_migrate_incorrect_action_types.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php b/database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php index 7eff84667a..b27ed56ef3 100644 --- a/database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php +++ b/database/migrations/2025_10_22_144927_migrate_incorrect_action_types.php @@ -13,16 +13,9 @@ return new class extends Migration { /** - * So the concern here is that the following statement _could_ take a long time - the action_logs table is not indexed - * against the action_type column (and shouldn't be) so this could take a few beats. But, still, we're not talking about - * a particularly wide table or anything; we've certainly heard about a couple of times where people had a few million - * action_logs but, again, not too many more than that. - * - * But @snipe has mentioned multiple times that in some older migrations, trying to run an UPDATE in batch, there were - * memory issues. - * - * I've investigated and it looks like we've rarely or never done a 'batch update' the way we do below. I'm pretty sure - * it will be fine (famous last words...) + * We actually *do* have an index on action_type, so this query shouldn't take too terribly long. I don't know + * that we have a ton of people canceling requests (and only certain types of request-cancellation use this + * erroneous action_type), so I feel pretty comfortable with this fixup. Fingers crossed! * */ DB::table('action_logs')->where('action_type', 'request_canceled')->update(['action_type' => 'request canceled']); From 5dc675040de715c5c872635b050c000e62e021b6 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 10 Nov 2025 13:48:59 +0000 Subject: [PATCH 23/28] Bumped version --- config/version.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/version.php b/config/version.php index 9253702808..2884de98cf 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v8.3.4', - 'full_app_version' => 'v8.3.4 - build 20218-g3ab2e2011', - 'build_version' => '20218', + 'app_version' => 'v8.3.5', + 'full_app_version' => 'v8.3.5 - build 20406-gf92f76b48', + 'build_version' => '20406', 'prerelease_version' => '', - 'hash_version' => 'g3ab2e2011', - 'full_hash' => 'v8.3.4-149-g3ab2e2011', - 'branch' => 'master', + 'hash_version' => 'gf92f76b48', + 'full_hash' => 'v8.3.5-183-gf92f76b48', + 'branch' => 'develop', ); \ No newline at end of file From a1cc427c9cb0b964fe3ed450f3fb14493711834f Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 10 Nov 2025 14:14:51 +0000 Subject: [PATCH 24/28] Add @smarsching as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 5b730fb705..c99dd610a9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -4235,6 +4235,15 @@ "contributions": [ "code" ] + }, + { + "login": "smarsching", + "name": "Sebastian Marsching", + "avatar_url": "https://avatars.githubusercontent.com/u/2880129?v=4", + "profile": "http://sebastian.marsching.com/", + "contributions": [ + "code" + ] } ] } diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ad29260375..0587500c43 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -68,7 +68,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken | [
Juan Font](https://github.com/juanfont)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [
Juho Taipale](https://github.com/juhotaipale)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [
Korvin Szanto](https://github.com/KorvinSzanto)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [
Lewis Foster](https://lewisfoster.foo/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [
Logan Swartzendruber](https://github.com/loganswartz)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [
Lorenzo P.](https://github.com/lopezio)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [
Lukas Jung](https://github.com/m4us1ne)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") | | [
Ellie](https://leafedfox.xyz/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [
GA Stamper](https://github.com/gastamper)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [
Guillaume Lefranc](https://github.com/gl-pup)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [
Hajo MΓΆller](https://github.com/dasjoe)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [
Istvan Basa](https://github.com/pottom)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [
JJ Asghar](https://jjasghar.github.io/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [
James E. Msenga](https://github.com/JemCdo)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") | | [
Jan Felix Wiebe](https://github.com/jfwiebe)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [
Jo Drexl](https://www.nfon.com/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [
Austin Sasko](https://github.com/austinsasko)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [
Jasson](http://jassoncordones.github.io)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [
Okean](https://github.com/Tinyblargon)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [
Alejandro Medrano](https://www.lst.tfo.upm.es/alejandro-medrano/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [
Lukas Kraic](https://github.com/lukaskraic)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") | -| [
Π“Π΅Ρ€Ρ…Π°Ρ€Π΄ PICCORO Lenz McKAY ](https://github-readme-stats.vercel.app/api?username=mckaygerhard)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [
Johannes Pollitt](https://github.com/FlorestanII)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [
Michael Strobel](https://strobelm.de)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [
Nicky West](http://nickwest.me)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [
akaspeh1](https://github.com/akaspeh1)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | +| [
Π“Π΅Ρ€Ρ…Π°Ρ€Π΄ PICCORO Lenz McKAY ](https://github-readme-stats.vercel.app/api?username=mckaygerhard)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [
Johannes Pollitt](https://github.com/FlorestanII)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [
Michael Strobel](https://strobelm.de)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [
Nicky West](http://nickwest.me)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [
akaspeh1](https://github.com/akaspeh1)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | [
Sebastian Marsching](http://sebastian.marsching.com/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=smarsching "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! From 8b7e0a0d783061db785d6f4efe41828900138c82 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 10 Nov 2025 14:15:01 +0000 Subject: [PATCH 25/28] Add @mohammad-ahmadi1 as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index c99dd610a9..3cb95b52c5 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -4244,6 +4244,15 @@ "contributions": [ "code" ] + }, + { + "login": "mohammad-ahmadi1", + "name": "Mo", + "avatar_url": "https://avatars.githubusercontent.com/u/40658372?v=4", + "profile": "https://github.com/mohammad-ahmadi1", + "contributions": [ + "code" + ] } ] } diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0587500c43..ae46948191 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -68,7 +68,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken | [
Juan Font](https://github.com/juanfont)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [
Juho Taipale](https://github.com/juhotaipale)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [
Korvin Szanto](https://github.com/KorvinSzanto)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [
Lewis Foster](https://lewisfoster.foo/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [
Logan Swartzendruber](https://github.com/loganswartz)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [
Lorenzo P.](https://github.com/lopezio)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [
Lukas Jung](https://github.com/m4us1ne)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") | | [
Ellie](https://leafedfox.xyz/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [
GA Stamper](https://github.com/gastamper)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [
Guillaume Lefranc](https://github.com/gl-pup)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [
Hajo MΓΆller](https://github.com/dasjoe)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [
Istvan Basa](https://github.com/pottom)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [
JJ Asghar](https://jjasghar.github.io/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [
James E. Msenga](https://github.com/JemCdo)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") | | [
Jan Felix Wiebe](https://github.com/jfwiebe)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [
Jo Drexl](https://www.nfon.com/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [
Austin Sasko](https://github.com/austinsasko)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [
Jasson](http://jassoncordones.github.io)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [
Okean](https://github.com/Tinyblargon)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [
Alejandro Medrano](https://www.lst.tfo.upm.es/alejandro-medrano/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [
Lukas Kraic](https://github.com/lukaskraic)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") | -| [
Π“Π΅Ρ€Ρ…Π°Ρ€Π΄ PICCORO Lenz McKAY ](https://github-readme-stats.vercel.app/api?username=mckaygerhard)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [
Johannes Pollitt](https://github.com/FlorestanII)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [
Michael Strobel](https://strobelm.de)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [
Nicky West](http://nickwest.me)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [
akaspeh1](https://github.com/akaspeh1)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | [
Sebastian Marsching](http://sebastian.marsching.com/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=smarsching "Code") | +| [
Π“Π΅Ρ€Ρ…Π°Ρ€Π΄ PICCORO Lenz McKAY ](https://github-readme-stats.vercel.app/api?username=mckaygerhard)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [
Johannes Pollitt](https://github.com/FlorestanII)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [
Michael Strobel](https://strobelm.de)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [
Nicky West](http://nickwest.me)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [
akaspeh1](https://github.com/akaspeh1)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | [
Sebastian Marsching](http://sebastian.marsching.com/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=smarsching "Code") | [
Mo](https://github.com/mohammad-ahmadi1)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=mohammad-ahmadi1 "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! From e1d371444510a20cbcfaa772c1e9f4ad1e796af5 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 10 Nov 2025 14:15:10 +0000 Subject: [PATCH 26/28] Add @MarvelousAnything as a contributor --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3cb95b52c5..a277fb8563 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -4253,6 +4253,15 @@ "contributions": [ "code" ] + }, + { + "login": "MarvelousAnything", + "name": "Owen V. Hayes", + "avatar_url": "https://avatars.githubusercontent.com/u/20994684?v=4", + "profile": "https://github.com/MarvelousAnything", + "contributions": [ + "code" + ] } ] } diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ae46948191..a2fda56ce8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -69,6 +69,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken | [
Ellie](https://leafedfox.xyz/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [
GA Stamper](https://github.com/gastamper)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [
Guillaume Lefranc](https://github.com/gl-pup)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [
Hajo MΓΆller](https://github.com/dasjoe)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [
Istvan Basa](https://github.com/pottom)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [
JJ Asghar](https://jjasghar.github.io/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [
James E. Msenga](https://github.com/JemCdo)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") | | [
Jan Felix Wiebe](https://github.com/jfwiebe)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [
Jo Drexl](https://www.nfon.com/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [
Austin Sasko](https://github.com/austinsasko)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [
Jasson](http://jassoncordones.github.io)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [
Okean](https://github.com/Tinyblargon)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [
Alejandro Medrano](https://www.lst.tfo.upm.es/alejandro-medrano/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [
Lukas Kraic](https://github.com/lukaskraic)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") | | [
Π“Π΅Ρ€Ρ…Π°Ρ€Π΄ PICCORO Lenz McKAY ](https://github-readme-stats.vercel.app/api?username=mckaygerhard)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [
Johannes Pollitt](https://github.com/FlorestanII)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [
Michael Strobel](https://strobelm.de)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [
Nicky West](http://nickwest.me)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [
akaspeh1](https://github.com/akaspeh1)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | [
Sebastian Marsching](http://sebastian.marsching.com/)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=smarsching "Code") | [
Mo](https://github.com/mohammad-ahmadi1)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=mohammad-ahmadi1 "Code") | +| [
Owen V. Hayes](https://github.com/MarvelousAnything)
[πŸ’»](https://github.com/snipe/snipe-it/commits?author=MarvelousAnything "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! From 34daffcdf493d789eef11149d9e0aa4f2becf9b6 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 10 Nov 2025 20:47:45 +0000 Subject: [PATCH 27/28] Added supplier file uploads --- app/Http/Controllers/Controller.php | 4 +++ app/Models/Actionlog.php | 2 ++ app/Models/Supplier.php | 4 +++ resources/views/suppliers/view.blade.php | 31 ++++++++++++++++++++ routes/api.php | 8 ++--- routes/web.php | 6 ++-- storage/private_uploads/suppliers/.gitignore | 2 ++ 7 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 storage/private_uploads/suppliers/.gitignore diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 367ab1c10c..53b256ef05 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -30,6 +30,7 @@ use App\Models\Consumable; use App\Models\License; use App\Models\Location; use App\Models\Maintenance; +use App\Models\Supplier; use App\Models\User; use Illuminate\Support\Facades\Auth; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; @@ -52,6 +53,7 @@ abstract class Controller extends BaseController 'licenses' => License::class, 'locations' => Location::class, 'models' => AssetModel::class, + 'suppliers' => Supplier::class, 'users' => User::class, ]; @@ -66,6 +68,7 @@ abstract class Controller extends BaseController 'licenses' => 'private_uploads/licenses/', 'locations' => 'private_uploads/locations/', 'models' => 'private_uploads/models/', + 'suppliers' => 'private_uploads/suppliers/', 'users' => 'private_uploads/users/', ]; @@ -80,6 +83,7 @@ abstract class Controller extends BaseController 'licenses' => 'license', 'locations' => 'location', 'models' => 'model', + 'suppliers' => 'supplier', 'users' => 'user', ]; diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 786246778c..030850b50e 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -518,6 +518,8 @@ class Actionlog extends SnipeModel return 'private_uploads/locations/'.$this->filename; case Maintenance::class: return 'private_uploads/maintenances/'.$this->filename; + case Supplier::class: + return 'private_uploads/suppliers/'.$this->filename; case User::class: return 'private_uploads/users/'.$this->filename; default: diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index 2c99330604..25e763c957 100755 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -3,15 +3,18 @@ namespace App\Models; use App\Http\Traits\UniqueUndeletedTrait; +use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Watson\Validating\ValidatingTrait; use \Illuminate\Database\Eloquent\Relations\Relation; +use App\Models\Traits\Loggable; class Supplier extends SnipeModel { use HasFactory; use SoftDeletes; + use HasUploads; protected $table = 'suppliers'; @@ -42,6 +45,7 @@ class Supplier extends SnipeModel use ValidatingTrait; use UniqueUndeletedTrait; use Searchable; + use Loggable; /** * The attributes that should be included when searching the model. diff --git a/resources/views/suppliers/view.blade.php b/resources/views/suppliers/view.blade.php index bc216a964e..dfee233e1b 100755 --- a/resources/views/suppliers/view.blade.php +++ b/resources/views/suppliers/view.blade.php @@ -100,6 +100,26 @@
+ +
  • + + + + + +
  • + +
  • + + + {{ trans('button.upload') }} + +
  • @@ -243,6 +263,14 @@ +
    +
    +
    + +
    +
    +
    + @@ -318,6 +346,9 @@ + @can('update', \App\Models\Supplier::class) + @include ('modals.upload-file', ['item_type' => 'supplier', 'item_id' => $supplier->id]) + @endcan @stop @section('moar_scripts') diff --git a/routes/api.php b/routes/api.php index b057705455..dbb9348f7e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1348,7 +1348,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu 'index' ] )->name('api.files.index') - ->where(['object_type' => 'accessories|audits|assets|components|consumables|hardware|licenses|locations|maintenances|models|users']); + ->where(['object_type' => 'accessories|audits|assets|components|consumables|hardware|licenses|locations|maintenances|models|suppliers|users']); // Get a file Route::get('{object_type}/{id}/files/{file_id}', @@ -1357,7 +1357,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu 'show' ] )->name('api.files.show') - ->where(['object_type' => 'accessories|audits|assets|components|consumables|hardware|licenses|locations|maintenances|models|users']); + ->where(['object_type' => 'accessories|audits|assets|components|consumables|hardware|licenses|locations|maintenances|models|suppliers|users']); // Upload files(s) Route::post('{object_type}/{id}/files', @@ -1366,7 +1366,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu 'store' ] )->name('api.files.store') - ->where(['object_type' => 'accessories|audits|assets|components|consumables|hardware|licenses|locations|maintenances|models|users']); + ->where(['object_type' => 'accessories|audits|assets|components|consumables|hardware|licenses|locations|maintenances|models|suppliers|users']); // Delete files(s) Route::delete('{object_type}/{id}/files/{file_id}/delete', @@ -1375,6 +1375,6 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu 'destroy' ] )->name('api.files.destroy') - ->where(['object_type' => 'accessories|assets|components|consumables|hardware|licenses|locations|maintenances|models|users']); + ->where(['object_type' => 'accessories|assets|components|consumables|hardware|licenses|locations|maintenances|models|suppliers|users']); }); // end API routes diff --git a/routes/web.php b/routes/web.php index 53595cb8a3..47e1c46003 100644 --- a/routes/web.php +++ b/routes/web.php @@ -716,7 +716,7 @@ Route::group(['middleware' => 'web'], function () { 'show' ] )->name('ui.files.show') - ->where(['object_type' => 'assets|audits|maintenances|hardware|models|users|locations|accessories|consumables|licenses|components']); + ->where(['object_type' => 'assets|audits|maintenances|hardware|models|users|locations|accessories|consumables|licenses|suppliers|components']); // Upload files(s) Route::post('{object_type}/{id}/files', @@ -725,7 +725,7 @@ Route::group(['middleware' => 'web'], function () { 'store' ] )->name('ui.files.store') - ->where(['object_type' => 'assets|audits|maintenances|hardware|models|users|locations|accessories|consumables|licenses|components']); + ->where(['object_type' => 'assets|audits|maintenances|hardware|models|users|locations|accessories|consumables|licenses|suppliers|components']); // Delete files(s) Route::delete('{object_type}/{id}/files/{file_id}/delete', @@ -734,7 +734,7 @@ Route::group(['middleware' => 'web'], function () { 'destroy' ] )->name('ui.files.destroy') - ->where(['object_type' => 'assets|maintenances|hardware|models|users|locations|accessories|consumables|licenses|components']); + ->where(['object_type' => 'assets|maintenances|hardware|models|users|locations|accessories|consumables|licenses|suppliers|components']); }); diff --git a/storage/private_uploads/suppliers/.gitignore b/storage/private_uploads/suppliers/.gitignore new file mode 100644 index 0000000000..c96a04f008 --- /dev/null +++ b/storage/private_uploads/suppliers/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From baa4a8a461b588c480da3c037208483fe85768e8 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 10 Nov 2025 20:53:21 +0000 Subject: [PATCH 28/28] Migration to change notes on suppliers to text from varchar --- ..._205136_change_suppliers_notes_to_text.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/migrations/2025_11_10_205136_change_suppliers_notes_to_text.php diff --git a/database/migrations/2025_11_10_205136_change_suppliers_notes_to_text.php b/database/migrations/2025_11_10_205136_change_suppliers_notes_to_text.php new file mode 100644 index 0000000000..e155623a67 --- /dev/null +++ b/database/migrations/2025_11_10_205136_change_suppliers_notes_to_text.php @@ -0,0 +1,28 @@ +text('notes')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('text', function (Blueprint $table) { + // + }); + } +};