Skip to content

WIP: Added command to purge orphaned checkout acceptances #16603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
Draft
61 changes: 61 additions & 0 deletions app/Console/Commands/PurgeOrphanedCheckoutAcceptances.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Console\Commands;

use App\Models\CheckoutAcceptance;
use Illuminate\Console\Command;

class PurgeOrphanedCheckoutAcceptances extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:purge-orphaned-checkout-acceptances';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Deletes pending checkout acceptances where the user has been deleted.';

/**
* Execute the console command.
*/
public function handle()
{
$orphanedAcceptances = CheckoutAcceptance::pending()
->whereDoesntHave('assignedTo')
->get();

if ($orphanedAcceptances->isEmpty()) {
$this->info('No orphaned checkout acceptances found.');

return 0;
}

$this->info('Found ' . $orphanedAcceptances->count() . ' orphaned checkout acceptances.');

$this->table(['ID', 'Checkoutable Type', 'Assigned To ID'], $orphanedAcceptances->map(function ($acceptance) {
return [
$acceptance->id,
$acceptance->checkoutable_type,
$acceptance->assigned_to_id,
];
}));

if (!$this->confirm('Do you wish to permanently delete these ' . $orphanedAcceptances->count() . ' orphaned checkout acceptances?')) {
$this->info('Aborting.');

return 0;
}

$orphanedAcceptances->each->forceDelete();

$this->info('Orphaned checkout acceptances have been deleted.');

return 0;
}
}
8 changes: 6 additions & 2 deletions app/Http/Controllers/ReportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
use App\Models\License;
use App\Models\ReportTemplate;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
Expand Down Expand Up @@ -1117,9 +1119,11 @@ public function getAssetAcceptanceReport($deleted = false) : View
'checkoutable' => function (MorphTo $query) {
$query->morphWith([
AssetModel::class => ['model'],
Company::class => ['company'],
Asset::class => ['assignedTo'],
])->with('model.category');
])->with([
'company',
'model.category',
])->where('assigned_type', User::class);
},
'assignedTo' => function($query){
$query->withTrashed();
Expand Down
1 change: 1 addition & 0 deletions app/Http/Controllers/Users/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ public function destroy(DeleteUserRequest $request, $id)
$this->authorize('delete', $user);

if ($user->delete()) {
$user->pendingCheckoutAcceptances()->delete();
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete'));
}
}
Expand Down
5 changes: 5 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,11 @@ public function acceptances()
->orderBy('created_at', 'desc');
}

public function pendingCheckoutAcceptances()
{
return $this->hasMany(CheckoutAcceptance::class, 'assigned_to_id')->pending();
}

