Skip to content

Commit 1258eb6

Browse files
authored
Merge pull request #17913 from Godmartinz/add_types_to_unaccepted_asset_report
Fixed #15664 - Adds Accessories, Components, Consumables, and Licenses to Unaccepted Assets report
2 parents 83abfc9 + ca44ee9 commit 1258eb6

File tree

9 files changed

+383
-103
lines changed

9 files changed

+383
-103
lines changed

app/Console/Commands/SendAcceptanceReminder.php

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
namespace App\Console\Commands;
44

55
use App\Mail\UnacceptedAssetReminderMail;
6+
use App\Models\Accessory;
67
use App\Models\Asset;
78
use App\Models\CheckoutAcceptance;
9+
use App\Models\Component;
10+
use App\Models\Consumable;
11+
use App\Models\LicenseSeat;
812
use App\Models\Setting;
913
use App\Models\User;
1014
use App\Notifications\CheckoutAssetNotification;
1115
use App\Notifications\CurrentInventory;
1216
use Illuminate\Console\Command;
17+
use Illuminate\Database\Eloquent\Relations\MorphTo;
1318
use Illuminate\Support\Facades\Mail;
1419

1520
class SendAcceptanceReminder extends Command
@@ -45,19 +50,30 @@ public function __construct()
4550
*/
4651
public function handle()
4752
{
48-
$pending = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')
49-
->whereHas('checkoutable', function($query) {
50-
$query->where('accepted_at', null)
51-
->where('declined_at', null);
52-
})
53-
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
54-
->get();
53+
$pending = CheckoutAcceptance::query()
54+
->with([
55+
'checkoutable' => function (MorphTo $morph) {
56+
$morph->morphWith([
57+
Asset::class => ['model.category', 'assignedTo', 'adminuser', 'company', 'checkouts'],
58+
Accessory::class => ['category', 'company', 'checkouts'],
59+
LicenseSeat::class => ['user', 'license', 'checkouts'],
60+
Component::class => ['assignedTo', 'company', 'checkouts'],
61+
Consumable::class => ['company', 'checkouts'],
62+
]);
63+
},
64+
'assignedTo',
65+
])
66+
->whereHasMorph(
67+
'checkoutable',
68+
[Asset::class, Accessory::class, LicenseSeat::class, Component::class, Consumable::class],
69+
fn ($q) => $q->whereNull('accepted_at')
70+
->whereNull('declined_at')
71+
)
72+
->pending()
73+
->get();
5574

5675
$count = 0;
5776
$unacceptedAssetGroups = $pending
58-
->filter(function($acceptance) {
59-
return $acceptance->checkoutable_type == 'App\Models\Asset';
60-
})
6177
->map(function($acceptance) {
6278
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
6379
})

app/Http/Controllers/ReportsController.php

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@
33
namespace App\Http\Controllers;
44

55
use App\Helpers\Helper;
6+
use App\Mail\CheckoutAccessoryMail;
67
use App\Mail\CheckoutAssetMail;
8+
use App\Mail\CheckoutComponentMail;
9+
use App\Mail\CheckoutConsumableMail;
10+
use App\Mail\CheckoutLicenseMail;
711
use App\Models\Accessory;
12+
use App\Models\AccessoryCheckout;
813
use App\Models\Actionlog;
914
use App\Models\Asset;
1015
use App\Models\AssetModel;
1116
use App\Models\Category;
17+
use App\Models\Checkoutable;
18+
use App\Models\Component;
19+
use App\Models\Consumable;
20+
use App\Models\LicenseSeat;
1221
use App\Models\Maintenance;
1322
use App\Models\CheckoutAcceptance;
1423
use App\Models\Company;
@@ -18,9 +27,11 @@
1827
use App\Models\ReportTemplate;
1928
use App\Models\Setting;
2029
use Carbon\Carbon;
30+
use Illuminate\Database\Eloquent\Model;
2131
use Illuminate\Database\Eloquent\Relations\MorphTo;
2232
use Illuminate\Http\Request;
2333
use Illuminate\Http\Response;
34+
use Illuminate\Mail\Mailable;
2435
use Illuminate\Support\Facades\Mail;
2536
use \Illuminate\Contracts\View\View;
2637
use League\Csv\Reader;
@@ -1117,33 +1128,29 @@ public function getAssetAcceptanceReport($deleted = false) : View
11171128
$showDeleted = $deleted == 'deleted';
11181129

11191130
$query = CheckoutAcceptance::pending()
1120-
->where('checkoutable_type', 'App\Models\Asset')
11211131
->with([
11221132
'checkoutable' => function (MorphTo $query) {
11231133
$query->morphWith([
1124-
AssetModel::class => ['model'],
1125-
Company::class => ['company'],
1126-
Asset::class => ['assignedTo'],
1127-
])->with('model.category');
1134+
Asset::class => ['model.category', 'assignedTo', 'company'],
1135+
Accessory::class => ['category','checkouts', 'company'],
1136+
LicenseSeat::class => ['user', 'license'],
1137+
Component::class => ['assignedTo', 'company'],
1138+
Consumable::class => ['company'],
1139+
]);
11281140
},
11291141
'assignedTo' => function($query){
11301142
$query->withTrashed();
11311143
}
1132-
]);
1144+
])->orderByDesc('checkout_acceptances.created_at');
1145+
11331146

