Skip to content

Commit 8b38dc8

Browse files
authored
Merge pull request #15851 from marcusmoore/testing/api-component-checkin
Fixed bug in component checkins via api
2 parents 85c1b0e + f932a4f commit 8b38dc8

File tree

2 files changed

+173
-20
lines changed

2 files changed

+173
-20
lines changed

app/Http/Controllers/Api/ComponentsController.php

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -309,27 +309,21 @@ public function checkout(Request $request, $componentId) : JsonResponse
309309
public function checkin(Request $request, $component_asset_id) : JsonResponse
310310
{
311311
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
312-
313312
if (is_null($component = Component::find($component_assets->component_id))) {
314-
315313
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.not_found')));
316314
}
317315

318316
$this->authorize('checkin', $component);
319317

320318
$max_to_checkin = $component_assets->assigned_qty;
321319

322-
if ($max_to_checkin > 1) {
323-
324-
$validator = Validator::make($request->all(), [
325-
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
326-
]);
327-
328-
if ($validator->fails()) {
329-
return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and '.$max_to_checkin));
330-
}
320+
$validator = Validator::make($request->all(), [
321+
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
322+
]);
323+
324+
if ($validator->fails()) {
325+
return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and ' . $max_to_checkin));
331326
}
332-
333327

334328
// Validation passed, so let's figure out what we have to do here.
335329
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty', 1));
@@ -339,28 +333,23 @@ public function checkin(Request $request, $component_asset_id) : JsonResponse
339333
$component_assets->assigned_qty = $qty_remaining_in_checkout;
340334

341335
Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
342-
343-
DB::table('components_assets')->where('id',
344-
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
336+
337+
DB::table('components_assets')->where('id', $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
345338

346339
// If the checked-in qty is exactly the same as the assigned_qty,
347340
// we can simply delete the associated components_assets record
348-
if ($qty_remaining_in_checkout == 0) {
341+
if ($qty_remaining_in_checkout === 0) {
349342
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
350343
}
351-
352344

353345
$asset = Asset::find($component_assets->asset_id);
354346

355347
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
356348

357349
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success')));
358-
359350
}
360351

361352
return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record'));
362-
363-
364353
}
365354

366355
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
namespace Tests\Feature\Checkins\Api;
4+
5+
use App\Events\CheckoutableCheckedIn;
6+
use App\Models\Asset;
7+
use App\Models\Company;
8+
use App\Models\Component;
9+
use App\Models\User;
10+
use Illuminate\Support\Facades\Event;
11+
use Tests\Concerns\TestsFullMultipleCompaniesSupport;
12+
use Tests\Concerns\TestsPermissionsRequirement;
13+
use Tests\TestCase;
14+
15+
class ComponentCheckinTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement
16+
{
17+
public function testRequiresPermission()
18+
{
19+
$component = Component::factory()->checkedOutToAsset()->create();
20+
21+
$this->actingAsForApi(User::factory()->create())
22+
->postJson(route('api.components.checkin', $component->assets->first()->pivot->id))
23+
->assertForbidden();
24+
}
25+
26+
public function testHandlesNonExistentPivotId()
27+
{
28+
$this->actingAsForApi(User::factory()->checkinComponents()->create())
29+
->postJson(route('api.components.checkin', 1000), [
30+
'checkin_qty' => 1,
31+
])
32+
->assertOk()
33+
->assertStatusMessageIs('error');
34+
}
35+
36+
public function testHandlesNonExistentComponent()
37+
{
38+
$component = Component::factory()->checkedOutToAsset()->create();
39+
$pivotId = $component->assets->first()->pivot->id;
40+
$component->delete();
41+
42+
$this->actingAsForApi(User::factory()->checkinComponents()->create())
43+
->postJson(route('api.components.checkin', $pivotId), [
44+
'checkin_qty' => 1,
45+
])
46+
->assertOk()
47+
->assertStatusMessageIs('error');
48+
}
49+
50+
public function testCannotCheckinMoreThanCheckedOut()
51+
{
52+
$component = Component::factory()->checkedOutToAsset()->create();
53+
54+
$pivot = $component->assets->first()->pivot;
55+
$pivot->update(['assigned_qty' => 1]);
56+
57+
$this->actingAsForApi(User::factory()->checkinComponents()->create())
58+
->postJson(route('api.components.checkin', $component->assets->first()->pivot->id), [
59+
'checkin_qty' => 3,
60+
])
61+
->assertOk()
62+
->assertStatusMessageIs('error');
63+
}
64+
65+
public function testCanCheckinComponent()
66+
{
67+
Event::fake([CheckoutableCheckedIn::class]);
68+
69+
$user = User::factory()->checkinComponents()->create();
70+
71+
$component = Component::factory()->checkedOutToAsset()->create();
72+
$pivot = $component->assets->first()->pivot;
73+
$pivot->update(['assigned_qty' => 3]);
74+
75+
76+
$this->actingAsForApi($user)
77+
->postJson(route('api.components.checkin', $component->assets->first()->pivot->id), [
78+
'checkin_qty' => 2,
79+
'note' => 'my note',
80+
])
81+
->assertOk()
82+
->assertStatusMessageIs('success');
83+
84+
$this->assertEquals(1, $component->fresh()->assets->first()->pivot->assigned_qty);
85+
86+
Event::assertDispatched(function (CheckoutableCheckedIn $event) use ($user, $component) {
87+
return $event->checkoutable->is($component)
88+
&& $event->checkedOutTo->is($component->assets->first())
89+
&& $event->checkedInBy->is($user)
90+
&& $event->note === 'my note';
91+
});
92+
}
93+
94+
public function testCheckingInEntireAssignedQuantityClearsThePivotRecordFromTheDatabase()
95+
{
96+
Event::fake([CheckoutableCheckedIn::class]);
97+
98+
$user = User::factory()->checkinComponents()->create();
99+
100+
$component = Component::factory()->checkedOutToAsset()->create();
101+
$pivot = $component->assets->first()->pivot;
102+
$pivot->update(['assigned_qty' => 3]);
103+
104+
$this->actingAsForApi($user)
105+
->postJson(route('api.components.checkin', $component->assets->first()->pivot->id), [
106+
'checkin_qty' => 3,
107+
'note' => 'my note',
108+
])
109+
->assertOk()
110+
->assertStatusMessageIs('success');
111+
112+
$this->assertEmpty($component->fresh()->assets);
113+
114+
Event::assertDispatched(function (CheckoutableCheckedIn $event) use ($user, $component) {
115+
return $event->checkoutable->is($component)
116+
&& $event->checkedOutTo->is($component->assets->first())
117+
&& $event->checkedInBy->is($user)
118+
&& $event->note === 'my note';
119+
});
120+
}
121+
122+
public function testAdheresToFullMultipleCompaniesSupportScoping()
123+
{
124+
$this->settings->enableMultipleFullCompanySupport();
125+
126+
[$companyA, $companyB] = Company::factory()->count(2)->create();
127+
128+
$componentInCompanyA = Component::factory()->for($companyA)->checkedOutToAsset()->create();
129+
$userInCompanyB = User::factory()->for($companyB)->create();
130+
$pivotId = $componentInCompanyA->assets->first()->pivot->id;
131+
132+
$this->actingAsForApi($userInCompanyB)
133+
->postJson(route('api.components.checkin', $pivotId), [
134+
'checkin_qty' => 1,
135+
])
136+
->assertOk()
137+
->assertStatusMessageIs('error');
138+
}
139+
140+
public function testCheckinIsLogged()
141+
{
142+
$user = User::factory()->checkinComponents()->create();
143+
144+
$component = Component::factory()->checkedOutToAsset()->create();
145+
$pivot = $component->assets->first()->pivot;
146+
$pivot->update(['assigned_qty' => 3]);
147+
148+
$this->actingAsForApi($user)
149+
->postJson(route('api.components.checkin', $component->assets->first()->pivot->id), [
150+
'checkin_qty' => 3,
151+
'note' => 'my note',
152+
]);
153+
154+
$this->assertDatabaseHas('action_logs', [
155+
'created_by' => $user->id,
156+
'action_type' => 'checkin from',
157+
'target_id' => $component->assets->first()->id,
158+
'target_type' => Asset::class,
159+
'note' => 'my note',
160+
'item_id' => $component->id,
161+
'item_type' => Component::class,
162+
]);
163+
}
164+
}

0 commit comments

Comments
 (0)