diff --git a/controllers/BatteriesController.php b/controllers/BatteriesController.php index 89d77f8b..cf6a4749 100644 --- a/controllers/BatteriesController.php +++ b/controllers/BatteriesController.php @@ -4,6 +4,7 @@ namespace Grocy\Controllers; use \Grocy\Services\BatteriesService; use \Grocy\Services\UsersService; +use \Grocy\Services\UserfieldsService; class BatteriesController extends BaseController { @@ -11,9 +12,11 @@ class BatteriesController extends BaseController { parent::__construct($container); $this->BatteriesService = new BatteriesService(); + $this->UserfieldsService = new UserfieldsService(); } protected $BatteriesService; + protected $UserfieldsService; public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { @@ -23,7 +26,9 @@ class BatteriesController extends BaseController return $this->AppContainer->view->render($response, 'batteriesoverview', [ 'batteries' => $this->Database->batteries()->orderBy('name'), 'current' => $this->BatteriesService->GetCurrent(), - 'nextXDays' => $nextXDays + 'nextXDays' => $nextXDays, + 'userfields' => $this->UserfieldsService->GetFields('batteries'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('batteries') ]); } @@ -37,7 +42,9 @@ class BatteriesController extends BaseController public function BatteriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { return $this->AppContainer->view->render($response, 'batteries', [ - 'batteries' => $this->Database->batteries()->orderBy('name') + 'batteries' => $this->Database->batteries()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('batteries'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('batteries') ]); } @@ -46,14 +53,16 @@ class BatteriesController extends BaseController if ($args['batteryId'] == 'new') { return $this->AppContainer->view->render($response, 'batteryform', [ - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->UserfieldsService->GetFields('batteries') ]); } else { return $this->AppContainer->view->render($response, 'batteryform', [ 'battery' => $this->Database->batteries($args['batteryId']), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->UserfieldsService->GetFields('batteries') ]); } } diff --git a/controllers/ChoresController.php b/controllers/ChoresController.php index c437290e..e483441b 100644 --- a/controllers/ChoresController.php +++ b/controllers/ChoresController.php @@ -4,6 +4,7 @@ namespace Grocy\Controllers; use \Grocy\Services\ChoresService; use \Grocy\Services\UsersService; +use \Grocy\Services\UserfieldsService; class ChoresController extends BaseController { @@ -11,9 +12,11 @@ class ChoresController extends BaseController { parent::__construct($container); $this->ChoresService = new ChoresService(); + $this->UserfieldsService = new UserfieldsService(); } protected $ChoresService; + protected $UserfieldsService; public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { @@ -23,7 +26,9 @@ class ChoresController extends BaseController return $this->AppContainer->view->render($response, 'choresoverview', [ 'chores' => $this->Database->chores()->orderBy('name'), 'currentChores' => $this->ChoresService->GetCurrent(), - 'nextXDays' => $nextXDays + 'nextXDays' => $nextXDays, + 'userfields' => $this->UserfieldsService->GetFields('chores'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('chores') ]); } @@ -38,7 +43,9 @@ class ChoresController extends BaseController public function ChoresList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { return $this->AppContainer->view->render($response, 'chores', [ - 'chores' => $this->Database->chores()->orderBy('name') + 'chores' => $this->Database->chores()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('chores'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('chores') ]); } @@ -57,7 +64,8 @@ class ChoresController extends BaseController { return $this->AppContainer->view->render($response, 'choreform', [ 'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'), - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->UserfieldsService->GetFields('chores') ]); } else @@ -65,7 +73,8 @@ class ChoresController extends BaseController return $this->AppContainer->view->render($response, 'choreform', [ 'chore' => $this->Database->chores($args['choreId']), 'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->UserfieldsService->GetFields('chores') ]); } } diff --git a/controllers/GenericEntityApiController.php b/controllers/GenericEntityApiController.php index 1881dcf4..8d95ea77 100644 --- a/controllers/GenericEntityApiController.php +++ b/controllers/GenericEntityApiController.php @@ -2,8 +2,18 @@ namespace Grocy\Controllers; +use \Grocy\Services\UserfieldsService; + class GenericEntityApiController extends BaseApiController { + public function __construct(\Slim\Container $container) + { + parent::__construct($container); + $this->UserfieldsService = new UserfieldsService(); + } + + protected $UserfieldsService; + public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity'])) @@ -44,7 +54,9 @@ class GenericEntityApiController extends BaseApiController $newRow = $this->Database->{$args['entity']}()->createRow($requestBody); $newRow->save(); $success = $newRow->isClean(); - return $this->EmptyApiResponse($response); + return $this->ApiResponse(array( + 'created_object_id' => $this->Database->lastInsertId() + )); } catch (\Exception $ex) { @@ -101,6 +113,38 @@ class GenericEntityApiController extends BaseApiController } } + public function GetUserfields(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + try + { + return $this->ApiResponse($this->UserfieldsService->GetValues($args['entity'], $args['objectId'])); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + + public function SetUserfields(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + $requestBody = $request->getParsedBody(); + + try + { + if ($requestBody === null) + { + throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); + } + + $this->UserfieldsService->SetValues($args['entity'], $args['objectId'], $requestBody); + return $this->EmptyApiResponse($response); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + private function IsValidEntity($entity) { return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum); diff --git a/controllers/GenericEntityController.php b/controllers/GenericEntityController.php new file mode 100644 index 00000000..ed1956c6 --- /dev/null +++ b/controllers/GenericEntityController.php @@ -0,0 +1,45 @@ +UserfieldsService = new UserfieldsService(); + } + + protected $UserfieldsService; + + public function UserfieldsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + return $this->AppContainer->view->render($response, 'userfields', [ + 'userfields' => $this->UserfieldsService->GetAllFields(), + 'entities' => $this->UserfieldsService->GetEntities() + ]); + } + + public function UserfieldEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + if ($args['userfieldId'] == 'new') + { + return $this->AppContainer->view->render($response, 'userfieldform', [ + 'mode' => 'create', + 'userfieldTypes' => $this->UserfieldsService->GetFieldTypes(), + 'entities' => $this->UserfieldsService->GetEntities() + ]); + } + else + { + return $this->AppContainer->view->render($response, 'userfieldform', [ + 'mode' => 'edit', + 'userfield' => $this->UserfieldsService->GetField($args['userfieldId']), + 'userfieldTypes' => $this->UserfieldsService->GetFieldTypes(), + 'entities' => $this->UserfieldsService->GetEntities() + ]); + } + } +} diff --git a/controllers/StockController.php b/controllers/StockController.php index bccb00cb..f292c629 100644 --- a/controllers/StockController.php +++ b/controllers/StockController.php @@ -4,6 +4,7 @@ namespace Grocy\Controllers; use \Grocy\Services\StockService; use \Grocy\Services\UsersService; +use \Grocy\Services\UserfieldsService; class StockController extends BaseController { @@ -12,9 +13,11 @@ class StockController extends BaseController { parent::__construct($container); $this->StockService = new StockService(); + $this->UserfieldsService = new UserfieldsService(); } protected $StockService; + protected $UserfieldsService; public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { @@ -29,7 +32,9 @@ class StockController extends BaseController 'currentStockLocations' => $this->StockService->GetCurrentStockLocations(), 'missingProducts' => $this->StockService->GetMissingProducts(), 'nextXDays' => $nextXDays, - 'productGroups' => $this->Database->product_groups()->orderBy('name') + 'productGroups' => $this->Database->product_groups()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('products'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('products') ]); } @@ -82,7 +87,9 @@ class StockController extends BaseController 'products' => $this->Database->products()->orderBy('name'), 'locations' => $this->Database->locations()->orderBy('name'), 'quantityunits' => $this->Database->quantity_units()->orderBy('name'), - 'productGroups' => $this->Database->product_groups()->orderBy('name') + 'productGroups' => $this->Database->product_groups()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('products'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('products') ]); } @@ -98,7 +105,9 @@ class StockController extends BaseController public function LocationsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { return $this->AppContainer->view->render($response, 'locations', [ - 'locations' => $this->Database->locations()->orderBy('name') + 'locations' => $this->Database->locations()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('locations'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('locations') ]); } @@ -106,14 +115,18 @@ class StockController extends BaseController { return $this->AppContainer->view->render($response, 'productgroups', [ 'productGroups' => $this->Database->product_groups()->orderBy('name'), - 'products' => $this->Database->products()->orderBy('name') + 'products' => $this->Database->products()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('product_groups'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('product_groups') ]); } public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { return $this->AppContainer->view->render($response, 'quantityunits', [ - 'quantityunits' => $this->Database->quantity_units()->orderBy('name') + 'quantityunits' => $this->Database->quantity_units()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('quantity_units'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('quantity_units') ]); } @@ -125,6 +138,7 @@ class StockController extends BaseController 'locations' => $this->Database->locations()->orderBy('name'), 'quantityunits' => $this->Database->quantity_units()->orderBy('name'), 'productgroups' => $this->Database->product_groups()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('products'), 'mode' => 'create' ]); } @@ -135,6 +149,7 @@ class StockController extends BaseController 'locations' => $this->Database->locations()->orderBy('name'), 'quantityunits' => $this->Database->quantity_units()->orderBy('name'), 'productgroups' => $this->Database->product_groups()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('products'), 'mode' => 'edit' ]); } @@ -145,14 +160,16 @@ class StockController extends BaseController if ($args['locationId'] == 'new') { return $this->AppContainer->view->render($response, 'locationform', [ - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->UserfieldsService->GetFields('locations') ]); } else { return $this->AppContainer->view->render($response, 'locationform', [ 'location' => $this->Database->locations($args['locationId']), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->UserfieldsService->GetFields('locations') ]); } } @@ -162,14 +179,16 @@ class StockController extends BaseController if ($args['productGroupId'] == 'new') { return $this->AppContainer->view->render($response, 'productgroupform', [ - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->UserfieldsService->GetFields('product_groups') ]); } else { return $this->AppContainer->view->render($response, 'productgroupform', [ 'group' => $this->Database->product_groups($args['productGroupId']), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->UserfieldsService->GetFields('product_groups') ]); } } @@ -179,14 +198,16 @@ class StockController extends BaseController if ($args['quantityunitId'] == 'new') { return $this->AppContainer->view->render($response, 'quantityunitform', [ - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->UserfieldsService->GetFields('quantity_units') ]); } else { return $this->AppContainer->view->render($response, 'quantityunitform', [ 'quantityunit' => $this->Database->quantity_units($args['quantityunitId']), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->UserfieldsService->GetFields('quantity_units') ]); } } diff --git a/controllers/TasksController.php b/controllers/TasksController.php index 9150a0b7..98f79dc2 100644 --- a/controllers/TasksController.php +++ b/controllers/TasksController.php @@ -4,6 +4,7 @@ namespace Grocy\Controllers; use \Grocy\Services\TasksService; use \Grocy\Services\UsersService; +use \Grocy\Services\UserfieldsService; class TasksController extends BaseController { @@ -11,9 +12,11 @@ class TasksController extends BaseController { parent::__construct($container); $this->TasksService = new TasksService(); + $this->UserfieldsService = new UserfieldsService(); } protected $TasksService; + protected $UserfieldsService; public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { @@ -44,7 +47,8 @@ class TasksController extends BaseController return $this->AppContainer->view->render($response, 'taskform', [ 'mode' => 'create', 'taskCategories' => $this->Database->task_categories()->orderBy('name'), - 'users' => $this->Database->users()->orderBy('username') + 'users' => $this->Database->users()->orderBy('username'), + 'userfields' => $this->UserfieldsService->GetFields('tasks') ]); } else @@ -53,7 +57,8 @@ class TasksController extends BaseController 'task' => $this->Database->tasks($args['taskId']), 'mode' => 'edit', 'taskCategories' => $this->Database->task_categories()->orderBy('name'), - 'users' => $this->Database->users()->orderBy('username') + 'users' => $this->Database->users()->orderBy('username'), + 'userfields' => $this->UserfieldsService->GetFields('tasks') ]); } } @@ -61,7 +66,9 @@ class TasksController extends BaseController public function TaskCategoriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { return $this->AppContainer->view->render($response, 'taskcategories', [ - 'taskCategories' => $this->Database->task_categories()->orderBy('name') + 'taskCategories' => $this->Database->task_categories()->orderBy('name'), + 'userfields' => $this->UserfieldsService->GetFields('task_categories'), + 'userfieldValues' => $this->UserfieldsService->GetAllValues('task_categories') ]); } @@ -70,14 +77,16 @@ class TasksController extends BaseController if ($args['categoryId'] == 'new') { return $this->AppContainer->view->render($response, 'taskcategoryform', [ - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->UserfieldsService->GetFields('task_categories') ]); } else { return $this->AppContainer->view->render($response, 'taskcategoryform', [ 'category' => $this->Database->task_categories($args['categoryId']), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->UserfieldsService->GetFields('task_categories') ]); } } diff --git a/data/viewcache/.gitignore b/data/viewcache/.gitignore deleted file mode 100644 index d6b7ef32..00000000 --- a/data/viewcache/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/grocy.openapi.json b/grocy.openapi.json index 59fdb17d..c7862444 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -234,8 +234,22 @@ } }, "responses": { - "204": { - "description": "The operation was successful" + "200": { + "description": "The operation was successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "created_object_id": { + "type": "number", + "format": "integer", + "description": "The id of the created object" + } + } + } + } + } }, "400": { "description": "The operation was not successful", @@ -438,6 +452,109 @@ } } }, + "/userfields/{entity}/{objectId}": { + "get": { + "summary": "Returns all userfields with their values of the given object of the given entity", + "tags": [ + "Generic entity interactions" + ], + "parameters": [ + { + "in": "path", + "name": "entity", + "required": true, + "description": "A valid entity name", + "schema": { + "$ref": "#/components/internalSchemas/ExposedEntity" + } + }, + { + "in": "path", + "name": "objectId", + "required": true, + "description": "A valid object id of the given entity", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "An entity object", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Just key/value pairs of userfields" + } + } + } + }, + "400": { + "description": "The operation was not successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericErrorResponse" + } + } + } + } + } + }, + "put": { + "summary": "Edits the given userfields of the given object of the given entity", + "tags": [ + "Generic entity interactions" + ], + "parameters": [ + { + "in": "path", + "name": "entity", + "required": true, + "description": "A valid entity name", + "schema": { + "$ref": "#/components/internalSchemas/ExposedEntity" + } + }, + { + "in": "path", + "name": "objectId", + "required": true, + "description": "A valid object id of the given entity", + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "description": "A valid entity object of the entity specified in parameter *entity*", + "required": true, + "content": { + "application/json": { + "schema": { + "description": "Just key/value pairs of userfields" + } + } + } + }, + "responses": { + "204": { + "description": "The operation was successful" + }, + "400": { + "description": "The operation was not successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericErrorResponse" + } + } + } + } + } + } + }, "/files/{group}/{fileName}": { "get": { "summary": "Serves the given file", @@ -2030,7 +2147,8 @@ "task_categories", "product_groups", "equipment", - "api_keys" + "api_keys", + "userfields" ] }, "ExposedEntitiesPreventListing": { diff --git a/localization/en/userfield_types.php b/localization/en/userfield_types.php new file mode 100644 index 00000000..6e338fb7 --- /dev/null +++ b/localization/en/userfield_types.php @@ -0,0 +1,11 @@ + 'Text (single line)', + 'text-multi-line' => 'Text (multi line)', + 'number-integral' => 'Number (integral)', + 'number-decimal' => 'Number (decimal)', + 'date' => 'Date (without time)', + 'datetime' => 'Date & time', + 'checkbox' => 'Checkbox' +); diff --git a/migrations/0066.sql b/migrations/0066.sql new file mode 100644 index 00000000..af4b18ec --- /dev/null +++ b/migrations/0066.sql @@ -0,0 +1,31 @@ +CREATE TABLE userfields ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, + entity TEXT NOT NULL, + name TEXT NOT NULL, + caption TEXT NOT NULL, + type TEXT NOT NULL, + show_as_column_in_tables TINYINT NOT NULL DEFAULT 0, + row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')), + + UNIQUE(entity, name) +); + +CREATE TABLE userfield_values ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, + field_id INTEGER NOT NULL, + object_id INTEGER NOT NULL, + value TEXT NOT NULL, + row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')), + + UNIQUE(field_id, object_id) +); + +CREATE VIEW userfield_values_resolved +AS +SELECT + u.*, + uv.object_id, + uv.value +FROM userfields u +JOIN userfield_values uv + ON u.id = uv.field_id; diff --git a/public/viewjs/batteryform.js b/public/viewjs/batteryform.js index 8a3c662d..3597f028 100644 --- a/public/viewjs/batteryform.js +++ b/public/viewjs/batteryform.js @@ -10,7 +10,11 @@ Grocy.Api.Post('objects/batteries', jsonData, function(result) { - window.location.href = U('/batteries'); + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/batteries'); + }); }, function(xhr) { @@ -24,7 +28,10 @@ Grocy.Api.Put('objects/batteries/' + Grocy.EditObjectId, jsonData, function(result) { - window.location.href = U('/batteries'); + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/batteries'); + }); }, function(xhr) { @@ -57,5 +64,6 @@ $('#battery-form input').keydown(function(event) } }); +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('battery-form'); diff --git a/public/viewjs/choreform.js b/public/viewjs/choreform.js index 44d24d04..160bfbdd 100644 --- a/public/viewjs/choreform.js +++ b/public/viewjs/choreform.js @@ -10,7 +10,11 @@ Grocy.Api.Post('objects/chores', jsonData, function(result) { - window.location.href = U('/chores'); + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/chores'); + }); }, function(xhr) { @@ -24,7 +28,10 @@ Grocy.Api.Put('objects/chores/' + Grocy.EditObjectId, jsonData, function(result) { - window.location.href = U('/chores'); + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/chores'); + }); }, function(xhr) { @@ -66,6 +73,7 @@ for (var i = 0; i < checkboxValues.length; i++) } } +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('chore-form'); diff --git a/public/viewjs/components/userfieldsform.js b/public/viewjs/components/userfieldsform.js new file mode 100644 index 00000000..1202589c --- /dev/null +++ b/public/viewjs/components/userfieldsform.js @@ -0,0 +1,69 @@ +Grocy.Components.UserfieldsForm = { }; + +Grocy.Components.UserfieldsForm.Save = function(success, error) +{ + var jsonData = { }; + + $("#userfields-form .userfield-input").each(function() + { + var input = $(this); + var fieldName = input.attr("id"); + var fieldValue = input.val(); + + if (input.attr("type") == "checkbox") + { + jsonData[fieldName] = "0"; + if (input.is(":checked")) + { + jsonData[fieldName] = "1"; + } + } + else + { + jsonData[fieldName] = fieldValue; + } + }); + + Grocy.Api.Put('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId, jsonData, + function(result) + { + if (success) + { + success(); + } + }, + function(xhr) + { + if (error) + { + error(); + } + } + ); +} + +Grocy.Components.UserfieldsForm.Load = function() +{ + Grocy.Api.Get('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId, + function(result) + { + $.each(result, function(key, value) + { + var input = $("#" + key + ".userfield-input"); + + if (input.attr("type") == "checkbox" && value == 1) + { + input.prop("checked", true); + } + else + { + input.val(value); + } + }); + }, + function(xhr) + { + console.log(xhr); + } + ); +} diff --git a/public/viewjs/locationform.js b/public/viewjs/locationform.js index 148bf41e..65e7862b 100644 --- a/public/viewjs/locationform.js +++ b/public/viewjs/locationform.js @@ -10,7 +10,11 @@ Grocy.Api.Post('objects/locations', jsonData, function(result) { - window.location.href = U('/locations'); + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/locations'); + }); }, function(xhr) { @@ -24,7 +28,10 @@ Grocy.Api.Put('objects/locations/' + Grocy.EditObjectId, jsonData, function(result) { - window.location.href = U('/locations'); + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/locations'); + }); }, function(xhr) { @@ -57,5 +64,6 @@ $('#location-form input').keydown(function (event) } }); +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('location-form'); diff --git a/public/viewjs/productform.js b/public/viewjs/productform.js index 5392b9a3..1de230ff 100644 --- a/public/viewjs/productform.js +++ b/public/viewjs/productform.js @@ -1,4 +1,4 @@ -$('#save-product-button').on('click', function (e) +$('#save-product-button').on('click', function(e) { e.preventDefault(); @@ -26,26 +26,30 @@ if (Grocy.EditMode === 'create') { Grocy.Api.Post('objects/products', jsonData, - function (result) + function(result) { - if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteProductPictureOnSave) + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() { - Grocy.Api.UploadFile($("#product-picture")[0].files[0], 'productpictures', jsonData.picture_file_name, - function (result) - { - window.location.href = redirectDestination; - }, - function (xhr) - { - Grocy.FrontendHelpers.EndUiBusy("product-form"); - Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) - } - ); - } - else - { - window.location.href = redirectDestination; - } + if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteProductPictureOnSave) + { + Grocy.Api.UploadFile($("#product-picture")[0].files[0], 'productpictures', jsonData.picture_file_name, + function(result) + { + window.location.href = redirectDestination; + }, + function (xhr) + { + Grocy.FrontendHelpers.EndUiBusy("product-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } + else + { + window.location.href = redirectDestination; + } + }); }, function (xhr) { @@ -74,24 +78,27 @@ Grocy.Api.Put('objects/products/' + Grocy.EditObjectId, jsonData, function(result) { - if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteProductPictureOnSave) + Grocy.Components.UserfieldsForm.Save(function() { - Grocy.Api.UploadFile($("#product-picture")[0].files[0], 'productpictures', jsonData.picture_file_name, - function(result) - { - window.location.href = redirectDestination; - }, - function(xhr) - { - Grocy.FrontendHelpers.EndUiBusy("product-form"); - Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) - } - ); - } - else - { - window.location.href = redirectDestination; - } + if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteProductPictureOnSave) + { + Grocy.Api.UploadFile($("#product-picture")[0].files[0], 'productpictures', jsonData.picture_file_name, + function(result) + { + window.location.href = redirectDestination; + }, + function(xhr) + { + Grocy.FrontendHelpers.EndUiBusy("product-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } + else + { + window.location.href = redirectDestination; + } + }); }, function(xhr) { @@ -258,6 +265,7 @@ if (Grocy.EditMode === 'create') } } +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); $('.input-group-qu').trigger('change'); Grocy.FrontendHelpers.ValidateForm('product-form'); diff --git a/public/viewjs/productgroupform.js b/public/viewjs/productgroupform.js index 42c6fd2d..d4095fa4 100644 --- a/public/viewjs/productgroupform.js +++ b/public/viewjs/productgroupform.js @@ -10,7 +10,11 @@ Grocy.Api.Post('objects/product_groups', jsonData, function(result) { - window.location.href = U('/productgroups'); + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/productgroups'); + }); }, function(xhr) { @@ -24,7 +28,10 @@ Grocy.Api.Put('objects/product_groups/' + Grocy.EditObjectId, jsonData, function(result) { - window.location.href = U('/productgroups'); + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/productgroups'); + }); }, function(xhr) { @@ -57,5 +64,6 @@ $('#product-group-form input').keydown(function (event) } }); +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('product-group-form'); diff --git a/public/viewjs/quantityunitform.js b/public/viewjs/quantityunitform.js index 3b58e6cb..1c282030 100644 --- a/public/viewjs/quantityunitform.js +++ b/public/viewjs/quantityunitform.js @@ -10,7 +10,11 @@ Grocy.Api.Post('objects/quantity_units', jsonData, function(result) { - window.location.href = U('/quantityunits'); + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/quantityunits'); + }); }, function(xhr) { @@ -24,7 +28,10 @@ Grocy.Api.Put('objects/quantity_units/' + Grocy.EditObjectId, jsonData, function(result) { - window.location.href = U('/quantityunits'); + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/quantityunits'); + }); }, function(xhr) { @@ -57,5 +64,6 @@ $('#quantityunit-form input').keydown(function(event) } }); +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('quantityunit-form'); diff --git a/public/viewjs/taskcategoryform.js b/public/viewjs/taskcategoryform.js index 464a0531..9658a745 100644 --- a/public/viewjs/taskcategoryform.js +++ b/public/viewjs/taskcategoryform.js @@ -10,7 +10,11 @@ Grocy.Api.Post('objects/task_categories', jsonData, function(result) { - window.location.href = U('/taskcategories'); + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/taskcategories'); + }); }, function(xhr) { @@ -24,7 +28,10 @@ Grocy.Api.Put('objects/task_categories/' + Grocy.EditObjectId, jsonData, function(result) { - window.location.href = U('/taskcategories'); + Grocy.Components.UserfieldsForm.Save(function() + { + window.location.href = U('/taskcategories'); + }); }, function(xhr) { @@ -57,5 +64,6 @@ $('#task-category-form input').keydown(function (event) } }); +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('task-category-form'); diff --git a/public/viewjs/userfieldform.js b/public/viewjs/userfieldform.js new file mode 100644 index 00000000..f4f308b2 --- /dev/null +++ b/public/viewjs/userfieldform.js @@ -0,0 +1,74 @@ +$('#save-userfield-button').on('click', function(e) +{ + e.preventDefault(); + + var jsonData = $('#userfield-form').serializeJSON(); + Grocy.FrontendHelpers.BeginUiBusy("userfield-form"); + + if (Grocy.EditMode === 'create') + { + Grocy.Api.Post('objects/userfields', jsonData, + function(result) + { + window.location.href = U('/userfields'); + }, + function(xhr) + { + Grocy.FrontendHelpers.EndUiBusy("userfield-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } + else + { + Grocy.Api.Put('objects/userfields/' + Grocy.EditObjectId, jsonData, + function(result) + { + window.location.href = U('/userfields'); + }, + function(xhr) + { + Grocy.FrontendHelpers.EndUiBusy("userfield-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } +}); + +$('#userfield-form input').keyup(function(event) +{ + Grocy.FrontendHelpers.ValidateForm('userfield-form'); +}); + +$('#userfield-form select').change(function(event) +{ + Grocy.FrontendHelpers.ValidateForm('userfield-form'); +}); + +$('#userfield-form input').keydown(function(event) +{ + if (event.keyCode === 13) //Enter + { + event.preventDefault(); + + if (document.getElementById('userfield-form').checkValidity() === false) //There is at least one validation error + { + return false; + } + else + { + $('#save-userfield-button').click(); + } + } +}); + +$('#entity').focus(); + +if (typeof GetUriParam("entity") !== "undefined") +{ + $("#entity").val(GetUriParam("entity")); + $("#entity").trigger("change"); + $('#name').focus(); +} + +Grocy.FrontendHelpers.ValidateForm('userfield-form'); diff --git a/public/viewjs/userfields.js b/public/viewjs/userfields.js new file mode 100644 index 00000000..32a80a0b --- /dev/null +++ b/public/viewjs/userfields.js @@ -0,0 +1,86 @@ +var userfieldsTable = $('#userfields-table').DataTable({ + 'paginate': false, + 'order': [[1, 'asc']], + 'columnDefs': [ + { 'orderable': false, 'targets': 0 } + ], + 'language': JSON.parse(L('datatables_localization')), + 'scrollY': false, + 'colReorder': true, + 'stateSave': true, + 'stateSaveParams': function(settings, data) + { + data.search.search = ""; + + data.columns.forEach(column => + { + column.search.search = ""; + }); + } +}); +$('#userfields-table tbody').removeClass("d-none"); +userfieldsTable.columns.adjust().draw(); + +$("#search").on("keyup", function() +{ + var value = $(this).val(); + if (value === "all") + { + value = ""; + } + + userfieldsTable.search(value).draw(); +}); + +$("#entity-filter").on("change", function() +{ + var value = $("#entity-filter option:selected").text(); + if (value === L("All")) + { + value = ""; + } + + userfieldsTable.column(1).search(value).draw(); +}); + +$(document).on('click', '.userfield-delete-button', function (e) +{ + var objectName = $(e.currentTarget).attr('data-userfield-name'); + var objectId = $(e.currentTarget).attr('data-userfield-id'); + + bootbox.confirm({ + message: L('Are you sure to delete user field "#1"?', objectName), + buttons: { + confirm: { + label: L('Yes'), + className: 'btn-success' + }, + cancel: { + label: L('No'), + className: 'btn-danger' + } + }, + callback: function(result) + { + if (result === true) + { + Grocy.Api.Delete('objects/userfields/' + objectId, {}, + function(result) + { + window.location.href = U('/userfields'); + }, + function(xhr) + { + console.error(xhr); + } + ); + } + } + }); +}); + +if (typeof GetUriParam("entity") !== "undefined") +{ + $("#entity-filter").val(GetUriParam("entity")); + $("#entity-filter").trigger("change"); +} diff --git a/routes.php b/routes.php index cb456c25..a68fd1a4 100644 --- a/routes.php +++ b/routes.php @@ -16,6 +16,10 @@ $app->group('', function() $this->post('/login', 'LoginControllerInstance:ProcessLogin')->setName('login'); $this->get('/logout', 'LoginControllerInstance:Logout'); + // Generic entity interaction + $this->get('/userfields', '\Grocy\Controllers\GenericEntityController:UserfieldsList'); + $this->get('/userfield/{userfieldId}', '\Grocy\Controllers\GenericEntityController:UserfieldEditForm'); + // User routes $this->get('/users', '\Grocy\Controllers\UsersController:UsersList'); $this->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm'); @@ -119,6 +123,8 @@ $app->group('/api', function() $this->post('/objects/{entity}', '\Grocy\Controllers\GenericEntityApiController:AddObject'); $this->put('/objects/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:EditObject'); $this->delete('/objects/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:DeleteObject'); + $this->get('/userfields/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:GetUserfields'); + $this->put('/userfields/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:SetUserfields'); // Files $this->put('/files/{group}/{fileName}', '\Grocy\Controllers\FilesApiController:UploadFile'); diff --git a/services/LocalizationService.php b/services/LocalizationService.php index 06f3a841..b165a454 100644 --- a/services/LocalizationService.php +++ b/services/LocalizationService.php @@ -28,6 +28,7 @@ class LocalizationService 'strings.php', 'stock_transaction_types.php', 'chore_types.php', + 'userfield_types.php', 'component_translations.php', 'demo_data.php' ); diff --git a/services/UserfieldsService.php b/services/UserfieldsService.php new file mode 100644 index 00000000..990d411e --- /dev/null +++ b/services/UserfieldsService.php @@ -0,0 +1,121 @@ +OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json')); + } + + protected $OpenApiSpec; + + public function GetFields($entity) + { + if (!$this->IsValidEntity($entity)) + { + throw new \Exception('Entity does not exist or is not exposed'); + } + + return $this->Database->userfields()->where('entity', $entity)->orderBy('name')->fetchAll(); + } + + public function GetField($fieldId) + { + return $this->Database->userfields($fieldId); + } + + public function GetAllFields() + { + return $this->Database->userfields()->orderBy('name')->fetchAll(); + } + + public function GetValues($entity, $objectId) + { + if (!$this->IsValidEntity($entity)) + { + throw new \Exception('Entity does not exist or is not exposed'); + } + + $userfields = $this->Database->userfield_values_resolved()->where('entity = :1 AND object_id = :2', $entity, $objectId)->orderBy('name')->fetchAll(); + $userfieldKeyValuePairs = array(); + foreach ($userfields as $userfield) + { + $userfieldKeyValuePairs[$userfield->name] = $userfield->value; + } + + return $userfieldKeyValuePairs; + } + + public function GetAllValues($entity) + { + if (!$this->IsValidEntity($entity)) + { + throw new \Exception('Entity does not exist or is not exposed'); + } + + return $this->Database->userfield_values_resolved()->where('entity', $entity)->orderBy('name')->fetchAll(); + } + + public function SetValues($entity, $objectId, $userfields) + { + if (!$this->IsValidEntity($entity)) + { + throw new \Exception('Entity does not exist or is not exposed'); + } + + foreach ($userfields as $key => $value) + { + $fieldRow = $this->Database->userfields()->where('entity = :1 AND name = :2', $entity, $key)->fetch(); + + if ($fieldRow === null) + { + throw new \Exception("Field $key is not a valid userfield of the given entity"); + } + + $fieldId = $fieldRow->id; + + $alreadyExistingEntry = $this->Database->userfield_values()->where('field_id = :1 AND object_id = :2', $fieldId, $objectId)->fetch(); + if ($alreadyExistingEntry) // Update + { + $alreadyExistingEntry->update(array( + 'value' => $value + )); + } + else // Insert + { + $newRow = $this->Database->userfield_values()->createRow(array( + 'field_id' => $fieldId, + 'object_id' => $objectId, + 'value' => $value + )); + $newRow->save(); + } + } + } + + public function GetEntities() + { + return $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum; + } + + public function GetFieldTypes() + { + return GetClassConstants('\Grocy\Services\UserfieldsService'); + } + + private function IsValidEntity($entity) + { + return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum); + } +} diff --git a/views/batteries.blade.php b/views/batteries.blade.php index 70c1274e..a3caaf8d 100644 --- a/views/batteries.blade.php +++ b/views/batteries.blade.php @@ -12,6 +12,9 @@  {{ $L('Add') }} + +  {{ $L('Configure userfields') }} + @@ -33,6 +36,11 @@ {{ $L('Description') }} {{ $L('Used in') }} {{ $L('Charge cycle interval (days)') }} + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -58,6 +66,12 @@ {{ $battery->charge_interval_days }} + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $battery->id) + )) + @endforeach diff --git a/views/batteriesoverview.blade.php b/views/batteriesoverview.blade.php index 17fdb62a..3bb3b954 100644 --- a/views/batteriesoverview.blade.php +++ b/views/batteriesoverview.blade.php @@ -46,6 +46,11 @@ {{ $L('Last charged') }} {{ $L('Next planned charge cycle') }} Hidden status + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -92,6 +97,12 @@ "@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s')) overdue @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $curentBatteryEntry->battery_id) + )) + @endforeach diff --git a/views/batteryform.blade.php b/views/batteryform.blade.php index cccf6904..3caeadfc 100644 --- a/views/batteryform.blade.php +++ b/views/batteryform.blade.php @@ -47,6 +47,11 @@ 'invalidFeedback' => $L('This cannot be negative') )) + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'batteries' + )) + diff --git a/views/choreform.blade.php b/views/choreform.blade.php index d928de31..960769d8 100644 --- a/views/choreform.blade.php +++ b/views/choreform.blade.php @@ -87,6 +87,11 @@ + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'chores' + )) + diff --git a/views/chores.blade.php b/views/chores.blade.php index c144f1f8..461ce8bd 100644 --- a/views/chores.blade.php +++ b/views/chores.blade.php @@ -12,6 +12,9 @@  {{ $L('Add') }} + +  {{ $L('Configure userfields') }} + @@ -32,6 +35,11 @@ {{ $L('Name') }} {{ $L('Period type') }} {{ $L('Description') }} + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -54,6 +62,12 @@ {{ $chore->description }} + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $chore->id) + )) + @endforeach diff --git a/views/choresoverview.blade.php b/views/choresoverview.blade.php index 4d6cf2d7..fcd79968 100644 --- a/views/choresoverview.blade.php +++ b/views/choresoverview.blade.php @@ -46,6 +46,11 @@ {{ $L('Next estimated tracking') }} {{ $L('Last tracked') }} Hidden status + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -92,6 +97,12 @@ @if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) overdue @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $curentChoreEntry->chore_id) + )) + @endforeach diff --git a/views/components/userfields_tbody.blade.php b/views/components/userfields_tbody.blade.php new file mode 100644 index 00000000..fb696963 --- /dev/null +++ b/views/components/userfields_tbody.blade.php @@ -0,0 +1,16 @@ +@push('componentScripts') + +@endpush + +@if(count($userfields) > 0) + +@foreach($userfields as $userfield) + +@if($userfield->show_as_column_in_tables == 1) + @php $userfieldObject = FindObjectInArrayByPropertyValue($userfieldValues, 'name', $userfield->name) @endphp + @if($userfieldObject !== null){{ $userfieldObject->value }}@endif +@endif + +@endforeach + +@endif diff --git a/views/components/userfields_thead.blade.php b/views/components/userfields_thead.blade.php new file mode 100644 index 00000000..cd7022f4 --- /dev/null +++ b/views/components/userfields_thead.blade.php @@ -0,0 +1,15 @@ +@push('componentScripts') + +@endpush + +@if(count($userfields) > 0) + +@foreach($userfields as $userfield) + +@if($userfield->show_as_column_in_tables == 1) + {{ $userfield->name }} +@endif + +@endforeach + +@endif diff --git a/views/components/userfieldsform.blade.php b/views/components/userfieldsform.blade.php new file mode 100644 index 00000000..af55b17a --- /dev/null +++ b/views/components/userfieldsform.blade.php @@ -0,0 +1,32 @@ +@push('componentScripts') + +@endpush + +@if(count($userfields) > 0) + +
+

{{ $L('Userfields') }}

+ + @foreach($userfields as $userfield) + + @if($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_SINGLE_LINE_TEXT) +
+ + +
+ @endif + + @if($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_CHECKBOX) +
+
+ + +
+
+ @endif + + @endforeach + +
+ +@endif diff --git a/views/locationform.blade.php b/views/locationform.blade.php index 6de7fafd..c93ec8c9 100644 --- a/views/locationform.blade.php +++ b/views/locationform.blade.php @@ -32,6 +32,11 @@ + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'locations' + )) + diff --git a/views/locations.blade.php b/views/locations.blade.php index 945167c5..692d7526 100644 --- a/views/locations.blade.php +++ b/views/locations.blade.php @@ -12,6 +12,9 @@  {{ $L('Add') }} + +  {{ $L('Configure userfields') }} + @@ -31,6 +34,11 @@ {{ $L('Name') }} {{ $L('Description') }} + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -50,6 +58,12 @@ {{ $location->description }} + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $location->id) + )) + @endforeach diff --git a/views/productform.blade.php b/views/productform.blade.php index da5630f1..ad0a402d 100644 --- a/views/productform.blade.php +++ b/views/productform.blade.php @@ -187,8 +187,14 @@ + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'products' + )) + +
diff --git a/views/productgroupform.blade.php b/views/productgroupform.blade.php index 919d31c0..cb0abc56 100644 --- a/views/productgroupform.blade.php +++ b/views/productgroupform.blade.php @@ -32,6 +32,11 @@
+ @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'product_groups' + )) + diff --git a/views/productgroups.blade.php b/views/productgroups.blade.php index c37d4339..4146e8ce 100644 --- a/views/productgroups.blade.php +++ b/views/productgroups.blade.php @@ -12,6 +12,9 @@  {{ $L('Add') }} + +  {{ $L('Configure userfields') }} + @@ -32,6 +35,10 @@ {{ $L('Name') }} {{ $L('Description') }} {{ $L('Product count') }} + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) @@ -57,6 +64,12 @@ + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $productGroup->id) + )) + @endforeach diff --git a/views/products.blade.php b/views/products.blade.php index fa2c74c9..24011b9f 100644 --- a/views/products.blade.php +++ b/views/products.blade.php @@ -12,6 +12,9 @@  {{ $L('Add') }} + +  {{ $L('Configure userfields') }} +  {{ $L('Presets for new products') }} @@ -48,6 +51,11 @@ {{ $L('QU stock') }} {{ $L('QU factor') }} {{ $L('Product group') }} + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -82,6 +90,12 @@ @if(!empty($product->product_group_id)) {{ FindObjectInArrayByPropertyValue($productGroups, 'id', $product->product_group_id)->name }} @endif + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $product->id) + )) + @endforeach diff --git a/views/quantityunitform.blade.php b/views/quantityunitform.blade.php index 74246f5e..f890245e 100644 --- a/views/quantityunitform.blade.php +++ b/views/quantityunitform.blade.php @@ -37,6 +37,11 @@ + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'quantity_units' + )) + diff --git a/views/quantityunits.blade.php b/views/quantityunits.blade.php index 65ec3def..b0bd3181 100644 --- a/views/quantityunits.blade.php +++ b/views/quantityunits.blade.php @@ -12,6 +12,9 @@  {{ $L('Add') }} + +  {{ $L('Configure userfields') }} + @@ -31,6 +34,10 @@ {{ $L('Name') }} {{ $L('Description') }} + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) @@ -50,6 +57,12 @@ {{ $quantityunit->description }} + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $quantityunit->id) + )) + @endforeach diff --git a/views/stockoverview.blade.php b/views/stockoverview.blade.php index 4de82b44..279d04b5 100644 --- a/views/stockoverview.blade.php +++ b/views/stockoverview.blade.php @@ -77,6 +77,11 @@ Hidden location Hidden status Hidden product group + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -143,6 +148,12 @@ @if($productGroup !== null){{ $productGroup->name }}@endif + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $currentStockEntry->product_id) + )) + @endforeach diff --git a/views/taskcategories.blade.php b/views/taskcategories.blade.php index beaae1d9..2af710c0 100644 --- a/views/taskcategories.blade.php +++ b/views/taskcategories.blade.php @@ -12,6 +12,9 @@  {{ $L('Add') }} + +  {{ $L('Configure userfields') }} + @@ -31,6 +34,11 @@ {{ $L('Name') }} {{ $L('Description') }} + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + @@ -50,6 +58,12 @@ {{ $taskCategory->description }} + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $taskCategory->id) + )) + @endforeach diff --git a/views/taskcategoryform.blade.php b/views/taskcategoryform.blade.php index bce77b3f..188e7e02 100644 --- a/views/taskcategoryform.blade.php +++ b/views/taskcategoryform.blade.php @@ -32,6 +32,11 @@ + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'task_categories' + )) + diff --git a/views/userfieldform.blade.php b/views/userfieldform.blade.php new file mode 100644 index 00000000..ccc224cb --- /dev/null +++ b/views/userfieldform.blade.php @@ -0,0 +1,71 @@ +@extends('layout.default') + +@if($mode == 'edit') + @section('title', $L('Edit userfield')) +@else + @section('title', $L('Create userfield')) +@endif + +@section('viewJsName', 'userfieldform') + +@section('content') +
+
+

