diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php
new file mode 100644
index 0000000000000000000000000000000000000000..05cdd989512a7a8a5c8e18c0e984778eb6664de6
--- /dev/null
+++ b/app/Http/Controllers/Api/LicenseSeatsController.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Helpers\Helper;
+use App\Http\Controllers\Controller;
+use App\Http\Transformers\LicenseSeatsTransformer;
+use App\Models\Asset;
+use App\Models\License;
+use App\Models\LicenseSeat;
+use App\Models\User;
+use Auth;
+use Illuminate\Http\Request;
+
+class LicenseSeatsController extends Controller
+{
+ /**
+ * Display a listing of the resource.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function index(Request $request, $licenseId)
+ {
+ //
+ if ($license = License::find($licenseId)) {
+ $this->authorize('view', $license);
+
+ $seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
+ ->where('license_seats.license_id', $licenseId);
+
+ $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
+
+ if ($request->input('sort')=='department') {
+ $seats->OrderDepartments($order);
+ } else {
+ $seats->orderBy('id', $order);
+ }
+
+ $total = $seats->count();
+ $offset = (($seats) && (request('offset') > $total)) ? 0 : request('offset', 0);
+ $limit = request('limit', 50);
+
+ $seats = $seats->skip($offset)->take($limit)->get();
+
+ if ($seats) {
+ return (new LicenseSeatsTransformer)->transformLicenseSeats($seats, $total);
+ }
+ }
+
+ return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.does_not_exist')), 200);
+ }
+
+ /**
+ * Display the specified resource.
+ *
+ * @param int $id
+ * @return \Illuminate\Http\Response
+ */
+ public function show($licenseId, $seatId)
+ {
+ //
+ $this->authorize('view', License::class);
+ // sanity checks:
+ // 1. does the license seat exist?
+ if (!$licenseSeat = LicenseSeat::find($seatId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
+ }
+ // 2. does the seat belong to the specified license?
+ if (!$license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
+ }
+ return (new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat);
+ }
+
+ /**
+ * Update the specified resource in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param int $licenseId
+ * @param int $seatId
+ * @return \Illuminate\Http\Response
+ */
+ public function update(Request $request, $licenseId, $seatId)
+ {
+ $this->authorize('checkout', License::class);
+
+ // sanity checks:
+ // 1. does the license seat exist?
+ if (!$licenseSeat = LicenseSeat::find($seatId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
+ }
+ // 2. does the seat belong to the specified license?
+ if (!$license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) {
+ return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
+ }
+
+ $oldUser = $licenseSeat->user()->first();
+ $oldAsset = $licenseSeat->asset()->first();
+
+ // attempt to update the license seat
+ $licenseSeat->fill($request->all());
+ $licenseSeat->user_id = Auth::user()->id;
+
+ // check if this update is a checkin operation
+ // 1. are relevant fields touched at all?
+ $touched = $licenseSeat->isDirty('assigned_to') || $licenseSeat->isDirty('asset_id');
+ // 2. are they cleared? if yes then this is a checkin operation
+ $is_checkin = ($touched && $licenseSeat->assigned_to === null && $licenseSeat->asset_id === null);
+
+ if (!$touched) {
+ // nothing to update
+ return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
+ }
+
+ if ($licenseSeat->save()) {
+ // the logging functions expect only one "target". if both asset and user are present in the request,
+ // we simply let assets take precedence over users...
+ $changes = $licenseSeat->getChanges();
+ if (array_key_exists('assigned_to', $changes)) {
+ $target = $is_checkin ? $oldUser : User::find($changes['assigned_to']);
+ }
+ if (array_key_exists('asset_id', $changes)) {
+ $target = $is_checkin ? $oldAsset : Asset::find($changes['asset_id']);
+ }
+
+ if ($is_checkin) {
+ $licenseSeat->logCheckin($target, $request->input('note'));
+ return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
+ }
+
+ // in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation.
+ $licenseSeat->logCheckout($request->input('note'), $target);
+ return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
+ }
+
+ return Helper::formatStandardApiResponse('error', null, $licenseSeat->getErrors());
+ }
+}
diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php
index 07bacdc4de216b231a7ba107d8382b4c830e1838..268248ab775028af10e08d2d88a46fe9dbdeb0cd 100644
--- a/app/Http/Controllers/Api/LicensesController.php
+++ b/app/Http/Controllers/Api/LicensesController.php
@@ -237,50 +237,6 @@ class LicensesController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.assoc_users')));
}
-
- /**
- * Get license seat listing
- *
- * @author [A. Gianotto] [<snipe@snipe.net>]
- * @since [v1.0]
- * @param int $licenseId
- * @return \Illuminate\Contracts\View\View
- */
- public function seats(Request $request, $licenseId)
- {
-
- if ($license = License::find($licenseId)) {
-
- $this->authorize('view', $license);
-
- $seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
- ->where('license_seats.license_id', $licenseId);
-
- $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
-
- if ($request->input('sort')=='department') {
- $seats->OrderDepartments($order);
- } else {
- $seats->orderBy('id', $order);
- }
-
- $offset = (($seats) && (request('offset') > $seats->count())) ? 0 : request('offset', 0);
- $limit = request('limit', 50);
-
- $total = $seats->count();
-
- $seats = $seats->skip($offset)->take($limit)->get();
-
- if ($seats) {
- return (new LicenseSeatsTransformer)->transformLicenseSeats($seats, $total);
- }
-
- }
-
- return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.does_not_exist')), 200);
-
- }
-
/**
* Gets a paginated collection for the select2 menus
diff --git a/app/Http/Transformers/LicenseSeatsTransformer.php b/app/Http/Transformers/LicenseSeatsTransformer.php
index f7685db16739459bc560375654b85ae17388818b..c8454d06e7b22b0e5f7f870bb058dbc29083a31c 100644
--- a/app/Http/Transformers/LicenseSeatsTransformer.php
+++ b/app/Http/Transformers/LicenseSeatsTransformer.php
@@ -20,12 +20,11 @@ class LicenseSeatsTransformer
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
- public function transformLicenseSeat (LicenseSeat $seat, $seat_count)
+ public function transformLicenseSeat (LicenseSeat $seat, $seat_count=0)
{
$array = [
'id' => (int) $seat->id,
'license_id' => (int) $seat->license->id,
- 'name' => 'Seat '.$seat_count,
'assigned_user' => ($seat->user) ? [
'id' => (int) $seat->user->id,
'name'=> e($seat->user->present()->fullName),
@@ -49,6 +48,10 @@ class LicenseSeatsTransformer
'user_can_checkout' => (($seat->assigned_to=='') && ($seat->asset_id=='')),
];
+ if($seat_count != 0) {
+ $array['name'] = 'Seat '.$seat_count;
+ }
+
$permissions_array['available_actions'] = [
'checkout' => Gate::allows('checkout', License::class),
'checkin' => Gate::allows('checkin', License::class),
diff --git a/app/Models/LicenseSeat.php b/app/Models/LicenseSeat.php
index 590409f772fa97703ee42fb2188618567d2ced1e..40a53adf860835691b8f9a7f9385d876035521e3 100755
--- a/app/Models/LicenseSeat.php
+++ b/app/Models/LicenseSeat.php
@@ -20,6 +20,16 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
protected $guarded = 'id';
protected $table = 'license_seats';
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var array
+ */
+ protected $fillable = [
+ 'assigned_to',
+ 'asset_id'
+ ];
+
use Acceptable;
public function getCompanyableParents()
diff --git a/resources/views/licenses/view.blade.php b/resources/views/licenses/view.blade.php
index a578efc0a3f3496b32cab495d3df24475d44de32..80de54d956ae3fad4213338ec14aba7b901bb01f 100755
--- a/resources/views/licenses/view.blade.php
+++ b/resources/views/licenses/view.blade.php
@@ -350,7 +350,7 @@
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
- data-url="{{ route('api.license.seats', $license->id) }}"
+ data-url="{{ route('api.licenses.seats.index', $license->id) }}"
data-export-options='{
"fileName": "export-seats-{{ str_slug($license->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
diff --git a/routes/api.php b/routes/api.php
index 2c6a6ab26a97370fb578ca3ee08457fe37c21768..c8f05d2f1925ec52df1a34fb8f88c5b4e44510c4 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -166,7 +166,6 @@ Route::group(['prefix' => 'v1','namespace' => 'Api', 'middleware' => 'auth:api']
/*--- Departments API ---*/
- /*--- Suppliers API ---*/
Route::group(['prefix' => 'departments'], function () {
@@ -496,11 +495,6 @@ Route::group(['prefix' => 'v1','namespace' => 'Api', 'middleware' => 'auth:api']
/*--- Licenses API ---*/
Route::group(['prefix' => 'licenses'], function () {
- Route::get('{licenseId}/seats', [
- 'as' => 'api.license.seats',
- 'uses' => 'LicensesController@seats'
- ]);
-
Route::get('selectlist',
[
'as' => 'api.licenses.selectlist',
@@ -525,7 +519,18 @@ Route::group(['prefix' => 'v1','namespace' => 'Api', 'middleware' => 'auth:api']
]
); // Licenses resource
-
+ Route::resource('licenses.seats', 'LicenseSeatsController',
+ [
+ 'names' =>
+ [
+ 'index' => 'api.licenses.seats.index',
+ 'show' => 'api.licenses.seats.show',
+ 'update' => 'api.licenses.seats.update'
+ ],
+ 'except' => ['create', 'edit', 'destroy', 'store'],
+ 'parameters' => ['licenseseat' => 'licenseseat_id']
+ ]
+ ); // Licenseseats resource
/*--- Locations API ---*/
diff --git a/tests/api/ApiLicenseSeatsCest.php b/tests/api/ApiLicenseSeatsCest.php
new file mode 100644
index 0000000000000000000000000000000000000000..30c9ccaa9221de69a173dd4f5822793f4f83af4b
--- /dev/null
+++ b/tests/api/ApiLicenseSeatsCest.php
@@ -0,0 +1,185 @@
+<?php
+
+use App\Http\Transformers\LicenseSeatsTransformer;
+use App\Models\Asset;
+use App\Models\License;
+use App\Models\LicenseSeat;
+use App\Models\User;
+
+class ApiLicenseSeatsCest
+{
+ protected $license;
+ protected $timeFormat;
+
+ public function _before(ApiTester $I)
+ {
+ $this->user = \App\Models\User::find(1);
+ $I->haveHttpHeader('Accept', 'application/json');
+ $I->amBearerAuthenticated($I->getToken($this->user));
+ }
+
+ /** @test */
+ public function indexLicenseSeats(ApiTester $I)
+ {
+ $I->wantTo('Get a list of license seats for a specific license');
+
+ // call
+ $I->sendGET('/licenses/1/seats?limit=10&order=desc');
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+
+ // sample verify
+ $licenseSeats = App\Models\LicenseSeat::where('license_id', 1)
+ ->orderBy('id','desc')->take(10)->get();
+ // pick a random seat
+ $licenseSeat = $licenseSeats->random();
+ // need the index in the original list so that the "name" field is determined correctly
+ $licenseSeatNumber = 0;
+ foreach($licenseSeats as $index=>$seat) {
+ if ($licenseSeat === $seat) {
+ $licenseSeatNumber = $index+1;
+ }
+ }
+ $I->seeResponseContainsJson($I->removeTimestamps((new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat, $licenseSeatNumber)));
+ }
+
+ /** @test */
+ public function showLicenseSeat(ApiTester $I)
+ {
+ $I->wantTo('Get a license seat');
+
+ // call
+ $I->sendGET('/licenses/1/seats/10');
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+
+ // sample verify
+ $licenseSeat = App\Models\LicenseSeat::findOrFail(10);
+ $I->seeResponseContainsJson($I->removeTimestamps((new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat)));
+ }
+
+ /** @test */
+ public function checkoutLicenseSeatToUser(ApiTester $I)
+ {
+ $I->wantTo('Checkout a license seat to a user');
+
+ $user = App\Models\User::all()->random();
+ $licenseSeat = App\Models\LicenseSeat::all()->random();
+ $endpoint = '/licenses/'.$licenseSeat->license_id.'/seats/'.$licenseSeat->id;
+
+ $data = [
+ 'assigned_to' => $user->id,
+ 'note' => 'Test Checkout to User via API'
+ ];
+
+ // update
+ $I->sendPATCH($endpoint, $data);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+
+ $response = json_decode($I->grabResponse());
+ $I->assertEquals('success', $response->status);
+ $I->assertEquals(trans('admin/licenses/message.update.success'), $response->messages);
+ $I->assertEquals($licenseSeat->license_id, $response->payload->license_id); // license id does not change
+ $I->assertEquals($licenseSeat->id, $response->payload->id); // license seat id does not change
+
+ // verify
+ $licenseSeat = $licenseSeat->fresh();
+ $I->sendGET($endpoint);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+ $I->seeResponseContainsJson($I->removeTimestamps((new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat)));
+
+ // verify that the last logged action is a checkout
+ $I->sendGET('/reports/activity?item_type=license&limit=1&item_id='.$licenseSeat->license_id);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+ $I->seeResponseContainsJson([
+ "action_type" => "checkout"
+ ]);
+ }
+
+ /** @test */
+ public function checkoutLicenseSeatToAsset(ApiTester $I)
+ {
+ $I->wantTo('Checkout a license seat to an asset');
+
+ $asset = App\Models\Asset::all()->random();
+ $licenseSeat = App\Models\LicenseSeat::all()->random();
+ $endpoint = '/licenses/'.$licenseSeat->license_id.'/seats/'.$licenseSeat->id;
+
+ $data = [
+ 'asset_id' => $asset->id,
+ 'note' => 'Test Checkout to Asset via API'
+ ];
+
+ // update
+ $I->sendPATCH($endpoint, $data);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+
+ $response = json_decode($I->grabResponse());
+ $I->assertEquals('success', $response->status);
+ $I->assertEquals(trans('admin/licenses/message.update.success'), $response->messages);
+ $I->assertEquals($licenseSeat->license_id, $response->payload->license_id); // license id does not change
+ $I->assertEquals($licenseSeat->id, $response->payload->id); // license seat id does not change
+
+ // verify
+ $licenseSeat = $licenseSeat->fresh();
+ $I->sendGET($endpoint);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+ $I->seeResponseContainsJson($I->removeTimestamps((new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat)));
+
+ // verify that the last logged action is a checkout
+ $I->sendGET('/reports/activity?item_type=license&limit=1&item_id='.$licenseSeat->license_id);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+ $I->seeResponseContainsJson([
+ "action_type" => "checkout"
+ ]);
+ }
+
+ /** @test */
+ public function checkoutLicenseSeatToUserAndAsset(ApiTester $I)
+ {
+ $I->wantTo('Checkout a license seat to a user AND an asset');
+
+ $asset = App\Models\Asset::all()->random();
+ $user = App\Models\User::all()->random();
+ $licenseSeat = App\Models\LicenseSeat::all()->random();
+ $endpoint = '/licenses/'.$licenseSeat->license_id.'/seats/'.$licenseSeat->id;
+
+ $data = [
+ 'asset_id' => $asset->id,
+ 'assigned_to' => $user->id,
+ 'note' => 'Test Checkout to User and Asset via API'
+ ];
+
+ // update
+ $I->sendPATCH($endpoint, $data);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+
+ $response = json_decode($I->grabResponse());
+ $I->assertEquals('success', $response->status);
+ $I->assertEquals(trans('admin/licenses/message.update.success'), $response->messages);
+ $I->assertEquals($licenseSeat->license_id, $response->payload->license_id); // license id does not change
+ $I->assertEquals($licenseSeat->id, $response->payload->id); // license seat id does not change
+
+ // verify
+ $licenseSeat = $licenseSeat->fresh();
+ $I->sendGET($endpoint);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+ $I->seeResponseContainsJson($I->removeTimestamps((new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat)));
+
+ // verify that the last logged action is a checkout
+ $I->sendGET('/reports/activity?item_type=license&limit=1&item_id='.$licenseSeat->license_id);
+ $I->seeResponseIsJson();
+ $I->seeResponseCodeIs(200);
+ $I->seeResponseContainsJson([
+ "action_type" => "checkout"
+ ]);
+ }
+}