3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 11:15:42 +00:00
Files
snipe-it/app/Http/Controllers/Users/UserItemTransferController.php
2026-07-22 17:16:35 +01:00

232 lines
8.1 KiB
PHP

<?php
namespace App\Http\Controllers\Users;
use App\Events\CheckoutableCheckedIn;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
use App\Http\Requests\TransferUserItemsRequest;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
class UserItemTransferController extends Controller
{
public function show(User $user): View|RedirectResponse
{
$this->authorize('view', $user);
$this->authorize('checkin', Asset::class);
$this->authorize('checkout', Asset::class);
$assets = $user->assets()
->with(['model.category', 'company'])
->whereNull('deleted_at')
->get();
$accessoryCheckouts = AccessoryCheckout::with(['accessory.category', 'accessory.company'])
->where('assigned_to', $user->id)
->where('assigned_type', User::class)
->get();
$licenseSeats = LicenseSeat::with(['license.category', 'license.company'])
->where('assigned_to', $user->id)
->whereNull('asset_id')
->get();
if ($assets->isEmpty() && $accessoryCheckouts->isEmpty() && $licenseSeats->isEmpty()) {
return redirect()->route('users.show', $user)
->with('error', trans('admin/users/general.transfer.nothing_to_transfer'));
}
return view('users.transfer', [
'sourceUser' => $user,
'assets' => $assets,
'accessoryCheckouts' => $accessoryCheckouts,
'licenseSeats' => $licenseSeats,
]);
}
public function store(TransferUserItemsRequest $request, User $user): RedirectResponse
{
$validated = $request->validated();
$target = User::findOrFail($validated['target_user_id']);
$assetIds = $validated['asset_ids'] ?? [];
$accessoryCheckoutIds = $validated['accessory_checkout_ids'] ?? [];
$licenseSeatIds = $validated['license_seat_ids'] ?? [];
$note = $validated['note'];
$result = DB::transaction(function () use ($user, $target, $assetIds, $accessoryCheckoutIds, $licenseSeatIds, $note) {
$assetsTransferred = 0;
$accessoriesTransferred = 0;
$licensesTransferred = 0;
$skipped = [];
foreach ($assetIds as $assetId) {
$asset = Asset::find($assetId);
if (! $asset || (int) $asset->assigned_to !== (int) $user->id || $asset->assigned_type !== User::class) {
$skipped[] = 'asset:'.$assetId;
continue;
}
if (! $asset->canCheckoutTo($target)) {
$skipped[] = 'asset:'.$assetId;
continue;
}
$this->checkInAsset($asset, $user, $note);
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), null, $note);
$assetsTransferred++;
}
foreach ($accessoryCheckoutIds as $checkoutId) {
$checkout = AccessoryCheckout::with('accessory')->find($checkoutId);
if (! $checkout || (int) $checkout->assigned_to !== (int) $user->id || $checkout->assigned_type !== User::class) {
$skipped[] = 'accessory:'.$checkoutId;
continue;
}
$accessory = $checkout->accessory;
if (! $accessory || ! $accessory->canCheckoutTo($target)) {
$skipped[] = 'accessory:'.$checkoutId;
continue;
}
$this->checkInAccessory($checkout, $accessory, $note);
$this->checkOutAccessory($accessory, $target, $note);
$accessoriesTransferred++;
}
foreach ($licenseSeatIds as $seatId) {
$seat = LicenseSeat::with('license')->find($seatId);
if (! $seat || (int) $seat->assigned_to !== (int) $user->id || $seat->asset_id !== null) {
$skipped[] = 'license:'.$seatId;
continue;
}
$license = $seat->license;
// Non-reassignable licenses stay with the original assignee.
// Transferring one would defeat the whole point of the flag,
// so we skip and surface it in the warning bucket.
if (! $license || ! $license->reassignable || ! $license->canCheckoutTo($target)) {
$skipped[] = 'license:'.$seatId;
continue;
}
$this->transferLicenseSeat($seat, $user, $target, $note);
$licensesTransferred++;
}
return compact('assetsTransferred', 'accessoriesTransferred', 'licensesTransferred', 'skipped');
});
$flash = trans('admin/users/general.transfer.success', [
'assets' => $result['assetsTransferred'],
'accessories' => $result['accessoriesTransferred'],
'licenses' => $result['licensesTransferred'],
'target' => $target->display_name,
]);
$redirect = redirect()->route('users.show', $target)->with('success', $flash);
if (! empty($result['skipped'])) {
$redirect->with('warning', trans('admin/users/general.transfer.some_skipped', [
'count' => count($result['skipped']),
]));
}
return $redirect;
}
private function checkInAsset(Asset $asset, User $source, ?string $note): void
{
$originalValues = $asset->getRawOriginal();
$checkinAt = date('Y-m-d H:i:s');
$asset->expected_checkin = null;
$asset->last_checkin = $checkinAt;
$asset->accepted = null;
$asset->assignedTo()->dissociate();
$asset->licenseseats->each(function (LicenseSeat $seat) {
$seat->update(['assigned_to' => null]);
});
CheckoutAcceptance::pending()
->where('checkoutable_type', Asset::class)
->where('checkoutable_id', $asset->id)
->get()
->each(fn ($a) => $a->delete());
$asset->save();
event(new CheckoutableCheckedIn($asset, $source, auth()->user(), $note, $checkinAt, $originalValues));
}
private function checkInAccessory(AccessoryCheckout $checkout, Accessory $accessory, ?string $note): void
{
$source = $checkout->assignedTo;
CheckoutAcceptance::pending()
->where('checkoutable_type', Accessory::class)
->where('checkoutable_id', $accessory->id)
->where('assigned_to_id', $checkout->assigned_to)
->get()
->each(fn ($a) => $a->delete());
$checkout->delete();
event(new CheckoutableCheckedIn($accessory, $source, auth()->user(), $note, date('Y-m-d H:i:s')));
}
private function checkOutAccessory(Accessory $accessory, User $target, ?string $note): void
{
$newCheckout = new AccessoryCheckout([
'accessory_id' => $accessory->id,
'assigned_to' => $target->id,
'assigned_type' => User::class,
'note' => $note,
]);
$newCheckout->created_by = auth()->id();
$newCheckout->save();
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $note, [], 1, false));
}
private function transferLicenseSeat(LicenseSeat $seat, User $source, User $target, ?string $note): void
{
CheckoutAcceptance::pending()
->where('checkoutable_type', License::class)
->where('checkoutable_id', $seat->license_id)
->where('assigned_to_id', $source->id)
->get()
->each(fn ($a) => $a->delete());
$seat->assigned_to = null;
$seat->save();
event(new CheckoutableCheckedIn($seat, $source, auth()->user(), $note));
$seat->assigned_to = $target->id;
$seat->save();
event(new CheckoutableCheckedOut($seat, $target, auth()->user(), $note, [], 1, false));
}
}