diff --git a/.env.example b/.env.example index ea7790cb..70ae8a7e 100644 --- a/.env.example +++ b/.env.example @@ -12,9 +12,12 @@ APP_HASH_SALT_RESOURCE_TYPE= APP_HASH_SALT_RESOURCE= APP_HASH_SALT_ITEM= APP_HASH_SALT_ITEM_CATEGORY= +APP_HASH_SALT_ITEM_PARTIAL_TRANSFER= APP_HASH_SALT_ITEM_SUBCATEGORY= +APP_HASH_SALT_ITEM_TRANSFER= APP_HASH_SALT_ITEM_TYPE= APP_HASH_SALT_PERMITTED_USER= +APP_HASH_SALT_USER= LOG_CHANNEL=stack diff --git a/CHANGELOG.md b/CHANGELOG.md index 94f20211..48ac2ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ The complete changelog for the Costs to Expect REST API, our changelog follows the format defined at https://keepachangelog.com/en/1.0.0/ +## [v2.10.0] - 2020-04-01 +### Added +- We have added a new route, `/resource_types/[id]/resources/[id]/items/[id]/partial-transfer`; A partial transfer allows you to transfer a percentage of the `total` for an item from one resource to another. +- We have added an `item_transfer` table; the table will log which items were transferred and by whom. +- We have added a partial transfers collection; the route is `/resource_types/[id]/partial-transfers`. +- We have added a partial transfers item view; the route is `/resource_types/[id]/partial-transfers/[id]`. +- We have added a transfers collection; the route is `/resource_types/[id]/transfers`. +- We have added a transfers item view; the route is `/resource_types/[id]/transfers/[id]`. +- We have added a delete endpoint for partial transfers. + +### Changed +- We have reformatted the validation rules in the configuration files; easier to read and simpler to add additional rules. +- We have switched the HTTP status code for a "Constraint error" from 500 to 409. +- We have tweaked the description for the resource field in the `/resource_types/[id]/resources/[id]/items/[id]/transfer` OPTIONS request. +- We have renamed the third parameter of the route validation methods; we changed the name from `$manage` to `$write`. +- We have renamed a response helper method; it was not clear from the name that the method is used for updates and delete. + +### Fixed +- It is possible to set the quantity for a `simple-item` item as zero. +- It is possible to clear optional values in a PATCH request. + ## [v2.09.4] - 2020-03-25 ### Changed - When a response includes additional data via the include parameters, we include the URI fragment for that data. diff --git a/README.md b/README.md index c8d165c3..707ecefe 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,12 @@ additionally, the same is true if you are assigned to a resource type. | DELETE | v2/resource-types/{resource_type_id}/categories/{category_id}/subcategories/{subcategory_id} | | GET/HEAD | v2/resource-types/{resource_type_id}/items | | OPTIONS | v2/resource-types/{resource_type_id}/items | +| GET/HEAD | v2/resource-types/{resource_type_id}/partial-transfers | +| OPTIONS | v2/resource-types/{resource_type_id}/partial-transfers | +| GET/HEAD | v2/resource-types/{resource_type_id}/partial-transfers/{item_partial_transfer_id} | +| OPTIONS | v2/resource-types/{resource_type_id}/partial-transfers/{item_partial_transfer_id} | +| DELETE | v2/resource-types/{resource_type_id}/partial-transfers/{item_partial_transfer_id} | +| GET/HEAD | v2/resource-types/{resource_type_id}/permitted-users | | OPTIONS | v2/resource-types/{resource_type_id}/permitted-users | | GET/HEAD | v2/resource-types/{resource_type_id}/resources | | OPTIONS | v2/resource-types/{resource_type_id}/resources | @@ -153,8 +159,14 @@ additionally, the same is true if you are assigned to a resource type. | GET/HEAD | v2/resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/category/{item_category_id}/subcategory/{item_subcategory_id} | | OPTIONS | v2/resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/category/{item_category_id}/subcategory/{item_subcategory_id} | | DELETE | v2/resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/category/{item_category_id}/sub_category/{item_subcategory_id} | +| OPTIONS | v2/resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/partial-transfer | +| POST | v2/resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/partial-transfer | | OPTIONS | v2/resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/transfer | | POST | v2/resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/transfer | +| GET/HEAD | v2/resource-types/{resource_type_id}/transfers | +| OPTIONS | v2/resource-types/{resource_type_id}/transfers | +| GET/HEAD | v2/resource-types/{resource_type_id}/transfers/{item_transfer_id} | +| OPTIONS | v2/resource-types/{resource_type_id}/transfers/{item_transfer_id} | | GET/HEAD | v2/request/error-log | | OPTIONS | v2/request/error-log | | POST | v2/request/error-log | diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index d8c395cc..09823cf8 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -269,16 +269,16 @@ public function create($resource_type_id): JsonResponse ]); UtilityRequest::validateAndReturnErrors($validator); - //try { + try { $category = new Category([ 'name' => request()->input('name'), 'description' => request()->input('description'), 'resource_type_id' => $resource_type_id ]); $category->save(); - //} catch (Exception $e) { - // UtilityResponse::failedToSaveModelForCreate(); - //} + } catch (Exception $e) { + UtilityResponse::failedToSaveModelForCreate(); + } return response()->json( (new CategoryTransformer((new Category)->instanceToArray($category)))->toArray(), @@ -337,7 +337,7 @@ public function update($resource_type_id, $category_id): JsonResponse $category = (new Category())->instance($category_id); if ($category === null) { - UtilityResponse::failedToSelectModelForUpdate(); + UtilityResponse::failedToSelectModelForUpdateOrDelete(); } UtilityRequest::checkForEmptyPatch(); diff --git a/app/Http/Controllers/ItemController.php b/app/Http/Controllers/ItemController.php index adc4d8cb..9618b590 100644 --- a/app/Http/Controllers/ItemController.php +++ b/app/Http/Controllers/ItemController.php @@ -402,7 +402,7 @@ public function update( $item_type = $item_interface->instance((int) $item_id); if ($item === null || $item_type === null) { - UtilityResponse::failedToSelectModelForUpdate(); + UtilityResponse::failedToSelectModelForUpdateOrDelete(); } try { @@ -415,7 +415,7 @@ public function update( UtilityResponse::failedToSaveModelForUpdate(); } - UtilityResponse::successNoContent(); + return UtilityResponse::successNoContent(); } /** diff --git a/app/Http/Controllers/ItemPartialTransferController.php b/app/Http/Controllers/ItemPartialTransferController.php new file mode 100644 index 00000000..20638202 --- /dev/null +++ b/app/Http/Controllers/ItemPartialTransferController.php @@ -0,0 +1,355 @@ + + * @copyright Dean Blackborough 2018-2020 + * @license https://github.com/costs-to-expect/api/blob/master/LICENSE + */ +class ItemPartialTransferController extends Controller +{ + /** + * Return the categories collection + * + * @param string $resource_type_id + * + * @return JsonResponse + */ + public function index($resource_type_id): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $total = (new ItemPartialTransfer())->total( + (int) $resource_type_id, + $this->permitted_resource_types, + $this->include_public + ); + + $pagination = UtilityPagination::init( + request()->path(), + $total, + 10, + $this->allow_entire_collection + )-> + paging(); + + $transfers = (new ItemPartialTransfer())->paginatedCollection( + (int) $resource_type_id, + $this->permitted_resource_types, + $this->include_public, + $pagination['offset'], + $pagination['limit'] + ); + + $headers = new Header(); + $headers->collection($pagination, count($transfers), $total); + + return response()->json( + array_map( + static function($transfer) { + return (new ItemPartialTransferTransformer($transfer))->toArray(); + }, + $transfers + ), + 200, + $headers->headers() + ); + } + + /** + * Delete the requested partial transfer + * + * @param $resource_type_id + * @param $item_partial_transfer_id + * + * @return JsonResponse + */ + public function delete( + $resource_type_id, + $item_partial_transfer_id + ): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types, + true + ); + + try { + $partial_transfer = (new ItemPartialTransfer())->find($item_partial_transfer_id); + + if ($partial_transfer !== null) { + $partial_transfer->delete(); + return UtilityResponse::successNoContent(); + } + + return UtilityResponse::failedToSelectModelForUpdateOrDelete(); + } catch (QueryException $e) { + return UtilityResponse::foreignKeyConstraintError(); + } catch (Exception $e) { + return UtilityResponse::notFound(trans('entities.item-partial-transfer'), $e); + } + } + + /** + * Return a single item partial transfer + * + * @param $resource_type_id + * @param $item_partial_transfer_id + * + * @return JsonResponse + */ + public function show( + $resource_type_id, + $item_partial_transfer_id + ): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $item_partial_transfer = (new ItemPartialTransfer())->single( + (int) $resource_type_id, + (int) $item_partial_transfer_id + ); + + if ($item_partial_transfer === null) { + UtilityResponse::notFound(trans('entities.item_partial_transfer')); + } + + $headers = new Header(); + $headers->item(); + + return response()->json( + (new ItemPartialTransferTransformer($item_partial_transfer))->toArray(), + 200, + $headers->headers() + ); + } + + public function transfer( + string $resource_type_id, + string $resource_id, + string $item_id + ): JsonResponse + { + Route::item( + $resource_type_id, + $resource_id, + $item_id, + $this->permitted_resource_types, + true + ); + + $validator = (new ItemPartialTransferValidator)->create( + [ + 'resource_type_id' => $resource_type_id, + 'existing_resource_id' => $resource_id + ] + ); + UtilityRequest::validateAndReturnErrors($validator); + + $new_resource_id = $this->hash->decode('resource', request()->input('resource_id')); + + if ($new_resource_id === false) { + UtilityResponse::unableToDecode(); + } + + try { + $partial_transfer = new ItemPartialTransfer([ + 'resource_type_id' => $resource_type_id, + 'from' => (int) $resource_id, + 'to' => $new_resource_id, + 'item_id' => $item_id, + 'percentage' => request()->input('percentage'), + 'transferred_by' => Auth::user()->id + ]); + $partial_transfer->save(); + } catch (QueryException $e) { + return UtilityResponse::foreignKeyConstraintError(); + } catch (Exception $e) { + return UtilityResponse::failedToSaveModelForCreate(); + } + + $item_partial_transfer = (new ItemPartialTransfer())->single( + (int) $resource_type_id, + (int) $partial_transfer->id + ); + + if ($item_partial_transfer === null) { + return UtilityResponse::notFound(trans('entities.item_partial_transfer')); + } + + return response()->json( + (new ItemPartialTransferTransformer($item_partial_transfer))->toArray(), + 201 + ); + } + + /** + * Generate the OPTIONS request for the partial transfers collection + * + * @param $resource_type_id + * + * @return JsonResponse + */ + public function optionsIndex($resource_type_id): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $permissions = RoutePermission::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $get = Get::init()-> + setPagination(true)-> + setAuthenticationStatus($permissions['view'])-> + setDescription('route-descriptions.item_partial_transfer_GET_index')-> + option(); + + return $this->optionsResponse( + $get, + 200 + ); + } + + /** + * Generate the OPTIONS request for a specific item partial transfer + * + * @param $resource_type_id + * @param $item_partial_transfer_id + * + * @return JsonResponse + */ + public function optionsShow($resource_type_id, $item_partial_transfer_id): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $permissions = RoutePermission::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $get = Get::init()-> + setDescription('route-descriptions.item_partial_transfer_GET_show')-> + setAuthenticationStatus($permissions['view'])-> + option(); + + $delete = Delete::init()-> + setAuthenticationRequired(true)-> + setAuthenticationStatus($permissions['manage'])-> + setDescription('route-descriptions.item_partial_transfer_DELETE')-> + option(); + + return $this->optionsResponse( + $get + $delete, + 200 + ); + } + + public function optionsTransfer( + string $resource_type_id, + string $resource_id, + string $item_id + ): JsonResponse + { + Route::item( + $resource_type_id, + $resource_id, + $item_id, + $this->permitted_resource_types + ); + + $permissions = RoutePermission::item( + $resource_type_id, + $resource_id, + $item_id, + $this->permitted_resource_types + ); + + $post = Post::init()-> + setFields('api.item-partial-transfer.fields')-> + setFieldsData( + $this->fieldsData( + $resource_type_id, + $resource_id + ) + )-> + setDescription('route-descriptions.item_partial_transfer_POST')-> + setAuthenticationStatus($permissions['manage'])-> + setAuthenticationRequired(true)-> + option(); + + return $this->optionsResponse($post, 200); + } + + /** + * Generate any conditional POST parameters, will be merged with the + * relevant config/api/[type]/fields.php data array + * + * @param integer $resource_type_id + * @param integer $resource_id + * + * @return array + */ + private function fieldsData( + int $resource_type_id, + int $resource_id + ): array + { + $resources = (new Resource())->resourcesForResourceType( + $resource_type_id, + $resource_id + ); + + $conditional_post_parameters = ['resource_id' => []]; + foreach ($resources as $resource) { + $id = $this->hash->encode('resource', $resource['resource_id']); + + if ($id === false) { + UtilityResponse::unableToDecode(); + } + + $conditional_post_parameters['resource_id']['allowed_values'][$id] = [ + 'value' => $id, + 'name' => $resource['resource_name'], + 'description' => $resource['resource_description'] + ]; + } + + return $conditional_post_parameters; + } +} diff --git a/app/Http/Controllers/ItemTransferController.php b/app/Http/Controllers/ItemTransferController.php index 10a390f9..ddbb1eef 100644 --- a/app/Http/Controllers/ItemTransferController.php +++ b/app/Http/Controllers/ItemTransferController.php @@ -3,15 +3,22 @@ namespace App\Http\Controllers; use App\Models\Item; +use App\Models\ItemTransfer; use App\Models\Resource; +use App\Models\Transformers\ItemTransfer as ItemTransferTransformer; +use App\Option\Get; use App\Option\Post; +use App\Utilities\Header; +use App\Utilities\Pagination as UtilityPagination; use App\Utilities\Request as UtilityRequest; use App\Utilities\Response as UtilityResponse; use App\Utilities\RoutePermission; use App\Validators\Fields\ItemTransfer as ItemTransferValidator; use App\Validators\Route; use Exception; +use Illuminate\Database\QueryException; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\Auth; /** * Transfer items @@ -22,46 +29,117 @@ */ class ItemTransferController extends Controller { - protected $pagination = []; - - public function transfer( - string $resource_type_id, - string $resource_id, - string $item_id - ): JsonResponse + /** + * Return the item transfers collection + * + * @param string $resource_type_id + * + * @return JsonResponse + */ + public function index($resource_type_id): JsonResponse { - Route::item( - $resource_type_id, - $resource_id, - $item_id, + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $total = (new ItemTransfer())->total( + (int) $resource_type_id, $this->permitted_resource_types, - true + $this->include_public ); - $validator = (new ItemTransferValidator)->create( - [ - 'resource_type_id' => $resource_type_id, - 'existing_resource_id' => $resource_id - ] + $pagination = UtilityPagination::init( + request()->path(), + $total, + 10, + $this->allow_entire_collection + )-> + paging(); + + $transfers = (new ItemTransfer())->paginatedCollection( + (int) $resource_type_id, + $this->permitted_resource_types, + $this->include_public, + $pagination['offset'], + $pagination['limit'] ); - UtilityRequest::validateAndReturnErrors($validator); - try { - $new_resource_id = $this->hash->decode('resource', request()->input('resource_id')); + $headers = new Header(); + $headers->collection($pagination, count($transfers), $total); - if ($new_resource_id === false) { - UtilityResponse::unableToDecode(); - } + return response()->json( + array_map( + static function($transfer) { + return (new ItemTransferTransformer($transfer))->toArray(); + }, + $transfers + ), + 200, + $headers->headers() + ); + } - $item = (new Item())->instance($resource_type_id, $resource_id, $item_id); - $item->resource_id = $new_resource_id; - $item->save(); - } catch (Exception $e) { - UtilityResponse::failedToSaveModelForUpdate(); - } + /** + * Generate the OPTIONS request for the transfers collection + * + * @param $resource_type_id + * + * @return JsonResponse + */ + public function optionsIndex($resource_type_id): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $permissions = RoutePermission::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $get = Get::init()-> + setPagination(true)-> + setAuthenticationStatus($permissions['view'])-> + setDescription('route-descriptions.item_transfer_GET_index')-> + option(); + + return $this->optionsResponse( + $get, + 200 + ); + } + + /** + * Generate the OPTIONS request for a specific item transfer + * + * @param $resource_type_id + * @param $item_transfer_id + * + * @return JsonResponse + */ + public function optionsShow($resource_type_id, $item_transfer_id): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); - // Endpoint should 404 after request so figure 204 better than redirect or 404 - UtilityResponse::successNoContent(); + $permissions = RoutePermission::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $get = Get::init()-> + setDescription('route-descriptions.item_transfer_GET_show')-> + setAuthenticationStatus($permissions['view'])-> + option(); + + return $this->optionsResponse( + $get, + 200 + ); } public function optionsTransfer( @@ -100,6 +178,97 @@ public function optionsTransfer( return $this->optionsResponse($post, 200); } + /** + * Return a single item transfer + * + * @param $resource_type_id + * @param $item_transfer_id + * + * @return JsonResponse + */ + public function show( + $resource_type_id, + $item_transfer_id + ): JsonResponse + { + Route::resourceType( + (int) $resource_type_id, + $this->permitted_resource_types + ); + + $item_transfer = (new ItemTransfer())->single( + (int) $resource_type_id, + (int) $item_transfer_id + ); + + if ($item_transfer === null) { + UtilityResponse::notFound(trans('entities.item_transfer')); + } + + $headers = new Header(); + $headers->item(); + + return response()->json( + (new ItemTransferTransformer($item_transfer))->toArray(), + 200, + $headers->headers() + ); + } + + public function transfer( + string $resource_type_id, + string $resource_id, + string $item_id + ): JsonResponse + { + Route::item( + $resource_type_id, + $resource_id, + $item_id, + $this->permitted_resource_types, + true + ); + + $validator = (new ItemTransferValidator)->create( + [ + 'resource_type_id' => $resource_type_id, + 'existing_resource_id' => $resource_id + ] + ); + UtilityRequest::validateAndReturnErrors($validator); + + try { + $new_resource_id = $this->hash->decode('resource', request()->input('resource_id')); + + if ($new_resource_id === false) { + return UtilityResponse::unableToDecode(); + } + + $item = (new Item())->instance($resource_type_id, $resource_id, $item_id); + if ($item !== null) { + $item->resource_id = $new_resource_id; + $item->save(); + } else { + return UtilityResponse::failedToSelectModelForUpdateOrDelete(); + } + + $item_transfer = new ItemTransfer([ + 'resource_type_id' => $resource_type_id, + 'from' => (int) $resource_id, + 'to' => $new_resource_id, + 'item_id' => $item_id, + 'transferred_by' => Auth::user()->id + ]); + $item_transfer->save(); + } catch (QueryException $e) { + return UtilityResponse::foreignKeyConstraintError(); + } catch (Exception $e) { + return UtilityResponse::failedToSaveModelForUpdate(); + } + + return UtilityResponse::successNoContent(); + } + /** * Generate any conditional POST parameters, will be merged with the * relevant config/api/[type]/fields.php data array diff --git a/app/Http/Controllers/ResourceController.php b/app/Http/Controllers/ResourceController.php index eccd1b79..8bc0e175 100644 --- a/app/Http/Controllers/ResourceController.php +++ b/app/Http/Controllers/ResourceController.php @@ -327,7 +327,7 @@ public function update( $resource = (new Resource())->instance($resource_type_id, $resource_id); if ($resource === null) { - UtilityResponse::failedToSelectModelForUpdate(); + UtilityResponse::failedToSelectModelForUpdateOrDelete(); } UtilityRequest::checkForEmptyPatch(); diff --git a/app/Http/Controllers/ResourceTypeController.php b/app/Http/Controllers/ResourceTypeController.php index 97a469b9..e2a96990 100644 --- a/app/Http/Controllers/ResourceTypeController.php +++ b/app/Http/Controllers/ResourceTypeController.php @@ -327,7 +327,7 @@ public function update( $resource_type = (new ResourceType())->instance($resource_type_id); if ($resource_type === null) { - UtilityResponse::failedToSelectModelForUpdate(); + UtilityResponse::failedToSelectModelForUpdateOrDelete(); } UtilityRequest::checkForEmptyPatch(); diff --git a/app/Http/Controllers/SubcategoryController.php b/app/Http/Controllers/SubcategoryController.php index a78a7456..511e32a1 100644 --- a/app/Http/Controllers/SubcategoryController.php +++ b/app/Http/Controllers/SubcategoryController.php @@ -352,7 +352,7 @@ public function update( $subcategory = (new Subcategory())->instance($category_id, $subcategory_id); if ($subcategory === null) { - UtilityResponse::failedToSelectModelForUpdate(); + UtilityResponse::failedToSelectModelForUpdateOrDelete(); } UtilityRequest::checkForEmptyPatch(); diff --git a/app/Http/Middleware/ConvertRouteParameters.php b/app/Http/Middleware/ConvertRouteParameters.php index b7d2d3f3..92beeae8 100644 --- a/app/Http/Middleware/ConvertRouteParameters.php +++ b/app/Http/Middleware/ConvertRouteParameters.php @@ -35,7 +35,9 @@ public function handle($request, Closure $next) 'resource_id' => new Hashids($config['resource'], $min_length), 'item_id' => new Hashids($config['item'], $min_length), 'item_category_id' => new Hashids($config['item_category'], $min_length), + 'item_partial_transfer_id' => new Hashids($config['item_partial_transfer'], $min_length), 'item_subcategory_id' => new Hashids($config['item_subcategory'], $min_length), + 'item_transfer_id' => new Hashids($config['item_transfer'], $min_length), 'item_type_id' => new Hashids($config['item_type'], $min_length), ]; diff --git a/app/Models/ItemPartialTransfer.php b/app/Models/ItemPartialTransfer.php new file mode 100644 index 00000000..66883381 --- /dev/null +++ b/app/Models/ItemPartialTransfer.php @@ -0,0 +1,143 @@ + + * @copyright Dean Blackborough 2018-2020 + * @license https://github.com/costs-to-expect/api/blob/master/LICENSE + */ +class ItemPartialTransfer extends Model +{ + protected $table = 'item_partial_transfer'; + + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Return the paginated collection + * + * @param integer $resource_type_id + * @param array $permitted_resource_types + * @param boolean $include_public + * @param integer $offset + * @param integer $limit + * + * @return array + */ + public function paginatedCollection( + int $resource_type_id, + array $permitted_resource_types, + bool $include_public, + int $offset = 0, + int $limit = 10 + ): array { + $collection = $this->select( + $this->table . '.id', + $this->table . '.percentage', + $this->table . '.item_id', + $this->table . '.created_at', + 'from_resource.id AS from_resource_id', + 'from_resource.name AS from_resource_name', + 'to_resource.id AS to_resource_id', + 'to_resource.name AS to_resource_name', + 'users.id AS user_id', + 'users.name AS user_name' + )-> + join("resource_type",$this->table . ".resource_type_id","resource_type.id")-> + join("resource AS from_resource",$this->table . ".from","from_resource.id")-> + join("resource AS to_resource",$this->table . ".to","to_resource.id")-> + join("item",$this->table . ".item_id","item.id")-> + join("users",$this->table . ".transferred_by","users.id")-> + where($this->table .'.resource_type_id', '=', $resource_type_id); + + $collection = ModelUtility::applyResourceTypeCollectionCondition( + $collection, + $permitted_resource_types, + $include_public + ); + + $collection->offset($offset); + $collection->limit($limit); + + return $collection->get()->toArray(); + } + + /** + * Return a single partial transfer + * + * @param integer $resource_type_id + * @param integer $item_partial_transfer_id + * + * @return array|null + */ + public function single( + int $resource_type_id, + int $item_partial_transfer_id + ): ?array + { + $result = $this->join("resource_type",$this->table . ".resource_type_id","resource_type.id")-> + join("resource AS from_resource",$this->table . ".from","from_resource.id")-> + join("resource AS to_resource",$this->table . ".to","to_resource.id")-> + join("item",$this->table . ".item_id","item.id")-> + join("users",$this->table . ".transferred_by","users.id")-> + where($this->table .'.resource_type_id', '=', $resource_type_id)-> + where($this->table .'.id', '=', $item_partial_transfer_id)-> + select( + $this->table . '.id', + $this->table . '.percentage', + $this->table . '.item_id', + $this->table . '.created_at', + 'from_resource.id AS from_resource_id', + 'from_resource.name AS from_resource_name', + 'to_resource.id AS to_resource_id', + 'to_resource.name AS to_resource_name', + 'users.id AS user_id', + 'users.name AS user_name' + )-> + first(); + + if ($result === null) { + return null; + } else { + return $result->toArray(); + } + } + + /** + * @param integer $resource_type_id + * @param array $permitted_resource_types + * @param boolean $include_public + * + * @return integer + */ + public function total( + int $resource_type_id, + array $permitted_resource_types, + bool $include_public + ): int + { + $collection = $this->select($this->table . '.id')-> + join("resource_type",$this->table . ".resource_type_id","resource_type.id")-> + join("resource AS from_resource",$this->table . ".from","from_resource.id")-> + join("resource AS to_resource",$this->table . ".to","to_resource.id")-> + join("item",$this->table . ".item_id","item.id")-> + join("users",$this->table . ".transferred_by","users.id")-> + where($this->table .'.resource_type_id', '=', $resource_type_id); + + $collection = ModelUtility::applyResourceTypeCollectionCondition( + $collection, + $permitted_resource_types, + $include_public + ); + + return $collection->count(); + } +} diff --git a/app/Models/ItemTransfer.php b/app/Models/ItemTransfer.php new file mode 100644 index 00000000..05acdb79 --- /dev/null +++ b/app/Models/ItemTransfer.php @@ -0,0 +1,141 @@ + + * @copyright Dean Blackborough 2018-2020 + * @license https://github.com/costs-to-expect/api/blob/master/LICENSE + */ +class ItemTransfer extends Model +{ + protected $table = 'item_transfer'; + + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Return the paginated collection + * + * @param integer $resource_type_id + * @param array $permitted_resource_types + * @param boolean $include_public + * @param integer $offset + * @param integer $limit + * + * @return array + */ + public function paginatedCollection( + int $resource_type_id, + array $permitted_resource_types, + bool $include_public, + int $offset = 0, + int $limit = 10 + ): array { + $collection = $this->select( + $this->table . '.id', + $this->table . '.item_id', + $this->table . '.created_at', + 'from_resource.id AS from_resource_id', + 'from_resource.name AS from_resource_name', + 'to_resource.id AS to_resource_id', + 'to_resource.name AS to_resource_name', + 'users.id AS user_id', + 'users.name AS user_name' + )-> + join("resource_type",$this->table . ".resource_type_id","resource_type.id")-> + join("resource AS from_resource",$this->table . ".from","from_resource.id")-> + join("resource AS to_resource",$this->table . ".to","to_resource.id")-> + join("item",$this->table . ".item_id","item.id")-> + join("users",$this->table . ".transferred_by","users.id")-> + where($this->table .'.resource_type_id', '=', $resource_type_id); + + $collection = ModelUtility::applyResourceTypeCollectionCondition( + $collection, + $permitted_resource_types, + $include_public + ); + + $collection->offset($offset); + $collection->limit($limit); + + return $collection->get()->toArray(); + } + + /** + * Return a single partial transfer + * + * @param integer $resource_type_id + * @param integer $item_partial_transfer_id + * + * @return array|null + */ + public function single( + int $resource_type_id, + int $item_partial_transfer_id + ): ?array + { + $result = $this->join("resource_type",$this->table . ".resource_type_id","resource_type.id")-> + join("resource AS from_resource",$this->table . ".from","from_resource.id")-> + join("resource AS to_resource",$this->table . ".to","to_resource.id")-> + join("item",$this->table . ".item_id","item.id")-> + join("users",$this->table . ".transferred_by","users.id")-> + where($this->table .'.resource_type_id', '=', $resource_type_id)-> + where($this->table .'.id', '=', $item_partial_transfer_id)-> + select( + $this->table . '.id', + $this->table . '.item_id', + $this->table . '.created_at', + 'from_resource.id AS from_resource_id', + 'from_resource.name AS from_resource_name', + 'to_resource.id AS to_resource_id', + 'to_resource.name AS to_resource_name', + 'users.id AS user_id', + 'users.name AS user_name' + )-> + first(); + + if ($result === null) { + return null; + } else { + return $result->toArray(); + } + } + + /** + * @param integer $resource_type_id + * @param array $permitted_resource_types + * @param boolean $include_public + * + * @return integer + */ + public function total( + int $resource_type_id, + array $permitted_resource_types, + bool $include_public + ): int + { + $collection = $this->select($this->table . '.id')-> + join("resource_type",$this->table . ".resource_type_id","resource_type.id")-> + join("resource AS from_resource",$this->table . ".from","from_resource.id")-> + join("resource AS to_resource",$this->table . ".to","to_resource.id")-> + join("item",$this->table . ".item_id","item.id")-> + join("users",$this->table . ".transferred_by","users.id")-> + where($this->table .'.resource_type_id', '=', $resource_type_id); + + $collection = ModelUtility::applyResourceTypeCollectionCondition( + $collection, + $permitted_resource_types, + $include_public + ); + + return $collection->count(); + } +} diff --git a/app/Models/Transformers/ItemPartialTransfer.php b/app/Models/Transformers/ItemPartialTransfer.php new file mode 100644 index 00000000..49227b5f --- /dev/null +++ b/app/Models/Transformers/ItemPartialTransfer.php @@ -0,0 +1,49 @@ + + * @copyright Dean Blackborough 2018-2020 + * @license https://github.com/costs-to-expect/api/blob/master/LICENSE + */ +class ItemPartialTransfer extends Transformer +{ + private $data_to_transform; + + public function __construct(array $data_to_transform) + { + parent::__construct(); + + $this->data_to_transform = $data_to_transform; + } + + public function toArray(): array + { + return [ + 'id' => $this->hash->itemPartialTransfer()->encode($this->data_to_transform['id']), + 'from' => [ + 'id' => $this->hash->resource()->encode($this->data_to_transform['from_resource_id']), + 'name' => $this->data_to_transform['from_resource_name'], + ], + 'to' => [ + 'id' => $this->hash->resource()->encode($this->data_to_transform['to_resource_id']), + 'name' => $this->data_to_transform['to_resource_name'], + ], + 'item' => [ + 'id' => $this->hash->item()->encode($this->data_to_transform['item_id']) + ], + 'percentage' => intval($this->data_to_transform['percentage']), + 'transferred' => [ + 'at' => $this->data_to_transform['created_at'], + 'user' => [ + 'id' => $this->hash->user()->encode($this->data_to_transform['user_id']), + 'name' => $this->data_to_transform['user_name'] + ] + ] + ]; + } +} diff --git a/app/Models/Transformers/ItemTransfer.php b/app/Models/Transformers/ItemTransfer.php new file mode 100644 index 00000000..30217cec --- /dev/null +++ b/app/Models/Transformers/ItemTransfer.php @@ -0,0 +1,48 @@ + + * @copyright Dean Blackborough 2018-2020 + * @license https://github.com/costs-to-expect/api/blob/master/LICENSE + */ +class ItemTransfer extends Transformer +{ + private $data_to_transform; + + public function __construct(array $data_to_transform) + { + parent::__construct(); + + $this->data_to_transform = $data_to_transform; + } + + public function toArray(): array + { + return [ + 'id' => $this->hash->itemTransfer()->encode($this->data_to_transform['id']), + 'from' => [ + 'id' => $this->hash->resource()->encode($this->data_to_transform['from_resource_id']), + 'name' => $this->data_to_transform['from_resource_name'], + ], + 'to' => [ + 'id' => $this->hash->resource()->encode($this->data_to_transform['to_resource_id']), + 'name' => $this->data_to_transform['to_resource_name'], + ], + 'item' => [ + 'id' => $this->hash->item()->encode($this->data_to_transform['item_id']) + ], + 'transferred' => [ + 'at' => $this->data_to_transform['created_at'], + 'user' => [ + 'id' => $this->hash->user()->encode($this->data_to_transform['user_id']), + 'name' => $this->data_to_transform['user_name'] + ] + ] + ]; + } +} diff --git a/app/Option/Delete.php b/app/Option/Delete.php index 70a20392..4be252fc 100644 --- a/app/Option/Delete.php +++ b/app/Option/Delete.php @@ -13,7 +13,7 @@ */ class Delete extends Option { - static private function reset() + private static function reset(): void { self::resetBase(); @@ -21,20 +21,20 @@ static private function reset() self::$description = null; } - static public function init(): Delete + public static function init(): Delete { - self::$instance = new Delete(); - self::$instance->reset(); + self::$instance = new self(); + self::$instance::reset(); return self::$instance; } - static protected function build() + protected static function build() { // Not necessary for this simple Option } - static public function option(): array + public static function option(): array { return [ 'DELETE' => [ diff --git a/app/Option/Get.php b/app/Option/Get.php index 55b93f5a..f5192a58 100644 --- a/app/Option/Get.php +++ b/app/Option/Get.php @@ -70,9 +70,9 @@ class Get extends Option */ static private $sortable_parameters; - static private function reset() + private static function reset(): void { - self::resetBase();; + self::resetBase(); self::$localised_parameters = []; @@ -89,15 +89,15 @@ static private function reset() self::$sortable_parameters = []; } - static public function init(): Get + public static function init(): Get { - self::$instance = new Get(); - self::$instance->reset(); + self::$instance = new self(); + self::$instance::reset(); return self::$instance; } - static public function setFilterable( + public static function setFilterable( string $config_path ): Get { @@ -110,7 +110,7 @@ static public function setFilterable( return self::$instance; } - static public function setPagination( + public static function setPagination( bool $status = false ): Get { @@ -121,7 +121,7 @@ static public function setPagination( return self::$instance; } - static public function setPaginationOverride( + public static function setPaginationOverride( bool $status = false ): Get { @@ -132,7 +132,7 @@ static public function setPaginationOverride( return self::$instance; } - static public function setParameters( + public static function setParameters( string $config_path ): Get { @@ -141,7 +141,7 @@ static public function setParameters( return self::$instance; } - static public function setParametersData( + public static function setParametersData( array $parameters = [] ): Get { @@ -150,7 +150,7 @@ static public function setParametersData( return self::$instance; } - static public function setSearchable( + public static function setSearchable( string $config_path ): Get { @@ -163,7 +163,7 @@ static public function setSearchable( return self::$instance; } - static public function setSortable( + public static function setSortable( string $config_path ): Get { @@ -176,7 +176,7 @@ static public function setSortable( return self::$instance; } - static protected function build() + protected static function build() { self::$localised_parameters = []; @@ -198,7 +198,7 @@ static protected function build() } } - static public function option(): array + public static function option(): array { self::build(); diff --git a/app/Option/Patch.php b/app/Option/Patch.php index 9599f6fd..706c1d20 100644 --- a/app/Option/Patch.php +++ b/app/Option/Patch.php @@ -30,7 +30,7 @@ class Patch extends Option */ static private $fields; - static private function reset() + private static function reset(): void { self::resetBase(); @@ -39,16 +39,16 @@ static private function reset() self::$localised_fields = []; } - static public function init(): Patch + public static function init(): Patch { - self::$instance = new Patch(); - self::$instance->reset(); + self::$instance = new self(); + self::$instance::reset(); return self::$instance; } - static public function setFieldsData( + public static function setFieldsData( array $fields = [] ): Patch { @@ -57,7 +57,7 @@ static public function setFieldsData( return self::$instance; } - static public function setFields( + public static function setFields( string $config_path ): Patch { @@ -65,7 +65,7 @@ static public function setFields( return self::$instance; } - static protected function build() + protected static function build() { self::$localised_fields = []; @@ -84,7 +84,7 @@ static protected function build() } } - static public function option(): array + public static function option(): array { self::build(); diff --git a/app/Option/Post.php b/app/Option/Post.php index 9e491118..61d07d76 100644 --- a/app/Option/Post.php +++ b/app/Option/Post.php @@ -30,7 +30,7 @@ class Post extends Option */ static private $localised_fields; - static private function reset() + private static function reset(): void { self::resetBase(); @@ -39,15 +39,15 @@ static private function reset() self::$localised_fields = []; } - static public function init(): Post + public static function init(): Post { - self::$instance = new Post(); - self::$instance->reset(); + self::$instance = new self(); + self::$instance::reset(); return self::$instance; } - static public function setFieldsData( + public static function setFieldsData( array $parameters = [] ): Post { @@ -56,7 +56,7 @@ static public function setFieldsData( return self::$instance; } - static public function setFields( + public static function setFields( string $config_path ): Post { @@ -65,7 +65,7 @@ static public function setFields( return self::$instance; } - static protected function build() + protected static function build() { self::$localised_fields = []; @@ -83,7 +83,7 @@ static protected function build() } } - static public function option(): array + public static function option(): array { self::build(); diff --git a/app/Utilities/General.php b/app/Utilities/General.php index 239221b9..2996201f 100644 --- a/app/Utilities/General.php +++ b/app/Utilities/General.php @@ -22,13 +22,9 @@ class General * * @return bool */ - static public function booleanValue($value): bool + public static function booleanValue($value): bool { - if (filter_var($value, FILTER_VALIDATE_BOOLEAN) === true) { - return true; - } - - return false; + return filter_var($value, FILTER_VALIDATE_BOOLEAN) === true; } /** @@ -40,14 +36,10 @@ static public function booleanValue($value): bool * * @return bool */ - static public function isBooleanValue($value): bool + public static function isBooleanValue($value): bool { $filtered = filter_var($value, FILTER_VALIDATE_BOOLEAN,FILTER_NULL_ON_FAILURE); - if ($filtered === true || $filtered === false) { - return true; - } - - return false; + return $filtered === true || $filtered === false; } } diff --git a/app/Utilities/Hash.php b/app/Utilities/Hash.php index 09bcc8ce..fd6530db 100644 --- a/app/Utilities/Hash.php +++ b/app/Utilities/Hash.php @@ -42,9 +42,12 @@ private function setUp() $this->hashers['resource'] = new Hashids($config['resource'], $min_length); $this->hashers['item'] = new Hashids($config['item'], $min_length); $this->hashers['item_category'] = new Hashids($config['item_category'], $min_length); - $this->hashers['item_sub_category'] = new Hashids($config['item_subcategory'], $min_length); + $this->hashers['item_partial_transfer'] = new Hashids($config['item_partial_transfer'], $min_length); + $this->hashers['item_subcategory'] = new Hashids($config['item_subcategory'], $min_length); + $this->hashers['item_transfer'] = new Hashids($config['item_transfer'], $min_length); $this->hashers['item_type'] = new Hashids($config['item_type'], $min_length); $this->hashers['permitted_user'] = new Hashids($config['permitted_user'], $min_length); + $this->hashers['user'] = new Hashids($config['user'], $min_length); } /** @@ -57,9 +60,9 @@ public function encode(string $type, int $parameter) { if (array_key_exists($type, $this->hashers) === true) { return $this->hashers[$type]->encode($parameter); - } else { - return false; } + + return false; } /** @@ -73,13 +76,13 @@ public function decode(string $type, string $parameter) if (array_key_exists($type, $this->hashers) === true) { $id = $this->hashers[$type]->decode($parameter); if (is_array($id) && array_key_exists(0, $id)) { - return intval($id[0]); - } else { - return false; + return (int) $id[0]; } - } else { + return false; } + + return false; } /** @@ -142,6 +145,16 @@ public function itemCategory(): Hashids return $this->hashers['item_category']; } + /** + * Helper method to return the item partial transfer hash object + * + * @return Hashids + */ + public function itemPartialTransfer(): Hashids + { + return $this->hashers['item_partial_transfer']; + } + /** * Helper method to return the item sub category hash object * @@ -149,11 +162,21 @@ public function itemCategory(): Hashids */ public function itemSubCategory(): Hashids { - return $this->hashers['item_sub_category']; + return $this->hashers['item_subcategory']; } /** - * Helper method to return the item type + * Helper method to return the item transfer hash object + * + * @return Hashids + */ + public function itemTransfer(): Hashids + { + return $this->hashers['item_transfer']; + } + + /** + * Helper method to return the item type object * * @return Hashids */ @@ -163,7 +186,7 @@ public function itemType(): Hashids } /** - * Helper method to return the permitted user + * Helper method to return the permitted user object * * @return Hashids */ @@ -171,4 +194,14 @@ public function permittedUser(): Hashids { return $this->hashers['permitted_user']; } + + /** + * Helper method to return the user object + * + * @return Hashids + */ + public function user(): Hashids + { + return $this->hashers['user']; + } } diff --git a/app/Utilities/Model.php b/app/Utilities/Model.php index 50592c1e..90ca25d1 100644 --- a/app/Utilities/Model.php +++ b/app/Utilities/Model.php @@ -3,6 +3,8 @@ namespace App\Utilities; +use Illuminate\Database\Query\Builder as QueryBuilder; + /** * Model helper class * @@ -10,6 +12,7 @@ * they gain more than a few functions and the creation of a library makes * sense. * + * @mixin QueryBuilder * @author Dean Blackborough * @copyright Dean Blackborough 2018-2020 * @license https://github.com/costs-to-expect/api/blob/master/LICENSE diff --git a/app/Utilities/Pagination.php b/app/Utilities/Pagination.php index 9c6bf21b..de700ad3 100644 --- a/app/Utilities/Pagination.php +++ b/app/Utilities/Pagination.php @@ -18,7 +18,7 @@ */ class Pagination { - private static $instance = null; + private static $instance; /** * @var array @@ -79,7 +79,7 @@ public static function init( ): Pagination { if (self::$instance === null) { - self::$instance = new Pagination; + self::$instance = new self; } self::$parameters = []; @@ -153,13 +153,13 @@ public static function paging(): array 'offset' => self::$offset, 'limit' => self::$limit ]; - } else { - return [ - 'links' => $pagination_uris, - 'offset' => 0, - 'limit' => self::$total - ]; } + + return [ + 'links' => $pagination_uris, + 'offset' => 0, + 'limit' => self::$total + ]; } /** @@ -173,7 +173,7 @@ private static function processParameters(): string if (count(self::$parameters) > 0) { foreach (self::$parameters as $parameter => $parameter_value) { if ($parameter_value !== null) { - if (strlen($parameters) > 0) { + if ($parameters !== '') { $parameters .= '&'; } @@ -192,7 +192,7 @@ private static function processParameters(): string } } - if (strlen($parameters) > 0) { + if ($parameters !== '') { $parameters .= '&'; } @@ -204,14 +204,14 @@ private static function processParameters(): string * * @return string */ - private static function processSortParameters() + private static function processSortParameters(): string { $sort_parameters = ''; foreach (self::$sort_parameters as $field => $order) { $sort_parameters .= '|' . $field . ':' . $order; } - if (strlen($sort_parameters) > 0) { + if ($sort_parameters !== '') { $sort_parameters = 'sort=' . ltrim($sort_parameters, '|') . '&'; } @@ -223,14 +223,14 @@ private static function processSortParameters() * * @return string */ - private static function processSearchParameters() + private static function processSearchParameters(): string { $search_parameters = ''; foreach (self::$search_parameters as $field => $partial_term) { $search_parameters .= '|' . $field . ':' . urlencode($partial_term); } - if (strlen($search_parameters) > 0) { + if ($search_parameters !== '') { $search_parameters = 'search=' . ltrim($search_parameters, '|') . '&'; } @@ -244,8 +244,8 @@ private static function processSearchParameters() */ private static function render(): array { - self::$offset = intval(request()->query('offset', 0)); - self::$limit = intval(request()->query('limit', self::$limit)); + self::$offset = (int) request()->query('offset', 0); + self::$limit = (int) request()->query('limit', self::$limit); if (self::$allow_override === true) { self::$collection = General::booleanValue(request()->query('collection', false)); } diff --git a/app/Utilities/Request.php b/app/Utilities/Request.php index 13439a67..3ef77717 100644 --- a/app/Utilities/Request.php +++ b/app/Utilities/Request.php @@ -33,7 +33,7 @@ public static function checkForInvalidFields(array $patchable_fields): ?JsonResp { $invalid_fields = []; foreach (request()->all() as $key => $value) { - if (in_array($key, $patchable_fields) === false) { + if (in_array($key, $patchable_fields, true) === false) { $invalid_fields[] = $key; } } @@ -54,7 +54,7 @@ public static function checkForInvalidFields(array $patchable_fields): ?JsonResp public static function checkForEmptyPatch(): ?JsonResponse { if (count(request()->all()) === 0) { - return UtilityResponse::nothingToPatch();; + return UtilityResponse::nothingToPatch(); } return null; diff --git a/app/Utilities/Response.php b/app/Utilities/Response.php index af6b9c18..eee715d9 100644 --- a/app/Utilities/Response.php +++ b/app/Utilities/Response.php @@ -3,9 +3,9 @@ namespace App\Utilities; -use App; use Exception; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\App; /** * Utility class to return default responses, we want some consistency @@ -23,9 +23,9 @@ class Response { - static protected function addException(array $response, ?Exception $e = null): array + protected static function addException(array $response, ?Exception $e = null): array { - if (App::environment() !== 'production' && $e !== null) { + if ($e !== null && App::environment() !== 'production') { $response['exception'] = [ 'message' => $e->getMessage(), 'file' => $e->getFile(), @@ -45,7 +45,7 @@ static protected function addException(array $response, ?Exception $e = null): a * * @return JsonResponse */ - static public function notFound(?string $type = null, ?Exception $e = null): JsonResponse + public static function notFound(?string $type = null, ?Exception $e = null): JsonResponse { $response = [ 'message' => ($type !== null) ? trans('responses.not-found-entity', ['type'=>$type]) : @@ -71,7 +71,7 @@ static public function notFound(?string $type = null, ?Exception $e = null): Jso * * @return JsonResponse */ - static public function notFoundOrNotAccessible(?string $type = null, ?Exception $e = null): JsonResponse + public static function notFoundOrNotAccessible(?string $type = null, ?Exception $e = null): JsonResponse { $response = [ 'message' => ($type !== null) ? trans('responses.not-found-or-not-accessible-entity', ['type'=>$type]) : @@ -97,7 +97,7 @@ static public function notFoundOrNotAccessible(?string $type = null, ?Exception * * @return JsonResponse */ - static public function foreignKeyConstraintError($message = '', ?Exception $e = null): JsonResponse + public static function foreignKeyConstraintError($message = '', ?Exception $e = null): JsonResponse { $response = [ 'message' => (strlen($message) > 0) ? $message : trans('responses.constraint') @@ -109,13 +109,13 @@ static public function foreignKeyConstraintError($message = '', ?Exception $e = response()->json( $response, - 500 + 409 )->send(); exit; } /** - * 500 error, unable to select the data ready to update + * 500 error, unable to select the data ready to enable us to update or delete * * Until we add logging this is an unknown server error, later we will * add MySQL error logging @@ -124,7 +124,7 @@ static public function foreignKeyConstraintError($message = '', ?Exception $e = * * @return JsonResponse */ - static public function failedToSelectModelForUpdate(?Exception $e = null): JsonResponse + public static function failedToSelectModelForUpdateOrDelete(?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.model-select-failure'), @@ -151,7 +151,7 @@ static public function failedToSelectModelForUpdate(?Exception $e = null): JsonR * * @return JsonResponse */ - static public function failedToSaveModelForUpdate(?Exception $e = null): JsonResponse + public static function failedToSaveModelForUpdate(?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.model-save-failure-update'), @@ -175,7 +175,7 @@ static public function failedToSaveModelForUpdate(?Exception $e = null): JsonRes * * @return JsonResponse */ - static public function authenticationRequired(?Exception $e = null): JsonResponse + public static function authenticationRequired(?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.authentication-required') @@ -202,7 +202,7 @@ static public function authenticationRequired(?Exception $e = null): JsonRespons * * @return JsonResponse */ - static public function failedToSaveModelForCreate(?Exception $e = null): JsonResponse + public static function failedToSaveModelForCreate(?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.model-save-failure-create'), @@ -227,7 +227,7 @@ static public function failedToSaveModelForCreate(?Exception $e = null): JsonRes * * @return JsonResponse */ - static public function unableToDecode(?Exception $e = null): JsonResponse + public static function unableToDecode(?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.decode-error') @@ -251,7 +251,7 @@ static public function unableToDecode(?Exception $e = null): JsonResponse * * @return JsonResponse */ - static public function successNoContent(?Exception $e = null): JsonResponse + public static function successNoContent(?Exception $e = null): JsonResponse { $response = []; @@ -271,7 +271,7 @@ static public function successNoContent(?Exception $e = null): JsonResponse * * @return JsonResponse */ - static public function successEmptyContent(bool $array = false, ?Exception $e = null): JsonResponse + public static function successEmptyContent(bool $array = false, ?Exception $e = null): JsonResponse { $response = ($array === true ? [] : null); @@ -290,7 +290,7 @@ static public function successEmptyContent(bool $array = false, ?Exception $e = * * @return JsonResponse */ - static public function nothingToPatch(?Exception $e = null) + public static function nothingToPatch(?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.patch-empty') @@ -315,7 +315,7 @@ static public function nothingToPatch(?Exception $e = null) * * @return JsonResponse */ - static public function invalidFieldsInRequest(array $invalid_fields, ?Exception $e = null): JsonResponse + public static function invalidFieldsInRequest(array $invalid_fields, ?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.patch-invalid'), @@ -340,7 +340,7 @@ static public function invalidFieldsInRequest(array $invalid_fields, ?Exception * * @return JsonResponse */ - static public function maintenance(?Exception $e = null): JsonResponse + public static function maintenance(?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.maintenance') @@ -365,7 +365,7 @@ static public function maintenance(?Exception $e = null): JsonResponse * * @return JsonResponse */ - static public function validationErrors(array $validation_errors, ?Exception $e = null): JsonResponse + public static function validationErrors(array $validation_errors, ?Exception $e = null): JsonResponse { $response = [ 'message' => trans('responses.validation'), diff --git a/app/Utilities/RoutePermission.php b/app/Utilities/RoutePermission.php index 5570d6cd..9f14958c 100644 --- a/app/Utilities/RoutePermission.php +++ b/app/Utilities/RoutePermission.php @@ -30,7 +30,7 @@ class RoutePermission * * @return array Two indexes, view and manage, values for both boolean */ - static public function category( + public static function category( $resource_type_id, $category_id, array $permitted_resource_types @@ -62,7 +62,7 @@ static public function category( * * @return array Two indexes, view and manage, values for both boolean */ - static public function subcategory( + public static function subcategory( $resource_type_id, $category_id, $subcategory_id, @@ -94,7 +94,7 @@ static public function subcategory( * * @return array Two indexes, view and manage, values for both boolean */ - static public function resourceType( + public static function resourceType( $resource_type_id, array $permitted_resource_types ): array @@ -121,7 +121,7 @@ static public function resourceType( * * @return array Two indexes, view and manage, values for both boolean */ - static public function resource( + public static function resource( $resource_type_id, $resource_id, array $permitted_resource_types @@ -152,7 +152,7 @@ static public function resource( * * @return array Two indexes, view and manage, values for both boolean */ - static public function item( + public static function item( $resource_type_id, $resource_id, $item_id, @@ -187,7 +187,7 @@ static public function item( * * @return array Two indexes, view and manage, values for both boolean */ - static public function itemCategory( + public static function itemCategory( $resource_type_id, $resource_id, $item_id, @@ -226,7 +226,7 @@ static public function itemCategory( * * @return array Two indexes, view and manage, values for both boolean */ - static public function itemSubcategory( + public static function itemSubcategory( $resource_type_id, $resource_id, $item_id, diff --git a/app/Validators/Fields/ItemPartialTransfer.php b/app/Validators/Fields/ItemPartialTransfer.php new file mode 100644 index 00000000..b6dfcec0 --- /dev/null +++ b/app/Validators/Fields/ItemPartialTransfer.php @@ -0,0 +1,78 @@ + + * @copyright Dean Blackborough 2018-2020 + * @license https://github.com/costs-to-expect/api/blob/master/LICENSE + */ +class ItemPartialTransfer extends BaseValidator +{ + /** + * Return the validator object for the create request + * + * @param array $options + * + * @return Validator + */ + public function create(array $options = []): Validator + { + $this->requiredIndexes([ + 'resource_type_id', + 'existing_resource_id' + ], + $options + ); + + $decode = $this->hash->resource()->decode(request()->input('resource_id')); + $resource_id = null; + if (count($decode) === 1) { + $resource_id = $decode[0]; + } + + // We need to merge the decoded resource_id with the POSTed data + return ValidatorFacade::make( + array_merge( + request()->all(), + [ + 'resource_id' => $resource_id, + ] + ), + array_merge( + [ + 'resource_id' => [ + 'required', + Rule::exists('resource', 'id')->where(function ($query) use ($options) + { + $query->where('resource_type_id', '=', $options['resource_type_id'])-> + where('id', '!=', $options['existing_resource_id']); + }), + ], + ], + Config::get('api.item-partial-transfer.validation.POST.fields') + ), + $this->translateMessages('api.item-partial-transfer.validation.POST.messages') + ); + } + + /** + * @param array $options + * + * @return Validator + */ + public function update(array $options = []): \Illuminate\Contracts\Validation\Validator + { + // TODO: Implement update() method. + } +} diff --git a/app/Validators/Route.php b/app/Validators/Route.php index 8a288d72..7842a964 100644 --- a/app/Validators/Route.php +++ b/app/Validators/Route.php @@ -29,16 +29,16 @@ class Route * @param integer $resource_type_id * @param integer $category_id * @param array $permitted_resource_types - * @param bool $manage + * @param bool $write */ static public function category( int $resource_type_id, int $category_id, array $permitted_resource_types, - bool $manage = false + bool $write = false ) { - if ($manage === false) { + if ($write === false) { if ( Category::existsToUserForViewing( $resource_type_id, @@ -70,17 +70,17 @@ static public function category( * @param integer $category_id * @param integer $subcategory_id * @param array $permitted_resource_types - * @param bool $manage + * @param bool $write */ static public function subcategory( int $resource_type_id, int $category_id, int $subcategory_id, array $permitted_resource_types, - $manage = false + $write = false ) { - if ($manage === false) { + if ($write === false) { if ( Subcategory::existsToUserForViewing( $resource_type_id, @@ -111,15 +111,15 @@ static public function subcategory( * * @param $resource_type_id * @param array $permitted_resource_types - * @param bool $manage + * @param bool $write */ static public function resourceType( $resource_type_id, array $permitted_resource_types, - bool $manage = false + bool $write = false ) { - if ($manage === false) { + if ($write === false) { if ( ResourceType::existsToUserForViewing( (int) $resource_type_id, @@ -147,16 +147,16 @@ static public function resourceType( * @param $resource_type_id * @param $resource_id * @param array $permitted_resource_types - * @param bool $manage + * @param bool $write */ static public function resource( $resource_type_id, $resource_id, array $permitted_resource_types, - bool $manage = false + bool $write = false ) { - if ($manage === false) { + if ($write === false) { if ( Resource::existsToUserForViewing( (int) $resource_type_id, @@ -187,17 +187,17 @@ static public function resource( * @param $resource_id * @param $item_id * @param array $permitted_resource_types - * @param bool $manage + * @param bool $write */ static public function item( $resource_type_id, $resource_id, $item_id, array $permitted_resource_types, - bool $manage = false + bool $write = false ) { - if ($manage === false) { + if ($write === false) { if ( Item::existsToUserForViewing( (int) $resource_type_id, @@ -231,7 +231,7 @@ static public function item( * @param $item_id * @param $item_category_id, * @param array $permitted_resource_types - * @param bool $manage + * @param bool $write */ static public function itemCategory( $resource_type_id, @@ -239,9 +239,9 @@ static public function itemCategory( $item_id, $item_category_id, array $permitted_resource_types, - bool $manage = false + bool $write = false ) { - if ($manage === false) { + if ($write === false) { if ( ItemCategory::existsToUserForViewing( (int) $resource_type_id, @@ -293,7 +293,7 @@ static public function itemType($item_type_id) * @param $item_category_id * @param $item_subcategory_id * @param array $permitted_resource_types - * @param bool $manage + * @param bool $write */ static public function itemSubcategory( $resource_type_id, @@ -302,9 +302,9 @@ static public function itemSubcategory( $item_category_id, $item_subcategory_id, array $permitted_resource_types, - bool $manage = false + bool $write = false ) { - if ($manage === false) { + if ($write === false) { if ( ItemSubcategory::existsToUserForViewing( (int) $resource_type_id, diff --git a/config/api/app/hashids.php b/config/api/app/hashids.php index 9163adae..9629171a 100644 --- a/config/api/app/hashids.php +++ b/config/api/app/hashids.php @@ -10,7 +10,10 @@ 'resource' => env('APP_HASH_SALT_RESOURCE'), 'item' => env('APP_HASH_SALT_ITEM'), 'item_category' => env('APP_HASH_SALT_ITEM_CATEGORY'), + 'item_partial_transfer' => env('APP_HASH_SALT_ITEM_PARTIAL_TRANSFER'), 'item_subcategory' => env('APP_HASH_SALT_ITEM_SUBCATEGORY'), + 'item_transfer' => env('APP_HASH_SALT_ITEM_TRANSFER'), 'item_type' => env('APP_HASH_SALT_ITEM_TYPE'), - 'permitted_user' => env('APP_HASH_SALT_PERMITTED_USER') + 'permitted_user' => env('APP_HASH_SALT_PERMITTED_USER'), + 'user' => env('APP_HASH_SALT_USER') ]; diff --git a/config/api/app/version.php b/config/api/app/version.php index bb685199..b36c97ef 100644 --- a/config/api/app/version.php +++ b/config/api/app/version.php @@ -3,9 +3,9 @@ declare(strict_types=1); return [ - 'version'=> '2.09.4', + 'version'=> '2.10.0', 'prefix' => 'v2', - 'release_date' => '2020-03-25', + 'release_date' => '2020-04-01', 'changelog' => [ 'api' => '/v2/changelog', 'markdown' => 'https://github.com/costs-to-expect/api/blob/master/CHANGELOG.md' diff --git a/config/api/category/validation.php b/config/api/category/validation.php index 6e036308..36581719 100644 --- a/config/api/category/validation.php +++ b/config/api/category/validation.php @@ -5,7 +5,10 @@ return [ 'POST' => [ 'fields' => [ - 'description' => 'required|string' + 'description' => [ + 'required', + 'string' + ] ], 'messages' => [ 'name.unique' => 'category/validation.name-unique' @@ -13,7 +16,11 @@ ], 'PATCH' => [ 'fields' => [ - 'description' => 'sometimes|string|max:255' + 'description' => [ + 'sometimes', + 'string', + 'max:255' + ] ], 'messages' => [ 'name.unique' => 'category/validation.name-unique' diff --git a/config/api/item-category/validation.php b/config/api/item-category/validation.php index c299cc20..d2de5cae 100644 --- a/config/api/item-category/validation.php +++ b/config/api/item-category/validation.php @@ -5,7 +5,10 @@ return [ 'POST' => [ 'fields' => [ - 'category_id' => 'required|exists:category,id' + 'category_id' => [ + 'required', + 'exists:category,id' + ] ], 'messages' => [ 'category_id.required' => 'item-category/validation.category_id-required', diff --git a/config/api/item-partial-transfer/fields.php b/config/api/item-partial-transfer/fields.php new file mode 100644 index 00000000..2675d739 --- /dev/null +++ b/config/api/item-partial-transfer/fields.php @@ -0,0 +1,27 @@ + [ + 'field' => 'resource_id', + 'title' => 'item-partial-transfer/fields.title-resource_id', + 'description' => 'item-partial-transfer/fields.description-resource_id', + 'type' => 'string', + 'validation' => [ + 'length' => 10 + ], + 'required' => true + ], + 'percentage' => [ + 'field' => 'percentage', + 'title' => 'item-partial-transfer/fields.title-percentage', + 'description' => 'item-partial-transfer/fields.description-percentage', + 'type' => 'integer', + 'validation' => [ + 'min' => 1, + 'max' => 99 + ], + 'required' => true + ] +]; diff --git a/config/api/item-partial-transfer/validation.php b/config/api/item-partial-transfer/validation.php new file mode 100644 index 00000000..30b59233 --- /dev/null +++ b/config/api/item-partial-transfer/validation.php @@ -0,0 +1,18 @@ + [ + 'fields' => [ + 'percentage' => [ + 'required', + 'integer', + 'between:1,99' + ] + ], + 'messages' => [ + 'resource_id.exists' => 'item-partial-transfer/validation.resource_id-exists' + ] + ] +]; diff --git a/config/api/item-subcategory/validation.php b/config/api/item-subcategory/validation.php index b116e37a..d21805e3 100644 --- a/config/api/item-subcategory/validation.php +++ b/config/api/item-subcategory/validation.php @@ -5,7 +5,10 @@ return [ 'POST' => [ 'fields' => [ - 'subcategory_id' => 'required|exists:sub_category,id' + 'subcategory_id' => [ + 'required', + 'exists:sub_category,id' + ] ], 'messages' => [ 'subcategory_id.required' => 'item-subcategory/validation.subcategory_id-required', diff --git a/config/api/item-type-allocated-expense/validation.php b/config/api/item-type-allocated-expense/validation.php index d90c3128..ae4b0bc4 100644 --- a/config/api/item-type-allocated-expense/validation.php +++ b/config/api/item-type-allocated-expense/validation.php @@ -5,12 +5,35 @@ return [ 'POST' => [ 'fields' => [ - 'name' => 'required|string|max:255', - 'description' => 'sometimes|string|max:255', - 'effective_date' => 'required|date_format:Y-m-d', - 'publish_after' => 'sometimes|date_format:Y-m-d', - 'total' => 'required|string|regex:/^\d+\.\d{2}$/', - 'percentage' => 'sometimes|required|integer|between:1,100' + 'name' => [ + 'required', + 'string', + 'max:255' + ], + 'description' => [ + 'sometimes', + 'string', + 'max:255' + ], + 'effective_date' => [ + 'required', + 'date_format:Y-m-d' + ], + 'publish_after' => [ + 'sometimes', + 'date_format:Y-m-d' + ], + 'total' => [ + 'required', + 'string', + 'regex:/^\d+\.\d{2}$/' + ], + 'percentage' => [ + 'sometimes', + 'required', + 'integer', + 'between:1,100' + ] ], 'messages' => [ 'total.regex' => 'item-type-allocated-expense/validation.total-regex' @@ -18,12 +41,36 @@ ], 'PATCH' => [ 'fields' => [ - 'name' => 'sometimes|string|max:255', - 'description' => 'sometimes|string|max:255', - 'effective_date' => 'sometimes|date_format:Y-m-d', - 'publish_after' => 'sometimes|date_format:Y-m-d', - 'total' => 'sometimes|string|regex:/^\d+\.\d{2}$/', - 'percentage' => 'sometimes|integer|between:1,100' + 'name' => [ + 'sometimes', + 'string', + 'max:255' + ], + 'description' => [ + 'sometimes', + 'nullable', + 'string', + 'max:255' + ], + 'effective_date' => [ + 'sometimes', + 'date_format:Y-m-d' + ], + 'publish_after' => [ + 'sometimes', + 'nullable', + 'date_format:Y-m-d' + ], + 'total' => [ + 'sometimes', + 'string', + 'regex:/^\d+\.\d{2}$/' + ], + 'percentage' => [ + 'sometimes', + 'integer', + 'between:1,100' + ] ], 'messages' => [ 'total.regex' => 'item-type-allocated-expense/validation.total-regex' diff --git a/config/api/item-type-simple-expense/validation.php b/config/api/item-type-simple-expense/validation.php index 4540a7df..4c8e520a 100644 --- a/config/api/item-type-simple-expense/validation.php +++ b/config/api/item-type-simple-expense/validation.php @@ -5,9 +5,21 @@ return [ 'POST' => [ 'fields' => [ - 'name' => 'required|string|max:255', - 'description' => 'sometimes|string|max:255', - 'total' => 'required|string|regex:/^\d+\.\d{2}$/', + 'name' => [ + 'required', + 'string', + 'max:255' + ], + 'description' => [ + 'sometimes', + 'string', + 'max:255' + ], + 'total' => [ + 'required', + 'string', + 'regex:/^\d+\.\d{2}$/' + ], ], 'messages' => [ 'total.regex' => 'item-type-simple-expense/validation.total-regex' @@ -15,9 +27,22 @@ ], 'PATCH' => [ 'fields' => [ - 'name' => 'sometimes|string|max:255', - 'description' => 'sometimes|string|max:255', - 'total' => 'sometimes|string|regex:/^\d+\.\d{2}$/', + 'name' => [ + 'sometimes', + 'string', + 'max:255' + ], + 'description' => [ + 'sometimes', + 'nullable', + 'string', + 'max:255' + ], + 'total' => [ + 'sometimes', + 'string', + 'regex:/^\d+\.\d{2}$/' + ], ], 'messages' => [ 'total.regex' => 'item-type-simple-expense/validation.total-regex' diff --git a/config/api/item-type-simple-item/validation.php b/config/api/item-type-simple-item/validation.php index 0c890c74..9244d1e1 100644 --- a/config/api/item-type-simple-item/validation.php +++ b/config/api/item-type-simple-item/validation.php @@ -5,17 +5,41 @@ return [ 'POST' => [ 'fields' => [ - 'name' => 'required|string|max:255', - 'description' => 'sometimes|string|max:255', - 'quantity' => 'required|integer|min:1', - ], + 'name' => + 'required', + 'string', + 'max:255' + ], + 'description' => [ + 'sometimes', + 'string', + 'max:255' + ], + 'quantity' => [ + 'required', + 'integer', + 'min:0' + ], 'messages' => [] ], 'PATCH' => [ 'fields' => [ - 'name' => 'sometimes|string|max:255', - 'description' => 'sometimes|string|max:255', - 'quantity' => 'sometimes|integer|min:1', + 'name' => [ + 'sometimes', + 'string', + 'max:255' + ], + 'description' => [ + 'sometimes', + 'nullable', + 'string', + 'max:255', + ], + 'quantity' => [ + 'sometimes', + 'integer', + 'min:0', + ] ], 'messages' => [] ] diff --git a/config/api/request-error-log/validation.php b/config/api/request-error-log/validation.php index e1f2a9dd..eca63c50 100644 --- a/config/api/request-error-log/validation.php +++ b/config/api/request-error-log/validation.php @@ -5,12 +5,33 @@ return [ 'POST' => [ 'fields' => [ - 'method' => 'required|string', - 'expected_status_code' => 'required|integer|between:100,530', - 'returned_status_code' => 'required|integer|between:100,530', - 'request_uri' => 'required|string', - 'source' => 'required|string|in:website,api,app,legacy,postman', - 'debug' => 'sometimes|string' + 'method' => [ + 'required', + 'string' + ], + 'expected_status_code' => [ + 'required', + 'integer', + 'between:100,530' + ], + 'returned_status_code' => [ + 'required', + 'integer', + 'between:100,530' + ], + 'request_uri' => [ + 'required', + 'string' + ], + 'source' => [ + 'required', + 'string', + 'in:website,api,app,legacy,postman' + ], + 'debug' => [ + 'sometimes', + 'string' + ] ], 'messages' => [] ] diff --git a/config/api/resource-type/validation.php b/config/api/resource-type/validation.php index 664a4148..a4026720 100644 --- a/config/api/resource-type/validation.php +++ b/config/api/resource-type/validation.php @@ -14,7 +14,11 @@ ], 'PATCH' => [ 'fields' => [ - 'description' => 'sometimes|string|max:255', + 'description' => [ + 'sometimes', + 'string', + 'max:255' + ], 'public' => 'sometimes|boolean' ], 'messages' => [ diff --git a/config/api/subcategory/validation.php b/config/api/subcategory/validation.php index d732b4e2..afb5879e 100644 --- a/config/api/subcategory/validation.php +++ b/config/api/subcategory/validation.php @@ -13,7 +13,12 @@ ], 'PATCH' => [ 'fields' => [ - 'description' => 'sometimes|string|max:255' + 'description' => + [ + 'sometimes', + 'string', + 'max:255' + ] ], 'messages' => [ 'name.unique' => 'subcategory/validation.name-unique' diff --git a/database/migrations/2020_03_29_124056_create_item_partial_transfer.php b/database/migrations/2020_03_29_124056_create_item_partial_transfer.php new file mode 100644 index 00000000..03b450d9 --- /dev/null +++ b/database/migrations/2020_03_29_124056_create_item_partial_transfer.php @@ -0,0 +1,46 @@ +charset = 'utf8mb4'; + $table->collation = 'utf8mb4_unicode_ci'; + + $table->bigIncrements('id'); + $table->unsignedBigInteger('resource_type_id'); + $table->unsignedBigInteger('from'); + $table->unsignedBigInteger('to'); + $table->unsignedBigInteger('item_id'); + $table->unsignedTinyInteger('percentage'); + $table->unsignedBigInteger('transferred_by'); + $table->timestamps(); + $table->foreign('resource_type_id')->references('id')->on('resource_type')->onDelete('cascade'); + $table->foreign('from')->references('id')->on('resource')->onDelete('cascade'); + $table->foreign('to')->references('id')->on('resource')->onDelete('cascade'); + $table->foreign('item_id')->references('id')->on('item')->onDelete('cascade'); + $table->foreign('transferred_by')->references('id')->on('users')->onDelete('cascade'); + $table->unique(['resource_type_id', 'from', 'item_id'], 'unique_item_partial_transfer'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('partial_transfer'); + } +} diff --git a/database/migrations/2020_03_31_103855_create_item_transfer.php b/database/migrations/2020_03_31_103855_create_item_transfer.php new file mode 100644 index 00000000..116c2962 --- /dev/null +++ b/database/migrations/2020_03_31_103855_create_item_transfer.php @@ -0,0 +1,45 @@ +charset = 'utf8mb4'; + $table->collation = 'utf8mb4_unicode_ci'; + + $table->bigIncrements('id'); + $table->unsignedBigInteger('resource_type_id'); + $table->unsignedBigInteger('from'); + $table->unsignedBigInteger('to'); + $table->unsignedBigInteger('item_id'); + $table->unsignedBigInteger('transferred_by'); + $table->timestamps(); + $table->foreign('resource_type_id')->references('id')->on('resource_type')->onDelete('cascade'); + $table->foreign('from')->references('id')->on('resource')->onDelete('cascade'); + $table->foreign('to')->references('id')->on('resource')->onDelete('cascade'); + $table->foreign('item_id')->references('id')->on('item')->onDelete('cascade'); + $table->foreign('transferred_by')->references('id')->on('users')->onDelete('cascade'); + $table->unique(['resource_type_id', 'from', 'item_id'], 'unique_item_partial_transfer'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index 063c3ae6..b56450a2 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -9,6 +9,8 @@ 'subcategory' => 'Subcategory', 'item' => 'Item (Expense)', 'item-category' => 'Assigned Category', + 'item-partial-transfer' => 'Item Partial Transfer', 'item-subcategory' => 'Assigned Subcategory', + 'item-transfer' => 'Item Transfer', 'item-type' => 'Item type' ]; diff --git a/resources/lang/en/item-partial-transfer/fields.php b/resources/lang/en/item-partial-transfer/fields.php new file mode 100644 index 00000000..c4b06129 --- /dev/null +++ b/resources/lang/en/item-partial-transfer/fields.php @@ -0,0 +1,11 @@ + 'Resource id', + 'description-resource_id' => 'Resource to partially transfer the item to', + + 'title-percentage' => 'Percentage', + 'description-percentage' => 'Percentage of the total to transfer to the given resource' +]; diff --git a/resources/lang/en/item-partial-transfer/validation.php b/resources/lang/en/item-partial-transfer/validation.php new file mode 100644 index 00000000..63aa0942 --- /dev/null +++ b/resources/lang/en/item-partial-transfer/validation.php @@ -0,0 +1,6 @@ + 'The provided resource does not belong to the current resource type or you are trying to partially transfer the item to the same resource', +]; diff --git a/resources/lang/en/item-transfer/fields.php b/resources/lang/en/item-transfer/fields.php index 87c2e867..2e99ac2c 100644 --- a/resources/lang/en/item-transfer/fields.php +++ b/resources/lang/en/item-transfer/fields.php @@ -4,5 +4,8 @@ return [ 'title-resource_id' => 'Resource id', - 'description-resource_id' => 'Resource to move the item to' + 'description-resource_id' => 'Resource to transfer the item to', + + 'title-percentage' => 'Percentage', + 'description-percentage' => 'The percentage of the total that should be allocated to the resource' ]; diff --git a/resources/lang/en/item-transfer/validation.php b/resources/lang/en/item-transfer/validation.php index 9057d035..f495093d 100644 --- a/resources/lang/en/item-transfer/validation.php +++ b/resources/lang/en/item-transfer/validation.php @@ -3,5 +3,5 @@ declare(strict_types=1); return [ - 'resource_id-exists' => 'The provided resource does not belong to the current resource type or you are trying to move the item to the same resource', + 'resource_id-exists' => 'The provided resource does not belong to the current resource type or you are trying to transfer the item to the same resource', ]; diff --git a/resources/lang/en/responses.php b/resources/lang/en/responses.php index 31176c89..ef1dd7e0 100644 --- a/resources/lang/en/responses.php +++ b/resources/lang/en/responses.php @@ -6,7 +6,7 @@ 'not-found' => 'The requested resource does not exist.', 'not-found-entity' => 'The requested `:type` does not exist.', 'not-found-or-not-accessible-entity' => 'The requested `:type` does not exist or is not accessible with your permissions.', - 'constraint' => 'Unable to handle your request, dependent data exists.', + 'constraint' => 'Unable to handle your request, dependent data exists or the result is being limited by a key.', 'model-select-failure' => 'Unable to handle your request, an error occurred when selecting the data to complete your request.', 'model-save-failure-update' => 'Unable to handle your request, an error occurred when processing your update request.', 'model-save-failure-create' => 'Unable to handle your request, an error occurred when processing your create request.', diff --git a/resources/lang/en/route-descriptions.php b/resources/lang/en/route-descriptions.php index 32f67af2..79ab8357 100644 --- a/resources/lang/en/route-descriptions.php +++ b/resources/lang/en/route-descriptions.php @@ -53,8 +53,15 @@ 'item_sub_category_PATCH' => 'Update the subcategory assigned to the selected item', 'item_sub_category_DELETE' => 'Delete the subcategory assigned to the selected item', + 'item_transfer_GET_index' => 'Return the transfers for the selected resource type', + 'item_transfer_GET_show' => 'Return the selected transfer', 'item_transfer_POST' => 'Transfer an item to another resource', + 'item_partial_transfer_GET_index' => 'Return the partial transfers for the selected resource type', + 'item_partial_transfer_GET_show' => 'Return the selected partial transfer', + 'item_partial_transfer_POST' => 'Reassign a percentage of the total for an item to another resource', + 'item_partial_transfer_DELETE' => 'Delete the selected partial transfer', + 'permitted_user_GET_index' => 'Return the permitted users', 'permitted_user_POST' => 'Assign a permitted user', diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index a44aff59..978547ba 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -204,7 +204,7 @@ function gtag(){dataLayer.push(arguments);}
-