/**
* Establishes the user -> requested assets relationship
*
Expand Down
28 changes: 27 additions & 1 deletion database/factories/CheckoutAcceptanceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

Expand All @@ -31,7 +33,7 @@ public function configure(): static
$this->createdAssociatedActionLogEntry($acceptance);
}

if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) {
if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User && !$acceptance->isPending()) {
$acceptance->checkoutable->update([
'assigned_to' => $acceptance->assigned_to_id,
'assigned_type' => get_class($acceptance->assignedTo),
Expand All @@ -48,6 +50,30 @@ public function forAccessory()
]);
}

public function forAsset()
{
return $this->state([
'checkoutable_type' => Asset::class,
'checkoutable_id' => Asset::factory(),
]);
}

public function forConsumable()
{
return $this->state([
'checkoutable_type' => Consumable::class,
'checkoutable_id' => Consumable::factory(),
]);
}

public function forLicenseSeat()
{
return $this->state([
'checkoutable_type' => LicenseSeat::class,
'checkoutable_id' => LicenseSeat::factory(),
]);
}

public function pending()
{
return $this->state([
Expand Down
62 changes: 62 additions & 0 deletions tests/Feature/Console/PurgeOrphanedCheckoutAcceptancesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Tests\Feature\Console;

use App\Models\CheckoutAcceptance;
use Tests\TestCase;

class PurgeOrphanedCheckoutAcceptancesTest extends TestCase
{
public function testPurgeOrphanedCheckoutAcceptances()
{
[$pendingForAccessory, $pendingForAccessoryWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->pending()->forAccessory()->create();
[$pendingForAsset, $pendingForAssetWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->pending()->forAsset()->create();
[$pendingForConsumable, $pendingForConsumableWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->pending()->forConsumable()->create();
[$pendingForLicenseSeat, $pendingForLicenseSeatWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->pending()->forLicenseSeat()->create();

[$acceptedForAccessory, $acceptedForAccessoryWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->accepted()->forAccessory()->create();
[$acceptedForAsset, $acceptedForAssetWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->accepted()->forAsset()->create();
[$acceptedForConsumable, $acceptedForConsumableWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->accepted()->forConsumable()->create();
[$acceptedForLicenseSeat, $acceptedForLicenseSeatWithDeletedUser] = CheckoutAcceptance::factory()->count(2)->accepted()->forLicenseSeat()->create();

$this->assertDatabaseCount('checkout_acceptances', 16);

$pendingForAccessoryWithDeletedUser->assignedTo->delete();
$pendingForAssetWithDeletedUser->assignedTo->delete();
$pendingForConsumableWithDeletedUser->assignedTo->forceDelete();
$pendingForLicenseSeatWithDeletedUser->assignedTo->forceDelete();

$acceptedForAccessoryWithDeletedUser->assignedTo->delete();
$acceptedForAssetWithDeletedUser->assignedTo->delete();
$acceptedForConsumableWithDeletedUser->assignedTo->forceDelete();
$acceptedForLicenseSeatWithDeletedUser->assignedTo->forceDelete();

$this->assertDatabaseCount('checkout_acceptances', 16);

$this->artisan('snipeit:purge-orphaned-checkout-acceptances')
->expectsConfirmation('Do you wish to permanently delete these 4 orphaned checkout acceptances?', 'yes')
->assertSuccessful();

$this->assertDatabaseHas('checkout_acceptances', ['id' => $pendingForAccessory->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $pendingForAsset->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $pendingForConsumable->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $pendingForLicenseSeat->id]);

$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForAccessory->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForAsset->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForConsumable->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForLicenseSeat->id]);

$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForAccessoryWithDeletedUser->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForAssetWithDeletedUser->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForConsumableWithDeletedUser->id]);
$this->assertDatabaseHas('checkout_acceptances', ['id' => $acceptedForLicenseSeatWithDeletedUser->id]);

$this->assertDatabaseMissing('checkout_acceptances', ['id' => $pendingForAccessoryWithDeletedUser->id]);
$this->assertDatabaseMissing('checkout_acceptances', ['id' => $pendingForAssetWithDeletedUser->id]);
$this->assertDatabaseMissing('checkout_acceptances', ['id' => $pendingForConsumableWithDeletedUser->id]);
$this->assertDatabaseMissing('checkout_acceptances', ['id' => $pendingForLicenseSeatWithDeletedUser->id]);

$this->assertDatabaseCount('checkout_acceptances', 12);
}
}
43 changes: 28 additions & 15 deletions tests/Feature/Users/Ui/DeleteUserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests\Feature\Users\Ui;

use App\Models\CheckoutAcceptance;
use Tests\TestCase;
use App\Models\LicenseSeat;
use App\Models\Location;
Expand Down Expand Up @@ -159,20 +160,6 @@ public function testDisallowUserDeletionIfStillHaveLicenses()
}


public function testAllowUserDeletionIfNotManagingLocations()
{
$manager = User::factory()->create();
$this->actingAs(User::factory()->deleteUsers()->viewUsers()->create())->assertTrue($manager->isDeletable());

$response = $this->actingAs(User::factory()->deleteUsers()->viewUsers()->create())
->delete(route('users.destroy', $manager->id))
->assertStatus(302)
->assertRedirect(route('users.index'));

$this->followRedirects($response)->assertSee('Success');

}

public function testDisallowUserDeletionIfNoDeletePermissions()
{
$manager = User::factory()->create();
Expand Down Expand Up @@ -202,7 +189,6 @@ public function testDisallowUserDeletionIfTheyStillHaveAssets()
$this->followRedirects($response)->assertSee('Error');
}


public function testUsersCannotDeleteThemselves()
{
$manager = User::factory()->deleteUsers()->viewUsers()->create();
Expand All @@ -216,5 +202,32 @@ public function testUsersCannotDeleteThemselves()
$this->followRedirects($response)->assertSee('Error');
}

public function testCanDeleteUser()
{
$user = User::factory()->create();

$response = $this->actingAs(User::factory()->deleteUsers()->viewUsers()->create())
->delete(route('users.destroy', $user->id))
->assertStatus(302)
->assertRedirect(route('users.index'));

$this->followRedirects($response)->assertSee('Success');

$this->assertSoftDeleted($user);
}

public function testUsersUnacceptedCheckoutAcceptancesAreSoftDeletedWhenUserIsDeleted()
{
$user = User::factory()->create();

$checkoutAcceptance = CheckoutAcceptance::factory()
->pending()
->for($user, 'assignedTo')
->create();

$this->actingAs(User::factory()->deleteUsers()->viewUsers()->create())
->delete(route('users.destroy', $user->id));

$this->assertSoftDeleted($checkoutAcceptance);
}
}
23 changes: 23 additions & 0 deletions tests/Feature/Users/Ui/RestoreUserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Tests\Feature\Users\Ui;

use Tests\TestCase;

class RestoreUserTest extends TestCase
{
public function testPermissionNeededToRestoreUser()
{
$this->markTestIncomplete();
}

public function testCanRestoreUser()
{
$this->markTestIncomplete();
}

public function testRestoringUserDoesNotRestorePendingCheckoutAcceptances()
{
$this->markTestIncomplete();
}
}
19 changes: 19 additions & 0 deletions tests/Unit/UserTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Tests\Unit;

use App\Models\CheckoutAcceptance;
use App\Models\User;
use Tests\TestCase;

Expand Down Expand Up @@ -103,4 +104,22 @@ public function testFirstNameLastInitial()
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastinitial');
$this->assertEquals($expected_username, $user['username']);
}

public function testPendingCheckoutAcceptancesRelationship()
{
$user = User::factory()->create();

$acceptedAcceptance = CheckoutAcceptance::factory()
->accepted()
->for($user, 'assignedTo')
->create();

$pendingAcceptance = CheckoutAcceptance::factory()
->pending()
->for($user, 'assignedTo')
->create();

$this->assertFalse($user->pendingCheckoutAcceptances->contains($acceptedAcceptance));
$this->assertTrue($user->pendingCheckoutAcceptances->contains($pendingAcceptance));
}
}
Loading