@yield('title')

+ + + + @if($mode == 'edit') + + @endif + +
+ +
+ + +
{{ $L('A entity is required') }}
+
+ +
+ + +
{{ $L('A name is required') }}
+
+ +
+ + +
{{ $L('A caption is required') }}
+
+ +
+ + +
{{ $L('A type is required') }}
+
+ +
+
+ + show_as_column_in_tables == 1) checked @endif class="form-check-input" type="checkbox" id="show_as_column_in_tables" name="show_as_column_in_tables" value="1"> + +
+
+ + + +
+
+
+@stop diff --git a/views/userfields.blade.php b/views/userfields.blade.php new file mode 100644 index 00000000..7fd8dfde --- /dev/null +++ b/views/userfields.blade.php @@ -0,0 +1,76 @@ +@extends('layout.default') + +@section('title', $L('Userfields')) +@section('activeNav', 'userfields') +@section('viewJsName', 'userfields') + +@section('content') +
+
+

+ @yield('title') + +  {{ $L('Add') }} + +

+
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + @foreach($userfields as $userfield) + + + + + + + + @endforeach + +
{{ $L('Entity') }}{{ $L('Name') }}{{ $L('Caption') }}{{ $L('Type') }}
+ + + + + + + + {{ $userfield->entity }} + + {{ $userfield->name }} + + {{ $userfield->caption }} + + {{ $L($userfield->type) }} +
+
+
+@stop