From 3ea93afafd043ebc654a53ffe77e695d7868dcd8 Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 26 Feb 2024 12:42:35 +0000 Subject: [PATCH 01/34] WIP DEvices API. --- app/Http/Controllers/API/DeviceController.php | 353 ++++++++++++++++++ app/Http/Controllers/API/ItemController.php | 2 +- app/Http/Resources/Device.php | 184 +++++++++ app/Http/Resources/Item.php | 2 +- routes/api.php | 6 + tests/Feature/Devices/APIv2DeviceTest.php | 127 +++++++ 6 files changed, 672 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/API/DeviceController.php create mode 100644 app/Http/Resources/Device.php create mode 100644 tests/Feature/Devices/APIv2DeviceTest.php diff --git a/app/Http/Controllers/API/DeviceController.php b/app/Http/Controllers/API/DeviceController.php new file mode 100644 index 0000000000..5ba6fb0d88 --- /dev/null +++ b/app/Http/Controllers/API/DeviceController.php @@ -0,0 +1,353 @@ +getUser(); + + list($partyid, + $category, + $item_type, + $brand, + $model, + $age, + $estimate, + $problem, + $notes, + $case_study, + $repair_status, + $spare_parts, + $parts_provider, + $professional_help, + $more_time_needed, + $do_it_yourself, + $barrier + ) = $this->validateDeviceParams($request,true); + + Party::findOrFail($partyid); + + if (!Fixometer::userHasEditEventsDevicesPermission($partyid, $user->id)) { + // Only hosts can add devices to events. + abort(403); + } + + $data = [ + 'event' => $partyid, + 'category' => $category, + 'item_type' => $item_type, + 'brand' => $brand, + 'model' => $model, + 'age' => $age, + 'estimate' => $estimate, + 'problem' => $problem, + 'notes' => $notes, + 'wiki' => $case_study, + 'repair_status' => $repair_status, + 'spare_parts' => $spare_parts, + 'parts_provider' => $parts_provider, + 'professional_help' => $professional_help, + 'more_time_needed' => $more_time_needed, + 'do_it_yourself' => $do_it_yourself, + 'repaired_by' => $user->id, + ]; + + $device = Device::create($data); + $idDevice = $device->iddevices; + + if ($idDevice) { + event(new DeviceCreatedOrUpdated($device)); + + if ($barrier) { + DeviceBarrier::create([ + 'device_id' => $idDevice, + 'barrier_id' => $barrier + ]); + } + } + + // TODO Images - probably a separate API Call. + + return response()->json([ + 'id' => $idDevice, + ]); + } + + private function validateDeviceParams(Request $request, $create): array + { + // We don't validate max lengths of other strings, to avoid duplicating the length information both here + // and in the migrations. If we wanted to do that we should extract the length dynamically from the + // schema, which is possible but not trivial. + if ($create) { + $request->validate([ + 'eventid' => 'required|integer', + 'item_type' => 'required|string', + 'category' => 'required|integer', + 'brand' => 'string', + 'model' => 'string', + 'age' => [ 'integer', 'max:500' ], + 'estimate' => [ 'numeric', 'min:0' ], + 'problem' => 'string', + 'notes' => 'string', + 'repair_status' => [ 'string', 'in:Fixed,Repairable,End-of-life' ], + 'next_steps' => [ 'string', 'in:More time needed,Professional help,Do it yourself' ], + 'spare_parts' => [ 'string', 'in:No,Manufacturer,Third party' ], + 'case_study' => ['boolean'], + 'barrier' => [ 'string', 'in:Spare parts not available,Spare parts too expensive,No way to open the product,Repair information not available,Lack of equipment' ], + ]); + } else { + $request->validate([ + 'item_type' => 'required|string', + 'category' => 'required|integer', + 'brand' => 'string', + 'model' => 'string', + 'age' => [ 'integer', 'max:500' ], + 'estimate' => [ 'numeric', 'min:0' ], + 'problem' => 'string', + 'notes' => 'string', + 'repair_status' => [ 'string', 'in:Fixed,Repairable,End-of-life' ], + 'next_steps' => [ 'string', 'in:More time needed,Professional help,Do it yourself' ], + 'spare_parts' => [ 'string', 'in:No,Manufacturer,Third party' ], + 'case_study' => ['boolean'], + 'barrier' => [ 'string', 'in:Spare parts not available,Spare parts too expensive,No way to open the product,Repair information not available,Lack of equipment' ], + ]); + } + + $partyid = $request->input('eventid'); + $category = $request->input('category'); + $item_type = $request->input('item_type'); + $brand = $request->input('brand'); + $model = $request->input('model'); + $age = $request->input('age'); + $estimate = $request->input('estimate'); + $problem = $request->input('problem'); + $notes = $request->input('notes'); + $case_study = $request->input('case_study'); + + // Our database has a slightly complex structure for historical reasons, so we need to map some input + // values to the underlying fields. This keeps the API clean. + // + // There is mirror code in Resources\Device. + $spare_parts = Device::SPARE_PARTS_UNKNOWN; + $parts_provider = NULL; + $professional_help = 0; + $more_time_needed = 0; + $do_it_yourself = 0; + $barrier = 0; + + switch ($request->input('repair_status')) { + case 'Fixed': + $repair_status = Device::REPAIR_STATUS_FIXED; + break; + case 'Repairable': + switch ($request->input('next_steps')) { + case 'More time needed': + $more_time_needed = 1; + break; + case 'Professional help': + $professional_help = 1; + break; + case 'Do it yourself': + $do_it_yourself = 1; + break; + } + + switch ($request->input('spare_parts')) { + case 'No': + $spare_parts = Device::SPARE_PARTS_NOT_NEEDED; + break; + case 'Manufacturer': + $spare_parts = Device::SPARE_PARTS_NEEDED; + $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; + break; + case 'Third party': + $spare_parts = Device::SPARE_PARTS_NEEDED; + $parts_provider = Device::PARTS_PROVIDER_THIRD_PARTY; + break; + } + + $repair_status = Device::REPAIR_STATUS_REPAIRABLE; + break; + case 'End of life': + $repair_status = Device::REPAIR_STATUS_ENDOFLIFE; + + // Look up the barrier. + $barrier = Barrier::firstOrFail()->where('barrier', $request->input('barrier'))->id; + break; + } + + return array( + $partyid, + $category, + $item_type, + $brand, + $model, + $age, + $estimate, + $problem, + $notes, + $case_study, + $repair_status, + $spare_parts, + $parts_provider, + $professional_help, + $more_time_needed, + $do_it_yourself, + $barrier + ); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/API/ItemController.php b/app/Http/Controllers/API/ItemController.php index a20aca4c7f..db477afe3c 100644 --- a/app/Http/Controllers/API/ItemController.php +++ b/app/Http/Controllers/API/ItemController.php @@ -20,7 +20,7 @@ class ItemController extends Controller * path="/api/v2/items", * operationId="listItemsv2", * tags={"Items"}, - * summary="Get list of items", + * summary="Get suggested list of items which could be used in a Device.", * @OA\Response( * response=200, * description="Successful operation", diff --git a/app/Http/Resources/Device.php b/app/Http/Resources/Device.php new file mode 100644 index 0000000000..ed6383bf00 --- /dev/null +++ b/app/Http/Resources/Device.php @@ -0,0 +1,184 @@ + intval($this->iddevices), + 'category' => intval($this->category), + 'item_type' => $this->item_type, + 'brand' => $this->brand, + 'model' => $this->model, + 'age' => floatval($this->age), + 'estimate' => floatval($this->estimate), + 'problem' => $this->problem, + 'notes' => $this->notes, + 'case_study' => intval($this->case_study) ? true : false, + ]; + + // Our database has a slightly complex structure for historical reasons, so we need to map some underlying + // fields to simpler values to keep the API clean. + // + // There is mirror code in API\DeviceController. + switch ($this->repair_status) { + case \App\Device::REPAIR_STATUS_FIXED: + $ret['repair_status'] = 'Fixed'; + break; + case \App\Device::REPAIR_STATUS_REPAIRABLE: + $ret['repair_status'] = 'Repairable'; + + if ($this->more_time_needed) { + $ret['next_steps'] = 'More time needed'; + } else if ($this->professional_help) { + $ret['next_steps'] = 'Professional help'; + } else if ($this->do_it_yourself) { + $ret['next_steps'] = 'Do it yourself'; + } + + if ($this->parts_provider == \App\Device::PARTS_PROVIDER_MANUFACTURER) { + $ret['spare_parts'] = 'Manufacturer'; + } else if ($this->parts_provider == \App\Device::PARTS_PROVIDER_THIRD_PARTY) { + $ret['spare_parts'] = 'Third party'; + } else { + $ret['spare_parts'] = 'No'; + } + break; + case \App\Device::REPAIR_STATUS_ENDOFLIFE: + $ret['repair_status'] = 'End of life'; + + // The underlying DB might have multiple barriers, but we only support one across the API. + if (count($this->barriers)) { + $ret['barrier'] = $this->barriers[0]->barrier; + } + break; + } + + return $ret; + } +} diff --git a/app/Http/Resources/Item.php b/app/Http/Resources/Item.php index 2e5a80aae7..fd9a870d2b 100644 --- a/app/Http/Resources/Item.php +++ b/app/Http/Resources/Item.php @@ -8,7 +8,7 @@ * @OA\Schema( * title="Item", * schema="Item", - * description="An item which can be specified in a device", + * description="An item which can be specified in a Device", * @OA\Property( * property="type", * title="type", diff --git a/routes/api.php b/routes/api.php index 784a66230c..e58c12e586 100644 --- a/routes/api.php +++ b/routes/api.php @@ -103,5 +103,11 @@ }); Route::get('/items', [API\ItemController::class, 'listItemsv2']); + + Route::prefix('/devices')->group(function() { + Route::get('{id}', [API\DeviceController::class, 'getDevicev2']); + Route::post('', [API\DeviceController::class, 'createDevicev2']); + Route::patch('{id}', [API\DeviceController::class, 'updateDevicev2']); + }); }); }); \ No newline at end of file diff --git a/tests/Feature/Devices/APIv2DeviceTest.php b/tests/Feature/Devices/APIv2DeviceTest.php new file mode 100644 index 0000000000..91a01d727d --- /dev/null +++ b/tests/Feature/Devices/APIv2DeviceTest.php @@ -0,0 +1,127 @@ +loginAsTestUser(Role::ADMINISTRATOR); + $idGroup = $this->createGroup(); + $this->assertNotNull($idGroup); + + $idEvents = $this->createEvent($idGroup, 'yesterday'); + $this->assertNotNull($idEvents); + + $device = Device::create([ + 'event' => $idEvents, + 'category' => 11, + 'category_creation' => 11, + 'problem' => 'Test problem', + 'notes' => 'Test notes', + 'brand' => 'Test brand', + 'model' => 'Test model', + 'age' => 1.5, + 'estimate' => 100.00, + 'item_type' => 'Test item type', + 'repair_status' => $repair_status, + 'spare_parts' => $spare_parts, + 'parts_provider' => $parts_provider, + ]); + + $iddevices = $device->iddevices; + $this->assertNotNull($iddevices); + + if ($barrierid) { + DeviceBarrier::create([ + 'device_id' => $iddevices, + 'barrier_id' => $barrierid, + ]); + } + + // Test invalid device id. + try { + $this->get('/api/v2/devices/-1'); + $this->assertFalse(true); + } catch (ModelNotFoundException $e) { + } + + $response = $this->get("/api/v2/devices/$iddevices"); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $this->assertEquals($iddevices, $json['data']['id']); + $this->assertEquals(11, $json['data']['category']); + $this->assertEquals('Test item type', $json['data']['item_type']); + $this->assertEquals('Test brand', $json['data']['brand']); + $this->assertEquals('Test model', $json['data']['model']); + $this->assertEquals(1.5, $json['data']['age']); + $this->assertEquals(100.00, $json['data']['estimate']); + $this->assertEquals('Test problem', $json['data']['problem']); + $this->assertEquals('Test notes', $json['data']['notes']); + $this->assertEquals($repair_status_str, $json['data']['repair_status']); + + if ($parts_provider) { + $this->assertEquals($parts_provider_str, $json['data']['spare_parts']); + } + + if ($barrierid) { + $this->assertEquals($barrierstr, $json['data']['barrier']); + } + } + + public function providerDevice() + { + return [ + [ + Device::REPAIR_STATUS_FIXED, + 'Fixed', + Device::SPARE_PARTS_NOT_NEEDED, + null, + null, + 0, + null + ], + [ + Device::REPAIR_STATUS_REPAIRABLE, + 'Repairable', + Device::SPARE_PARTS_NEEDED, + Device::PARTS_PROVIDER_THIRD_PARTY, + 'Third party', + 0, + null + ], + [ + Device::REPAIR_STATUS_REPAIRABLE, + 'End of life', + Device::SPARE_PARTS_NOT_NEEDED, + null, + null, + 1, + 'Spare parts not available' + ], + ]; + } +} From bc564f4869ab7c0663e2b635a1223a9dddfa1e3c Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 26 Feb 2024 13:15:56 +0000 Subject: [PATCH 02/34] WIP Devices API. --- app/DeviceBarrier.php | 2 +- app/Http/Controllers/API/DeviceController.php | 23 ++++- app/Http/Resources/Device.php | 5 +- tests/Feature/Devices/APIv2DeviceTest.php | 97 ++++++++++++++++++- 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/app/DeviceBarrier.php b/app/DeviceBarrier.php index 5b27f40518..4eaa637700 100644 --- a/app/DeviceBarrier.php +++ b/app/DeviceBarrier.php @@ -7,7 +7,7 @@ class DeviceBarrier extends Model { protected $table = 'devices_barriers'; - protected $fillable = ['device_id', 'barrier']; + protected $fillable = ['device_id', 'barrier_id']; protected $hidden = []; public $timestamps = false; } diff --git a/app/Http/Controllers/API/DeviceController.php b/app/Http/Controllers/API/DeviceController.php index 5ba6fb0d88..db3ab28d2f 100644 --- a/app/Http/Controllers/API/DeviceController.php +++ b/app/Http/Controllers/API/DeviceController.php @@ -189,6 +189,7 @@ public function createDevicev2(Request $request) $data = [ 'event' => $partyid, 'category' => $category, + 'category_creation' => $category, 'item_type' => $item_type, 'brand' => $brand, 'model' => $model, @@ -239,7 +240,7 @@ private function validateDeviceParams(Request $request, $create): array 'category' => 'required|integer', 'brand' => 'string', 'model' => 'string', - 'age' => [ 'integer', 'max:500' ], + 'age' => [ 'numeric', 'max:500' ], 'estimate' => [ 'numeric', 'min:0' ], 'problem' => 'string', 'notes' => 'string', @@ -255,7 +256,7 @@ private function validateDeviceParams(Request $request, $create): array 'category' => 'required|integer', 'brand' => 'string', 'model' => 'string', - 'age' => [ 'integer', 'max:500' ], + 'age' => [ 'numeric', 'max:500' ], 'estimate' => [ 'numeric', 'min:0' ], 'problem' => 'string', 'notes' => 'string', @@ -350,4 +351,22 @@ private function validateDeviceParams(Request $request, $create): array $barrier ); } + private function getUser() + { + // We want to allow this call to work if a) we are logged in as a user, or b) we have a valid API token. + // + // This is a slightly odd thing to do, but it is necessary to get both the PHPUnit tests and the + // real client use of the API to work. + $user = Auth::user(); + + if (!$user) { + $user = auth('api')->user(); + } + + if (!$user) { + throw new AuthenticationException(); + } + + return $user; + } } \ No newline at end of file diff --git a/app/Http/Resources/Device.php b/app/Http/Resources/Device.php index ed6383bf00..2c2cda6c32 100644 --- a/app/Http/Resources/Device.php +++ b/app/Http/Resources/Device.php @@ -173,8 +173,9 @@ public function toArray($request) $ret['repair_status'] = 'End of life'; // The underlying DB might have multiple barriers, but we only support one across the API. - if (count($this->barriers)) { - $ret['barrier'] = $this->barriers[0]->barrier; + foreach ($this->resource->barriers as $barrier) { + $ret['barrier'] = $barrier->barrier; + break; } break; } diff --git a/tests/Feature/Devices/APIv2DeviceTest.php b/tests/Feature/Devices/APIv2DeviceTest.php index 91a01d727d..568fd8d391 100644 --- a/tests/Feature/Devices/APIv2DeviceTest.php +++ b/tests/Feature/Devices/APIv2DeviceTest.php @@ -28,7 +28,7 @@ class APIv2DeviceTest extends TestCase * * @dataProvider providerDevice */ - public function testGetDevice($repair_status, $repair_status_str, $spare_parts, $parts_provider, $parts_provider_str, $barrierid, $barrierstr) { + public function testGetDevice($repair_status, $repair_status_str, $spare_parts, $parts_provider, $parts_provider_str, $barrierid, $barrierstr, $professional_help, $more_time_needed, $do_it_yourself, $next_steps_str) { $this->loginAsTestUser(Role::ADMINISTRATOR); $idGroup = $this->createGroup(); $this->assertNotNull($idGroup); @@ -50,6 +50,9 @@ public function testGetDevice($repair_status, $repair_status_str, $spare_parts, 'repair_status' => $repair_status, 'spare_parts' => $spare_parts, 'parts_provider' => $parts_provider, + 'professional_help' => $professional_help, + 'more_time_needed' => $more_time_needed, + 'do_it_yourself' => $do_it_yourself, ]); $iddevices = $device->iddevices; @@ -90,6 +93,80 @@ public function testGetDevice($repair_status, $repair_status_str, $spare_parts, if ($barrierid) { $this->assertEquals($barrierstr, $json['data']['barrier']); } + + if ($next_steps_str) { + $this->assertEquals($next_steps_str, $json['data']['next_steps']); + } + } + + /** + * Create a device over the API and check it retrieves as expected. + * + * @dataProvider providerDevice + */ + public function testCreate($repair_status, $repair_status_str, $spare_parts, $parts_provider, $parts_provider_str, $barrierid, $barrierstr, $professional_help, $more_time_needed, $do_it_yourself, $next_steps_str) { + $this->loginAsTestUser(Role::ADMINISTRATOR); + $idGroup = $this->createGroup(); + $this->assertNotNull($idGroup); + + $idEvents = $this->createEvent($idGroup, 'yesterday'); + $this->assertNotNull($idEvents); + + $params = [ + 'eventid' => $idEvents, + 'category' => 11, + 'problem' => 'Test problem', + 'notes' => 'Test notes', + 'brand' => 'Test brand', + 'model' => 'Test model', + 'age' => 1.5, + 'estimate' => 100.00, + 'item_type' => 'Test item type', + 'repair_status' => $repair_status_str, + 'parts_provider' => $parts_provider, + ]; + + if ($next_steps_str) { + $params['next_steps'] = $next_steps_str; + } + + if ($parts_provider_str) { + $params['spare_parts'] = $parts_provider_str; + } + + $response = $this->post('/api/v2/devices', $params); + + $this->assertTrue($response->isSuccessful()); + $json = json_decode($response->getContent(), true); + $this->assertTrue(array_key_exists('id', $json)); + $iddevices = $json['id']; + $this->assertNotNull($iddevices); + + $response = $this->get("/api/v2/devices/$iddevices"); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $this->assertEquals($iddevices, $json['data']['id']); + $this->assertEquals(11, $json['data']['category']); + $this->assertEquals('Test item type', $json['data']['item_type']); + $this->assertEquals('Test brand', $json['data']['brand']); + $this->assertEquals('Test model', $json['data']['model']); + $this->assertEquals(1.5, $json['data']['age']); + $this->assertEquals(100.00, $json['data']['estimate']); + $this->assertEquals('Test problem', $json['data']['problem']); + $this->assertEquals('Test notes', $json['data']['notes']); + $this->assertEquals($repair_status_str, $json['data']['repair_status']); + + if ($parts_provider) { + $this->assertEquals($parts_provider_str, $json['data']['spare_parts']); + } + + if ($barrierid) { + $this->assertEquals($barrierstr, $json['data']['barrier']); + } + + if ($next_steps_str) { + $this->assertEquals($next_steps_str, $json['data']['next_steps']); + } } public function providerDevice() @@ -102,6 +179,10 @@ public function providerDevice() null, null, 0, + null, + 0, + 0, + 0, null ], [ @@ -111,16 +192,24 @@ public function providerDevice() Device::PARTS_PROVIDER_THIRD_PARTY, 'Third party', 0, - null + null, + 1, + 0, + 0, + 'Professional help' ], [ - Device::REPAIR_STATUS_REPAIRABLE, + Device::REPAIR_STATUS_ENDOFLIFE, 'End of life', Device::SPARE_PARTS_NOT_NEEDED, null, null, 1, - 'Spare parts not available' + 'Spare parts not available', + 0, + 0, + 0, + null ], ]; } From 3554136cb3db765fda8954193ee539e3bb5d21bc Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Thu, 7 Mar 2024 09:24:02 +0000 Subject: [PATCH 03/34] Test fixes --- tests/Feature/Devices/APIv2DeviceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/Devices/APIv2DeviceTest.php b/tests/Feature/Devices/APIv2DeviceTest.php index 568fd8d391..4eff418037 100644 --- a/tests/Feature/Devices/APIv2DeviceTest.php +++ b/tests/Feature/Devices/APIv2DeviceTest.php @@ -200,7 +200,7 @@ public function providerDevice() ], [ Device::REPAIR_STATUS_ENDOFLIFE, - 'End of life', + 'End-of-life', Device::SPARE_PARTS_NOT_NEEDED, null, null, From fb0414ee3aae72927db825279eca35a7dec78509 Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Thu, 7 Mar 2024 12:42:56 +0000 Subject: [PATCH 04/34] WIP Device API --- app/Http/Controllers/DeviceController.php | 324 ---------------------- resources/js/store/devices.js | 21 +- routes/web.php | 3 - 3 files changed, 20 insertions(+), 328 deletions(-) diff --git a/app/Http/Controllers/DeviceController.php b/app/Http/Controllers/DeviceController.php index f231ff8b2a..ea4937f966 100644 --- a/app/Http/Controllers/DeviceController.php +++ b/app/Http/Controllers/DeviceController.php @@ -66,330 +66,6 @@ public function index($search = null) ]); } - public function ajaxCreate(Request $request) - { - $rules = [ - 'category' => 'required|filled' - ]; - - $validator = Validator::make($request->all(), $rules); - - if ($validator->fails()) { - return response()->json($validator->messages(), 200); - } - - $request->validate([ - 'age' => 'nullable|numeric|max:500' - ]); - - $category = $request->input('category'); - $weight = $request->filled('estimate') ? $request->input('estimate', 0) : 0; - $brand = $request->input('brand'); - $model = $request->input('model'); - $item_type = $request->input('item_type'); - $age = $request->filled('age') ? $request->input('age',0) : 0; - $problem = $request->input('problem'); - $notes = $request->input('notes'); - $repair_status = $request->input('repair_status'); - $repair_details = $request->input('repair_details'); - $spare_parts = $request->input('spare_parts'); - $quantity = $request->input('quantity'); - $event_id = $request->input('event_id'); - $barrier = $request->input('barrier'); - - $iddevices = $request->input('iddevices'); - - // Get party for later - $event = Party::find($event_id); - - // add quantity loop - for ($i = 0; $i < $quantity; $i++) { - $device[$i] = new Device; - $device[$i]->category = $category; - $device[$i]->category_creation = $category; - $device[$i]->estimate = $weight; - $device[$i]->brand = $brand; - $device[$i]->item_type = $item_type; - $device[$i]->model = $model; - $device[$i]->age = $age; - $device[$i]->problem = $problem; - $device[$i]->notes = $notes; - $device[$i]->repair_status = isset($repair_status) ? $repair_status : 0; - - if ($repair_details == 1) { - $device[$i]->more_time_needed = 1; - } else { - $device[$i]->more_time_needed = 0; - } - - if ($repair_details == 2) { - $device[$i]->professional_help = 1; - } else { - $device[$i]->professional_help = 0; - } - - if ($repair_details == 3) { - $device[$i]->do_it_yourself = 1; - } else { - $device[$i]->do_it_yourself = 0; - } - - // $spare_parts as input comes from a single dropdown with three options. - // This is mapped to corresponding DB values - // for spare_parts and parts_provider columns. - if ($spare_parts == 3) { // Input option was 'Third party' - $spare_parts = Device::SPARE_PARTS_NEEDED; - $parts_provider = Device::PARTS_PROVIDER_THIRD_PARTY; - } elseif ($spare_parts == 1) { // Input option was 'Manufacturer' - $spare_parts = Device::SPARE_PARTS_NEEDED; - $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; - } elseif ($spare_parts == 2) { // Input option was 'Not needed' - $spare_parts = Device::SPARE_PARTS_NOT_NEEDED; - $parts_provider = null; - } else { - $parts_provider = null; - } - - if (! isset($barrier)) { - $barrier = null; - } elseif (in_array(1, $barrier) || in_array(2, $barrier)) { // 'Spare parts not available' or 'spare parts too expensive' selected - $spare_parts = Device::SPARE_PARTS_NEEDED; - } elseif (count($barrier) > 0) { - $spare_parts = Device::SPARE_PARTS_NOT_NEEDED; - } - - $device[$i]->spare_parts = isset($spare_parts) ? $spare_parts : Device::SPARE_PARTS_UNKNOWN; - $device[$i]->parts_provider = $parts_provider; - $device[$i]->event = $event_id; - $device[$i]->repaired_by = Auth::id(); - - $device[$i]->save(); - $device[$i]->refresh(); - - event(new DeviceCreatedOrUpdated($device[$i])); - - // Update barriers - if (isset($barrier) && ! empty($barrier) && $repair_status == Device::REPAIR_STATUS_ENDOFLIFE) { - Device::find($device[$i]->iddevices)->barriers()->sync($barrier); - } else { - Device::find($device[$i]->iddevices)->barriers()->sync([]); - } - - // If the number of devices exceeds set amount then show the following message - $deviceMiscCount = DB::table('devices')->where('category', env('MISC_CATEGORY_ID_POWERED'))->where('event', $event_id)->count() + - DB::table('devices')->where('category', env('MISC_CATEGORY_ID_UNPOWERED'))->where('event', $event_id)->count(); - if ($deviceMiscCount == env('DEVICE_ABNORMAL_MISC_COUNT', 5)) { - $notify_users = Fixometer::usersWhoHavePreference('admin-abnormal-devices'); - Notification::send($notify_users, new AdminAbnormalDevices([ - 'event_venue' => $event->getEventName(), - 'event_url' => url('/party/edit/'.$event_id), - ])); - } - - // Expand a few things so that the new devices are returned with the same information that existing - // ones are returned in the view blade. - $device[$i]->idevents = $device[$i]->event; - $device[$i]->category = $device[$i]->deviceCategory; - $device[$i]->shortProblem = $device[$i]->getShortProblem(); - - $barriers = []; - - foreach ($device[$i]->barriers as $b) { - $barriers[] = $b->id; - } - - $device[$i]->barrier = $barriers; - - if ($iddevices && $iddevices < 0) { - // We might have some photos uploaded for this device. Record them against this device instance. - // Each instance of a device shares the same underlying photo file. - $File = new \FixometerFile; - $images = $File->findImages(env('TBL_DEVICES'), $iddevices); - foreach ($images as $image) { - $xref = Xref::findOrFail($image->idxref); - $xref->copy($device[$i]->iddevices); - } - - $device[$i]->images = $device[$i]->getImages(); - } - } - // end quantity loop - - $return['success'] = true; - $return['devices'] = $device; - - $return['stats'] = $event->getEventStats(); - - return response()->json($return); - } - - public function ajaxEdit(Request $request, $id) - { - $category = $request->input('category'); - $brand = $request->input('brand'); - $item_type = $request->input('item_type'); - $model = $request->input('model'); - $age = $request->filled('age') ? $request->input('age',0) : 0; - $problem = $request->input('problem'); - $notes = $request->input('notes'); - $repair_status = $request->input('repair_status'); - $barrier = $request->input('barrier'); - $repair_details = $request->input('repair_details'); - $spare_parts = $request->input('spare_parts'); - $event_id = $request->input('event_id'); - $wiki = $request->input('wiki'); - $estimate = $request->filled('estimate') ? $request->input('estimate', 0) : 0; - - $request->validate([ - 'age' => 'nullable|numeric|max:500' - ]); - - if (empty($repair_status)) { //Override - $repair_status = 0; - } - - if ($repair_status != 2) { //Override - $repair_details = 0; - } - - if (Fixometer::userHasEditEventsDevicesPermission($event_id)) { - if ($repair_details == 1) { - $more_time_needed = 1; - } else { - $more_time_needed = 0; - } - - if ($repair_details == 2) { - $professional_help = 1; - } else { - $professional_help = 0; - } - - if ($repair_details == 3) { - $do_it_yourself = 1; - } else { - $do_it_yourself = 0; - } - - if ($spare_parts == 3) { // Third party - $spare_parts = 1; - $parts_provider = 2; - } elseif ($spare_parts == 1) { // Manufacturer - $spare_parts = 1; - $parts_provider = 1; - } elseif ($spare_parts == 2) { // Not needed - $spare_parts = 2; - $parts_provider = null; - } elseif ($spare_parts == 4) { // Historical data, resets spare parts to 1 but keeps parts provider as null - $spare_parts = 1; - $parts_provider = null; - } else { - $parts_provider = null; - } - - if (! isset($barrier)) { - $barrier = null; - } elseif (in_array(1, $barrier) || in_array(2, $barrier)) { // 'Spare parts not available' or 'spare parts too expensive' selected - $spare_parts = 1; - } elseif (count($barrier) > 0) { - $spare_parts = 2; - } - - $Device = Device::find($id); - - $Device->update([ - 'category' => $category, - 'brand' => $brand, - 'item_type' => $item_type, - 'model' => $model, - 'age' => $age, - 'problem' => $problem, - 'notes' => $notes, - 'spare_parts' => $spare_parts, - 'parts_provider' => $parts_provider, - 'repair_status' => $repair_status, - 'more_time_needed' => $more_time_needed, - 'professional_help' => $professional_help, - 'do_it_yourself' => $do_it_yourself, - 'wiki' => $wiki, - 'estimate' => $estimate, - ]); - - // Update barriers - if (isset($barrier) && ! empty($barrier) && $repair_status == 3) { // Only sync when repair status is end-of-life - $Device->barriers()->sync($barrier); - } else { - $Device->barriers()->sync([]); - } - - $event = Party::find($event_id); - event(new DeviceCreatedOrUpdated($Device)); - - $stats = $event->getEventStats(); - $data['stats'] = $stats; - $data['success'] = 'Device updated!'; - - // Expand a few things so that the devices are returned with the same information that existing - // ones are returned in the view blade. - $device = Device::find($id); - $device->idevents = $device->event; - $device->category = $device->deviceCategory; - $device->shortProblem = $device->getShortProblem(); - $device->images = $device->getImages(); - - $barriers = []; - - foreach ($device->barriers as $b) { - $barriers[] = $b->id; - } - - $device->barrier = $barriers; - - $data['device'] = $device; - - return response()->json($data); - } - } - - public function delete(Request $request, $id) - { - $user = Auth::user(); - - $device = Device::find($id); - - if ($device) { - $eventId = $device->event; - $is_attending = EventsUsers::where('event', $device->event)->where('user', Auth::id())->first(); - $is_attending = is_object($is_attending) && $is_attending->status == 1; - - if (Fixometer::hasRole($user, 'Administrator') || - Fixometer::userHasEditPartyPermission($eventId, $user->id) || - $is_attending - ) { - $device->delete(); - - if ($request->ajax()) { - $event = Party::find($eventId); - $stats = $event->getEventStats(); - - return response()->json([ - 'success' => true, - 'stats' => $stats, - ]); - } - - return redirect('/party/view/'.$eventId)->with('success', __('devices.device_delete_sucess')); - } - } - - if ($request->ajax()) { - return response()->json(['success' => false]); - } - - \Sentry\CaptureMessage(__('devices.device_delete_permissions')); - return redirect('/party/view/'.$eventId)->with('warning', __('devices.device_delete_permissions')); - } - public function imageUpload(Request $request, $id) { try { diff --git a/resources/js/store/devices.js b/resources/js/store/devices.js index 4550cf78b0..b25ef0de53 100644 --- a/resources/js/store/devices.js +++ b/resources/js/store/devices.js @@ -31,7 +31,26 @@ export default { Vue.set(state.images, d.iddevices, d.images) }) }, - add (state, params) { + async add (state, params) { + const formData = new FormData() + + for (var key in params) { + if (params[key]) { + formData.append(key, params[key]); + } + } + + let ret = await axios.post('/api/v2/devices?api_token=' + rootGetters['auth/apiToken'], formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }) + + console.log('Create device returned', ret) + if (ret && ret.data) { + id = ret.data.id + } + let exists = false if (params.iddevices) { diff --git a/routes/web.php b/routes/web.php index de1cfc407b..1795702c01 100644 --- a/routes/web.php +++ b/routes/web.php @@ -279,9 +279,6 @@ return redirect('/fixometer'); }); Route::get('/search', [DeviceController::class, 'search']); - Route::post('/edit/{id}', [DeviceController::class, 'ajaxEdit']); - Route::post('/create', [DeviceController::class, 'ajaxCreate']); - Route::get('/delete/{id}', [DeviceController::class, 'delete']); Route::post('/image-upload/{id}', [DeviceController::class, 'imageUpload']); Route::get('/image/delete/{iddevices}/{idxref}', [DeviceController::class, 'deleteImage']); }); From 7f6aab37a47e40e0da334324ab4ea2302d22107e Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 10 Jun 2024 13:11:55 +0100 Subject: [PATCH 05/34] Some test failures, introduce edit device APIv2 call. --- app/Http/Controllers/API/DeviceController.php | 191 +++++++++++++++++- app/Http/Resources/Device.php | 7 + tests/Feature/Devices/APIv2DeviceTest.php | 3 +- tests/Feature/Devices/CategoryTest.php | 31 ++- 4 files changed, 216 insertions(+), 16 deletions(-) diff --git a/app/Http/Controllers/API/DeviceController.php b/app/Http/Controllers/API/DeviceController.php index db3ab28d2f..70e07d5373 100644 --- a/app/Http/Controllers/API/DeviceController.php +++ b/app/Http/Controllers/API/DeviceController.php @@ -189,7 +189,7 @@ public function createDevicev2(Request $request) $data = [ 'event' => $partyid, 'category' => $category, - 'category_creation' => $category, + 'category_creation' => $category, // We don't expose this over the API but we record it in case it changes. 'item_type' => $item_type, 'brand' => $brand, 'model' => $model, @@ -228,6 +228,172 @@ public function createDevicev2(Request $request) ]); } + /** + * @OA\Patch( + * path="/api/v2/devices/{id}", + * operationId="editDevice", + * tags={"Devices"}, + * summary="Edit Device", + * description="Edits a device.", + * @OA\Parameter( + * name="api_token", + * description="A valid user API token", + * required=true, + * in="query", + * @OA\Schema( + * type="string", + * example="1234" + * ) + * ), + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="multipart/form-data", + * @OA\Schema( + * required={"category","item_type"}, + * @OA\Property( + * property="eventid", + * title="id", + * description="Unique identifier of the event to which the device belongs", + * format="int64", + * example=1 + * ), + * @OA\Property( + * property="category", + * ref="#/components/schemas/Device/properties/category", + * ), + * @OA\Property( + * property="item_type", + * ref="#/components/schemas/Device/properties/item_type", + * ), + * @OA\Property( + * property="brand", + * ref="#/components/schemas/Device/properties/brand", + * ), + * @OA\Property( + * property="model", + * ref="#/components/schemas/Device/properties/model", + * ), + * @OA\Property( + * property="age", + * ref="#/components/schemas/Device/properties/age", + * ), + * @OA\Property( + * property="estimate", + * ref="#/components/schemas/Device/properties/estimate", + * ), + * @OA\Property( + * property="problem", + * ref="#/components/schemas/Device/properties/problem", + * ), + * @OA\Property( + * property="notes", + * ref="#/components/schemas/Device/properties/notes", + * ), + * @OA\Property( + * property="repair_status", + * ref="#/components/schemas/Device/properties/repair_status", + * ), + * @OA\Property( + * property="next_steps", + * ref="#/components/schemas/Device/properties/next_steps", + * ), + * @OA\Property( + * property="spare_parts", + * ref="#/components/schemas/Device/properties/spare_parts", + * ), + * @OA\Property( + * property="case_study", + * ref="#/components/schemas/Device/properties/case_study", + * ), + * @OA\Property( + * property="barrier", + * ref="#/components/schemas/Device/properties/barrier", + * ), + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * title="data", + * ref="#/components/schemas/Device" + * ) + * ), + * ) + * ) + */ + public function updateDevicev2(Request $request, $iddevices) + { + $user = $this->getUser(); + + list($partyid, + $category, + $item_type, + $brand, + $model, + $age, + $estimate, + $problem, + $notes, + $case_study, + $repair_status, + $spare_parts, + $parts_provider, + $professional_help, + $more_time_needed, + $do_it_yourself, + $barrier + ) = $this->validateDeviceParams($request,false); + + Party::findOrFail($partyid); + + if (!Fixometer::userHasEditEventsDevicesPermission($partyid, $user->id)) { + // Only hosts can add devices to events. + abort(403); + } + + $data = [ + 'event' => $partyid, + 'category' => $category, + 'item_type' => $item_type, + 'brand' => $brand, + 'model' => $model, + 'age' => $age, + 'estimate' => $estimate, + 'problem' => $problem, + 'notes' => $notes, + 'wiki' => $case_study, + 'repair_status' => $repair_status, + 'spare_parts' => $spare_parts, + 'parts_provider' => $parts_provider, + 'professional_help' => $professional_help, + 'more_time_needed' => $more_time_needed, + 'do_it_yourself' => $do_it_yourself, + 'repaired_by' => $user->id, + ]; + + $device = Device::findOrFail($iddevices); + $device->update($data); + + event(new DeviceCreatedOrUpdated($device)); + + if ($barrier) { + DeviceBarrier::updateOrCreate([ + 'device_id' => $iddevices, + 'barrier_id' => $barrier + ]); + } + + // TODO Images - probably a separate API Call. + + return response()->json([ + 'id' => $iddevices, + ]); + } + private function validateDeviceParams(Request $request, $create): array { // We don't validate max lengths of other strings, to avoid duplicating the length information both here @@ -244,11 +410,11 @@ private function validateDeviceParams(Request $request, $create): array 'estimate' => [ 'numeric', 'min:0' ], 'problem' => 'string', 'notes' => 'string', - 'repair_status' => [ 'string', 'in:Fixed,Repairable,End-of-life' ], + 'repair_status' => [ 'string', 'in:Fixed,Repairable,End of life' ], 'next_steps' => [ 'string', 'in:More time needed,Professional help,Do it yourself' ], 'spare_parts' => [ 'string', 'in:No,Manufacturer,Third party' ], 'case_study' => ['boolean'], - 'barrier' => [ 'string', 'in:Spare parts not available,Spare parts too expensive,No way to open the product,Repair information not available,Lack of equipment' ], + 'barrier' => [ 'string', 'nullable', 'in:Spare parts not available,Spare parts too expensive,No way to open the product,Repair information not available,Lack of equipment' ], ]); } else { $request->validate([ @@ -260,7 +426,7 @@ private function validateDeviceParams(Request $request, $create): array 'estimate' => [ 'numeric', 'min:0' ], 'problem' => 'string', 'notes' => 'string', - 'repair_status' => [ 'string', 'in:Fixed,Repairable,End-of-life' ], + 'repair_status' => [ 'string', 'in:Fixed,Repairable,End of life' ], 'next_steps' => [ 'string', 'in:More time needed,Professional help,Do it yourself' ], 'spare_parts' => [ 'string', 'in:No,Manufacturer,Third party' ], 'case_study' => ['boolean'], @@ -270,6 +436,7 @@ private function validateDeviceParams(Request $request, $create): array $partyid = $request->input('eventid'); $category = $request->input('category'); + $category_creation = $request->input('category_creation'); $item_type = $request->input('item_type'); $brand = $request->input('brand'); $model = $request->input('model'); @@ -278,6 +445,7 @@ private function validateDeviceParams(Request $request, $create): array $problem = $request->input('problem'); $notes = $request->input('notes'); $case_study = $request->input('case_study'); + $repair_status = $request->input('repair_status'); // Our database has a slightly complex structure for historical reasons, so we need to map some input // values to the underlying fields. This keeps the API clean. @@ -290,7 +458,7 @@ private function validateDeviceParams(Request $request, $create): array $do_it_yourself = 0; $barrier = 0; - switch ($request->input('repair_status')) { + switch ($repair_status) { case 'Fixed': $repair_status = Device::REPAIR_STATUS_FIXED; break; @@ -325,9 +493,20 @@ private function validateDeviceParams(Request $request, $create): array break; case 'End of life': $repair_status = Device::REPAIR_STATUS_ENDOFLIFE; + $barrierInput = $request->input('barrier'); + + if (!$barrierInput) { + throw ValidationException::withMessages(['barrier' => ['Barrier is required for End of life devices']]); + } // Look up the barrier. - $barrier = Barrier::firstOrFail()->where('barrier', $request->input('barrier'))->id; + $barrierEnt = Barrier::firstOrFail()->where('barrier', $barrierInput)->get(); + $barrier = $barrierEnt->toArray()[0]['id']; + + if (!$barrier) { + throw ValidationException::withMessages(['barrier' => ['Invalid barrier supplied']]); + } + break; } diff --git a/app/Http/Resources/Device.php b/app/Http/Resources/Device.php index 2c2cda6c32..4bdcea8147 100644 --- a/app/Http/Resources/Device.php +++ b/app/Http/Resources/Device.php @@ -26,6 +26,13 @@ * example="16" * ), * @OA\Property( + * property="category_creation", + * title="category_creation", + * description="The id of the category to which this item belonged at the time of creation (if different).", + * format="int64", + * example="16" + * ), + * @OA\Property( * property="item_type", * title="item_type", * description="The name of the item.", diff --git a/tests/Feature/Devices/APIv2DeviceTest.php b/tests/Feature/Devices/APIv2DeviceTest.php index 4eff418037..af66b7523d 100644 --- a/tests/Feature/Devices/APIv2DeviceTest.php +++ b/tests/Feature/Devices/APIv2DeviceTest.php @@ -124,6 +124,7 @@ public function testCreate($repair_status, $repair_status_str, $spare_parts, $pa 'item_type' => 'Test item type', 'repair_status' => $repair_status_str, 'parts_provider' => $parts_provider, + 'barrier' => $barrierstr, ]; if ($next_steps_str) { @@ -200,7 +201,7 @@ public function providerDevice() ], [ Device::REPAIR_STATUS_ENDOFLIFE, - 'End-of-life', + 'End of life', Device::SPARE_PARTS_NOT_NEEDED, null, null, diff --git a/tests/Feature/Devices/CategoryTest.php b/tests/Feature/Devices/CategoryTest.php index 61a99c89bc..1964092c11 100644 --- a/tests/Feature/Devices/CategoryTest.php +++ b/tests/Feature/Devices/CategoryTest.php @@ -14,27 +14,40 @@ class CategoryTest extends TestCase public function testCategoryChange() { $event = Party::factory()->create(); - $this->device_inputs = Device::factory()->raw([ - 'event_id' => $event->idevents, - 'category' => 11, - 'category_creation' => 11, - 'quantity' => 1, - ]); $admin = User::factory()->administrator()->create(); $this->actingAs($admin); - $this->post('/device/create', $this->device_inputs); + $rsp = $this->post('/api/v2/devices', [ + 'eventid' => $event->idevents, + 'category' => 11, + 'category_creation' => 11, + 'age' => 1.5, + 'estimate' => 100.00, + 'item_type' => 'Test item type', + 'repair_status' => 'Fixed', + ]); + $rsp->assertSuccessful(); + $iddevices = Device::latest()->first()->iddevices; $this->device_inputs['category'] = 46; unset($this->device_inputs['category_creation']); - $rsp = $this->post('/device/edit/' . $iddevices, $this->device_inputs); - self::assertEquals('Device updated!', $rsp['success']); + $rsp = $this->patch('/api/v2/devices/' . $iddevices, [ + 'eventid' => $event->idevents, + 'category' => 46, + 'age' => 1.5, + 'estimate' => 100.00, + 'item_type' => 'Test item type', + 'repair_status' => 'Fixed', + ]); + + $rsp->assertSuccessful(); $device = Device::findOrFail($iddevices); self::assertEquals($device->category_creation, 11); + self::assertEquals($device->category, 46); } public function testListItems() { From 752d2f5f61ad8dff7db97feb3cd63fa54095db2a Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 17 Jun 2024 12:36:08 +0100 Subject: [PATCH 06/34] Migrate more tests over to the v2 API. call. --- app/Device.php | 22 +++ app/Http/Controllers/API/DeviceController.php | 140 ++++++++++++------ app/Http/Resources/Device.php | 58 ++++---- lang/en/devices.php | 1 - lang/fr-BE/devices.php | 1 - lang/fr/devices.php | 1 - routes/api.php | 1 + tests/Feature/Devices/APIv2DeviceTest.php | 91 ++++++------ tests/Feature/Devices/EditTest.php | 113 +++++--------- tests/Feature/Devices/NullAgeProblemTest.php | 39 +++-- .../Devices/NullEstimateProblemTest.php | 38 +++-- tests/Feature/Devices/NullProblemTest.php | 10 +- tests/Feature/Devices/SparePartsTest.php | 38 ++--- tests/TestCase.php | 72 ++++++++- 14 files changed, 365 insertions(+), 260 deletions(-) diff --git a/app/Device.php b/app/Device.php index 931f6572df..a3a35a5adb 100644 --- a/app/Device.php +++ b/app/Device.php @@ -15,15 +15,37 @@ class Device extends Model implements Auditable use HasFactory; const REPAIR_STATUS_FIXED = 1; + const REPAIR_STATUS_FIXED_STR = 'Fixed'; const REPAIR_STATUS_REPAIRABLE = 2; + const REPAIR_STATUS_REPAIRABLE_STR = 'Repairable'; const REPAIR_STATUS_ENDOFLIFE = 3; + const REPAIR_STATUS_ENDOFLIFE_STR = 'End of life'; const SPARE_PARTS_NEEDED = 1; const SPARE_PARTS_NOT_NEEDED = 2; const SPARE_PARTS_UNKNOWN = 0; const PARTS_PROVIDER_MANUFACTURER = 1; + const PARTS_PROVIDER_MANUFACTURER_STR = 'Manufacturer'; const PARTS_PROVIDER_THIRD_PARTY = 2; + const PARTS_PROVIDER_THIRD_PARTY_STR = 'Third party'; + const PARTS_PROVIDER_NO_STR = 'No'; + + const BARRIER_SPARE_PARTS_NOT_AVAILABLE = 1; + const BARRIER_SPARE_PARTS_NOT_AVAILABLE_STR = 'Spare parts not available'; + const BARRIER_SPARE_PARTS_TOO_EXPENSIVE = 2; + const BARRIER_SPARE_PARTS_TOO_EXPENSIVE_STR = 'Spare parts too expensive'; + const BARRIER_NO_WAY_TO_OPEN_THE_PRODUCT = 3; + const BARRIER_NO_WAY_TO_OPEN_THE_PRODUCT_STR = 'No way to open the product'; + const BARRIER_REPAIR_INFORMATION_NOT_AVAILABLE = 4; + const BARRIER_REPAIR_INFORMATION_NOT_AVAILABLE_STR = 'Repair information not available'; + const BARRIER_LACK_OF_EQUIPMENT = 5; + const BARRIER_LACK_OF_EQUIPMENT_STR = 'Lack of equpment'; + + const NEXT_STEPS_MORE_TIME_NEEDED_STR = 'More time needed'; + const NEXT_STEPS_PROFESSIONAL_HELP_STR = 'Professional help'; + const NEXT_STEPS_DO_IT_YOURSELF_STR = 'Do it yourself'; + use \OwenIt\Auditing\Auditable; protected $table = 'devices'; diff --git a/app/Http/Controllers/API/DeviceController.php b/app/Http/Controllers/API/DeviceController.php index 70e07d5373..ad81327f8a 100644 --- a/app/Http/Controllers/API/DeviceController.php +++ b/app/Http/Controllers/API/DeviceController.php @@ -6,6 +6,7 @@ use App\Device; use App\DeviceBarrier; use App\Events\DeviceCreatedOrUpdated; +use App\EventsUsers; use App\Helpers\Fixometer; use App\Http\Controllers\Controller; use App\Party; @@ -47,7 +48,7 @@ class DeviceController extends Controller { * ), * @OA\Response( * response=404, - * description="Event not found", + * description="Device not found", * ), * ) */ @@ -394,6 +395,51 @@ public function updateDevicev2(Request $request, $iddevices) ]); } + /** + * @OA\Delete( + * path="/api/v2/devices/{id}", + * operationId="deleteDevice", + * tags={"Devices"}, + * summary="Delete Device", + * description="Deletes a device.", + * @OA\Parameter( + * name="id", + * description="Device id", + * required=true, + * in="path", + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * ), + * @OA\Response( + * response=404, + * description="Device not found", + * ), + * ) + */ + + public function deleteDevicev2(Request $request, $iddevices) + { + $user = $this->getUser(); + + $device = Device::findOrFail($iddevices); + + if (!Fixometer::userHasEditEventsDevicesPermission($device->event, $user->id)) { + // Only hosts can delete devices for events. + abort(403); + } + + $device->delete(); + + return response()->json([ + 'id' => $iddevices, + ]); + } + private function validateDeviceParams(Request $request, $create): array { // We don't validate max lengths of other strings, to avoid duplicating the length information both here @@ -408,7 +454,7 @@ private function validateDeviceParams(Request $request, $create): array 'model' => 'string', 'age' => [ 'numeric', 'max:500' ], 'estimate' => [ 'numeric', 'min:0' ], - 'problem' => 'string', + 'problem' => [ 'string', 'nullable' ], 'notes' => 'string', 'repair_status' => [ 'string', 'in:Fixed,Repairable,End of life' ], 'next_steps' => [ 'string', 'in:More time needed,Professional help,Do it yourself' ], @@ -424,8 +470,7 @@ private function validateDeviceParams(Request $request, $create): array 'model' => 'string', 'age' => [ 'numeric', 'max:500' ], 'estimate' => [ 'numeric', 'min:0' ], - 'problem' => 'string', - 'notes' => 'string', + 'problem' => [ 'string', 'nullable' ], 'notes' => 'string', 'repair_status' => [ 'string', 'in:Fixed,Repairable,End of life' ], 'next_steps' => [ 'string', 'in:More time needed,Professional help,Do it yourself' ], 'spare_parts' => [ 'string', 'in:No,Manufacturer,Third party' ], @@ -436,7 +481,6 @@ private function validateDeviceParams(Request $request, $create): array $partyid = $request->input('eventid'); $category = $request->input('category'); - $category_creation = $request->input('category_creation'); $item_type = $request->input('item_type'); $brand = $request->input('brand'); $model = $request->input('model'); @@ -446,11 +490,13 @@ private function validateDeviceParams(Request $request, $create): array $notes = $request->input('notes'); $case_study = $request->input('case_study'); $repair_status = $request->input('repair_status'); + $barrierInput = $request->input('barrier'); // Our database has a slightly complex structure for historical reasons, so we need to map some input // values to the underlying fields. This keeps the API clean. // // There is mirror code in Resources\Device. + $problem = $problem ? $problem : ''; $spare_parts = Device::SPARE_PARTS_UNKNOWN; $parts_provider = NULL; $professional_help = 0; @@ -459,58 +505,64 @@ private function validateDeviceParams(Request $request, $create): array $barrier = 0; switch ($repair_status) { - case 'Fixed': + case Device::REPAIR_STATUS_FIXED_STR: $repair_status = Device::REPAIR_STATUS_FIXED; break; - case 'Repairable': - switch ($request->input('next_steps')) { - case 'More time needed': - $more_time_needed = 1; - break; - case 'Professional help': - $professional_help = 1; - break; - case 'Do it yourself': - $do_it_yourself = 1; - break; - } - - switch ($request->input('spare_parts')) { - case 'No': - $spare_parts = Device::SPARE_PARTS_NOT_NEEDED; - break; - case 'Manufacturer': - $spare_parts = Device::SPARE_PARTS_NEEDED; - $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; - break; - case 'Third party': - $spare_parts = Device::SPARE_PARTS_NEEDED; - $parts_provider = Device::PARTS_PROVIDER_THIRD_PARTY; - break; - } - + case Device::REPAIR_STATUS_REPAIRABLE_STR: $repair_status = Device::REPAIR_STATUS_REPAIRABLE; break; - case 'End of life': + case Device::REPAIR_STATUS_ENDOFLIFE_STR: $repair_status = Device::REPAIR_STATUS_ENDOFLIFE; - $barrierInput = $request->input('barrier'); if (!$barrierInput) { throw ValidationException::withMessages(['barrier' => ['Barrier is required for End of life devices']]); } - // Look up the barrier. - $barrierEnt = Barrier::firstOrFail()->where('barrier', $barrierInput)->get(); - $barrier = $barrierEnt->toArray()[0]['id']; + break; + } - if (!$barrier) { - throw ValidationException::withMessages(['barrier' => ['Invalid barrier supplied']]); - } + if ($barrierInput) { + // Look up the barrier. + $barrierEnt = Barrier::firstOrFail()->where('barrier', $barrierInput)->get(); + $barrier = $barrierEnt->toArray()[0]['id']; - break; + if (!$barrier) { + throw ValidationException::withMessages(['barrier' => ['Invalid barrier supplied']]); + } } - return array( + // We can provide next_steps and spare_parts for any status - this is for recording historical information. + if ($request->has('next_steps')) { + switch ($request->input('next_steps')) { + case Device::NEXT_STEPS_MORE_TIME_NEEDED_STR: + $more_time_needed = 1; + break; + case Device::NEXT_STEPS_PROFESSIONAL_HELP_STR: + $professional_help = 1; + break; + case Device::NEXT_STEPS_DO_IT_YOURSELF_STR: + $do_it_yourself = 1; + break; + } + } + + if ($request->has('spare_parts')) { + switch ($request->input('spare_parts')) { + case Device::PARTS_PROVIDER_NO_STR: + $spare_parts = Device::SPARE_PARTS_NOT_NEEDED; + break; + case Device::PARTS_PROVIDER_MANUFACTURER_STR: + $spare_parts = Device::SPARE_PARTS_NEEDED; + $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; + break; + case Device::PARTS_PROVIDER_THIRD_PARTY_STR: + $spare_parts = Device::SPARE_PARTS_NEEDED; + $parts_provider = Device::PARTS_PROVIDER_THIRD_PARTY; + break; + } + } + + return [ $partyid, $category, $item_type, @@ -528,7 +580,7 @@ private function validateDeviceParams(Request $request, $create): array $more_time_needed, $do_it_yourself, $barrier - ); + ]; } private function getUser() { diff --git a/app/Http/Resources/Device.php b/app/Http/Resources/Device.php index 4bdcea8147..ecff6c9890 100644 --- a/app/Http/Resources/Device.php +++ b/app/Http/Resources/Device.php @@ -19,6 +19,13 @@ * example=1 * ), * @OA\Property( + * property="eventid", + * title="eventid", + * description="The event to which this device belongs.", + * format="int64", + * example=1 + * ), + * @OA\Property( * property="category", * title="category", * description="The id of the category to which this item belongs.", @@ -138,6 +145,7 @@ public function toArray($request) { $ret = [ 'id' => intval($this->iddevices), + 'eventid' => intval($this->event), 'category' => intval($this->category), 'item_type' => $this->item_type, 'brand' => $this->brand, @@ -155,38 +163,38 @@ public function toArray($request) // There is mirror code in API\DeviceController. switch ($this->repair_status) { case \App\Device::REPAIR_STATUS_FIXED: - $ret['repair_status'] = 'Fixed'; + $ret['repair_status'] = \App\Device::REPAIR_STATUS_FIXED_STR; break; case \App\Device::REPAIR_STATUS_REPAIRABLE: - $ret['repair_status'] = 'Repairable'; - - if ($this->more_time_needed) { - $ret['next_steps'] = 'More time needed'; - } else if ($this->professional_help) { - $ret['next_steps'] = 'Professional help'; - } else if ($this->do_it_yourself) { - $ret['next_steps'] = 'Do it yourself'; - } - - if ($this->parts_provider == \App\Device::PARTS_PROVIDER_MANUFACTURER) { - $ret['spare_parts'] = 'Manufacturer'; - } else if ($this->parts_provider == \App\Device::PARTS_PROVIDER_THIRD_PARTY) { - $ret['spare_parts'] = 'Third party'; - } else { - $ret['spare_parts'] = 'No'; - } + $ret['repair_status'] = \App\Device::REPAIR_STATUS_REPAIRABLE_STR; break; case \App\Device::REPAIR_STATUS_ENDOFLIFE: - $ret['repair_status'] = 'End of life'; - - // The underlying DB might have multiple barriers, but we only support one across the API. - foreach ($this->resource->barriers as $barrier) { - $ret['barrier'] = $barrier->barrier; - break; - } + $ret['repair_status'] = \App\Device::REPAIR_STATUS_ENDOFLIFE_STR; break; } + if ($this->more_time_needed) { + $ret['next_steps'] = \App\Device::NEXT_STEPS_MORE_TIME_NEEDED_STR; + } else if ($this->professional_help) { + $ret['next_steps'] = \App\Device::NEXT_STEPS_PROFESSIONAL_HELP_STR; + } else if ($this->do_it_yourself) { + $ret['next_steps'] = \App\Device::NEXT_STEPS_DO_IT_YOURSELF_STR; + } + + if ($this->parts_provider == \App\Device::PARTS_PROVIDER_MANUFACTURER) { + $ret['spare_parts'] = \App\Device::PARTS_PROVIDER_MANUFACTURER_STR; + } else if ($this->parts_provider == \App\Device::PARTS_PROVIDER_THIRD_PARTY) { + $ret['spare_parts'] = \App\Device::PARTS_PROVIDER_THIRD_PARTY_STR; + } else { + $ret['spare_parts'] = \App\Device::PARTS_PROVIDER_NO_STR; + } + + // The underlying DB might have multiple barriers, but we only support one across the API. + foreach ($this->resource->barriers as $barrier) { + $ret['barrier'] = $barrier->barrier; + break; + } + return $ret; } } diff --git a/lang/en/devices.php b/lang/en/devices.php index a0738d7f20..0deb864223 100644 --- a/lang/en/devices.php +++ b/lang/en/devices.php @@ -79,6 +79,5 @@ 'image_delete_success' => 'Thank you, the image has been deleted', 'image_delete_error' => 'Sorry, but the image can\'t be deleted', 'image_upload_error' => 'fail - image could not be uploaded', - 'device_delete_sucess' => 'Device has been deleted!', 'device_delete_permissions' => 'You do not have the right permissions for deleting a device.', ]; diff --git a/lang/fr-BE/devices.php b/lang/fr-BE/devices.php index 5798fdc0fa..a3f0ca052d 100644 --- a/lang/fr-BE/devices.php +++ b/lang/fr-BE/devices.php @@ -79,6 +79,5 @@ 'image_delete_success' => 'Merci, l\'image a été supprimée', 'image_delete_error' => 'Désolé, mais l\'image ne peut pas être supprimée.', 'image_upload_error' => 'fail - l\'image n\'a pas pu être téléchargée', - 'device_delete_sucess' => 'L\'appareil a été supprimé!', 'device_delete_permissions' => 'Vous n\'avez pas les autorisations nécessaires pour supprimer un appareil.', ]; diff --git a/lang/fr/devices.php b/lang/fr/devices.php index 5798fdc0fa..a3f0ca052d 100644 --- a/lang/fr/devices.php +++ b/lang/fr/devices.php @@ -79,6 +79,5 @@ 'image_delete_success' => 'Merci, l\'image a été supprimée', 'image_delete_error' => 'Désolé, mais l\'image ne peut pas être supprimée.', 'image_upload_error' => 'fail - l\'image n\'a pas pu être téléchargée', - 'device_delete_sucess' => 'L\'appareil a été supprimé!', 'device_delete_permissions' => 'Vous n\'avez pas les autorisations nécessaires pour supprimer un appareil.', ]; diff --git a/routes/api.php b/routes/api.php index cd7a2dbe53..7fee321182 100644 --- a/routes/api.php +++ b/routes/api.php @@ -114,6 +114,7 @@ Route::get('{id}', [API\DeviceController::class, 'getDevicev2']); Route::post('', [API\DeviceController::class, 'createDevicev2']); Route::patch('{id}', [API\DeviceController::class, 'updateDevicev2']); + Route::delete('{id}', [API\DeviceController::class, 'deleteDevicev2']); }); }); }); \ No newline at end of file diff --git a/tests/Feature/Devices/APIv2DeviceTest.php b/tests/Feature/Devices/APIv2DeviceTest.php index af66b7523d..b1cb88c5ee 100644 --- a/tests/Feature/Devices/APIv2DeviceTest.php +++ b/tests/Feature/Devices/APIv2DeviceTest.php @@ -28,7 +28,7 @@ class APIv2DeviceTest extends TestCase * * @dataProvider providerDevice */ - public function testGetDevice($repair_status, $repair_status_str, $spare_parts, $parts_provider, $parts_provider_str, $barrierid, $barrierstr, $professional_help, $more_time_needed, $do_it_yourself, $next_steps_str) { + public function testGetDevice($repair_status_str, $parts_provider_str, $barrierstr, $next_steps_str) { $this->loginAsTestUser(Role::ADMINISTRATOR); $idGroup = $this->createGroup(); $this->assertNotNull($idGroup); @@ -36,6 +36,29 @@ public function testGetDevice($repair_status, $repair_status_str, $spare_parts, $idEvents = $this->createEvent($idGroup, 'yesterday'); $this->assertNotNull($idEvents); + switch ($repair_status_str) { + case Device::REPAIR_STATUS_FIXED_STR: + $repair_status = Device::REPAIR_STATUS_FIXED; + break; + case Device::REPAIR_STATUS_REPAIRABLE_STR: + $repair_status = Device::REPAIR_STATUS_REPAIRABLE; + break; + case Device::REPAIR_STATUS_ENDOFLIFE_STR: + $repair_status = Device::REPAIR_STATUS_ENDOFLIFE; + break; + } + + switch ($parts_provider_str) { + case Device::PARTS_PROVIDER_NO_STR: + $parts_provider = 0; + break; + case Device::PARTS_PROVIDER_THIRD_PARTY_STR: + $parts_provider = Device::PARTS_PROVIDER_THIRD_PARTY; + break; + case Device::PARTS_PROVIDER_MANUFACTURER_STR: + $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; + } + $device = Device::create([ 'event' => $idEvents, 'category' => 11, @@ -48,23 +71,12 @@ public function testGetDevice($repair_status, $repair_status_str, $spare_parts, 'estimate' => 100.00, 'item_type' => 'Test item type', 'repair_status' => $repair_status, - 'spare_parts' => $spare_parts, 'parts_provider' => $parts_provider, - 'professional_help' => $professional_help, - 'more_time_needed' => $more_time_needed, - 'do_it_yourself' => $do_it_yourself, ]); $iddevices = $device->iddevices; $this->assertNotNull($iddevices); - if ($barrierid) { - DeviceBarrier::create([ - 'device_id' => $iddevices, - 'barrier_id' => $barrierid, - ]); - } - // Test invalid device id. try { $this->get('/api/v2/devices/-1'); @@ -86,11 +98,11 @@ public function testGetDevice($repair_status, $repair_status_str, $spare_parts, $this->assertEquals('Test notes', $json['data']['notes']); $this->assertEquals($repair_status_str, $json['data']['repair_status']); - if ($parts_provider) { + if ($parts_provider_str) { $this->assertEquals($parts_provider_str, $json['data']['spare_parts']); } - if ($barrierid) { + if ($barrierstr) { $this->assertEquals($barrierstr, $json['data']['barrier']); } @@ -104,7 +116,7 @@ public function testGetDevice($repair_status, $repair_status_str, $spare_parts, * * @dataProvider providerDevice */ - public function testCreate($repair_status, $repair_status_str, $spare_parts, $parts_provider, $parts_provider_str, $barrierid, $barrierstr, $professional_help, $more_time_needed, $do_it_yourself, $next_steps_str) { + public function testCreate($repair_status_str, $parts_provider_str, $barrierstr, $next_steps_str) { $this->loginAsTestUser(Role::ADMINISTRATOR); $idGroup = $this->createGroup(); $this->assertNotNull($idGroup); @@ -123,7 +135,7 @@ public function testCreate($repair_status, $repair_status_str, $spare_parts, $pa 'estimate' => 100.00, 'item_type' => 'Test item type', 'repair_status' => $repair_status_str, - 'parts_provider' => $parts_provider, + 'parts_provider' => $parts_provider_str, 'barrier' => $barrierstr, ]; @@ -157,11 +169,11 @@ public function testCreate($repair_status, $repair_status_str, $spare_parts, $pa $this->assertEquals('Test notes', $json['data']['notes']); $this->assertEquals($repair_status_str, $json['data']['repair_status']); - if ($parts_provider) { + if ($parts_provider_str) { $this->assertEquals($parts_provider_str, $json['data']['spare_parts']); } - if ($barrierid) { + if ($barrierstr) { $this->assertEquals($barrierstr, $json['data']['barrier']); } @@ -172,45 +184,28 @@ public function testCreate($repair_status, $repair_status_str, $spare_parts, $pa public function providerDevice() { + // TODO Neil - I am not confident that I know the valid combinations of repair status/parts provider/ + // barrier/next steps. + // + // Please can you consider which values we should be supporting? return [ [ - Device::REPAIR_STATUS_FIXED, - 'Fixed', - Device::SPARE_PARTS_NOT_NEEDED, + Device::REPAIR_STATUS_FIXED_STR, + Device::PARTS_PROVIDER_NO_STR, null, null, - 0, - null, - 0, - 0, - 0, - null ], [ - Device::REPAIR_STATUS_REPAIRABLE, - 'Repairable', - Device::SPARE_PARTS_NEEDED, - Device::PARTS_PROVIDER_THIRD_PARTY, - 'Third party', - 0, - null, - 1, - 0, - 0, - 'Professional help' + Device::REPAIR_STATUS_REPAIRABLE_STR, + Device::PARTS_PROVIDER_THIRD_PARTY_STR, + Device::BARRIER_SPARE_PARTS_NOT_AVAILABLE_STR, + Device::NEXT_STEPS_PROFESSIONAL_HELP_STR, ], [ - Device::REPAIR_STATUS_ENDOFLIFE, - 'End of life', - Device::SPARE_PARTS_NOT_NEEDED, - null, + Device::REPAIR_STATUS_ENDOFLIFE_STR, + Device::PARTS_PROVIDER_NO_STR, + Device::BARRIER_SPARE_PARTS_NOT_AVAILABLE_STR, null, - 1, - 'Spare parts not available', - 0, - 0, - 0, - null ], ]; } diff --git a/tests/Feature/Devices/EditTest.php b/tests/Feature/Devices/EditTest.php index fabd2a150c..5646cd68ee 100644 --- a/tests/Feature/Devices/EditTest.php +++ b/tests/Feature/Devices/EditTest.php @@ -10,6 +10,7 @@ use DB; use Illuminate\Support\Facades\Storage; use Tests\TestCase; +use Illuminate\Database\Eloquent\ModelNotFoundException; class EditTest extends TestCase { @@ -19,10 +20,6 @@ protected function setUp(): void $this->event = Party::factory()->create(); $this->admin = User::factory()->administrator()->create(); - $this->device_inputs = Device::factory()->raw([ - 'event_id' => $this->event->idevents, - 'quantity' => 1, - ]); $this->actingAs($this->admin); $this->withoutExceptionHandling(); @@ -30,32 +27,27 @@ protected function setUp(): void public function testEdit() { - $rsp = $this->post('/device/create', $this->device_inputs); - self::assertTrue($rsp['success']); - $iddevices = $rsp['devices'][0]['iddevices']; - self::assertNotNull($iddevices); + $iddevices = $this->createDevice($this->event->idevents, 'misc'); # Add a barrier to repair - there was a bug in this case with quantity > 1. - $this->device_inputs['repair_status'] = Device::REPAIR_STATUS_ENDOFLIFE; - $this->device_inputs['barrier'] = [1]; + $iddevices = $this->createDevice($this->event->idevents, 'misc', Device::BARRIER_SPARE_PARTS_NOT_AVAILABLE_STR); - # Edit the quantity. - $atts = $this->device_inputs; - $atts['quantity'] = 2; - $rsp = $this->post('/device/edit/' . $iddevices, $atts); - self::assertEquals('Device updated!', $rsp['success']); + # Edit the problem. + $atts = $this->getDevice($iddevices); + $atts['problem'] = 'New problem'; + + $response = $this->patch("/api/v2/devices/$iddevices", $atts); + $response->assertSuccessful(); + + $atts = $this->getDevice($iddevices); + $this->assertEquals('New problem', $atts['problem']); # Delete the device. - $rsp = $this->get('/device/delete/' . $iddevices, [ - 'HTTP_X-Requested-With' => 'XMLHttpRequest' - ]); - self::assertTrue($rsp['success']); + $this->deleteDevice($iddevices); # Delete again - should fail. - $rsp = $this->get('/device/delete/' . $iddevices, [ - 'HTTP_X-Requested-With' => 'XMLHttpRequest' - ]); - self::assertFalse($rsp['success']); + $this->expectException(ModelNotFoundException::class); + $this->deleteDevice($iddevices); } public function testEditAsNetworkCoordinator() @@ -74,21 +66,14 @@ public function testEditAsNetworkCoordinator() $network->addCoordinator($coordinator); $this->actingAs($coordinator); - $device_inputs = Device::factory()->raw([ - 'event_id' => $event->idevents, - 'quantity' => 1, - ]); - - $rsp = $this->post('/device/create', $device_inputs); - self::assertTrue($rsp['success']); - $iddevices = $rsp['devices'][0]['iddevices']; - self::assertNotNull($iddevices); - - # Edit the quantity. - $atts = $device_inputs; - $atts['quantity'] = 2; - $rsp = $this->post('/device/edit/' . $iddevices, $atts); - self::assertEquals('Device updated!', $rsp['success']); + $iddevices = $this->createDevice($event->idevents, 'misc'); + + # Edit the problem. + $atts = $this->getDevice($iddevices); + $atts['problem'] = 'New problem'; + + $response = $this->patch("/api/v2/devices/$iddevices", $atts); + $response->assertSuccessful(); } public function testDeviceEditAddImage() { @@ -96,10 +81,7 @@ public function testDeviceEditAddImage() { $user = User::factory()->administrator()->create(); $this->actingAs($user); - $rsp = $this->post('/device/create', $this->device_inputs); - self::assertTrue($rsp['success']); - $iddevices = $rsp['devices'][0]['iddevices']; - self::assertNotNull($iddevices); + $iddevices = $this->createDevice($this->event->idevents, 'misc'); // Try with no file. $response = $this->json('POST', '/device/image-upload/' . $iddevices, []); @@ -214,21 +196,13 @@ public function testDeviceAddAddImage() { } public function testNextSteps() { - $device_inputs = Device::factory()->raw([ - 'event_id' => $this->event->idevents, - 'quantity' => 1, - 'repair_status' => 2, - ]); - $rsp = $this->post('/device/create', $device_inputs); - self::assertTrue($rsp['success']); - $iddevices = $rsp['devices'][0]['iddevices']; - self::assertNotNull($iddevices); + $iddevices = $this->createDevice($this->event->idevents, 'misc'); # Edit the repair details to say more time needed - $atts = $device_inputs; - $atts['repair_details'] = 1; - $rsp = $this->post('/device/edit/' . $iddevices, $atts); - self::assertEquals('Device updated!', $rsp['success']); + $atts = $this->getDevice($iddevices); + $atts['next_steps'] = Device::NEXT_STEPS_MORE_TIME_NEEDED_STR; + $response = $this->patch("/api/v2/devices/$iddevices", $atts); + $response->assertSuccessful(); # Check the resulting fields. $device = Device::findOrFail($iddevices); @@ -237,10 +211,10 @@ public function testNextSteps() { self::assertEquals(1, $device->more_time_needed); # Edit the repair details to say professional help needed. - $atts = $device_inputs; - $atts['repair_details'] = 2; - $rsp = $this->post('/device/edit/' . $iddevices, $atts); - self::assertEquals('Device updated!', $rsp['success']); + $atts = $this->getDevice($iddevices); + $atts['next_steps'] = Device::NEXT_STEPS_PROFESSIONAL_HELP_STR; + $response = $this->patch("/api/v2/devices/$iddevices", $atts); + $response->assertSuccessful(); # Check the resulting fields. $device = Device::findOrFail($iddevices); @@ -249,10 +223,10 @@ public function testNextSteps() { self::assertEquals(0, $device->more_time_needed); # Edit the repair details to say DIY needed. - $atts = $device_inputs; - $atts['repair_details'] = 3; - $rsp = $this->post('/device/edit/' . $iddevices, $atts); - self::assertEquals('Device updated!', $rsp['success']); + $atts = $this->getDevice($iddevices); + $atts['next_steps'] = Device::NEXT_STEPS_DO_IT_YOURSELF_STR; + $response = $this->patch("/api/v2/devices/$iddevices", $atts); + $response->assertSuccessful(); # Check the resulting fields. $device = Device::findOrFail($iddevices); @@ -260,17 +234,4 @@ public function testNextSteps() { self::assertEquals(1, $device->do_it_yourself); self::assertEquals(0, $device->more_time_needed); } - - public function testBarrierMultiple() - { - $atts = $this->device_inputs; - $atts['quantity'] = 2; - $atts['repair_status'] = Device::REPAIR_STATUS_ENDOFLIFE; - $atts['barrier'] = [1]; - - $rsp = $this->post('/device/create', $atts); - self::assertTrue($rsp['success']); - $iddevices = $rsp['devices'][0]['iddevices']; - self::assertNotNull($iddevices); - } } diff --git a/tests/Feature/Devices/NullAgeProblemTest.php b/tests/Feature/Devices/NullAgeProblemTest.php index 377a1efe89..65ef3e0a05 100644 --- a/tests/Feature/Devices/NullAgeProblemTest.php +++ b/tests/Feature/Devices/NullAgeProblemTest.php @@ -7,32 +7,39 @@ use App\User; use DB; use Tests\TestCase; +use Illuminate\Validation\ValidationException; class NullAgeProblemTest extends TestCase { - public function testNullAge() + public function testNullAgeCreate() { $event = Party::factory()->create(); - $this->device_inputs = Device::factory()->raw([ - 'event_id' => $event->idevents, - 'quantity' => 1, - 'age' => null - ]); $admin = User::factory()->administrator()->create(); $this->actingAs($admin); - $this->post('/device/create', $this->device_inputs); - $iddevices = Device::latest()->first()->iddevices; - $device = Device::find($iddevices); - $this->assertEquals(0, $device->age); + // Creating a device with a null age should result in an exception. We are supposed to pass in 0, + // which can either mean "age not known" or "age < 1 year". + $this->expectException(ValidationException::class); + $iddevices = $this->createDevice($event->idevents, 'misc', null, null); + } + + public function testNullAgeEdit() + { + $event = Party::factory()->create(); - $this->device_inputs['age'] = 1; - $rsp = $this->post('/device/edit/' . $iddevices, $this->device_inputs); - self::assertEquals('Device updated!', $rsp['success']); + $admin = User::factory()->administrator()->create(); + $this->actingAs($admin); + + $iddevices = $this->createDevice($event->idevents, 'misc', null, 1); + $device = Device::find($iddevices); + $this->assertEquals(1, $device->age); - $this->device_inputs['age'] = null; - $rsp = $this->post('/device/edit/' . $iddevices, $this->device_inputs); - self::assertEquals('Device updated!', $rsp['success']); + // Editing a device with a null age should result in an exception. We are supposed to pass in 0, + // which can either mean "age not known" or "age < 1 year". + $atts = $this->getDevice($iddevices); + $atts['age'] = null; + $this->expectException(ValidationException::class); + $response = $this->patch("/api/v2/devices/$iddevices", $atts); } } diff --git a/tests/Feature/Devices/NullEstimateProblemTest.php b/tests/Feature/Devices/NullEstimateProblemTest.php index c79ee95484..674ef12672 100644 --- a/tests/Feature/Devices/NullEstimateProblemTest.php +++ b/tests/Feature/Devices/NullEstimateProblemTest.php @@ -7,32 +7,38 @@ use App\User; use DB; use Tests\TestCase; +use Illuminate\Validation\ValidationException; class NullEstimateProblemTest extends TestCase { - public function testNullAge() + public function testNullEstimateCreate() { $event = Party::factory()->create(); - $this->device_inputs = Device::factory()->raw([ - 'event_id' => $event->idevents, - 'quantity' => 1, - 'estimate' => null - ]); $admin = User::factory()->administrator()->create(); $this->actingAs($admin); - $this->post('/device/create', $this->device_inputs); - $iddevices = Device::latest()->first()->iddevices; - $device = Device::find($iddevices); - $this->assertEquals(0, $device->estimate); + // Creating a device with a null estimate should result in an exception. We are supposed to pass in 0. + $this->expectException(ValidationException::class); + $iddevices = $this->createDevice($event->idevents, 'misc', null, 1, null); + } + + public function testNullEstimateEdit() + { + $event = Party::factory()->create(); - $this->device_inputs['estimate'] = 1; - $rsp = $this->post('/device/edit/' . $iddevices, $this->device_inputs); - self::assertEquals('Device updated!', $rsp['success']); + $admin = User::factory()->administrator()->create(); + $this->actingAs($admin); + + $iddevices = $this->createDevice($event->idevents, 'misc', null, 1, 100); + $device = Device::find($iddevices); + $this->assertEquals(1, $device->age); - $this->device_inputs['estimate'] = null; - $rsp = $this->post('/device/edit/' . $iddevices, $this->device_inputs); - self::assertEquals('Device updated!', $rsp['success']); + // Editing a device with a null age should result in an exception. We are supposed to pass in 0, + // which can either mean "age not known" or "age < 1 year". + $atts = $this->getDevice($iddevices); + $atts['estimate'] = null; + $this->expectException(ValidationException::class); + $response = $this->patch("/api/v2/devices/$iddevices", $atts); } } diff --git a/tests/Feature/Devices/NullProblemTest.php b/tests/Feature/Devices/NullProblemTest.php index dac304058c..ebc48636a9 100644 --- a/tests/Feature/Devices/NullProblemTest.php +++ b/tests/Feature/Devices/NullProblemTest.php @@ -19,12 +19,7 @@ protected function setUp(): void Device::truncate(); DB::statement('SET foreign_key_checks=1'); - $event = Party::factory()->create(); - $this->device_inputs = Device::factory()->raw([ - 'event_id' => $event->idevents, - 'quantity' => 1, - ]); - + $this->event = Party::factory()->create(); $admin = User::factory()->administrator()->create(); $this->actingAs($admin); @@ -34,8 +29,7 @@ protected function setUp(): void /** @test */ public function null_problem_mapped_to_empty_string() { - $this->device_inputs['problem'] = null; - $this->post('/device/create', $this->device_inputs); + $iddevices = $this->createDevice($this->event->idevents, 'misc', null, 1, 100, null); $iddevices = Device::latest()->first()->iddevices; $device = Device::find($iddevices); diff --git a/tests/Feature/Devices/SparePartsTest.php b/tests/Feature/Devices/SparePartsTest.php index d793f51988..e2b852eb4c 100644 --- a/tests/Feature/Devices/SparePartsTest.php +++ b/tests/Feature/Devices/SparePartsTest.php @@ -15,9 +15,9 @@ protected function setUp(): void { parent::setUp(); - $event = Party::factory()->create(); + $this->event = Party::factory()->create(); $this->device_inputs = Device::factory()->raw([ - 'event_id' => $event->idevents, + 'event_id' => $this->event->idevents, 'quantity' => 1, ]); @@ -34,10 +34,11 @@ protected function setUp(): void /** @test */ public function recording_spare_parts_from_manufacturer() { - $this->device_inputs['repair_status'] = Device::REPAIR_STATUS_FIXED; - $this->device_inputs['spare_parts'] = $this->input_spare_parts_from_manufacturer; - $response = $this->post('/device/create', $this->device_inputs); - $iddevices = Device::latest()->first()->iddevices; + $iddevices = $this->createDevice($this->event->idevents, + 'misc', null, 1.5, 100, '', + Device::REPAIR_STATUS_FIXED_STR, + Device::NEXT_STEPS_MORE_TIME_NEEDED_STR, + Device::PARTS_PROVIDER_MANUFACTURER_STR); $device = Device::find($iddevices); $this->assertEquals(Device::SPARE_PARTS_NEEDED, $device->spare_parts); @@ -52,8 +53,11 @@ public function recording_spare_parts_from_third_party() $this->device_inputs['repair_status'] = Device::REPAIR_STATUS_REPAIRABLE; $this->device_inputs['spare_parts'] = $this->input_spare_parts_from_third_party; - $response = $this->post('/device/create', $this->device_inputs); - $iddevices = Device::latest()->first()->iddevices; + $iddevices = $this->createDevice($this->event->idevents, + 'misc', null, 1.5, 100, '', + Device::REPAIR_STATUS_REPAIRABLE_STR, + Device::NEXT_STEPS_MORE_TIME_NEEDED_STR, + Device::PARTS_PROVIDER_THIRD_PARTY_STR); $device = Device::find($iddevices); $this->assertEquals(trans('partials.repairable'), $device->getRepairStatus()); @@ -65,11 +69,11 @@ public function recording_spare_parts_from_third_party() /** @test */ public function recording_no_spare_parts_needed() { - $this->device_inputs['repair_status'] = Device::REPAIR_STATUS_FIXED; - $this->device_inputs['spare_parts'] = $this->input_no_spare_parts_needed; - - $response = $this->post('/device/create', $this->device_inputs); - $iddevices = Device::latest()->first()->iddevices; + $iddevices = $this->createDevice($this->event->idevents, + 'misc', null, 1.5, 100, '', + Device::REPAIR_STATUS_FIXED_STR, + NULL, + Device::PARTS_PROVIDER_NO_STR); $device = Device::find($iddevices); $this->assertEquals(Device::SPARE_PARTS_NOT_NEEDED, $device->spare_parts); @@ -80,11 +84,9 @@ public function recording_no_spare_parts_needed() /** @test */ public function recording_spare_parts_related_barrier() { - $this->device_inputs['repair_status'] = Device::REPAIR_STATUS_ENDOFLIFE; - $this->device_inputs['barrier'] = [1]; - - $response = $this->post('/device/create', $this->device_inputs); - $iddevices = Device::latest()->first()->iddevices; + $iddevices = $this->createDevice($this->event->idevents, + 'misc', Device::BARRIER_SPARE_PARTS_NOT_AVAILABLE_STR, 1.5, 100, '', + Device::REPAIR_STATUS_ENDOFLIFE_STR, null, Device::PARTS_PROVIDER_MANUFACTURER_STR); $device = Device::find($iddevices); $this->assertEquals(Device::SPARE_PARTS_NEEDED, $device->spare_parts); diff --git a/tests/TestCase.php b/tests/TestCase.php index 152cc919e5..199a57af87 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -30,7 +30,6 @@ use Illuminate\Support\Facades\Queue; use Symfony\Component\DomCrawler\Crawler; use Osteel\OpenApi\Testing\ValidatorBuilder; -use Osteel\OpenApi\Testing\Exceptions\ValidationException; abstract class TestCase extends BaseTestCase { @@ -270,20 +269,81 @@ public function createEvent($idgroups, $date, $assert = true, $approve = false) return $idevents; } - public function createDevice($idevents, $type) + public function createDevice($idevents, $type, $barrierstr = null, $age = 1.5, $estimate = 100, $problem = '', $repair_status = NULL, $next_steps = NULL, $spare_parts = NULL) { + // Many tests use $type to create a device from DeviceFactory. $deviceAttributes = Device::factory()->{lcfirst($type)}()->raw(); - $deviceAttributes['event_id'] = $idevents; - $deviceAttributes['quantity'] = 1; + if (array_key_exists('problem', $deviceAttributes)) { + $problem = $deviceAttributes['problem']; + } + + // The v2 API takes the repair stats as a string + if (!$repair_status) { + $rs = array_key_exists('repair_status', $deviceAttributes) ? $deviceAttributes['repair_status'] : Device::REPAIR_STATUS_REPAIRABLE; + + switch ($rs) { + case Device::REPAIR_STATUS_FIXED: + $repair_status = Device::REPAIR_STATUS_FIXED_STR; + break; + case Device::REPAIR_STATUS_REPAIRABLE: + $repair_status = Device::REPAIR_STATUS_REPAIRABLE_STR; + break; + case Device::REPAIR_STATUS_ENDOFLIFE: + $repair_status = Device::REPAIR_STATUS_ENDOFLIFE_STR; + break; + default: + $this->assertTrue(false); + } + } + + $params = [ + 'eventid' => $idevents, + 'category' => $deviceAttributes['category'], + 'problem' => $problem, + 'notes' => 'Test notes', + 'brand' => 'Test brand', + 'model' => 'Test model', + 'age' => $age, + 'estimate' => $estimate, + 'item_type' => 'Test item type', + 'repair_status' => $repair_status, + 'barrier' => $barrierstr, + ]; + + if ($next_steps) { + $params['next_steps'] = $next_steps; + } + + if ($spare_parts) { + $params['spare_parts'] = $spare_parts; + } - $response = $this->post('/device/create', $deviceAttributes); - $iddevices = Device::latest()->first()->iddevices; + $response = $this->post('/api/v2/devices', $params); + + $this->assertTrue($response->isSuccessful()); + $json = json_decode($response->getContent(), true); + $this->assertTrue(array_key_exists('id', $json)); + $iddevices = $json['id']; $this->assertNotNull($iddevices); return $iddevices; } + public function getDevice($iddevices) { + $response = $this->get("/api/v2/devices/$iddevices"); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $atts = $json['data']; + return $atts; + } + + public function deleteDevice($iddevices) + { + $response = $this->delete("/api/v2/devices/$iddevices"); + $this->assertTrue($response->isSuccessful()); + } + public function createJane() { $user = User::factory()->create([ From 35898983c3790a51048e3b8bf067a991e35e9004 Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 24 Jun 2024 10:23:34 +0100 Subject: [PATCH 07/34] Migrate more tests over to the v2 API call. --- app/Http/Controllers/API/DeviceController.php | 47 ++++++++++++++----- lang/en/devices.php | 1 - lang/fr-BE/devices.php | 1 - lang/fr/devices.php | 1 - tests/Feature/Devices/APIv2DeviceTest.php | 28 ++++++++++- tests/Feature/Devices/SparePartsTest.php | 10 ++-- .../{TooManyMisc.php => TooManyMiscTest.php} | 7 +-- tests/Feature/Groups/GroupViewTest.php | 12 ++--- tests/TestCase.php | 6 ++- 9 files changed, 78 insertions(+), 35 deletions(-) rename tests/Feature/Devices/{TooManyMisc.php => TooManyMiscTest.php} (88%) diff --git a/app/Http/Controllers/API/DeviceController.php b/app/Http/Controllers/API/DeviceController.php index ad81327f8a..920868a347 100644 --- a/app/Http/Controllers/API/DeviceController.php +++ b/app/Http/Controllers/API/DeviceController.php @@ -6,9 +6,9 @@ use App\Device; use App\DeviceBarrier; use App\Events\DeviceCreatedOrUpdated; -use App\EventsUsers; use App\Helpers\Fixometer; use App\Http\Controllers\Controller; +use App\Notifications\AdminAbnormalDevices; use App\Party; use Illuminate\Auth\AuthenticationException; use Illuminate\Validation\ValidationException; @@ -17,6 +17,7 @@ use Illuminate\Http\Request; use Carbon\Carbon; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\DB; class DeviceController extends Controller { /** @@ -161,7 +162,7 @@ public function createDevicev2(Request $request) { $user = $this->getUser(); - list($partyid, + list($eventid, $category, $item_type, $brand, @@ -180,15 +181,15 @@ public function createDevicev2(Request $request) $barrier ) = $this->validateDeviceParams($request,true); - Party::findOrFail($partyid); + $event = Party::findOrFail($eventid); - if (!Fixometer::userHasEditEventsDevicesPermission($partyid, $user->id)) { + if (!Fixometer::userHasEditEventsDevicesPermission($eventid, $user->id)) { // Only hosts can add devices to events. abort(403); } $data = [ - 'event' => $partyid, + 'event' => $eventid, 'category' => $category, 'category_creation' => $category, // We don't expose this over the API but we record it in case it changes. 'item_type' => $item_type, @@ -220,6 +221,17 @@ public function createDevicev2(Request $request) 'barrier_id' => $barrier ]); } + + // If the number of devices exceeds set amount then notify admins. + $deviceMiscCount = DB::table('devices')->where('category', env('MISC_CATEGORY_ID_POWERED'))->where('event', $eventid)->count() + + DB::table('devices')->where('category', env('MISC_CATEGORY_ID_UNPOWERED'))->where('event', $eventid)->count(); + if ($deviceMiscCount == env('DEVICE_ABNORMAL_MISC_COUNT', 5)) { + $notify_users = Fixometer::usersWhoHavePreference('admin-abnormal-devices'); + Notification::send($notify_users, new AdminAbnormalDevices([ + 'event_venue' => $event->getEventName(), + 'event_url' => url('/party/edit/'.$eventid), + ])); + } } // TODO Images - probably a separate API Call. @@ -330,7 +342,7 @@ public function updateDevicev2(Request $request, $iddevices) { $user = $this->getUser(); - list($partyid, + list($eventid, $category, $item_type, $brand, @@ -349,15 +361,15 @@ public function updateDevicev2(Request $request, $iddevices) $barrier ) = $this->validateDeviceParams($request,false); - Party::findOrFail($partyid); + Party::findOrFail($eventid); - if (!Fixometer::userHasEditEventsDevicesPermission($partyid, $user->id)) { + if (!Fixometer::userHasEditEventsDevicesPermission($eventid, $user->id)) { // Only hosts can add devices to events. abort(403); } $data = [ - 'event' => $partyid, + 'event' => $eventid, 'category' => $category, 'item_type' => $item_type, 'brand' => $brand, @@ -479,7 +491,7 @@ private function validateDeviceParams(Request $request, $create): array ]); } - $partyid = $request->input('eventid'); + $eventid = $request->input('eventid'); $category = $request->input('category'); $item_type = $request->input('item_type'); $brand = $request->input('brand'); @@ -553,17 +565,25 @@ private function validateDeviceParams(Request $request, $create): array break; case Device::PARTS_PROVIDER_MANUFACTURER_STR: $spare_parts = Device::SPARE_PARTS_NEEDED; - $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; + + if ($repair_status != Device::REPAIR_STATUS_ENDOFLIFE) { + // If it is end of life we record that the parts are needed, but there is no provider. + $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; + } break; case Device::PARTS_PROVIDER_THIRD_PARTY_STR: $spare_parts = Device::SPARE_PARTS_NEEDED; - $parts_provider = Device::PARTS_PROVIDER_THIRD_PARTY; + + if ($repair_status != Device::REPAIR_STATUS_ENDOFLIFE) { + // If it is end of life we record that the parts are needed, but there is no provider. + $parts_provider = Device::PARTS_PROVIDER_THIRD_PARTY; + } break; } } return [ - $partyid, + $eventid, $category, $item_type, $brand, @@ -582,6 +602,7 @@ private function validateDeviceParams(Request $request, $create): array $barrier ]; } + private function getUser() { // We want to allow this call to work if a) we are logged in as a user, or b) we have a valid API token. diff --git a/lang/en/devices.php b/lang/en/devices.php index 0deb864223..97fdc577d2 100644 --- a/lang/en/devices.php +++ b/lang/en/devices.php @@ -79,5 +79,4 @@ 'image_delete_success' => 'Thank you, the image has been deleted', 'image_delete_error' => 'Sorry, but the image can\'t be deleted', 'image_upload_error' => 'fail - image could not be uploaded', - 'device_delete_permissions' => 'You do not have the right permissions for deleting a device.', ]; diff --git a/lang/fr-BE/devices.php b/lang/fr-BE/devices.php index a3f0ca052d..8e73aab8fb 100644 --- a/lang/fr-BE/devices.php +++ b/lang/fr-BE/devices.php @@ -79,5 +79,4 @@ 'image_delete_success' => 'Merci, l\'image a été supprimée', 'image_delete_error' => 'Désolé, mais l\'image ne peut pas être supprimée.', 'image_upload_error' => 'fail - l\'image n\'a pas pu être téléchargée', - 'device_delete_permissions' => 'Vous n\'avez pas les autorisations nécessaires pour supprimer un appareil.', ]; diff --git a/lang/fr/devices.php b/lang/fr/devices.php index a3f0ca052d..8e73aab8fb 100644 --- a/lang/fr/devices.php +++ b/lang/fr/devices.php @@ -79,5 +79,4 @@ 'image_delete_success' => 'Merci, l\'image a été supprimée', 'image_delete_error' => 'Désolé, mais l\'image ne peut pas être supprimée.', 'image_upload_error' => 'fail - l\'image n\'a pas pu être téléchargée', - 'device_delete_permissions' => 'Vous n\'avez pas les autorisations nécessaires pour supprimer un appareil.', ]; diff --git a/tests/Feature/Devices/APIv2DeviceTest.php b/tests/Feature/Devices/APIv2DeviceTest.php index b1cb88c5ee..3f8c5bd2d1 100644 --- a/tests/Feature/Devices/APIv2DeviceTest.php +++ b/tests/Feature/Devices/APIv2DeviceTest.php @@ -25,6 +25,8 @@ class APIv2DeviceTest extends TestCase { /** * Test that a device we create directly in the DB retrieves as expected over the API. + * This logic duplicates that in DeviceController, but it's worth testing to make sure that the API is + * behaving as we'd expect from the DB entries. * * @dataProvider providerDevice */ @@ -59,6 +61,18 @@ public function testGetDevice($repair_status_str, $parts_provider_str, $barriers $parts_provider = Device::PARTS_PROVIDER_MANUFACTURER; } + switch ($next_steps_str) { + case Device::NEXT_STEPS_MORE_TIME_NEEDED_STR: + $more_time_needed = 1; + break; + case Device::NEXT_STEPS_PROFESSIONAL_HELP_STR: + $professional_help = 1; + break; + case Device::NEXT_STEPS_DO_IT_YOURSELF_STR: + $do_it_yourself = 1; + break; + } + $device = Device::create([ 'event' => $idEvents, 'category' => 11, @@ -72,11 +86,23 @@ public function testGetDevice($repair_status_str, $parts_provider_str, $barriers 'item_type' => 'Test item type', 'repair_status' => $repair_status, 'parts_provider' => $parts_provider, + 'more_time_needed' => $more_time_needed ?? 0, + 'professional_help' => $professional_help ?? 0, + 'do_it_yourself' => $do_it_yourself ?? 0, ]); $iddevices = $device->iddevices; $this->assertNotNull($iddevices); + if ($barrierstr) { + $barrier = DB::table('barriers')->where('barrier', $barrierstr)->first()->id; + + DeviceBarrier::create([ + 'device_id' => $iddevices, + 'barrier_id' => $barrier + ]); + } + // Test invalid device id. try { $this->get('/api/v2/devices/-1'); @@ -187,7 +213,7 @@ public function providerDevice() // TODO Neil - I am not confident that I know the valid combinations of repair status/parts provider/ // barrier/next steps. // - // Please can you consider which values we should be supporting? + // Please can you consider which values we should be testing here? return [ [ Device::REPAIR_STATUS_FIXED_STR, diff --git a/tests/Feature/Devices/SparePartsTest.php b/tests/Feature/Devices/SparePartsTest.php index e2b852eb4c..3f97328b15 100644 --- a/tests/Feature/Devices/SparePartsTest.php +++ b/tests/Feature/Devices/SparePartsTest.php @@ -98,14 +98,12 @@ public function recording_spare_parts_related_barrier() /** @test */ public function recording_no_spare_parts_related_barrier() { - $this->device_inputs['repair_status'] = Device::REPAIR_STATUS_ENDOFLIFE; - $this->device_inputs['barrier'] = [4]; - - $response = $this->post('/device/create', $this->device_inputs); - $iddevices = Device::latest()->first()->iddevices; + $iddevices = $this->createDevice($this->event->idevents, + 'misc', Device::BARRIER_REPAIR_INFORMATION_NOT_AVAILABLE_STR, 1.5, 100, '', + Device::REPAIR_STATUS_ENDOFLIFE_STR, null, Device::PARTS_PROVIDER_MANUFACTURER_STR); $device = Device::find($iddevices); - $this->assertEquals(Device::SPARE_PARTS_NOT_NEEDED, $device->spare_parts); + $this->assertEquals(1, $device->spare_parts); $this->assertNull($device->parts_provider); } } diff --git a/tests/Feature/Devices/TooManyMisc.php b/tests/Feature/Devices/TooManyMiscTest.php similarity index 88% rename from tests/Feature/Devices/TooManyMisc.php rename to tests/Feature/Devices/TooManyMiscTest.php index 50bae42b09..fc30e87d84 100644 --- a/tests/Feature/Devices/TooManyMisc.php +++ b/tests/Feature/Devices/TooManyMiscTest.php @@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Notification; use Tests\TestCase; -class TooManyTest extends TestCase +class TooManyMiscTest extends TestCase { /** * @dataProvider provider @@ -32,8 +32,9 @@ public function testTooMany($count, $notif) $this->actingAs($this->admin); for ($i = 0; $i < $count; $i++) { - $rsp = $this->post('/device/create', $this->device_inputs); - self::assertTrue($rsp['success']); + echo "Create $i of $count\n"; + $iddevices = $this->createDevice($this->event->idevents, 'misc'); + $this->assertNotNull($iddevices); } if ($notif) { diff --git a/tests/Feature/Groups/GroupViewTest.php b/tests/Feature/Groups/GroupViewTest.php index 04a3834591..983d2d287e 100644 --- a/tests/Feature/Groups/GroupViewTest.php +++ b/tests/Feature/Groups/GroupViewTest.php @@ -91,14 +91,10 @@ public function testCanDelete() ], ]); - $response = $this->post('/device/create', Device::factory()->raw([ - 'category' => env('MISC_CATEGORY_ID_POWERED'), - 'category_creation' => env('MISC_CATEGORY_ID_POWERED'), - 'event_id' => $event->idevents, - 'quantity' => 1, - 'repair_status' => Device::REPAIR_STATUS_FIXED, - 'category' => 111 - ])); + $iddevices = $this->createDevice($event->idevents, + 'misc', null, 1.5, 0, '', + Device::REPAIR_STATUS_FIXED_STR, null, null, 111); + $response = $this->get("/group/view/$id"); $this->assertVueProperties($response, [ [], diff --git a/tests/TestCase.php b/tests/TestCase.php index 199a57af87..48a992cc82 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -269,7 +269,7 @@ public function createEvent($idgroups, $date, $assert = true, $approve = false) return $idevents; } - public function createDevice($idevents, $type, $barrierstr = null, $age = 1.5, $estimate = 100, $problem = '', $repair_status = NULL, $next_steps = NULL, $spare_parts = NULL) + public function createDevice($idevents, $type, $barrierstr = null, $age = 1.5, $estimate = 100, $problem = '', $repair_status = NULL, $next_steps = NULL, $spare_parts = NULL, $category = NULL) { // Many tests use $type to create a device from DeviceFactory. $deviceAttributes = Device::factory()->{lcfirst($type)}()->raw(); @@ -319,6 +319,10 @@ public function createDevice($idevents, $type, $barrierstr = null, $age = 1.5, $ $params['spare_parts'] = $spare_parts; } + if ($category) { + $params['category'] = $category; + } + $response = $this->post('/api/v2/devices', $params); $this->assertTrue($response->isSuccessful()); From c583d093f7e4899ef537cce609edead48d995771 Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 24 Jun 2024 10:56:57 +0100 Subject: [PATCH 08/34] Update store to use new API calls. --- app/Http/Controllers/API/DeviceController.php | 3 + resources/js/components/EventDevice.vue | 2 - resources/js/store/devices.js | 61 +++++++------------ tests/Feature/Devices/APIv2DeviceTest.php | 1 + tests/Feature/Devices/TooManyMiscTest.php | 1 - 5 files changed, 26 insertions(+), 42 deletions(-) diff --git a/app/Http/Controllers/API/DeviceController.php b/app/Http/Controllers/API/DeviceController.php index 920868a347..88b93b5917 100644 --- a/app/Http/Controllers/API/DeviceController.php +++ b/app/Http/Controllers/API/DeviceController.php @@ -235,9 +235,12 @@ public function createDevicev2(Request $request) } // TODO Images - probably a separate API Call. + // We return the device and the stats to save the client another API call to update its store. return response()->json([ 'id' => $idDevice, + 'device' => \App\Http\Resources\Device::make($device), + 'stats' => $event->getEventStats() ]); } diff --git a/resources/js/components/EventDevice.vue b/resources/js/components/EventDevice.vue index 3377aab3fd..fb7399e569 100644 --- a/resources/js/components/EventDevice.vue +++ b/resources/js/components/EventDevice.vue @@ -422,8 +422,6 @@ export default { iddevices: this.device.iddevices, idevents: this.idevents }) - - window.location = '/fixometer' }, } } diff --git a/resources/js/store/devices.js b/resources/js/store/devices.js index b25ef0de53..da59d424eb 100644 --- a/resources/js/store/devices.js +++ b/resources/js/store/devices.js @@ -31,26 +31,7 @@ export default { Vue.set(state.images, d.iddevices, d.images) }) }, - async add (state, params) { - const formData = new FormData() - - for (var key in params) { - if (params[key]) { - formData.append(key, params[key]); - } - } - - let ret = await axios.post('/api/v2/devices?api_token=' + rootGetters['auth/apiToken'], formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }) - - console.log('Create device returned', ret) - if (ret && ret.data) { - id = ret.data.id - } - + add (state, params) { let exists = false if (params.iddevices) { @@ -144,32 +125,34 @@ export default { commit('set', params) }, async add ({commit, dispatch, rootGetters}, params) { - let created = null + const formData = new FormData() - let ret = await axios.post('/device/create', params, { - headers: { - 'X-CSRF-TOKEN': rootGetters['auth/CSRF'] - } - }).catch(function(error) { - if (error && error.response && error.response.data) { - throw new Error(error.response.data.message) - } else { - throw new Error('Unknown error') + for (var key in params) { + if (params[key]) { + formData.append(key, params[key]); } + } + + let ret = await axios.post('/api/v2/devices?api_token=' + rootGetters['auth/apiToken'], formData, { + headers: { + "Content-Type": "multipart/form-data", + }, }) - if (ret && ret.data && ret.data.success && ret.data.devices) { - // We have been returned the device objects from the server. Add them into the store, and lo! All our - // stats and views will update. - created = ret.data.devices + console.log('Create device returned', ret) + let id = null - created.forEach(d => { - commit('add', d) - }) + if (ret && ret.data) { + id = ret.data.id + } + + if (ret && ret.data && ret.data.device) { + // We have been returned the device object from the server. Add it into the store, and lo! All our + // stats and views will update. + const created = ret.data.device + commit('add', ret.data.device) // Update our stats - // TODO LATER There are some uses of event_id in the server which should really be idevents for - // consistency. dispatch('events/setStats', { idevents: params.event_id, stats: ret.data.stats diff --git a/tests/Feature/Devices/APIv2DeviceTest.php b/tests/Feature/Devices/APIv2DeviceTest.php index 3f8c5bd2d1..e9be211cc0 100644 --- a/tests/Feature/Devices/APIv2DeviceTest.php +++ b/tests/Feature/Devices/APIv2DeviceTest.php @@ -180,6 +180,7 @@ public function testCreate($repair_status_str, $parts_provider_str, $barrierstr, $this->assertTrue(array_key_exists('id', $json)); $iddevices = $json['id']; $this->assertNotNull($iddevices); + $this->assertEquals($iddevices, $json['device']['id']); $response = $this->get("/api/v2/devices/$iddevices"); $response->assertSuccessful(); diff --git a/tests/Feature/Devices/TooManyMiscTest.php b/tests/Feature/Devices/TooManyMiscTest.php index fc30e87d84..85104bb138 100644 --- a/tests/Feature/Devices/TooManyMiscTest.php +++ b/tests/Feature/Devices/TooManyMiscTest.php @@ -32,7 +32,6 @@ public function testTooMany($count, $notif) $this->actingAs($this->admin); for ($i = 0; $i < $count; $i++) { - echo "Create $i of $count\n"; $iddevices = $this->createDevice($this->event->idevents, 'misc'); $this->assertNotNull($iddevices); } From eb8db292b4e244d82593d59d08ff46ec3e36feb3 Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 24 Jun 2024 11:49:46 +0100 Subject: [PATCH 09/34] Add devices using new API call --- app/Http/Controllers/API/DeviceController.php | 6 +-- app/Http/Resources/Category.php | 52 +++++++++++++++++++ app/Http/Resources/Device.php | 6 ++- resources/js/app.js | 1 - resources/js/components/EventDevice.vue | 2 +- resources/js/components/EventDevices.vue | 10 +++- resources/js/store/devices.js | 42 ++++++++++++--- resources/js/store/events.js | 2 +- resources/js/store/groups.js | 2 +- tests/Feature/Devices/APIv2DeviceTest.php | 4 +- 10 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 app/Http/Resources/Category.php diff --git a/app/Http/Controllers/API/DeviceController.php b/app/Http/Controllers/API/DeviceController.php index 88b93b5917..cb560022fd 100644 --- a/app/Http/Controllers/API/DeviceController.php +++ b/app/Http/Controllers/API/DeviceController.php @@ -499,12 +499,12 @@ private function validateDeviceParams(Request $request, $create): array $item_type = $request->input('item_type'); $brand = $request->input('brand'); $model = $request->input('model'); - $age = $request->input('age'); - $estimate = $request->input('estimate'); + $age = $request->input('age') || 0; + $estimate = $request->input('estimate') || 0; $problem = $request->input('problem'); $notes = $request->input('notes'); $case_study = $request->input('case_study'); - $repair_status = $request->input('repair_status'); + $repair_status = $request->input('repair_status') || 0; $barrierInput = $request->input('barrier'); // Our database has a slightly complex structure for historical reasons, so we need to map some input diff --git a/app/Http/Resources/Category.php b/app/Http/Resources/Category.php new file mode 100644 index 0000000000..fcd8d1ac13 --- /dev/null +++ b/app/Http/Resources/Category.php @@ -0,0 +1,52 @@ + $this->idcategories, + 'name' => $this->name, + 'powered' => $this->powered, + ]; + } +} diff --git a/app/Http/Resources/Device.php b/app/Http/Resources/Device.php index ecff6c9890..6796e7e5ea 100644 --- a/app/Http/Resources/Device.php +++ b/app/Http/Resources/Device.php @@ -2,7 +2,6 @@ namespace App\Http\Resources; -use App\DeviceBarrier; use Illuminate\Http\Resources\Json\JsonResource; /** @@ -28,7 +27,7 @@ * @OA\Property( * property="category", * title="category", - * description="The id of the category to which this item belongs.", + * description="The category to which this item belongs.", * format="int64", * example="16" * ), @@ -195,6 +194,9 @@ public function toArray($request) break; } + $category = \App\Category::find($this->category); + $ret['category']= \App\Http\Resources\Category::make($category); + return $ret; } } diff --git a/resources/js/app.js b/resources/js/app.js index fd79e0ef31..89e85cd27d 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1293,7 +1293,6 @@ jQuery(document).ready(function () { // resources/js/components. Lower level components can be included from within those as normal; // they don't need listing here. $(".vue").each(function(index) { - console.log('Create vue', $(this).get(0)) new Vue({ el: $(this).get(0), store: store, diff --git a/resources/js/components/EventDevice.vue b/resources/js/components/EventDevice.vue index fb7399e569..e09f5a7437 100644 --- a/resources/js/components/EventDevice.vue +++ b/resources/js/components/EventDevice.vue @@ -386,7 +386,7 @@ export default { } else { this.missingCategory = false - const createdDevices = await this.$store.dispatch('devices/add', this.currentDevice) + await this.$store.dispatch('devices/add', this.currentDevice) this.$emit('close') } diff --git a/resources/js/components/EventDevices.vue b/resources/js/components/EventDevices.vue index 6a5a2ab1dd..b1a34b203e 100644 --- a/resources/js/components/EventDevices.vue +++ b/resources/js/components/EventDevices.vue @@ -31,7 +31,7 @@ {{ __('partials.add_device_powered') }} - +