Skip to content

Commit 4465aef

Browse files
authored
Merge pull request #15114 from snipe/checkout_multiple_accessories
Checkout multiple of an accessory in one checkout
2 parents ee589ca + 822bc6f commit 4465aef

File tree

14 files changed

+532
-168
lines changed

14 files changed

+532
-168
lines changed

app/Http/Controllers/Accessories/AccessoryCheckoutController.php

+17-32
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
use App\Events\CheckoutableCheckedOut;
66
use App\Http\Controllers\Controller;
7+
use App\Http\Requests\AccessoryCheckoutRequest;
78
use App\Models\Accessory;
89
use App\Models\User;
910
use Carbon\Carbon;
1011
use Illuminate\Http\Request;
1112
use Illuminate\Support\Facades\Auth;
12-
use Illuminate\Support\Facades\DB;
1313
use \Illuminate\Contracts\View\View;
1414
use \Illuminate\Http\RedirectResponse;
1515

@@ -57,44 +57,29 @@ public function create($id) : View | RedirectResponse
5757
*
5858
* @author [A. Gianotto] [<[email protected]>]
5959
* @param Request $request
60-
* @param int $accessoryId
60+
* @param int $accessory
6161
*/
62-
public function store(Request $request, $accessoryId) : RedirectResponse
62+
public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
6363
{
64-
// Check if the accessory exists
65-
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
66-
// Redirect to the accessory management page with error
67-
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
68-
}
6964

7065
$this->authorize('checkout', $accessory);
71-
72-
if (!$user = User::find($request->input('assigned_to'))) {
73-
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
66+
$accessory->assigned_to = $request->input('assigned_to');
67+
$user = User::find($request->input('assigned_to'));
68+
$accessory->checkout_qty = $request->input('checkout_qty', 1);
69+
70+
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
71+
$accessory->users()->attach($accessory->id, [
72+
'accessory_id' => $accessory->id,
73+
'created_at' => Carbon::now(),
74+
'user_id' => Auth::id(),
75+
'assigned_to' => $request->input('assigned_to'),
76+
'note' => $request->input('note'),
77+
]);
7478
}
75-
76-
// Make sure there is at least one available to checkout
77-
if ($accessory->numRemaining() <= 0){
78-
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
79-
}
80-
81-
82-
// Update the accessory data
83-
$accessory->assigned_to = e($request->input('assigned_to'));
84-
85-
$accessory->users()->attach($accessory->id, [
86-
'accessory_id' => $accessory->id,
87-
'created_at' => Carbon::now(),
88-
'user_id' => Auth::id(),
89-
'assigned_to' => $request->get('assigned_to'),
90-
'note' => $request->input('note'),
91-
]);
92-
93-
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
94-
9579
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
9680

9781
// Redirect to the new accessory page
98-
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
82+
return redirect()->route('accessories.index')
83+
->with('success', trans('admin/accessories/message.checkout.success'));
9984
}
10085
}

app/Http/Controllers/Api/AccessoriesController.php

