mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Implement custom entities / objects (closes #242)
This commit is contained in:
parent
918f84f568
commit
096fb7a116
@ -1,3 +1,8 @@
|
|||||||
|
### New feature: Custom entities / objects
|
||||||
|
- Custom entities are based on Userfields and can be used to add any custom lists you want to have in grocy
|
||||||
|
- They can have an own menu entry in the sidebar
|
||||||
|
- => See "Manage master data" -> Userentities or try it on the demo: https://demo.grocy.info/userobjects/exampleuserentity
|
||||||
|
|
||||||
### Stock improvements
|
### Stock improvements
|
||||||
- Products can now have variations (nested products)
|
- Products can now have variations (nested products)
|
||||||
- Define the parent product for a product on the product edit page (only one level is possible, means a product which is used as a parent product in another product, cannot have a parent product itself)
|
- Define the parent product for a product on the product edit page (only one level is possible, means a product which is used as a parent product in another product, cannot have a parent product itself)
|
||||||
|
@ -16,21 +16,10 @@ class BaseController
|
|||||||
$localizationService = new LocalizationService(GROCY_CULTURE);
|
$localizationService = new LocalizationService(GROCY_CULTURE);
|
||||||
$this->LocalizationService = $localizationService;
|
$this->LocalizationService = $localizationService;
|
||||||
|
|
||||||
if (GROCY_MODE === 'prerelease')
|
|
||||||
{
|
|
||||||
$commitHash = trim(exec('git log --pretty="%h" -n1 HEAD'));
|
|
||||||
$commitDate = trim(exec('git log --date=iso --pretty="%cd" -n1 HEAD'));
|
|
||||||
|
|
||||||
$container->view->set('version', "pre-release-$commitHash");
|
|
||||||
$container->view->set('releaseDate', \substr($commitDate, 0, 19));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$applicationService = new ApplicationService();
|
$applicationService = new ApplicationService();
|
||||||
$versionInfo = $applicationService->GetInstalledVersion();
|
$versionInfo = $applicationService->GetInstalledVersion();
|
||||||
$container->view->set('version', $versionInfo->Version);
|
$container->view->set('version', $versionInfo->Version);
|
||||||
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
||||||
}
|
|
||||||
|
|
||||||
$container->view->set('__t', function(string $text, ...$placeholderValues) use($localizationService)
|
$container->view->set('__t', function(string $text, ...$placeholderValues) use($localizationService)
|
||||||
{
|
{
|
||||||
@ -64,6 +53,8 @@ class BaseController
|
|||||||
}
|
}
|
||||||
$container->view->set('featureFlags', $constants);
|
$container->view->set('featureFlags', $constants);
|
||||||
|
|
||||||
|
$container->view->set('userentitiesForSidebar', $this->Database->userentities()->where('show_in_sidebar_menu = 1')->orderBy('name'));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$usersService = new UsersService();
|
$usersService = new UsersService();
|
||||||
|
@ -22,6 +22,25 @@ class GenericEntityController extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function UserentitiesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'userentities', [
|
||||||
|
'userentities' => $this->Database->userentities()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function UserobjectsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$userentity = $this->Database->userentities()->where('name = :1', $args['userentityName'])->fetch();
|
||||||
|
|
||||||
|
return $this->AppContainer->view->render($response, 'userobjects', [
|
||||||
|
'userentity' => $userentity,
|
||||||
|
'userobjects' => $this->Database->userobjects()->where('userentity_id = :1', $userentity->id),
|
||||||
|
'userfields' => $this->UserfieldsService->GetFields('userentity-' . $args['userentityName']),
|
||||||
|
'userfieldValues' => $this->UserfieldsService->GetAllValues('userentity-' . $args['userentityName'])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function UserfieldEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function UserfieldEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
if ($args['userfieldId'] == 'new')
|
if ($args['userfieldId'] == 'new')
|
||||||
@ -42,4 +61,44 @@ class GenericEntityController extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function UserentityEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['userentityId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'userentityform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'userentityform', [
|
||||||
|
'mode' => 'edit',
|
||||||
|
'userentity' => $this->Database->userentities($args['userentityId'])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function UserobjectEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$userentity = $this->Database->userentities()->where('name = :1', $args['userentityName'])->fetch();
|
||||||
|
|
||||||
|
if ($args['userobjectId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'userobjectform', [
|
||||||
|
'userentity' => $userentity,
|
||||||
|
'mode' => 'create',
|
||||||
|
'userfields' => $this->UserfieldsService->GetFields('userentity-' . $args['userentityName'])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'userobjectform', [
|
||||||
|
'userentity' => $userentity,
|
||||||
|
'mode' => 'edit',
|
||||||
|
'userobject' => $this->Database->userobjects($args['userobjectId']),
|
||||||
|
'userfields' => $this->UserfieldsService->GetFields('userentity-' . $args['userentityName'])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2515,6 +2515,8 @@
|
|||||||
"equipment",
|
"equipment",
|
||||||
"api_keys",
|
"api_keys",
|
||||||
"userfields",
|
"userfields",
|
||||||
|
"userentities",
|
||||||
|
"userobjects",
|
||||||
"meal_plan"
|
"meal_plan"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -288,3 +288,15 @@ msgid "Slice"
|
|||||||
msgid_plural "Slices"
|
msgid_plural "Slices"
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "Example userentity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This is an example user entity..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Custom field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Example field value..."
|
||||||
|
msgstr ""
|
||||||
|
@ -1420,3 +1420,33 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Consume product on chore execution"
|
msgid "Consume product on chore execution"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Are you sure to delete user field \"%s\"?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Userentities"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create userentity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Show in sidebar menu"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Edit userentity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Edit %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Are you sure to delete this userobject?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Icon CSS class"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "For example"
|
||||||
|
msgstr ""
|
||||||
|
17
migrations/0085.sql
Normal file
17
migrations/0085.sql
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
CREATE TABLE userentities (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
caption TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
show_in_sidebar_menu TINYINT NOT NULL DEFAULT 1,
|
||||||
|
icon_css_class TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||||
|
|
||||||
|
UNIQUE(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE userobjects (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
userentity_id INTEGER NOT NULL,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
69
public/viewjs/userentities.js
Normal file
69
public/viewjs/userentities.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
var userentitiesTable = $('#userentities-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[1, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(__t('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#userentities-table tbody').removeClass("d-none");
|
||||||
|
userentitiesTable.columns.adjust().draw();
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
userentitiesTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.userentity-delete-button', function (e)
|
||||||
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-userentity-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-userentity-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: __t('Are you sure to delete userentity "%s"?', objectName),
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: __t('Yes'),
|
||||||
|
className: 'btn-success'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: __t('No'),
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result)
|
||||||
|
{
|
||||||
|
if (result === true)
|
||||||
|
{
|
||||||
|
Grocy.Api.Delete('objects/userentities/' + objectId, { },
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/userentities');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
68
public/viewjs/userentityform.js
Normal file
68
public/viewjs/userentityform.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
$('#save-userentity-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var jsonData = $('#userentity-form').serializeJSON();
|
||||||
|
Grocy.FrontendHelpers.BeginUiBusy("userentity-form");
|
||||||
|
|
||||||
|
var redirectUrl = U("/userentities");
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('objects/userentities', jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy("userentity-form");
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Put('objects/userentities/' + Grocy.EditObjectId, jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy("userentity-form");
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#userentity-form input').keyup(function(event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('userentity-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#userentity-form select').change(function(event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('userentity-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#userentity-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('userentity-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-userentity-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#name').focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('userentity-form');
|
47
public/viewjs/userobjectform.js
Normal file
47
public/viewjs/userobjectform.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
$('#save-userobject-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var jsonData = {};
|
||||||
|
jsonData.userentity_id = Grocy.EditObjectParentId;
|
||||||
|
console.log(jsonData);
|
||||||
|
Grocy.FrontendHelpers.BeginUiBusy("userobject-form");
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('objects/userobjects', jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
Grocy.EditObjectId = result.created_object_id;
|
||||||
|
Grocy.Components.UserfieldsForm.Save(function()
|
||||||
|
{
|
||||||
|
window.location.href = U('/userobjects/' + Grocy.EditObjectParentName);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy("userobject-form");
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Put('objects/userobjects/' + Grocy.EditObjectId, jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
Grocy.Components.UserfieldsForm.Save(function()
|
||||||
|
{
|
||||||
|
window.location.href = U('/userobjects/' + Grocy.EditObjectParentName);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy("userobject-form");
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Grocy.Components.UserfieldsForm.Load();
|
68
public/viewjs/userobjects.js
Normal file
68
public/viewjs/userobjects.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
var userobjectsTable = $('#userobjects-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[1, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(__t('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#userobjects-table tbody').removeClass("d-none");
|
||||||
|
userobjectsTable.columns.adjust().draw();
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
userobjectsTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.userobject-delete-button', function (e)
|
||||||
|
{
|
||||||
|
var objectId = $(e.currentTarget).attr('data-userobject-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: __t('Are you sure to delete this userobject?'),
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: __t('Yes'),
|
||||||
|
className: 'btn-success'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: __t('No'),
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result)
|
||||||
|
{
|
||||||
|
if (result === true)
|
||||||
|
{
|
||||||
|
Grocy.Api.Delete('objects/userobjects/' + objectId, { },
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -19,6 +19,10 @@ $app->group('', function()
|
|||||||
// Generic entity interaction
|
// Generic entity interaction
|
||||||
$this->get('/userfields', '\Grocy\Controllers\GenericEntityController:UserfieldsList');
|
$this->get('/userfields', '\Grocy\Controllers\GenericEntityController:UserfieldsList');
|
||||||
$this->get('/userfield/{userfieldId}', '\Grocy\Controllers\GenericEntityController:UserfieldEditForm');
|
$this->get('/userfield/{userfieldId}', '\Grocy\Controllers\GenericEntityController:UserfieldEditForm');
|
||||||
|
$this->get('/userentities', '\Grocy\Controllers\GenericEntityController:UserentitiesList');
|
||||||
|
$this->get('/userentity/{userentityId}', '\Grocy\Controllers\GenericEntityController:UserentityEditForm');
|
||||||
|
$this->get('/userobjects/{userentityName}', '\Grocy\Controllers\GenericEntityController:UserobjectsList');
|
||||||
|
$this->get('/userobject/{userentityName}/{userobjectId}', '\Grocy\Controllers\GenericEntityController:UserobjectEditForm');
|
||||||
|
|
||||||
// User routes
|
// User routes
|
||||||
$this->get('/users', '\Grocy\Controllers\UsersController:UsersList');
|
$this->get('/users', '\Grocy\Controllers\UsersController:UsersList');
|
||||||
|
@ -8,9 +8,22 @@ class ApplicationService extends BaseService
|
|||||||
public function GetInstalledVersion()
|
public function GetInstalledVersion()
|
||||||
{
|
{
|
||||||
if ($this->InstalledVersion == null)
|
if ($this->InstalledVersion == null)
|
||||||
|
{
|
||||||
|
if (GROCY_MODE === 'prerelease')
|
||||||
|
{
|
||||||
|
$commitHash = trim(exec('git log --pretty="%h" -n1 HEAD'));
|
||||||
|
$commitDate = trim(exec('git log --date=iso --pretty="%cd" -n1 HEAD'));
|
||||||
|
|
||||||
|
$this->InstalledVersion = array(
|
||||||
|
'Version' => "pre-release-$commitHash",
|
||||||
|
'ReleaseDate' => substr($commitDate, 0, 19)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
$this->InstalledVersion = json_decode(file_get_contents(__DIR__ . '/../version.json'));
|
$this->InstalledVersion = json_decode(file_get_contents(__DIR__ . '/../version.json'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->InstalledVersion;
|
return $this->InstalledVersion;
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,19 @@ class DemoDataGeneratorService extends BaseService
|
|||||||
INSERT INTO equipment (name, description, instruction_manual_file_name) VALUES ('{$this->__t_sql('Coffee machine')}', '{$loremIpsumWithHtmlFormattings}', 'loremipsum.pdf'); --1
|
INSERT INTO equipment (name, description, instruction_manual_file_name) VALUES ('{$this->__t_sql('Coffee machine')}', '{$loremIpsumWithHtmlFormattings}', 'loremipsum.pdf'); --1
|
||||||
INSERT INTO equipment (name, description) VALUES ('{$this->__t_sql('Dishwasher')}', '{$loremIpsumWithHtmlFormattings}'); --2
|
INSERT INTO equipment (name, description) VALUES ('{$this->__t_sql('Dishwasher')}', '{$loremIpsumWithHtmlFormattings}'); --2
|
||||||
|
|
||||||
|
INSERT INTO userentities (name, caption, description, show_in_sidebar_menu, icon_css_class) VALUES ('exampleuserentity', '{$this->__t_sql('Example userentity')}', '{$this->__t_sql('This is an example user entity...')}', 1, 'fas fa-smile'); --1
|
||||||
|
|
||||||
|
INSERT INTO userfields (entity, name, caption, type, show_as_column_in_tables) VALUES ('userentity-exampleuserentity', 'customfield1', '{$this->__t_sql('Custom field')} 1', 'text-single-line', 1); --1
|
||||||
|
INSERT INTO userfields (entity, name, caption, type, show_as_column_in_tables) VALUES ('userentity-exampleuserentity', 'customfield2', '{$this->__t_sql('Custom field')} 2', 'text-single-line', 1); --2
|
||||||
|
|
||||||
|
INSERT INTO userobjects (userentity_id) VALUES (1); --1
|
||||||
|
INSERT INTO userobjects (userentity_id) VALUES (1); --2
|
||||||
|
|
||||||
|
INSERT INTO userfield_values (field_id, object_id, value) VALUES (1, 1, '{$this->__t_sql('Example field value...')}');
|
||||||
|
INSERT INTO userfield_values (field_id, object_id, value) VALUES (2, 1, '{$this->__t_sql('Example field value...')}');
|
||||||
|
INSERT INTO userfield_values (field_id, object_id, value) VALUES (1, 2, '{$this->__t_sql('Example field value...')}');
|
||||||
|
INSERT INTO userfield_values (field_id, object_id, value) VALUES (2, 2, '{$this->__t_sql('Example field value...')}');
|
||||||
|
|
||||||
INSERT INTO migrations (migration) VALUES (-1);
|
INSERT INTO migrations (migration) VALUES (-1);
|
||||||
";
|
";
|
||||||
|
|
||||||
|
@ -109,7 +109,15 @@ class UserfieldsService extends BaseService
|
|||||||
|
|
||||||
public function GetEntities()
|
public function GetEntities()
|
||||||
{
|
{
|
||||||
return $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum;
|
$exposedDefaultEntities = $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum;
|
||||||
|
|
||||||
|
$userentities = array();
|
||||||
|
foreach ($this->Database->userentities()->orderBy('name') as $userentity)
|
||||||
|
{
|
||||||
|
$userentities[] = 'userentity-' . $userentity->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge($exposedDefaultEntities, $userentities);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetFieldTypes()
|
public function GetFieldTypes()
|
||||||
@ -119,6 +127,6 @@ class UserfieldsService extends BaseService
|
|||||||
|
|
||||||
private function IsValidEntity($entity)
|
private function IsValidEntity($entity)
|
||||||
{
|
{
|
||||||
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum);
|
return in_array($entity, $this->GetEntities());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,6 +187,17 @@
|
|||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@php $firstUserentity = true; @endphp
|
||||||
|
@foreach($userentitiesForSidebar as $userentity)
|
||||||
|
<li class="nav-item @if($firstUserentity) mt-4 @endif" data-toggle="tooltip" data-placement="right" title="{{ $userentity->caption }}" data-nav-for-page="userentity-{{ $userentity->name }}">
|
||||||
|
<a class="nav-link discrete-link" href="{{ $U('/userobjects/' . $userentity->name) }}">
|
||||||
|
<i class="{{ $userentity->icon_css_class }}"></i>
|
||||||
|
<span class="nav-link-text">{{ $userentity->caption }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@php if ($firstUserentity) { $firstUserentity = false; } @endphp
|
||||||
|
@endforeach
|
||||||
|
|
||||||
<li class="nav-item mt-4" data-toggle="tooltip" data-placement="right" title="{{ $__t('Manage master data') }}">
|
<li class="nav-item mt-4" data-toggle="tooltip" data-placement="right" title="{{ $__t('Manage master data') }}">
|
||||||
<a class="nav-link nav-link-collapse collapsed discrete-link" data-toggle="collapse" href="#top-nav-manager-master-data">
|
<a class="nav-link nav-link-collapse collapsed discrete-link" data-toggle="collapse" href="#top-nav-manager-master-data">
|
||||||
<i class="fas fa-table"></i>
|
<i class="fas fa-table"></i>
|
||||||
@ -249,6 +260,12 @@
|
|||||||
<span class="nav-link-text">{{ $__t('Userfields') }}</span>
|
<span class="nav-link-text">{{ $__t('Userfields') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li data-nav-for-page="userentities" data-sub-menu-of="#top-nav-manager-master-data">
|
||||||
|
<a class="nav-link discrete-link" href="{{ $U('/userentities') }}">
|
||||||
|
<i class="fas fa-bookmark "></i>
|
||||||
|
<span class="nav-link-text">{{ $__t('Userentities') }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
62
views/userentities.blade.php
Normal file
62
views/userentities.blade.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@section('title', $__t('Userentities'))
|
||||||
|
@section('activeNav', 'userentities')
|
||||||
|
@section('viewJsName', 'userentities')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1>
|
||||||
|
@yield('title')
|
||||||
|
<a id="new-userentity-button" class="btn btn-outline-dark" href="{{ $U('/userentity/new') }}">
|
||||||
|
<i class="fas fa-plus"></i> {{ $__t('Add') }}
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||||
|
<label for="search">{{ $__t('Search') }}</label> <i class="fas fa-search"></i>
|
||||||
|
<input type="text" class="form-control" id="search">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<table id="userentities-table" class="table table-sm table-striped dt-responsive">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="border-right"></th>
|
||||||
|
<th>{{ $__t('Name') }}</th>
|
||||||
|
<th>{{ $__t('Caption') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="d-none">
|
||||||
|
@foreach($userentities as $userentity)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content border-right">
|
||||||
|
<a class="btn btn-info btn-sm" href="{{ $U('/userentity/') }}{{ $userentity->id }}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger btn-sm userentity-delete-button" href="#" data-userentity-id="{{ $userentity->id }}" data-userentity-name="{{ $userentity->name }}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{{ $U('/userfields?entity=userentity-') }}{{ $userentity->name }}">
|
||||||
|
<i class="fas fa-th-list"></i> {{ $__t('Configure userfields') }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $userentity->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $userentity->caption }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
59
views/userentityform.blade.php
Normal file
59
views/userentityform.blade.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@if($mode == 'edit')
|
||||||
|
@section('title', $__t('Edit userentity'))
|
||||||
|
@else
|
||||||
|
@section('title', $__t('Create userentity'))
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@section('viewJsName', 'userentityform')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-xs-12">
|
||||||
|
<h1>@yield('title')</h1>
|
||||||
|
|
||||||
|
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||||
|
|
||||||
|
@if($mode == 'edit')
|
||||||
|
<script>Grocy.EditObjectId = {{ $userentity->id }};</script>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form id="userentity-form" novalidate>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">{{ $__t('Name') }}</label>
|
||||||
|
<input @if($mode == 'edit') disabled @endif type="text" class="form-control" required pattern="^[a-zA-Z0-9]*$" id="name" name="name" value="@if($mode == 'edit'){{ $userentity->name }}@endif">
|
||||||
|
<div class="invalid-feedback">{{ $__t('This is required and can only contain letters and numbers') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">{{ $__t('Caption') }}</label>
|
||||||
|
<input type="text" class="form-control" required id="caption" name="caption" value="@if($mode == 'edit'){{ $userentity->caption }}@endif">
|
||||||
|
<div class="invalid-feedback">{{ $__t('A caption is required') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">{{ $__t('Description') }}</label>
|
||||||
|
<textarea class="form-control" rows="2" id="description" name="description">@if($mode == 'edit'){{ $userentity->description }}@endif</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="hidden" name="show_in_sidebar_menu" value="0">
|
||||||
|
<input @if($mode == 'edit' && $userentity->show_in_sidebar_menu == 1) checked @endif class="form-check-input" type="checkbox" id="show_in_sidebar_menu" name="show_in_sidebar_menu" value="1">
|
||||||
|
<label class="form-check-label" for="show_in_sidebar_menu">{{ $__t('Show in sidebar menu') }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">{{ $__t('Icon CSS class') }}</label>
|
||||||
|
<input type="text" class="form-control" id="icon_css_class" name="icon_css_class" value="@if($mode == 'edit'){{ $userentity->icon_css_class }}@endif" placeholder='{{ $__t('For example') }} "fas fa-smile"'>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="save-userentity-button" class="btn btn-success">{{ $__t('Save') }}</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
38
views/userobjectform.blade.php
Normal file
38
views/userobjectform.blade.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@if($mode == 'edit')
|
||||||
|
@section('title', $__t('Edit %s', $userentity->caption))
|
||||||
|
@else
|
||||||
|
@section('title', $__t('Create %s', $userentity->caption))
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@section('viewJsName', 'userobjectform')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-xs-12">
|
||||||
|
<h1>@yield('title')</h1>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Grocy.EditMode = '{{ $mode }}';
|
||||||
|
Grocy.EditObjectParentId = {{ $userentity->id }};
|
||||||
|
Grocy.EditObjectParentName = "{{ $userentity->name }}";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@if($mode == 'edit')
|
||||||
|
<script>Grocy.EditObjectId = {{ $userobject->id }};</script>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form id="userobject-form" novalidate>
|
||||||
|
|
||||||
|
@include('components.userfieldsform', array(
|
||||||
|
'userfields' => $userfields,
|
||||||
|
'entity' => 'userentity-' . $userentity->name
|
||||||
|
))
|
||||||
|
|
||||||
|
<button id="save-userobject-button" class="btn btn-success">{{ $__t('Save') }}</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
66
views/userobjects.blade.php
Normal file
66
views/userobjects.blade.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@section('title', $userentity->caption)
|
||||||
|
@section('activeNav', 'userentity-' . $userentity->name)
|
||||||
|
@section('viewJsName', 'userobjects')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1>
|
||||||
|
@yield('title')
|
||||||
|
<a class="btn btn-outline-dark" href="{{ $U('/userobject/' . $userentity->name . '/new') }}">
|
||||||
|
<i class="fas fa-plus"></i> {{ $__t('Add') }}
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-outline-secondary" href="{{ $U('/userfields?entity=' . 'userentity-' . $userentity->name) }}">
|
||||||
|
<i class="fas fa-sliders-h"></i> {{ $__t('Configure userfields') }}
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<h5 class="text-muted">{{ $userentity->description }}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||||
|
<label for="search">{{ $__t('Search') }}</label> <i class="fas fa-search"></i>
|
||||||
|
<input type="text" class="form-control" id="search">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<table id="userobjects-table" class="table table-sm table-striped dt-responsive">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="border-right"></th>
|
||||||
|
|
||||||
|
@include('components.userfields_thead', array(
|
||||||
|
'userfields' => $userfields
|
||||||
|
))
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="d-none">
|
||||||
|
@foreach($userobjects as $userobject)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content border-right">
|
||||||
|
<a class="btn btn-info btn-sm" href="{{ $U('/userobject/' . $userentity->name . '/') }}{{ $userobject->id }}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger btn-sm userobject-delete-button" href="#" data-userobject-id="{{ $userobject->id }}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
@include('components.userfields_tbody', array(
|
||||||
|
'userfields' => $userfields,
|
||||||
|
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $userobject->id)
|
||||||
|
))
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
Loading…
x
Reference in New Issue
Block a user