diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php index db39f987aa41..105a20bd8689 100644 --- a/app/Http/Controllers/Api/LicensesController.php +++ b/app/Http/Controllers/Api/LicensesController.php @@ -26,6 +26,12 @@ public function index(Request $request) : JsonResponse | array $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 @@ public function index(Request $request) : JsonResponse | array $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 5b2d344ba598..5db8bb17d77d 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 @@ public function create(License $license) 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 @@ public function store(LicenseCheckoutRequest $request, $licenseId, $seatId = nul 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,7 @@ protected function findLicenseSeatToCheckout($license, $seatId) 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/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php index 39079c6ec565..c360834c1c75 100644 --- a/app/Http/Transformers/LicensesTransformer.php +++ b/app/Http/Transformers/LicensesTransformer.php @@ -31,11 +31,11 @@ public function transformLicense(License $license) '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 d0a0faf8d97a..3edc7a10a900 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -299,16 +299,17 @@ public function setTerminationDateAttribute($value) } 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 @@ public static function getExpiringLicenses($days = 60) ->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 * diff --git a/app/Providers/BreadcrumbsServiceProvider.php b/app/Providers/BreadcrumbsServiceProvider.php index a5092b3dedce..07c912bfb729 100644 --- a/app/Providers/BreadcrumbsServiceProvider.php +++ b/app/Providers/BreadcrumbsServiceProvider.php @@ -349,10 +349,19 @@ public function boot() /** * 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')) diff --git a/database/factories/LicenseFactory.php b/database/factories/LicenseFactory.php index 1f5b105f42a0..591a36da1f70 100644 --- a/database/factories/LicenseFactory.php +++ b/database/factories/LicenseFactory.php @@ -33,9 +33,9 @@ public function definition() '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(), ]; diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index a57c7c9f3a90..a3b6eb37e6c7 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -589,6 +589,7 @@ '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 c4efc9359301..8012cc2bbe0c 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"] diff --git a/resources/views/models/index.blade.php b/resources/views/models/index.blade.php index 95351e768d27..41d402de0d0b 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"] diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 7c3468537def..8ebbc9fb16d2 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -557,12 +557,17 @@ function checkboxEnabledFormatter (value, row) { } 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 @@ function licenseSeatInOutFormatter(value, row) { 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 @@ function licenseSeatInOutFormatter(value, row) { 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 @@ class: 'btn btn-primary', 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') }}', + + } + }, }); diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 6ce7118791d8..0b0be323ffd0 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -897,7 +897,7 @@ class="table table-striped snipe-table table-hover" @can('update', $license) - {{ trans('general.checkin') }} + {{ trans('general.checkin') }} @endcan