+22-32
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use App\Events\CheckoutableCheckedOut;
66
use App\Helpers\Helper;
77
use App\Http\Controllers\Controller;
8+
use App\Http\Requests\AccessoryCheckoutRequest;
9+
use App\Http\Requests\StoreAccessoryRequest;
810
use App\Http\Transformers\AccessoriesTransformer;
911
use App\Http\Transformers\SelectlistTransformer;
1012
use App\Models\Accessory;
@@ -121,12 +123,12 @@ public function index(Request $request)
121123
/**
122124
* Store a newly created resource in storage.
123125
*
126+
* @param \App\Http\Requests\ImageUploadRequest $request
127+
* @return \Illuminate\Http\JsonResponse
124128
* @author [A. Gianotto] [<[email protected]>]
125129
* @since [v4.0]
126-
* @param \App\Http\Requests\ImageUploadRequest $request
127-
* @return \Illuminate\Http\Response
128130
*/
129-
public function store(ImageUploadRequest $request)
131+
public function store(StoreAccessoryRequest $request)
130132
{
131133
$this->authorize('create', Accessory::class);
132134
$accessory = new Accessory;
@@ -144,10 +146,10 @@ public function store(ImageUploadRequest $request)
144146
/**
145147
* Display the specified resource.
146148
*
149+
* @param int $id
150+
* @return array
147151
* @author [A. Gianotto] [<[email protected]>]
148152
* @since [v4.0]
149-
* @param int $id
150-
* @return \Illuminate\Http\Response
151153
*/
152154
public function show($id)
153155
{
@@ -161,10 +163,10 @@ public function show($id)
161163
/**
162164
* Display the specified resource.
163165
*
166+
* @param int $id
167+
* @return array
164168
* @author [A. Gianotto] [<[email protected]>]
165169
* @since [v4.0]
166-
* @param int $id
167-
* @return \Illuminate\Http\Response
168170
*/
169171
public function accessory_detail($id)
170172
{
@@ -273,43 +275,31 @@ public function destroy($id)
273275
* If Slack is enabled and/or asset acceptance is enabled, it will also
274276
* trigger a Slack message and send an email.
275277
*
276-
* @author [A. Gianotto] [<[email protected]>]
277278
* @param int $accessoryId
278-
* @return \Illuminate\Http\RedirectResponse
279+
* @return \Illuminate\Http\JsonResponse
280+
* @author [A. Gianotto] [<[email protected]>]
279281
*/
280-
public function checkout(Request $request, $accessoryId)
282+
public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory)
281283
{
282-
// Check if the accessory exists
283-
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
284-
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
285-
}
286-
287284
$this->authorize('checkout', $accessory);
285+
$accessory->assigned_to = $request->input('assigned_to');
286+
$user = User::find($request->input('assigned_to'));
287+
$accessory->checkout_qty = $request->input('checkout_qty', 1);
288288

289-
290-
if ($accessory->numRemaining() > 0) {
291-
292-
if (! $user = User::find($request->input('assigned_to'))) {
293-
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
294-
}
295-
296-
// Update the accessory data
297-
$accessory->assigned_to = $request->input('assigned_to');
298-
289+
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
299290
$accessory->users()->attach($accessory->id, [
300291
'accessory_id' => $accessory->id,
301292
'created_at' => Carbon::now(),
302293
'user_id' => Auth::id(),
303-
'assigned_to' => $request->get('assigned_to'),
304-
'note' => $request->get('note'),
294+
'assigned_to' => $request->input('assigned_to'),
295+
'note' => $request->input('note'),
305296
]);
306-
307-
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
308-
309-
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
310297
}
311298

312-
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
299+
// Set this value to be able to pass the qty through to the event
300+
event(new CheckoutableCheckedOut($accessory, $user, auth()->user(), $request->input('note')));
301+
302+
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
313303

314304
}
315305

app/Http/Kernel.php

+2
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ class Kernel extends HttpKernel
4343
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
4444
\App\Http\Middleware\AssetCountForSidebar::class,
4545
\Illuminate\Session\Middleware\AuthenticateSession::class,
46+
\Illuminate\Routing\Middleware\SubstituteBindings::class,
4647
],
4748

