mirror of
https://github.com/grocy/grocy.git
synced 2025-04-30 01:55:47 +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-)
|
- 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)
|
- 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-)
|
- 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
|
- 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 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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`)
|
- 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 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)
|
- 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
|
- 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)
|
- 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
|
- 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)
|
- 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 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 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 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 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 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 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
|
### Meal plan fixes
|
||||||
- Fixed that for products the quantity unit purchase was displayed instead of the products quantity unit stock (thanks @BenoitAnastay)
|
- 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`)
|
- 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)
|
- 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 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 `/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 `/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`)
|
- 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)
|
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)
|
private function IsEntityWithPreventedListing($entity)
|
||||||
|
@ -238,8 +238,6 @@ class StockApiController extends BaseApiController
|
|||||||
|
|
||||||
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
|
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
|
||||||
|
|
||||||
$result = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if ($requestBody === null)
|
if ($requestBody === null)
|
||||||
@ -253,57 +251,55 @@ class StockApiController extends BaseApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$spoiled = false;
|
$spoiled = false;
|
||||||
|
|
||||||
if (array_key_exists('spoiled', $requestBody))
|
if (array_key_exists('spoiled', $requestBody))
|
||||||
{
|
{
|
||||||
$spoiled = $requestBody['spoiled'];
|
$spoiled = $requestBody['spoiled'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
|
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
|
||||||
|
|
||||||
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
|
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
|
||||||
{
|
{
|
||||||
$transactionType = $requestBody['transactiontype'];
|
$transactionType = $requestBody['transactiontype'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$specificStockEntryId = 'default';
|
$specificStockEntryId = 'default';
|
||||||
|
|
||||||
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
|
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
|
||||||
{
|
{
|
||||||
$specificStockEntryId = $requestBody['stock_entry_id'];
|
$specificStockEntryId = $requestBody['stock_entry_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$locationId = null;
|
$locationId = null;
|
||||||
|
|
||||||
if (array_key_exists('location_id', $requestBody) && !empty($requestBody['location_id']) && is_numeric($requestBody['location_id']))
|
if (array_key_exists('location_id', $requestBody) && !empty($requestBody['location_id']) && is_numeric($requestBody['location_id']))
|
||||||
{
|
{
|
||||||
$locationId = $requestBody['location_id'];
|
$locationId = $requestBody['location_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$recipeId = null;
|
$recipeId = null;
|
||||||
|
|
||||||
if (array_key_exists('recipe_id', $requestBody) && is_numeric($requestBody['recipe_id']))
|
if (array_key_exists('recipe_id', $requestBody) && is_numeric($requestBody['recipe_id']))
|
||||||
{
|
{
|
||||||
$recipeId = $requestBody['recipe_id'];
|
$recipeId = $requestBody['recipe_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$consumeExact = false;
|
$consumeExact = false;
|
||||||
|
|
||||||
if (array_key_exists('exact_amount', $requestBody))
|
if (array_key_exists('exact_amount', $requestBody))
|
||||||
{
|
{
|
||||||
$consumeExact = $requestBody['exact_amount'];
|
$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));
|
return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
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)
|
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';
|
$specificStockEntryId = 'default';
|
||||||
|
|
||||||
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
|
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
|
||||||
{
|
{
|
||||||
$specificStockEntryId = $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));
|
return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
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)
|
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', [
|
return $this->renderPage($response, 'products', [
|
||||||
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
|
'products' => $products,
|
||||||
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
|
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
|
||||||
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
||||||
'productGroups' => $this->getDatabase()->product_groups()->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
|
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;
|
return $this->getPermissions()->where('permission_name', $permission)->fetch() !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,22 +152,32 @@ class UsersApiController extends BaseApiController
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
User::checkPermission($request, User::PERMISSION_ADMIN);
|
User::checkPermission($request, User::PERMISSION_ADMIN);
|
||||||
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
|
|
||||||
|
$requestBody = $request->getParsedBody();
|
||||||
$db = $this->getDatabase();
|
$db = $this->getDatabase();
|
||||||
$db->user_permissions()
|
$db->user_permissions()
|
||||||
->where('user_id', $args['userId'])
|
->where('user_id', $args['userId'])
|
||||||
->delete();
|
->delete();
|
||||||
|
|
||||||
$perms = [];
|
$perms = [];
|
||||||
|
if (GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease')
|
||||||
foreach ($requestBody['permissions'] as $perm_id)
|
|
||||||
{
|
{
|
||||||
|
// For demo mode always all users have and keep the ADMIN permission
|
||||||
$perms[] = [
|
$perms[] = [
|
||||||
'user_id' => $args['userId'],
|
'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');
|
$db->insert('user_permissions', $perms, 'batch');
|
||||||
|
|
||||||
return $this->EmptyApiResponse($response);
|
return $this->EmptyApiResponse($response);
|
||||||
|
@ -1835,6 +1835,10 @@
|
|||||||
"exact_amount": {
|
"exact_amount": {
|
||||||
"type": "boolean",
|
"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"
|
"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": {
|
"example": {
|
||||||
@ -2054,6 +2058,10 @@
|
|||||||
"stock_entry_id": {
|
"stock_entry_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A specific stock entry id to open, if used, the amount has to be 1"
|
"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": {
|
"example": {
|
||||||
@ -2262,6 +2270,10 @@
|
|||||||
"exact_amount": {
|
"exact_amount": {
|
||||||
"type": "boolean",
|
"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"
|
"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": {
|
"example": {
|
||||||
@ -2476,6 +2488,10 @@
|
|||||||
"stock_entry_id": {
|
"stock_entry_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A specific stock entry id to open, if used, the amount has to be 1"
|
"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": {
|
"example": {
|
||||||
|
@ -751,7 +751,7 @@ msgstr ""
|
|||||||
msgid "Image of product %s"
|
msgid "Image of product %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Delete not possible"
|
msgid "Deletion not possible"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Equipment"
|
msgid "Equipment"
|
||||||
@ -1906,19 +1906,19 @@ msgstr ""
|
|||||||
msgid "For purchases this amount of days will be added to today for the due date suggestion"
|
msgid "For purchases this amount of days will be added to today for the due date suggestion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "-1 means that this product wille be never overdue"
|
msgid "-1 means that this product will be never overdue"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Default due days"
|
msgid "Default due days"
|
||||||
msgstr ""
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Default due days after opened"
|
msgid "Default due days after opened"
|
||||||
msgstr ""
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Default due days after freezing"
|
msgid "Default due days after freezing"
|
||||||
@ -1996,3 +1996,15 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Use the products \"Quick consume amount\""
|
msgid "Use the products \"Quick consume amount\""
|
||||||
msgstr ""
|
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,
|
cumulate_min_stock_amount_of_sub_products TINYINT DEFAULT 0,
|
||||||
due_type TINYINT NOT NULL DEFAULT 1 CHECK(due_type IN (1, 2)),
|
due_type TINYINT NOT NULL DEFAULT 1 CHECK(due_type IN (1, 2)),
|
||||||
quick_consume_amount REAL NOT NULL DEFAULT 1,
|
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'))
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@ FROM (
|
|||||||
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
||||||
) sc
|
) sc
|
||||||
LEFT JOIN products p
|
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
|
CREATE VIEW uihelper_stock_current_overview
|
||||||
AS
|
AS
|
||||||
@ -78,4 +79,5 @@ FROM (
|
|||||||
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
||||||
) sc
|
) sc
|
||||||
LEFT JOIN products p
|
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
|
SELECT pc.permission_name
|
||||||
FROM user_permissions_resolved pc
|
FROM user_permissions_resolved pc
|
||||||
WHERE pc.user_id = u.id
|
WHERE pc.user_id = u.id
|
||||||
)
|
)
|
||||||
) AS has_permission,
|
) AS has_permission,
|
||||||
ph.parent AS parent
|
ph.parent AS parent
|
||||||
FROM users u, permission_hierarchy ph;
|
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]);
|
return JSON.parse(Grocy.UserSettings[settingKey]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
'columnDefs': [
|
||||||
|
{ type: 'chinese-string', targets: '_all' }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// serializeJSON defaults
|
// serializeJSON defaults
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
],
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#batteries-table tbody').removeClass("d-none");
|
$('#batteries-table tbody').removeClass("d-none");
|
||||||
batteriesTable.columns.adjust().draw();
|
batteriesTable.columns.adjust().draw();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#batteries-journal-table tbody').removeClass("d-none");
|
$('#batteries-journal-table tbody').removeClass("d-none");
|
||||||
batteriesJournalTable.columns.adjust().draw();
|
batteriesJournalTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#batteries-overview-table tbody').removeClass("d-none");
|
$('#batteries-overview-table tbody').removeClass("d-none");
|
||||||
batteriesOverviewTable.columns.adjust().draw();
|
batteriesOverviewTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#chores-table tbody').removeClass("d-none");
|
$('#chores-table tbody').removeClass("d-none");
|
||||||
choresTable.columns.adjust().draw();
|
choresTable.columns.adjust().draw();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#chores-journal-table tbody').removeClass("d-none");
|
$('#chores-journal-table tbody').removeClass("d-none");
|
||||||
choresJournalTable.columns.adjust().draw();
|
choresJournalTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#chores-overview-table tbody').removeClass("d-none");
|
$('#chores-overview-table tbody').removeClass("d-none");
|
||||||
choresOverviewTable.columns.adjust().draw();
|
choresOverviewTable.columns.adjust().draw();
|
||||||
|
@ -172,6 +172,59 @@ $('#product_id_text_input').on('blur', function(e)
|
|||||||
addProductWorkflowsAdditionalCssClasses = "d-none";
|
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;
|
Grocy.Components.ProductPicker.PopupOpen = true;
|
||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
message: __t('"%s" could not be resolved to a product, how do you want to proceed?', input),
|
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',
|
size: 'large',
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
closeButton: false,
|
closeButton: false,
|
||||||
buttons: {
|
buttons: 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).on('keypress', function(e)
|
}).on('keypress', function(e)
|
||||||
{
|
{
|
||||||
if (e.key === 'B' || e.key === 'b')
|
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();
|
$('.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...
|
// Don't know why the blur event does not fire immediately ... this works...
|
||||||
|
|
||||||
Grocy.Components.ProductPicker.GetInputElement().focusout();
|
Grocy.Components.ProductPicker.GetInputElement().focusout();
|
||||||
Grocy.Components.ProductPicker.GetInputElement().focus();
|
Grocy.Components.ProductPicker.GetInputElement().focus();
|
||||||
Grocy.Components.ProductPicker.GetInputElement().blur();
|
Grocy.Components.ProductPicker.GetInputElement().blur();
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
jsonData.amount = jsonForm.amount;
|
jsonData.amount = jsonForm.amount;
|
||||||
jsonData.exact_amount = $('#consume-exact-amount').is(':checked');
|
jsonData.exact_amount = $('#consume-exact-amount').is(':checked');
|
||||||
jsonData.spoiled = $('#spoiled').is(':checked');
|
jsonData.spoiled = $('#spoiled').is(':checked');
|
||||||
|
jsonData.allow_subproduct_substitution = true;
|
||||||
|
|
||||||
if ($("#use_specific_stock_entry").is(":checked"))
|
if ($("#use_specific_stock_entry").is(":checked"))
|
||||||
{
|
{
|
||||||
@ -28,7 +29,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var bookingResponse = null;
|
var bookingResponse = null;
|
||||||
|
|
||||||
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
|
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
|
||||||
function(productDetails)
|
function(productDetails)
|
||||||
{
|
{
|
||||||
@ -146,6 +146,7 @@ $('#save-mark-as-open-button').on('click', function(e)
|
|||||||
|
|
||||||
jsonData = {};
|
jsonData = {};
|
||||||
jsonData.amount = jsonForm.amount;
|
jsonData.amount = jsonForm.amount;
|
||||||
|
jsonData.allow_subproduct_substitution = true;
|
||||||
|
|
||||||
if ($("#use_specific_stock_entry").is(":checked"))
|
if ($("#use_specific_stock_entry").is(":checked"))
|
||||||
{
|
{
|
||||||
@ -215,7 +216,7 @@ $("#location_id").on('change', function(e)
|
|||||||
|
|
||||||
if (locationId)
|
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)
|
function(stockEntries)
|
||||||
{
|
{
|
||||||
stockEntries.forEach(stockEntry =>
|
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
|
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)
|
if (stockEntry.stock_id == stockId)
|
||||||
{
|
{
|
||||||
@ -302,7 +303,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
|||||||
$(".input-group-productamountpicker").trigger("change");
|
$(".input-group-productamountpicker").trigger("change");
|
||||||
|
|
||||||
$("#location_id").find("option").remove().end().append("<option></option>");
|
$("#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)
|
function(stockLocations)
|
||||||
{
|
{
|
||||||
var setDefault = 0;
|
var setDefault = 0;
|
||||||
@ -369,7 +370,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
|||||||
$("#tare-weight-handling-info").addClass("d-none");
|
$("#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.ProductAmountPicker.Reset();
|
||||||
Grocy.Components.ProductPicker.Clear();
|
Grocy.Components.ProductPicker.Clear();
|
||||||
@ -448,14 +449,14 @@ $("#specific_stock_entry").on("change", function(e)
|
|||||||
if ($(e.target).val() == "")
|
if ($(e.target).val() == "")
|
||||||
{
|
{
|
||||||
sumValue = 0;
|
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)
|
function(stockEntries)
|
||||||
{
|
{
|
||||||
stockEntries.forEach(stockEntry =>
|
stockEntries.forEach(stockEntry =>
|
||||||
{
|
{
|
||||||
if (stockEntry.location_id == $("#location_id").val() || stockEntry.location_id == "")
|
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);
|
$("#display_amount").attr("max", sumValue);
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
'order': [[0, 'asc']],
|
'order': [[0, 'asc']],
|
||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 },
|
{ 'searchable': false, "targets": 0 }
|
||||||
{ 'orderData': 2, 'targets': 1 }
|
].concat($.fn.dataTable.defaults.columnDefs),
|
||||||
],
|
|
||||||
select: {
|
select: {
|
||||||
style: 'single',
|
style: 'single',
|
||||||
selector: 'tr td:not(:first-child)'
|
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);
|
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);
|
$('#display_amount').val(productDetails.stock_amount);
|
||||||
RefreshLocaleNumberInput();
|
RefreshLocaleNumberInput();
|
||||||
$(".input-group-productamountpicker").trigger("change");
|
$(".input-group-productamountpicker").trigger("change");
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#locations-table tbody').removeClass("d-none");
|
$('#locations-table tbody').removeClass("d-none");
|
||||||
locationsTable.columns.adjust().draw();
|
locationsTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#apikeys-table tbody').removeClass("d-none");
|
$('#apikeys-table tbody').removeClass("d-none");
|
||||||
apiKeysTable.columns.adjust().draw();
|
apiKeysTable.columns.adjust().draw();
|
||||||
|
@ -291,7 +291,7 @@ var quConversionsTable = $('#qu-conversions-table-products').DataTable({
|
|||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 },
|
{ 'searchable': false, "targets": 0 },
|
||||||
{ 'visible': false, 'targets': 4 }
|
{ 'visible': false, 'targets': 4 }
|
||||||
],
|
].concat($.fn.dataTable.defaults.columnDefs),
|
||||||
'rowGroup': {
|
'rowGroup': {
|
||||||
dataSrc: 4
|
dataSrc: 4
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ var barcodeTable = $('#barcode-table').DataTable({
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#barcode-table tbody').removeClass("d-none");
|
$('#barcode-table tbody').removeClass("d-none");
|
||||||
barcodeTable.columns.adjust().draw();
|
barcodeTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#productgroups-table tbody').removeClass("d-none");
|
$('#productgroups-table tbody').removeClass("d-none");
|
||||||
groupsTable.columns.adjust().draw();
|
groupsTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#products-table tbody').removeClass("d-none");
|
$('#products-table tbody').removeClass("d-none");
|
||||||
productsTable.columns.adjust().draw();
|
productsTable.columns.adjust().draw();
|
||||||
@ -36,6 +36,7 @@ $("#clear-filter-button").on("click", function()
|
|||||||
$("#product-group-filter").val("all");
|
$("#product-group-filter").val("all");
|
||||||
productsTable.column(7).search("").draw();
|
productsTable.column(7).search("").draw();
|
||||||
productsTable.search("").draw();
|
productsTable.search("").draw();
|
||||||
|
$("#show-disabled-products").prop('checked', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (typeof GetUriParam("product-group") !== "undefined")
|
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 objectName = $(e.currentTarget).attr('data-product-name');
|
||||||
var objectId = $(e.currentTarget).attr('data-product-id');
|
var objectId = $(e.currentTarget).attr('data-product-id');
|
||||||
|
|
||||||
Grocy.Api.Get('stock/products/' + objectId,
|
bootbox.confirm({
|
||||||
function(productDetails)
|
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,
|
||||||
var stockAmount = productDetails.stock_amount || '0';
|
buttons: {
|
||||||
|
confirm: {
|
||||||
if (stockAmount.toString() == "0")
|
label: __t('Yes'),
|
||||||
{
|
className: 'btn-success'
|
||||||
bootbox.confirm({
|
},
|
||||||
message: __t('Are you sure you want to deactivate this product "%s"?', objectName),
|
cancel: {
|
||||||
closeButton: false,
|
label: __t('No'),
|
||||||
buttons: {
|
className: 'btn-danger'
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#qu-conversions-table tbody').removeClass("d-none");
|
$('#qu-conversions-table tbody').removeClass("d-none");
|
||||||
quConversionsTable.columns.adjust().draw();
|
quConversionsTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#quantityunits-table tbody').removeClass("d-none");
|
$('#quantityunits-table tbody').removeClass("d-none");
|
||||||
quantityUnitsTable.columns.adjust().draw();
|
quantityUnitsTable.columns.adjust().draw();
|
||||||
|
@ -80,7 +80,7 @@ var recipesPosTables = $('#recipes-pos-table').DataTable({
|
|||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 },
|
{ 'searchable': false, "targets": 0 },
|
||||||
{ 'visible': false, 'targets': 4 }
|
{ 'visible': false, 'targets': 4 }
|
||||||
],
|
].concat($.fn.dataTable.defaults.columnDefs),
|
||||||
'rowGroup': {
|
'rowGroup': {
|
||||||
dataSrc: 4
|
dataSrc: 4
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ var recipesIncludesTables = $('#recipes-includes-table').DataTable({
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#recipes-includes-table tbody').removeClass("d-none");
|
$('#recipes-includes-table tbody').removeClass("d-none");
|
||||||
recipesIncludesTables.columns.adjust().draw();
|
recipesIncludesTables.columns.adjust().draw();
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
'order': [[1, 'asc']],
|
'order': [[1, 'asc']],
|
||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 },
|
{ 'searchable': false, "targets": 0 }
|
||||||
{ 'orderData': 2, 'targets': 1 }
|
].concat($.fn.dataTable.defaults.columnDefs),
|
||||||
],
|
|
||||||
select: {
|
select: {
|
||||||
style: 'single',
|
style: 'single',
|
||||||
selector: 'tr td:not(:first-child)'
|
selector: 'tr td:not(:first-child)'
|
||||||
|
@ -7,7 +7,7 @@ var shoppingListTable = $('#shoppinglist-table').DataTable({
|
|||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 },
|
{ 'searchable': false, "targets": 0 },
|
||||||
{ 'visible': false, 'targets': 3 }
|
{ 'visible': false, 'targets': 3 }
|
||||||
],
|
].concat($.fn.dataTable.defaults.columnDefs),
|
||||||
'rowGroup': {
|
'rowGroup': {
|
||||||
dataSrc: 3,
|
dataSrc: 3,
|
||||||
startRender: function(rows, group)
|
startRender: function(rows, group)
|
||||||
|
@ -3,7 +3,7 @@ var locationsTable = $('#shoppinglocations-table').DataTable({
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#shoppinglocations-table tbody').removeClass("d-none");
|
$('#shoppinglocations-table tbody').removeClass("d-none");
|
||||||
locationsTable.columns.adjust().draw();
|
locationsTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
],
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#stockentries-table tbody').removeClass("d-none");
|
$('#stockentries-table tbody').removeClass("d-none");
|
||||||
stockEntriesTable.columns.adjust().draw();
|
stockEntriesTable.columns.adjust().draw();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#stock-journal-table tbody').removeClass("d-none");
|
$('#stock-journal-table tbody').removeClass("d-none");
|
||||||
stockJournalTable.columns.adjust().draw();
|
stockJournalTable.columns.adjust().draw();
|
||||||
|
@ -4,7 +4,7 @@ var journalSummaryTable = $('#stock-journal-summary-table').DataTable({
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#stock-journal-summary-table tbody').removeClass("d-none");
|
$('#stock-journal-summary-table tbody').removeClass("d-none");
|
||||||
journalSummaryTable.columns.adjust().draw();
|
journalSummaryTable.columns.adjust().draw();
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
{ 'visible': false, 'targets': 4 },
|
{ 'visible': false, 'targets': 4 },
|
||||||
{ 'visible': false, 'targets': 9 },
|
{ 'visible': false, 'targets': 9 },
|
||||||
{ 'visible': false, 'targets': 10 }
|
{ 'visible': false, 'targets': 10 }
|
||||||
],
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#stock-overview-table tbody').removeClass("d-none");
|
$('#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 originalTotalStockAmount = $(e.currentTarget).attr('data-original-total-stock-amount');
|
||||||
var wasSpoiled = $(e.currentTarget).hasClass("product-consume-button-spoiled");
|
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)
|
function(bookingResponse)
|
||||||
{
|
{
|
||||||
Grocy.Api.Get('stock/products/' + productId,
|
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 amount = $(e.currentTarget).attr('data-open-amount');
|
||||||
var button = $(e.currentTarget);
|
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)
|
function(bookingResponse)
|
||||||
{
|
{
|
||||||
Grocy.Api.Get('stock/products/' + productId,
|
Grocy.Api.Get('stock/products/' + productId,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#taskcategories-table tbody').removeClass("d-none");
|
$('#taskcategories-table tbody').removeClass("d-none");
|
||||||
categoriesTable.columns.adjust().draw();
|
categoriesTable.columns.adjust().draw();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 },
|
{ 'searchable': false, "targets": 0 },
|
||||||
{ 'visible': false, 'targets': 3 }
|
{ 'visible': false, 'targets': 3 }
|
||||||
],
|
].concat($.fn.dataTable.defaults.columnDefs),
|
||||||
'rowGroup': {
|
'rowGroup': {
|
||||||
dataSrc: 3
|
dataSrc: 3
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#userentities-table tbody').removeClass("d-none");
|
$('#userentities-table tbody').removeClass("d-none");
|
||||||
userentitiesTable.columns.adjust().draw();
|
userentitiesTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#userfields-table tbody').removeClass("d-none");
|
$('#userfields-table tbody').removeClass("d-none");
|
||||||
userfieldsTable.columns.adjust().draw();
|
userfieldsTable.columns.adjust().draw();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#userobjects-table tbody').removeClass("d-none");
|
$('#userobjects-table tbody').removeClass("d-none");
|
||||||
userobjectsTable.columns.adjust().draw();
|
userobjectsTable.columns.adjust().draw();
|
||||||
|
@ -24,15 +24,16 @@ $('#permission-save').click(
|
|||||||
{
|
{
|
||||||
return $(this).data('perm-id');
|
return $(this).data('perm-id');
|
||||||
}).toArray();
|
}).toArray();
|
||||||
Grocy.Api.Put('users/' + Grocy.EditObjectId + '/permissions', {
|
|
||||||
'permissions': permission_list,
|
Grocy.Api.Put('users/' + Grocy.EditObjectId + '/permissions', { 'permissions': permission_list },
|
||||||
}, function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
toastr.success(__t("Permissions saved"));
|
toastr.success(__t("Permissions saved"));
|
||||||
}, function(xhr)
|
},
|
||||||
{
|
function(xhr)
|
||||||
toastr.error(__t(JSON.parse(xhr.response).error_message));
|
{
|
||||||
}
|
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': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 },
|
{ 'orderable': false, 'targets': 0 },
|
||||||
{ 'searchable': false, "targets": 0 }
|
{ 'searchable': false, "targets": 0 }
|
||||||
]
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
});
|
});
|
||||||
$('#users-table tbody').removeClass("d-none");
|
$('#users-table tbody').removeClass("d-none");
|
||||||
usersTable.columns.adjust().draw();
|
usersTable.columns.adjust().draw();
|
||||||
|
@ -242,12 +242,11 @@ class StockService extends BaseService
|
|||||||
throw new \Exception('Location does not exist');
|
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 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
|
// 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 ($productDetails->product->enable_tare_weight_handling == 1)
|
||||||
{
|
{
|
||||||
if ($consumeExactAmount)
|
if ($consumeExactAmount)
|
||||||
@ -265,11 +264,13 @@ class StockService extends BaseService
|
|||||||
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
||||||
{
|
{
|
||||||
if ($locationId === null)
|
if ($locationId === null)
|
||||||
{ // Consume from any location
|
{
|
||||||
|
// Consume from any location
|
||||||
$potentialStockEntries = $this->GetProductStockEntries($productId, false, $allowSubproductSubstitution);
|
$potentialStockEntries = $this->GetProductStockEntries($productId, false, $allowSubproductSubstitution);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // Consume only from the supplied location
|
{
|
||||||
|
// Consume only from the supplied location
|
||||||
$potentialStockEntries = $this->GetProductStockEntriesForLocation($productId, $locationId, false, $allowSubproductSubstitution);
|
$potentialStockEntries = $this->GetProductStockEntriesForLocation($productId, $locationId, false, $allowSubproductSubstitution);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,8 +298,20 @@ class StockService extends BaseService
|
|||||||
break;
|
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)
|
if ($amount >= $stockEntry->amount)
|
||||||
{ // Take the whole stock entry
|
{
|
||||||
|
// Take the whole stock entry
|
||||||
$logRow = $this->getDatabase()->stock_log()->createRow([
|
$logRow = $this->getDatabase()->stock_log()->createRow([
|
||||||
'product_id' => $stockEntry->product_id,
|
'product_id' => $stockEntry->product_id,
|
||||||
'amount' => $stockEntry->amount * -1,
|
'amount' => $stockEntry->amount * -1,
|
||||||
@ -321,7 +334,8 @@ class StockService extends BaseService
|
|||||||
$amount -= $stockEntry->amount;
|
$amount -= $stockEntry->amount;
|
||||||
}
|
}
|
||||||
else
|
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;
|
$restStockAmount = $stockEntry->amount - $amount;
|
||||||
|
|
||||||
$logRow = $this->getDatabase()->stock_log()->createRow([
|
$logRow = $this->getDatabase()->stock_log()->createRow([
|
||||||
@ -665,7 +679,7 @@ class StockService extends BaseService
|
|||||||
$sqlWhereProductId = 'product_id = ' . $productId;
|
$sqlWhereProductId = 'product_id = ' . $productId;
|
||||||
if ($allowSubproductSubstitution)
|
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)';
|
$sqlWhereAndOpen = 'AND open IN (0, 1)';
|
||||||
@ -697,7 +711,7 @@ class StockService extends BaseService
|
|||||||
$sqlWhereProductId = 'product_id = ' . $productId;
|
$sqlWhereProductId = 'product_id = ' . $productId;
|
||||||
if ($allowSubproductSubstitution)
|
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);
|
return $this->getDatabase()->stock_current_locations()->where($sqlWhereProductId);
|
||||||
@ -768,15 +782,16 @@ class StockService extends BaseService
|
|||||||
return null;
|
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))
|
if (!$this->ProductExists($productId))
|
||||||
{
|
{
|
||||||
throw new \Exception('Product does not exist or is inactive');
|
throw new \Exception('Product does not exist or is inactive');
|
||||||
}
|
}
|
||||||
|
|
||||||
$productStockAmountUnopened = $this->getDatabase()->stock()->where('product_id = :1 AND open = 0', $productId)->sum('amount');
|
$productDetails = (object)$this->GetProductDetails($productId);
|
||||||
$potentialStockEntries = $this->GetProductStockEntries($productId, true);
|
$productStockAmountUnopened = floatval($productDetails->stock_amount_aggregated) - floatval($productDetails->stock_amount_opened_aggregated);
|
||||||
|
$potentialStockEntries = $this->GetProductStockEntries($productId, true, $allowSubproductSubstitution);
|
||||||
$product = $this->getDatabase()->products($productId);
|
$product = $this->getDatabase()->products($productId);
|
||||||
|
|
||||||
if ($product->enable_tare_weight_handling == 1)
|
if ($product->enable_tare_weight_handling == 1)
|
||||||
@ -807,14 +822,25 @@ class StockService extends BaseService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$newBestBeforeDate = $stockEntry->best_before_date;
|
$newBestBeforeDate = $stockEntry->best_before_date;
|
||||||
|
|
||||||
if ($product->default_best_before_days_after_open > 0)
|
if ($product->default_best_before_days_after_open > 0)
|
||||||
{
|
{
|
||||||
$newBestBeforeDate = date('Y-m-d', strtotime('+' . $product->default_best_before_days_after_open . ' days'));
|
$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)
|
if ($amount >= $stockEntry->amount)
|
||||||
{ // Mark the whole stock entry as opened
|
{
|
||||||
|
// Mark the whole stock entry as opened
|
||||||
$logRow = $this->getDatabase()->stock_log()->createRow([
|
$logRow = $this->getDatabase()->stock_log()->createRow([
|
||||||
'product_id' => $stockEntry->product_id,
|
'product_id' => $stockEntry->product_id,
|
||||||
'amount' => $stockEntry->amount,
|
'amount' => $stockEntry->amount,
|
||||||
@ -840,7 +866,8 @@ class StockService extends BaseService
|
|||||||
$amount -= $stockEntry->amount;
|
$amount -= $stockEntry->amount;
|
||||||
}
|
}
|
||||||
else
|
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;
|
$restStockAmount = $stockEntry->amount - $amount;
|
||||||
|
|
||||||
$newStockRow = $this->getDatabase()->stock()->createRow([
|
$newStockRow = $this->getDatabase()->stock()->createRow([
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
name="{{ $perm->permission_name }}"
|
name="{{ $perm->permission_name }}"
|
||||||
class="permission-cb"
|
class="permission-cb"
|
||||||
data-perm-id="{{ $perm->permission_id }}"
|
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) }}
|
{{ $__t($perm->permission_name) }}
|
||||||
</label>
|
</label>
|
||||||
<div id="permission-sub-{{ $perm->permission_name }}">
|
<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)
|
@foreach($perm->uihelper_user_permissionsList(array('user_id' => $user->id))->via('parent') as $p)
|
||||||
<li>
|
<li>
|
||||||
@include('components.userpermission_select', array(
|
@include('components.userpermission_select', array(
|
||||||
'perm' => $p
|
'perm' => $p,
|
||||||
|
'permParent' => $perm
|
||||||
))
|
))
|
||||||
</li>
|
</li>
|
||||||
@endforeach
|
@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/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-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/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/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', 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>
|
<script src="{{ $U('/node_modules/toastr/build/toastr.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
@ -75,6 +75,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 $prefillById = ''; if($mode=='edit') { $prefillById = $product->parent_product_id; } @endphp
|
||||||
@php
|
@php
|
||||||
$hint = '';
|
$hint = '';
|
||||||
@ -211,7 +224,7 @@
|
|||||||
'label' => 'Default due days',
|
'label' => 'Default due days',
|
||||||
'min' => -1,
|
'min' => -1,
|
||||||
'value' => $value,
|
'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)
|
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||||
@ -221,7 +234,7 @@
|
|||||||
'label' => 'Default due days after opened',
|
'label' => 'Default due days after opened',
|
||||||
'min' => 0,
|
'min' => 0,
|
||||||
'value' => $value,
|
'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
|
@else
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
@ -376,7 +389,7 @@
|
|||||||
'label' => 'Default due days after freezing',
|
'label' => 'Default due days after freezing',
|
||||||
'min' => -1,
|
'min' => -1,
|
||||||
'value' => $value,
|
'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
|
@php if($mode == 'edit') { $value = $product->default_best_before_days_after_thawing; } else { $value = 0; } @endphp
|
||||||
|
@ -71,6 +71,17 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</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="col">
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<a id="clear-filter-button"
|
<a id="clear-filter-button"
|
||||||
@ -124,7 +135,7 @@
|
|||||||
title="{{ $__t('Copy this item') }}">
|
title="{{ $__t('Copy this item') }}">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i>
|
||||||
</a>
|
</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="#"
|
href="#"
|
||||||
data-product-id="{{ $product->id }}"
|
data-product-id="{{ $product->id }}"
|
||||||
data-product-name="{{ $product->name }}"
|
data-product-name="{{ $product->name }}"
|
||||||
@ -134,9 +145,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<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"
|
data-toggle="tooltip"
|
||||||
title="{{ $__t('This product has a picture') }}"></i>@endif
|
title="{{ $__t('This product has a picture') }}"></i>
|
||||||
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
|
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
|
||||||
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
|
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
|
||||||
|
@ -391,7 +391,7 @@
|
|||||||
@endif
|
@endif
|
||||||
<li class="list-group-item px-0 @if($hasIngredientGroups && $hasProductGroups) ml-4 @elseif($hasIngredientGroups || $hasProductGroups) ml-2 @else ml-0 @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)
|
@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
|
@endif
|
||||||
@php
|
@php
|
||||||
$product = FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id);
|
$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 }}
|
{{ $__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
|
@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>
|
<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))
|
@if(!empty($selectedRecipePosition->recipe_variable_amount))
|
||||||
<div class="small text-muted font-italic">{{ $__t('Variable amount') }}</div>
|
<div class="small text-muted font-italic">{{ $__t('Variable amount') }}</div>
|
||||||
@endif
|
@endif
|
||||||
|
@ -178,7 +178,7 @@
|
|||||||
<tr id="product-{{ $currentStockEntry->product_id }}-row"
|
<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">
|
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">
|
<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="#"
|
href="#"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
data-placement="left"
|
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>
|
<i class="fas fa-utensils"></i> <span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button"
|
<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="#"
|
href="#"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
@ -203,7 +203,7 @@
|
|||||||
<i class="fas fa-utensils"></i> {{ $__t('All') }}
|
<i class="fas fa-utensils"></i> {{ $__t('All') }}
|
||||||
</a>
|
</a>
|
||||||
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
@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="#"
|
href="#"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
data-placement="left"
|
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>
|
<span class="dropdown-item-icon"><i class="fas fa-list"></i></span> <span class="dropdown-item-text">{{ $__t('Inventory') }}</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<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"
|
type="button"
|
||||||
href="#"
|
href="#"
|
||||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user