diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php index 3194d40c77f8..e10f1c7b7fee 100644 --- a/app/Http/Controllers/Api/ComponentsController.php +++ b/app/Http/Controllers/Api/ComponentsController.php @@ -4,7 +4,9 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; +use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\ComponentsTransformer; +use App\Http\Transformers\ComponentsAssetsTransformer; use App\Models\Component; use App\Models\Company; use App\Helpers\Helper; @@ -131,4 +133,26 @@ public function destroy($id) return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.delete.success'))); } + /** + * Display all assets attached to a component + * + * @author [A. Bergamasco] [@vjandrea] + * @since [v4.0] + * @param Request $request + * @param int $id + * @return \Illuminate\Http\Response + */ + public function getAssets(Request $request, $id) + { + $this->authorize('index', Asset::class); + + $component = Component::findOrFail($id); + $assets = $component->assets(); + + $offset = request('offset', 0); + $limit = $request->input('limit', 50); + $total = $assets->count(); + $assets = $assets->skip($offset)->take($limit)->get(); + return (new ComponentsAssetsTransformer)->transformAssets($assets, $total); + } } diff --git a/app/Http/Transformers/ComponentsAssetsTransformer.php b/app/Http/Transformers/ComponentsAssetsTransformer.php new file mode 100644 index 000000000000..65054b4209fe --- /dev/null +++ b/app/Http/Transformers/ComponentsAssetsTransformer.php @@ -0,0 +1,54 @@ +transformDatatables($array, $total); + } + + + public function transformAsset (Asset $asset) + { + $array = [ + 'id' => $asset->id, + 'name' => e($asset->name), + 'created_at' => $asset->created_at->format('Y-m-d'), + 'qty' => $asset->components()->count(), + 'can_checkout' => $asset->availableForCheckout(), + ]; + + $permissions_array['available_actions'] = [ + 'checkout' => Gate::allows('checkout', Asset::class) ? true : false, + 'checkin' => Gate::allows('checkin', Asset::class) ? true : false, + 'update' => Gate::allows('update', Asset::class) ? true : false, + 'delete' => Gate::allows('delete', Asset::class) ? true : false, + ]; + + $array += $permissions_array; + + if ($asset->model->fieldset) { + foreach ($asset->model->fieldset->fields as $field) { + $fields_array = [$field->name => $asset->{$field->convertUnicodeDbSlug()}]; + $array += $fields_array; + } + } + + return $array; + } + + public function transformAssetsDatatable ($assets) { + return (new DatatablesTransformer)->transformDatatables($assets); + } +} diff --git a/resources/views/components/view.blade.php b/resources/views/components/view.blade.php index 18c327db0d28..5b1f96198dcd 100644 --- a/resources/views/components/view.blade.php +++ b/resources/views/components/view.blade.php @@ -49,14 +49,14 @@ name="component_users" class="table table-striped snipe-table" id="table" - data-url="{{route('api.components.show', $component->id)}}" + data-url="{{route('api.components.assets', $component->id)}}" data-cookie="true" data-click-to-select="true" data-cookie-id-table="componentDetailTable-{{ config('version.hash_version') }}" > - {{ trans('general.asset') }} + {{ trans('general.asset') }} {{ trans('general.qty') }} {{ trans('general.date') }} diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index 2812cebcd379..ad7b2795fb89 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -84,7 +84,7 @@ @if ($asset->company) {{ trans('general.company') }} - {{ $asset->company->name }} + {{ $asset->company->name }} @endif diff --git a/resources/views/settings/api.blade.php b/resources/views/settings/api.blade.php index f176921fafc0..ae66bcfe43cf 100644 --- a/resources/views/settings/api.blade.php +++ b/resources/views/settings/api.blade.php @@ -11,6 +11,9 @@ @if (!config('app.lock_passwords')) + @if(env('APP_ENV') != 'production') + + @endif @else

{{ trans('general.feature_disabled') }}

