mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 17:45:39 +00:00
Squashed commit
Fixed some localization strings Reviewed/optimized product deletion handling Add option to hide products from the stock overview page (closes #906) Prefill default_due_days also on the inventory page (closes #591) Added DataTables accent chinese-string plugin (closes #872) Show costs and calories per recipe ingredient (closes #1072) Fixed user permission saving (fixes #1099) User permissions should not have an effect for demo mode (closes #972) Handle QU conversion when consuming a substituation (child) product (fixes #1118) Consume/open any child product when the parent product is not in stock (closes #899) Added a retry camera barcode scanning button to product picker workflow (closes #736)
This commit is contained in:
parent
2bdb6ab2d4
commit
cf34df5e3f
@ -56,7 +56,7 @@
|
||||
- Various display/CSS improvements (thanks @Mik-)
|
||||
- Prerequisites (PHP extensions, critical files/folders) will now be checked and properly reported if there are problems (thanks @Forceu)
|
||||
- Improved the the overview pages on mobile devices (main column was hidden) (thanks @Mik-)
|
||||
- The general search field now searches accent insensitive
|
||||
- The general search field now searches accent insensitive (and table sorting is also accent insensitive)
|
||||
- Fixed that all number inputs are always prefilled in the browser locale number format
|
||||
- Optimized the handling of settings provided by `data/settingoverrides` files (thanks @dacto)
|
||||
- Optimized the update script (`update.sh`) to create the backup tar archive before writing to it (was a problem on Btrfs file systems) (thanks @shane-kerr)
|
||||
|
@ -46,10 +46,13 @@
|
||||
- The amount to be used for the "quick consume/open buttons" on the stock overview page can now be configured per product (new product option "Quick consume amount", defaults to 1)
|
||||
- This "Quick consume amount" can optionally also be used as the default on the consume page (new stock setting / top right corner settings menu)
|
||||
- Products can now be duplicated (new button on the products list page, all fields will be preset from the copied product, except the name)
|
||||
- When consuming or opening a parent product, which is currently not in stock, any in-stock sub product will now be consumed/opened (like already automatically done when consuming recipes)
|
||||
- Optimized/clarified what the total/unit price is on the purchase page (thanks @kriddles)
|
||||
- On the purchase page the amount field is now displayed above/before the due date for better `TAB` handling (thanks @kriddles)
|
||||
- Changed that when `FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING` is disabled, products now get internally a due date of "never overdue" (aka `2999-12-31`) instead of today (thanks @kriddles)
|
||||
- Products can now be hidden instead of deleted to prevent problems / missing information on existing references (new checkbox on the product edit page) (thanks @kriddles)
|
||||
- Products can now be disabled to keep the history/journal, but hide it everywhere, without deleting it (new product option "Active", deleting a product now explicitly also deletes its journal and all other references) (thanks @kriddles for the initial work on this)
|
||||
- Products can now be hidden from the stock overview page (new product option "Show on stock overview page", enabled by default, so no changed behavior when not configured)
|
||||
- The due date is now also prefilled on the inventory page based on the products "Default due days" (was only done on the purchase page before)
|
||||
- On the stock journal page, it's now visible if a consume-booking was spoiled
|
||||
- It's now tracked who made a stock change (currently logged in user, visible on the stock journal page) (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Product edit page improvements ("Save & continue" button, deleting and adding a product picuture is now possible in one go) (thanks @Ma27)
|
||||
@ -60,6 +63,7 @@
|
||||
- When clicking the product name on the shopping list, the product card will now be displayed (like on the stock overview page) (thanks @kriddles)
|
||||
- On the product card there is now also a button to jump directly to the stock entries of the corresponding product (thanks @kriddles)
|
||||
- The product picker workflows can now also be started by `ENTER` (additionally to `TAB`)
|
||||
- Added a "retry camera barcode scan" button (button with camera icon, shortcut `C`) to the product picker workflow dialog
|
||||
- Added more filters on the stock journal page
|
||||
- Added a grouped/summarized stock journal (new button "Journal summary" at the top of the stock journal page) (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Provides an overview of summarized transactions per product, transaction type and user + summarized amount
|
||||
@ -91,12 +95,14 @@
|
||||
- Changed that recipe costs are now based on the costs of the products picked by the default consume rule "First due first, then first in first out" (thanks @kriddles)
|
||||
- Recipe costs were based on the last purchase price per product before, so this now better reflects the current real costs
|
||||
- Improved the recipe add workflow (a recipe called "New recipe" is now not automatically created when starting to add a recipe) (thanks @zsarnett)
|
||||
- On the recipe page, the calories and costs per ingredient are now shown to get a better overview of how much each ingredient contributed
|
||||
- Fixed that images on the recipe gallery view were not scaled correctly on larger screens (thanks @zsarnett)
|
||||
- Fixed that decimal ingredient amounts maybe resulted in wrong conversions or truncated decimal places if your locale does not use a dot as the decimal separator (thanks @m-byte)
|
||||
- Fixed that a recipe cannot be included in itself (because this will cause an infinite loop) (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that when editing a recipe ingredient the checkbox "Disable stock fulfillment checking for this ingredient" was not initaliased with the saved value
|
||||
- Fixed that the status filter ("Enough in stock", etc.) on the recipes page did not filter recipes on the gallery tab (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that consuming a recipe ingredient with tare weight handling enabled consumed a wrong amount (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that consuming a parent product recipe ingredient did not consider quantity unit conversion when effectively consuming a child product
|
||||
|
||||
### Meal plan fixes
|
||||
- Fixed that for products the quantity unit purchase was displayed instead of the products quantity unit stock (thanks @BenoitAnastay)
|
||||
@ -201,6 +207,11 @@
|
||||
- The stock journal (entity `stock_log`) is now also available via the endpoint `/objects/{entity}` (=> `/objects/stock_log`)
|
||||
- Performance improvements of the `/stock/products/*` endpoints (thanks @fipwmaqzufheoxq92ebc)
|
||||
- The endpoint `/stock/products/{productId}/locations` now also has an optional query parameter `include_sub_products` to optionally also return locations of sub products of the given product
|
||||
- The following endpoints now have an optional request body parameter `allow_subproduct_substitution` to consume/open any child product when the given product is a parent product and currently not in stock
|
||||
- `/stock/products/{productId}/consume`
|
||||
- `/stock/products/by-barcode/{barcode}/consume`
|
||||
- `/stock/products/{productId}/open`
|
||||
- `/stock/products/by-barcode/{barcode}/open`
|
||||
- Fixed that the endpoint `/objects/{entity}/{objectId}` always returned successfully, even when the given object not exists (now returns `404` when the object is not found) (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that the endpoint `/stock/volatile` didn't include products which expire today (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that the endpoint `/objects/{entity}` did not include Userfields for Userentities (so the effective endpoint `/objects/userobjects`)
|
||||
|
@ -206,7 +206,7 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
private function IsEntityWithEditRequiresAdmin($entity)
|
||||
{
|
||||
return !in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->EntityEditRequiresAdmin->enum);
|
||||
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->EntityEditRequiresAdmin->enum);
|
||||
}
|
||||
|
||||
private function IsEntityWithPreventedListing($entity)
|
||||
|
@ -238,8 +238,6 @@ class StockApiController extends BaseApiController
|
||||
|
||||
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
|
||||
|
||||
$result = null;
|
||||
|
||||
try
|
||||
{
|
||||
if ($requestBody === null)
|
||||
@ -253,57 +251,55 @@ class StockApiController extends BaseApiController
|
||||
}
|
||||
|
||||
$spoiled = false;
|
||||
|
||||
if (array_key_exists('spoiled', $requestBody))
|
||||
{
|
||||
$spoiled = $requestBody['spoiled'];
|
||||
}
|
||||
|
||||
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
|
||||
|
||||
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
|
||||
{
|
||||
$transactionType = $requestBody['transactiontype'];
|
||||
}
|
||||
|
||||
$specificStockEntryId = 'default';
|
||||
|
||||
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
|
||||
{
|
||||
$specificStockEntryId = $requestBody['stock_entry_id'];
|
||||
}
|
||||
|
||||
$locationId = null;
|
||||
|
||||
if (array_key_exists('location_id', $requestBody) && !empty($requestBody['location_id']) && is_numeric($requestBody['location_id']))
|
||||
{
|
||||
$locationId = $requestBody['location_id'];
|
||||
}
|
||||
|
||||
$recipeId = null;
|
||||
|
||||
if (array_key_exists('recipe_id', $requestBody) && is_numeric($requestBody['recipe_id']))
|
||||
{
|
||||
$recipeId = $requestBody['recipe_id'];
|
||||
}
|
||||
|
||||
$consumeExact = false;
|
||||
|
||||
if (array_key_exists('exact_amount', $requestBody))
|
||||
{
|
||||
$consumeExact = $requestBody['exact_amount'];
|
||||
}
|
||||
$transactionId = null;
|
||||
|
||||
$bookingId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId, $transactionId, false, $consumeExact);
|
||||
$allowSubproductSubstitution = false;
|
||||
if (array_key_exists('allow_subproduct_substitution', $requestBody))
|
||||
{
|
||||
$allowSubproductSubstitution = $requestBody['allow_subproduct_substitution'];
|
||||
}
|
||||
|
||||
$transactionId = null;
|
||||
$bookingId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId, $transactionId, $allowSubproductSubstitution, $consumeExact);
|
||||
return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
$result = $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function ConsumeProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
@ -510,13 +506,20 @@ class StockApiController extends BaseApiController
|
||||
}
|
||||
|
||||
$specificStockEntryId = 'default';
|
||||
|
||||
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
|
||||
{
|
||||
$specificStockEntryId = $requestBody['stock_entry_id'];
|
||||
}
|
||||
|
||||
$bookingId = $this->getStockService()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId);
|
||||
$allowSubproductSubstitution = false;
|
||||
if (array_key_exists('allow_subproduct_substitution', $requestBody))
|
||||
{
|
||||
$allowSubproductSubstitution = $requestBody['allow_subproduct_substitution'];
|
||||
}
|
||||
|
||||
$transactionId = null;
|
||||
|
||||
$bookingId = $this->getStockService()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId, $transactionId, $allowSubproductSubstitution);
|
||||
return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
|
@ -207,8 +207,17 @@ class StockController extends BaseController
|
||||
|
||||
public function ProductsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
if (isset($request->getQueryParams()['include_disabled']))
|
||||
{
|
||||
$products = $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE');
|
||||
}
|
||||
else
|
||||
{
|
||||
$products = $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE');
|
||||
}
|
||||
|
||||
return $this->renderPage($response, 'products', [
|
||||
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
|
||||
'products' => $products,
|
||||
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
|
||||
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
||||
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
|
||||
|
@ -84,10 +84,6 @@ class User
|
||||
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
// global $PERMISSION_CACHE;
|
||||
|
||||
// if(isset($PERMISSION_CACHE[$permission]))
|
||||
// return $PERMISSION_CACHE[$permission];
|
||||
return $this->getPermissions()->where('permission_name', $permission)->fetch() !== null;
|
||||
}
|
||||
|
||||
|
@ -152,22 +152,32 @@ class UsersApiController extends BaseApiController
|
||||
try
|
||||
{
|
||||
User::checkPermission($request, User::PERMISSION_ADMIN);
|
||||
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
$db = $this->getDatabase();
|
||||
$db->user_permissions()
|
||||
->where('user_id', $args['userId'])
|
||||
->delete();
|
||||
|
||||
$perms = [];
|
||||
|
||||
foreach ($requestBody['permissions'] as $perm_id)
|
||||
if (GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease')
|
||||
{
|
||||
// For demo mode always all users have and keep the ADMIN permission
|
||||
$perms[] = [
|
||||
'user_id' => $args['userId'],
|
||||
'permission_id' => $perm_id
|
||||
'permission_id' => 1
|
||||
];
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
foreach ($requestBody['permissions'] as $perm_id)
|
||||
{
|
||||
$perms[] = [
|
||||
'user_id' => $args['userId'],
|
||||
'permission_id' => $perm_id
|
||||
];
|
||||
}
|
||||
}
|
||||
$db->insert('user_permissions', $perms, 'batch');
|
||||
|
||||
return $this->EmptyApiResponse($response);
|
||||
|
@ -1835,6 +1835,10 @@
|
||||
"exact_amount": {
|
||||
"type": "boolean",
|
||||
"description": "For tare weight handling enabled products, `true` when the given is the absolute amount to be consumed, not the amount including the container weight"
|
||||
},
|
||||
"allow_subproduct_substitution": {
|
||||
"type": "boolean",
|
||||
"description": "`True` when any in-stock sub product should be used when the given product is a parent product and currently not in-stock"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
@ -2054,6 +2058,10 @@
|
||||
"stock_entry_id": {
|
||||
"type": "string",
|
||||
"description": "A specific stock entry id to open, if used, the amount has to be 1"
|
||||
},
|
||||
"allow_subproduct_substitution": {
|
||||
"type": "boolean",
|
||||
"description": "`True` when any in-stock sub product should be used when the given product is a parent product and currently not in-stock"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
@ -2262,6 +2270,10 @@
|
||||
"exact_amount": {
|
||||
"type": "boolean",
|
||||
"description": "For tare weight handling enabled products, `true` when the given is the absolute amount to be consumed, not the amount including the container weight"
|
||||
},
|
||||
"allow_subproduct_substitution": {
|
||||
"type": "boolean",
|
||||
"description": "`True` when any in-stock sub product should be used when the given product is a parent product and currently not in-stock"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
@ -2476,6 +2488,10 @@
|
||||
"stock_entry_id": {
|
||||
"type": "string",
|
||||
"description": "A specific stock entry id to open, if used, the amount has to be 1"
|
||||
},
|
||||
"allow_subproduct_substitution": {
|
||||
"type": "boolean",
|
||||
"description": "`True` when any in-stock sub product should be used when the given product is a parent product and currently not in-stock"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
|
@ -751,7 +751,7 @@ msgstr ""
|
||||
msgid "Image of product %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete not possible"
|
||||
msgid "Deletion not possible"
|
||||
msgstr ""
|
||||
|
||||
msgid "Equipment"
|
||||
@ -1906,19 +1906,19 @@ msgstr ""
|
||||
msgid "For purchases this amount of days will be added to today for the due date suggestion"
|
||||
msgstr ""
|
||||
|
||||
msgid "-1 means that this product wille be never overdue"
|
||||
msgid "-1 means that this product will be never overdue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default due days"
|
||||
msgstr ""
|
||||
|
||||
msgid "When this product was marked as opened, the expiry date will be replaced by today + this amount of days (a value of 0 disables this)"
|
||||
msgid "When this product was marked as opened, the due date will be replaced by today + this amount of days (a value of 0 disables this)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default due days after opened"
|
||||
msgstr ""
|
||||
|
||||
msgid "On moving this product to a freezer location (so when freezing it), the expiry date will be replaced by today + this amount of days"
|
||||
msgid "On moving this product to a freezer location (so when freezing it), the due date will be replaced by today + this amount of days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default due days after freezing"
|
||||
@ -1996,3 +1996,15 @@ msgstr ""
|
||||
|
||||
msgid "Use the products \"Quick consume amount\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "This also removes any stock amount, the journal and all other references of this product - consider disabling this product instead, if you want to keep that and just hide it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Show disabled products"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show on stock overview page"
|
||||
msgstr ""
|
||||
|
@ -70,6 +70,7 @@ CREATE TABLE products (
|
||||
cumulate_min_stock_amount_of_sub_products TINYINT DEFAULT 0,
|
||||
due_type TINYINT NOT NULL DEFAULT 1 CHECK(due_type IN (1, 2)),
|
||||
quick_consume_amount REAL NOT NULL DEFAULT 1,
|
||||
show_on_stock_overview TINYINT NOT NULL DEFAULT 1 CHECK(show_on_stock_overview IN (0, 1)),
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
|
@ -37,7 +37,8 @@ FROM (
|
||||
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
||||
) sc
|
||||
LEFT JOIN products p
|
||||
ON sc.product_id = p.id;
|
||||
ON sc.product_id = p.id
|
||||
WHERE p.show_on_stock_overview = 1;
|
||||
|
||||
CREATE VIEW uihelper_stock_current_overview
|
||||
AS
|
||||
@ -78,4 +79,5 @@ FROM (
|
||||
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
||||
) sc
|
||||
LEFT JOIN products p
|
||||
ON sc.product_id = p.id;
|
||||
ON sc.product_id = p.id
|
||||
WHERE p.show_on_stock_overview = 1;
|
||||
|
@ -128,7 +128,7 @@ SELECT
|
||||
SELECT pc.permission_name
|
||||
FROM user_permissions_resolved pc
|
||||
WHERE pc.user_id = u.id
|
||||
)
|
||||
) AS has_permission,
|
||||
ph.parent AS parent
|
||||
)
|
||||
) AS has_permission,
|
||||
ph.parent AS parent
|
||||
FROM users u, permission_hierarchy ph;
|
||||
|
25
migrations/0120.sql
Normal file
25
migrations/0120.sql
Normal file
@ -0,0 +1,25 @@
|
||||
CREATE TRIGGER cascade_product_removal AFTER DELETE ON products
|
||||
BEGIN
|
||||
DELETE FROM stock_log
|
||||
WHERE product_id = OLD.id;
|
||||
|
||||
DELETE FROM product_barcodes
|
||||
WHERE product_id = OLD.id;
|
||||
|
||||
DELETE FROM quantity_unit_conversions
|
||||
WHERE product_id = OLD.id;
|
||||
|
||||
DELETE FROM recipes_pos
|
||||
WHERE product_id = OLD.id;
|
||||
|
||||
UPDATE recipes
|
||||
SET product_id = NULL
|
||||
WHERE product_id = OLD.id;
|
||||
|
||||
DELETE FROM meal_plan
|
||||
WHERE product_id = OLD.id
|
||||
AND type = 'product';
|
||||
|
||||
DELETE FROM shopping_list
|
||||
WHERE product_id = OLD.id;
|
||||
END;
|
@ -745,7 +745,10 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
{
|
||||
return JSON.parse(Grocy.UserSettings[settingKey]);
|
||||
}
|
||||
}
|
||||
},
|
||||
'columnDefs': [
|
||||
{ type: 'chinese-string', targets: '_all' }
|
||||
]
|
||||
});
|
||||
|
||||
// serializeJSON defaults
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
],
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#batteries-table tbody').removeClass("d-none");
|
||||
batteriesTable.columns.adjust().draw();
|
||||
|
@ -4,7 +4,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#batteries-journal-table tbody').removeClass("d-none");
|
||||
batteriesJournalTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#batteries-overview-table tbody').removeClass("d-none");
|
||||
batteriesOverviewTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#chores-table tbody').removeClass("d-none");
|
||||
choresTable.columns.adjust().draw();
|
||||
|
@ -4,7 +4,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#chores-journal-table tbody').removeClass("d-none");
|
||||
choresJournalTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#chores-overview-table tbody').removeClass("d-none");
|
||||
choresOverviewTable.columns.adjust().draw();
|
||||
|
@ -172,6 +172,59 @@ $('#product_id_text_input').on('blur', function(e)
|
||||
addProductWorkflowsAdditionalCssClasses = "d-none";
|
||||
}
|
||||
|
||||
var buttons = {
|
||||
cancel: {
|
||||
label: __t('Cancel'),
|
||||
className: 'btn-secondary responsive-button',
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
Grocy.Components.ProductPicker.SetValue('');
|
||||
}
|
||||
},
|
||||
addnewproduct: {
|
||||
label: '<strong>P</strong> ' + __t('Add as new product'),
|
||||
className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
window.location.href = U('/product/new?flow=InplaceNewProductWithName&name=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName"));
|
||||
}
|
||||
},
|
||||
addbarcode: {
|
||||
label: '<strong>B</strong> ' + __t('Add as barcode to existing product'),
|
||||
className: 'btn-info add-new-barcode-dialog-button responsive-button',
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
window.location.href = U(Grocy.CurrentUrlRelative + '?flow=InplaceAddBarcodeToExistingProduct&barcode=' + encodeURIComponent(input));
|
||||
}
|
||||
},
|
||||
addnewproductwithbarcode: {
|
||||
label: '<strong>A</strong> ' + __t('Add as new product and prefill barcode'),
|
||||
className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
window.location.href = U('/product/new?flow=InplaceNewProductWithBarcode&barcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceAddBarcodeToExistingProduct&barcode=" + input));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!Grocy.FeatureFlags.DISABLE_BROWSER_BARCODE_CAMERA_SCANNING)
|
||||
{
|
||||
buttons.retrycamerascanning = {
|
||||
label: '<strong>C</strong> <i class="fas fa-camera"></i>',
|
||||
className: 'btn-primary responsive-button retry-camera-scanning-button',
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
Grocy.Components.ProductPicker.SetValue('');
|
||||
$("#barcodescanner-start-button").click();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Grocy.Components.ProductPicker.PopupOpen = true;
|
||||
bootbox.dialog({
|
||||
message: __t('"%s" could not be resolved to a product, how do you want to proceed?', input),
|
||||
@ -184,44 +237,7 @@ $('#product_id_text_input').on('blur', function(e)
|
||||
size: 'large',
|
||||
backdrop: true,
|
||||
closeButton: false,
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: __t('Cancel'),
|
||||
className: 'btn-secondary responsive-button',
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
Grocy.Components.ProductPicker.SetValue('');
|
||||
}
|
||||
},
|
||||
addnewproduct: {
|
||||
label: '<strong>P</strong> ' + __t('Add as new product'),
|
||||
className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
window.location.href = U('/product/new?flow=InplaceNewProductWithName&name=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName"));
|
||||
}
|
||||
},
|
||||
addbarcode: {
|
||||
label: '<strong>B</strong> ' + __t('Add as barcode to existing product'),
|
||||
className: 'btn-info add-new-barcode-dialog-button responsive-button',
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
window.location.href = U(Grocy.CurrentUrlRelative + '?flow=InplaceAddBarcodeToExistingProduct&barcode=' + encodeURIComponent(input));
|
||||
}
|
||||
},
|
||||
addnewproductwithbarcode: {
|
||||
label: '<strong>A</strong> ' + __t('Add as new product and prefill barcode'),
|
||||
className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
|
||||
callback: function()
|
||||
{
|
||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||
window.location.href = U('/product/new?flow=InplaceNewProductWithBarcode&barcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceAddBarcodeToExistingProduct&barcode=" + input));
|
||||
}
|
||||
}
|
||||
}
|
||||
buttons: buttons
|
||||
}).on('keypress', function(e)
|
||||
{
|
||||
if (e.key === 'B' || e.key === 'b')
|
||||
@ -236,6 +252,10 @@ $('#product_id_text_input').on('blur', function(e)
|
||||
{
|
||||
$('.add-new-product-with-barcode-dialog-button').not(".d-none").click();
|
||||
}
|
||||
if (e.key === 'c' || e.key === 'C')
|
||||
{
|
||||
$('.retry-camera-scanning-button').not(".d-none").click();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -249,7 +269,6 @@ $(document).on("Grocy.BarcodeScanned", function(e, barcode, target)
|
||||
}
|
||||
|
||||
// Don't know why the blur event does not fire immediately ... this works...
|
||||
|
||||
Grocy.Components.ProductPicker.GetInputElement().focusout();
|
||||
Grocy.Components.ProductPicker.GetInputElement().focus();
|
||||
Grocy.Components.ProductPicker.GetInputElement().blur();
|
||||
|
@ -11,6 +11,7 @@
|
||||
jsonData.amount = jsonForm.amount;
|
||||
jsonData.exact_amount = $('#consume-exact-amount').is(':checked');
|
||||
jsonData.spoiled = $('#spoiled').is(':checked');
|
||||
jsonData.allow_subproduct_substitution = true;
|
||||
|
||||
if ($("#use_specific_stock_entry").is(":checked"))
|
||||
{
|
||||
@ -28,7 +29,6 @@
|
||||
}
|
||||
|
||||
var bookingResponse = null;
|
||||
|
||||
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
|
||||
function(productDetails)
|
||||
{
|
||||
@ -146,6 +146,7 @@ $('#save-mark-as-open-button').on('click', function(e)
|
||||
|
||||
jsonData = {};
|
||||
jsonData.amount = jsonForm.amount;
|
||||
jsonData.allow_subproduct_substitution = true;
|
||||
|
||||
if ($("#use_specific_stock_entry").is(":checked"))
|
||||
{
|
||||
@ -215,7 +216,7 @@ $("#location_id").on('change', function(e)
|
||||
|
||||
if (locationId)
|
||||
{
|
||||
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries',
|
||||
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
|
||||
function(stockEntries)
|
||||
{
|
||||
stockEntries.forEach(stockEntry =>
|
||||
@ -234,7 +235,7 @@ $("#location_id").on('change', function(e)
|
||||
text: __t("Amount: %1$s; Due on %2$s; Bought on %3$s", stockEntry.amount, moment(stockEntry.best_before_date).format("YYYY-MM-DD"), moment(stockEntry.purchased_date).format("YYYY-MM-DD")) + "; " + openTxt
|
||||
}));
|
||||
|
||||
sumValue = sumValue + parseFloat(stockEntry.amount);
|
||||
sumValue = sumValue + parseFloat(stockEntry.amount_aggregated);
|
||||
|
||||
if (stockEntry.stock_id == stockId)
|
||||
{
|
||||
@ -302,7 +303,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
|
||||
$("#location_id").find("option").remove().end().append("<option></option>");
|
||||
Grocy.Api.Get("stock/products/" + productId + '/locations',
|
||||
Grocy.Api.Get("stock/products/" + productId + '/locations?include_sub_products=true',
|
||||
function(stockLocations)
|
||||
{
|
||||
var setDefault = 0;
|
||||
@ -369,7 +370,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
$("#tare-weight-handling-info").addClass("d-none");
|
||||
}
|
||||
|
||||
if ((parseFloat(productDetails.stock_amount) || 0) === 0)
|
||||
if ((parseFloat(productDetails.stock_amount_aggregated) || 0) === 0)
|
||||
{
|
||||
Grocy.Components.ProductAmountPicker.Reset();
|
||||
Grocy.Components.ProductPicker.Clear();
|
||||
@ -448,14 +449,14 @@ $("#specific_stock_entry").on("change", function(e)
|
||||
if ($(e.target).val() == "")
|
||||
{
|
||||
sumValue = 0;
|
||||
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries',
|
||||
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
|
||||
function(stockEntries)
|
||||
{
|
||||
stockEntries.forEach(stockEntry =>
|
||||
{
|
||||
if (stockEntry.location_id == $("#location_id").val() || stockEntry.location_id == "")
|
||||
{
|
||||
sumValue = sumValue + parseFloat(stockEntry.amount);
|
||||
sumValue = sumValue + parseFloat(stockEntry.amount_aggregated);
|
||||
}
|
||||
});
|
||||
$("#display_amount").attr("max", sumValue);
|
||||
|
@ -2,9 +2,8 @@
|
||||
'order': [[0, 'asc']],
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 },
|
||||
{ 'orderData': 2, 'targets': 1 }
|
||||
],
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
].concat($.fn.dataTable.defaults.columnDefs),
|
||||
select: {
|
||||
style: 'single',
|
||||
selector: 'tr td:not(:first-child)'
|
||||
|
@ -159,6 +159,24 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
|
||||
}
|
||||
|
||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
{
|
||||
if (productDetails.product.default_best_before_days.toString() !== '0')
|
||||
{
|
||||
if (productDetails.product.default_best_before_days == -1)
|
||||
{
|
||||
if (!$("#datetimepicker-shortcut").is(":checked"))
|
||||
{
|
||||
$("#datetimepicker-shortcut").click();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Grocy.Components.DateTimePicker.SetValue(moment().add(productDetails.product.default_best_before_days, 'days').format('YYYY-MM-DD'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('#display_amount').val(productDetails.stock_amount);
|
||||
RefreshLocaleNumberInput();
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#locations-table tbody').removeClass("d-none");
|
||||
locationsTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#apikeys-table tbody').removeClass("d-none");
|
||||
apiKeysTable.columns.adjust().draw();
|
||||
|
@ -291,7 +291,7 @@ var quConversionsTable = $('#qu-conversions-table-products').DataTable({
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 },
|
||||
{ 'visible': false, 'targets': 4 }
|
||||
],
|
||||
].concat($.fn.dataTable.defaults.columnDefs),
|
||||
'rowGroup': {
|
||||
dataSrc: 4
|
||||
}
|
||||
@ -305,7 +305,7 @@ var barcodeTable = $('#barcode-table').DataTable({
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#barcode-table tbody').removeClass("d-none");
|
||||
barcodeTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#productgroups-table tbody').removeClass("d-none");
|
||||
groupsTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#products-table tbody').removeClass("d-none");
|
||||
productsTable.columns.adjust().draw();
|
||||
@ -36,6 +36,7 @@ $("#clear-filter-button").on("click", function()
|
||||
$("#product-group-filter").val("all");
|
||||
productsTable.column(7).search("").draw();
|
||||
productsTable.search("").draw();
|
||||
$("#show-disabled-products").prop('checked', false);
|
||||
});
|
||||
|
||||
if (typeof GetUriParam("product-group") !== "undefined")
|
||||
@ -49,58 +50,53 @@ $(document).on('click', '.product-delete-button', function(e)
|
||||
var objectName = $(e.currentTarget).attr('data-product-name');
|
||||
var objectId = $(e.currentTarget).attr('data-product-id');
|
||||
|
||||
Grocy.Api.Get('stock/products/' + objectId,
|
||||
function(productDetails)
|
||||
{
|
||||
var stockAmount = productDetails.stock_amount || '0';
|
||||
|
||||
if (stockAmount.toString() == "0")
|
||||
{
|
||||
bootbox.confirm({
|
||||
message: __t('Are you sure you want to deactivate this product "%s"?', objectName),
|
||||
closeButton: false,
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: __t('Yes'),
|
||||
className: 'btn-success'
|
||||
},
|
||||
cancel: {
|
||||
label: __t('No'),
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function(result)
|
||||
{
|
||||
if (result === true)
|
||||
{
|
||||
jsonData = {};
|
||||
jsonData.active = 0;
|
||||
Grocy.Api.Put('objects/products/' + objectId, jsonData,
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/products');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
bootbox.alert({
|
||||
title: __t('Deactivation not possible'),
|
||||
message: __t('This product cannot be deactivated because it is in stock, please remove the stock amount first.') + '<br><br>' + __t('Stock amount') + ': ' + stockAmount + ' ' + __n(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural),
|
||||
closeButton: false
|
||||
});
|
||||
bootbox.confirm({
|
||||
message: __t('Are you sure to delete product "%s"?', objectName) + '<br><br>' + __t('This also removes any stock amount, the journal and all other references of this product - consider disabling this product instead, if you want to keep that and just hide it.'),
|
||||
closeButton: false,
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: __t('Yes'),
|
||||
className: 'btn-success'
|
||||
},
|
||||
cancel: {
|
||||
label: __t('No'),
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
function(xhr)
|
||||
callback: function(result)
|
||||
{
|
||||
console.error(xhr);
|
||||
if (result === true)
|
||||
{
|
||||
jsonData = {};
|
||||
jsonData.active = 0;
|
||||
Grocy.Api.Delete('objects/products/' + objectId, {},
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/products');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$("#show-disabled-products").change(function()
|
||||
{
|
||||
if (this.checked)
|
||||
{
|
||||
window.location.href = U('/products?include_disabled');
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.href = U('/products');
|
||||
}
|
||||
});
|
||||
|
||||
if (GetUriParam('include_disabled'))
|
||||
{
|
||||
$("#show-disabled-products").prop('checked', true);
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ var quConversionsTable = $('#qu-conversions-table').DataTable({
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#qu-conversions-table tbody').removeClass("d-none");
|
||||
quConversionsTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#quantityunits-table tbody').removeClass("d-none");
|
||||
quantityUnitsTable.columns.adjust().draw();
|
||||
|
@ -80,7 +80,7 @@ var recipesPosTables = $('#recipes-pos-table').DataTable({
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 },
|
||||
{ 'visible': false, 'targets': 4 }
|
||||
],
|
||||
].concat($.fn.dataTable.defaults.columnDefs),
|
||||
'rowGroup': {
|
||||
dataSrc: 4
|
||||
}
|
||||
@ -93,7 +93,7 @@ var recipesIncludesTables = $('#recipes-includes-table').DataTable({
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#recipes-includes-table tbody').removeClass("d-none");
|
||||
recipesIncludesTables.columns.adjust().draw();
|
||||
|
@ -2,9 +2,8 @@
|
||||
'order': [[1, 'asc']],
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 },
|
||||
{ 'orderData': 2, 'targets': 1 }
|
||||
],
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
].concat($.fn.dataTable.defaults.columnDefs),
|
||||
select: {
|
||||
style: 'single',
|
||||
selector: 'tr td:not(:first-child)'
|
||||
|
@ -7,7 +7,7 @@ var shoppingListTable = $('#shoppinglist-table').DataTable({
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 },
|
||||
{ 'visible': false, 'targets': 3 }
|
||||
],
|
||||
].concat($.fn.dataTable.defaults.columnDefs),
|
||||
'rowGroup': {
|
||||
dataSrc: 3,
|
||||
startRender: function(rows, group)
|
||||
|
@ -3,7 +3,7 @@ var locationsTable = $('#shoppinglocations-table').DataTable({
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#shoppinglocations-table tbody').removeClass("d-none");
|
||||
locationsTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
],
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#stockentries-table tbody').removeClass("d-none");
|
||||
stockEntriesTable.columns.adjust().draw();
|
||||
|
@ -4,7 +4,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#stock-journal-table tbody').removeClass("d-none");
|
||||
stockJournalTable.columns.adjust().draw();
|
||||
|
@ -4,7 +4,7 @@ var journalSummaryTable = $('#stock-journal-summary-table').DataTable({
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#stock-journal-summary-table tbody').removeClass("d-none");
|
||||
journalSummaryTable.columns.adjust().draw();
|
||||
|
@ -11,7 +11,7 @@
|
||||
{ 'visible': false, 'targets': 4 },
|
||||
{ 'visible': false, 'targets': 9 },
|
||||
{ 'visible': false, 'targets': 10 }
|
||||
],
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
|
||||
$('#stock-overview-table tbody').removeClass("d-none");
|
||||
@ -106,7 +106,7 @@ $(document).on('click', '.product-consume-button', function(e)
|
||||
var originalTotalStockAmount = $(e.currentTarget).attr('data-original-total-stock-amount');
|
||||
var wasSpoiled = $(e.currentTarget).hasClass("product-consume-button-spoiled");
|
||||
|
||||
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled },
|
||||
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled, 'allow_subproduct_substitution': true },
|
||||
function(bookingResponse)
|
||||
{
|
||||
Grocy.Api.Get('stock/products/' + productId,
|
||||
@ -162,7 +162,7 @@ $(document).on('click', '.product-open-button', function(e)
|
||||
var amount = $(e.currentTarget).attr('data-open-amount');
|
||||
var button = $(e.currentTarget);
|
||||
|
||||
Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': amount },
|
||||
Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': amount, 'allow_subproduct_substitution': true },
|
||||
function(bookingResponse)
|
||||
{
|
||||
Grocy.Api.Get('stock/products/' + productId,
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#taskcategories-table tbody').removeClass("d-none");
|
||||
categoriesTable.columns.adjust().draw();
|
||||
|
@ -4,7 +4,7 @@
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 },
|
||||
{ 'visible': false, 'targets': 3 }
|
||||
],
|
||||
].concat($.fn.dataTable.defaults.columnDefs),
|
||||
'rowGroup': {
|
||||
dataSrc: 3
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#userentities-table tbody').removeClass("d-none");
|
||||
userentitiesTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#userfields-table tbody').removeClass("d-none");
|
||||
userfieldsTable.columns.adjust().draw();
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#userobjects-table tbody').removeClass("d-none");
|
||||
userobjectsTable.columns.adjust().draw();
|
||||
|
@ -24,15 +24,16 @@ $('#permission-save').click(
|
||||
{
|
||||
return $(this).data('perm-id');
|
||||
}).toArray();
|
||||
Grocy.Api.Put('users/' + Grocy.EditObjectId + '/permissions', {
|
||||
'permissions': permission_list,
|
||||
}, function(result)
|
||||
{
|
||||
toastr.success(__t("Permissions saved"));
|
||||
}, function(xhr)
|
||||
{
|
||||
toastr.error(__t(JSON.parse(xhr.response).error_message));
|
||||
}
|
||||
|
||||
Grocy.Api.Put('users/' + Grocy.EditObjectId + '/permissions', { 'permissions': permission_list },
|
||||
function(result)
|
||||
{
|
||||
toastr.success(__t("Permissions saved"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
toastr.error(JSON.parse(xhr.response).error_message);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -51,5 +52,3 @@ if (Grocy.EditObjectId == Grocy.UserId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
check_hierachy($("input.permission-cb[name=ADMIN]").is(":checked"), "ADMIN");
|
||||
|
@ -3,7 +3,7 @@
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
].concat($.fn.dataTable.defaults.columnDefs)
|
||||
});
|
||||
$('#users-table tbody').removeClass("d-none");
|
||||
usersTable.columns.adjust().draw();
|
||||
|
@ -242,12 +242,11 @@ class StockService extends BaseService
|
||||
throw new \Exception('Location does not exist');
|
||||
}
|
||||
|
||||
// Tare weight handling
|
||||
$productDetails = (object)$this->GetProductDetails($productId);
|
||||
|
||||
// Tare weight handling
|
||||
// The given amount is the new total amount including the container weight (gross)
|
||||
// The amount to be posted needs to be the absolute value of the given amount - stock amount - tare weight
|
||||
$productDetails = (object) $this->GetProductDetails($productId);
|
||||
|
||||
if ($productDetails->product->enable_tare_weight_handling == 1)
|
||||
{
|
||||
if ($consumeExactAmount)
|
||||
@ -265,11 +264,13 @@ class StockService extends BaseService
|
||||
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
||||
{
|
||||
if ($locationId === null)
|
||||
{ // Consume from any location
|
||||
{
|
||||
// Consume from any location
|
||||
$potentialStockEntries = $this->GetProductStockEntries($productId, false, $allowSubproductSubstitution);
|
||||
}
|
||||
else
|
||||
{ // Consume only from the supplied location
|
||||
{
|
||||
// Consume only from the supplied location
|
||||
$potentialStockEntries = $this->GetProductStockEntriesForLocation($productId, $locationId, false, $allowSubproductSubstitution);
|
||||
}
|
||||
|
||||
@ -297,8 +298,20 @@ class StockService extends BaseService
|
||||
break;
|
||||
}
|
||||
|
||||
if ($allowSubproductSubstitution && $stockEntry->product_id != $productId)
|
||||
{
|
||||
// A sub product will be used -> use QU conversions
|
||||
$subProduct = $this->getDatabase()->products($stockEntry->product_id);
|
||||
$conversion = $this->getDatabase()->quantity_unit_conversions_resolved()->where('product_id = :1 AND from_qu_id = :2 AND to_qu_id = :3', $stockEntry->product_id, $productDetails->product->qu_id_stock, $subProduct->qu_id_stock)->fetch();
|
||||
if ($conversion != null)
|
||||
{
|
||||
$amount = $amount * floatval($conversion->factor);
|
||||
}
|
||||
}
|
||||
|
||||
if ($amount >= $stockEntry->amount)
|
||||
{ // Take the whole stock entry
|
||||
{
|
||||
// Take the whole stock entry
|
||||
$logRow = $this->getDatabase()->stock_log()->createRow([
|
||||
'product_id' => $stockEntry->product_id,
|
||||
'amount' => $stockEntry->amount * -1,
|
||||
@ -321,7 +334,8 @@ class StockService extends BaseService
|
||||
$amount -= $stockEntry->amount;
|
||||
}
|
||||
else
|
||||
{ // Stock entry amount is > than needed amount -> split the stock entry resp. update the amount
|
||||
{
|
||||
// Stock entry amount is > than needed amount -> split the stock entry resp. update the amount
|
||||
$restStockAmount = $stockEntry->amount - $amount;
|
||||
|
||||
$logRow = $this->getDatabase()->stock_log()->createRow([
|
||||
@ -665,7 +679,7 @@ class StockService extends BaseService
|
||||
$sqlWhereProductId = 'product_id = ' . $productId;
|
||||
if ($allowSubproductSubstitution)
|
||||
{
|
||||
$sqlWhereProductId = 'product_id IN (SELECT sub_product_id FROM products_resolved WHERE parent_product_id = ' . $productId . ')';
|
||||
$sqlWhereProductId = '(product_id IN (SELECT sub_product_id FROM products_resolved WHERE parent_product_id = ' . $productId . ') OR product_id = ' . $productId . ')';
|
||||
}
|
||||
|
||||
$sqlWhereAndOpen = 'AND open IN (0, 1)';
|
||||
@ -697,7 +711,7 @@ class StockService extends BaseService
|
||||
$sqlWhereProductId = 'product_id = ' . $productId;
|
||||
if ($allowSubproductSubstitution)
|
||||
{
|
||||
$sqlWhereProductId = 'product_id IN (SELECT sub_product_id FROM products_resolved WHERE parent_product_id = ' . $productId . ')';
|
||||
$sqlWhereProductId = '(product_id IN (SELECT sub_product_id FROM products_resolved WHERE parent_product_id = ' . $productId . ') OR product_id = ' . $productId . ')';
|
||||
}
|
||||
|
||||
return $this->getDatabase()->stock_current_locations()->where($sqlWhereProductId);
|
||||
@ -768,15 +782,16 @@ class StockService extends BaseService
|
||||
return null;
|
||||
}
|
||||
|
||||
public function OpenProduct(int $productId, float $amount, $specificStockEntryId = 'default', &$transactionId = null)
|
||||
public function OpenProduct(int $productId, float $amount, $specificStockEntryId = 'default', &$transactionId = null, $allowSubproductSubstitution = false)
|
||||
{
|
||||
if (!$this->ProductExists($productId))
|
||||
{
|
||||
throw new \Exception('Product does not exist or is inactive');
|
||||
}
|
||||
|
||||
$productStockAmountUnopened = $this->getDatabase()->stock()->where('product_id = :1 AND open = 0', $productId)->sum('amount');
|
||||
$potentialStockEntries = $this->GetProductStockEntries($productId, true);
|
||||
$productDetails = (object)$this->GetProductDetails($productId);
|
||||
$productStockAmountUnopened = floatval($productDetails->stock_amount_aggregated) - floatval($productDetails->stock_amount_opened_aggregated);
|
||||
$potentialStockEntries = $this->GetProductStockEntries($productId, true, $allowSubproductSubstitution);
|
||||
$product = $this->getDatabase()->products($productId);
|
||||
|
||||
if ($product->enable_tare_weight_handling == 1)
|
||||
@ -807,14 +822,25 @@ class StockService extends BaseService
|
||||
}
|
||||
|
||||
$newBestBeforeDate = $stockEntry->best_before_date;
|
||||
|
||||
if ($product->default_best_before_days_after_open > 0)
|
||||
{
|
||||
$newBestBeforeDate = date('Y-m-d', strtotime('+' . $product->default_best_before_days_after_open . ' days'));
|
||||
}
|
||||
|
||||
if ($allowSubproductSubstitution && $stockEntry->product_id != $productId)
|
||||
{
|
||||
// A sub product will be used -> use QU conversions
|
||||
$subProduct = $this->getDatabase()->products($stockEntry->product_id);
|
||||
$conversion = $this->getDatabase()->quantity_unit_conversions_resolved()->where('product_id = :1 AND from_qu_id = :2 AND to_qu_id = :3', $stockEntry->product_id, $product->qu_id_stock, $subProduct->qu_id_stock)->fetch();
|
||||
if ($conversion != null)
|
||||
{
|
||||
$amount = $amount * floatval($conversion->factor);
|
||||
}
|
||||
}
|
||||
|
||||
if ($amount >= $stockEntry->amount)
|
||||
{ // Mark the whole stock entry as opened
|
||||
{
|
||||
// Mark the whole stock entry as opened
|
||||
$logRow = $this->getDatabase()->stock_log()->createRow([
|
||||
'product_id' => $stockEntry->product_id,
|
||||
'amount' => $stockEntry->amount,
|
||||
@ -840,7 +866,8 @@ class StockService extends BaseService
|
||||
$amount -= $stockEntry->amount;
|
||||
}
|
||||
else
|
||||
{ // Stock entry amount is > than needed amount -> split the stock entry
|
||||
{
|
||||
// Stock entry amount is > than needed amount -> split the stock entry
|
||||
$restStockAmount = $stockEntry->amount - $amount;
|
||||
|
||||
$newStockRow = $this->getDatabase()->stock()->createRow([
|
||||
|
@ -3,7 +3,8 @@
|
||||
name="{{ $perm->permission_name }}"
|
||||
class="permission-cb"
|
||||
data-perm-id="{{ $perm->permission_id }}"
|
||||
@if($perm->has_permission) checked @endif autocomplete="off">
|
||||
@if($perm->has_permission) checked @endif
|
||||
@if(isset($permParent) && $permParent->has_permission) disabled @endif>
|
||||
{{ $__t($perm->permission_name) }}
|
||||
</label>
|
||||
<div id="permission-sub-{{ $perm->permission_name }}">
|
||||
@ -11,7 +12,8 @@
|
||||
@foreach($perm->uihelper_user_permissionsList(array('user_id' => $user->id))->via('parent') as $p)
|
||||
<li>
|
||||
@include('components.userpermission_select', array(
|
||||
'perm' => $p
|
||||
'perm' => $p,
|
||||
'permParent' => $perm
|
||||
))
|
||||
</li>
|
||||
@endforeach
|
||||
|
@ -677,6 +677,7 @@
|
||||
<script src="{{ $U('/node_modules/datatables.net-select/js/dataTables.select.min.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/datatables.net-select-bs4/js/select.bootstrap4.min.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/datatables.net-plugins/filtering/type-based/accent-neutralise.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/datatables.net-plugins/sorting/chinese-string.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/timeago/jquery.timeago.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules', true) }}/timeago/locales/jquery.timeago.{{ $__t('timeago_locale') }}.js?v={{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/toastr/build/toastr.min.js?v=', true) }}{{ $version }}"></script>
|
||||
|
@ -75,6 +75,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input @if($mode=='create'
|
||||
)
|
||||
checked
|
||||
@elseif($mode=='edit'
|
||||
&&
|
||||
$product->show_on_stock_overview == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="show_on_stock_overview" name="show_on_stock_overview" value="1">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="show_on_stock_overview">{{ $__t('Show on stock overview page') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php $prefillById = ''; if($mode=='edit') { $prefillById = $product->parent_product_id; } @endphp
|
||||
@php
|
||||
$hint = '';
|
||||
@ -211,7 +224,7 @@
|
||||
'label' => 'Default due days',
|
||||
'min' => -1,
|
||||
'value' => $value,
|
||||
'hint' => $__t('For purchases this amount of days will be added to today for the due date suggestion') . ' (' . $__t('-1 means that this product wille be never overdue') . ')'
|
||||
'hint' => $__t('For purchases this amount of days will be added to today for the due date suggestion') . ' (' . $__t('-1 means that this product will be never overdue') . ')'
|
||||
))
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||
@ -221,7 +234,7 @@
|
||||
'label' => 'Default due days after opened',
|
||||
'min' => 0,
|
||||
'value' => $value,
|
||||
'hint' => $__t('When this product was marked as opened, the expiry date will be replaced by today + this amount of days (a value of 0 disables this)')
|
||||
'hint' => $__t('When this product was marked as opened, the due date will be replaced by today + this amount of days (a value of 0 disables this)')
|
||||
))
|
||||
@else
|
||||
<input type="hidden"
|
||||
@ -376,7 +389,7 @@
|
||||
'label' => 'Default due days after freezing',
|
||||
'min' => -1,
|
||||
'value' => $value,
|
||||
'hint' => $__t('On moving this product to a freezer location (so when freezing it), the expiry date will be replaced by today + this amount of days')
|
||||
'hint' => $__t('On moving this product to a freezer location (so when freezing it), the due date will be replaced by today + this amount of days')
|
||||
))
|
||||
|
||||
@php if($mode == 'edit') { $value = $product->default_best_before_days_after_thawing; } else { $value = 0; } @endphp
|
||||
|
@ -71,6 +71,17 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||
<div class="form-check custom-control custom-checkbox">
|
||||
<input class="form-check-input custom-control-input"
|
||||
type="checkbox"
|
||||
id="show-disabled-products">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="show-disabled-products">
|
||||
{{ $__t('Show disabled products') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="float-right">
|
||||
<a id="clear-filter-button"
|
||||
@ -124,7 +135,7 @@
|
||||
title="{{ $__t('Copy this item') }}">
|
||||
<i class="fas fa-copy"></i>
|
||||
</a>
|
||||
<a class="btn btn-danger btn-sm product-delete-button @if($product->active == 0) disabled @endif"
|
||||
<a class="btn btn-danger btn-sm product-delete-button"
|
||||
href="#"
|
||||
data-product-id="{{ $product->id }}"
|
||||
data-product-name="{{ $product->name }}"
|
||||
@ -134,9 +145,12 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
@if($product->active == 0) (deactivated) @endif {{ $product->name }}@if(!empty($product->picture_file_name)) <i class="fas fa-image text-muted"
|
||||
{{ $product->name }}
|
||||
@if(!empty($product->picture_file_name))
|
||||
<i class="fas fa-image text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('This product has a picture') }}"></i>@endif
|
||||
title="{{ $__t('This product has a picture') }}"></i>
|
||||
@endif
|
||||
</td>
|
||||
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
|
||||
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
|
||||
|
@ -391,7 +391,7 @@
|
||||
@endif
|
||||
<li class="list-group-item px-0 @if($hasIngredientGroups && $hasProductGroups) ml-4 @elseif($hasIngredientGroups || $hasProductGroups) ml-2 @else ml-0 @endif">
|
||||
@if($selectedRecipePosition->product_active == 0)
|
||||
<div class="small text-muted font-italic">{{ $__t('Deactivated Product') }}</div>
|
||||
<div class="small text-muted font-italic">{{ $__t('Disabled') }}</div>
|
||||
@endif
|
||||
@php
|
||||
$product = FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id);
|
||||
@ -411,7 +411,8 @@
|
||||
{{ $__n($selectedRecipePosition->recipe_amount, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name_plural) }} {{ FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id)->name }}
|
||||
@if($selectedRecipePosition->need_fulfilled == 1)<i class="fas fa-check text-success"></i>@elseif($selectedRecipePosition->need_fulfilled_with_shopping_list == 1)<i class="fas fa-exclamation text-warning"></i>@else<i class="fas fa-times text-danger"></i>@endif
|
||||
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->need_fulfilled == 1) {{ $__t('Enough in stock') }} @else {{ $__t('Not enough in stock (not included in costs), %1$s missing, %2$s already on shopping list', round(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->missing_amount, 2), round(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->amount_on_shopping_list, 2)) }} @endif</span>
|
||||
|
||||
@if($selectedRecipePosition->need_fulfilled == 1 && GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) <span class="float-right font-italic ml-2 locale-number locale-number-currency">{{ $selectedRecipePosition->costs }}</span> @endif
|
||||
<span class="float-right font-italic"><span class="locale-number locale-number-quantity-amount">{{ $selectedRecipePosition->calories }} {{ $__t('Calories') }}</span></span>
|
||||
@if(!empty($selectedRecipePosition->recipe_variable_amount))
|
||||
<div class="small text-muted font-italic">{{ $__t('Variable amount') }}</div>
|
||||
@endif
|
||||
|
@ -178,7 +178,7 @@
|
||||
<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) @if($currentStockEntry->due_type == 1) table-secondary @else table-danger @endif @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-STOCK_CONSUME btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount < $currentStockEntry->quick_consume_amount || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
|
||||
<a class="permission-STOCK_CONSUME btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount_aggregated < $currentStockEntry->quick_consume_amount || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
|
||||
href="#"
|
||||
data-toggle="tooltip"
|
||||
data-placement="left"
|
||||
@ -190,7 +190,7 @@
|
||||
<i class="fas fa-utensils"></i> <span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
|
||||
</a>
|
||||
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button"
|
||||
class="permission-STOCK_CONSUME btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif"
|
||||
class="permission-STOCK_CONSUME btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount_aggregated == 0) disabled @endif"
|
||||
href="#"
|
||||
data-toggle="tooltip"
|
||||
data-placement="right"
|
||||
@ -203,7 +203,7 @@
|
||||
<i class="fas fa-utensils"></i> {{ $__t('All') }}
|
||||
</a>
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||
<a class="btn btn-success btn-sm product-open-button @if($currentStockEntry->amount < $currentStockEntry->quick_consume_amount || $currentStockEntry->amount == $currentStockEntry->amount_opened || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
|
||||
<a class="btn btn-success btn-sm product-open-button @if($currentStockEntry->amount_aggregated < $currentStockEntry->quick_consume_amount || $currentStockEntry->amount_aggregated == $currentStockEntry->amount_opened_aggregated || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
|
||||
href="#"
|
||||
data-toggle="tooltip"
|
||||
data-placement="left"
|
||||
@ -251,7 +251,7 @@
|
||||
<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>
|
||||
<a class="dropdown-item product-consume-button product-consume-button-spoiled permission-STOCK_CONSUME @if($currentStockEntry->amount < 1) disabled @endif"
|
||||
<a class="dropdown-item product-consume-button product-consume-button-spoiled permission-STOCK_CONSUME @if($currentStockEntry->amount_aggregated < 1) disabled @endif"
|
||||
type="button"
|
||||
href="#"
|
||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||
|
Loading…
x
Reference in New Issue
Block a user