Latest feature release [v2.09.0]

+

Latest feature release [v2.10.0]

The latest release of the Costs to Expect API is {{ $version }}; we released it on the {{ date('jS M Y', strtotime($date)) }}.

@@ -215,48 +215,30 @@ function gtag(){dataLayer.push(arguments);}

Added

Changed

Fixed

- -

Removed

- - diff --git a/routes/api/private-routes.php b/routes/api/private-routes.php index 7bdc2358..d442141d 100644 --- a/routes/api/private-routes.php +++ b/routes/api/private-routes.php @@ -52,6 +52,11 @@ function () { 'ItemSubcategoryController@create' ); + Route::post( + 'resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/partial-transfer', + 'ItemPartialTransferController@transfer' + ); + Route::post( 'resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/transfer', 'ItemTransferController@transfer' @@ -72,6 +77,11 @@ function () { 'SubcategoryController@delete' ); + Route::delete( + 'resource-types/{resource_type_id}/partial-transfers/{item_partial_transfer_id}', + 'ItemPartialTransferController@delete' + ); + Route::delete( 'resource-types/{resource_type_id}/resources/{resource_id}', 'ResourceController@delete' diff --git a/routes/api/public-routes.php b/routes/api/public-routes.php index 5457cfd5..a27ca7f7 100644 --- a/routes/api/public-routes.php +++ b/routes/api/public-routes.php @@ -74,16 +74,6 @@ function () { 'ResourceTypeController@optionsShow' ); - Route::get( - 'resource-types/{resource_type_id}/permitted-users', - 'PermittedUserController@index' - ); - - Route::options( - 'resource-types/{resource_type_id}/permitted-users', - 'PermittedUserController@optionsIndex' - ); - Route::get( 'resource-types/{resource_type_id}/categories', 'CategoryController@index' @@ -134,6 +124,36 @@ function () { 'ResourceTypeItemController@optionsIndex' ); + Route::get( + 'resource-types/{resource_type_id}/partial-transfers', + 'ItemPartialTransferController@index' + ); + + Route::options( + 'resource-types/{resource_type_id}/partial-transfers', + 'ItemPartialTransferController@optionsIndex' + ); + + Route::get( + 'resource-types/{resource_type_id}/partial-transfers/{item_partial_transfer_id}', + 'ItemPartialTransferController@show' + ); + + Route::options( + 'resource-types/{resource_type_id}/partial-transfers/{item_partial_transfer_id}', + 'ItemPartialTransferController@optionsShow' + ); + + Route::get( + 'resource-types/{resource_type_id}/permitted-users', + 'PermittedUserController@index' + ); + + Route::options( + 'resource-types/{resource_type_id}/permitted-users', + 'PermittedUserController@optionsIndex' + ); + Route::get( 'resource-types/{resource_type_id}/resources', 'ResourceController@index' @@ -174,6 +194,11 @@ function () { 'ItemController@optionsShow' ); + Route::options( + 'resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/partial-transfer', + 'ItemPartialTransferController@optionsTransfer' + ); + Route::options( 'resource-types/{resource_type_id}/resources/{resource_id}/items/{item_id}/transfer', 'ItemTransferController@optionsTransfer' @@ -219,6 +244,26 @@ function () { 'ItemSubcategoryController@optionsShow' ); + Route::options( + 'resource-types/{resource_type_id}/transfers', + 'ItemTransferController@optionsIndex' + ); + + Route::get( + 'resource-types/{resource_type_id}/transfers', + 'ItemTransferController@index' + ); + + Route::options( + 'resource-types/{resource_type_id}/transfers/{item_transfer_id}', + 'ItemTransferController@optionsShow' + ); + + Route::get( + 'resource-types/{resource_type_id}/transfers/{item_transfer_id}', + 'ItemTransferController@show' + ); + // Request access and error logs Route::options( 'request/error-log',