diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php
index 95a344dce9e7..8f653ff35303 100644
--- a/app/Helpers/Helper.php
+++ b/app/Helpers/Helper.php
@@ -1529,4 +1529,5 @@ static public function getRedirectOption($request, $id, $table, $item_id = null)
}
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
}
+
}
diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php
index 2ed709732236..0f752cbb83c9 100644
--- a/app/Http/Controllers/Api/LicenseSeatsController.php
+++ b/app/Http/Controllers/Api/LicenseSeatsController.php
@@ -119,7 +119,9 @@ public function update(Request $request, $licenseId, $seatId) : JsonResponse | a
// nothing to update
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
-
+ if( $touched && $licenseSeat->unreassignable_seat) {
+ return response()->json(Helper::formatStandardApiResponse('error', $licenseSeat, trans('admin/licenses/message.checkout.unavailable')));
+ }
// the logging functions expect only one "target". if both asset and user are present in the request,
// we simply let assets take precedence over users...
if ($licenseSeat->isDirty('assigned_to')) {
@@ -136,7 +138,11 @@ public function update(Request $request, $licenseId, $seatId) : JsonResponse | a
if ($licenseSeat->save()) {
if ($is_checkin) {
- $licenseSeat->logCheckin($target, $request->input('note'));
+ if(!$licenseSeat->license->reassignable){
+ $licenseSeat->unreassignable_seat = true;
+ $licenseSeat->save();
+ }
+ $licenseSeat->logCheckin($target, $licenseSeat->notes);
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php
index 373a167642d0..011eca5e8150 100644
--- a/app/Http/Controllers/Licenses/LicenseCheckinController.php
+++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php
@@ -69,12 +69,7 @@ public function store(Request $request, $seatId = null, $backTo = null)
$this->authorize('checkout', $license);
- if (! $license->reassignable) {
- // Not allowed to checkin
- Session::flash('error', trans('admin/licenses/message.checkin.not_reassignable') . '.');
- return redirect()->back()->withInput();
- }
// Declare the rules for the form validation
$rules = [
@@ -100,13 +95,16 @@ public function store(Request $request, $seatId = null, $backTo = null)
$licenseSeat->assigned_to = null;
$licenseSeat->asset_id = null;
$licenseSeat->notes = $request->input('notes');
+ if (! $licenseSeat->license->reassignable) {
+ $licenseSeat->unreassignable_seat = true;
+ }
session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the asset updated?
if ($licenseSeat->save()) {
- event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes')));
+ event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $licenseSeat->notes));
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success'));
@@ -131,21 +129,17 @@ public function bulkCheckin(Request $request, $licenseId) {
$license = License::findOrFail($licenseId);
$this->authorize('checkin', $license);
- if (! $license->reassignable) {
- // Not allowed to checkin
- Session::flash('error', 'License not reassignable.');
-
- return redirect()->back()->withInput();
- }
-
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
->whereNotNull('assigned_to')
- ->with('user')
+ ->with('user', 'license')
->get();
+ $license = $licenseSeatsByUser->first()?->license;
foreach ($licenseSeatsByUser as $user_seat) {
$user_seat->assigned_to = null;
-
+ if ($license && ! $license->reassignable) {
+ $user_seat->unreassignable_seat = true;
+ }
if ($user_seat->save()) {
Log::debug('Checking in '.$license->name.' from user '.$user_seat->username);
$user_seat->logCheckin($user_seat->user, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
@@ -158,9 +152,12 @@ public function bulkCheckin(Request $request, $licenseId) {
->get();
$count = 0;
+ $license = $licenseSeatsByAsset->first()?->license;
foreach ($licenseSeatsByAsset as $asset_seat) {
$asset_seat->asset_id = null;
-
+ if ($license && ! $license->reassignable) {
+ $asset_seat->unreassignable_seat = true;
+ }
if ($asset_seat->save()) {
Log::debug('Checking in '.$license->name.' from asset '.$asset_seat->asset_tag);
$asset_seat->logCheckin($asset_seat->asset, trans('admin/licenses/general.bulk.checkin_all.log_msg'));
diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php
index 6098423ba3e6..7ef6eeb30b9e 100755
--- a/app/Http/Controllers/Licenses/LicensesController.php
+++ b/app/Http/Controllers/Licenses/LicensesController.php
@@ -248,16 +248,25 @@ public function show($licenseId = null)
}
$users_count = User::where('autoassign_licenses', '1')->count();
- $total_seats_count = $license->totalSeatsByLicenseID();
+
+ $total_seats_count = (int) $license->totalSeatsByLicenseID();
$available_seats_count = $license->availCount()->count();
- $checkedout_seats_count = ($total_seats_count - $available_seats_count);
+ $unreassignable_seats_count = License::unReassignableCount($license);
+
+ if(!$license->reassignable){
+ $checkedout_seats_count = ($total_seats_count - $available_seats_count - $unreassignable_seats_count );
+ }
+ else {
+ $checkedout_seats_count = ($total_seats_count - $available_seats_count);
+ }
$this->authorize('view', $license);
return view('licenses.view', compact('license'))
->with('users_count', $users_count)
->with('total_seats_count', $total_seats_count)
->with('available_seats_count', $available_seats_count)
- ->with('checkedout_seats_count', $checkedout_seats_count);
+ ->with('checkedout_seats_count', $checkedout_seats_count)
+ ->with('unreassignable_seats_count', $unreassignable_seats_count);
}
diff --git a/app/Http/Transformers/LicenseSeatsTransformer.php b/app/Http/Transformers/LicenseSeatsTransformer.php
index 7ae68e9e449c..98688762956f 100644
--- a/app/Http/Transformers/LicenseSeatsTransformer.php
+++ b/app/Http/Transformers/LicenseSeatsTransformer.php
@@ -6,7 +6,6 @@
use App\Models\LicenseSeat;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
-
class LicenseSeatsTransformer
{
public function transformLicenseSeats(Collection $seats, $total)
@@ -48,6 +47,7 @@ public function transformLicenseSeat(LicenseSeat $seat, $seat_count = 0)
'reassignable' => (bool) $seat->license->reassignable,
'notes' => e($seat->notes),
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
+ 'disabled' => $seat->unreassignable_seat,
];
if ($seat_count != 0) {
diff --git a/app/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php
index 673ac06b3dab..3be7b256da8e 100644
--- a/app/Http/Transformers/LicensesTransformer.php
+++ b/app/Http/Transformers/LicensesTransformer.php
@@ -7,7 +7,7 @@
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
-class LicensesTransformer
+class LicensesTransformer
{
public function transformLicenses(Collection $licenses, $total)
{
@@ -37,7 +37,7 @@ public function transformLicense(License $license)
'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,
+ 'free_seats_count' => (int) $license->free_seats_count - License::unReassignableCount($license),
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
'license_name' => ($license->license_name) ? e($license->license_name) : null,
'license_email' => ($license->license_email) ? e($license->license_email) : null,
diff --git a/app/Models/License.php b/app/Models/License.php
index 0997c1e57b99..490a8401a4eb 100755
--- a/app/Models/License.php
+++ b/app/Models/License.php
@@ -533,6 +533,7 @@ public function availCount()
return $this->licenseSeatsRelation()
->whereNull('asset_id')
->whereNull('assigned_to')
+ ->where('unreassignable_seat', '=', false)
->whereNull('deleted_at');
}
@@ -582,7 +583,22 @@ public function getAssignedSeatsCountAttribute()
return 0;
}
-
+ /**
+ * Calculates the number of unreassignable seats
+ *
+ * @author G. Martinez
+ * @since [v7.1.15]
+ */
+ public static function unReassignableCount($license) : int
+ {
+ $count = 0;
+ if (!$license->reassignable) {
+ $count = licenseSeat::query()->where('unreassignable_seat', '=', true)
+ ->where('license_id', '=', $license->id)
+ ->count();
+ }
+ return $count;
+ }
/**
* Calculates the number of remaining seats
*
@@ -590,11 +606,12 @@ public function getAssignedSeatsCountAttribute()
* @since [v1.0]
* @return int
*/
- public function remaincount()
+ public function remaincount() : int
{
$total = $this->licenseSeatsCount;
$taken = $this->assigned_seats_count;
- $diff = ($total - $taken);
+ $unreassignable = self::unReassignableCount($this);
+ $diff = ($total - $taken - $unreassignable);
return (int) $diff;
}
@@ -652,6 +669,7 @@ public function freeSeat()
{
return $this->licenseseats()
->whereNull('deleted_at')
+ ->where('unreassignable_seat', '=', false)
->where(function ($query) {
$query->whereNull('assigned_to')
->whereNull('asset_id');
diff --git a/app/Models/LicenseSeat.php b/app/Models/LicenseSeat.php
index 397a14146870..6243d7d02006 100755
--- a/app/Models/LicenseSeat.php
+++ b/app/Models/LicenseSeat.php
@@ -21,6 +21,9 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
protected $guarded = 'id';
protected $table = 'license_seats';
+ protected $casts = [
+ 'unreassignable_seat' => 'boolean',
+ ];
/**
* The attributes that are mass assignable.
diff --git a/database/factories/LicenseSeatFactory.php b/database/factories/LicenseSeatFactory.php
index aaf75bdc2d82..ee516b6dac24 100644
--- a/database/factories/LicenseSeatFactory.php
+++ b/database/factories/LicenseSeatFactory.php
@@ -14,6 +14,7 @@ public function definition()
{
return [
'license_id' => License::factory(),
+ 'unreassignable_seat' => false,
];
}
diff --git a/database/migrations/2025_01_15_190348_adds_unavailable_to_license_seats_tables.php b/database/migrations/2025_01_15_190348_adds_unavailable_to_license_seats_tables.php
new file mode 100644
index 000000000000..40c8ad53679b
--- /dev/null
+++ b/database/migrations/2025_01_15_190348_adds_unavailable_to_license_seats_tables.php
@@ -0,0 +1,26 @@
+addColumn('boolean', 'unreassignable_seat')->default(false)->after('assigned_to');
+ });
+ }
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('license_seats', function (Blueprint $table) {
+ $table->dropColumn('unreassignable_seat');
+ });
+ }
+};
diff --git a/resources/lang/en-US/admin/licenses/message.php b/resources/lang/en-US/admin/licenses/message.php
index 74e1d7af5ac5..9a0219d857a6 100644
--- a/resources/lang/en-US/admin/licenses/message.php
+++ b/resources/lang/en-US/admin/licenses/message.php
@@ -50,7 +50,7 @@
'checkin' => array(
'error' => 'There was an issue checking in the license. Please try again.',
- 'not_reassignable' => 'License not reassignable',
+ 'not_reassignable' => 'Seat has been used',
'success' => 'The license was checked in successfully'
),
diff --git a/resources/views/licenses/view.blade.php b/resources/views/licenses/view.blade.php
index 2de0f9d6e0bc..cc2f10b980a7 100755
--- a/resources/views/licenses/view.blade.php
+++ b/resources/views/licenses/view.blade.php
@@ -32,7 +32,7 @@
{{ trans('admin/licenses/form.seats') }}
- {{ number_format($license->availCount()->count()) }} / {{ number_format($license->seats) }}
+ {{ number_format($license->availCount()->count()) }} / {{ number_format($license->seats)}}
@@ -573,13 +573,6 @@ class="table table-striped snipe-table"
{{ trans('admin/licenses/general.bulk.checkin_all.button') }}
- @elseif (! $license->reassignable)
-
-
-
- {{ trans('admin/licenses/general.bulk.checkin_all.button') }}
-
-
@else
diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php
index 099640f0b670..136428870dad 100644
--- a/resources/views/partials/bootstrap-table.blade.php
+++ b/resources/views/partials/bootstrap-table.blade.php
@@ -431,13 +431,16 @@ function checkboxEnabledFormatter (value, row) {
// Checkouts need the license ID, checkins need the specific seat ID
function licenseSeatInOutFormatter(value, row) {
+ if(row.disabled) {
+ 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) && ((!row.asset_id) && (!row.assigned_to))) {
return '{{ trans('general.checkout') }}';
- } else {
- return '{{ trans('general.checkin') }}';
}
-
+ else {
+ return '{{ trans('general.checkin') }}';
+ }
}
function genericCheckinCheckoutFormatter(destination) {
diff --git a/tests/Feature/Checkins/Api/LicenseCheckinTest.php b/tests/Feature/Checkins/Api/LicenseCheckinTest.php
new file mode 100644
index 000000000000..e649b9f43b95
--- /dev/null
+++ b/tests/Feature/Checkins/Api/LicenseCheckinTest.php
@@ -0,0 +1,27 @@
+notReassignable()
+ ->assignedToUser()
+ ->create();
+
+ $this->assertEquals(false, $licenseSeat->unreassignable_seat);
+
+ $this->actingAs(User::factory()->checkoutLicenses()->create())
+ ->post(route('licenses.checkin.save', $licenseSeat));
+
+ $licenseSeat->refresh();
+
+ $this->assertEquals(true, $licenseSeat->unreassignable_seat);
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Checkins/Ui/LicenseCheckinTest.php b/tests/Feature/Checkins/Ui/LicenseCheckinTest.php
index 7a6553d8ba61..f1f912cad454 100644
--- a/tests/Feature/Checkins/Ui/LicenseCheckinTest.php
+++ b/tests/Feature/Checkins/Ui/LicenseCheckinTest.php
@@ -20,7 +20,7 @@ public function testCheckingInLicenseRequiresCorrectPermission()
->assertForbidden();
}
- public function testCannotCheckinNonReassignableLicense()
+ public function testNonReassignableLicenseSeatCantBeCheckedOut()
{
$licenseSeat = LicenseSeat::factory()
->notReassignable()
@@ -28,13 +28,11 @@ public function testCannotCheckinNonReassignableLicense()
->create();
$this->actingAs(User::factory()->checkoutLicenses()->create())
- ->post(route('licenses.checkin.save', $licenseSeat), [
- 'notes' => 'my note',
- 'redirect_option' => 'index',
- ])
- ->assertSessionHas('error', trans('admin/licenses/message.checkin.not_reassignable') . '.');
+ ->post(route('licenses.checkin.save', $licenseSeat));
+
+ $licenseSeat->refresh();
- $this->assertNotNull($licenseSeat->fresh()->assigned_to);
+ $this->assertEquals(true, $licenseSeat->unreassignable_seat);
}
public function testCannotCheckinLicenseThatIsNotAssigned()