11341147
if ($showDeleted) {
11351148
$query->withTrashed();
11361149
}
11371150

1138-
$assetsForReport = $query->get()
1139-
->map(function ($acceptance) {
1140-
return [
1141-
'assetItem' => $acceptance->checkoutable,
1142-
'acceptance' => $acceptance,
1143-
];
1144-
});
1151+
$itemsForReport = $query->get()->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
11451152

1146-
return view('reports/unaccepted_assets', compact('assetsForReport','showDeleted' ));
1153+
return view('reports/unaccepted_assets', compact('itemsForReport','showDeleted' ));
11471154
}
11481155

11491156
/**
@@ -1155,41 +1162,77 @@ public function getAssetAcceptanceReport($deleted = false) : View
11551162
public function sentAssetAcceptanceReminder(Request $request) : RedirectResponse
11561163
{
11571164
$this->authorize('reports.view');
1158-
1159-
if (!$acceptance = CheckoutAcceptance::pending()->find($request->input('acceptance_id'))) {
1165+
$id = $request->input('acceptance_id');
1166+
$query = CheckoutAcceptance::query()
1167+
->with([
1168+
'checkoutable' => function (MorphTo $query) {
1169+
$query->morphWith([
1170+
Asset::class => ['model.category', 'assignedTo', 'company', 'checkouts'],
1171+
Accessory::class => ['category', 'company', 'checkouts'],
1172+
LicenseSeat::class => ['user', 'license', 'checkouts'],
1173+
Component::class => ['assignedTo', 'company', 'checkouts'],
1174+
Consumable::class => ['company', 'checkouts'],
1175+
]);
1176+
},
1177+
'assignedTo' => fn ($q) => $q->withTrashed(),
1178+
])
1179+
->pending();
1180+
$acceptance = $query->find($id);
1181+
if (!$acceptance) {
11601182
Log::debug('No pending acceptances');
11611183
// Redirect to the unaccepted assets report page with error
11621184
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
11631185
}
1186+
$item = $acceptance->checkoutable;
1187+
$assignee = $acceptance->assignedTo ?? $item->assignedTo ?? null;
1188+
$email = $assignee?->email;
1189+
$locale = $assignee?->locale;
11641190

1165-
$assetItem = $acceptance->checkoutable;
1166-
1167-
Log::debug(print_r($assetItem, true));
1191+
Log::debug(print_r($acceptance, true));
11681192

11691193
if (is_null($acceptance->created_at)){
11701194
Log::debug('No acceptance created_at');
11711195
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
11721196
} else {
1173-
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
1174-
1197+
if($item instanceof LicenseSeat){
1198+
$logItem_res = $item->license->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
1199+
}
1200+
else{
1201+
$logItem_res = $item->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
1202+
}
11751203
if ($logItem_res->isEmpty()){
11761204
Log::debug('Acceptance date mismatch');
11771205
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
11781206
}
11791207
$logItem = $logItem_res[0];
11801208
}
1181-
$email = $assetItem->assignedTo?->email;
1182-
$locale = $assetItem->assignedTo?->locale;
11831209

11841210
if (is_null($email) || $email === '') {
11851211
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
11861212
}
1187-
1188-
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale));
1213+
$mailable = $this->getCheckoutMailType($acceptance, $logItem);
1214+
Mail::to($email)->send($mailable->locale($locale));
11891215

11901216
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
11911217
}
1218+
private function getCheckoutMailType(CheckoutAcceptance $acceptance, $logItem) : Mailable
1219+
{
1220+
$lookup = [
1221+
Accessory::class => CheckoutAccessoryMail::class,
1222+
Asset::class => CheckoutAssetMail::class,
1223+
LicenseSeat::class => CheckoutLicenseMail::class,
1224+
Consumable::class => CheckoutConsumableMail::class,
1225+
Component::class => CheckoutComponentMail::class,
1226+
];
1227+
$mailable= $lookup[get_class($acceptance->checkoutable)];
11921228

1229+
return new $mailable($acceptance->checkoutable,
1230+
$acceptance->checkedOutTo ?? $acceptance->assignedTo,
1231+
$logItem->adminuser,
1232+
$acceptance,
1233+
$acceptance->note);
1234+
1235+
}
11931236
/**
11941237
* sentAssetAcceptanceReminder
11951238
*
@@ -1221,48 +1264,61 @@ public function deleteAssetAcceptance($acceptanceId = null) : RedirectResponse
12211264
public function postAssetAcceptanceReport($deleted = false) : Response
12221265
{
12231266
$this->authorize('reports.view');
1224-
$showDeleted = $deleted == 'deleted';
1267+
$showDeleted = request('deleted') === 'deleted';;
12251268

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

1235-
$assetsForReport = $acceptances
1236-
->filter(function($acceptance) {
1237-
return $acceptance->checkoutable_type == 'App\Models\Asset';
1238-
})
1239-
->map(function($acceptance) {
1240-
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
1241-
});
1273+
$acceptances = CheckoutAcceptance::pending()
1274+
->with([
1275+
'checkoutable' => function (MorphTo $acceptance) {
1276+
$acceptance->morphWith([
1277+
Asset::class => ['model.category', 'assignedTo', 'company'],
1278+
Accessory::class => ['category','checkouts', 'company'],
1279+
LicenseSeat::class => ['user', 'license'],
1280+
Component::class => ['assignedTo', 'company'],
1281+
Consumable::class => ['company'],
1282+
]);
1283+
},
1284+
'assignedTo',
1285+
])->orderByDesc('checkout_acceptances.created_at');
1286+
1287+
if ($showDeleted) {
1288+
$acceptances->withTrashed();
1289+
}
1290+
1291+
$itemsForReport = $acceptances->get()->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
12421292

12431293
$rows = [];
12441294

12451295
$header = [
1296+
trans('general.date'),
1297+
trans('general.type'),
1298+
trans('admin/companies/table.title'),
12461299
trans('general.category'),
12471300
trans('admin/hardware/form.model'),
1248-
trans('admin/hardware/form.name'),
1301+
trans('general.name'),
12491302
trans('admin/hardware/table.asset_tag'),
12501303
trans('admin/hardware/table.checkoutto'),
12511304
];
12521305

12531306
$header = array_map('trim', $header);
12541307
$rows[] = implode(',', $header);
12551308

1256-
foreach ($assetsForReport as $item) {
1309+
foreach ($itemsForReport as $item) {
12571310

1258-
if ($item['assetItem'] != null){
1311+
if ($item != null){
12591312

12601313
$row = [ ];
1261-
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
1262-
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
1263-
$row[] = str_replace(',', '', e($item['assetItem']->name));
1264-
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
1265-
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->display_name : trans('admin/reports/general.deleted_user')));
1314+
$row[] = str_replace(',', '', $item->acceptance->created_at);
1315+
$row[] = str_replace(',', '', $item->type);
1316+
$row[] = str_replace(',', '', $item->plain_text_company);
1317+
$row[] = str_replace(',', '', $item->plain_text_category);
1318+
$row[] = str_replace(',', '', $item->plain_text_model);
1319+
$row[] = str_replace(',', '', $item->plain_text_name);
1320+
$row[] = str_replace(',', '', $item->asset_tag);
1321+
$row[] = str_replace(',', '', ($item->acceptance->assignedto) ? $item->acceptance->assignedto->display_name : trans('admin/reports/general.deleted_user'));
12661322
$rows[] = implode(',', $row);
12671323
}
12681324
}

app/Models/Checkoutable.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
class Checkoutable
6+
{
7+
public function __construct(
8+
public int $acceptance_id,
9+
public string $company,
10+
public string $category,
11+
public string $model,
12+
public string $asset_tag,
13+
public string $name,
14+
public string $type,
15+
public object $acceptance,
16+
public object $assignee,
17+
public readonly string $plain_text_category,
18+
public readonly string $plain_text_model,
19+
public readonly string $plain_text_name,
20+
public readonly string $plain_text_company,
21+
){}
22+
23+
public static function fromAcceptance(CheckoutAcceptance $unaccepted): self
24+
{
25+
$unaccepted_row = $unaccepted->checkoutable;
26+
$acceptance = $unaccepted;
27+
28+
$assignee = $acceptance->assignedTo;
29+
$company = optional($unaccepted_row->company)->present()?->nameUrl() ?? '';
30+
$category = $model = $name = $tag = '';
31+
$type = $acceptance->checkoutable_item_type ?? '';
32+
33+
34+
if($unaccepted_row instanceof Asset){
35+
$category = optional($unaccepted_row->model?->category?->present())->nameUrl() ?? '';
36+
$model = optional($unaccepted_row->present())->modelUrl() ?? '';
37+
$name = optional($unaccepted_row->present())->nameUrl() ?? '';
38+
$tag = (string) ($unaccepted_row->asset_tag ?? '');
39+
}
40+
if($unaccepted_row instanceof Accessory){
41+
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
42+
$model = $unaccepted_row->model_number ?? '';
43+
$name = optional($unaccepted_row->present())->nameUrl() ?? '';
44+
}
45+
if($unaccepted_row instanceof LicenseSeat){
46+
$category = optional($unaccepted_row->license->category?->present())->nameUrl() ?? '';
47+
$company = optional($unaccepted_row->license->company?->present())?->nameUrl() ?? '';
48+
$model = '';
49+
$name = $unaccepted_row->license->present()->nameUrl() ?? '';
50+
}
51+
if($unaccepted_row instanceof Consumable){
52+
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
53+
$model = $unaccepted_row->model_number ?? '';
54+
$name = $unaccepted_row?->present()?->nameUrl() ?? '';
55+
}
56+
if($unaccepted_row instanceof Component){
57+
$category = optional($unaccepted_row->category?->present())->nameUrl() ?? '';
58+
$model = $unaccepted_row->model_number ?? '';
59+
$name = $unaccepted_row?->present()?->nameUrl() ?? '';
60+
}
61+
62+
return new self(
63+
acceptance_id: $acceptance->id,
64+
company: $company,
65+
category: $category,
66+
model: $model,
67+
asset_tag: $tag,
68+
name: $name,
69+
type: $type,
70+
acceptance: $acceptance,
71+
assignee: $assignee,
72+
//plain text for CSVs
73+
plain_text_category: ($unaccepted_row->model?->category?->name ?? $unaccepted_row->license->category?->name ?? $unaccepted_row->category?->name ?? ''),
74+
plain_text_model: ($unaccepted_row->model?->name ?? $unaccepted_row->model_number ?? ''),
75+
plain_text_name: ($unaccepted_row->name ?? $unaccepted_row->license?->name ?? ''),
76+
plain_text_company: ($unaccepted_row->company)->name ?? $unaccepted_row->license->company?->name ?? '',
77+
);
78+
}
79+
}

0 commit comments

Comments
 (0)