3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 11:15:42 +00:00

Make accessories requestable

Accessories can now be flagged as requestable and requested by users from
the requestable items page, the same way assets and asset models already
can. No migration is needed since the accessories table already carries a
requestable column.

I kept the semantics deliberately simple: a request just records intent
(and the requested quantity) and notifies the admins. It does not touch or
reserve stock. The admin still performs the normal checkout, which is what
actually decrements quantity. That way requests behave the same for unique
assets and for quantity-based accessories, instead of inventing a separate
"reserved" state.

The admin "Requested" queue already lists every checkout request
polymorphically (it showed asset models too), so accessory requests appear
there as well; I extended that view to render the accessory name, image and
a checkout action so an admin can actually see and fulfil the request.

While wiring this up I also fixed a pre-existing bug in the request flow:
the Requestable trait saved a 'qty' key, but the column is 'quantity' and
wasn't fillable, so requested quantities were being silently dropped (this
affected asset models too). Quantity is now persisted and read back
correctly.

Components and licenses can follow the same pattern; they each just need a
small migration to add the requestable column.
This commit is contained in:
Olivier Lambert
2026-06-10 19:57:53 +02:00
parent a9d1069705
commit 409e11dca4
12 changed files with 225 additions and 15 deletions

View File

@ -6,6 +6,7 @@ use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
use App\Enums\ActionType;
use App\Exceptions\AssetNotRequestable;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
@ -160,11 +161,17 @@ class ViewAssetsController extends Controller
},
])->RequestableModels()->get();
return view('account/requestable-assets', compact('assets', 'models'));
$accessories = Accessory::with('category', 'location', 'requests')
->withCount('checkouts as checkouts_count')
->RequestableAccessories()
->get();
return view('account/requestable-assets', compact('assets', 'models', 'accessories'));
}
public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null): RedirectResponse
{
$data = [];
$item = null;
$fullItemType = 'App\\Models\\'.studly_case($itemType);
@ -193,11 +200,12 @@ class ViewAssetsController extends Controller
$data['item_type'] = $itemType;
$data['target'] = auth()->user();
if ($fullItemType == Asset::class) {
$data['item_url'] = route('hardware.show', $item->id);
} else {
$data['item_url'] = route("view/{$itemType}", $item->id);
}
$data['item_url'] = match ($fullItemType) {
Asset::class => route('hardware.show', $item->id),
AssetModel::class => route('view/model', $item->id),
Accessory::class => route('accessories.show', $item->id),
default => route("view/{$itemType}", $item->id),
};
$settings = Setting::getSettings();
@ -209,7 +217,7 @@ class ViewAssetsController extends Controller
if (($item_request = $item->isRequestedBy($user)) || ($is_admin && $cancel_by_admin)) {
$item->cancelRequest($is_admin && $cancel_by_admin ? $requestingUser : null);
$data['item_quantity'] = ($item_request) ? $item_request->qty : 1;
$data['item_quantity'] = ($item_request) ? $item_request->quantity : 1;
$logaction->logaction(ActionType::RequestCanceled);
if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) {
@ -222,11 +230,12 @@ class ViewAssetsController extends Controller
return redirect()->back()->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
} else {
if ($fullItemType === Asset::class && is_null(Asset::RequestableAssets()->find($item->id))) {
if (($fullItemType === Asset::class && is_null(Asset::RequestableAssets()->find($item->id)))
|| ($fullItemType === Accessory::class && is_null(Accessory::RequestableAccessories()->find($item->id)))) {
return redirect()->back()->with('error', trans('admin/hardware/message.requests.error'));
}
$item->request();
$item->request($data['item_quantity']);
if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) {
$logaction->logaction('requested');
try {