From 312353551ddc65d88a34e537dcab14d21fd78909 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:11:58 +0100 Subject: [PATCH 1/9] Changed button color --- resources/views/users/view.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 6ce7118791..0b0be323ff 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -897,7 +897,7 @@ @can('update', $license) - {{ trans('general.checkin') }} + {{ trans('general.checkin') }} @endcan From f6a823e0a81fc11478532fb32cd79dbe669e7828 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:13:04 +0100 Subject: [PATCH 2/9] Escape text --- resources/views/models/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/models/index.blade.php b/resources/views/models/index.blade.php index 95351e768d..41d402de0d 100755 --- a/resources/views/models/index.blade.php +++ b/resources/views/models/index.blade.php @@ -36,7 +36,7 @@ id="asssetModelsTable" data-buttons="modelButtons" class="table table-striped snipe-table" - data-url="{{ route('api.models.index', ['status' => request('status')]) }}" + data-url="{{ route('api.models.index', ['status' => e(request('status'))]) }}" data-export-options='{ "fileName": "export-models-{{ date('Y-m-d') }}", "ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"] From 36183ac19d15a7fb68520175e30c108b68dc72a4 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:13:18 +0100 Subject: [PATCH 3/9] Fixed url and tooltip text --- .../views/partials/bootstrap-table.blade.php | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 7c3468537d..8ebbc9fb16 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -557,12 +557,17 @@ } function licenseInOutFormatter(value, row) { - if(row.disabled || row.user_can_checkout === false) { - return '{{ trans('general.checkout') }}'; + + // check that checkin is not disabled + if (row.user_can_checkout === false) { + return '{{ trans('general.checkout') }}'; + } else if (row.disabled === true) { + return '{{ trans('general.checkout') }}'; + } else // The user is allowed to check the license seat out and it's available - if ((row.available_actions.checkout === true) && (row.user_can_checkout === true)) { - return '{{ trans('general.checkout') }}'; + if ((row.available_actions.checkout === true) && (row.user_can_checkout === true) && (row.disabled === false)) { + return '{{ trans('general.checkout') }}'; } } // We need a special formatter for license seats, since they don't work exactly the same @@ -572,7 +577,7 @@ if (row.disabled && (row.assigned_user || row.assigned_asset)) { return '{{ trans('general.checkin') }}'; } - if(row.disabled) { + if (row.disabled) { return '{{ trans('general.checkout') }}'; } // The user is allowed to check the license seat out and it's available @@ -580,6 +585,11 @@ return '{{ trans('general.checkout') }}'; } + // The user is allowed to check the license seat in and it's available + if ((row.available_actions.checkin === true) && ((row.assigned_asset) || (row.assigned_user))) { + return '{{ trans('general.checkin') }}'; + } + } function genericCheckinCheckoutFormatter(destination) { @@ -1775,7 +1785,19 @@ attributes: { title: '{{ trans('general.export') }}' } - } + }, + + btnShowInactive: { + text: '{{ (request()->input('status') == "inactive") ? trans('general.list_all') : trans('general.show_inactive') }}', + icon: 'fas fa-clock {{ (request()->input('status') == "inactive") ? ' text-danger' : '' }}', + event () { + window.location.href = '{{ (request()->input('status') == "inactive") ? route('licenses.index') : route('licenses.index', ['status' => 'inactive']) }}'; + }, + attributes: { + title: '{{ (request()->input('status') == "inactive") ? trans('general.list_all') : trans('general.show_inactive') }}', + + } + }, }); From 05f66229123ab2a2ad35515fd7e6e94f7113a452 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:13:32 +0100 Subject: [PATCH 4/9] Added status to URL --- resources/lang/en-US/general.php | 1 + resources/views/licenses/index.blade.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index a57c7c9f3a..a3b6eb37e6 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -589,6 +589,7 @@ return [ 'components' => ':count Component|:count Components', ], + 'show_inactive' => 'Expired or Terminated', 'more_info' => 'More Info', 'quickscan_bulk_help' => 'Checking this box will edit the asset record to reflect this new location. Leaving it unchecked will simply note the location in the audit log. Note that if this asset is checked out, it will not change the location of the person, asset or location it is checked out to.', 'whoops' => 'Whoops!', diff --git a/resources/views/licenses/index.blade.php b/resources/views/licenses/index.blade.php index c4efc93593..8012cc2bbe 100755 --- a/resources/views/licenses/index.blade.php +++ b/resources/views/licenses/index.blade.php @@ -27,7 +27,7 @@ id="licensesTable" data-buttons="licenseButtons" class="table table-striped snipe-table" - data-url="{{ route('api.licenses.index') }}" + data-url="{{ route('api.licenses.index', ['status' => e(request('status'))]) }}" data-export-options='{ "fileName": "export-licenses-{{ date('Y-m-d') }}", "ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"] From 30b6dcd767a320252d6b1e66bd5136c9f359ebac Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:13:50 +0100 Subject: [PATCH 5/9] Added status to breadcrumbs --- app/Providers/BreadcrumbsServiceProvider.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/Providers/BreadcrumbsServiceProvider.php b/app/Providers/BreadcrumbsServiceProvider.php index a5092b3ded..07c912bfb7 100644 --- a/app/Providers/BreadcrumbsServiceProvider.php +++ b/app/Providers/BreadcrumbsServiceProvider.php @@ -349,10 +349,19 @@ class BreadcrumbsServiceProvider extends ServiceProvider /** * Licenses Breadcrumbs */ - Breadcrumbs::for('licenses.index', fn (Trail $trail) => - $trail->parent('home', route('home')) - ->push(trans('general.licenses'), route('licenses.index')) - ); + if ((request()->is('licenses*')) && (request()->status=='inactive')) { + Breadcrumbs::for('licenses.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.licenses'), route('licenses.index')) + ->push(trans('general.show_inactive'), route('licenses.index')) + ); + } else { + Breadcrumbs::for('licenses.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.licenses'), route('licenses.index')) + ); + } + Breadcrumbs::for('licenses.create', fn (Trail $trail) => $trail->parent('licenses.index', route('licenses.index')) From 251a3db880d8ee01900a3204d747eb862be95e6f Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:20:34 +0100 Subject: [PATCH 6/9] Fixed factory --- database/factories/LicenseFactory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/factories/LicenseFactory.php b/database/factories/LicenseFactory.php index 1f5b105f42..591a36da1f 100644 --- a/database/factories/LicenseFactory.php +++ b/database/factories/LicenseFactory.php @@ -33,9 +33,9 @@ class LicenseFactory extends Factory 'seats' => $this->faker->numberBetween(1, 10), 'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'), 'order_number' => $this->faker->numberBetween(1000000, 50000000), - 'expiration_date' => $this->faker->dateTimeBetween('now', '+3 years', date_default_timezone_get())->format('Y-m-d H:i:s'), + 'expiration_date' => null, 'reassignable' => $this->faker->boolean(), - 'termination_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d H:i:s'), + 'termination_date' => null, 'supplier_id' => Supplier::factory(), 'category_id' => Category::factory(), ]; From 58cda5ae6d759183e45e3332de0d5621f3cb8989 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:22:25 +0100 Subject: [PATCH 7/9] Added scopes --- .../Controllers/Api/LicensesController.php | 8 ++++ .../Licenses/LicenseCheckoutController.php | 24 +++++++++- app/Http/Transformers/LicensesTransformer.php | 2 +- app/Models/License.php | 45 ++++++++++++++++--- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php index db39f987aa..105a20bd86 100644 --- a/app/Http/Controllers/Api/LicensesController.php +++ b/app/Http/Controllers/Api/LicensesController.php @@ -26,6 +26,12 @@ class LicensesController extends Controller $licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count'); + if ($request->input('status')=='inactive') { + $licenses->ExpiredLicenses(); + } else { + $licenses->ActiveLicenses(); + } + if ($request->filled('company_id')) { $licenses->where('licenses.company_id', '=', $request->input('company_id')); } @@ -94,6 +100,8 @@ class LicensesController extends Controller $licenses->onlyTrashed(); } + + // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value'); $limit = app('api_limit_value'); diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index 5b2d344ba5..90aff29627 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -25,7 +25,7 @@ class LicenseCheckoutController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param $id - * @return \Illuminate\Contracts\View\View + * @return \Illuminate\Contracts\View\View |\Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function create(License $license) @@ -39,6 +39,11 @@ class LicenseCheckoutController extends Controller return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')); } + // Make sure the license is expired or terminated + if ($license->isInactive()){ + return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive')); + } + // We don't currently allow checking out licenses to locations, so we'll reset that to user if needed if (session()->get('checkout_to_type') == 'location') { session()->put(['checkout_to_type' => 'user']); @@ -70,8 +75,19 @@ class LicenseCheckoutController extends Controller return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found')); } + $this->authorize('checkout', $license); + // Make sure there is at least one available to checkout + if ($license->availCount()->count() < 1) { + return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')); + } + + // Make sure the license is expired or terminated + if ($license->isInactive()) { + return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive')); + } + $licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId); $licenseSeat->created_by = auth()->id(); $licenseSeat->notes = $request->input('notes'); @@ -114,6 +130,12 @@ class LicenseCheckoutController extends Controller throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'))); } + + \Log::debug('Seat ID: '.$seatId); + \Log::debug('License: '.$license); + \Log::debug('$licenseSeat->license: '.$licenseSeat->license); + // dd($licenseSeat->license); + // dd($license); if (! $licenseSeat->license->is($license)) { throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch'))); } diff --git a/app/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php index 39079c6ec5..c360834c1c 100644 --- a/app/Http/Transformers/LicensesTransformer.php +++ b/app/Http/Transformers/LicensesTransformer.php @@ -31,11 +31,11 @@ class LicensesTransformer 'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null, 'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'), 'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'), + 'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'), 'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null, 'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost), 'purchase_cost_numeric' => $license->purchase_cost, 'notes' => Helper::parseEscapedMarkedownInline($license->notes), - 'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'), 'seats' => (int) $license->seats, 'free_seats_count' => (int) $license->free_seats_count - License::unReassignableCount($license), 'remaining' => (int) $license->free_seats_count, diff --git a/app/Models/License.php b/app/Models/License.php index d0a0faf8d9..3edc7a10a9 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -299,16 +299,17 @@ class License extends Depreciable } public function isInactive(): bool -{ - $day = now()->startOfDay(); + { + $day = now()->startOfDay(); - $expired = $this->expiration_date && $this->asDateTime($this->expiration_date)->startofDay()->lessThanOrEqualTo($day); + $expired = $this->expiration_date && $this->asDateTime($this->expiration_date)->startofDay()->lessThanOrEqualTo($day); - $terminated = $this->termination_date && $this->asDateTime($this->termination_date)->startofDay()->lessThanOrEqualTo($day); + $terminated = $this->termination_date && $this->asDateTime($this->termination_date)->startofDay()->lessThanOrEqualTo($day); - return $expired || $terminated; -} + return $expired || $terminated; + } + /** * Sets free_seat_count attribute * @@ -750,6 +751,38 @@ class License extends Depreciable ->get(); } + public function scopeActiveLicenses($query) + { + + return $query->whereNull('deleted_at') + + // The termination date is null or within range + ->where(function ($query) { + $query->whereNull('termination_date') + ->orWhereDate('termination_date', '>', [Carbon::now()]); + }) + ->where(function ($query) { + $query->whereNull('expiration_date') + ->orWhereDate('expiration_date', '>', [Carbon::now()]); + }); + } + + public function scopeExpiredLicenses($query) + { + + return $query->whereNull('deleted_at') + + // The termination date is null or within range + ->where(function ($query) { + $query->whereNull('termination_date') + ->orWhereDate('termination_date', '<=', [Carbon::now()]); + }) + ->orWhere(function ($query) { + $query->whereNull('expiration_date') + ->orWhereDate('expiration_date', '<=', [Carbon::now()]); + }); + } + /** * Query builder scope to order on manufacturer * From 319cb1bd1ef22c58a2179f308c40756edbd77e4d Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:23:44 +0100 Subject: [PATCH 8/9] Removed logging --- .../Controllers/Licenses/LicenseCheckoutController.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index 90aff29627..5db8bb17d7 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -130,12 +130,7 @@ class LicenseCheckoutController extends Controller throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'))); } - - \Log::debug('Seat ID: '.$seatId); - \Log::debug('License: '.$license); - \Log::debug('$licenseSeat->license: '.$licenseSeat->license); - // dd($licenseSeat->license); - // dd($license); + if (! $licenseSeat->license->is($license)) { throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch'))); } From 7062962cc8829c99c83d1f5c1e220f0ef4a6a4d0 Mon Sep 17 00:00:00 2001 From: snipe Date: Mon, 15 Sep 2025 13:43:08 +0100 Subject: [PATCH 9/9] Show warnings on the dates if expired or terminated --- .../Licenses/LicenseCheckoutController.php | 4 ++-- app/Models/License.php | 21 ++++++++++++++++++- resources/views/licenses/view.blade.php | 21 ++++++++++++------- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index 5db8bb17d7..db378b8f0f 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -40,7 +40,7 @@ class LicenseCheckoutController extends Controller } // Make sure the license is expired or terminated - if ($license->isInactive()){ + if ($license->isInactive()) { return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.license_is_inactive')); } @@ -130,7 +130,7 @@ class LicenseCheckoutController extends Controller throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'))); } - + if (! $licenseSeat->license->is($license)) { throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch'))); } diff --git a/app/Models/License.php b/app/Models/License.php index 3edc7a10a9..855995b43c 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -307,7 +307,26 @@ class License extends Depreciable $terminated = $this->termination_date && $this->asDateTime($this->termination_date)->startofDay()->lessThanOrEqualTo($day); - return $expired || $terminated; + return $this->isExpired() || $this->isTerminated(); + } + + public function isExpired(): bool + { + $day = now()->startOfDay(); + + $expired = $this->expiration_date && $this->asDateTime($this->expiration_date)->startofDay()->lessThanOrEqualTo($day); + + return $expired; + } + + public function isTerminated(): bool + { + $day = now()->startOfDay(); + + $terminated = $this->termination_date && $this->asDateTime($this->termination_date)->startofDay()->lessThanOrEqualTo($day); + + + return $terminated; } /** diff --git a/resources/views/licenses/view.blade.php b/resources/views/licenses/view.blade.php index 591870c544..43d2c697c0 100755 --- a/resources/views/licenses/view.blade.php +++ b/resources/views/licenses/view.blade.php @@ -249,6 +249,11 @@
+ @if ($license->isExpired()) + + + + @endif {{ Helper::getFormattedDateObject($license->expiration_date, 'date', false) }}
@@ -262,6 +267,12 @@
+ @if ($license->isTerminated()) + + + + @endif + {{ Helper::getFormattedDateObject($license->termination_date, 'date', false) }}
@@ -545,7 +556,7 @@ @can('checkout', $license) - @if ($license->availCount()->count() > 0) + @if (($license->availCount()->count() > 0) && (!$license->isInactive())) @@ -558,17 +569,13 @@ @else - - + {{ trans('general.checkout') }} - - - + {{ trans('admin/licenses/general.bulk.checkout_all.button') }} - @endif @endcan