diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index 6c75ae06db05..4b9ccb675373 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -647,6 +647,15 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse $assets = Asset::findOrFail($asset_ids); + // Prevent checking out assets that are already checked out + if ($assets->pluck('assigned_to')->unique()->filter()->isNotEmpty()) { + // re-add the asset ids so the assets select is re-populated + $request->session()->flashInput(['selected_assets' => $asset_ids]); + + return redirect(route('hardware.bulkcheckout.show')) + ->with('error', trans('general.error_assets_already_checked_out')); + } + if (request('checkout_to_type') == 'asset') { foreach ($asset_ids as $asset_id) { if ($target->id == $asset_id) { diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index 61b7c9f809f5..42751c5cf924 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -520,6 +520,7 @@ 'item_name_var' => ':item Name', 'error_user_company' => 'Checkout target company and asset company do not match', 'error_user_company_accept_view' => 'An Asset assigned to you belongs to a different company so you can\'t accept nor deny it, please check with your manager', + 'error_assets_already_checked_out' => 'One or more of the assets are already checked out', 'importer' => [ 'checked_out_to_fullname' => 'Checked Out to: Full Name', 'checked_out_to_first_name' => 'Checked Out to: First Name', diff --git a/tests/Feature/Checkouts/Ui/BulkAssetCheckoutTest.php b/tests/Feature/Checkouts/Ui/BulkAssetCheckoutTest.php index f05c4389e6c1..2f325ba68a0e 100644 --- a/tests/Feature/Checkouts/Ui/BulkAssetCheckoutTest.php +++ b/tests/Feature/Checkouts/Ui/BulkAssetCheckoutTest.php @@ -4,8 +4,10 @@ use App\Mail\CheckoutAssetMail; use App\Models\Asset; +use App\Models\Location; use App\Models\User; use Illuminate\Support\Facades\Mail; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use Tests\TestCase; @@ -89,4 +91,63 @@ public function testHandleMissingModelBeingIncluded() $this->fail('Asset checkout email was sent when the entire checkout failed.'); } } + + public static function checkoutTargets() + { + yield 'Checkout to user' => [ + function () { + return [ + 'type' => 'user', + 'target' => User::factory()->forCompany()->create(), + ]; + } + ]; + + yield 'Checkout to asset' => [ + function () { + return [ + 'type' => 'asset', + 'target' => Asset::factory()->forCompany()->create(), + ]; + } + ]; + + yield 'Checkout to location' => [ + function () { + return [ + 'type' => 'location', + 'target' => Location::factory()->forCompany()->create(), + ]; + } + ]; + } + + #[DataProvider('checkoutTargets')] + public function test_prevents_checkouts_of_checked_out_items($data) + { + ['type' => $type, 'target' => $target] = $data(); + + $asset = Asset::factory()->create(); + $checkedOutAsset = Asset::factory()->assignedToUser()->create(); + $existingUserId = $checkedOutAsset->assigned_to; + + $response = $this->actingAs(User::factory()->superuser()->create()) + ->post(route('hardware.bulkcheckout.store'), [ + 'selected_assets' => [ + $asset->id, + $checkedOutAsset->id, + ], + 'checkout_to_type' => $type, + "assigned_$type" => $target->id, + ]); + + $this->assertEquals( + $existingUserId, + $checkedOutAsset->fresh()->assigned_to, + 'Asset was checked out when it should have been prevented.' + ); + + // ensure redirected back + $response->assertRedirectToRoute('hardware.bulkcheckout.show'); + } }