diff --git a/app/Console/Commands/SendExpectedCheckinAlerts.php b/app/Console/Commands/SendExpectedCheckinAlerts.php index 34e9609ce5..e042e8b088 100644 --- a/app/Console/Commands/SendExpectedCheckinAlerts.php +++ b/app/Console/Commands/SendExpectedCheckinAlerts.php @@ -9,7 +9,6 @@ use App\Notifications\ExpectedCheckinAdminNotification; use App\Notifications\ExpectedCheckinNotification; use Carbon\Carbon; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Log; class SendExpectedCheckinAlerts extends Command { @@ -43,25 +42,31 @@ class SendExpectedCheckinAlerts extends Command public function handle() { $settings = Setting::getSettings(); - $whenNotify = Carbon::now(); - $assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get(); + $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval); + + $assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get(); + + $this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline'); - $this->info($whenNotify.' is deadline'); - $this->info($assets->count().' assets'); foreach ($assets as $asset) { - if ($asset->assigned && $asset->checkedOutToUser()) { - Log::info('Sending ExpectedCheckinNotification to ' . $asset->assigned->email); - $asset->assigned->notify((new ExpectedCheckinNotification($asset))); + if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) { + $this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email); + $asset->assignedTo->notify((new ExpectedCheckinNotification($asset))); } } if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) { // Send a rollup to the admin, if settings dictate - $recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) { + $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) { return new AlertRecipient($item); }); + + $this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email); \Notification::send($recipients, new ExpectedCheckinAdminNotification($assets)); + } } } diff --git a/app/Console/Commands/SendUpcomingAuditReport.php b/app/Console/Commands/SendUpcomingAuditReport.php index e9cba106c6..fc06785cfe 100644 --- a/app/Console/Commands/SendUpcomingAuditReport.php +++ b/app/Console/Commands/SendUpcomingAuditReport.php @@ -3,10 +3,8 @@ namespace App\Console\Commands; use App\Models\Asset; -use App\Models\License; -use App\Models\Recipients; +use App\Models\Recipients\AlertRecipient; use App\Models\Setting; -use App\Notifications\ExpiringAssetsNotification; use App\Notifications\SendUpcomingAuditNotification; use Carbon\Carbon; use DB; @@ -45,40 +43,26 @@ class SendUpcomingAuditReport extends Command */ public function handle() { + + $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval); + $settings = Setting::getSettings(); + $assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get(); + $this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline'); - if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) { + if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) { // Send a rollup to the admin, if settings dictate - $recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) { - return new \App\Models\Recipients\AlertRecipient($item); + $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) { + return new AlertRecipient($item); }); - // Assets due for auditing + $this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email); + \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days)); - $assets = Asset::whereNotNull('next_audit_date') - ->DueOrOverdueForAudit($settings) - ->orderBy('last_audit_date', 'asc')->get(); - - if ($assets->count() > 0) { - $this->info(trans_choice('mail.upcoming-audits', $assets->count(), - ['count' => $assets->count(), 'threshold' => $settings->audit_warning_days])); - \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days)); - $this->info('Audit report sent to '.$settings->alert_email); - } else { - $this->info('No assets to be audited. No report sent.'); - } - } elseif ($settings->alert_email == '') { - $this->error('Could not send email. No alert email configured in settings'); - } elseif (! $settings->audit_warning_days) { - $this->error('No audit warning days set in Admin Notifications. No mail will be sent.'); - } elseif ($settings->alerts_enabled != 1) { - $this->info('Alerts are disabled in the settings. No mail will be sent'); - } else { - $this->error('Something went wrong. :( '); - $this->error('Admin Notifications Email Setting: '.$settings->alert_email); - $this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days); - $this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled); } + } } diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index 57307acbb3..b234e9cbf1 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -59,7 +59,7 @@ class AssetsController extends Controller * @since [v4.0] * @return \Illuminate\Http\JsonResponse */ - public function index(Request $request, $audit = null) + public function index(Request $request, $action = null, $upcoming_status = null) { $filter_non_deprecable_assets = false; @@ -155,17 +155,44 @@ class AssetsController extends Controller $assets->TextSearch($request->input('search')); } - // This is used by the audit reporting routes - if (Gate::allows('audit', Asset::class)) { - switch ($audit) { - case 'due': - $assets->DueOrOverdueForAudit($settings); - break; - case 'overdue': - $assets->overdueForAudit($settings); - break; + + /** + * Handle due and overdue audits and checkin dates + */ + switch ($action) { + case 'audits': + + switch ($upcoming_status) { + case 'due': + $assets->DueForAudit($settings); + break; + case 'overdue': + $assets->OverdueForAudit(); + break; + case 'due-or-overdue': + $assets->DueOrOverdueForAudit($settings); + break; + } + break; + + case 'checkins': + switch ($upcoming_status) { + case 'due': + $assets->DueForCheckin($settings); + break; + case 'overdue': + $assets->OverdueForCheckin(); + break; + case 'due-or-overdue': + $assets->DueOrOverdueForCheckin($settings); + break; + } + break; } - } + + /** + * End handling due and overdue audits and checkin dates + */ // This is used by the sidenav, mostly diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 6054718e6b..ac755fd389 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -854,11 +854,11 @@ class AssetsController extends Controller return view('hardware/audit-due'); } - public function overdueForAudit() + public function dueForCheckin() { - $this->authorize('audit', Asset::class); + $this->authorize('checkin', Asset::class); - return view('hardware/audit-overdue'); + return view('hardware/checkin-due'); } diff --git a/app/Http/Middleware/AssetCountForSidebar.php b/app/Http/Middleware/AssetCountForSidebar.php index 5d9656f5c1..2bc3f950f9 100644 --- a/app/Http/Middleware/AssetCountForSidebar.php +++ b/app/Http/Middleware/AssetCountForSidebar.php @@ -5,6 +5,7 @@ namespace App\Http\Middleware; use App\Models\Asset; use Auth; use Closure; +use App\Models\Setting; class AssetCountForSidebar { @@ -24,6 +25,13 @@ class AssetCountForSidebar \Log::debug($e); } + try { + $total_assets = Asset::RTD()->count(); + view()->share('total_assets', $total_assets); + } catch (\Exception $e) { + \Log::debug($e); + } + try { $total_deployed_sidebar = Asset::Deployed()->count(); view()->share('total_deployed_sidebar', $total_deployed_sidebar); @@ -59,6 +67,44 @@ class AssetCountForSidebar \Log::debug($e); } + try { + $settings = Setting::getSettings(); + view()->share('settings', $settings); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_due_for_audit = Asset::DueForAudit($settings)->count(); + view()->share('total_due_for_audit', $total_due_for_audit); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_overdue_for_audit = Asset::OverdueForAudit()->count(); + view()->share('total_overdue_for_audit', $total_overdue_for_audit); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_due_for_checkin = Asset::DueForCheckin($settings)->count(); + view()->share('total_due_for_checkin', $total_due_for_checkin); + } catch (\Exception $e) { + \Log::debug($e); + } + + try { + $total_overdue_for_checkin = Asset::OverdueForCheckin()->count(); + view()->share('total_overdue_for_checkin', $total_overdue_for_checkin); + } catch (\Exception $e) { + \Log::debug($e); + } + + view()->share('total_due_and_overdue_for_checkin', ($total_due_for_checkin + $total_overdue_for_checkin)); + view()->share('total_due_and_overdue_for_audit', ($total_due_for_audit + $total_overdue_for_audit)); + return $next($request); } } diff --git a/app/Models/Asset.php b/app/Models/Asset.php index 1f4079e491..c5373a04e2 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -74,9 +74,9 @@ class Asset extends Depreciable 'eol_explicit' => 'boolean', 'last_checkout' => 'datetime', 'last_checkin' => 'datetime', - 'expected_checkin' => 'date', + 'expected_checkin' => 'datetime:m-d-Y', 'last_audit_date' => 'datetime', - 'next_audit_date' => 'date', + 'next_audit_date' => 'datetime:m-d-Y', 'model_id' => 'integer', 'status_id' => 'integer', 'company_id' => 'integer', @@ -1163,10 +1163,11 @@ class Asset extends Depreciable public function scopeDueForAudit($query, $settings) { $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); return $query->whereNotNull('assets.next_audit_date') - ->where('assets.next_audit_date', '>=', Carbon::now()) - ->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'") + ->whereBetween('assets.next_audit_date', [$today->format('Y-m-d'), $interval_date]) ->where('assets.archived', '=', 0) ->NotArchived(); } @@ -1188,7 +1189,7 @@ class Asset extends Depreciable public function scopeOverdueForAudit($query) { return $query->whereNotNull('assets.next_audit_date') - ->where('assets.next_audit_date', '<', Carbon::now()) + ->where('assets.next_audit_date', '<', Carbon::now()->format('Y-m-d')) ->where('assets.archived', '=', 0) ->NotArchived(); } @@ -1209,14 +1210,69 @@ class Asset extends Depreciable public function scopeDueOrOverdueForAudit($query, $settings) { - $interval = $settings->audit_warning_days ?? 0; - return $query->whereNotNull('assets.next_audit_date') - ->whereRaw('DATE_SUB('.DB::getTablePrefix()."assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'") + return $query->where(function ($query) { + $query->OverdueForAudit(); + })->orWhere(function ($query) use ($settings) { + $query->DueForAudit($settings); + }); + } + + + /** + * Query builder scope for Assets that are DUE for checkin, based on the assets.expected_checkin + * and settings.audit_warning_days. It checks to see if assets.expected_checkin is now + * + * @author A. Gianotto + * @since v6.4.0 + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + + public function scopeDueForCheckin($query, $settings) + { + $interval = $settings->audit_warning_days ?? 0; + $today = Carbon::now(); + $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); + + return $query->whereNotNull('assets.expected_checkin') + ->whereBetween('assets.expected_checkin', [$today->format('Y-m-d'), $interval_date]) ->where('assets.archived', '=', 0) + ->whereNotNull('assets.assigned_to') ->NotArchived(); } + /** + * Query builder scope for Assets that are overdue for checkin OR overdue + * + * @author A. Gianotto + * @since v6.4.0 + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOverdueForCheckin($query) + { + return $query->whereNotNull('assets.expected_checkin') + ->where('assets.expected_checkin', '<', Carbon::now()->format('Y-m-d')) + ->where('assets.archived', '=', 0) + ->whereNotNull('assets.assigned_to') + ->NotArchived(); + } + + /** + * Query builder scope for Assets that are due for checkin OR overdue + * + * @author A. Gianotto + * @since v6.4.0 + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeDueOrOverdueForCheckin($query, $settings) + { + return $query->where(function ($query) { + $query->OverdueForCheckin(); + })->orWhere(function ($query) use ($settings) { + $query->DueForCheckin($settings); + }); + } + /** * Query builder scope for Archived assets counting diff --git a/public/css/build/app.css b/public/css/build/app.css index 6b9729d7e2..e527c35aa0 100644 --- a/public/css/build/app.css +++ b/public/css/build/app.css @@ -1154,4 +1154,9 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index e879341cf1..fe8cb67e84 100644 --- a/public/css/build/overrides.css +++ b/public/css/build/overrides.css @@ -787,4 +787,9 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} diff --git a/public/css/dist/all.css b/public/css/dist/all.css index 834ade82e4..e3032d5452 100644 --- a/public/css/dist/all.css +++ b/public/css/dist/all.css @@ -22654,6 +22654,11 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} .select2-container { @@ -23927,4 +23932,9 @@ input[type="radio"]:checked::before { .datepicker.dropdown-menu { z-index: 1030 !important; } +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 32e9266e11..548cc78dd5 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,8 +1,8 @@ { "/js/build/app.js": "/js/build/app.js?id=ea5f3edebafdb29b616d23fa89106080", "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374", - "/css/build/overrides.css": "/css/build/overrides.css?id=3f1125ebfd37987dd0d6e2bf18bb41ce", - "/css/build/app.css": "/css/build/app.css?id=ca42da2140f0b5be6c2d0fa84ae90108", + "/css/build/overrides.css": "/css/build/overrides.css?id=be3c0a217bc6c0e0744f75faed784887", + "/css/build/app.css": "/css/build/app.css?id=a168b0a799aa800ee926bffa1b1a434a", "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b", "/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", "/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=620b684d9dd9d3bb5fdda00a3a2467c3", @@ -18,7 +18,7 @@ "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", - "/css/dist/all.css": "/css/dist/all.css?id=0836d3b034a9b1de17d797ff35f1983e", + "/css/dist/all.css": "/css/dist/all.css?id=628956bb260b760b56216f01d1d71fd8", "/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba", diff --git a/resources/assets/less/mixins.less b/resources/assets/less/mixins.less index b6ff57a4e9..ff801d415e 100755 --- a/resources/assets/less/mixins.less +++ b/resources/assets/less/mixins.less @@ -131,7 +131,7 @@ //Hover and active states &:hover > a, &.active > a { color: @sidebar-dark-hover-color; - background: @link-hover-border-color; + background: @sidebar-dark-hover-bg; border-left-color: @link-hover-border-color; } //First Level Submenu diff --git a/resources/assets/less/overrides.less b/resources/assets/less/overrides.less index 69bd32a3e7..31e85ca26e 100644 --- a/resources/assets/less/overrides.less +++ b/resources/assets/less/overrides.less @@ -900,4 +900,10 @@ input[type="radio"]:checked::before { } .datepicker.dropdown-menu { z-index: 1030 !important; -} \ No newline at end of file +} + +.sidebar-menu > li .badge { + margin-top: 0px; + filter: brightness(70%); + font-size: 70%; +} diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index dc44b22b2a..0b6f613399 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -313,6 +313,10 @@ return [ 'token_expired' => 'Your form session has expired. Please try again.', 'login_enabled' => 'Login Enabled', 'audit_due' => 'Due for Audit', + 'audit_due_days' => 'Assets Due for Audit Within :days Day|Assets Due for Audit Within :days Days', + 'checkin_due' => 'Due for Checkin', + 'checkin_overdue' => 'Overdue for Checkin', + 'checkin_due_days' => 'Assets Due for Checkin Within :days Day|Assets Due for Checkin Within :days Days', 'audit_overdue' => 'Overdue for Audit', 'accept' => 'Accept :asset', 'i_accept' => 'I accept', diff --git a/resources/views/hardware/audit-due.blade.php b/resources/views/hardware/audit-due.blade.php index 0232facd77..0ee17ec23d 100644 --- a/resources/views/hardware/audit-due.blade.php +++ b/resources/views/hardware/audit-due.blade.php @@ -1,40 +1,59 @@ @extends('layouts/default') -@section('title0') - - @if ((Request::get('company_id')) && ($company)) - {{ $company->name }} - @endif - - {{ trans('general.audit_due') }} - -@stop - {{-- Page title --}} @section('title') - @yield('title0') @parent + {{ trans_choice('general.audit_due_days', $settings->audit_warning_days, ['days' => $settings->audit_warning_days]) }} @stop + {{-- Page content --}} @section('content') - + {{-- Page content --}}
-
-
- @include('partials.asset-bulk-actions') + +
- {{ Form::close() }} -
-
- - + @stop @section('moar_scripts') @include('partials.bootstrap-table') - @stop diff --git a/resources/views/hardware/audit-overdue.blade.php b/resources/views/hardware/audit-overdue.blade.php deleted file mode 100644 index ddd477fb23..0000000000 --- a/resources/views/hardware/audit-overdue.blade.php +++ /dev/null @@ -1,69 +0,0 @@ -@extends('layouts/default') - -@section('title0') - - @if ((Request::get('company_id')) && ($company)) - {{ $company->name }} - @endif - - {{ trans('general.audit_overdue') }} - -@stop - -{{-- Page title --}} -@section('title') - @yield('title0') @parent -@stop - - -{{-- Page content --}} -@section('content') - -
-
-
-
- @include('partials.asset-bulk-actions') -
-
- - -
- -
-
- {{ Form::close() }} -
-
-
-
-@stop - -@section('moar_scripts') - @include('partials.bootstrap-table') - -@stop diff --git a/resources/views/hardware/checkin-due.blade.php b/resources/views/hardware/checkin-due.blade.php new file mode 100644 index 0000000000..6ed7cf42c5 --- /dev/null +++ b/resources/views/hardware/checkin-due.blade.php @@ -0,0 +1,131 @@ +@extends('layouts/default') + +{{-- Page title --}} +@section('title') + {{ trans_choice('general.checkin_due_days', $settings->audit_warning_days, ['days' => $settings->audit_warning_days]) }} +@stop + +{{-- Page content --}} +@section('content') + {{-- Page content --}} +
+
+ + + + +
+
+ +@stop + +@section('moar_scripts') + @include('partials.bootstrap-table') +@stop diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index a83b006c56..dfb1513936 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -51,7 +51,7 @@ @@ -62,7 +62,7 @@ @@ -73,7 +73,7 @@ @@ -96,7 +96,7 @@ @@ -107,7 +107,7 @@ @@ -119,7 +119,7 @@ diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index a1a4bc30cc..c22118161e 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -437,6 +437,9 @@ {{ trans('general.list_all') }} + + {{ (isset($total_assets)) ? $total_assets : '' }} + @@ -447,7 +450,8 @@ - {{ $status_nav->name }} ({{ $status_nav->asset_count }}) + {{ $status_nav->name }} + {{ $status_nav->asset_count }}) @endforeach @endif @@ -455,49 +459,43 @@ - {{ trans('general.all') }} {{ trans('general.deployed') }} - ({{ (isset($total_deployed_sidebar)) ? $total_deployed_sidebar : '' }}) + {{ (isset($total_deployed_sidebar)) ? $total_deployed_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.ready_to_deploy') }} - ({{ (isset($total_rtd_sidebar)) ? $total_rtd_sidebar : '' }}) + {{ (isset($total_rtd_sidebar)) ? $total_rtd_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.pending') }} - ({{ (isset($total_pending_sidebar)) ? $total_pending_sidebar : '' }}) + {{ (isset($total_pending_sidebar)) ? $total_pending_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.undeployable') }} - ({{ (isset($total_undeployable_sidebar)) ? $total_undeployable_sidebar : '' }}) + {{ (isset($total_undeployable_sidebar)) ? $total_undeployable_sidebar : '' }} - {{ trans('general.all') }} {{ trans('general.byod') }} - ({{ (isset($total_byod_sidebar)) ? $total_byod_sidebar : '' }}) + {{ (isset($total_byod_sidebar)) ? $total_byod_sidebar : '' }} - {{ trans('general.all') }} {{ trans('admin/hardware/general.archived') }} - ({{ (isset($total_archived_sidebar)) ? $total_archived_sidebar : '' }}) + {{ (isset($total_archived_sidebar)) ? $total_archived_sidebar : '' }} {{ trans('general.audit_due') }} + {{ (isset($total_due_and_overdue_for_audit)) ? $total_due_and_overdue_for_audit : '' }} - - - {{ trans('general.audit_overdue') }} - - + @endcan + + @can('checkin', \App\Models\Asset::class) + + + {{ trans('general.checkin_due') }} + {{ (isset($total_due_and_overdue_for_checkin)) ? $total_due_and_overdue_for_checkin : '' }} + + @endcan
  •  
  • diff --git a/resources/views/notifications/markdown/report-expected-checkins.blade.php b/resources/views/notifications/markdown/report-expected-checkins.blade.php index 3715188d42..08b81350e4 100644 --- a/resources/views/notifications/markdown/report-expected-checkins.blade.php +++ b/resources/views/notifications/markdown/report-expected-checkins.blade.php @@ -10,7 +10,7 @@ @php $checkin = Helper::getFormattedDateObject($asset->expected_checkin, 'date'); @endphp -| [{{ $asset->present()->name }}]({{ route('hardware.show', ['hardware' => $asset->id]) }}) | [{{ $asset->assigned->present()->fullName }}]({{ route($asset->targetShowRoute().'.show', [$asset->assigned->id]) }}) | {{ $checkin['formatted'] }} +| [{{ $asset->present()->name }}]({{ route('hardware.show', ['hardware' => $asset->id]) }}) | [{{ $asset->assignedTo->present()->fullName }}]({{ route($asset->targetShowRoute().'.show', [$asset->assignedTo->id]) }}) | {{ $checkin['formatted'] }} @endforeach @endcomponent diff --git a/routes/api.php b/routes/api.php index 842e6210df..8adb0af619 100644 --- a/routes/api.php +++ b/routes/api.php @@ -496,13 +496,27 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi )->name('api.assets.show.byserial') ->where('any', '.*'); - Route::get('audit/{audit}', + // LEGACY URL - Get assets that are due or overdue for audit + Route::get('audit/{status}', [ Api\AssetsController::class, 'index' ] )->name('api.asset.to-audit'); + + + // This gets the "due or overdue" API endpoints for audits and checkins + Route::get('{action}/{upcoming_status}', + [ + Api\AssetsController::class, + 'index' + ] + )->name('api.assets.list-upcoming') + ->where(['action' => 'audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']); + + + Route::post('audit', [ Api\AssetsController::class, diff --git a/routes/web/hardware.php b/routes/web/hardware.php index d4f2892281..7833f0fdaa 100644 --- a/routes/web/hardware.php +++ b/routes/web/hardware.php @@ -50,26 +50,10 @@ Route::group( [AssetsController::class, 'dueForAudit'] )->name('assets.audit.due'); - Route::get('audit/overdue', - [AssetsController::class, 'overdueForAudit'] - )->name('assets.audit.overdue'); - - Route::get('audit/due', - [AssetsController::class, 'dueForAudit'] - )->name('assets.audit.due'); - - Route::get('audit/overdue', - [AssetsController::class, 'overdueForAudit'] - )->name('assets.audit.overdue'); - - Route::get('audit/due', - [AssetsController::class, 'dueForAudit'] - )->name('assets.audit.due'); - - Route::get('audit/overdue', - [AssetsController::class, 'overdueForAudit'] - )->name('assets.audit.overdue'); - + Route::get('checkins/due', + [AssetsController::class, 'dueForCheckin'] + )->name('assets.checkins.due'); + Route::get('audit/{id}', [AssetsController::class, 'audit'] )->name('asset.audit.create'); diff --git a/tests/Feature/Api/Assets/AssetIndexTest.php b/tests/Feature/Api/Assets/AssetIndexTest.php index 3175db6953..0a21e13f26 100644 --- a/tests/Feature/Api/Assets/AssetIndexTest.php +++ b/tests/Feature/Api/Assets/AssetIndexTest.php @@ -7,10 +7,10 @@ use App\Models\Company; use App\Models\User; use Illuminate\Testing\Fluent\AssertableJson; use Tests\TestCase; - +use Carbon\Carbon; class AssetIndexTest extends TestCase { - public function testAssetIndexReturnsExpectedAssets() + public function testAssetApiIndexReturnsExpectedAssets() { Asset::factory()->count(3)->create(); @@ -30,7 +30,102 @@ class AssetIndexTest extends TestCase ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); } - public function testAssetIndexAdheresToCompanyScoping() + public function testAssetApiIndexReturnsDisplayUpcomingAuditsDue() + { + Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->format('Y-m-d')]); + + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'due'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + public function testAssetApiIndexReturnsOverdueForAudit() + { + Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->subDays(1)->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + + public function testAssetApiIndexReturnsDueOrOverdueForAudit() + { + Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->format('Y-m-d')]); + Asset::factory()->count(2)->create(['next_audit_date' => Carbon::now()->subDays(1)->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'due-or-overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 5)->etc()); + } + + + + public function testAssetApiIndexReturnsDueForExpectedCheckin() + { + Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'due']) + ) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + public function testAssetApiIndexReturnsOverdueForExpectedCheckin() + { + Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->subDays(1)->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson(route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc()); + } + + public function testAssetApiIndexReturnsDueOrOverdueForExpectedCheckin() + { + Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->subDays(1)->format('Y-m-d')]); + Asset::factory()->count(2)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->format('Y-m-d')]); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson(route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'due-or-overdue'])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson(fn(AssertableJson $json) => $json->has('rows', 5)->etc()); + } + + public function testAssetApiIndexAdheresToCompanyScoping() { [$companyA, $companyB] = Company::factory()->count(2)->create();