@endif diff --git a/routes/api.php b/routes/api.php index de210c132590..3e534d76ad68 100644 --- a/routes/api.php +++ b/routes/api.php @@ -142,6 +142,11 @@ ] ); + Route::get('components/{id}/assets', [ + 'as' =>'api.components.assets', + 'uses' => 'ComponentsController@getAssets', + ]); + Route::resource('suppliers', 'SuppliersController', ['names' => diff --git a/tests/_support/ApiTester.php b/tests/_support/ApiTester.php new file mode 100644 index 000000000000..c53fda8f8acd --- /dev/null +++ b/tests/_support/ApiTester.php @@ -0,0 +1,46 @@ +createPersonalAccessClient($user->id, 'Codeception API Test Client', + 'http://localhost/'); + + \Illuminate\Support\Facades\DB::table('oauth_personal_access_clients')->insert([ + 'client_id' => $client->id, + 'created_at' => new DateTime, + 'updated_at' => new DateTime, + ]); + + $user->permissions = json_encode(['superuser' => true]); + $user->save(); + + $token = $user->createToken('CodeceptionAPItestToken')->accessToken; + + return $token; + } +} diff --git a/tests/_support/Helper/Api.php b/tests/_support/Helper/Api.php new file mode 100644 index 000000000000..7a4621e8542c --- /dev/null +++ b/tests/_support/Helper/Api.php @@ -0,0 +1,10 @@ +faker = \Faker\Factory::create(); + $this->user = \App\Models\User::find(1); + + $I->amBearerAuthenticated($I->getToken($this->user)); + } + + /** @test */ + public function indexComponentsAssets(ApiTester $I) + { + $I->wantTo('Get a list of assets related to a component'); + + // generate + $component = factory(\App\Models\Component::class, 'component') + ->create(['user_id' => $this->user->id, 'qty' => 20]); + + $assets = factory(\App\Models\Asset::class, 'asset', 2) + ->create(['user_id' => $this->user->id]) + ->each(function ($asset) use ($component) { + $component->assets()->attach($component->id, [ + 'component_id' => $component->id, + 'user_id' => $this->user->id, + 'created_at' => date('Y-m-d H:i:s'), + 'assigned_qty' => 2, + 'asset_id' => $asset->id + ]); + }); + + $I->sendGET('/components/' . $component->id . '/assets/'); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + $response = json_decode($I->grabResponse()); + + $I->assertEquals(2, $response->total); + $I->assertInstanceOf(\Illuminate\Database\Eloquent\Collection::class, $assets); + + $I->seeResponseContainsJson(['rows' => [ + 0 => [ + 'name' => $assets[0]->name, + 'id' => $assets[0]->id, + 'created_at' => $assets[0]->created_at->format('Y-m-d'), + ], + 1 => [ + 'name' => $assets[1]->name, + 'id' => $assets[1]->id, + 'created_at' => $assets[1]->created_at->format('Y-m-d'), + ], + ] + ]); + } +} diff --git a/tests/api/ApiComponentsCest.php b/tests/api/ApiComponentsCest.php new file mode 100644 index 000000000000..a0b311142dd0 --- /dev/null +++ b/tests/api/ApiComponentsCest.php @@ -0,0 +1,182 @@ +faker = \Faker\Factory::create(); + $this->user = \App\Models\User::find(1); + + $I->amBearerAuthenticated($I->getToken($this->user)); + } + + /** @test */ + public function indexComponents(ApiTester $I) + { + $I->wantTo('Get a list of components'); + + // setup + $components = factory(\App\Models\Component::class, 'component', 10)->create(['user_id' => $this->user->id]); + + // call + $I->sendGET('/components'); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + // sample verify + $component = $components->random(); + $I->seeResponseContainsJson([ + 'name' => $component->name, + 'qty' => $component->qty, + ]); + } + + /** @test */ + public function createComponent(ApiTester $I) + { + $I->wantTo('Create a new component'); + + // setup + $category = factory(\App\Models\Category::class, 'category')->create(['user_id' => $this->user->id]); + $location = factory(\App\Models\Location::class, 'location')->create(['user_id' => $this->user->id]); + $company = factory(\App\Models\Company::class, 'company')->create(); + + $data = [ + 'category_id' => $category->id, + 'company_id' => $company->id, + 'location_id' => $location->id, + 'name' => $this->faker->sentence(3), + 'purchase_cost' => $this->faker->randomFloat(2, 0), + 'purchase_date' => $this->faker->dateTime->format('Y-m-d'), + 'qty' => rand(1, 10), + ]; + + // create + $I->sendPOST('/components', $data); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + $response = json_decode($I->grabResponse()); + $id = $response->payload->id; + + $I->assertEquals('success', $response->status); + + // verify + $I->sendGET('/components/' . $id); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + $I->seeResponseContainsJson([ + 'id' => $id, + 'category' => [ + 'id' => $data['category_id'], + 'name' => $category->name, + ], + 'company' => [ + 'id' => $data['company_id'], + 'name' => $company->name, + ], + 'location' => [ + 'id' => $data['location_id'], + 'name' => $location->name, + ], + 'name' => $data['name'], + 'qty' => $data['qty'], + 'purchase_cost' => $data['purchase_cost'], + 'purchase_date' => $data['purchase_date'], + ]); + } + + /** @test */ + public function updateComponentWithPatch(ApiTester $I) + { + $I->wantTo('Update a component with PATCH'); + + // create + $component = factory(\App\Models\Component::class, 'component')->create(); + $I->assertInstanceOf(\App\Models\Component::class, $component); + + $data = [ + 'name' => $this->faker->sentence(3), + 'qty' => $this->faker->randomDigit + 1, + ]; + + $I->assertNotEquals($component->name, $data['name']); + + // update + $I->sendPATCH('/components/' . $component->id, $data); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + $response = json_decode($I->grabResponse()); + $I->assertEquals('success', $response->status); + + // verify + $I->sendGET('/components/' . $component->id); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + $I->seeResponseContainsJson([ + 'name' => $data['name'], + 'id' => $component->id, + 'qty' => $data['qty'], + ]); + } + + /** @test */ + public function updateComponentWithPut(ApiTester $I) + { + $I->wantTo('Update a component with PUT'); + + // create + $component = factory(\App\Models\Component::class, 'component')->create(); + $I->assertInstanceOf(\App\Models\Component::class, $component); + + $data = [ + 'name' => $this->faker->sentence(3), + ]; + + $I->assertNotEquals($component->name, $data['name']); + + // update + $I->sendPUT('/components/' . $component->id, $data); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + $response = json_decode($I->grabResponse()); + $I->assertEquals('success', $response->status); + + // verify + $I->sendGET('/components/' . $component->id); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + $I->seeResponseContainsJson([ + 'name' => e($data['name']), + 'id' => e($component->id), + 'qty' => e($component->qty), + ]); + } + + /** @test */ + public function deleteComponentTest(ApiTester $I) + { + $I->wantTo('Delete a component'); + + // create + $component = factory(\App\Models\Component::class, 'component')->create(); + $I->assertInstanceOf(\App\Models\Component::class, $component); + + // delete + $I->sendDELETE('/components/' . $component->id); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + // verify, expect a 404 + $I->sendGET('/components/' . $component->id); + $I->seeResponseCodeIs(404); + // $I->seeResponseIsJson(); // @todo: response is not JSON + } +} diff --git a/tests/api/_bootstrap.php b/tests/api/_bootstrap.php new file mode 100644 index 000000000000..8a885558065a --- /dev/null +++ b/tests/api/_bootstrap.php @@ -0,0 +1,2 @@ +