Refined permissions by existing feature structure (closes #971, references #960)

This commit is contained in:
Bernd Bestel 2020-08-29 18:31:28 +02:00
parent a8395cb748
commit 86300b7025
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
21 changed files with 322 additions and 223 deletions

View File

@ -13,7 +13,7 @@ class BatteriesApiController extends BaseApiController
public function TrackChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_BATTERY_TRACK_CHARGE_CYCLE);
User::checkPermission($request, User::PERMISSION_BATTERIES_TRACK_CHARGE_CYCLE);
$requestBody = $request->getParsedBody();
@ -53,7 +53,7 @@ class BatteriesApiController extends BaseApiController
public function UndoChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_BATTERY_UNDO_TRACK_CHARGE_CYCLE);
User::checkPermission($request, User::PERMISSION_BATTERIES_UNDO_CHARGE_CYCLE);
try
{

View File

@ -17,7 +17,7 @@ class ChoresApiController extends BaseApiController
try
{
User::checkPermission($request, User::PERMISSION_CHORE_TRACK);
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION);
$trackedTime = date('Y-m-d H:i:s');
if (array_key_exists('tracked_time', $requestBody) && (IsIsoDateTime($requestBody['tracked_time']) || IsIsoDate($requestBody['tracked_time'])))
@ -31,7 +31,7 @@ class ChoresApiController extends BaseApiController
$doneBy = $requestBody['done_by'];
}
if($doneBy != GROCY_USER_ID)
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_OTHERS);
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION_EXECUTION);
$choreExecutionId = $this->getChoresService()->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->ApiResponse($response, $this->getDatabase()->chores_log($choreExecutionId));
@ -63,7 +63,7 @@ class ChoresApiController extends BaseApiController
{
try
{
User::checkPermission($request, User::PERMISSION_CHORE_UNDO);
User::checkPermission($request, User::PERMISSION_CHORE_UNDO_EXECUTION);
$this->ApiResponse($response, $this->getChoresService()->UndoChoreExecution($args['executionId']));
return $this->EmptyApiResponse($response);
@ -78,8 +78,6 @@ class ChoresApiController extends BaseApiController
{
try
{
User::checkPermission($request, User::PERMISSION_CHORE_EDIT);
$requestBody = $request->getParsedBody();
$choreId = null;

View File

@ -16,8 +16,6 @@ class FilesApiController extends BaseApiController
{
try
{
User::checkPermission($request, User::PERMISSION_UPLOAD_FILE);
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = base64_decode($args['fileName']);
@ -100,8 +98,6 @@ class FilesApiController extends BaseApiController
{
try
{
User::checkPermission($request, User::PERMISSION_DELETE_FILE);
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = base64_decode($args['fileName']);

View File

@ -29,7 +29,7 @@ class RecipesApiController extends BaseApiController
public function ConsumeRecipe(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_PRODUCT_CONSUME);
User::checkPermission($request, User::PERMISSION_STOCK_CONSUME);
try
{

View File

@ -63,7 +63,7 @@ class StockApiController extends BaseApiController
public function AddProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_PRODUCT_PURCHASE);
User::checkPermission($request, User::PERMISSION_STOCK_PURCHASE);
$requestBody = $request->getParsedBody();
@ -246,7 +246,7 @@ class StockApiController extends BaseApiController
public function ConsumeProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_PRODUCT_CONSUME);
User::checkPermission($request, User::PERMISSION_STOCK_CONSUME);
$requestBody = $request->getParsedBody();
@ -319,7 +319,7 @@ class StockApiController extends BaseApiController
public function InventoryProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_STOCK_CORRECTION);
User::checkPermission($request, User::PERMISSION_STOCK_INVENTORY);
$requestBody = $request->getParsedBody();
@ -383,7 +383,7 @@ class StockApiController extends BaseApiController
public function OpenProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_PRODUCT_OPEN);
User::checkPermission($request, User::PERMISSION_STOCK_OPEN);
$requestBody = $request->getParsedBody();
@ -600,7 +600,7 @@ class StockApiController extends BaseApiController
public function UndoBooking(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_STOCK_CORRECTION);
User::checkPermission($request, User::PERMISSION_STOCK_EDIT);
try
{
@ -615,7 +615,7 @@ class StockApiController extends BaseApiController
public function UndoTransaction(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_STOCK_CORRECTION);
User::checkPermission($request, User::PERMISSION_STOCK_EDIT);
try
{

View File

@ -41,7 +41,7 @@ class TasksApiController extends BaseApiController
public function UndoTask(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_TASKS_UNDO);
User::checkPermission($request, User::PERMISSION_TASKS_UNDO_EXECUTION);
try
{

View File

@ -8,29 +8,45 @@ use LessQL\Result;
class User
{
const PERMISSION_ADMIN = 'ADMIN';
const PERMISSION_CREATE_USER = 'CREATE_USER';
const PERMISSION_EDIT_USER = 'EDIT_USER';
const PERMISSION_READ_USER = 'READ_USER';
const PERMISSION_EDIT_SELF = 'EDIT_SELF';
const PERMISSION_BATTERY_UNDO_TRACK_CHARGE_CYCLE = 'BATTERY_UNDO_TRACK_CHARGE_CYCLE';
const PERMISSION_BATTERY_TRACK_CHARGE_CYCLE = 'BATTERY_TRACK_CHARGE_CYCLE';
const PERMISSION_CHORE_TRACK = 'CHORE_TRACK';
const PERMISSION_CHORE_TRACK_OTHERS = 'CHORE_TRACK_OTHERS';
const PERMISSION_CHORE_EDIT = 'CHORE_EDIT';
const PERMISSION_CHORE_UNDO = 'CHORE_UNDO';
const PERMISSION_UPLOAD_FILE = 'UPLOAD_FILE';
const PERMISSION_DELETE_FILE = 'DELETE_FILE';
const PERMISSION_MASTER_DATA_EDIT = 'MASTER_DATA_EDIT';
const PERMISSION_TASKS_UNDO = 'TASKS_UNDO';
const PERMISSION_TASKS_MARK_COMPLETED = 'TASKS_MARK_COMPLETED';
const PERMISSION_USERS = 'USERS';
const PERMISSION_USERS_CREATE = 'USERS_CREATE';
const PERMISSION_USERS_EDIT = 'USERS_EDIT';
const PERMISSION_USERS_READ = 'USERS_READ';
const PERMISSION_USERS_EDIT_SELF = 'USERS_EDIT_SELF';
const PERMISSION_STOCK = 'STOCK';
const PERMISSION_STOCK_PURCHASE = 'STOCK_PURCHASE';
const PERMISSION_STOCK_CONSUME = 'STOCK_CONSUME';
const PERMISSION_STOCK_INVENTORY = 'STOCK_INVENTORY';
const PERMISSION_STOCK_TRANSFER = 'STOCK_TRANSFER';
const PERMISSION_STOCK_OPEN = 'STOCK_OPEN';
const PERMISSION_STOCK_EDIT = 'STOCK_EDIT';
const PERMISSION_PRODUCT_CONSUME = 'PRODUCT_CONSUME';
const PERMISSION_STOCK_CORRECTION = 'STOCK_CORRECTION';
const PERMISSION_PRODUCT_OPEN = 'PRODUCT_OPEN';
const PERMISSION_RECIPES = 'RECIPES';
const PERMISSION_RECIPES_MEALPLAN = 'RECIPES_MEALPLAN';
const PERMISSION_SHOPPINGLIST = 'SHOPPINGLIST';
const PERMISSION_SHOPPINGLIST_ITEMS_ADD = 'SHOPPINGLIST_ITEMS_ADD';
const PERMISSION_SHOPPINGLIST_ITEMS_DELETE = 'SHOPPINGLIST_ITEMS_DELETE';
const PERMISSION_PRODUCT_PURCHASE = 'PRODUCT_PURCHASE';
const PERMISSION_CHORES = 'CHORES';
const PERMISSION_CHORE_TRACK_EXECUTION = 'CHORE_TRACK_EXECUTION';
const PERMISSION_CHORE_UNDO_EXECUTION = 'CHORE_UNDO_EXECUTION';
const PERMISSION_BATTERIES = 'BATTERIES';
const PERMISSION_BATTERIES_TRACK_CHARGE_CYCLE = 'BATTERIES_TRACK_CHARGE_CYCLE';
const PERMISSION_BATTERIES_UNDO_CHARGE_CYCLE = 'BATTERIES_UNDO_CHARGE_CYCLE';
const PERMISSION_TASKS = 'TASKS';
const PERMISSION_TASKS_UNDO_EXECUTION = 'TASKS_UNDO_EXECUTION';
const PERMISSION_TASKS_MARK_COMPLETED = 'TASKS_MARK_COMPLETED';
const PERMISSION_EQUIPMENT = 'EQUIPMENT';
const PERMISSION_CALENDAR = 'CALENDAR';
const PERMISSION_MASTER_DATA_EDIT = 'MASTER_DATA_EDIT';
/**
* @var \LessQL\Database|null

View File

@ -13,7 +13,7 @@ class UsersApiController extends BaseApiController
public function GetUsers(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_READ_USER);
User::checkPermission($request, User::PERMISSION_USERS_READ);
try
{
return $this->ApiResponse($response, $this->getUsersService()->GetUsersAsDto());
@ -26,7 +26,7 @@ class UsersApiController extends BaseApiController
public function CreateUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_CREATE_USER);
User::checkPermission($request, User::PERMISSION_USERS_CREATE);
$requestBody = $request->getParsedBody();
try
@ -47,7 +47,7 @@ class UsersApiController extends BaseApiController
public function DeleteUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_EDIT_USER);
User::checkPermission($request, User::PERMISSION_USERS_EDIT);
try
{
$this->getUsersService()->DeleteUser($args['userId']);
@ -62,9 +62,9 @@ class UsersApiController extends BaseApiController
public function EditUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['userId'] == GROCY_USER_ID) {
User::checkPermission($request, User::PERMISSION_EDIT_SELF);
User::checkPermission($request, User::PERMISSION_USERS_EDIT_SELF);
} else {
User::checkPermission($request, User::PERMISSION_EDIT_USER);
User::checkPermission($request, User::PERMISSION_USERS_EDIT);
}
$requestBody = $request->getParsedBody();

View File

@ -8,7 +8,7 @@ class UsersController extends BaseController
{
public function UsersList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_READ_USER);
User::checkPermission($request, User::PERMISSION_USERS_READ);
return $this->renderPage($response, 'users', [
'users' => $this->getDatabase()->users()->orderBy('username')
]);
@ -18,7 +18,7 @@ class UsersController extends BaseController
{
if ($args['userId'] == 'new')
{
User::checkPermission($request, User::PERMISSION_CREATE_USER);
User::checkPermission($request, User::PERMISSION_USERS_CREATE);
return $this->renderPage($response, 'userform', [
'mode' => 'create'
]);
@ -26,8 +26,8 @@ class UsersController extends BaseController
else
{
if($args['userId'] == GROCY_USER_ID)
User::checkPermission($request, User::PERMISSION_EDIT_SELF);
else User::checkPermission($request, User::PERMISSION_EDIT_USER);
User::checkPermission($request, User::PERMISSION_USERS_EDIT_SELF);
else User::checkPermission($request, User::PERMISSION_USERS_EDIT);
return $this->renderPage($response, 'userform', [
'user' => $this->getDatabase()->users($args['userId']),
'mode' => 'edit'
@ -37,7 +37,7 @@ class UsersController extends BaseController
public function PermissionList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_READ_USER);
User::checkPermission($request, User::PERMISSION_USERS_READ);
return $this->renderPage($response, 'userpermissions', [
'user' => $this->getDatabase()->users($args['userId']),
'permissions' => $this->getDatabase()->uihelper_user_permissions()

View File

@ -13,73 +13,91 @@ msgstr ""
"X-Domain: grocy/permissions\n"
msgid "ADMIN"
msgstr ""
msgstr "All permissions"
msgid "CREATE_USER"
msgstr "Create new users"
msgid "USERS_CREATE"
msgstr "Create users"
msgid "EDIT_USER"
msgstr "Edit existing users (including passwords)"
msgid "USERS_EDIT"
msgstr "Edit users (including passwords)"
msgid "READ_USER"
msgstr "View user data"
msgid "USERS_READ"
msgstr "Show users"
msgid "EDIT_SELF"
msgstr "Edit own user data, e.g. password and name"
msgid "USERS_EDIT_SELF"
msgstr "Edit own user data / change own password"
msgid "BATTERY_UNDO_TRACK_CHARGE_CYCLE"
msgstr "Batteries: undo tracking of charge cycles"
msgid "BATTERIES_UNDO_CHARGE_CYCLE"
msgstr "Undo charge cycle"
msgid "BATTERY_TRACK_CHARGE_CYCLE"
msgstr "Batteries: track charge cycle"
msgid "BATTERIES_TRACK_CHARGE_CYCLE"
msgstr "Track charge cycle"
msgid "CHORE_TRACK"
msgstr "Chores: track execution"
msgid "CHORE_TRACK_EXECUTION"
msgstr "Track execution"
msgid "CHORE_TRACK_OTHERS"
msgstr "Chores: Track execution for others"
msgid "CHORE_EDIT"
msgstr "Chores: Edit chore data"
msgid "CHORE_UNDO"
msgstr "Chores: undo tracked execution"
msgid "UPLOAD_FILE"
msgstr "Upload files, e.g. product images"
msgid "DELETE_FILE"
msgstr "Delete (uploaded) files"
msgid "CHORE_UNDO_EXECUTION"
msgstr "Undo execution"
msgid "MASTER_DATA_EDIT"
msgstr "Edit Master data (e.g. products)"
msgstr "Edit master data"
msgid "TASKS_UNDO"
msgstr "Tasks: undo tracked execution"
msgid "TASKS_UNDO_EXECUTION"
msgstr "Undo execution"
msgid "TASKS_MARK_COMPLETED"
msgstr "Tasks: mark as completed"
msgstr "Mark completed"
msgid "STOCK_EDIT"
msgstr "Stock: edit entries"
msgstr "Edit stock entries"
msgid "STOCK_TRANSFER"
msgstr "Stock: transfer products between locations"
msgstr "Transfer"
msgid "STOCK_CORRECTION"
msgstr "Stock: correct wrong entries"
msgid "STOCK_INVENTORY"
msgstr "Inventory"
msgid "PRODUCT_CONSUME"
msgstr "Consume Products"
msgid "STOCK_CONSUME"
msgstr "Consume"
msgid "PRODUCT_OPEN"
msgid "STOCK_OPEN"
msgstr "Open products"
msgid "PRODUCT_PURCHASE"
msgstr "Purchase new products and add them to stock"
msgid "STOCK_PURCHASE"
msgstr "Purchase"
msgid "SHOPPINGLIST_ITEMS_ADD"
msgstr "Add items to shopping list"
msgstr "Add items"
msgid "SHOPPINGLIST_ITEMS_DELETE"
msgstr "Remove items from shopping list"
msgstr "Remove items"
msgid "USERS"
msgstr "User management"
msgid "STOCK"
msgstr "Stock"
msgid "SHOPPINGLIST"
msgstr "Shopping list"
msgid "CHORES"
msgstr "Chores"
msgid "BATTERIES"
msgstr "Batteries"
msgid "TASKS"
msgstr "Tasks"
msgid "RECIPES"
msgstr "Recipes"
msgid "EQUIPMENT"
msgstr "Equipment"
msgid "CALENDAR"
msgstr "Calendar"
msgid "RECIPES_MEALPLAN"
msgstr "Meal plan"

View File

@ -12,74 +12,122 @@ msgstr ""
"Language: en\n"
"X-Domain: grocy/permissions\n"
# All permissions
msgid "ADMIN"
msgstr ""
msgid "CREATE_USER"
# Create users
msgid "USERS_CREATE"
msgstr ""
msgid "EDIT_USER"
#Edit users (including passwords)
msgid "USERS_EDIT"
msgstr ""
msgid "READ_USER"
# Show users
msgid "USERS_READ"
msgstr ""
msgid "EDIT_SELF"
# Edit own user data / change own password
msgid "USERS_EDIT_SELF"
msgstr ""
msgid "BATTERY_UNDO_TRACK_CHARGE_CYCLE"
# Undo charge cycle
msgid "BATTERIES_UNDO_CHARGE_CYCLE"
msgstr ""
msgid "BATTERY_TRACK_CHARGE_CYCLE"
# Track charge cycle
msgid "BATTERIES_TRACK_CHARGE_CYCLE"
msgstr ""
msgid "CHORE_TRACK"
# Track execution
msgid "CHORE_TRACK_EXECUTION"
msgstr ""
msgid "CHORE_TRACK_OTHERS"
msgstr ""
msgid "CHORE_EDIT"
msgstr ""
msgid "CHORE_UNDO"
msgstr ""
msgid "UPLOAD_FILE"
msgstr ""
msgid "DELETE_FILE"
# Undo execution
msgid "CHORE_UNDO_EXECUTION"
msgstr ""
# Edit master data
msgid "MASTER_DATA_EDIT"
msgstr ""
msgid "TASKS_UNDO"
# Undo execution
msgid "TASKS_UNDO_EXECUTION"
msgstr ""
# Mark completed
msgid "TASKS_MARK_COMPLETED"
msgstr ""
# Edit stock entries
msgid "STOCK_EDIT"
msgstr ""
# Transfer
msgid "STOCK_TRANSFER"
msgstr ""
msgid "STOCK_CORRECTION"
# Inventory
msgid "STOCK_INVENTORY"
msgstr ""
msgid "PRODUCT_CONSUME"
# Consume
msgid "STOCK_CONSUME"
msgstr ""
msgid "PRODUCT_OPEN"
# Open products
msgid "STOCK_OPEN"
msgstr ""
msgid "PRODUCT_PURCHASE"
# Purchase
msgid "STOCK_PURCHASE"
msgstr ""
# Add items
msgid "SHOPPINGLIST_ITEMS_ADD"
msgstr ""
# Remove items
msgid "SHOPPINGLIST_ITEMS_DELETE"
msgstr ""
# User management
msgid "USERS"
msgstr ""
# Stock
msgid "STOCK"
msgstr ""
# Shopping list
msgid "SHOPPINGLIST"
msgstr ""
# Chores
msgid "CHORES"
msgstr ""
# Batteries
msgid "BATTERIES"
msgstr ""
# Tasks
msgid "TASKS"
msgstr ""
# Recipes
msgid "RECIPES"
msgstr ""
# Equipment
msgid "EQUIPMENT"
msgstr ""
# Calendar
msgid "CALENDAR"
msgstr ""
# Meal plan
msgid "RECIPES_MEALPLAN"
msgstr ""

View File

@ -1853,7 +1853,7 @@ msgstr ""
msgid "Permissions for user %s"
msgstr ""
msgid "Are you sure you want to stop being an ADMIN?"
msgid "Are you sure you want to remove full permissions for yourself?"
msgstr ""
msgid "Permissions saved"

View File

@ -1,38 +1,107 @@
CREATE TABLE user_permissions
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
permission_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
UNIQUE (user_id, permission_id)
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
permission_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
UNIQUE (user_id, permission_id)
);
CREATE TABLE permission_hierarchy
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
parent INTEGER NULL -- If the user has the parent permission, the user also has the child permission
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
parent INTEGER NULL -- If the user has the parent permission, the user also has the child permission
);
-- The root/ADMIN permission
INSERT INTO permission_hierarchy
(name, parent)
(name, parent)
VALUES
('ADMIN', NULL);
('ADMIN', NULL);
-- User add/edit/read permissions
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('USERS', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('USERS_CREATE', (SELECT id FROM permission_hierarchy WHERE name = 'USERS'));
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('USERS_EDIT', last_insert_rowid());
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('USERS_READ', last_insert_rowid()),
('USERS_EDIT_SELF', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
-- Base permissions per major feature
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('STOCK', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('SHOPPINGLIST', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('RECIPES', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('CHORES', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('BATTERIES', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('TASKS', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
-- Sub feature permissions
INSERT INTO permission_hierarchy
(name, parent)
VALUES
-- Stock
('STOCK_PURCHASE', (SELECT id FROM permission_hierarchy WHERE name = 'STOCK')),
('STOCK_CONSUME', (SELECT id FROM permission_hierarchy WHERE name = 'STOCK')),
('STOCK_INVENTORY', (SELECT id FROM permission_hierarchy WHERE name = 'STOCK')),
('STOCK_TRANSFER', (SELECT id FROM permission_hierarchy WHERE name = 'STOCK')),
('STOCK_OPEN', (SELECT id FROM permission_hierarchy WHERE name = 'STOCK')),
('STOCK_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'STOCK')),
-- Shopping list
('SHOPPINGLIST_ITEMS_ADD', (SELECT id FROM permission_hierarchy WHERE name = 'SHOPPINGLIST')),
('SHOPPINGLIST_ITEMS_DELETE', (SELECT id FROM permission_hierarchy WHERE name = 'SHOPPINGLIST')),
-- Recipes
('RECIPES_MEALPLAN', (SELECT id FROM permission_hierarchy WHERE name = 'RECIPES')),
-- Batteries
('BATTERIES_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'BATTERIES')),
('BATTERIES_UNDO_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'BATTERIES')),
-- Chores
('CHORE_TRACK_EXECUTION', (SELECT id FROM permission_hierarchy WHERE name = 'CHORES')),
('CHORE_UNDO_EXECUTION', (SELECT id FROM permission_hierarchy WHERE name = 'CHORES')),
-- Tasks
('TASKS_UNDO_EXECUTION', (SELECT id FROM permission_hierarchy WHERE name = 'TASKS')),
('TASKS_MARK_COMPLETED', (SELECT id FROM permission_hierarchy WHERE name = 'TASKS')),
-- Others
('MASTER_DATA_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
-- All existing users get the ADMIN permission
INSERT INTO user_permissions
(permission_id, user_id)
(permission_id, user_id)
SELECT (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'), id
FROM users;
CREATE VIEW permission_tree
AS
WITH RECURSIVE perm AS (
SELECT id AS root, id AS child, name, parent
FROM permission_hierarchy
UNION
SELECT perm.root, ph.id, ph.name, ph.id
FROM permission_hierarchy ph, perm
WHERE ph.parent = perm.child
SELECT id AS root, id AS child, name, parent
FROM permission_hierarchy
UNION
SELECT perm.root, ph.id, ph.name, ph.id
FROM permission_hierarchy ph, perm
WHERE ph.parent = perm.child
)
SELECT root AS id, name AS name
FROM perm;
@ -40,70 +109,24 @@ FROM perm;
CREATE VIEW user_permissions_resolved
AS
SELECT
u.id AS id, -- Dummy for LessQL
u.id AS user_id,
pt.name AS permission_name
u.id AS id, -- Dummy for LessQL
u.id AS user_id,
pt.name AS permission_name
FROM permission_tree pt, users u
WHERE pt.id IN (SELECT permission_id FROM user_permissions sub_up WHERE sub_up.user_id = u.id);
CREATE VIEW uihelper_user_permissions
AS
SELECT
ph.id AS id,
u.id AS user_id,
ph.name AS permission_name,
ph.id AS permission_id,
(ph.name IN (
SELECT pc.permission_name
FROM user_permissions_resolved pc
WHERE pc.user_id = u.id
)
) AS has_permission,
ph.parent AS parent
SELECT
ph.id AS id,
u.id AS user_id,
ph.name AS permission_name,
ph.id AS permission_id,
(ph.name IN (
SELECT pc.permission_name
FROM user_permissions_resolved pc
WHERE pc.user_id = u.id
)
) AS has_permission,
ph.parent AS parent
FROM users u, permission_hierarchy ph;
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('CREATE_USER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('EDIT_USER', last_insert_rowid());
INSERT INTO permission_hierarchy
(name, parent)
VALUES
('READ_USER', last_insert_rowid()),
('EDIT_SELF', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
INSERT INTO permission_hierarchy
(name, parent)
VALUES
-- Batteries
('BATTERY_UNDO_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('BATTERY_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- Chores
('CHORE_TRACK', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('CHORE_TRACK_OTHERS', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('CHORE_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('CHORE_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- Files
('UPLOAD_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('DELETE_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- master data
('MASTER_DATA_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- Tasks
('TASKS_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('TASKS_MARK_COMPLETED', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- Stock / Products
('STOCK_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('STOCK_TRANSFER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('STOCK_CORRECTION', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_PURCHASE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_CONSUME', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_OPEN', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- shopping list
('SHOPPINGLIST_ITEMS_ADD', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('SHOPPINGLIST_ITEMS_DELETE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));

View File

@ -33,7 +33,7 @@ $('#permission-save').click(
if (Grocy.EditObjectId == Grocy.UserId) {
$('input.permission-cb[name=ADMIN]').click(function () {
if (!this.checked) {
if (!confirm(__t('Are you sure you want to stop being an ADMIN?'))) {
if (!confirm(__t('Are you sure you want to remove full permissions for yourself?'))) {
this.checked = true;
check_hierachy(this.checked, this.name);
}

View File

@ -50,7 +50,7 @@
@foreach($chargeCycles as $chargeCycleEntry)
<tr id="charge-cycle-{{ $chargeCycleEntry->id }}-row" class="@if($chargeCycleEntry->undone == 1) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-sm undo-battery-execution-button @if($chargeCycleEntry->undone == 1) disabled @endif permission-BATTERY_UNDO_TRACK_CHARGE_CYCLE" href="#" data-charge-cycle-id="{{ $chargeCycleEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo charge cycle') }}">
<a class="btn btn-secondary btn-sm undo-battery-execution-button @if($chargeCycleEntry->undone == 1) disabled @endif permission-BATTERIES_UNDO_CHARGE_CYCLE" href="#" data-charge-cycle-id="{{ $chargeCycleEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo charge cycle') }}">
<i class="fas fa-undo"></i>
</a>
</td>

View File

@ -69,7 +69,7 @@
@foreach($current as $currentBatteryEntry)
<tr id="battery-{{ $currentBatteryEntry->battery_id }}-row" class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->charge_interval_days > 0 && $currentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->charge_interval_days > 0 && $currentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm track-charge-cycle-button permission-BATTERY_TRACK_CHARGE_CYCLE" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track charge cycle of battery %s', FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name) }}"
<a class="btn btn-success btn-sm track-charge-cycle-button permission-BATTERIES_TRACK_CHARGE_CYCLE" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track charge cycle of battery %s', FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name) }}"
data-battery-id="{{ $currentBatteryEntry->battery_id }}"
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name }}">
<i class="fas fa-fire"></i>

View File

@ -53,7 +53,7 @@
@foreach($choresLog as $choreLogEntry)
<tr id="chore-execution-{{ $choreLogEntry->id }}-row" class="@if($choreLogEntry->undone == 1) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-sm undo-chore-execution-button permission-CHORE_UNDO @if($choreLogEntry->undone == 1) disabled @endif" href="#" data-execution-id="{{ $choreLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo chore execution') }}">
<a class="btn btn-secondary btn-sm undo-chore-execution-button permission-CHORE_UNDO_EXECUTION @if($choreLogEntry->undone == 1) disabled @endif" href="#" data-execution-id="{{ $choreLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo chore execution') }}">
<i class="fas fa-undo"></i>
</a>
</td>

View File

@ -95,7 +95,7 @@
@foreach($currentChores as $curentChoreEntry)
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row" class="@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm track-chore-button permission-CHORE_TRACK" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track execution of chore %s', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
<a class="btn btn-success btn-sm track-chore-button permission-CHORE_TRACK_EXECUTION" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track execution of chore %s', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
<i class="fas fa-play"></i>

View File

@ -92,7 +92,7 @@
<button id="save-consume-button" class="btn btn-success">{{ $__t('OK') }}</button>
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<button id="save-mark-as-open-button" class="btn btn-secondary">{{ $__t('Mark as opened') }}</button>
<button id="save-mark-as-open-button" class="btn btn-secondary permission-STOCK_OPEN">{{ $__t('Mark as opened') }}</button>
@endif
</form>

View File

@ -106,13 +106,13 @@
@endif
@if(GROCY_FEATURE_FLAG_RECIPES)
<div class="nav-item-divider"></div>
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Recipes') }}" data-nav-for-page="recipes">
<li class="nav-item nav-item-sidebar permission-RECIPES" data-toggle="tooltip" data-placement="right" title="{{ $__t('Recipes') }}" data-nav-for-page="recipes">
<a class="nav-link discrete-link" href="{{ $U('/recipes') }}">
<i class="fas fa-cocktail"></i>
<span class="nav-link-text">{{ $__t('Recipes') }}</span>
</a>
</li>
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Meal plan') }}" data-nav-for-page="mealplan">
<li class="nav-item nav-item-sidebar permission-RECIPES_MEALPLAN" data-toggle="tooltip" data-placement="right" title="{{ $__t('Meal plan') }}" data-nav-for-page="mealplan">
<a class="nav-link discrete-link" href="{{ $U('/mealplan') }}">
<i class="fas fa-paper-plane"></i>
<span class="nav-link-text">{{ $__t('Meal plan') }}</span>
@ -145,7 +145,7 @@
</li>
@endif
@if(GROCY_FEATURE_FLAG_EQUIPMENT)
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Equipment') }}" data-nav-for-page="equipment">
<li class="nav-item nav-item-sidebar permission-EQUIPMENT" data-toggle="tooltip" data-placement="right" title="{{ $__t('Equipment') }}" data-nav-for-page="equipment">
<a class="nav-link discrete-link" href="{{ $U('/equipment') }}">
<i class="fas fa-toolbox"></i>
<span class="nav-link-text">{{ $__t('Equipment') }}</span>
@ -154,7 +154,7 @@
@endif
@if(GROCY_FEATURE_FLAG_CALENDAR)
<div class="nav-item-divider"></div>
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Calendar') }}" data-nav-for-page="calendar">
<li class="nav-item nav-item-sidebar permission-CALENDAR" data-toggle="tooltip" data-placement="right" title="{{ $__t('Calendar') }}" data-nav-for-page="calendar">
<a class="nav-link discrete-link" href="{{ $U('/calendar') }}">
<i class="fas fa-calendar-alt"></i>
<span class="nav-link-text">{{ $__t('Calendar') }}</span>
@ -164,13 +164,13 @@
@if(GROCY_FEATURE_FLAG_STOCK)
<div class="nav-item-divider"></div>
<li class="nav-item nav-item-sidebar permission-PRODUCT_PURCHASE" data-toggle="tooltip" data-placement="right" title="{{ $__t('Purchase') }}" data-nav-for-page="purchase">
<li class="nav-item nav-item-sidebar permission-STOCK_PURCHASE" data-toggle="tooltip" data-placement="right" title="{{ $__t('Purchase') }}" data-nav-for-page="purchase">
<a class="nav-link discrete-link" href="{{ $U('/purchase') }}">
<i class="fas fa-shopping-cart"></i>
<span class="nav-link-text">{{ $__t('Purchase') }}</span>
</a>
</li>
<li class="nav-item nav-item-sidebar permission-PRODUCT_CONSUME" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume') }}" data-nav-for-page="consume">
<li class="nav-item nav-item-sidebar permission-STOCK_CONSUME" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume') }}" data-nav-for-page="consume">
<a class="nav-link discrete-link" href="{{ $U('/consume') }}">
<i class="fas fa-utensils"></i>
<span class="nav-link-text">{{ $__t('Consume') }}</span>
@ -184,7 +184,7 @@
</a>
</li>
@endif
<li class="nav-item nav-item-sidebar permission-STOCK_CORRECTION" data-toggle="tooltip" data-placement="right" title="{{ $__t('Inventory') }}" data-nav-for-page="inventory">
<li class="nav-item nav-item-sidebar permission-STOCK_INVENTORY" data-toggle="tooltip" data-placement="right" title="{{ $__t('Inventory') }}" data-nav-for-page="inventory">
<a class="nav-link discrete-link" href="{{ $U('/inventory') }}">
<i class="fas fa-list"></i>
<span class="nav-link-text">{{ $__t('Inventory') }}</span>
@ -192,7 +192,7 @@
</li>
@endif
@if(GROCY_FEATURE_FLAG_CHORES)
<li class="nav-item nav-item-sidebar permission-CHORE_TRACK_OTHERS" data-toggle="tooltip" data-placement="right" title="{{ $__t('Chore tracking') }}" data-nav-for-page="choretracking">
<li class="nav-item nav-item-sidebar permission-CHORE_TRACK_EXECUTION" data-toggle="tooltip" data-placement="right" title="{{ $__t('Chore tracking') }}" data-nav-for-page="choretracking">
<a class="nav-link discrete-link" href="{{ $U('/choretracking') }}">
<i class="fas fa-play"></i>
<span class="nav-link-text">{{ $__t('Chore tracking') }}</span>
@ -200,7 +200,7 @@
</li>
@endif
@if(GROCY_FEATURE_FLAG_BATTERIES)
<li class="nav-item nav-item-sidebar permission-BATTERY_TRACK_CHARGE_CYCLE" data-toggle="tooltip" data-placement="right" title="{{ $__t('Battery tracking') }}" data-nav-for-page="batterytracking">
<li class="nav-item nav-item-sidebar permission-BATTERIES_TRACK_CHARGE_CYCLE" data-toggle="tooltip" data-placement="right" title="{{ $__t('Battery tracking') }}" data-nav-for-page="batterytracking">
<a class="nav-link discrete-link" href="{{ $U('/batterytracking') }}">
<i class="fas fa-fire"></i>
<span class="nav-link-text">{{ $__t('Battery tracking') }}</span>
@ -402,23 +402,23 @@
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item discrete-link" href="{{ $U('/stocksettings') }}"><i class="fas fa-box"></i>&nbsp;{{ $__t('Stock settings') }}</a>
@if(GROCY_FEATURE_FLAG_SHOPPINGLIST)
<a class="dropdown-item discrete-link" href="{{ $U('/shoppinglistsettings') }}"><i class="fas fa-shopping-cart"></i>&nbsp;{{ $__t('Shopping list settings') }}</a>
<a class="dropdown-item discrete-link permission-SHOPPINGLIST" href="{{ $U('/shoppinglistsettings') }}"><i class="fas fa-shopping-cart"></i>&nbsp;{{ $__t('Shopping list settings') }}</a>
@endif
@if(GROCY_FEATURE_FLAG_RECIPES)
<a class="dropdown-item discrete-link" href="{{ $U('/recipessettings') }}"><i class="fas fa-cocktail"></i>&nbsp;{{ $__t('Recipes settings') }}</a>
<a class="dropdown-item discrete-link permission-RECIPES" href="{{ $U('/recipessettings') }}"><i class="fas fa-cocktail"></i>&nbsp;{{ $__t('Recipes settings') }}</a>
@endif
@if(GROCY_FEATURE_FLAG_CHORES)
<a class="dropdown-item discrete-link" href="{{ $U('/choressettings') }}"><i class="fas fa-home"></i>&nbsp;{{ $__t('Chores settings') }}</a>
<a class="dropdown-item discrete-link permission-CHORES" href="{{ $U('/choressettings') }}"><i class="fas fa-home"></i>&nbsp;{{ $__t('Chores settings') }}</a>
@endif
@if(GROCY_FEATURE_FLAG_BATTERIES)
<a class="dropdown-item discrete-link" href="{{ $U('/batteriessettings') }}"><i class="fas fa-battery-half"></i>&nbsp;{{ $__t('Batteries settings') }}</a>
<a class="dropdown-item discrete-link permission-BATTERIES" href="{{ $U('/batteriessettings') }}"><i class="fas fa-battery-half"></i>&nbsp;{{ $__t('Batteries settings') }}</a>
@endif
@if(GROCY_FEATURE_FLAG_TASKS)
<a class="dropdown-item discrete-link" href="{{ $U('/taskssettings') }}"><i class="fas fa-tasks"></i>&nbsp;{{ $__t('Tasks settings') }}</a>
<a class="dropdown-item discrete-link permission-TASKS" href="{{ $U('/taskssettings') }}"><i class="fas fa-tasks"></i>&nbsp;{{ $__t('Tasks settings') }}</a>
@endif
<div class="dropdown-divider"></div>
@if(GROCY_SHOW_AUTH_VIEWS)
<a class="dropdown-item discrete-link permission-READ_USER" href="{{ $U('/users') }}"><i class="fas fa-users"></i>&nbsp;{{ $__t('Manage users') }}</a>
<a class="dropdown-item discrete-link permission-USERS_READ" href="{{ $U('/users') }}"><i class="fas fa-users"></i>&nbsp;{{ $__t('Manage users') }}</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item discrete-link" href="{{ $U('/manageapikeys') }}"><i class="fas fa-handshake"></i>&nbsp;{{ $__t('Manage API keys') }}</a>
@endif

View File

@ -121,14 +121,14 @@
@foreach($currentStock as $currentStockEntry)
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) table-warning @elseif ($currentStockEntry->product_missing) table-info @endif">
<td class="fit-content border-right">
<a class="permission-PRODUCT_CONSUME btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount < 1 || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Consume %1$s of %2$s', '1 ' . $currentStockEntry->qu_unit_name, $currentStockEntry->product_name) }}"
<a class="permission-STOCK_CONSUME btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount < 1 || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Consume %1$s of %2$s', '1 ' . $currentStockEntry->qu_unit_name, $currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
data-consume-amount="1">
<i class="fas fa-utensils"></i> 1
</a>
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="permission-PRODUCT_CONSUME d-none d-sm-inline-block btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume all %s which are currently in stock', $currentStockEntry->product_name) }}"
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="permission-STOCK_CONSUME d-none d-sm-inline-block btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume all %s which are currently in stock', $currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
@ -160,10 +160,10 @@
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span> <span class="dropdown-item-text">{{ $__t('Add to shopping list') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item show-as-dialog-link permission-PRODUCT_PURCHASE" type="button" href="{{ $U('/purchase?embedded&product=' . $currentStockEntry->product_id ) }}">
<a class="dropdown-item show-as-dialog-link permission-STOCK_PURCHASE" type="button" href="{{ $U('/purchase?embedded&product=' . $currentStockEntry->product_id ) }}">
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span> <span class="dropdown-item-text">{{ $__t('Purchase') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link permission-PRODUCT_CONSUME" type="button" href="{{ $U('/consume?embedded&product=' . $currentStockEntry->product_id ) }}">
<a class="dropdown-item show-as-dialog-link permission-STOCK_CONSUME" type="button" href="{{ $U('/consume?embedded&product=' . $currentStockEntry->product_id ) }}">
<span class="dropdown-item-icon"><i class="fas fa-utensils"></i></span> <span class="dropdown-item-text">{{ $__t('Consume') }}</span>
</a>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@ -171,7 +171,7 @@
<span class="dropdown-item-icon"><i class="fas fa-exchange-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Transfer') }}</span>
</a>
@endif
<a class="dropdown-item show-as-dialog-link permission-STOCK_CORRECTION" type="button" href="{{ $U('/inventory?embedded&product=' . $currentStockEntry->product_id ) }}">
<a class="dropdown-item show-as-dialog-link permission-STOCK_INVENTORY" type="button" href="{{ $U('/inventory?embedded&product=' . $currentStockEntry->product_id ) }}">
<span class="dropdown-item-icon"><i class="fas fa-list"></i></span> <span class="dropdown-item-text">{{ $__t('Inventory') }}</span>
</a>
<div class="dropdown-divider"></div>
@ -189,7 +189,7 @@
<span class="dropdown-item-icon"><i class="fas fa-edit"></i></span> <span class="dropdown-item-text">{{ $__t('Edit product') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item product-consume-button product-consume-button-spoiled permission-PRODUCT_CONSUME @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
<a class="dropdown-item product-consume-button product-consume-button-spoiled permission-STOCK_CONSUME @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"