4849
'api' => [
4950
'auth:api',
51+
\Illuminate\Routing\Middleware\SubstituteBindings::class,
5052
],
5153
];
5254

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use App\Models\Accessory;
6+
use Illuminate\Support\Facades\Gate;
7+
8+
class AccessoryCheckoutRequest extends ImageUploadRequest
9+
{
10+
11+
/**
12+
* Determine if the user is authorized to make this request.
13+
*/
14+
public function authorize(): bool
15+
{
16+
return Gate::allows('checkout', new Accessory);
17+
}
18+
19+
public function prepareForValidation(): void
20+
{
21+
22+
if ($this->accessory) {
23+
24+
$this->diff = ($this->accessory->numRemaining() - $this->checkout_qty);
25+
$this->merge([
26+
'checkout_qty' => $this->checkout_qty ?? 1,
27+
'number_remaining_after_checkout' => (int) ($this->accessory->numRemaining() - $this->checkout_qty),
28+
'number_currently_remaining' => (int) $this->accessory->numRemaining(),
29+
'checkout_difference' => (int) $this->diff,
30+
]);
31+
32+
\Log::debug('---------------------------------------------');
33+
}
34+
35+
}
36+
37+
/**
38+
* Get the validation rules that apply to the request.
39+
*
40+
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
41+
*/
42+
public function rules(): array
43+
{
44+
45+
return array_merge(
46+
[
47+
'assigned_to' => [
48+
'required',
49+
'integer',
50+
'exists:users,id,deleted_at,NULL',
51+
'not_array'
52+
],
53+
54+
'number_remaining_after_checkout' => [
55+
'min:0',
56+
'required',
57+
'integer',
58+
],
59+
60+
'checkout_qty' => [
61+
'integer',
62+
'lte:number_currently_remaining',
63+
'min:1',
64+
],
65+
],
66+
);
67+
}
68+
69+
public function messages(): array
70+
{
71+
$messages = [
72+
'checkout_qty.lte' => trans_choice('admin/accessories/message.checkout.checkout_qty.lte', $this->number_currently_remaining, [
73+
'number_currently_remaining' => $this->number_currently_remaining,
74+
'checkout_qty' => $this->checkout_qty,
75+
]),
76+
];
77+
return $messages;
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use App\Models\Accessory;
6+
use App\Models\Category;
7+
use Illuminate\Support\Facades\Gate;
8+
9+
class StoreAccessoryRequest extends ImageUploadRequest
10+
{
11+
12+
/**
13+
* Determine if the user is authorized to make this request.
14+
*/
15+
public function authorize(): bool
16+
{
17+
return Gate::allows('create', new Accessory);
18+
}
19+
20+
public function prepareForValidation(): void
21+
{
22+
23+
if ($this->category_id) {
24+
if ($category = Category::find($this->category_id)) {
25+
$this->merge([
26+
'category_type' => $category->category_type ?? null,
27+
]);
28+
}
29+
}
30+
31+
}
32+
33+
/**
34+
* Get the validation rules that apply to the request.
35+
*
36+
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
37+
*/
38+
public function rules(): array
39+
{
40+
return array_merge(
41+
['category_type' => 'in:accessory'],
42+
parent::rules(),
43+
);
44+
}
45+
46+
public function messages(): array
47+
{
48+
$messages = ['category_type.in' => trans('admin/accessories/message.invalid_category_type')];
49+
return $messages;
50+
}
51+
52+
public function response(array $errors)
53+
{
54+
return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
55+
}
56+
}

app/Models/Accessory.php

+17-4
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class Accessory extends SnipeModel
6363
'company_id' => 'integer|nullable',
6464
'min_amt' => 'integer|min:0|nullable',
6565
'purchase_cost' => 'numeric|nullable|gte:0',
66-
'purchase_date' => 'date_format:Y-m-d|nullable',
66+
'purchase_date' => 'date_format:Y-m-d|nullable',
6767
];
6868

6969

@@ -329,11 +329,24 @@ public function getEula()
329329
}
330330

331331

332+
/**
333+
* Check how many items within an accessory are checked out
334+
*
335+
* @author [A. Gianotto] [<[email protected]>]
336+
* @since [v5.0]
337+
* @return int
338+
*/
339+
public function numCheckedOut()
340+
{
341+
return $this->users_count ?? $this->users()->count();
342+
}
343+
344+
332345
/**
333346
* Check how many items of an accessory remain.
334347
*
335348
* In order to use this model method, you MUST call withCount('users as users_count')
336-
* on the eloquent query in the controller, otherwise $this->>users_count will be null and
349+
* on the eloquent query in the controller, otherwise $this->users_count will be null and
337350
* bad things happen.
338351
*
339352
* @author [A. Gianotto] [<[email protected]>]
@@ -342,11 +355,11 @@ public function getEula()
342355
*/
343356
public function numRemaining()
344357
{
345-
$checkedout = $this->users_count;
358+
$checkedout = $this->numCheckedOut();
346359
$total = $this->qty;
347360
$remaining = $total - $checkedout;
348361

349-
return (int) $remaining;
362+
return $remaining;
350363
}
351364

352365
/**

0 commit comments

Comments
 (0)