Merge pull request #19058 from grokability/bulk-seat-checkin-toolbar
🎥 Bulk checkin license seats
This commit is contained in:
@@ -13,6 +13,7 @@ use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
@@ -127,10 +128,45 @@ class LicenseCheckinController extends Controller
|
||||
* @see LicenseCheckinController::create() method that provides the form view
|
||||
* @since [v6.1.1]
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function bulkCheckinSelected(Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('checkin', License::class);
|
||||
|
||||
$seatIds = $request->input('ids', []);
|
||||
|
||||
if (empty($seatIds)) {
|
||||
return redirect()->back()->with('warning', trans('admin/licenses/general.bulk.checkin_selected.no_seats_selected'));
|
||||
}
|
||||
|
||||
$seats = LicenseSeat::whereIn('id', $seatIds)
|
||||
->where(function ($query) {
|
||||
$query->whereNotNull('assigned_to')->orWhereNotNull('asset_id');
|
||||
})
|
||||
->with('license', 'user', 'asset')
|
||||
->get();
|
||||
|
||||
$count = 0;
|
||||
foreach ($seats as $seat) {
|
||||
if (! $seat->license || ! Gate::allows('checkin', $seat->license)) {
|
||||
continue;
|
||||
}
|
||||
$target = $seat->user ?? $seat->asset;
|
||||
$seat->assigned_to = null;
|
||||
$seat->asset_id = null;
|
||||
if (! $seat->license->reassignable) {
|
||||
$seat->unreassignable_seat = true;
|
||||
}
|
||||
if ($seat->save()) {
|
||||
event(new CheckoutableCheckedIn($seat, $target, auth()->user(), null));
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans_choice('admin/licenses/general.bulk.checkin_selected.success', $count, ['count' => $count]));
|
||||
}
|
||||
|
||||
public function bulkCheckin(Request $request, $licenseId)
|
||||
{
|
||||
|
||||
|
||||
@@ -388,6 +388,9 @@ class AssetsTransformer
|
||||
$permissions_array['available_actions'] = [
|
||||
'checkout' => false,
|
||||
'checkin' => Gate::allows('checkin', License::class),
|
||||
'bulk_selectable' => [
|
||||
'checkin' => Gate::allows('checkin', License::class),
|
||||
],
|
||||
];
|
||||
|
||||
$array += $permissions_array;
|
||||
|
||||
@@ -70,6 +70,9 @@ class LicenseSeatsTransformer
|
||||
'clone' => Gate::allows('create', License::class),
|
||||
'update' => Gate::allows('update', License::class),
|
||||
'delete' => Gate::allows('delete', License::class),
|
||||
'bulk_selectable' => [
|
||||
'checkin' => Gate::allows('checkin', License::class) && ($seat->assigned_to || $seat->asset_id),
|
||||
],
|
||||
];
|
||||
|
||||
$array += $permissions_array;
|
||||
|
||||
@@ -66,7 +66,6 @@ class LicensesTransformer
|
||||
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
|
||||
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
|
||||
'disabled' => $license->isInactive(),
|
||||
];
|
||||
|
||||
@@ -76,6 +75,7 @@ class LicensesTransformer
|
||||
'clone' => Gate::allows('create', License::class),
|
||||
'update' => Gate::allows('update', License::class),
|
||||
'delete' => $license->isDeletable(),
|
||||
'user_can_checkout' => (bool) (($license->free_seats_count - License::unReassignableCount($license)) > 0),
|
||||
'bulk_selectable' => [
|
||||
'delete' => $license->isDeletable(),
|
||||
],
|
||||
|
||||
@@ -240,33 +240,45 @@ class LicensePresenter extends Presenter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function dataTableLayoutSeats()
|
||||
public static function dataTableLayoutSeats(bool $withCheckbox = true)
|
||||
{
|
||||
$layout = [
|
||||
[
|
||||
'field' => 'id',
|
||||
'searchable' => false,
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('general.id'),
|
||||
'visible' => false,
|
||||
], [
|
||||
'field' => 'assigned_user',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'switchable' => true,
|
||||
'title' => trans('admin/licenses/general.user'),
|
||||
'visible' => true,
|
||||
'formatter' => 'usersLinkObjFormatter',
|
||||
], [
|
||||
'field' => 'assigned_user.email',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'switchable' => true,
|
||||
'title' => trans('admin/users/table.email'),
|
||||
'visible' => true,
|
||||
'formatter' => 'emailFormatter',
|
||||
],
|
||||
$layout = [];
|
||||
|
||||
if ($withCheckbox) {
|
||||
$layout[] = [
|
||||
'field' => 'checkbox',
|
||||
'checkbox' => true,
|
||||
'formatter' => 'checkboxEnabledFormatter',
|
||||
'titleTooltip' => trans('general.select_all_none'),
|
||||
'printIgnore' => true,
|
||||
'class' => 'hidden-print',
|
||||
];
|
||||
}
|
||||
|
||||
$layout = array_merge($layout, [[
|
||||
'field' => 'id',
|
||||
'searchable' => false,
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('general.id'),
|
||||
'visible' => false,
|
||||
], [
|
||||
'field' => 'assigned_user',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'switchable' => true,
|
||||
'title' => trans('admin/licenses/general.user'),
|
||||
'visible' => true,
|
||||
'formatter' => 'usersLinkObjFormatter',
|
||||
], [
|
||||
'field' => 'assigned_user.email',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'switchable' => true,
|
||||
'title' => trans('admin/users/table.email'),
|
||||
'visible' => true,
|
||||
'formatter' => 'emailFormatter',
|
||||
],
|
||||
[
|
||||
'field' => 'assigned_user.company',
|
||||
'searchable' => false,
|
||||
@@ -328,14 +340,27 @@ class LicensePresenter extends Presenter
|
||||
'printIgnore' => true,
|
||||
'class' => 'hidden-print',
|
||||
],
|
||||
];
|
||||
]);
|
||||
|
||||
return json_encode($layout);
|
||||
}
|
||||
|
||||
public static function dataTableLayoutSeatsCheckedOutToAssets()
|
||||
public static function dataTableLayoutSeatsCheckedOutToAssets($hide_fields = [])
|
||||
{
|
||||
$layout = [
|
||||
$layout = [];
|
||||
|
||||
if (! in_array('checkbox', $hide_fields)) {
|
||||
$layout[] = [
|
||||
'field' => 'checkbox',
|
||||
'checkbox' => true,
|
||||
'formatter' => 'checkboxEnabledFormatter',
|
||||
'titleTooltip' => trans('general.select_all_none'),
|
||||
'printIgnore' => true,
|
||||
'class' => 'hidden-print',
|
||||
];
|
||||
}
|
||||
|
||||
$layout = array_merge($layout, [
|
||||
[
|
||||
'field' => 'id',
|
||||
'searchable' => false,
|
||||
@@ -386,7 +411,7 @@ class LicensePresenter extends Presenter
|
||||
'printIgnore' => true,
|
||||
'class' => 'hidden-print',
|
||||
],
|
||||
];
|
||||
]);
|
||||
|
||||
return json_encode($layout);
|
||||
}
|
||||
|
||||
@@ -290,6 +290,11 @@ class UserFactory extends Factory
|
||||
return $this->appendPermission(['licenses.checkout' => '1']);
|
||||
}
|
||||
|
||||
public function checkinLicenses()
|
||||
{
|
||||
return $this->appendPermission(['licenses.checkin' => '1']);
|
||||
}
|
||||
|
||||
public function viewKeysLicenses()
|
||||
{
|
||||
return $this->appendPermission(['licenses.keys' => '1']);
|
||||
|
||||
@@ -31,6 +31,11 @@ return [
|
||||
'log_msg' => 'Checked in via bulk license checkin in license GUI',
|
||||
],
|
||||
|
||||
'checkin_selected' => [
|
||||
'success' => ':count seat checked in successfully. | :count seats checked in successfully.',
|
||||
'no_seats_selected' => 'No seats were selected.',
|
||||
],
|
||||
|
||||
'checkout_all' => [
|
||||
'button' => 'Checkout All Seats',
|
||||
'modal' => 'This action will checkout one seat to the first available user. | This action will checkout all :available_seats_count seats to the first available users. A user is considered available for this seat if they do not already have this license checked out to them, and the Auto-Assign License property is enabled on their user account.',
|
||||
|
||||
@@ -330,7 +330,29 @@
|
||||
</x-tabs.pane>
|
||||
|
||||
<x-tabs.pane name="licenses" :count="$asset->licenses->count()">
|
||||
<x-table.licenses show_search="false" :route="route('api.assets.licenselist', $asset)" :presenter="\App\Presenters\LicensePresenter::dataTableLayoutSeatsCheckedOutToAssets()"/>
|
||||
@can('view', \App\Models\License::class)
|
||||
<x-slot:table_header>{{ trans('general.licenses') }}</x-slot:table_header>
|
||||
@endcan
|
||||
|
||||
@can('checkin', \App\Models\License::class)
|
||||
<x-slot:bulkactions>
|
||||
<x-table.bulk-actions
|
||||
action_route="{{ route('licenses.bulkcheckin.selected') }}"
|
||||
model_name="seat"
|
||||
>
|
||||
<option value="checkin">{{ trans('general.checkin') }}</option>
|
||||
</x-table.bulk-actions>
|
||||
</x-slot:bulkactions>
|
||||
@endcan
|
||||
|
||||
@can('view', \App\Models\License::class)
|
||||
<x-table
|
||||
show_search="false"
|
||||
api_url="{{ route('api.assets.licenselist', $asset) }}"
|
||||
:presenter="\App\Presenters\LicensePresenter::dataTableLayoutSeatsCheckedOutToAssets()"
|
||||
export_filename="export-licenses-{{ str_slug($asset->asset_tag) }}-{{ date('Y-m-d') }}"
|
||||
/>
|
||||
@endcan
|
||||
</x-tabs.pane>
|
||||
|
||||
<x-tabs.pane name="components" :count="$asset->components->sum('assigned_qty')">
|
||||
|
||||
@@ -46,6 +46,17 @@
|
||||
{{ trans('general.assigned') }}
|
||||
</x-slot:table_header>
|
||||
|
||||
@can('checkin', $license)
|
||||
<x-slot:bulkactions>
|
||||
<x-table.bulk-actions
|
||||
action_route="{{ route('licenses.bulkcheckin.selected') }}"
|
||||
model_name="seat"
|
||||
>
|
||||
<option value="checkin">{{ trans('general.checkin') }}</option>
|
||||
</x-table.bulk-actions>
|
||||
</x-slot:bulkactions>
|
||||
@endcan
|
||||
|
||||
<x-table
|
||||
fixed_right_number="1"
|
||||
fixed_number="1"
|
||||
@@ -66,7 +77,7 @@
|
||||
<x-table
|
||||
show_search="false"
|
||||
api_url="{{ route('api.licenses.seats.index', [$license->id, 'status' => 'available']) }}"
|
||||
:presenter="\App\Presenters\LicensePresenter::dataTableLayoutSeats()"
|
||||
:presenter="\App\Presenters\LicensePresenter::dataTableLayoutSeats(false)"
|
||||
export_filename="export-{{ str_slug($license->name) }}-available-{{ date('Y-m-d') }}"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1486,7 +1486,6 @@
|
||||
});
|
||||
|
||||
|
||||
|
||||
// This specifies the footer columns that should have special styles associated
|
||||
// (usually numbers)
|
||||
window.footerStyle = column => ({
|
||||
@@ -1814,29 +1813,23 @@
|
||||
// However since different bulk actions have different requirements, we have to walk through the available_actions object
|
||||
// to determine whether to disable it
|
||||
function checkboxEnabledFormatter (value, row) {
|
||||
|
||||
// add some stuff to get the value of the select2 option here?
|
||||
|
||||
if ((row.available_actions) && (row.available_actions.bulk_selectable) && (row.available_actions.bulk_selectable.delete !== true)) {
|
||||
return {
|
||||
disabled:true,
|
||||
//checked: false, <-- not sure this will work the way we want?
|
||||
if (row.available_actions && row.available_actions.bulk_selectable) {
|
||||
var values = Object.values(row.available_actions.bulk_selectable);
|
||||
if (values.length > 0 && !values.some(function (v) { return v === true; })) {
|
||||
return { disabled: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function licenseInOutFormatter(value, row) {
|
||||
var user_can_checkout = row.available_actions && row.available_actions.user_can_checkout;
|
||||
|
||||
// check that checkin is not disabled
|
||||
if (row.user_can_checkout === false) {
|
||||
return '<span class="btn btn-sm bg-maroon btn-checkout disabled" data-tooltip="true" title="{{ trans('admin/licenses/message.checkout.unavailable') }}">{{ trans('general.checkout') }}</span>';
|
||||
} else if (row.disabled === true) {
|
||||
if (row.disabled === true) {
|
||||
return '<span class="btn btn-sm bg-maroon btn-checkout disabled" data-tooltip="true" title="{{ trans('admin/licenses/message.checkout.license_is_inactive') }}">{{ trans('general.checkout') }}</span>';
|
||||
|
||||
} 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.disabled === false)) {
|
||||
} else if ((row.available_actions.checkout === true) && (user_can_checkout === true) && (row.disabled === false)) {
|
||||
return '<a href="{{ config('app.url') }}/licenses/' + row.id + '/checkout" class="btn btn-sm bg-maroon btn-checkout" data-tooltip="true" title="{{ trans('general.checkout_tooltip') }}">{{ trans('general.checkout') }}</a>';
|
||||
} else if (row.available_actions.checkout === true) {
|
||||
return '<span class="btn btn-sm bg-maroon btn-checkout disabled" data-tooltip="true" title="{{ trans('admin/licenses/message.checkout.unavailable') }}">{{ trans('general.checkout') }}</span>';
|
||||
}
|
||||
}
|
||||
// We need a special formatter for license seats, since they don't work exactly the same
|
||||
|
||||
@@ -310,6 +310,24 @@
|
||||
</x-tabs.pane>
|
||||
|
||||
<x-tabs.pane name="licenses" :count="$user->licenses()->count()">
|
||||
|
||||
@can('checkin', \App\Models\License::class)
|
||||
<x-slot:table_header>{{ trans('general.licenses') }}</x-slot:table_header>
|
||||
<x-slot:bulkactions>
|
||||
<div class="hidden-print" style="padding-top:10px; min-width:400px;">
|
||||
<form method="POST" action="{{ route('licenses.bulkcheckin.selected') }}" id="userLicenseBulkCheckinForm" class="form-inline">
|
||||
@csrf
|
||||
<label for="userLicenseBulkActions"><span class="sr-only">{{ trans('button.bulk_actions') }}</span></label>
|
||||
<select name="bulk_actions" id="userLicenseBulkActions" class="form-control select2" style="min-width:350px;">
|
||||
<option value="checkin">{{ trans('general.checkin') }}</option>
|
||||
</select>
|
||||
<button type="submit" id="userLicenseBulkCheckinButton" class="btn btn-theme" disabled>{{ trans('button.go') }}</button>
|
||||
<span id="userLicenseBulkCheckinCount" style="display:none; margin-left:8px; line-height:34px;">— <span class="badge">0</span> {{ trans('general.selected') }}</span>
|
||||
</form>
|
||||
</div>
|
||||
</x-slot:bulkactions>
|
||||
@endcan
|
||||
|
||||
<table
|
||||
data-cookie-id-table="userLicenseTable"
|
||||
data-id-table="userLicenseTable"
|
||||
@@ -326,6 +344,9 @@
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
@can('checkin', \App\Models\License::class)
|
||||
<th class="hidden-print"><input type="checkbox" id="userLicenseSelectAll"></th>
|
||||
@endcan
|
||||
<th>{{ trans('general.name') }}</th>
|
||||
<th>{{ trans('admin/licenses/form.license_key') }}</th>
|
||||
<th data-footer-formatter="sumFormatter" data-fieldname="purchase_cost">{{ trans('general.purchase_cost') }}</th>
|
||||
@@ -337,6 +358,11 @@
|
||||
<tbody>
|
||||
@foreach ($user->licenses as $license)
|
||||
<tr>
|
||||
@can('checkin', \App\Models\License::class)
|
||||
<td class="hidden-print">
|
||||
<input type="checkbox" class="user-license-seat-checkbox hidden-print" form="userLicenseBulkCheckinForm" name="ids[]" value="{{ $license->pivot->id }}">
|
||||
</td>
|
||||
@endcan
|
||||
<td class="col-md-4">
|
||||
{!! $license->present()->nameUrl() !!}
|
||||
</td>
|
||||
@@ -652,6 +678,24 @@ $(function () {
|
||||
var optional_info_open = $('#optional_info_icon').hasClass('fa-caret-down');
|
||||
document.cookie = "optional_info_open="+optional_info_open+'; path=/';
|
||||
});
|
||||
|
||||
$(document).on('change', '.user-license-seat-checkbox', function () {
|
||||
var count = $('.user-license-seat-checkbox:checked').length;
|
||||
$('#userLicenseBulkCheckinButton').prop('disabled', count === 0);
|
||||
$('#userLicenseBulkCheckinCount .badge').text(count);
|
||||
if (count > 0) {
|
||||
$('#userLicenseBulkCheckinCount').show();
|
||||
} else {
|
||||
$('#userLicenseBulkCheckinCount').hide();
|
||||
}
|
||||
var total = $('.user-license-seat-checkbox').length;
|
||||
$('#userLicenseSelectAll').prop('indeterminate', count > 0 && count < total);
|
||||
$('#userLicenseSelectAll').prop('checked', count === total);
|
||||
});
|
||||
|
||||
$(document).on('change', '#userLicenseSelectAll', function () {
|
||||
$('.user-license-seat-checkbox').prop('checked', $(this).is(':checked')).trigger('change');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ Route::group(['prefix' => 'licenses', 'middleware' => ['auth']], function () {
|
||||
[Licenses\LicenseCheckinController::class, 'bulkCheckin']
|
||||
)->name('licenses.bulkcheckin');
|
||||
|
||||
Route::post(
|
||||
'bulkcheckin/selected',
|
||||
[Licenses\LicenseCheckinController::class, 'bulkCheckinSelected']
|
||||
)->name('licenses.bulkcheckin.selected');
|
||||
|
||||
Route::post(
|
||||
'{licenseId}/bulkcheckout',
|
||||
[Licenses\LicenseCheckoutController::class, 'bulkCheckout']
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Licenses\Ui;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Tests\Concerns\TestsPermissionsRequirement;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BulkCheckinSelectedLicenseSeatsTest extends TestCase implements TestsPermissionsRequirement
|
||||
{
|
||||
public function test_requires_permission()
|
||||
{
|
||||
$seat = LicenseSeat::factory()->assignedToUser()->create();
|
||||
|
||||
$this->actingAs(User::factory()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat->id]])
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_can_bulk_checkin_seats_assigned_to_users()
|
||||
{
|
||||
Event::fake([CheckoutableCheckedIn::class]);
|
||||
|
||||
$license = License::factory()->create(['seats' => 3]);
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
$seat1 = LicenseSeat::factory()->assignedToUser($user1)->create(['license_id' => $license->id]);
|
||||
$seat2 = LicenseSeat::factory()->assignedToUser($user2)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat1->id, $seat2->id]])
|
||||
->assertRedirect()
|
||||
->assertSessionHas('success');
|
||||
|
||||
$this->assertNull($seat1->fresh()->assigned_to);
|
||||
$this->assertNull($seat2->fresh()->assigned_to);
|
||||
|
||||
Event::assertDispatched(CheckoutableCheckedIn::class, 2);
|
||||
}
|
||||
|
||||
public function test_can_bulk_checkin_seats_assigned_to_assets()
|
||||
{
|
||||
Event::fake([CheckoutableCheckedIn::class]);
|
||||
|
||||
$license = License::factory()->create(['seats' => 2]);
|
||||
$asset = Asset::factory()->create();
|
||||
$seat = LicenseSeat::factory()->assignedToAsset($asset)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat->id]])
|
||||
->assertRedirect()
|
||||
->assertSessionHas('success');
|
||||
|
||||
$this->assertNull($seat->fresh()->asset_id);
|
||||
|
||||
Event::assertDispatched(CheckoutableCheckedIn::class, 1);
|
||||
}
|
||||
|
||||
public function test_empty_ids_returns_warning()
|
||||
{
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => []])
|
||||
->assertRedirect()
|
||||
->assertSessionHas('warning', trans('admin/licenses/general.bulk.checkin_selected.no_seats_selected'));
|
||||
}
|
||||
|
||||
public function test_missing_ids_returns_warning()
|
||||
{
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), [])
|
||||
->assertRedirect()
|
||||
->assertSessionHas('warning', trans('admin/licenses/general.bulk.checkin_selected.no_seats_selected'));
|
||||
}
|
||||
|
||||
public function test_unassigned_seats_in_submitted_ids_are_skipped()
|
||||
{
|
||||
Event::fake([CheckoutableCheckedIn::class]);
|
||||
|
||||
$license = License::factory()->create(['seats' => 2]);
|
||||
$unassignedSeat = LicenseSeat::factory()->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$unassignedSeat->id]])
|
||||
->assertRedirect()
|
||||
->assertSessionHas('success');
|
||||
|
||||
$this->assertNull($unassignedSeat->fresh()->assigned_to);
|
||||
$this->assertNull($unassignedSeat->fresh()->asset_id);
|
||||
|
||||
Event::assertNotDispatched(CheckoutableCheckedIn::class);
|
||||
}
|
||||
|
||||
public function test_non_reassignable_license_marks_unreassignable_seat()
|
||||
{
|
||||
$license = License::factory()->create(['seats' => 2, 'reassignable' => false]);
|
||||
$user = User::factory()->create();
|
||||
$seat = LicenseSeat::factory()->assignedToUser($user)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat->id]])
|
||||
->assertRedirect()
|
||||
->assertSessionHas('success');
|
||||
|
||||
$this->assertTrue((bool) $seat->fresh()->unreassignable_seat);
|
||||
}
|
||||
|
||||
public function test_reassignable_license_does_not_mark_unreassignable_seat()
|
||||
{
|
||||
$license = License::factory()->create(['seats' => 2, 'reassignable' => true]);
|
||||
$user = User::factory()->create();
|
||||
$seat = LicenseSeat::factory()->assignedToUser($user)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat->id]])
|
||||
->assertRedirect()
|
||||
->assertSessionHas('success');
|
||||
|
||||
$this->assertFalse((bool) $seat->fresh()->unreassignable_seat);
|
||||
}
|
||||
|
||||
public function test_only_submitted_seat_ids_are_processed()
|
||||
{
|
||||
Event::fake([CheckoutableCheckedIn::class]);
|
||||
|
||||
$license = License::factory()->create(['seats' => 3]);
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
$seat1 = LicenseSeat::factory()->assignedToUser($user1)->create(['license_id' => $license->id]);
|
||||
$seat2 = LicenseSeat::factory()->assignedToUser($user2)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat1->id]])
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertNull($seat1->fresh()->assigned_to);
|
||||
$this->assertNotNull($seat2->fresh()->assigned_to);
|
||||
|
||||
Event::assertDispatched(CheckoutableCheckedIn::class, 1);
|
||||
}
|
||||
|
||||
public function test_success_message_is_pluralized_correctly()
|
||||
{
|
||||
$license = License::factory()->create(['seats' => 3]);
|
||||
$user1 = User::factory()->create();
|
||||
$user2 = User::factory()->create();
|
||||
$seat1 = LicenseSeat::factory()->assignedToUser($user1)->create(['license_id' => $license->id]);
|
||||
$seat2 = LicenseSeat::factory()->assignedToUser($user2)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat1->id, $seat2->id]])
|
||||
->assertSessionHas('success', trans_choice('admin/licenses/general.bulk.checkin_selected.success', 2, ['count' => 2]));
|
||||
}
|
||||
|
||||
public function test_checkin_event_contains_correct_target_for_user_seat()
|
||||
{
|
||||
Event::fake([CheckoutableCheckedIn::class]);
|
||||
|
||||
$license = License::factory()->create(['seats' => 2]);
|
||||
$targetUser = User::factory()->create();
|
||||
$seat = LicenseSeat::factory()->assignedToUser($targetUser)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat->id]]);
|
||||
|
||||
Event::assertDispatched(CheckoutableCheckedIn::class, function ($event) use ($targetUser) {
|
||||
return $event->checkedOutTo->id === $targetUser->id;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_checkin_event_contains_correct_target_for_asset_seat()
|
||||
{
|
||||
Event::fake([CheckoutableCheckedIn::class]);
|
||||
|
||||
$license = License::factory()->create(['seats' => 2]);
|
||||
$targetAsset = Asset::factory()->create();
|
||||
$seat = LicenseSeat::factory()->assignedToAsset($targetAsset)->create(['license_id' => $license->id]);
|
||||
|
||||
$this->actingAs(User::factory()->checkinLicenses()->create())
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat->id]]);
|
||||
|
||||
Event::assertDispatched(CheckoutableCheckedIn::class, function ($event) use ($targetAsset) {
|
||||
return $event->checkedOutTo->id === $targetAsset->id;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_fmcs_prevents_checkin_of_seat_from_other_company()
|
||||
{
|
||||
Event::fake([CheckoutableCheckedIn::class]);
|
||||
|
||||
[$myCompany, $otherCompany] = Company::factory()->count(2)->create();
|
||||
|
||||
$actor = User::factory()->checkinLicenses()->create(['company_id' => $myCompany->id]);
|
||||
$otherLicense = License::factory()->create(['company_id' => $otherCompany->id, 'seats' => 2]);
|
||||
$targetUser = User::factory()->create(['company_id' => $otherCompany->id]);
|
||||
$seat = LicenseSeat::factory()->assignedToUser($targetUser)->create(['license_id' => $otherLicense->id]);
|
||||
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
$this->actingAs($actor)
|
||||
->post(route('licenses.bulkcheckin.selected'), ['ids' => [$seat->id]])
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertNotNull($seat->fresh()->assigned_to);
|
||||
|
||||
Event::assertNotDispatched(CheckoutableCheckedIn::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user