From 91d8eaeb7433c0817abf332c1a81dc4a4b9bdc53 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Tue, 13 Jul 2021 19:29:23 +0200 Subject: [PATCH] Squashed commit Improve journal pages loading time (new date range filter) Various small style adjustments (meal plan page and others) Pulled German translations from Transifex Show the shopping list total value (closes #1309) Make it possible to copy recipes (closes #714) Implemented optional "auto decimal separator for price inputs" (closes #1345) Removed table grouped column fixed order restriction (closes #1402) Don't filter out style, class, id attributes of html text (closes #1298) Added product picture as column on the stock overview page (closes #1283) Added grocycodes also for chores and batteries (+ camera barcode scanning for /choretracking and /batterytracking, this now closes #221) --- changelog/62_UNRELEASED_xxxx-xx-xx.md | 35 +- config-dist.php | 3 +- controllers/BaseController.php | 3 +- controllers/BatteriesApiController.php | 26 + controllers/BatteriesController.php | 57 +- controllers/ChoresApiController.php | 26 + controllers/ChoresController.php | 57 +- controllers/RecipesApiController.php | 14 + controllers/StockApiController.php | 66 ++- controllers/StockController.php | 25 +- docs/label-printing.md | 6 +- grocy.openapi.json | 136 ++++- localization/de/chore_assignment_types.po | 7 +- localization/de/chore_period_types.po | 7 +- localization/de/demo_data.po | 32 +- localization/de/permissions.po | 32 +- localization/de/strings.po | 604 ++++++++++++--------- localization/strings.pot | 50 +- migrations/0136.sql | 6 +- migrations/0147.sql | 28 + public/css/grocy.css | 5 +- public/js/grocy.js | 17 +- public/viewjs/batteriesjournal.js | 29 +- public/viewjs/batteriesoverview.js | 15 + public/viewjs/batteryform.js | 15 + public/viewjs/batterytracking.js | 48 +- public/viewjs/choreform.js | 15 + public/viewjs/choresjournal.js | 29 +- public/viewjs/choresoverview.js | 15 + public/viewjs/choretracking.js | 48 +- public/viewjs/components/numberpicker.js | 17 + public/viewjs/components/productcard.js | 4 +- public/viewjs/components/productpicker.js | 5 +- public/viewjs/mealplan.js | 51 +- public/viewjs/openapiui.js | 3 +- public/viewjs/productform.js | 4 +- public/viewjs/purchase.js | 6 +- public/viewjs/recipeform.js | 1 - public/viewjs/recipes.js | 21 + public/viewjs/shoppinglist.js | 1 - public/viewjs/stockentries.js | 2 +- public/viewjs/stockjournal.js | 30 +- public/viewjs/stockjournalsummary.js | 1 - public/viewjs/stockoverview.js | 4 +- public/viewjs/stocksettings.js | 5 + routes.php | 5 + services/DemoDataGeneratorService.php | 1 + services/RecipesService.php | 17 + services/StockService.php | 4 +- views/batteriesjournal.blade.php | 18 +- views/batteriesoverview.blade.php | 14 + views/batteryform.blade.php | 31 ++ views/batterytracking.blade.php | 14 +- views/choreform.blade.php | 33 ++ views/choresjournal.blade.php | 18 +- views/choresoverview.blade.php | 14 + views/choretracking.blade.php | 14 +- views/components/datetimepicker.blade.php | 3 +- views/components/datetimepicker2.blade.php | 3 +- views/equipment.blade.php | 24 +- views/layout/default.blade.php | 2 +- views/mealplan.blade.php | 24 + views/productform.blade.php | 10 +- views/purchase.blade.php | 2 +- views/recipes.blade.php | 30 +- views/shoppinglist.blade.php | 7 +- views/stockentries.blade.php | 10 +- views/stockjournal.blade.php | 26 +- views/stockoverview.blade.php | 15 +- views/stocksettings.blade.php | 17 + 70 files changed, 1476 insertions(+), 491 deletions(-) create mode 100644 migrations/0147.sql diff --git a/changelog/62_UNRELEASED_xxxx-xx-xx.md b/changelog/62_UNRELEASED_xxxx-xx-xx.md index 5febf134..12fa6f92 100644 --- a/changelog/62_UNRELEASED_xxxx-xx-xx.md +++ b/changelog/62_UNRELEASED_xxxx-xx-xx.md @@ -1,27 +1,27 @@ > ⚠️ The following PHP extensions are now additionally required: `json`, `intl`, `zlib` -> ⚠️ PHP 8 is now supported and from now on the only tested runtime version (but as of now, PHP 7.2 should still work). +> ⚠️ PHP 8 is now supported and from now on the only tested runtime version (although currently PHP 7.2 should still work). -### New feature: (Own) Product and stock entry labels/barcodes ("grocycode") -- Print own labels/barcodes for products and/or every stock entry and then scan that code on every place a product or stock entry can be selected +### New feature: (Own) Product/stock entry/chores/batteries labels/barcodes ("grocycode") +- Print own labels/barcodes for products/stock entries/chores/batteries and then scan that code on every place a product/stock entry/chore/battery can be selected - Can be printed (or downloaded) via - - The product edit page - - The context/more menu per line on the stock overview and stock entries page - - Automatically on purchase (new option on the purchase page, defaults can be configured per product) + - The product/chore/battery edit page + - The context/more menu per line on the overview pages and for stock entries on the stock entries page + - Automatically on purchase (new option on the purchase page, defaults can be configured per product) for stock entries - The used barcode type can be configured via the `config.php` option `GROCYCODE_TYPE`: - `1D` (default) will produce a `Code128` 1D barcode (supported by the integrated camera barcode scanner) - `2D` will produce a `DataMatrix` 2D barcode (currently not supported by the integrated camera barcode scanner, but can be probably printed smaller) -- Label printer functionality can be enabled via the new feature flag `FEATURE_FLAG_LABELPRINTER` (defaults to disabled) +- Label printer functionality can be enabled via the new feature flag `FEATURE_FLAG_LABEL_PRINTER` (defaults to disabled) - Label printer communication happens via WebHooks - see the new `LABEL_PRINTER*` `config.php` options -- Those grocycodes can also be used without a label printer - you can view or download the pictures and print them manually +- grocycodes can also be used without a label printer - you can view or download thm as pictures and print them manually - More information: - https://github.com/grocy/grocy/blob/master/docs/grocycode.md - https://github.com/grocy/grocy/blob/master/docs/label-printing.md -- (Thanks a lot @mistressofjellyfish) +- (Thanks a lot @mistressofjellyfish for the initial work on this) ### New feature: Shopping list thermal printer support - The shopping list can now be printed on a thermal printer - - The printer must compatible to the `ESC/POS` protocol and needs to be locally attached or network reachable to/by the machine hosting grocy (so the server) + - The printer must be compatible to the `ESC/POS` protocol and needs to be locally attached or network reachable to/by the machine hosting grocy (so the server) - See the new `TPRINTER*` `config.php` options to configure the printer connection and other options - => New button on the shopping list print dialog - Can be enabled via the new feature flag `FEATURE_FLAG_THERMAL_PRINTER` (defaults to disabled) @@ -31,11 +31,13 @@ - Product barcodes are now enforced to be unique across products - On the stock overview page it's now also possible to search/filter by product barcodes (via the general search field) - The product picker on the consume and transfer page now only shows products which are currently in stock -- Added a filter option to only show in-stock products on the stock overview and products list page (master data) -- Added new columns on the stock overview page (hidden by default): Product description, product default location, parent product +- Added a filter option to only show in-stock products on the stock overview and products list (master data) page +- Added new columns on the stock overview page (hidden by default): Product description, product default location, parent product, product picture - Added a new product option "Should not be frozen" (defaults to disabled and only visible when `FEATURE_FLAG_STOCK_PRODUCT_FREEZING` is enabled) - When enabled, on moving the product to a freezer location (so when freezing it), a corresponding warning will be shown - Optimized that when opening a product which has "Default due days after opened" set, the resulting date now never extends the original due date +- Added a new stock setting (top right corner settings menu) "Add decimal separator automatically for price inputs" (defaults to disabled) + - When enabled, you always have to enter the value including decimal places, the decimal separator will be automatically added based on the amount of allowed decimal places - Fixed that editing stock entries was not possible - Fixed that consuming with Scan Mode was not possible - Fixed that the current stock total value (header of the stock overview page) didn't include decimal amounts (thanks @Ape) @@ -50,6 +52,7 @@ ### Shopping list improvements/fixes - The amount now defaults to `1` for adding items quicker - Added a status filter for only _done_ items +- The total value is now also shown (based on "Last price (Total)" per item, displayed on the page header and only when `FEATURE_FLAG_STOCK_PRICE_TRACKING` is enabled) - Fixed that shopping list prints had a grey background (thanks @Forceu) - Fixed the form validation on the shopping list item page (thanks @Forceu) - Fixed that when adding products to the shopping list from the stock overview page, the used quantity unit was always the products default purchase QU (and not the selected one) @@ -60,6 +63,7 @@ - Recipe printing improvements (thanks @Ape) - Calories are now always displayed per single serving (on the recipe and meal plan page) - The note of an ingredient will now also be added to the corresponding shopping list item when using "Put missing products on the shopping list" +- It's now possible to copy a recipe (button/dropdown menu item per recipe) - Fixed that the recipe page was slow when there were a lot meal plan recipe entries - Fixed that "Only check if any amount is in stock" (recipe ingredient option) didn't work for stock amounts < 1 - Fixed that when adding missing items to the shopping list, on the popup deselected items got also added @@ -72,6 +76,7 @@ - Meal plan entries can now be visually marked as "done" (new button per entry) - This happens automatically on consuming a recipe/product from the meal plan page - It's now possible to copy all entries of a day to another day (in the dropdown of the add button in the header of each day column) +- The "Display recipe" button was removed, instead clicking the recipe title now displays the recipe (and this now also works for products; shows the product card) - Fixed that stock fulfillment checking used the desired servings of the recipe (those selected on the recipes page, not them from the meal plan entry) ### Chores improvements/fixes @@ -92,6 +97,7 @@ - The username attribute is now configurable - Filtering of accounts is now possible - => See the new `LDAP*` `config.php` options +- Improved the page loading time of all journal pages (stock/chores/batteries) by adding a new date range filter - Some night mode style improvements (thanks @BlizzWave and @KTibow) - Help tooltips are now additionally also triggered by clicking on them (instead of only hovering them, which doesn't work on mobile / touch devices) - The camera barcode scanner now also supports Code 39 barcodes (used for example in Germany on pharma products (PZN)) (thanks @andreheuer) @@ -101,8 +107,9 @@ ### API improvements/fixes > ❗ Numbers are now returned as numbers (so technically without quotes around them, were strings for nearly all endpoints before - should practically be no real difference) -- Added a new API endpoint `/system/localization-strings` to get the localization strings (gettext JSON representation; in the by the user desired language) -- The `GET /chores` endpoint now also returns the `next_execution_assigned_user` per chore (like the endpoint `GET /chores​/{choreId}` already did for a single chore) +- Added a new endpoint `/system/localization-strings` to get the localization strings (gettext JSON representation; in the by the user desired language) +- Added a new endpoint `/recipes/{recipeId}/copy` to copy a recipe +- The `GET /chores` endpoint now also returns the `next_execution_assigned_user` object per chore (like the endpoint `GET /chores​/{choreId}` already did for a single chore) - The `GET /tasks` endpoint now also returns the assigned user and category object per task - Empty Userfields are now also returned (were previously omitted, endpoint `GET /objects/{entity}` and `GET /objects/{entity}/{objectId}`) - Fixed that due soon products with `due_type` = "Expiration date" were missing in `due_products` of the `/stock/volatile` endpoint diff --git a/config-dist.php b/config-dist.php index 1af2a20f..15b8cff3 100644 --- a/config-dist.php +++ b/config-dist.php @@ -148,6 +148,7 @@ DefaultUserSetting('product_presets_product_group_id', -1); // Default product g DefaultUserSetting('product_presets_qu_id', -1); // Default quantity unit id for new products (-1 means no quantity unit is preset) DefaultUserSetting('stock_decimal_places_amounts', 4); // Default decimal places allowed for amounts DefaultUserSetting('stock_decimal_places_prices', 2); // Default decimal places allowed for prices +DefaultUserSetting('stock_auto_decimal_separator_prices', false); DefaultUserSetting('stock_due_soon_days', 5); DefaultUserSetting('stock_default_purchase_amount', 0); DefaultUserSetting('stock_default_consume_amount', 1); @@ -204,7 +205,7 @@ Setting('FEATURE_FLAG_TASKS', true); Setting('FEATURE_FLAG_BATTERIES', true); Setting('FEATURE_FLAG_EQUIPMENT', true); Setting('FEATURE_FLAG_CALENDAR', true); -Setting('FEATURE_FLAG_LABELPRINTER', false); +Setting('FEATURE_FLAG_LABEL_PRINTER', false); // Sub feature flags Setting('FEATURE_FLAG_STOCK_PRICE_TRACKING', true); diff --git a/controllers/BaseController.php b/controllers/BaseController.php index 309ed626..7d76c23b 100644 --- a/controllers/BaseController.php +++ b/controllers/BaseController.php @@ -210,7 +210,8 @@ class BaseController { $htmlPurifierConfig = \HTMLPurifier_Config::createDefault(); $htmlPurifierConfig->set('Cache.SerializerPath', GROCY_DATAPATH . '/viewcache'); - $htmlPurifierConfig->set('HTML.Allowed', 'div,b,strong,i,em,u,a[href|title|target],iframe[src|width|height|frameborder],ul,ol,li,p[style],br,span[style],img[width|height|alt|src],table[border|width|style],tbody,tr,td,th,blockquote'); + $htmlPurifierConfig->set('HTML.Allowed', 'div,b,strong,i,em,u,a[href|title|target],iframe[src|width|height|frameborder],ul,ol,li,p[style],br,span[style],img[width|height|alt|src],table[border|width|style],tbody,tr,td,th,blockquote,*[style|class|id]'); + $htmlPurifierConfig->set('Attr.EnableID', true); $htmlPurifierConfig->set('HTML.SafeIframe', true); $htmlPurifierConfig->set('CSS.AllowedProperties', 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align'); $htmlPurifierConfig->set('URI.AllowedSchemes', ['data' => true, 'http' => true, 'https' => true]); diff --git a/controllers/BatteriesApiController.php b/controllers/BatteriesApiController.php index 163b45b3..5640d78e 100644 --- a/controllers/BatteriesApiController.php +++ b/controllers/BatteriesApiController.php @@ -3,6 +3,8 @@ namespace Grocy\Controllers; use Grocy\Controllers\Users\User; +use Grocy\Helpers\WebhookRunner; +use Grocy\Helpers\Grocycode; class BatteriesApiController extends BaseApiController { @@ -62,6 +64,30 @@ class BatteriesApiController extends BaseApiController } } + public function BatteryPrintLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $battery = $this->getDatabase()->batteries()->where('id', $args['batteryId'])->fetch(); + + $webhookData = array_merge([ + 'battery' => $battery->name, + 'grocycode' => (string)(new Grocycode(Grocycode::BATTERY, $args['batteryId'])), + ], GROCY_LABEL_PRINTER_PARAMS); + + if (GROCY_LABEL_PRINTER_RUN_SERVER) + { + (new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON); + } + + return $this->ApiResponse($response, $webhookData); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + public function __construct(\DI\Container $container) { parent::__construct($container); diff --git a/controllers/BatteriesController.php b/controllers/BatteriesController.php index 2a7eb0d4..7b413a50 100644 --- a/controllers/BatteriesController.php +++ b/controllers/BatteriesController.php @@ -2,6 +2,10 @@ namespace Grocy\Controllers; +use Grocy\Helpers\Grocycode; +use jucksearm\barcode\lib\BarcodeFactory; +use jucksearm\barcode\lib\DatamatrixFactory; + class BatteriesController extends BaseController { public function BatteriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -48,8 +52,25 @@ class BatteriesController extends BaseController public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { + if (isset($request->getQueryParams()['months']) && filter_var($request->getQueryParams()['months'], FILTER_VALIDATE_INT) !== false) + { + $months = $request->getQueryParams()['months']; + $where = "tracked_time > DATE(DATE('now', 'localtime'), '-$months months')"; + } + else + { + // Default 2 years + $where = "tracked_time > DATE(DATE('now', 'localtime'), '-24 months')"; + } + + if (isset($request->getQueryParams()['battery']) && filter_var($request->getQueryParams()['battery'], FILTER_VALIDATE_INT) !== false) + { + $batteryId = $request->getQueryParams()['battery']; + $where .= " AND battery_id = $batteryId"; + } + return $this->renderPage($response, 'batteriesjournal', [ - 'chargeCycles' => $this->getDatabase()->battery_charge_cycles()->orderBy('tracked_time', 'DESC'), + 'chargeCycles' => $this->getDatabase()->battery_charge_cycles()->where($where)->orderBy('tracked_time', 'DESC'), 'batteries' => $this->getDatabase()->batteries()->where('active = 1')->orderBy('name', 'COLLATE NOCASE') ]); } @@ -75,6 +96,40 @@ class BatteriesController extends BaseController ]); } + public function BatteryGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $size = $request->getQueryParam('size', null); + $gc = new Grocycode(Grocycode::BATTERY, $args['batteryId']); + + if (GROCY_GROCYCODE_TYPE == '2D') + { + $png = (new DatamatrixFactory())->setCode((string) $gc)->setSize($size)->getDatamatrixPngData(); + } + else + { + $png = (new BarcodeFactory())->setType('C128')->setCode((string) $gc)->setHeight($size)->getBarcodePngData(); + } + + $isDownload = $request->getQueryParam('download', false); + if ($isDownload) + { + $response = $response->withHeader('Content-Type', 'application/octet-stream') + ->withHeader('Content-Disposition', 'attachment; filename=grocycode.png') + ->withHeader('Content-Length', strlen($png)) + ->withHeader('Cache-Control', 'no-cache') + ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT'); + } + else + { + $response = $response->withHeader('Content-Type', 'image/png') + ->withHeader('Content-Length', strlen($png)) + ->withHeader('Cache-Control', 'no-cache') + ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT'); + } + $response->getBody()->write($png); + return $response; + } + public function __construct(\DI\Container $container) { parent::__construct($container); diff --git a/controllers/ChoresApiController.php b/controllers/ChoresApiController.php index d9396467..f782977d 100644 --- a/controllers/ChoresApiController.php +++ b/controllers/ChoresApiController.php @@ -3,6 +3,8 @@ namespace Grocy\Controllers; use Grocy\Controllers\Users\User; +use Grocy\Helpers\WebhookRunner; +use Grocy\Helpers\Grocycode; class ChoresApiController extends BaseApiController { @@ -108,6 +110,30 @@ class ChoresApiController extends BaseApiController } } + public function ChorePrintLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $chore = $this->getDatabase()->chores()->where('id', $args['choreId'])->fetch(); + + $webhookData = array_merge([ + 'chore' => $chore->name, + 'grocycode' => (string)(new Grocycode(Grocycode::CHORE, $args['choreId'])), + ], GROCY_LABEL_PRINTER_PARAMS); + + if (GROCY_LABEL_PRINTER_RUN_SERVER) + { + (new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON); + } + + return $this->ApiResponse($response, $webhookData); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + public function __construct(\DI\Container $container) { parent::__construct($container); diff --git a/controllers/ChoresController.php b/controllers/ChoresController.php index 9a8e621a..f2dd5803 100644 --- a/controllers/ChoresController.php +++ b/controllers/ChoresController.php @@ -2,6 +2,10 @@ namespace Grocy\Controllers; +use Grocy\Helpers\Grocycode; +use jucksearm\barcode\lib\BarcodeFactory; +use jucksearm\barcode\lib\DatamatrixFactory; + class ChoresController extends BaseController { public function ChoreEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -59,8 +63,25 @@ class ChoresController extends BaseController public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { + if (isset($request->getQueryParams()['months']) && filter_var($request->getQueryParams()['months'], FILTER_VALIDATE_INT) !== false) + { + $months = $request->getQueryParams()['months']; + $where = "tracked_time > DATE(DATE('now', 'localtime'), '-$months months')"; + } + else + { + // Default 1 year + $where = "tracked_time > DATE(DATE('now', 'localtime'), '-12 months')"; + } + + if (isset($request->getQueryParams()['chore']) && filter_var($request->getQueryParams()['chore'], FILTER_VALIDATE_INT) !== false) + { + $choreId = $request->getQueryParams()['chore']; + $where .= " AND chore_id = $choreId"; + } + return $this->renderPage($response, 'choresjournal', [ - 'choresLog' => $this->getDatabase()->chores_log()->orderBy('tracked_time', 'DESC'), + 'choresLog' => $this->getDatabase()->chores_log()->where($where)->orderBy('tracked_time', 'DESC'), 'chores' => $this->getDatabase()->chores()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'users' => $this->getDatabase()->users()->orderBy('username'), 'userfields' => $this->getUserfieldsService()->GetFields('chores_log'), @@ -92,6 +113,40 @@ class ChoresController extends BaseController ]); } + public function ChoreGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $size = $request->getQueryParam('size', null); + $gc = new Grocycode(Grocycode::CHORE, $args['choreId']); + + if (GROCY_GROCYCODE_TYPE == '2D') + { + $png = (new DatamatrixFactory())->setCode((string) $gc)->setSize($size)->getDatamatrixPngData(); + } + else + { + $png = (new BarcodeFactory())->setType('C128')->setCode((string) $gc)->setHeight($size)->getBarcodePngData(); + } + + $isDownload = $request->getQueryParam('download', false); + if ($isDownload) + { + $response = $response->withHeader('Content-Type', 'application/octet-stream') + ->withHeader('Content-Disposition', 'attachment; filename=grocycode.png') + ->withHeader('Content-Length', strlen($png)) + ->withHeader('Cache-Control', 'no-cache') + ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT'); + } + else + { + $response = $response->withHeader('Content-Type', 'image/png') + ->withHeader('Content-Length', strlen($png)) + ->withHeader('Cache-Control', 'no-cache') + ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT'); + } + $response->getBody()->write($png); + return $response; + } + public function __construct(\DI\Container $container) { parent::__construct($container); diff --git a/controllers/RecipesApiController.php b/controllers/RecipesApiController.php index cc89b57a..46091c84 100644 --- a/controllers/RecipesApiController.php +++ b/controllers/RecipesApiController.php @@ -63,6 +63,20 @@ class RecipesApiController extends BaseApiController } } + public function CopyRecipe(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + return $this->ApiResponse($response, [ + 'created_object_id' => $this->getRecipesService()->CopyRecipe($args['recipeId']) + ]); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + public function __construct(\DI\Container $container) { parent::__construct($container); diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index 1fd36102..0f61bf49 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -620,42 +620,56 @@ class StockApiController extends BaseApiController public function ProductPrintLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - $product = $this->getDatabase()->products()->where('id', $args['productId'])->fetch(); - - $webhookData = array_merge([ - 'product' => $product->name, - 'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $product->id)), - ], GROCY_LABEL_PRINTER_PARAMS); - - if (GROCY_LABEL_PRINTER_RUN_SERVER) + try { - (new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON); - } + $product = $this->getDatabase()->products()->where('id', $args['productId'])->fetch(); - return $this->ApiResponse($response, $webhookData); + $webhookData = array_merge([ + 'product' => $product->name, + 'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $product->id)), + ], GROCY_LABEL_PRINTER_PARAMS); + + if (GROCY_LABEL_PRINTER_RUN_SERVER) + { + (new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON); + } + + return $this->ApiResponse($response, $webhookData); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } } public function StockEntryPrintLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - $stockEntry = $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(); - $product = $this->getDatabase()->products()->where('id', $stockEntry->product_id)->fetch(); - - $webhookData = array_merge([ - 'product' => $product->name, - 'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $stockEntry->product_id, [$stockEntry->stock_id])), - ], GROCY_LABEL_PRINTER_PARAMS); - - if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) + try { - $webhookData['duedate'] = $this->getLocalizationService()->__t('DD') . ': ' . $stockEntry->best_before_date; - } + $stockEntry = $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(); + $product = $this->getDatabase()->products()->where('id', $stockEntry->product_id)->fetch(); - if (GROCY_LABEL_PRINTER_RUN_SERVER) + $webhookData = array_merge([ + 'product' => $product->name, + 'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $stockEntry->product_id, [$stockEntry->stock_id])), + ], GROCY_LABEL_PRINTER_PARAMS); + + if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) + { + $webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $stockEntry->best_before_date; + } + + if (GROCY_LABEL_PRINTER_RUN_SERVER) + { + (new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON); + } + + return $this->ApiResponse($response, $webhookData); + } + catch (\Exception $ex) { - (new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON); + return $this->GenericErrorResponse($response, $ex->getMessage()); } - - return $this->ApiResponse($response, $webhookData); } public function RemoveProductFromShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) diff --git a/controllers/StockController.php b/controllers/StockController.php index 64e6c801..f5bc45f6 100644 --- a/controllers/StockController.php +++ b/controllers/StockController.php @@ -35,10 +35,27 @@ class StockController extends BaseController public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { + if (isset($request->getQueryParams()['months']) && filter_var($request->getQueryParams()['months'], FILTER_VALIDATE_INT) !== false) + { + $months = $request->getQueryParams()['months']; + $where = "row_created_timestamp > DATE(DATE('now', 'localtime'), '-$months months')"; + } + else + { + // Default 6 months + $where = "row_created_timestamp > DATE(DATE('now', 'localtime'), '-6 months')"; + } + + if (isset($request->getQueryParams()['product']) && filter_var($request->getQueryParams()['product'], FILTER_VALIDATE_INT) !== false) + { + $productId = $request->getQueryParams()['product']; + $where .= " AND product_id = $productId"; + } + $usersService = $this->getUsersService(); return $this->renderPage($response, 'stockjournal', [ - 'stockLog' => $this->getDatabase()->uihelper_stock_journal()->orderBy('row_created_timestamp', 'DESC'), + 'stockLog' => $this->getDatabase()->uihelper_stock_journal()->where($where)->orderBy('row_created_timestamp', 'DESC'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), 'users' => $usersService->GetUsersAsDto(), @@ -176,9 +193,7 @@ class StockController extends BaseController public function ProductGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { $size = $request->getQueryParam('size', null); - $product = $this->getDatabase()->products($args['productId']); - - $gc = new Grocycode(Grocycode::PRODUCT, $product->id); + $gc = new Grocycode(Grocycode::PRODUCT, $args['productId']); if (GROCY_GROCYCODE_TYPE == '2D') { @@ -190,7 +205,6 @@ class StockController extends BaseController } $isDownload = $request->getQueryParam('download', false); - if ($isDownload) { $response = $response->withHeader('Content-Type', 'application/octet-stream') @@ -489,7 +503,6 @@ class StockController extends BaseController } $isDownload = $request->getQueryParam('download', false); - if ($isDownload) { $response = $response->withHeader('Content-Type', 'application/octet-stream') diff --git a/docs/label-printing.md b/docs/label-printing.md index 99969739..c1972754 100644 --- a/docs/label-printing.md +++ b/docs/label-printing.md @@ -1,7 +1,7 @@ Label printing ==== -To enable label printing, set `FEATURE_FLAG_LABELPRINTER` to `true`in your `config.php`. You also need to provide a webhook target that is responsible for printing. +To enable label printing, set `FEATURE_FLAG_LABEL_PRINTER` to `true`in your `config.php`. You also need to provide a webhook target that is responsible for printing. Why webhook? --- @@ -28,7 +28,7 @@ Both methods fire this request upon printing: ``` POST /your/printing/api/endpoint HTTP/1.1 -product=&grocycode=grocy:x:xxx&duedate=DD:%2021-06-09&... +product=&grocycode=grocy:x:xxx&due_date=DD:%2021-06-09&... ``` @@ -37,4 +37,4 @@ If specified, the request body may also be JSON encoded, however the fields stay Additional POST parameters (like the font to use) may be supplied in `config.php`. Keep in mind that these config values will be distributed to all clients on all requests if the webhook is configured to run client-side. -The webhook receiver is required to layout and print the resulting label. \ No newline at end of file +The webhook receiver is required to layout and print the resulting label. diff --git a/grocy.openapi.json b/grocy.openapi.json index 8d8aeccc..e94b0cb3 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -1555,7 +1555,7 @@ }, "/stock/entry/{entryId}/printlabel": { "get": { - "summary": "Prints the label of the given stock entry", + "summary": "Prints the grocycode / stock entry label of the given entry on the configured label printer", "tags": [ "Stock" ], @@ -2285,7 +2285,7 @@ }, "/stock/products/{productId}/printlabel": { "get": { - "summary": "Prints the product label of the given product", + "summary": "Prints the grocycode label of the given product on the configured label printer", "tags": [ "Stock" ], @@ -3462,6 +3462,54 @@ } } }, + "/recipes/{recipeId}/copy": { + "post": { + "summary": "Copies a recipe", + "tags": [ + "Recipes" + ], + "parameters": [ + { + "in": "path", + "name": "recipeId", + "required": true, + "description": "A valid recipe id of the recipe to copy", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "The operation was successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "created_object_id": { + "type": "number", + "format": "integer", + "description": "The id of the created recipe" + } + } + } + } + } + }, + "400": { + "description": "The operation was not successful (possible errors are: Invalid recipe id)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error400" + } + } + } + } + } + } + }, "/chores": { "get": { "summary": "Returns all chores incl. the next estimated execution time per chore", @@ -3688,6 +3736,48 @@ } } }, + "/chores/{choreId}/printlabel": { + "get": { + "summary": "Prints the grocycode label of the given chore on the configured label printer", + "tags": [ + "Chores" + ], + "parameters": [ + { + "in": "path", + "name": "choreId", + "required": true, + "description": "A valid chore id", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "The operation was successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "WebHook data" + } + } + } + }, + "400": { + "description": "The operation was not successful (possible errors are: Not existing chore, error on WebHook execution)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error400" + } + } + } + } + } + } + }, "/batteries": { "get": { "summary": "Returns all batteries incl. the next estimated charge time per battery", @@ -3868,6 +3958,48 @@ } } }, + "/batteries/{batteryId}/printlabel": { + "get": { + "summary": "Prints the grocycode label of the given battery on the configured label printer", + "tags": [ + "Batteries" + ], + "parameters": [ + { + "in": "path", + "name": "batteryId", + "required": true, + "description": "A valid battery id", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "The operation was successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "WebHook data" + } + } + } + }, + "400": { + "description": "The operation was not successful (possible errors are: Not existing battery, error on WebHook execution)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error400" + } + } + } + } + } + } + }, "/tasks": { "get": { "summary": "Returns all tasks which are not done yet", diff --git a/localization/de/chore_assignment_types.po b/localization/de/chore_assignment_types.po index 62cd7c40..f8875ce1 100644 --- a/localization/de/chore_assignment_types.po +++ b/localization/de/chore_assignment_types.po @@ -1,7 +1,6 @@ # # Translators: -# Bernd Bestel , 2019 -# @RubenKelevra , 2021 +# Bernd Bestel , 2021 # msgid "" msgstr "" @@ -9,7 +8,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-05-01T17:59:17+00:00\n" "PO-Revision-Date: 2019-09-17 10:45+0000\n" -"Last-Translator: @RubenKelevra , 2021\n" +"Last-Translator: Bernd Bestel , 2021\n" "Language-Team: German (https://www.transifex.com/grocy/teams/93189/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,7 +18,7 @@ msgstr "" "X-Domain: grocy/chore_assignment_types\n" msgid "no-assignment" -msgstr "keine Zuweisung" +msgstr "Niemandem zuweisen" msgid "who-least-did-first" msgstr "Wer es am seltensten gemacht hat zuerst" diff --git a/localization/de/chore_period_types.po b/localization/de/chore_period_types.po index a900b06c..4aaef317 100644 --- a/localization/de/chore_period_types.po +++ b/localization/de/chore_period_types.po @@ -1,7 +1,6 @@ # # Translators: -# Bernd Bestel , 2019 -# @RubenKelevra , 2021 +# Bernd Bestel , 2021 # msgid "" msgstr "" @@ -9,7 +8,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-05-01T17:59:17+00:00\n" "PO-Revision-Date: 2019-05-01 17:42+0000\n" -"Last-Translator: @RubenKelevra , 2021\n" +"Last-Translator: Bernd Bestel , 2021\n" "Language-Team: German (https://www.transifex.com/grocy/teams/93189/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,7 +21,7 @@ msgid "manually" msgstr "Manuell" msgid "dynamic-regular" -msgstr "Dynamisch-Regelmäßig" +msgstr "Dynamisch regelmäßig" msgid "daily" msgstr "Täglich" diff --git a/localization/de/demo_data.po b/localization/de/demo_data.po index eb31de59..25120863 100644 --- a/localization/de/demo_data.po +++ b/localization/de/demo_data.po @@ -1,7 +1,6 @@ # # Translators: -# Bernd Bestel , 2020 -# @RubenKelevra , 2021 +# Bernd Bestel , 2021 # msgid "" msgstr "" @@ -9,7 +8,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-05-01T17:59:17+00:00\n" "PO-Revision-Date: 2019-05-01 17:42+0000\n" -"Last-Translator: @RubenKelevra , 2021\n" +"Last-Translator: Bernd Bestel , 2021\n" "Language-Team: German (https://www.transifex.com/grocy/teams/93189/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,7 +18,7 @@ msgstr "" "X-Domain: grocy/demo_data\n" msgid "Cookies" -msgstr "Kekse" +msgstr "Cookies" msgid "Chocolate" msgstr "Schokolade" @@ -91,7 +90,7 @@ msgid "Cheese" msgstr "Käse" msgid "Cold cuts" -msgstr "Wurst-Aufschnitt" +msgstr "Aufschnitt" msgid "Paprika" msgstr "Paprika" @@ -115,19 +114,19 @@ msgid "Warranty ends" msgstr "Garantie endet" msgid "TV remote control" -msgstr "TV-Fernbedienung" +msgstr "TV Fernbedienung" msgid "Alarm clock" msgstr "Wecker" msgid "Heat remote control" -msgstr "Fernbedienung der Heizung" +msgstr "Fernbedienung Heizung" msgid "Lawn mowed in the garden" msgstr "Rasen im Garten gemäht" msgid "Some good snacks" -msgstr "Gutes Knabberzeug" +msgstr "Paar gute Snacks" msgid "Pizza dough" msgstr "Pizzateig" @@ -163,10 +162,10 @@ msgid "Italian" msgstr "Italienisch" msgid "This is the note content of the recipe ingredient" -msgstr "Dies ist die Notiz zur Zutat" +msgstr "Dies ist der Inhalt der Notiz der Zutat" msgid "Demo User" -msgstr "Demo-Benutzer" +msgstr "Demo Benutzer" msgid "Gram" msgid_plural "Grams" @@ -198,14 +197,13 @@ msgid "Fork and improve grocy" msgstr "grocy forken und verbessern" msgid "Find a solution for what to do when I forget the door keys" -msgstr "" -"Eine Lösung finden, was zu tun ist, wenn ich die Türschlüssel vergesse" +msgstr "Eine Lösung für \"Haustürschlüssel vergessen\" finden" msgid "Sweets" msgstr "Süßigkeiten" msgid "Bakery products" -msgstr "Bäckerei-Produkte" +msgstr "Bäckerei Produkte" msgid "Tinned food" msgstr "Konservern" @@ -214,7 +212,7 @@ msgid "Butchery products" msgstr "Metzgerei" msgid "Vegetables/Fruits" -msgstr "Obst und Gemüse" +msgstr "Obst/Gemüse" msgid "Refrigerated products" msgstr "Kühlregal" @@ -294,7 +292,7 @@ msgstr[0] "Scheibe" msgstr[1] "Scheiben" msgid "Example userentity" -msgstr "Beispiel-Benutzerentität" +msgstr "Beispiel Benutzerentität" msgid "This is an example user entity..." msgstr "Dies ist eine Beispiel-Benutzerentität" @@ -303,7 +301,7 @@ msgid "Custom field" msgstr "Benutzerdefiniertes Feld" msgid "Example field value..." -msgstr "Beispiel-Feldwert..." +msgstr "Beispiel Feldwert..." msgid "Waffle rolls" msgstr "Waffelröllchen" @@ -330,7 +328,7 @@ msgid "current release" msgstr "aktuelles Release" msgid "not yet released" -msgstr "noch nicht erschienen" +msgstr "noch nicht freigegeben" msgid "Portuguese (Brazil)" msgstr "Portugiesisch (Brasilien)" diff --git a/localization/de/permissions.po b/localization/de/permissions.po index 12a866d3..c18e8bcc 100644 --- a/localization/de/permissions.po +++ b/localization/de/permissions.po @@ -1,7 +1,7 @@ # # Translators: -# Bernd Bestel , 2020 # @RubenKelevra , 2021 +# Bernd Bestel , 2021 # msgid "" msgstr "" @@ -9,7 +9,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-05-01T17:59:17+00:00\n" "PO-Revision-Date: 2020-08-29 16:33+0000\n" -"Last-Translator: @RubenKelevra , 2021\n" +"Last-Translator: Bernd Bestel , 2021\n" "Language-Team: German (https://www.transifex.com/grocy/teams/93189/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -36,23 +36,23 @@ msgstr "Benutzer anzeigen" # Edit own user data / change own password msgid "USERS_EDIT_SELF" -msgstr "Eigene Benutzerdaten bearbeiten und eigenes Passwort ändern" +msgstr "Eigene Benutzerdaten bearbeiten / eigenes Passwort ändern" # Undo charge cycle msgid "BATTERIES_UNDO_CHARGE_CYCLE" -msgstr "Akku-Ladezyklus rückgängig machen" +msgstr "Ladezyklus rückgängig machen" # Track charge cycle msgid "BATTERIES_TRACK_CHARGE_CYCLE" -msgstr "Akku-Ladezyklus erfassen" +msgstr "Ladezyklus erfassen" # Track execution msgid "CHORE_TRACK_EXECUTION" -msgstr "Hausarbeit-Ausführung erfassen" +msgstr "Ausführung erfassen" # Undo execution msgid "CHORE_UNDO_EXECUTION" -msgstr "Hausarbeit-Ausführung rückgängig machen" +msgstr "Ausführung rückgängig machen" # Edit master data msgid "MASTER_DATA_EDIT" @@ -60,15 +60,15 @@ msgstr "Stammdaten bearbeiten" # Undo execution msgid "TASKS_UNDO_EXECUTION" -msgstr "Aufgabe-Ausführung rückgängig machen" +msgstr "Ausführung rückgängig machen" # Mark completed msgid "TASKS_MARK_COMPLETED" -msgstr "Aufgabe als erledigt markieren" +msgstr "Als erledigt markieren" # Edit stock entries msgid "STOCK_EDIT" -msgstr "Lagerbestand bearbeiten" +msgstr "Bestandseinträge bearbeiten" # Transfer msgid "STOCK_TRANSFER" @@ -92,11 +92,11 @@ msgstr "Einkauf" # Add items msgid "SHOPPINGLIST_ITEMS_ADD" -msgstr "Einträge zur Einkaufsliste hinzufügen" +msgstr "Eintrag hinzufügen" # Remove items msgid "SHOPPINGLIST_ITEMS_DELETE" -msgstr "Einträge von Einkaufsliste entfernen" +msgstr "Eintrag entfernen" # User management msgid "USERS" @@ -104,11 +104,11 @@ msgstr "Benutzerverwaltung" # Stock msgid "STOCK" -msgstr "Lager" +msgstr "Bestand" # Shopping list msgid "SHOPPINGLIST" -msgstr "Einkaufsliste" +msgstr "Einkaufszettel" # Chores msgid "CHORES" @@ -116,7 +116,7 @@ msgstr "Hausarbeiten" # Batteries msgid "BATTERIES" -msgstr "Akkus" +msgstr "Batterien" # Tasks msgid "TASKS" @@ -128,7 +128,7 @@ msgstr "Rezepte" # Equipment msgid "EQUIPMENT" -msgstr "Haushaltsgeräte" +msgstr "Ausstattung" # Calendar msgid "CALENDAR" diff --git a/localization/de/strings.po b/localization/de/strings.po index 87bcd887..21aebd57 100644 --- a/localization/de/strings.po +++ b/localization/de/strings.po @@ -2,7 +2,6 @@ # Translators: # Luca RHK , 2020 # Tobias Wolter , 2020 -# Jes App , 2021 # @RubenKelevra , 2021 # Bernd Bestel , 2021 # @@ -22,12 +21,12 @@ msgstr "" "X-Domain: grocy/strings\n" msgid "Stock overview" -msgstr "Vorrat" +msgstr "Bestand" msgid "%s product expires" msgid_plural "%s products expiring" -msgstr[0] "%s Produkt verdirbt" -msgstr[1] "%s Produkte verderben" +msgstr[0] "%s läuft ab" +msgstr[1] "%s Produkte laufen ab" msgid "within the next day" msgid_plural "within the next %s days" @@ -41,13 +40,13 @@ msgstr[1] "%s Produkte sind bereits abgelaufen" msgid "%s product is overdue" msgid_plural "%s products are overdue" -msgstr[0] "%s Produkt ist abgelaufen" -msgstr[1] "%s Produkte sind abgelaufen" +msgstr[0] "%s Produkt ist überfällig" +msgstr[1] "%s Produkte sind überfällig" msgid "%s product is below defined min. stock amount" msgid_plural "%s products are below defined min. stock amount" -msgstr[0] "%s Produkt ist unter Mindestvorrat" -msgstr[1] "%s Produkte sind unter Mindestvorrat" +msgstr[0] "%s Produkt ist unter Mindestbestand" +msgstr[1] "%s Produkte sind unter Mindestbestand" msgid "Product" msgstr "Produkt" @@ -67,7 +66,7 @@ msgid "Chores overview" msgstr "Hausarbeiten" msgid "Batteries overview" -msgstr "Akkus" +msgstr "Batterien" msgid "Purchase" msgstr "Einkauf" @@ -79,13 +78,13 @@ msgid "Inventory" msgstr "Inventur" msgid "Shopping list" -msgstr "Einkaufslisten" +msgstr "Einkaufszettel" msgid "Chore tracking" -msgstr "Hausarbeit nachtragen" +msgstr "Hausarbeit-Ausführung" msgid "Battery tracking" -msgstr "Akkuladung nachtragen" +msgstr "Batterie-Ladezyklus" msgid "Locations" msgstr "Standorte" @@ -100,40 +99,40 @@ msgid "Chores" msgstr "Hausarbeiten" msgid "Batteries" -msgstr "Akkus" +msgstr "Batterien" msgid "Chore" msgstr "Hausarbeit" msgid "Next estimated tracking" -msgstr "Nächste geschätzte Ausführung" +msgstr "Nächste geplante Ausführung" msgid "Last tracked" msgstr "Zuletzt ausgeführt" msgid "Battery" -msgstr "Akku" +msgstr "Batterie" msgid "Last charged" msgstr "Zuletzt geladen" msgid "Next planned charge cycle" -msgstr "Nächstes geplantes Laden" +msgstr "Nächster geplanter Ladezyklus" msgid "Best before" -msgstr "Mindesthaltbarkeits-Datum" +msgstr "MHD" msgid "OK" -msgstr "Ok" +msgstr "OK" msgid "Product overview" -msgstr "Produkt-Übersicht" +msgstr "Produktübersicht" msgid "Stock quantity unit" -msgstr "Mengeneinheit im Vorrat" +msgstr "Mengeneinheit Bestand" msgid "Stock amount" -msgstr "Menge im Vorrat" +msgstr "Bestand" msgid "Last purchased" msgstr "Zuletzt gekauft" @@ -163,22 +162,22 @@ msgid "Tracked time" msgstr "Ausführungszeit" msgid "Chore overview" -msgstr "Hausarbeit-Übersicht" +msgstr "Hausarbeit Übersicht" msgid "Tracked count" msgstr "Ausführungsanzahl" msgid "Battery overview" -msgstr "Akkus" +msgstr "Batterie Übersicht" msgid "Charge cycles count" msgstr "Ladezyklen" msgid "Create shopping list item" -msgstr "Einkaufslisten-Eintrag hinzufügen" +msgstr "Einkaufszettel Eintrag erstellen" msgid "Edit shopping list item" -msgstr "Einkaufslisten-Eintrag ändern" +msgstr "Einkaufszettel Eintrag bearbeiten" msgid "Save" msgstr "Speichern" @@ -193,7 +192,7 @@ msgid "Location" msgstr "Standort" msgid "Min. stock amount" -msgstr "Mindestvorrat" +msgstr "Mindestbestand" msgid "Description" msgstr "Beschreibung" @@ -205,19 +204,19 @@ msgid "Barcode(s)" msgstr "Barcode(s)" msgid "Minimum stock amount" -msgstr "Mindestvorrat" +msgstr "Mindestbestand" msgid "Default best before days" msgstr "Standard-Haltbarkeit in Tagen" msgid "Default quantity unit purchase" -msgstr "Standard-Mengeneinheit beim Einkauf" +msgstr "Standard Mengeneinheit Einkauf" msgid "Quantity unit stock" -msgstr "Mengeneinheit im Vorrat" +msgstr "Mengeneinheit Bestand" msgid "Factor purchase to stock quantity unit" -msgstr "Faktor Mengeneinheit beim Einkauf zu Mengeneinheit im Vorrat" +msgstr "Faktor Mengeneinheit Einkauf zu Mengeneinheit Bestand" msgid "Create location" msgstr "Standort erstellen" @@ -229,10 +228,10 @@ msgid "Create quantity unit" msgstr "Mengeneinheit erstellen" msgid "Period type" -msgstr "Wiederholungsmodus" +msgstr "Periodentyp" msgid "Period days" -msgstr "Tage zwschen Wiederholungen" +msgstr "Tage/Periode" msgid "Create chore" msgstr "Hausarbeit erstellen" @@ -241,10 +240,10 @@ msgid "Used in" msgstr "Benutzt in" msgid "Create battery" -msgstr "Akku erstellen" +msgstr "Batterie erstellen" msgid "Edit battery" -msgstr "Akku bearbeiten" +msgstr "Batterie bearbeiten" msgid "Edit chore" msgstr "Hausarbeit bearbeiten" @@ -274,10 +273,10 @@ msgid "never" msgstr "nie" msgid "Add products that are below defined min. stock amount" -msgstr "Produkte unter Mindestvorrat hinzufügen" +msgstr "Produkte unter Mindestbestand hinzufügen" msgid "This means 1 %1$s purchased will be converted into %2$s %3$s in stock" -msgstr "Das bedeutet 1 %1$s im Einkauf entsprechen %2$s %3$s im Vorrat" +msgstr "Das bedeutet 1 %1$s im Einkauf entsprechen %2$s %3$s im Bestand" msgid "Login" msgstr "Anmelden" @@ -289,10 +288,10 @@ msgid "Password" msgstr "Passwort" msgid "Invalid credentials, please try again" -msgstr "Zugangsdaten nicht gültig, bitte versuche es erneut" +msgstr "Ungültige Zugangsdaten, bitte versuche es erneut" msgid "Are you sure to delete battery \"%s\"?" -msgstr "Sicher, dass du den Akku \"%s\" löschen möchtest?" +msgstr "Battery \"%s\" wirklich löschen?" msgid "Yes" msgstr "Ja" @@ -301,12 +300,12 @@ msgid "No" msgstr "Nein" msgid "Are you sure to delete chore \"%s\"?" -msgstr "Sicher, dass du die Hausarbeit \"%s\" löschen möchtest?" +msgstr "Hausarbeit \"%s\" wirklich löschen?" msgid "\"%s\" could not be resolved to a product, how do you want to proceed?" msgstr "" -"\"%s\" konnte nicht in der Produktdatenbank gefunden werden, wie möchtest du" -" weiter machen?" +"\"%s\" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du " +"weiter machen?" msgid "Create or assign product" msgstr "Produkt erstellen oder verknüpfen" @@ -321,25 +320,25 @@ msgid "Add as barcode to existing product" msgstr "Barcode vorhandenem Produkt zuweisen" msgid "Add as new product and prefill barcode" -msgstr "Neues Produkt erstellen und Barcode diesem hinzufügen" +msgstr "Neues Produkt erstellen und Barcode vorbelegen" msgid "Are you sure to delete quantity unit \"%s\"?" -msgstr "Sicher, dass du die Mengeneinheit \"%s\" löschen möchtest?" +msgstr "Mengeneinheit \"%s\" wirklich löschen?" msgid "Are you sure to delete product \"%s\"?" -msgstr "Sicher, dass du das Produkt \"%s\" löschen möchtest?" +msgstr "Produkt \"%s\" wirklich löschen?" msgid "Are you sure to delete location \"%s\"?" -msgstr "Sicher, dass du den Standort \"%s\" löschen möchtest?" +msgstr "Standort \"%s\" wirklich löschen?" msgid "Are you sure to delete store \"%s\"?" -msgstr "Sicher, dass du das Geschäft \"%s\" löschen möchtest?" +msgstr "Geschäft \"%s\" wirklich löschen?" msgid "Manage API keys" msgstr "API-Schlüssel verwalten" -msgid "REST API & data model documentation" -msgstr "REST-API & Datenmodell-Dokumentation" +msgid "REST API browser" +msgstr "REST API Browser" msgid "API keys" msgstr "API-Schlüssel" @@ -360,20 +359,20 @@ msgid "This product is not in stock" msgstr "Dieses Produkt ist nicht vorrätig" msgid "This means %s will be added to stock" -msgstr "Das bedeutet %s wird dem Vorrat hinzugefügt" +msgstr "Das bedeutet %s wird dem Bestand hinzugefügt" msgid "This means %s will be removed from stock" -msgstr "Das bedeutet %s wird aus dem Vorrat entfernt" +msgstr "Das bedeutet %s wird aus dem Bestand entfernt" msgid "" "This means the next execution of this chore is scheduled %s days after the " "last execution" msgstr "" -"Die erneute Ausführung der Hausarbeit wird %s Tage nach der letzten " -"Ausführung geplant" +"Das bedeutet, dass eine erneute Ausführung der Hausarbeit %s Tage nach der " +"letzten Ausführung geplant wird" msgid "Removed %1$s of %2$s from stock" -msgstr "%1$s %2$s aus dem Vorrat entfernt" +msgstr "%1$s %2$s aus dem Bestand entfernt" msgid "About grocy" msgstr "Über grocy" @@ -385,19 +384,19 @@ msgid "Released on" msgstr "Veröffentlicht am" msgid "Added %1$s of %2$s to stock" -msgstr "%1$s %2$s dem Vorrat hinzugefügt" +msgstr "%1$s %2$s dem Bestand hinzugefügt" msgid "Stock amount of %1$s is now %2$s" -msgstr "Es sind nun %1$s %2$s im Vorrat" +msgstr "Es sind nun %1$s %2$s im Bestand" msgid "Tracked execution of chore %1$s on %2$s" msgstr "Ausführung von %1$s am %2$s erfasst" msgid "Tracked charge cycle of battery %1$s on %2$s" -msgstr "Ladezyklus für Akku %1$s am %2$s erfasst" +msgstr "Ladezyklus für Batterie %1$s am %2$s erfasst" msgid "Consume all %s which are currently in stock" -msgstr "Verbrauche den kompletten Vorrat von %s" +msgstr "Verbrauche den kompletten Bestand von %s" msgid "All" msgstr "Alle" @@ -415,7 +414,7 @@ msgid "You have to select a chore" msgstr "Eine Hausarbeit muss ausgewählt werden" msgid "You have to select a battery" -msgstr "Ein Akku muss ausgewählt werden" +msgstr "Eine Batterie muss ausgewählt werden" msgid "A name is required" msgstr "Ein Name ist erforderlich" @@ -427,7 +426,7 @@ msgid "A quantity unit is required" msgstr "Eine Mengeneinheit muss ausgewählt werden" msgid "A period type is required" -msgstr "Ein Wiederholungsmodus muss ausgewählt werden" +msgstr "Eine Periodentyp muss ausgewählt werden" msgid "A best before date is required" msgstr "Ein Mindesthaltbarkeitsdatum ist erforderlich" @@ -457,36 +456,36 @@ msgid "Edit recipe ingredient" msgstr "Rezeptzutat bearbeiten" msgid "Are you sure to delete recipe \"%s\"?" -msgstr "Sicher, dass du das Rezept \"%s\" löschen möchtest?" +msgstr "Rezept \"%s\" wirklich löschen?" msgid "Are you sure to delete recipe ingredient \"%s\"?" -msgstr "Sicher, dass du die Rezept-Zutat \"%s\" löschen möchtest?" +msgstr "Rezeptzutat \"%s\" wirklich löschen?" msgid "Are you sure to empty shopping list \"%s\"?" -msgstr "Sicher, dass Du die Einkaufsliste \"%s\" leeren möchtest?" +msgstr " Einkaufszettel \"%s\" wirklich leeren? " msgid "Clear list" msgstr "Liste leeren" msgid "Requirements fulfilled" -msgstr "Bedarf im Vorrat" +msgstr "Bedarf im Bestand" msgid "Put missing products on shopping list" -msgstr "Fehlende Produkte auf die Einkaufsliste setzen" +msgstr "Fehlende Produkte auf den Einkaufszettel setzen" msgid "Enough in stock" -msgstr "Vorrat reicht aus" +msgstr "Bestand reicht aus" msgid "" "Not enough in stock, %s ingredient missing but already on the shopping list" msgid_plural "" "Not enough in stock, %s ingredients missing but already on the shopping list" msgstr[0] "" -"Vorrat nicht ausreichend, %s Zutat fehlt, steht aber bereits auf der " -"Einkaufsliste" +"Bestand nicht ausreichend, %s Zutat fehlt, steht aber bereits auf dem " +"Einkaufszettel" msgstr[1] "" -"Vorrat nicht ausreichend, %s Zutaten fehlen, diese stehen aber bereits auf " -"der Einkaufsliste" +"Bestand nicht ausreichend, %s Zutaten fehlen, stehen aber bereits auf dem " +"Einkaufszettel" msgid "Expand to fullscreen" msgstr "Auf ganzen Bildschirm vergrößern" @@ -502,21 +501,21 @@ msgstr "Rezept" msgid "Not enough in stock, %1$s missing, %2$s already on shopping list" msgstr "" -"Vorrat nicht ausreichend, %1$s fehlt(/en), %2$s steht(/en) bereits auf der " -"Einkaufsliste" +"Nicht ausreichend im Bestand, %1$s fehlen, %2$s stehen bereits auf dem " +"Einkaufszettel" msgid "Show notes" msgstr "Notizen anzeigen" msgid "Put missing amount on shopping list" -msgstr "Fehlende Menge auf die Einkaufsliste setzen" +msgstr "Fehlende Menge auf den Einkaufszettel setzen" msgid "" "Are you sure to put all missing ingredients for recipe \"%s\" on the " "shopping list?" msgstr "" -"Sicher, dass du alle fehlenden Zutaten für Rezept \"%s\" auf die " -"Einkaufsliste setzen möchtest?" +"Sicher alle fehlenden Zutaten für Rezept \"%s\" auf die Einkaufsliste zu " +"setzen?" msgid "Added for recipe %s" msgstr "Hinzugefügt für Rezept %s" @@ -531,7 +530,7 @@ msgid "Users" msgstr "Benutzer" msgid "Are you sure to delete user \"%s\"?" -msgstr "Sicher, dass du den Benutzer \"%s\" löschen möchtest?" +msgstr "Benutzer \"%s\" wirklich löschen?" msgid "Create user" msgstr "Benutzer erstellen" @@ -567,7 +566,7 @@ msgid "Unknown" msgstr "Unbekannt" msgid "Chores journal" -msgstr "Hausarbeiten-Protokoll" +msgstr "Hausarbeitenjournal" msgid "0 means suggestions for the next charge cycle are disabled" msgstr "" @@ -577,7 +576,7 @@ msgid "Charge cycle interval (days)" msgstr "Ladezyklusintervall (Tage)" msgid "Last price" -msgstr "Neuster Preis" +msgstr "Letzter Preis" msgid "Price history" msgstr "Preisentwicklung" @@ -608,13 +607,13 @@ msgstr[1] "%s Hausarbeiten sind überfällig" msgid "%s battery is due to be charged" msgid_plural "%s batteries are due to be charged" -msgstr[0] "%s Akku muss geladen werden" -msgstr[1] "%s Akkus müssen geladen werden" +msgstr[0] "%s Batterie muss geladen werden" +msgstr[1] "%s Batterien müssen geladen werden" msgid "%s battery is overdue to be charged" msgid_plural "%s batteries are overdue to be charged" -msgstr[0] "%s Akku ist überfällig" -msgstr[1] "%s Akkus sind überfällig" +msgstr[0] "%s Batterie ist überfällig" +msgstr[1] "%s Batterien sind überfällig" msgid "in singular form" msgstr "in der Einzahl" @@ -623,24 +622,24 @@ msgid "Quantity unit" msgstr "Mengeneinheit" msgid "Only check if any amount is in stock" -msgstr "Nur prüfen, ob eine beliebige Menge vorrätig ist" +msgstr "Nur prüfen, ob eine beliebige Menge im Bestand verfügbar ist" msgid "" "Are you sure to consume all ingredients needed by recipe \"%s\" (ingredients" " marked with \"only check if any amount is in stock\" will be ignored)?" msgstr "" "Sicher, dass alle Zutaten die vom Rezept \"%s\" benötigt werden aus dem " -"Vorrat entfernt werden sollen (Zutaten markiert mit \"Nur prüfen, ob eine " -"beliebige Menge vorrätig ist\" werden ignoriert)?" +"Bestand entfernt werden sollen (Zutaten markiert mit \"Nur prüfen, ob eine " +"beliebige Menge im Bestand verfügbar ist\" werden ignoriert)?" msgid "Removed all ingredients of recipe \"%s\" from stock" msgstr "" -"Alle Zutaten, die vom Rezept \"%s\" benötigt werden, wurden aus dem Vorrat " +"Alle Zutaten, die vom Rezept \"%s\" benötigt werden, wurden aus dem Bestand " "entfernt" msgid "Consume all ingredients needed by this recipe" msgstr "" -"Alle Zutaten, die von diesem Rezept benötigt werden, aus dem Vorrat " +"Alle Zutaten, die von diesem Rezept benötigt werden, aus dem Bestand " "entfernen" msgid "Click to show technical details" @@ -680,7 +679,7 @@ msgid "Create task" msgstr "Aufgabe erstellen" msgid "A due date is required" -msgstr "Ein Stichtag ist erforderlich" +msgstr "Ein Fälligkeitsdatum ist erforderlich" msgid "Category" msgstr "Kategorie" @@ -689,7 +688,7 @@ msgid "Edit task" msgstr "Aufgabe bearbeiten" msgid "Are you sure to delete task \"%s\"?" -msgstr "Sicher, dass du die Aufgabe \"%s\" löschen möchtest?" +msgstr "Aufgabe \"%s\" wirklich löschen?" msgid "%s task is due to be done" msgid_plural "%s tasks are due to be done" @@ -723,7 +722,7 @@ msgid "Product group" msgstr "Produktgruppe" msgid "Are you sure to delete product group \"%s\"?" -msgstr "Sicher, dass du die Produktgruppe \"%s\" löschen möchtest?" +msgstr "Produktgruppe \"%s\" wirklich löschen?" msgid "Stay logged in permanently" msgstr "Dauerhaft angemeldet bleiben" @@ -736,7 +735,7 @@ msgid "Status" msgstr "Status" msgid "Below min. stock amount" -msgstr "Unter Mindestvorrat" +msgstr "Unter Mindestbestand" msgid "Expiring soon" msgstr "Bald ablaufend" @@ -838,16 +837,16 @@ msgid "This will be used as a headline to group ingredients together" msgstr "Dies wird als Überschrift verwendet, um Zutaten zusammenzufassen" msgid "Journal" -msgstr "Protokoll" +msgstr "Journal" msgid "Stock journal" -msgstr "Protokoll des Vorrats" +msgstr "Bestandsjournal" msgid "Undone on" msgstr "Rückgängig gemacht am" msgid "Batteries journal" -msgstr "Akku-Protokoll" +msgstr "Batteriejournal" msgid "Undo charge cycle" msgstr "Ladezyklus rückgängig machen" @@ -868,26 +867,26 @@ msgid "Charge cycle successfully undone" msgstr "Ladezyklus erfolgreich rückgängig gemacht" msgid "Disable stock fulfillment checking for this ingredient" -msgstr "Vorrat für diese Zutat nicht prüfen" +msgstr "Bestandsprüfung für diese Zutat deaktivieren" msgid "Add all list items to stock" -msgstr "Alle Einträge zum Vorrat hinzufügen" +msgstr "Alle Einträge zum Bestand hinzufügen" msgid "Add this item to stock" -msgstr "Diesen Eintrag zum Vorrat hinzufügen" +msgstr "Diesen Eintrag zum Bestand hinzufügen" msgid "Adding shopping list item %1$s of %2$s" -msgstr "Bearbeite Einkaufslisten-Eintrag %1$s von %2$s" +msgstr "Bearbeite Einkausfzettel-Eintrag %1$s von %2$s" msgid "Use a specific stock item" -msgstr "Einen bestimmten Artikel aus dem Vorrat verwenden" +msgstr "Einen bestimmten Bestandseintrag verwenden" msgid "" "The first item in this list would be picked by the default rule which is " "\"Opened first, then first due first, then first in first out\"" msgstr "" "Der erste Eintrag in dieser Liste würde von der Standardregel \"Geöffnete " -"zuerst, dann zuerst ablaufende, dann älteste zuerst\" ausgewählt werden" +"zuerst, dann zuerst fällige, dann First In - First Out\" ausgewählt werden" msgid "Mark %1$s of %2$s as open" msgstr "%1$s %2$s als geöffnet markieren" @@ -908,7 +907,7 @@ msgid "%s opened" msgstr "%s geöffnet" msgid "Product due" -msgstr "Produkt läuft ab" +msgstr "Produkt fällig" msgid "Task due" msgstr "Aufgabe fällig" @@ -917,16 +916,16 @@ msgid "Chore due" msgstr "Hausarbeit fällig" msgid "Battery charge cycle due" -msgstr "Akku-Ladezyklus fällig" +msgstr "Battery-Ladezyklus fällig" msgid "Show clock in header" msgstr "Uhr in der Kopfzeile anzeigen" msgid "Stock settings" -msgstr "Vorrat-Einstellungen" +msgstr "Bestand-Einstellungen" msgid "Shopping list to stock workflow" -msgstr "\"Einkaufsliste zum Vorrat\"-Ablauf" +msgstr "Einkaufsliste -> Bestand Workflow" msgid "Skip" msgstr "Überspringen" @@ -938,7 +937,7 @@ msgid "Costs" msgstr "Kosten" msgid "Based on the prices of the last purchase per product" -msgstr "Basierend auf den Preisen des neusten Kaufs pro Produkt" +msgstr "Basierend auf den Preisen des letzten Kaufs pro Produkt" msgid "The ingredients listed here result in this amount of servings" msgstr "Die hier aufgeführten Zutaten ergeben diese Menge an Portionen" @@ -955,9 +954,9 @@ msgid "" "shopping list" msgstr "" "Standardmäßig ist die Menge, die der Einkaufsliste hinzugefügt werden soll, " -"\"benötigte Menge - Vorrat - Menge bereits auf der Einkaufsliste\" - wenn " -"dies aktiviert ist, wird nur gegen den Vorrat geprüft, nicht gegen das, was " -"bereits auf der Einkaufsliste steht" +"\"benötigte Menge - Lagerbestand - Menge bereits auf der Einkaufsliste\" - " +"wenn dies aktiviert ist, wird nur gegen den Lagerbestand geprüft, nicht " +"gegen das, was bereits auf der Einkaufsliste steht" msgid "Picture" msgstr "Bild" @@ -987,7 +986,7 @@ msgstr "" "teilen oder zu integrieren" msgid "Allow partial units in stock" -msgstr "Teilmengen im Vorrat zulassen" +msgstr "Teilmengen im Bestand zulassen" msgid "Enable tare weight handling" msgstr "Taragewichtbehandlung aktivieren" @@ -999,8 +998,8 @@ msgid "" msgstr "" "Dies ist z.B. für Mehl im Glas nützlich - beim Buchen eines Kaufs/Verbrauchs" " oder bei der Inventur musst du dann immer das gesamte Glas wiegen, die zu " -"buchende Menge wird dann automatisch basierend auf dem Vorrat und dem unten " -"definierten Eigengewicht berechnet" +"buchende Menge wird dann automatisch basierend auf dem Bestand und dem unten" +" definierten Eigengewicht berechnet" msgid "Tare weight" msgstr "Taragewicht" @@ -1028,7 +1027,7 @@ msgid "The current picture will be deleted on save" msgstr "Das aktuelle Bild wird beim Speichern gelöscht " msgid "Journal for this battery" -msgstr "Protokoll für diesen Akku" +msgstr "Journal für diese Batterie" msgid "System info" msgstr "Systeminformationen" @@ -1054,34 +1053,36 @@ msgstr "" msgid "" "This will be used as the default setting when adding this product as a " "recipe ingredient" -msgstr "Standardeinstellung wenn es als Rezeptzutat hinzugefügt wird" +msgstr "" +"Dies wird als Standardeinstellung verwendet wenn dieses Produkt als " +"Rezeptzutat hinzugefügt wird" msgid "Add item" msgstr "Eintrag hinzufügen" msgid "Selected shopping list" -msgstr "Ausgewählte Einkaufsliste" +msgstr "Ausgewählter Einkaufszettel" msgid "New shopping list" -msgstr "Neue Einkaufsliste" +msgstr "Neuer Einkaufszettel" msgid "Delete shopping list" -msgstr "Einkaufsliste löschen" +msgstr "Einkaufszettel löschen" msgid "Chores settings" msgstr "Hausarbeiten-Einstellungen" msgid "Batteries settings" -msgstr "Akku-Einstellungen" +msgstr "Batterie-Einstellungen" msgid "Tasks settings" msgstr "Aufgaben-Einstellungen" msgid "Create shopping list" -msgstr "Einkaufsliste erstellen" +msgstr "Einkaufszettel erstellen" msgid "Are you sure to delete shopping list \"%s\"?" -msgstr "Sicher, dass Du die Einkaufsliste \"%s\" löschen möchtest? " +msgstr " Einkaufszettel \"%s\" wirklich löschen? " msgid "Average shelf life" msgstr "Durchschnittliche Haltbarkeit" @@ -1175,14 +1176,14 @@ msgstr "in der Mehrzahl" msgid "Not enough in stock, %s ingredient missing" msgid_plural "Not enough in stock, %s ingredients missing" -msgstr[0] "Nicht alles vorrätig, %s Zutat fehlt" -msgstr[1] "Nicht alles vorrätig, %s Zutaten fehlen" +msgstr[0] "Nicht ausreichend im Bestand, %s Zutat fehlt" +msgstr[1] "Nicht ausreichend im Bestand, %s Zutaten fehlen" msgid "Not enough in stock, but already on the shopping list" -msgstr "Nicht ausreichend im Vorrat, steht aber bereits auf der Einkaufsliste" +msgstr "Bestand nicht ausreichend, steht aber bereits auf dem Einkaufszettel" msgid "Not enough in stock" -msgstr "Nicht ausreichend im Vorrat" +msgstr "Nicht ausreichend im Bestand" msgid "Expiring soon days" msgstr "\"Bald ablaufend\"-Tage" @@ -1203,8 +1204,8 @@ msgid "" "When this is not empty, it will be shown instead of the amount entered above" " while the amount there will still be used for stock fulfillment checking" msgstr "" -"Wenn dies nicht leer ist, wird dies anstelle der Menge oben angezeigt. Für " -"die Mengenprüfung des Vorrats wird trotzdem die normale Menge verwendet" +"Wenn dies nicht leer ist, wird dies anstelle der Menge oben angezeigt, für " +"die Bestandsprüfung wird trotzdem diese Menge verwendet" msgid "Track date only" msgstr "Nur Datum der Ausführung speichern" @@ -1258,25 +1259,25 @@ msgid "Today" msgstr "Heute" msgid "Consume %1$s of %2$s as spoiled" -msgstr "%1$s verdorbene(s) %2$saus Vorrat entfernen" +msgstr "Verbrauche %1$s %2$s als verdorben" msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed" msgstr "" "Nicht alle Zutaten, die vom Rezept \"%s\" benötigt werden, sind vorrätig, es" -" wurde nichts aus dem Vorrat entfernt" +" wurde nichts aus dem Bestand entfernt" msgid "Undo task" msgstr "Aufgabe rückgängig machen" msgid "Due date rollover" -msgstr "Stichtag hinausschieben" +msgstr "Fälligkeit hinausschieben" msgid "" "When enabled the chore can never be overdue, the due date will shift forward" " each day when due" msgstr "" -"Wenn gewählt, kann die Hausarbeit nie überfällig werden - am Stichtag wird " -"dieser ab Überfälligkeit einen Tag in die Zukunft geschoben" +"Wenn gewählt, kann die Hausarbeit nie überfällig werden - das " +"Fälligkeitsdatum wird jeden Tag ab Fälligkeit einen Tag vorgeschoben" msgid "Location Content Sheet" msgstr "Standort-Inhaltsblatt" @@ -1291,7 +1292,7 @@ msgid "" "Here you can print a page per location with the current stock, maybe to hang" " it there and note the consumed things on it" msgstr "" -"Hier kannst du eine Seite pro Standort mit dem aktuellen Vorrat drucken, " +"Hier kannst du eine Seite pro Standort mit dem aktuellen Bestand drucken, " "gedacht um diese am entsprechenden Platz aufzuhängen, damit die verbauchten " "Dinge darauf vermerkt werden können" @@ -1305,7 +1306,7 @@ msgid "Time of printing" msgstr "Druckzeitpunkt" msgid "Are you sure to delete equipment \"%s\"?" -msgstr "Sicher, dass du die Ausstattung \"%s\" löschen möchtest?" +msgstr "Ausstattung \"%s\" wirklich löschen?" msgid "Parent product" msgstr "Übergeordnetes Produkt" @@ -1327,16 +1328,16 @@ msgid "1 %s is the same as..." msgstr "1 %s entspricht..." msgid "Create QU conversion" -msgstr "Mengeneinheiten-Umrechnung erstellen" +msgstr "ME-Umrechnung erstellen" msgid "Default for QU" -msgstr "Standard für" +msgstr "Standard für ME" msgid "Quantity unit from" -msgstr "Von Mengeneinheit" +msgstr "ME von" msgid "Quantity unit to" -msgstr "Zu Mengeneinheit" +msgstr "ME nach" msgid "This cannot be equal to %s" msgstr "Dies darf nicht gleich sein wie %s" @@ -1345,7 +1346,7 @@ msgid "This means 1 %1$s is the same as %2$s %3$s" msgstr "Das bedeutet 1 %1$s entspricht %2$s %3$s" msgid "QU conversions" -msgstr "Mengeneinheiten-Umrechnung" +msgstr "ME-Umrechnungen" msgid "Product overrides" msgstr "Produktübersteuerung" @@ -1357,7 +1358,7 @@ msgid "This equals %1$s %2$s" msgstr "Dies entspricht %1$s %2$s" msgid "Edit QU conversion" -msgstr "Mengeneinheiten-Umrechnung bearbeiten" +msgstr "ME-Umrechnung bearbeiten" msgid "An assignment type is required" msgstr "Ein Zuweisungstyp ist erforderlich" @@ -1424,7 +1425,7 @@ msgstr "" msgid "%s chore is assigned to me" msgid_plural "%s chores are assigned to me" -msgstr[0] "%s Hausarbeit ist mir zugewiesen" +msgstr[0] "%s ist mir zugewiesen" msgstr[1] "%s Hausarbeiten sind mir zugewiesen" msgid "Assigned to me" @@ -1440,7 +1441,7 @@ msgid "Consume product on chore execution" msgstr "Ein Produkt bei Hausarbeit-Ausführung verbrauchen" msgid "Are you sure to delete user field \"%s\"?" -msgstr "Sicher, dass du das Benutzerfeld \"%s\" löschen möchtest?" +msgstr "Benutzerfeld \"%s\" wirklich löschen?" msgid "Userentities" msgstr "Benutzerentitäten" @@ -1461,7 +1462,7 @@ msgid "Create %s" msgstr "%s erstellen" msgid "Are you sure to delete this userobject?" -msgstr "Sicher, dass du dieses Benutzerobjekt löschen möchtest?" +msgstr "Dieses Benutzerobject wirklich löschen?" msgid "Icon CSS class" msgstr "Icon CSS-Klasse" @@ -1473,7 +1474,7 @@ msgid "Configure fields" msgstr "Felder konfigurieren" msgid "Quantity unit plural form testing" -msgstr "Mengeneinheit-Pluralformen testen" +msgstr "ME Pluralformen testen" msgid "Result" msgstr "Ergebnis" @@ -1505,10 +1506,10 @@ msgid "Search for recipes containing this product" msgstr "Suche nach Rezepten, die dieses Produkt enthalten" msgid "Add to shopping list" -msgstr "Zur Einkaufsliste hinzufügen" +msgstr "Zum Einkaufszettel hinzufügen" msgid "Added %1$s of %2$s to the shopping list \"%3$s\"" -msgstr "%1$s von %2$s wurden der Einkaufsliste \"%3$s\" hinzugefügt" +msgstr "%1$s %2$s dem Einkauszettel \"%3$s\" hinzugefügt" msgid "Output" msgstr "Ausgabe" @@ -1517,7 +1518,7 @@ msgid "Energy (kcal)" msgstr "Energie (kcal)" msgid "Per stock quantity unit" -msgstr "Pro Mengeneinheit im Vorrat" +msgstr "Pro Bestandsmengeneinheit" msgid "Barcode scanner testing" msgstr "Barcodescanner testen" @@ -1553,7 +1554,7 @@ msgstr "" "dieses" msgid "Are you sure to remove this conversion?" -msgstr "Sicher, dass du diese Umrechnung löschen möchtest?" +msgstr "Diese Umrechnung wirklich löschen?" msgid "Unit price" msgstr "Einzelpreis" @@ -1571,10 +1572,10 @@ msgid "Clear" msgstr "Löschen" msgid "Are you sure to remove the included recipe \"%s\"?" -msgstr "Sicher, dass du das enthaltene Rezept \"%s\" löschen möchtest?" +msgstr "Das enthaltene Rezept \"%s\" wirklich löschen?" msgid "Period interval" -msgstr "Wiederholungs-Intervall" +msgstr "Periodenintervall" msgid "" "This means the next execution of this chore should only be scheduled every " @@ -1630,7 +1631,7 @@ msgid "Transfered %1$s of %2$s from %3$s to %4$s" msgstr "%1$s %2$s von %3$s nach %4$s verschoben" msgid "Stock entries" -msgstr "Vorrat im Detail" +msgstr "Bestandseinträge" msgid "Best before date" msgstr "MHD" @@ -1639,16 +1640,16 @@ msgid "Purchased date" msgstr "Einkaufsdatum" msgid "Consume all %s for this stock entry" -msgstr "Verbrauche alle %s dieses Vorrats-Eintrags" +msgstr "Verbrauche alle %s dieses Bestandseintrags" msgid "The amount cannot be lower than %1$s" msgstr "Die Menge darf nicht kleiner als %1$s sein" msgid "Stock entry successfully updated" -msgstr "Vorrats-Eintrag wurde erfolgreich aktualisiert" +msgstr "Bestandseintrag wurde erfolgreich aktualisiert" msgid "Edit stock entry" -msgstr "Vorrats-Eintrag bearbeiten" +msgstr "Bestandseintrag bearbeiten" msgid "" "Camera access is only possible when supported and allowed by your browser " @@ -1674,7 +1675,7 @@ msgid "" "will be added to stock on consuming this recipe" msgstr "" "Wenn ein Produkt ausgewählt ist, wird beim Verbrauch dieses Rezeptes eine " -"Einheit (pro Portion in Mengeneinheit im Vorrat) dem Vorrat hinzugefügt" +"Einheit (pro Portion in Bestandsmengeneinheit) dem Bestand hinzugefügt" msgid "Produces product" msgstr "Produziertes Produkt" @@ -1686,7 +1687,7 @@ msgid "Booking does not exist or was already undone" msgstr "Buchung existiert nicht oder wurde bereits rückgängig gemacht" msgid "Are you sure to delete API key \"%s\"?" -msgstr "Sicher, dass du den API-Schlüssel \"%s\" löschen möchtest?" +msgstr "API-Schlüssel \"%s\" wirklich löschen?" msgid "Add note" msgstr "Notiz hinzufügen" @@ -1709,7 +1710,7 @@ msgstr "Produkt am %s hinzufügen" msgid "Consume all ingredients needed by this weeks recipes or products" msgstr "" "Alle Zutaten, die diese Woche von Rezepten oder Produkten benötigt werden, " -"aus dem Vorrat entfernen" +"aus dem Bestand entfernen" msgid "Meal plan recipe" msgstr "Speiseplan Rezept" @@ -1756,10 +1757,10 @@ msgid "Frozen" msgstr "Eingefroren" msgid "Are you sure to delete userentity \"%s\"?" -msgstr "Sicher, dass du die Benutzerentität \"%s\" löschen möchtest?" +msgstr "Benutzerentität \"%s\" wirklich löschen?" msgid "Shopping list settings" -msgstr "Einkaufslisten-Einstellungen" +msgstr "Einkaufszettel-Einstellungen" msgid "Show a month-view calendar" msgstr "Einen Kalender (Monatsansicht) anzeigen" @@ -1801,10 +1802,10 @@ msgid "Default store" msgstr "Standardgeschäft" msgid "Consume this stock entry" -msgstr "Verbrauche diesen Vorrats-Eintrag" +msgstr "Verbrauche diesen Bestandseintrag" msgid "Mark this stock entry as open" -msgstr "Diesen Vorrats-Eintrag als geöffnet markieren" +msgstr "Diesen Bestandseintrag als geöffnet markieren" msgid "Mark this item as done" msgstr "Diesen Eintrag als erledigt markieren" @@ -1817,7 +1818,7 @@ msgstr "Diesen Eintrag löschen" msgid "Show an icon if the product is already on the shopping list" msgstr "" -"Ein Symbol anzeigen, wenn das Produkt bereits auf der Einkaufsliste steht" +"Ein Symbol anzeigen, wenn das Produkt bereits auf dem Einkaufszettel steht" msgid "Calories" msgstr "Kalorien" @@ -1841,7 +1842,7 @@ msgid "Save & return to recipes" msgstr "Speichern & zurück zu Rezepte" msgid "Stock value" -msgstr "Gesamtwert des Vorrats" +msgstr "Bestandswert" msgid "Average price" msgstr "Durchschnittlicher Preis" @@ -1866,7 +1867,7 @@ msgstr "Barcode bearbeiten" msgid "Not enough in stock (not included in costs), %s ingredient missing" msgstr "" -"Nicht ausreichend im Vorrat (nicht in den Kosten enthalten), %s Zutaten " +"Nicht ausreichend im Bestand (nicht in den Kosten enthalten), %s Zutaten " "fehlen" msgid "" @@ -1874,19 +1875,19 @@ msgid "" "then first due first, then first in first out\"" msgstr "" "Basierend auf den Preisen der Standard-Verbrauchsregel \"Geöffnete zuerst, " -"dann zuerst ablaufende, dann älteste zuerst\"" +"dann zuerst fällige, dann First In - First Out\"" msgid "" "Not enough in stock (not included in costs), %1$s missing, %2$s already on " "shopping list" msgstr "" -"Nicht ausreichend im Vorrat (nicht in den Kosten enthalten), %1$s fehlen, " -"%2$s stehen bereits auf der Einkaufsliste" +"Nicht ausreichend im Bestand (nicht in den Kosten enthalten), %1$s fehlen, " +"%2$s stehen bereits auf dem Einkaufszettel" msgid "Quantity unit stock cannot be changed after first purchase" msgstr "" -"Die Mengeneinheit des Vorrats kann nach dem ersten Einkauf nicht mehr " -"geändert werden" +"Die Mengeneinheit Bestand kann nach dem ersten Einkauf nicht mehr geändert " +"werden" msgid "Clear filter" msgstr "Filter zurücksetzen" @@ -1896,7 +1897,8 @@ msgstr "Berechtigungen für Benutzer %s" msgid "Are you sure you want to remove full permissions for yourself?" msgstr "" -"Sicher, dass du die vollen Berechtigungen für dich selbst entfernen willst?" +"Bist du sicher, dass du die vollen Berechtigungen für dich selbst entfernen " +"willst?" msgid "Permissions saved" msgstr "Berechtigungen gespeichert" @@ -1947,13 +1949,13 @@ msgid "Default" msgstr "Standard" msgid "Stock journal summary" -msgstr "Zusammenfassung des Vorrats-Protokolls" +msgstr "Bestandsjournal-Zusammenfassung" msgid "Journal summary" -msgstr "Protokoll-Zusammenfassung" +msgstr "Journal-Zusammenfassung" msgid "Journal summary for this product" -msgstr "Protokoll-Zusammenfassung für dieses Produkt" +msgstr "Journal-Zusammenfassung für dieses Produkt" msgid "Consume exact amount" msgstr "Exakte Menge verbrauchen" @@ -1968,23 +1970,23 @@ msgid "" "Show purchased date on purchase and inventory page (otherwise the purchased " "date defaults to today)" msgstr "" -"Einkaufsdatum auf der Einkauf- und Inventur-Seite anzeigen (ansonsten wird " +"Einkaufsdattum auf der Einkauf- und Inventur-Seite anzeigen (ansonsten wird " "heute als Einkaufsdatum verwendet)" msgid "Common" msgstr "Allgemein" msgid "Decimal places allowed for amounts" -msgstr "Erlaubte Dezimalstellen für Mengen" +msgstr "Erlaubte Nachkommastellen für Mengen" msgid "Decimal places allowed for prices" -msgstr "Erlaubte Dezimalstellen für Preise" +msgstr "Erlaubte Nachkommastellen für Preise" msgid "Stock entries for this product" -msgstr "Vorrats-Einträge für dieses Produkt" +msgstr "Bestandseinträge für dieses Produkt" msgid "Edit shopping list" -msgstr "Einkaufsliste bearbeiten" +msgstr "Einkaufszettel bearbeiten" msgid "Save & continue to add quantity unit conversions & barcodes" msgstr "Speichern & fortfahren um ME-Umrechnungen & Barcodes hinzuzufügen" @@ -2002,16 +2004,16 @@ msgid "price" msgstr "Preis" msgid "New stock amount" -msgstr "Neue Anzahl im Vorrat" +msgstr "Neuer Bestand" msgid "Price per stock unit" -msgstr "Preis pro Mengeneinheit im Vorrat" +msgstr "Preis pro Bestandsmengeneinheit" msgid "Table options" msgstr "Tabellenoptionen" msgid "This product is currently on a shopping list" -msgstr "Dieses Produkt ist derzeit auf einer Einkaufsliste" +msgstr "Dieses Produkt ist derzeit auf einem Einkaufszettel" msgid "Undo transaction" msgstr "Transaktion rückgängig machen" @@ -2023,7 +2025,7 @@ msgid "Transaction time" msgstr "Transaktionszeit" msgid "Chore journal" -msgstr "Hausarbeit-Protokoll" +msgstr "Hausarbeit-Journal" msgid "Track chore execution" msgstr "Ausführung erfassen" @@ -2035,13 +2037,13 @@ msgid "Track charge cycle" msgstr "Ladezyklus erfassen" msgid "Battery journal" -msgstr "Akku-Protokoll" +msgstr "Batterie-Journal" msgid "This product has a picture" msgstr "Dieses Produkt hat ein Bild" msgid "Consume this stock entry as spoiled" -msgstr "Vorratseintrag als verdorben entfernen" +msgstr "Verbrauche diesen Bestandseintrag als verdorben" msgid "Configure user permissions" msgstr "Benutzerberechtigungen konfigurieren" @@ -2053,18 +2055,18 @@ msgid "" "This is the default quantity unit used when adding this product to the " "shopping list" msgstr "" -"Dies ist die Standardmengeneinheit, die beim Hinzufügen zum Einkaufszettel " -"verwendet wird" +"Dies ist die Standardmengeneinheit, die beim Hinzufügen dieses Produkts zum " +"Einkaufszettel verwendet wird" msgid "" "Show a warning when the due date of the purchased product is earlier than " "the next due date in stock" msgstr "" -"Eine Warnung anzeigen, wenn das Verfallsdatum des eingekauften Produkts " -"früher ist als im Vorrat" +"Eine Warnung anzeigen, wenn das Fälligkeitsdatum des eingekauften Produkts " +"früher ist als im Bestand" msgid "This is due earlier than already in-stock items" -msgstr "Dies wird früher ablaufen als bereits vorhandene Vorrats-Einträge" +msgstr "Dies ist früher fällig als bereits vorhandene Bestandseinträge" msgid "" "When enabled, after changing/scanning a product and if all fields could be " @@ -2083,31 +2085,31 @@ msgid "" "This amount is used for the \"quick consume/open buttons\" on the stock " "overview page (related to quantity unit stock)" msgstr "" -"Diese Menge wird für die \"Schnell-Verbrauch/Öffnen-Buttons\" in der " -"Vorrats-Ansicht benutzt (bezieht sich auf die Mengeneinheit im Vorrat)" +"Diese Menge wird für die \"Schnell-Verbrauch/Öffnen-Buttons\" auf der " +"Bestandsübersicht benutzt (bezieht sich auf die Bestandsmengeneinheit)" msgid "Copy" msgstr "Kopieren" msgid "Are you sure to remove this barcode?" -msgstr "Sicher, dass du diesen Barcode löschen möchtest?" +msgstr "Diesen Barcode wirklich löschen?" msgid "Due date type" -msgstr "Art des Verfallsdatums" +msgstr "Fälligkeitsdatum-Typ" msgid "" "Based on the selected type, the highlighting on the stock overview page will" " be different" msgstr "" -"Je nach ausgewähltem Typ wird die Hervorhebung auf der Vorrats-Ansicht " +"Je nach ausgewähltem Typ wird die Hervorhebung auf der Bestandsübersicht " "unterschiedlich sein" msgid "" "Means that the product is maybe still safe to be consumed after its due date" " is reached" msgstr "" -"Bedeutet, dass das Produkt nach dem Verfallsdatum möglicherweise immer noch " -"verzehrt werden kann" +"Bedeutet, dass das Produkt nach dem Fälligkeitsdatum möglicherweise immer " +"noch verzehrt werden kann" msgid "Expiration date" msgstr "Verbrauchsdatum" @@ -2116,77 +2118,83 @@ msgid "" "Means that the product is not safe to be consumed after its due date is " "reached" msgstr "" -"Bedeutet, dass das Produkt nach dem Verfallsdatum nicht mehr verzehrt werden" -" kann" +"Bedeutet, dass das Produkt nach dem Fälligkeitsdatum nicht mehr verzehrt " +"werden kann" msgid "" "For purchases this amount of days will be added to today for the due date " "suggestion" -msgstr "Bei Einkäufen wird hierauf basierend das Verfallsdatum vorausgefüllt" +msgstr "" +"Bei Einkäufen wird hierauf basierend das Fälligkeitsdatum vorausgefüllt" msgid "-1 means that this product will be never overdue" -msgstr "-1 bedeutet, dass es niemals abläuft" +msgstr "-1 bedeutet, dass dieses Produkt niemals überfällig ist" msgid "Default due days" -msgstr "Standard-Verfallstage" +msgstr "Standard-Fälligkeitstage" 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)" +"today + this amount of days, but only if the resulting date is not after the" +" original due date (a value of 0 disables this)" msgstr "" -"Wenn es als geöffnet markiert wurde, wird das Verfallsdatum durch heute + " -"diese Anzahl von Tagen ersetzt (ein Wert von 0 deaktiviert dies) " +"Wenn dieses Produkt als geöffnet markiert wurde, wird das Fälligkeitsdatum " +"durch heute + diese Anzahl von Tagen ersetzt, aber nur, wenn das " +"resultierende Datum nicht nach dem ursprünglichen Fälligkeitsdatum liegt " +"(ein Wert von 0 deaktiviert dies) " msgid "Default due days after opened" -msgstr "Standard-Verfallstage nach dem Öffnen" +msgstr "Standard-Fälligkeitstage nach dem Öffnen" 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 "" -"Wenn es zu einem Gefrier-Standort umgelagert (sprich eingefroren) wird, wird" -" das Verfallsdatum durch heute + diese Anzahl von Tagen ersetzt" +"Wenn dieses Produkt zu einem Gefrier-Standort umgelagert (sprich " +"eingefroren) wird, wird das Fälligkeitsdatum durch heute + diese Anzahl von " +"Tagen ersetzt" msgid "Default due days after freezing" -msgstr "Standard-Verfallstage nach dem Einfrieren" +msgstr "Standard-Fälligkeitstage nach dem Einfrieren" msgid "" "On moving this product from a freezer location (so when thawing it), the due" " date will be replaced by today + this amount of days" msgstr "" -"Wenn es von einem Gefrier-Standort umgelagert (sprich aufgetaut) wird, wird " -"das Verfallsdatum durch heute + diese Anzahl von Tagen ersetzt" +"Wenn dieses Produkt von einem Gefrier-Standort umgelagert (sprich aufgetaut)" +" wird, wird das Fälligkeitsdatum durch heute + diese Anzahl von Tagen " +"ersetzt" msgid "Default due days after thawing" -msgstr "Standard-Verfallstage nach dem Auftauen" +msgstr "Standard-Fälligkeitstage nach dem Auftauen" msgid "Next due date" -msgstr "Nächstes Verfallsdatum" +msgstr "Nächstes Fälligkeitsdatum" msgid "%s product is due" msgid_plural "%s products are due" -msgstr[0] "%s Produkt verdirbt" -msgstr[1] "%s Produkte verderben" +msgstr[0] "%s Produkte ist fällig" +msgstr[1] "%s Produkte sind fällig" msgid "Due date" -msgstr "Verfallsdatum" +msgstr "Fälligkeitsdatum" msgid "Never overdue" -msgstr "Läuft nie ab" +msgstr "Nie überfällig" msgid "%s product is expired" msgid_plural "%s products are expired" -msgstr[0] "%s Produkt ist verdorben" -msgstr[1] "%s Produkte sind verdorben" +msgstr[0] "%s Produkt ist abgelaufen" +msgstr[1] "%s Produkte sind abgelaufen" msgid "Expired" -msgstr "Verdorben" +msgstr "Abgelaufen" msgid "Due soon days" -msgstr "Verfallsdatum in Tagen (laufen bald ab)" +msgstr "\"Bald fällig\" Tage" msgid "Add overdue/expired products" -msgstr "Verfallene/Abgelaufene Produkte hinzufügen" +msgstr "Überfällige/Abgelaufene Produkte hinzufügen" msgid "" "Products with tare weight enabled are currently not supported for transfer" @@ -2226,16 +2234,16 @@ msgid "" "Automatically do the booking using the last price and the amount of the " "shopping list item, if the product has \"Default due days\" set" msgstr "" -"Buchung automatisch ausführen, wenn das Produkt \"Standard-Verfallstage\" " -"hinterlegt hat (als Preis wird der neuste Preis verwendet)" +"Buchung automatisch ausführen, wenn das Produkt \"Standard-Fälligkeitstage\"" +" hinterlegt hat (als Preis wird der letzte Preis verwendet)" msgid "" "When moving products from/to a freezer location, the products due date is " "automatically adjusted according to the product settings" msgstr "" "Beim Umlagen von Produkten von/zu einem Gefrier-Standort wird das " -"Verfallsdatum der Produkte automatisch entsprechend den Produkteinstellungen" -" angepasst" +"Fälligkeitsdatum der Produkte automatisch entsprechend den " +"Produkteinstellungen angepasst" msgid "This is the internal field name, e. g. for the API" msgstr "Dies ist der interne Feldname, z. B. für die API" @@ -2255,7 +2263,7 @@ msgid "Download file" msgstr "Datei herunterladen" msgid "Use the products \"Quick consume amount\"" -msgstr "Die Schnell-Verbrauchs-Menge des Produktes verwenden" +msgstr "Die Schnell-Verbrauchs-Menge des Produkts verwenden" msgid "Disabled" msgstr "Deaktiviert" @@ -2265,15 +2273,15 @@ msgid "" "this product - consider disabling it instead, if you want to keep that and " "just hide the product." msgstr "" -"Dadruch wird auch der Vorrat, das Protokoll und alle anderen Referenzen " -"entfernt - ziehe stattdessen in Betracht, dieses Produkt zu deaktivieren, " -"wenn du dies behalten und nur das Produkt ausblenden willst." +"Dadruch wird auch der Bestand, das Journal und alle anderen Referenzen " +"dieses Produkts entfernt - ziehe stattdessen in Betracht, dieses Produkt zu " +"deaktivieren, wenn du dies behalten und nur das Produkt ausblenden willst." msgid "Show disabled" msgstr "Deaktivierte anzeigen" msgid "Never show on stock overview" -msgstr "Nie auf der Vorrats-Seite anzeigen" +msgstr "Nie auf der Bestand-Seite anzeigen" msgid "None" msgstr "Keine" @@ -2288,7 +2296,7 @@ msgid "Reset" msgstr "Zurücksetzen" msgid "Are you sure to reset the table options?" -msgstr "Sicher, dass du die Tabellenoptionen zurücksetzen möchtest?" +msgstr "Tabellenoptionen wirklich zurücksetzen?" msgid "Hide/view columns" msgstr "Spalten anzeigen/ausblenden" @@ -2298,14 +2306,14 @@ msgid "" "checking it is sufficient when any amount of the product in stock" msgstr "" "Eine andere Menge/Einheit kann dann unten verwendet werden, wobei es für die" -" Vorrats-Prüfung ausreichend ist, wenn eine beliebige Menge des Produkts " +" Bestandsprüfung ausreichend ist, wenn eine beliebige Menge des Produkts " "verfügbar ist" msgid "Last price (Unit)" -msgstr "Neuster Preis (Einheit)" +msgstr "Letzter Preis (Einheit)" msgid "Last price (Total)" -msgstr "Neuster Preis (Gesamt)" +msgstr "Letzter Preis (Gesamt)" msgid "Show header" msgstr "Kopfzeile anzeigen" @@ -2317,7 +2325,7 @@ msgid "Table" msgstr "Tabelle" msgid "Layout type" -msgstr "Layout-Typ" +msgstr "Layout" msgid "Merge this product with another one" msgstr "Dieses Produkt mit einem anderen zusammenführen" @@ -2341,8 +2349,9 @@ msgid "" "After merging, all occurences of this product will be replaced by \"Product " "to keep\" (means this product will not exist anymore)" msgstr "" -"Nach dem Zusammenführen werden alle Referenzen auf das zu behaltende Produkt" -" zeigen (d. h. dieses Produkt existiert dann nicht mehr)" +"Nach dem Zusammenführen werden alle Referenzen auf dieses Produkt durch das " +"zu behaltende Produkt ersetzt (d. h. dieses Produkt existiert dann nicht " +"mehr)" msgid "Merge" msgstr "Zusammenführen" @@ -2358,9 +2367,9 @@ msgid "" "below their min. stock amount - enable this to hide this product there " "always" msgstr "" -"Die Vorrats-Seite listet alle Produkte auf, die derzeit im Vorrat oder unter" -" ihrem Mindestvorrat sind - aktiviere dies, um dieses Produkt dort nie " -"anzuzeigen" +"Die Bestand-Seite listet alle Produkte auf, die derzeit im Bestand oder " +"unter ihrem Mindestbestand sind - aktiviere dies, um dieses Produkt dort nie" +" anzuzeigen" msgid "Print options" msgstr "Druckoptionen" @@ -2374,17 +2383,15 @@ msgstr "grocycode" msgid "Download" msgstr "Herunterladen" -msgid "Download stock entry grocycode" -msgstr "Bestandseintrag grocycode herunterladen" - -msgid "Download product grocycode" -msgstr "Produkt grocycode herunterladen" +# Example: Download *Product* grocycode +msgid "Download %s grocycode" +msgstr "%s grocycode herunterladen" msgid "" -"grocycode is a unique referer to this product in your grocy instance - print" -" it onto a label and scan it like any other barcode" +"grocycode is a unique referer to this %s in your grocy instance - print it " +"onto a label and scan it like any other barcode" msgstr "" -"grocycode ist eine eindeutige Referenz zu diesem Produkt in dieser grocy-" +"grocycode ist eine eindeutige Referenz zu diesem/dieser %s in dieser grocy-" "Instanz - ausgedruckt kann es wie jeder andere Barcode gescannt werden" # Abbreviation for "due date" @@ -2419,13 +2426,11 @@ msgstr "" msgid "Error while executing WebHook" msgstr "Fehler bei WebHook-Ausführung" -msgid "Print product grocycode on label printer" -msgstr "Produkt grocycode auf Etikettendrucker drucken" +# Example: Print *Product* grocycode on label printer +msgid "Print %s grocycode on label printer" +msgstr "%s grocycode auf Etikettendrucker drucken" -msgid "Print stock entry grocycode on label printer" -msgstr "Bestandseintrag grocycode auf Etikettendrucker durcken" - -msgid "Open stock entry print label in new window" +msgid "Open stock entry label in new window" msgstr "Bestandseintrag-Etikett in neuem Fenster öffnen" msgid "Thermal printer" @@ -2439,3 +2444,100 @@ msgstr "Verbindung zum Drucker wird hergestellt..." msgid "Unable to print" msgstr "Drucken nicht möglich" + +msgid "Only done items" +msgstr "Nur erledigte Einträge" + +msgid "Show only in-stock products" +msgstr "Nur vorrätige Produkte anzeigen" + +msgid "Product description" +msgstr "Produktbeschreibung" + +# Example: *3.21 USD* per *Pack* +msgid "%1$s per %2$s" +msgstr "%1$s pro %2$s" + +msgid "Mark this item as undone" +msgstr "Diesen Eintrag als unerledigt markieren" + +msgid "Mandatory" +msgstr "Erforderlich" + +msgid "Mandatory Userfield" +msgstr "Erforderliches Benutzerfeld" + +msgid "When enabled, then this field must be filled on the destination form" +msgstr "" +"Wenn aktiviert, dann muss dieses Feld im Zielformular ausgefüllt werden" + +msgid "In-stock products" +msgstr "Vorrätige Produkte" + +msgid "Timestamp" +msgstr "Zeitstempel" + +msgid "Should not be frozen" +msgstr "Sollte nicht eingefroren werden" + +msgid "" +"When enabled, on moving this product to a freezer location (so when freezing" +" it), a warning will be shown" +msgstr "" +"Wenn aktiviert und wenn dieses Produkt zu einem Gefrier-Standort umgelagert " +"(sprich eingefroren) wird, wird eine entsprechende Warnung angezeigt" + +msgid "This product shouldn't be frozen" +msgstr "Dieses Produkt sollte nicht eingefroren werden" + +msgid "Copy all meal plan entries of %s" +msgstr "Alle Einträge vom %s kopieren" + +msgid "A date is required" +msgstr "Ein Datum ist erforderlich" + +msgid "Day" +msgstr "Tag" + +msgid "Add recipe" +msgstr "Rezept hinzufügen" + +msgid "Copy this day" +msgstr "Diesen Tag kopieren" + +msgid "Date range" +msgstr "Zeitraum" + +msgid "%s month" +msgid_plural "%s months" +msgstr[0] "%s Monat" +msgstr[1] "%s Monate" + +msgid "%s year" +msgid_plural "%s years" +msgstr[0] "%s Jahr" +msgstr[1] "%s Jahre" + +msgid "Display product" +msgstr "Produkt anzeigen" + +msgid "Copy recipe" +msgstr "Rezept kopieren" + +msgid "Copy of %s" +msgstr "Kopie von %s" + +msgid "Add decimal separator automatically for price inputs" +msgstr "Dezimaltrennzeichen für Preis-Eingabefelder automatisch hinzufügen" + +msgid "" +"When enabled, you always have to enter the value including decimal places, " +"the decimal separator will be automatically added based on the amount of " +"allowed decimal places" +msgstr "" +"Wenn aktiviert, müssen Preiseingaben immer inkl. Nachkommastellen erfolgen, " +"das Dezimaltrennzeichen wird automatisch entsprechend der Anzahl der " +"erlaubten Nachkommastellen hinzugefügt" + +msgid "Stock entry" +msgstr "Bestandseintrag" diff --git a/localization/strings.pot b/localization/strings.pot index 7a255692..aee0d9db 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -2084,13 +2084,11 @@ msgstr "" msgid "Download" msgstr "" -msgid "Download stock entry grocycode" +# Example: Download *Product* grocycode +msgid "Download %s grocycode" msgstr "" -msgid "Download product grocycode" -msgstr "" - -msgid "grocycode is a unique referer to this product in your grocy instance - print it onto a label and scan it like any other barcode" +msgid "grocycode is a unique referer to this %s in your grocy instance - print it onto a label and scan it like any other barcode" msgstr "" # Abbreviation for "due date" @@ -2121,13 +2119,11 @@ msgstr "" msgid "Error while executing WebHook" msgstr "" -msgid "Print product grocycode on label printer" +# Example: Print *Product* grocycode on label printer +msgid "Print %s grocycode on label printer" msgstr "" -msgid "Print stock entry grocycode on label printer" -msgstr "" - -msgid "Open stock entry print label in new window" +msgid "Open stock entry label in new window" msgstr "" msgid "Thermal printer" @@ -2193,3 +2189,37 @@ msgstr "" msgid "Add recipe" msgstr "" + +msgid "Copy this day" +msgstr "" + +msgid "Date range" +msgstr "" + +msgid "%s month" +msgid_plural "%s months" +msgstr[0] "" +msgstr[1] "" + +msgid "%s year" +msgid_plural "%s years" +msgstr[0] "" +msgstr[1] "" + +msgid "Display product" +msgstr "" + +msgid "Copy recipe" +msgstr "" + +msgid "Copy of %s" +msgstr "" + +msgid "Add decimal separator automatically for price inputs" +msgstr "" + +msgid "When enabled, you always have to enter the value including decimal places, the decimal separator will be automatically added based on the amount of allowed decimal places" +msgstr "" + +msgid "Stock entry" +msgstr "" diff --git a/migrations/0136.sql b/migrations/0136.sql index 6464fe02..fc3b6a6b 100644 --- a/migrations/0136.sql +++ b/migrations/0136.sql @@ -33,7 +33,8 @@ SELECT p.description as product_description, l.name AS product_default_location_name, p_parent.id AS parent_product_id, - p_parent.name AS parent_product_name + p_parent.name AS parent_product_name, + p.picture_file_name AS product_picture_file_name FROM ( SELECT * FROM stock_current @@ -92,7 +93,8 @@ SELECT p.description AS product_description, l.name AS product_default_location_name, p_parent.id AS parent_product_id, - p_parent.name AS parent_product_name + p_parent.name AS parent_product_name, + p.picture_file_name AS product_picture_file_name FROM ( SELECT * FROM stock_current diff --git a/migrations/0147.sql b/migrations/0147.sql new file mode 100644 index 00000000..39c67879 --- /dev/null +++ b/migrations/0147.sql @@ -0,0 +1,28 @@ +DROP VIEW uihelper_stock_journal; +CREATE VIEW uihelper_stock_journal +AS +SELECT + sl.id, + sl.row_created_timestamp, + sl.correlation_id, + sl.undone, + sl.undone_timestamp, + sl.transaction_type, + sl.spoiled, + sl.amount, + sl.location_id, + l.name AS location_name, + p.name AS product_name, + qu.name AS qu_name, + qu.name_plural AS qu_name_plural, + u.display_name AS user_display_name, + p.id AS product_id +FROM stock_log sl +JOIN users_dto u + ON sl.user_id = u.id +JOIN products p + ON sl.product_id = p.id +JOIN locations l + ON sl.location_id = l.id +JOIN quantity_units qu + ON p.qu_id_stock = qu.id; diff --git a/public/css/grocy.css b/public/css/grocy.css index c53bcab1..3776d719 100755 --- a/public/css/grocy.css +++ b/public/css/grocy.css @@ -270,7 +270,6 @@ a:not([href]) { padding: 0; word-wrap: break-word; white-space: pre-wrap; - color: inherit; } /* Barcodescanner Quagga */ @@ -318,6 +317,10 @@ a:not([href]) { border-radius: 0.2rem; } +.ls-n1 { + letter-spacing: -0.1rem; +} + .text-larger { font-size: 125%; } diff --git a/public/js/grocy.js b/public/js/grocy.js index 99fdeed3..4b7c119b 100644 --- a/public/js/grocy.js +++ b/public/js/grocy.js @@ -681,7 +681,7 @@ $("textarea.wysiwyg-editor").summernote({ function LoadImagesLazy() { - $(".lazy").Lazy({ + $(".lazy:visible").Lazy({ enableThrottle: true, throttle: 500 }); @@ -801,13 +801,6 @@ $.extend(true, $.fn.dataTable.defaults, { if ("dataSrc" in rowGroup) { api.rowGroup().dataSrc(rowGroup.dataSrc); - - // Apply fixed order for group column - var fixedOrder = { - pre: [rowGroup.dataSrc, 'asc'] - }; - - api.order.fixed(fixedOrder); } } } @@ -1105,7 +1098,7 @@ $(document).on("click", ".change-table-columns-rowgroup-toggle", function() dataTable.rowGroup().enable(false); // Remove fixed order - dataTable.order.fixed({}); + //dataTable.order.fixed({}); } else { @@ -1116,12 +1109,6 @@ $(document).on("click", ".change-table-columns-rowgroup-toggle", function() dataTable.rowGroup().enable(true); dataTable.rowGroup().dataSrc(columnIndex); - - // Apply fixed order for group column - var fixedOrder = { - pre: [columnIndex, 'asc'] - }; - dataTable.order.fixed(fixedOrder); } var settingKey = 'datatables_rowGroup_' + dataTable.settings()[0].sTableId; diff --git a/public/viewjs/batteriesjournal.js b/public/viewjs/batteriesjournal.js index 01b1fc3e..f419cbac 100644 --- a/public/viewjs/batteriesjournal.js +++ b/public/viewjs/batteriesjournal.js @@ -1,5 +1,4 @@ var batteriesJournalTable = $('#batteries-journal-table').DataTable({ - 'paginate': true, 'order': [[2, 'desc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, @@ -12,13 +11,16 @@ batteriesJournalTable.columns.adjust().draw(); $("#battery-filter").on("change", function() { var value = $(this).val(); - var text = $("#battery-filter option:selected").text(); if (value === "all") { - text = ""; + RemoveUriParam("battery"); + } + else + { + UpdateUriParam("battery", value); } - batteriesJournalTable.column(1).search(text).draw(); + window.location.reload(); }); $("#search").on("keyup", Delay(function() @@ -36,14 +38,27 @@ $("#clear-filter-button").on("click", function() { $("#search").val(""); $("#battery-filter").val("all"); - batteriesJournalTable.column(1).search("").draw(); - batteriesJournalTable.search("").draw(); + $("#daterange-filter").val("24"); + + RemoveUriParam("months"); + RemoveUriParam("battery"); + window.location.reload(); +}); + +$("#daterange-filter").on("change", function() +{ + UpdateUriParam("months", $(this).val()); + window.location.reload(); }); if (typeof GetUriParam("battery") !== "undefined") { $("#battery-filter").val(GetUriParam("battery")); - $("#battery-filter").trigger("change"); +} + +if (typeof GetUriParam("months") !== "undefined") +{ + $("#daterange-filter").val(GetUriParam("months")); } $(document).on('click', '.undo-battery-execution-button', function(e) diff --git a/public/viewjs/batteriesoverview.js b/public/viewjs/batteriesoverview.js index a137cb99..ae956dd5 100644 --- a/public/viewjs/batteriesoverview.js +++ b/public/viewjs/batteriesoverview.js @@ -122,6 +122,21 @@ $(document).on("click", ".battery-name-cell", function(e) $("#batteriesoverview-batterycard-modal").modal("show"); }); +$(document).on('click', '.battery-grocycode-label-print', function(e) +{ + e.preventDefault(); + document.activeElement.blur(); + + var batteryId = $(e.currentTarget).attr('data-battery-id'); + Grocy.Api.Get('batteries/' + batteryId + '/printlabel', function(labelData) + { + if (Grocy.Webhooks.labelprinter !== undefined) + { + Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData); + } + }); +}); + function RefreshStatistics() { var nextXDays = $("#info-due-batteries").data("next-x-days"); diff --git a/public/viewjs/batteryform.js b/public/viewjs/batteryform.js index 596e9a06..8e8a5d29 100644 --- a/public/viewjs/batteryform.js +++ b/public/viewjs/batteryform.js @@ -83,6 +83,21 @@ $('#battery-form input').keydown(function(event) } }); +$(document).on('click', '.battery-grocycode-label-print', function(e) +{ + e.preventDefault(); + document.activeElement.blur(); + + var batteryId = $(e.currentTarget).attr('data-chore-id'); + Grocy.Api.Get('batteries/' + batteryId + '/printlabel', function(labelData) + { + if (Grocy.Webhooks.labelprinter !== undefined) + { + Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData); + } + }); +}); + Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('battery-form'); diff --git a/public/viewjs/batterytracking.js b/public/viewjs/batterytracking.js index fc39cf78..a0633ce1 100644 --- a/public/viewjs/batterytracking.js +++ b/public/viewjs/batterytracking.js @@ -60,7 +60,8 @@ $('#battery_id').on('change', function(e) $('.combobox').combobox({ appendId: '_text_input', - bsVersion: '4' + bsVersion: '4', + clearIfNoMatch: false }); $('#battery_id').val(''); @@ -97,6 +98,16 @@ $('#tracked_time').find('input').on('keypress', function(e) Grocy.FrontendHelpers.ValidateForm('batterytracking-form'); }); +$(document).on("Grocy.BarcodeScanned", function(e, barcode, target) +{ + if (!(target == "@batterypicker" || target == "undefined" || target == undefined)) // Default target + { + return; + } + + $('#battery_id_text_input').val(barcode).trigger('change'); +}); + function UndoChargeCycle(chargeCycleId) { Grocy.Api.Post('batteries/charge-cycles/' + chargeCycleId.toString() + '/undo', {}, @@ -110,3 +121,38 @@ function UndoChargeCycle(chargeCycleId) } ); }; + +$('#battery_id_text_input').on('blur', function(e) +{ + if ($('#battery_id').hasClass("combobox-menu-visible")) + { + return; + } + + var input = $('#battery_id_text_input').val().toString(); + var possibleOptionElement = []; + + // grocycode handling + if (input.startsWith("grcy")) + { + var gc = input.split(":"); + if (gc[1] == "b") + { + possibleOptionElement = $("#battery_id option[value=\"" + gc[2] + "\"]").first(); + } + } + + if (possibleOptionElement.length > 0) + { + $('#battery_id').val(possibleOptionElement.val()); + $('#battery_id').data('combobox').refresh(); + $('#battery_id').trigger('change'); + } + else + { + $('#battery_id').val(null); + $('#battery_id_text_input').val(""); + $('#battery_id').data('combobox').refresh(); + $('#battery_id').trigger('change'); + } +}); diff --git a/public/viewjs/choreform.js b/public/viewjs/choreform.js index 538e6d28..9b500ec9 100644 --- a/public/viewjs/choreform.js +++ b/public/viewjs/choreform.js @@ -237,3 +237,18 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) ); } }); + +$(document).on('click', '.chore-grocycode-label-print', function(e) +{ + e.preventDefault(); + document.activeElement.blur(); + + var choreId = $(e.currentTarget).attr('data-chore-id'); + Grocy.Api.Get('chores/' + choreId + '/printlabel', function(labelData) + { + if (Grocy.Webhooks.labelprinter !== undefined) + { + Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData); + } + }); +}); diff --git a/public/viewjs/choresjournal.js b/public/viewjs/choresjournal.js index 6f75adcf..8d7c3dd4 100644 --- a/public/viewjs/choresjournal.js +++ b/public/viewjs/choresjournal.js @@ -1,5 +1,4 @@ var choresJournalTable = $('#chores-journal-table').DataTable({ - 'paginate': true, 'order': [[2, 'desc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, @@ -12,13 +11,22 @@ choresJournalTable.columns.adjust().draw(); $("#chore-filter").on("change", function() { var value = $(this).val(); - var text = $("#chore-filter option:selected").text(); if (value === "all") { - text = ""; + RemoveUriParam("chore"); + } + else + { + UpdateUriParam("chore", value); } - choresJournalTable.column(1).search(text).draw(); + window.location.reload(); +}); + +$("#daterange-filter").on("change", function() +{ + UpdateUriParam("months", $(this).val()); + window.location.reload(); }); $("#search").on("keyup", Delay(function() @@ -36,14 +44,21 @@ $("#clear-filter-button").on("click", function() { $("#search").val(""); $("#chore-filter").val("all"); - choresJournalTable.column(1).search("").draw(); - choresJournalTable.search("").draw(); + $("#daterange-filter").val("24"); + + RemoveUriParam("months"); + RemoveUriParam("chore"); + window.location.reload(); }); if (typeof GetUriParam("chore") !== "undefined") { $("#chore-filter").val(GetUriParam("chore")); - $("#chore-filter").trigger("change"); +} + +if (typeof GetUriParam("months") !== "undefined") +{ + $("#daterange-filter").val(GetUriParam("months")); } $(document).on('click', '.undo-chore-execution-button', function(e) diff --git a/public/viewjs/choresoverview.js b/public/viewjs/choresoverview.js index d5c4b81d..6a00f2d1 100644 --- a/public/viewjs/choresoverview.js +++ b/public/viewjs/choresoverview.js @@ -185,6 +185,21 @@ $(document).on("click", ".chore-name-cell", function(e) $("#choresoverview-chorecard-modal").modal("show"); }); +$(document).on('click', '.chore-grocycode-label-print', function(e) +{ + e.preventDefault(); + document.activeElement.blur(); + + var choreId = $(e.currentTarget).attr('data-chore-id'); + Grocy.Api.Get('chores/' + choreId + '/printlabel', function(labelData) + { + if (Grocy.Webhooks.labelprinter !== undefined) + { + Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData); + } + }); +}); + function RefreshStatistics() { var nextXDays = $("#info-due-chores").data("next-x-days"); diff --git a/public/viewjs/choretracking.js b/public/viewjs/choretracking.js index eaf23ea7..64435ed8 100644 --- a/public/viewjs/choretracking.js +++ b/public/viewjs/choretracking.js @@ -83,7 +83,8 @@ $('#chore_id').on('change', function(e) $('.combobox').combobox({ appendId: '_text_input', - bsVersion: '4' + bsVersion: '4', + clearIfNoMatch: false }); $('#chore_id_text_input').focus(); @@ -113,6 +114,16 @@ $('#choretracking-form input').keydown(function(event) } }); +$(document).on("Grocy.BarcodeScanned", function(e, barcode, target) +{ + if (!(target == "@chorepicker" || target == "undefined" || target == undefined)) // Default target + { + return; + } + + $('#chore_id_text_input').val(barcode).trigger('change'); +}); + Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e) { Grocy.FrontendHelpers.ValidateForm('choretracking-form'); @@ -131,3 +142,38 @@ function UndoChoreExecution(executionId) } ); }; + +$('#chore_id_text_input').on('blur', function(e) +{ + if ($('#chore_id').hasClass("combobox-menu-visible")) + { + return; + } + + var input = $('#chore_id_text_input').val().toString(); + var possibleOptionElement = []; + + // grocycode handling + if (input.startsWith("grcy")) + { + var gc = input.split(":"); + if (gc[1] == "c") + { + possibleOptionElement = $("#chore_id option[value=\"" + gc[2] + "\"]").first(); + } + } + + if (possibleOptionElement.length > 0) + { + $('#chore_id').val(possibleOptionElement.val()); + $('#chore_id').data('combobox').refresh(); + $('#chore_id').trigger('change'); + } + else + { + $('#chore_id').val(null); + $('#chore_id_text_input').val(""); + $('#chore_id').data('combobox').refresh(); + $('#chore_id').trigger('change'); + } +}); diff --git a/public/viewjs/components/numberpicker.js b/public/viewjs/components/numberpicker.js index 249e2b85..ac66c3e9 100644 --- a/public/viewjs/components/numberpicker.js +++ b/public/viewjs/components/numberpicker.js @@ -92,3 +92,20 @@ $(".numberpicker").on("keydown", function(e) $(this).parent().find(".numberpicker-down-button").click(); } }); + +$(".numberpicker.locale-number-input.locale-number-currency").on("blur", function() +{ + if (BoolVal(Grocy.UserSettings.stock_auto_decimal_separator_prices)) + { + var value = this.value.toString(); + var decimalPlaces = parseInt(Grocy.UserSettings.stock_decimal_places_prices); + + if (value.length <= decimalPlaces) + { + return; + } + + var valueNew = parseFloat(value.substring(0, value.length - decimalPlaces) + '.' + value.slice(decimalPlaces * -1)).toLocaleString(undefined, { minimumFractionDigits: decimalPlaces, maximumFractionDigits: decimalPlaces }); + $(this).val(valueNew); + } +}); diff --git a/public/viewjs/components/productcard.js b/public/viewjs/components/productcard.js index a6402804..86ef0f24 100644 --- a/public/viewjs/components/productcard.js +++ b/public/viewjs/components/productcard.js @@ -84,7 +84,7 @@ Grocy.Components.ProductCard.Refresh = function(productId) if (productDetails.last_price !== null) { - $('#productcard-product-last-price').text(__t("%1$s per %2$s", Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency, productDetails.quantity_unit_stock.name)); + $('#productcard-product-last-price').text(__t("%1$s per %2$s", Number.parseFloat(productDetails.last_price).toLocaleString(undefined, { style: "currency", currency: Grocy.Currency, minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices }), productDetails.quantity_unit_stock.name)); } else { @@ -93,7 +93,7 @@ Grocy.Components.ProductCard.Refresh = function(productId) if (productDetails.avg_price !== null) { - $('#productcard-product-average-price').text(__t("%1$s per %2$s", Number.parseFloat(productDetails.avg_price).toLocaleString() + ' ' + Grocy.Currency, productDetails.quantity_unit_stock.name)); + $('#productcard-product-average-price').text(__t("%1$s per %2$s", Number.parseFloat(productDetails.avg_price).toLocaleString(undefined, { style: "currency", currency: Grocy.Currency, minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices }), productDetails.quantity_unit_stock.name)); } else { diff --git a/public/viewjs/components/productpicker.js b/public/viewjs/components/productpicker.js index 88def83e..b479931c 100644 --- a/public/viewjs/components/productpicker.js +++ b/public/viewjs/components/productpicker.js @@ -149,18 +149,17 @@ $('#product_id_text_input').on('blur', function(e) var input = $('#product_id_text_input').val().toString(); var possibleOptionElement = []; - // did we enter a grocycode? + // grocycode handling if (input.startsWith("grcy")) { var gc = input.split(":"); if (gc[1] == "p") { - // find product id possibleOptionElement = $("#product_id option[value=\"" + gc[2] + "\"]").first(); $("#product_id").data("grocycode", true); } } - else // process barcode as usual + else // Normal product barcode handling { possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first(); } diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js index b8711859..f0302aa5 100644 --- a/public/viewjs/mealplan.js +++ b/public/viewjs/mealplan.js @@ -32,10 +32,10 @@ var calendar = $("#calendar").fullCalendar({
\ \ \ - '); @@ -121,11 +121,11 @@ var calendar = $("#calendar").fullCalendar({ var costsAndCaloriesPerServing = "" if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { - costsAndCaloriesPerServing = '
' + resolvedRecipe.costs + ' / ' + resolvedRecipe.calories + ' kcal ' + __t('per serving') + '
'; + costsAndCaloriesPerServing = '
' + resolvedRecipe.costs + ' / ' + resolvedRecipe.calories + ' kcal ' + __t('per serving') + '
'; } else { - costsAndCaloriesPerServing = '
' + resolvedRecipe.calories + ' kcal ' + __t('per serving') + '
'; + costsAndCaloriesPerServing = '
' + resolvedRecipe.calories + ' kcal ' + __t('per serving') + '
'; } if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK) @@ -136,16 +136,15 @@ var calendar = $("#calendar").fullCalendar({ element.html('\
\ -
' + recipe.name + '
\ -
' + __n(mealPlanEntry.recipe_servings, "%s serving", "%s servings") + '
\ -
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ + \ +
' + __n(mealPlanEntry.recipe_servings, "%s serving", "%s servings") + '
\ +
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ ' + costsAndCaloriesPerServing + ' \
\ \ \ \ \ - \ ' + doneButtonHtml + ' \
\
'); @@ -166,11 +165,11 @@ var calendar = $("#calendar").fullCalendar({ var costsAndCaloriesPerDay = "" if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { - costsAndCaloriesPerDay = '
' + dayRecipeResolved.costs + ' / ' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; + costsAndCaloriesPerDay = '
' + dayRecipeResolved.costs + ' / ' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } else { - costsAndCaloriesPerDay = '
' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; + costsAndCaloriesPerDay = '
' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } $(".fc-day-header[data-date='" + dayRecipeName + "']").append('
' + costsAndCaloriesPerDay + '
'); } @@ -214,18 +213,18 @@ var calendar = $("#calendar").fullCalendar({ var costsAndCaloriesPerServing = "" if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { - costsAndCaloriesPerServing = '
' + productDetails.last_price * mealPlanEntry.product_amount + ' / ' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; + costsAndCaloriesPerServing = '
' + productDetails.last_price * mealPlanEntry.product_amount + ' / ' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; } else { - costsAndCaloriesPerServing = '
' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; + costsAndCaloriesPerServing = '
' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; } element.html('\
\ -
' + productDetails.product.name + '
\ -
' + mealPlanEntry.product_amount + " " + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural) + '
\ -
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ + \ +
' + mealPlanEntry.product_amount + " " + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural) + '
\ +
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ ' + costsAndCaloriesPerServing + ' \
\ \ @@ -252,11 +251,11 @@ var calendar = $("#calendar").fullCalendar({ var costsAndCaloriesPerDay = "" if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { - costsAndCaloriesPerDay = '
' + dayRecipeResolved.costs + ' / ' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; + costsAndCaloriesPerDay = '
' + dayRecipeResolved.costs + ' / ' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } else { - costsAndCaloriesPerDay = '
' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; + costsAndCaloriesPerDay = '
' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } $(".fc-day-header[data-date='" + dayRecipeName + "']").append('
' + costsAndCaloriesPerDay + '
'); } @@ -266,7 +265,7 @@ var calendar = $("#calendar").fullCalendar({ { element.html('\
\ -
' + mealPlanEntry.note + '
\ +
' + mealPlanEntry.note + '
\
\ \ \ @@ -831,7 +830,7 @@ $(document).on('click', '.recipe-consume-button', function(e) }); }); -$(document).on("click", ".recipe-popup-button", function(e) +$(document).on("click", ".display-recipe-button", function(e) { // Remove the focus from the current button // to prevent that the tooltip stays until clicked anywhere else @@ -868,6 +867,16 @@ $(document).on("click", ".recipe-popup-button", function(e) ); }); +$(document).on("click", ".display-product-button", function(e) +{ + // Remove the focus from the current button + // to prevent that the tooltip stays until clicked anywhere else + document.activeElement.blur(); + + Grocy.Components.ProductCard.Refresh($(e.currentTarget).attr('data-product-id')); + $("#mealplan-productcard-modal").modal("show"); +}); + $(document).on("click", ".mealplan-entry-done-button", function(e) { e.preventDefault(); diff --git a/public/viewjs/openapiui.js b/public/viewjs/openapiui.js index 78315306..3bc95c0f 100644 --- a/public/viewjs/openapiui.js +++ b/public/viewjs/openapiui.js @@ -21,7 +21,8 @@ const swaggerUi = SwaggerUIBundle({ ], layout: 'StandaloneLayout', docExpansion: "list", - defaultModelsExpandDepth: -1 + defaultModelsExpandDepth: -1, + validatorUrl: false }); window.ui = swaggerUi; diff --git a/public/viewjs/productform.js b/public/viewjs/productform.js index e9c5dce8..f253d1c2 100644 --- a/public/viewjs/productform.js +++ b/public/viewjs/productform.js @@ -269,7 +269,6 @@ $("#delete-current-product-picture-button").on("click", function(e) var quConversionsTable = $('#qu-conversions-table-products').DataTable({ 'order': [[1, 'asc']], - "orderFixed": [[4, 'asc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, { 'searchable': false, "targets": 0 }, @@ -285,7 +284,6 @@ quConversionsTable.columns.adjust().draw(); var barcodeTable = $('#barcode-table').DataTable({ 'order': [[1, 'asc']], - "orderFixed": [[1, 'asc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, { 'searchable': false, "targets": 0 }, @@ -302,7 +300,7 @@ $('#name').focus(); $('.input-group-qu').trigger('change'); Grocy.FrontendHelpers.ValidateForm('product-form'); -$(document).on('click', '.stockentry-grocycode-product-label-print', function(e) +$(document).on('click', '.product-grocycode-label-print', function(e) { e.preventDefault(); document.activeElement.blur(); diff --git a/public/viewjs/purchase.js b/public/viewjs/purchase.js index f23ecb30..ab78c9e9 100644 --- a/public/viewjs/purchase.js +++ b/public/viewjs/purchase.js @@ -117,7 +117,7 @@ $('#save-purchase-button').on('click', function(e) } var successMessage = __t('Added %1$s of %2$s to stock', amountMessage + " " + __n(amountMessage, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''; - if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABELPRINTER) + if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER) { if (Grocy.Webhooks.labelprinter !== undefined) { @@ -126,7 +126,7 @@ $('#save-purchase-button').on('click', function(e) post_data.grocycode = 'grcy:p:' + jsonForm.product_id + ":" + result[0].stock_id if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) { - post_data.duedate = __t('DD') + ': ' + result[0].best_before_date + post_data.due_date = __t('DD') + ': ' + result[0].best_before_date } if (jsonForm.print_stock_label > 0) @@ -304,7 +304,7 @@ if (Grocy.Components.ProductPicker !== undefined) } } - if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABELPRINTER) + if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER) { $("#print_stock_label").val(productDetails.product.default_print_stock_label); if (productDetails.product.allow_label_per_unit) diff --git a/public/viewjs/recipeform.js b/public/viewjs/recipeform.js index 6237ee9a..e0944b79 100644 --- a/public/viewjs/recipeform.js +++ b/public/viewjs/recipeform.js @@ -77,7 +77,6 @@ $('.save-recipe').on('click', function(e) var recipesPosTables = $('#recipes-pos-table').DataTable({ 'order': [[1, 'asc']], - "orderFixed": [[4, 'asc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, { 'searchable': false, "targets": 0 }, diff --git a/public/viewjs/recipes.js b/public/viewjs/recipes.js index f60af8c7..00ca3ac6 100644 --- a/public/viewjs/recipes.js +++ b/public/viewjs/recipes.js @@ -61,6 +61,7 @@ $("a[data-toggle='tab']").on("shown.bs.tab", function(e) { var tabId = $(e.target).attr("id"); window.localStorage.setItem("recipes_last_tab_id", tabId); + LoadImagesLazy(); }); $("#search").on("keyup", Delay(function() @@ -166,6 +167,24 @@ $(".recipe-delete").on('click', function(e) }); }); +$(".recipe-copy").on('click', function(e) +{ + e.preventDefault(); + + var objectId = $(e.currentTarget).attr('data-recipe-id'); + + Grocy.Api.Post("recipes/" + objectId.toString() + "/copy", {}, + function(result) + { + window.location.href = U('/recipes?recipe=' + result.created_object_id.toString()); + }, + function(xhr) + { + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); + } + ); +}); + $(document).on('click', '.recipe-shopping-list', function(e) { var objectName = $(e.currentTarget).attr('data-recipe-name'); @@ -346,3 +365,5 @@ if (window.location.hash === "#fullscreen") { $("#selectedRecipeToggleFullscreenButton").click(); } + +LoadImagesLazy(); diff --git a/public/viewjs/shoppinglist.js b/public/viewjs/shoppinglist.js index 50ef1b74..e0715c74 100644 --- a/public/viewjs/shoppinglist.js +++ b/public/viewjs/shoppinglist.js @@ -1,6 +1,5 @@ var shoppingListTable = $('#shoppinglist-table').DataTable({ 'order': [[1, 'asc']], - "orderFixed": [[3, 'asc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, { 'searchable': false, "targets": 0 }, diff --git a/public/viewjs/stockentries.js b/public/viewjs/stockentries.js index d60b44ae..7f190e99 100644 --- a/public/viewjs/stockentries.js +++ b/public/viewjs/stockentries.js @@ -125,7 +125,7 @@ $(document).on("click", ".stock-name-cell", function(e) $("#stockentry-productcard-modal").modal("show"); }); -$(document).on('click', '.stockentry-grocycode-stockentry-label-print', function(e) +$(document).on('click', '.stockentry-grocycode-label-print', function(e) { e.preventDefault(); document.activeElement.blur(); diff --git a/public/viewjs/stockjournal.js b/public/viewjs/stockjournal.js index e4998e5b..47f92fbb 100644 --- a/public/viewjs/stockjournal.js +++ b/public/viewjs/stockjournal.js @@ -1,5 +1,4 @@ var stockJournalTable = $('#stock-journal-table').DataTable({ - 'paginate': true, 'order': [[3, 'desc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, @@ -12,15 +11,16 @@ stockJournalTable.columns.adjust().draw(); $("#product-filter").on("change", function() { var value = $(this).val(); - var text = $("#product-filter option:selected").text(); if (value === "all") { - stockJournalTable.column(1).search("").draw(); + RemoveUriParam("product"); } else { - stockJournalTable.column(1).search(">" + text + "<").draw(); + UpdateUriParam("product", value); } + + window.location.reload(); }); $("#transaction-type-filter").on("change", function() @@ -59,6 +59,12 @@ $("#user-filter").on("change", function() stockJournalTable.column(6).search(text).draw(); }); +$("#daterange-filter").on("change", function() +{ + UpdateUriParam("months", $(this).val()); + window.location.reload(); +}); + $("#search").on("keyup", Delay(function() { var value = $(this).val(); @@ -77,17 +83,21 @@ $("#clear-filter-button").on("click", function() $("#location-filter").val("all"); $("#user-filter").val("all"); $("#product-filter").val("all"); - stockJournalTable.column(1).search("").draw(); - stockJournalTable.column(4).search("").draw(); - stockJournalTable.column(5).search("").draw(); - stockJournalTable.column(6).search("").draw(); - stockJournalTable.search("").draw(); + $("#daterange-filter").val("6"); + + RemoveUriParam("months"); + RemoveUriParam("product"); + window.location.reload(); }); if (typeof GetUriParam("product") !== "undefined") { $("#product-filter").val(GetUriParam("product")); - $("#product-filter").trigger("change"); +} + +if (typeof GetUriParam("months") !== "undefined") +{ + $("#daterange-filter").val(GetUriParam("months")); } $(document).on('click', '.undo-stock-booking-button', function(e) diff --git a/public/viewjs/stockjournalsummary.js b/public/viewjs/stockjournalsummary.js index b9166d80..8af9a262 100644 --- a/public/viewjs/stockjournalsummary.js +++ b/public/viewjs/stockjournalsummary.js @@ -1,5 +1,4 @@ var journalSummaryTable = $('#stock-journal-summary-table').DataTable({ - 'paginate': true, 'order': [[1, 'asc']], 'columnDefs': [ { 'orderable': false, 'targets': 0 }, diff --git a/public/viewjs/stockoverview.js b/public/viewjs/stockoverview.js index d4df0095..855b9364 100755 --- a/public/viewjs/stockoverview.js +++ b/public/viewjs/stockoverview.js @@ -17,6 +17,7 @@ { 'visible': false, 'targets': 14 }, { 'visible': false, 'targets': 15 }, { 'visible': false, 'targets': 16 }, + { 'visible': false, 'targets': 17 }, { "type": "num", "targets": 3 }, { "type": "html-num-fmt", "targets": 9 }, { "type": "html-num-fmt", "targets": 10 }, @@ -29,6 +30,7 @@ $('#stock-overview-table tbody').removeClass("d-none"); stockOverviewTable.columns.adjust().draw(); +LoadImagesLazy(); $("#location-filter").on("change", function() { @@ -104,7 +106,7 @@ $("#search").on("keyup", Delay(function() stockOverviewTable.search(value).draw(); }, 200)); -$(document).on('click', '.stockentry-grocycode-product-label-print', function(e) +$(document).on('click', '.product-grocycode-label-print', function(e) { e.preventDefault(); document.activeElement.blur(); diff --git a/public/viewjs/stocksettings.js b/public/viewjs/stocksettings.js index 5e06e23f..d0e39682 100644 --- a/public/viewjs/stocksettings.js +++ b/public/viewjs/stocksettings.js @@ -27,6 +27,11 @@ if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_am $("#stock_default_consume_amount").attr("disabled", ""); } +if (BoolVal(Grocy.UserSettings.stock_auto_decimal_separator_prices)) +{ + $("#stock_auto_decimal_separator_prices").prop("checked", true); +} + RefreshLocaleNumberInput(); $("#stock_default_consume_amount_use_quick_consume_amount").on("click", function() diff --git a/routes.php b/routes.php index 2fd054f1..fc0dd152 100644 --- a/routes.php +++ b/routes.php @@ -99,6 +99,7 @@ $app->group('', function (RouteCollectorProxy $group) { $group->get('/chores', '\Grocy\Controllers\ChoresController:ChoresList'); $group->get('/chore/{choreId}', '\Grocy\Controllers\ChoresController:ChoreEditForm'); $group->get('/choressettings', '\Grocy\Controllers\ChoresController:ChoresSettings'); + $group->get('/chore/{choreId}/grocycode', '\Grocy\Controllers\ChoresController:ChoreGrocycodeImage'); } // Battery routes @@ -110,6 +111,7 @@ $app->group('', function (RouteCollectorProxy $group) { $group->get('/batteries', '\Grocy\Controllers\BatteriesController:BatteriesList'); $group->get('/battery/{batteryId}', '\Grocy\Controllers\BatteriesController:BatteryEditForm'); $group->get('/batteriessettings', '\Grocy\Controllers\BatteriesController:BatteriesSettings'); + $group->get('/battery/{batteryId}/grocycode', '\Grocy\Controllers\BatteriesController:BatteryGrocycodeImage'); } // Task routes @@ -225,6 +227,7 @@ $app->group('/api', function (RouteCollectorProxy $group) { $group->get('/recipes/{recipeId}/fulfillment', '\Grocy\Controllers\RecipesApiController:GetRecipeFulfillment'); $group->post('/recipes/{recipeId}/consume', '\Grocy\Controllers\RecipesApiController:ConsumeRecipe'); $group->get('/recipes/fulfillment', '\Grocy\Controllers\RecipesApiController:GetRecipeFulfillment'); + $group->Post('/recipes/{recipeId}/copy', '\Grocy\Controllers\RecipesApiController:CopyRecipe'); // Chores $group->get('/chores', '\Grocy\Controllers\ChoresApiController:Current'); @@ -232,6 +235,7 @@ $app->group('/api', function (RouteCollectorProxy $group) { $group->post('/chores/{choreId}/execute', '\Grocy\Controllers\ChoresApiController:TrackChoreExecution'); $group->post('/chores/executions/{executionId}/undo', '\Grocy\Controllers\ChoresApiController:UndoChoreExecution'); $group->post('/chores/executions/calculate-next-assignments', '\Grocy\Controllers\ChoresApiController:CalculateNextExecutionAssignments'); + $group->get('/chores/{choreId}/printlabel', '\Grocy\Controllers\ChoresApiController:ChorePrintLabel'); //Printing $group->get('/print/shoppinglist/thermal', '\Grocy\Controllers\PrintApiController:PrintShoppingListThermal'); @@ -241,6 +245,7 @@ $app->group('/api', function (RouteCollectorProxy $group) { $group->get('/batteries/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails'); $group->post('/batteries/{batteryId}/charge', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle'); $group->post('/batteries/charge-cycles/{chargeCycleId}/undo', '\Grocy\Controllers\BatteriesApiController:UndoChargeCycle'); + $group->get('/batteries/{batteryId}/printlabel', '\Grocy\Controllers\BatteriesApiController:BatteryPrintLabel'); // Tasks $group->get('/tasks', '\Grocy\Controllers\TasksApiController:Current'); diff --git a/services/DemoDataGeneratorService.php b/services/DemoDataGeneratorService.php index 9817ec56..46b038cf 100644 --- a/services/DemoDataGeneratorService.php +++ b/services/DemoDataGeneratorService.php @@ -145,6 +145,7 @@ class DemoDataGeneratorService extends BaseService INSERT INTO meal_plan(day, recipe_id) VALUES ('{$saturdayThisWeek}', 2); INSERT INTO meal_plan(day, recipe_id) VALUES ('{$sundayThisWeek}', 4); INSERT INTO meal_plan(day, type, note) VALUES ('{$tuesdayThisWeek}', 'note', '{$this->__t_sql('This is a note')}'); + INSERT INTO meal_plan(day, type, product_id, product_amount) VALUES (DATE('{$mondayThisWeek}', '-1 days'), 'product', 3, 1); INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('Changed towels in the bathroom')}', 'manually', 5); --1 INSERT INTO chores (name, period_type, period_days, assignment_type, assignment_config, next_execution_assigned_to_user_id) VALUES ('{$this->__t_sql('Cleaned the kitchen floor')}', 'dynamic-regular', 7, 'random', '1,2,3,4', 1); --2 diff --git a/services/RecipesService.php b/services/RecipesService.php index 01c7e7ef..0cdc126b 100644 --- a/services/RecipesService.php +++ b/services/RecipesService.php @@ -112,6 +112,23 @@ class RecipesService extends BaseService } } + public function CopyRecipe($recipeId) + { + if (!$this->RecipeExists($recipeId)) + { + throw new \Exception('Recipe does not exist'); + } + + $newName = $this->getLocalizationService()->__t('Copy of %s', $this->getDataBase()->recipes($recipeId)->name); + + $this->getDatabaseService()->ExecuteDbStatement('INSERT INTO recipes (name, description, picture_file_name, base_servings, desired_servings, not_check_shoppinglist, type, product_id) SELECT \'' . $newName . '\', description, picture_file_name, base_servings, desired_servings, not_check_shoppinglist, type, product_id FROM recipes WHERE id = ' . $recipeId); + $lastInsertId = $this->getDatabase()->lastInsertId(); + $this->getDatabaseService()->ExecuteDbStatement('INSERT INTO recipes_pos (recipe_id, product_id, amount, note, qu_id, only_check_single_unit_in_stock, ingredient_group, not_check_stock_fulfillment, variable_amount, price_factor) SELECT ' . $lastInsertId . ', product_id, amount, note, qu_id, only_check_single_unit_in_stock, ingredient_group, not_check_stock_fulfillment, variable_amount, price_factor FROM recipes_pos WHERE recipe_id = ' . $recipeId); + $this->getDatabaseService()->ExecuteDbStatement('INSERT INTO recipes_nestings (recipe_id, includes_recipe_id, servings) SELECT ' . $lastInsertId . ', includes_recipe_id, servings FROM recipes_nestings WHERE recipe_id = ' . $recipeId); + + return $lastInsertId; + } + public function __construct() { parent::__construct(); diff --git a/services/StockService.php b/services/StockService.php index 2088fb8a..bf4695a6 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -193,7 +193,7 @@ class StockService extends BaseService ]); $stockRow->save(); - if (GROCY_FEATURE_FLAG_LABELPRINTER && GROCY_LABEL_PRINTER_RUN_SERVER && $runWebhook) + if (GROCY_FEATURE_FLAG_LABEL_PRINTER && GROCY_LABEL_PRINTER_RUN_SERVER && $runWebhook) { $reps = 1; if ($runWebhook == 2) @@ -208,7 +208,7 @@ class StockService extends BaseService if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) { - $webhookData['duedate'] = $this->getLocalizationService()->__t('DD') . ': ' . $bestBeforeDate; + $webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $bestBeforeDate; } $runner = new WebhookRunner(); diff --git a/views/batteriesjournal.blade.php b/views/batteriesjournal.blade.php index 0d5d2f8b..89f482d1 100644 --- a/views/batteriesjournal.blade.php +++ b/views/batteriesjournal.blade.php @@ -48,6 +48,22 @@
+
+
+
+  {{ $__t('Date range') }} +
+ +
+
diff --git a/views/batteryform.blade.php b/views/batteryform.blade.php index 3d679a3e..b5ecc7f1 100644 --- a/views/batteryform.blade.php +++ b/views/batteryform.blade.php @@ -95,4 +95,35 @@
+ +
+
+ +
+
@stop diff --git a/views/batterytracking.blade.php b/views/batterytracking.blade.php index 67ef03cd..19c430fc 100644 --- a/views/batterytracking.blade.php +++ b/views/batterytracking.blade.php @@ -15,11 +15,17 @@ novalidate>
- - + required + data-target="@batterypicker"> @foreach($batteries as $battery) @@ -48,4 +54,6 @@ @include('components.batterycard')
+ +@include('components.barcodescanner') @stop diff --git a/views/choreform.blade.php b/views/choreform.blade.php index bb1677e8..a06b0d9e 100644 --- a/views/choreform.blade.php +++ b/views/choreform.blade.php @@ -289,5 +289,38 @@ + +
+
+
+ +
+
+
@stop diff --git a/views/choresjournal.blade.php b/views/choresjournal.blade.php index 8f6f9d99..23684b29 100644 --- a/views/choresjournal.blade.php +++ b/views/choresjournal.blade.php @@ -48,6 +48,22 @@ +
+
+
+  {{ $__t('Date range') }} +
+ +
+
diff --git a/views/choretracking.blade.php b/views/choretracking.blade.php index e77b1401..68720103 100644 --- a/views/choretracking.blade.php +++ b/views/choretracking.blade.php @@ -15,11 +15,17 @@ novalidate>
- - + required + data-target="@chorepicker"> @foreach($chores as $chore) @@ -67,4 +73,6 @@ @include('components.chorecard')
+ +@include('components.barcodescanner') @stop diff --git a/views/components/datetimepicker.blade.php b/views/components/datetimepicker.blade.php index 5d111e94..0f087151 100644 --- a/views/components/datetimepicker.blade.php +++ b/views/components/datetimepicker.blade.php @@ -18,8 +18,7 @@ @php if(empty($additionalGroupCssClasses)) { $additionalGroupCssClasses = ''; } @endphp @php if(empty($activateNumberPad)) { $activateNumberPad = false; } @endphp -
+
+ + @stop diff --git a/views/productform.blade.php b/views/productform.blade.php index e08ffefc..0329c5b3 100644 --- a/views/productform.blade.php +++ b/views/productform.blade.php @@ -425,7 +425,7 @@ 'entity' => 'products' )) - @if(GROCY_FEATURE_FLAG_LABELPRINTER) + @if(GROCY_FEATURE_FLAG_LABEL_PRINTER)
-
+
 {{ $__t('Location') }} @@ -87,7 +87,7 @@
-
+
 {{ $__t('User') }} @@ -101,6 +101,22 @@
+
+
+
+  {{ $__t('Date range') }} +
+ +
+
@@ -414,6 +415,12 @@ {{ $currentStockEntry->product_default_location_name }} + + @if(!empty($currentStockEntry->product_picture_file_name)) + + @endif + @include('components.userfields_tbody', array( 'userfields' => $userfields, diff --git a/views/stocksettings.blade.php b/views/stocksettings.blade.php index 7e95c8ae..19799311 100644 --- a/views/stocksettings.blade.php +++ b/views/stocksettings.blade.php @@ -157,6 +157,23 @@ 'min' => 0, 'additionalCssClasses' => 'user-setting-control' )) + +
+
+ + +
+
@endif