Compare commits

..

174 Commits

Author SHA1 Message Date
Bernd Bestel
edfa404ed6 Squashed commit
Always execute migration 9999 (can be used to fix things manually)
Optimized meal plan navigation / date range filtering
Prepared next release
Pulled translations from Transifex
Various code optimizations
2021-07-16 17:32:08 +02:00
Bernd Bestel
2d1d5d46f6 Updated screenshots 2021-07-15 21:04:52 +02:00
Bernd Bestel
45fe6a6362 Updated create_release_package.bat 2021-07-15 20:58:01 +02:00
Bernd Bestel
23f697f812 Pulled translations from Transifex 2021-07-15 20:53:12 +02:00
Bernd Bestel
263181baa0 Updated dependencies 2021-07-15 20:48:27 +02:00
Bernd Bestel
9132e222fe Upgraded bwip-js 2021-07-15 20:34:22 +02:00
Bernd Bestel
2bc108fe3e Only init meal plan menu entry when feature flag is enabled 2021-07-15 20:19:04 +02:00
Bernd Bestel
02d0121f4d Fixed meal plan note adding 2021-07-15 20:11:49 +02:00
Bernd Bestel
b5acb4c49b Settings menu order == sidebar menu order 2021-07-15 20:09:33 +02:00
Bernd Bestel
cd05a95a0f Fix meal plan section title in (some) foreign languages 2021-07-15 20:07:31 +02:00
Bernd Bestel
2d2700cacb Implemented meal plan sections (closes #370) 2021-07-15 17:54:48 +02:00
Bernd Bestel
1bacd8e13d Typo / added missing changelog 2021-07-14 18:15:02 +02:00
Bernd Bestel
f6986fac18 Fixed cost/calories calculation for nested recipes (references #1264) 2021-07-14 16:27:03 +02:00
Bernd Bestel
8b6f882edc Fixed missing FEATURE_FLAG_SHOPPINGLIST handling on /stockoverview, /mealplan and /stockentries pages (references #322) 2021-07-13 21:24:08 +02:00
Bernd Bestel
91d8eaeb74 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)
2021-07-13 19:29:23 +02:00
Bernd Bestel
8d2c3ae584 Partly reverted b856911f0f
Loading localization strings async for the fronted currently doesn't work in all cases...
2021-07-12 21:20:39 +02:00
Bernd Bestel
18e8fc8293 Added missing localization string 2021-07-12 20:48:14 +02:00
Bernd Bestel
e1c702f3d0 Typo 2021-07-12 20:45:30 +02:00
Bernd Bestel
71cede74a3 Make it possible to copy meal plan days (closes #573) 2021-07-12 20:44:42 +02:00
Bernd Bestel
7b0bc9e472 Fixed stock entries page missing columns 2021-07-12 19:55:53 +02:00
Bernd Bestel
8cb8611b4f Added a new product option "Should not be frozen" (closes #1320) 2021-07-12 19:27:21 +02:00
Bernd Bestel
c048f403e6 Check for missing localization strings also client side (dev mode only) 2021-07-12 19:10:07 +02:00
Bernd Bestel
4aee175105 Keep the newest instead of the oldest on campacting stock entries 2021-07-12 18:58:49 +02:00
Bernd Bestel
cf8604e984 Show row_created_timestamp on the stock entries page (closes #1063) 2021-07-12 18:25:07 +02:00
Bernd Bestel
cdf6ac78e2 Optimized product edit page default button handling (closes #1276) 2021-07-12 18:15:57 +02:00
Bernd Bestel
70433aace5 Added an status filter to only show in-stock products on the stock overview page (closes #1263) 2021-07-12 18:02:57 +02:00
Bernd Bestel
247221950d Never extend the original due date on when opening a product which has default_best_before_days (closes #1342) 2021-07-12 17:56:09 +02:00
Bernd Bestel
866d6647d2 Small meal plan page adjustments 2021-07-12 17:43:30 +02:00
Bernd Bestel
f1da3ef5e8 Optimized clean response handling 2021-07-12 17:08:59 +02:00
Bernd Bestel
2cc4f4d382 Make sure to clean the response before returning files
Was a problemw when returning images and there were leading empty lines in config.php which seem to get added to the response always...
2021-07-12 15:34:26 +02:00
Bernd Bestel
6659a5cd08 Add an option to make Userfields mandatory (closes #1339) 2021-07-11 22:05:08 +02:00
Bernd Bestel
21c221b520 Improved recipe page / group by fulfillment status 2021-07-11 21:32:24 +02:00
Bernd Bestel
55807bfc94 Auto-compact stock entries (closes #1343) 2021-07-11 21:06:05 +02:00
Bernd Bestel
696e9b3e28 Typo 2021-07-11 19:47:27 +02:00
Bernd Bestel
198216f38b Make it possible to track any information on chore execution (by using Userfields, closes #825) 2021-07-11 19:44:06 +02:00
Bernd Bestel
27b46e1abf Optimized meal plan week navigation 2021-07-11 18:44:04 +02:00
Bernd Bestel
7380175093 Make it possible to mark meal plan entries as done (closes #924) 2021-07-11 18:32:26 +02:00
Bernd Bestel
1ad0360e42 Fixed untranslated string 2021-07-11 10:55:29 +02:00
Bernd Bestel
2503590463 config-dist.php formatting 2021-07-11 10:34:46 +02:00
Bernd Bestel
40e16db01f Fixed consume amount validation when consuming a parent product (fixes #1306)
More a workaround for now, the max constraint is just removed when the product has child products,
but the amount to be consumed is checked by StockService anyway, so should not be a problem...
2021-07-11 10:21:36 +02:00
Bernd Bestel
684aef0a42 Made migration path faster (references #695) 2021-07-11 10:06:31 +02:00
Bernd Bestel
dd9cae5482 Fixed migration path (references #695) 2021-07-11 09:58:39 +02:00
Bernd Bestel
7ee15946c7 Improved page loading time of /recipes and /mealplan when having a big meal plan (closes #695) 2021-07-10 22:56:39 +02:00
Bernd Bestel
6660e1ff73 Fixed mealplan-shadow recipe handling when removing an meal plan entry (references #1391) 2021-07-10 20:51:20 +02:00
Bernd Bestel
2847908523 Some small recipe page adjustments 2021-07-10 20:35:38 +02:00
Bernd Bestel
9b37c450ed Fixed API error when adding missing products to the shopping list from a meal plan entry (references #b0d38b87de) 2021-07-10 19:56:35 +02:00
Bernd Bestel
003aea6047 Removed unnecessary migration (references #1264) 2021-07-10 19:15:17 +02:00
Bernd Bestel
9d1440fb45 Typo... 2021-07-10 18:30:50 +02:00
Bernd Bestel
832d83dfde Fixed indirect QU conversion factors (fixes #1264) 2021-07-10 18:28:19 +02:00
Bernd Bestel
90a0caf1dc Fixed meal plan recipe servings stock fulfillment checking (fixes #1391) 2021-07-10 12:32:29 +02:00
Bernd Bestel
d3c134e13f Fixed nested recipe ingredient amount calculation (fixes #1252) 2021-07-10 11:16:51 +02:00
Bernd Bestel
269ae34db3 Fixed battery_charge_cycles.battery_id data type
Kind of, doesn't really matter for SQLite; doesn't change anything practically
2021-07-10 09:33:10 +02:00
Bernd Bestel
8ff8c1ac5d Made the used grocycode barcode type configurable
DataMatrix reading via Quagga2 doesn't work currently, so default to an supported 1D barcode (=> Code128)
2021-07-09 23:08:47 +02:00
Bernd Bestel
2638bce851 Improve handling of not in-stock but valid manually entered products on the consume and transfer page (references #1429) 2021-07-09 22:16:08 +02:00
Bernd Bestel
72e6ed76bf Fixed an error when adding object and there are no Userfields (references b0d38b87de) 2021-07-09 21:30:35 +02:00
Bernd Bestel
8348438148 Workaround for file upload problem when the file name contains Umlaute (seems to be a Linux only issue, fixes #1382) 2021-07-09 21:23:04 +02:00
Bernd Bestel
338a5016cf Allow cyrillic letters in API filter values (fixes #1296) 2021-07-09 20:23:30 +02:00
Bernd Bestel
11b71e3af2 Issue template update 2021-07-08 20:52:28 +02:00
Bernd Bestel
8c5c12cb47 Added new columns on the stock overview page (closes #1351) 2021-07-08 20:42:07 +02:00
Bernd Bestel
8b977644f7 Added the product descrption as a column on the stock overview page (closes #1362) 2021-07-08 20:22:51 +02:00
Bernd Bestel
7595d640f5 Return empty Userfields empty (closes #1412) 2021-07-08 20:12:58 +02:00
Bernd Bestel
14cd6ca3bf Return numbers as numbers on all API endpoints 2021-07-08 19:34:16 +02:00
Bernd Bestel
633b26bf7e Add recipe ingredient notes to the corresponding shopping list item (closes #1397) 2021-07-06 20:19:50 +02:00
Bernd Bestel
1ead23cb87 Added on option to only show in-stock products on the /products page (closes #1388) 2021-07-06 20:08:02 +02:00
Bernd Bestel
6530d0f9df Clarify that "Group by product group" (printing a shopping list) works only for the list layout type (closes #1405) 2021-07-06 19:48:55 +02:00
Bernd Bestel
135ac118b0 Added a filter for only done items on the /shoppinglist page (closes #1406) 2021-07-06 19:40:26 +02:00
Bernd Bestel
70d51c757b Only show in-stock products on the /consume page (closes #1429) 2021-07-06 19:31:55 +02:00
Bernd Bestel
ffad8bfa7f Make it possible to search on the stock overview page for product barcodes (closes #1443) 2021-07-06 19:25:34 +02:00
Bernd Bestel
ffc5ba013f Added new API things related to #1494 and #1493 to grocy.openapi.json 2021-07-06 19:17:43 +02:00
Bernd Bestel
aaa054e0a5 Also return the next_execution_assigned_user for the /chores API endpoint (closes #1493)
Include the user and category object for the /tasks API endpoint (closes #1494)
2021-07-06 19:07:45 +02:00
Bernd Bestel
54bf7ed659 Produce a schema-valid OpenAPI specification (closes #1457) 2021-07-05 23:23:59 +02:00
Bernd Bestel
6462dd8af6 Removed legacy error suppression 2021-07-05 22:51:02 +02:00
Bernd Bestel
079437384e Use the now available @once directive instead of the legacy hack to only include component scripts once 2021-07-05 22:49:51 +02:00
Bernd Bestel
10fcd9177c Define error reporting 2021-07-05 22:40:01 +02:00
Bernd Bestel
b0d38b87de PHP 8 support 2021-07-05 17:48:34 +02:00
Bernd Bestel
d9470cb377 Added .devtools scripts to package.json 2021-07-05 17:47:47 +02:00
Bernd Bestel
b4ce0555d9 Upgraded/Replaced rubellum/slim-blade-view to support Laravel Blade Templates v8 2021-07-05 17:13:01 +02:00
Bernd Bestel
9ba7ee54a7 Invalidate browser cache on language change 2021-07-04 21:54:58 +02:00
Bernd Bestel
cb24a7149f Revert "Upgraded gettext/gettext (+ JS-Translator)"
This reverts commit 9abb92763d.
2021-07-04 21:47:55 +02:00
Bernd Bestel
9abb92763d Upgraded gettext/gettext (+ JS-Translator) 2021-07-04 21:47:10 +02:00
Bernd Bestel
54d4c90ec4 Upgraded morris/lessql 2021-07-04 20:06:49 +02:00
Bernd Bestel
76037d1f4e Upgraded gumlet/php-image-resize 2021-07-04 20:05:00 +02:00
Bernd Bestel
735743047f Upgraded eluceo/ical 2021-07-04 20:02:04 +02:00
Bernd Bestel
82c474d0ae Allow hyphens in API filter value (fixes #1333) 2021-07-04 17:48:58 +02:00
Bernd Bestel
0dc37fb361 Don't allow a min. stock amount for child products when the parent has "Accumulate sub products min. stock amount" set (references #1409) 2021-07-04 17:36:44 +02:00
Bernd Bestel
734e174442 Fixed the "Add as barcode to existing product" productpicker workflow from the /shoppinglistitem page (fixes #1262) 2021-07-04 15:46:19 +02:00
Bernd Bestel
4086a63ebd Improved tables horizontally scrollbar appearance (fixes #1476) 2021-07-04 15:34:39 +02:00
Bernd Bestel
f2a0b7cded Pulled translations from Transifex 2021-07-04 12:31:17 +02:00
Bernd Bestel
bda40dfbb9 Updated dependencies 2021-07-04 12:26:14 +02:00
Bernd Bestel
fbb0064505 Consider selected QU for calories calculation for "Only check if any amount is in stock" recipe ingredients (fixes #1338) 2021-07-03 20:01:49 +02:00
Bernd Bestel
4b02ac8f35 Fixed shopping list setting initialization (fixes #1344) 2021-07-03 19:43:32 +02:00
Bernd Bestel
47c936e026 Reworked authentication related menu item handling (fixes #1462) 2021-07-03 19:40:42 +02:00
Bernd Bestel
bcf963ac49 Fixed self production amount was wrong for tare weight handling enabled products (fixes #1431) 2021-07-03 18:30:53 +02:00
Bernd Bestel
765ba77621 Fixed shopping list unit/total price QU handling (fixes #1460) 2021-07-03 18:15:30 +02:00
Bernd Bestel
0f88eed08c Upgraded to PHP-CS-Fixer v3 2021-07-03 17:46:47 +02:00
Bernd Bestel
766eae5a7a Remove accidentally committed debug statement 2021-07-02 22:19:54 +02:00
Bernd Bestel
90b8ea15ff Also delete downscaled image files when deleting an image (closes #1499) 2021-07-02 20:50:52 +02:00
Bernd Bestel
34ffb96ae3 Enforce file groups 2021-07-02 20:29:53 +02:00
Bernd Bestel
74d745cfc4 Typo 2021-07-02 20:29:25 +02:00
Bernd Bestel
cc9345136c Use exact search for product filter on /stockjournal and /stockjournalsummary (fixes #1353) 2021-07-02 18:24:08 +02:00
Bernd Bestel
5ba9bbbcd1 Fixed mssing-recipe-ingredients-to-shopping-list checkbox inner-click (fixes #1383) 2021-07-02 18:04:20 +02:00
Bernd Bestel
cae924eb81 Fixed shopping list QU handling (fixes #1385, fixes #1384) 2021-07-02 17:37:06 +02:00
Bernd Bestel
187d48f93d Use stock_log location instead of product location for stock journal (fixes #1381) 2021-07-02 17:04:40 +02:00
Bernd Bestel
9f833b9bd5 Prvent potentially duplicate stock items in drodpown on /consume and /transfer page (fixes #1368) 2021-07-02 16:59:37 +02:00
Bernd Bestel
b856911f0f Browser-cache localization strings (+ new API endpoint to get them) 2021-06-29 20:24:02 +02:00
Bernd Bestel
d18a8d8b56 Added changelog for #1527 2021-06-29 17:43:13 +02:00
André Heuer
416c138017 Added support for Code 39 (#1527) 2021-06-29 17:40:28 +02:00
Bernd Bestel
76cfe7fece Fixed productcard spoil rate (fixes #1319) 2021-06-28 19:43:08 +02:00
Bernd Bestel
7587ead732 Fixed /tasks group by category (fixes #1274) 2021-06-28 19:31:27 +02:00
Bernd Bestel
69f8c237ff Fixed /stockentries group by purchased_date (fixes #1419) 2021-06-28 19:14:15 +02:00
Bernd Bestel
b8e15b990b Typo 2021-06-28 18:39:08 +02:00
Bernd Bestel
35fb87ab1e Squashed commit
Use managed fonts
Include userentities dynamically in grocy.openapi.json for /userfields/{entity}/{objectId} endpoints (closes #1218)
Fixed userfieldsform load / save (for products and recipes) handling (fixes #1302)
Fixed PUT/DELETE /objects/{entity}/{objectId} when the given object id was invalid (fixes #1396)
Allow arrays in HTMLPurifier (fixes #1407)
2021-06-28 17:00:16 +02:00
Bernd Bestel
acb81187d9 Fixed missing shopping_location_id on stock transfer actions (fixes #1408) 2021-06-27 20:55:38 +02:00
Bernd Bestel
5153818b4e Fixed shopping_list_id when adding products from /stockoverview to the shopping list (fixes #1442) 2021-06-27 20:46:21 +02:00
Bernd Bestel
5c3809aa33 Exclude inactive products from recipe ingredient edit page (fixes #1448) 2021-06-27 20:34:18 +02:00
Bernd Bestel
95d212a076 Added missing shopping_list_id for ShoppingListItem in grocy.openapi.json (fixes #1451) 2021-06-27 20:30:05 +02:00
Bernd Bestel
7133c85deb Persist filters on reload (recipe selection change) on the /recipes page (fixes #1455) 2021-06-27 20:28:12 +02:00
Bernd Bestel
7ab59273da Allow links and iframes in HTMLPurifier (fixes #1461) 2021-06-27 20:13:24 +02:00
Bernd Bestel
33ea1e56cf Trigger help-tooltips also by click (instead of only hover, which is Bootstraps default) (fixes #1468) 2021-06-27 19:34:28 +02:00
Bernd Bestel
b7a6b91039 Fixed stock QU change restriction / include undone stock transactions (fixes #1473) 2021-06-27 19:11:45 +02:00
Bernd Bestel
e646dd9332 Fixed barcode QU was not saved for single QU products (fixes #1504) 2021-06-27 19:04:09 +02:00
Bernd Bestel
30e5cc3bc3 Fixed filter clearing on /quantityunits (fixes #1511) 2021-06-27 18:42:15 +02:00
Bernd Bestel
e44f4802d5 Fixed filter clearing on /products (fixes #1512) 2021-06-27 18:41:07 +02:00
Bernd Bestel
9ef48e79cd Remove user request parameter when clearing filter on /choresoverview (fixes #1513) 2021-06-27 18:39:29 +02:00
Bernd Bestel
3acad5056a Fixed inventory action hint when entered amount equals current stock amount (fixes #1522) 2021-06-27 18:37:18 +02:00
Bernd Bestel
44d6173569 Also disable generic consume context menu item on /stockoverview when the item is not in stock (fixes #1523) 2021-06-27 18:34:58 +02:00
Bernd Bestel
9a0cad079c Fixed undoing consume/open from notification on /stockentries (fixes #1524) 2021-06-27 18:32:22 +02:00
Katharina Bogad
f5da53a761 Migrated bootstrap3 col-xs-* to bootstrap4 col-* classes (#1521) 2021-06-24 22:46:47 +02:00
Bernd Bestel
f8fa5db3e7 Fixed multiple datetimepicker contextual timeago (references #1520) 2021-06-24 07:46:32 +02:00
Bernd Bestel
5e189c8a4a Fixed multi instace date/time Userfields (fixes #1520) 2021-06-23 22:13:54 +02:00
Bernd Bestel
9e3c68982b Added changelog for #1380 2021-06-20 13:27:16 +02:00
tank0226
b3ed80d186 Improved support for other LDAP servers (#1380)
Co-authored-by: kuanhong <>
2021-06-20 13:22:18 +02:00
Bernd Bestel
a4f7aac963 Mention newly required PHP extensions in changelog (references #1273) 2021-06-20 13:19:09 +02:00
Bernd Bestel
c45034e6b1 Fixed composer.lock merge conflict (references #1273) 2021-06-18 20:58:51 +02:00
Bernd Bestel
5ad4d9f421 Added changelog for #1273 2021-06-18 20:57:08 +02:00
Marc Ole Bulling
eb135aee39 Add support for printing shoppinglist with thermal printer (#1273)
* Added escpos-php library

* Added button to shoppinglist print menu

* Added to translation

* Added basic printing logic and API call

* Working implementation for printing with the API

* Added openapi json

* Correctly parsing boolean parameter

* Working button in UI

* Change to grocy formatting

* Add Date

* Only show thermal print button when Feature Flag is set

* Fixed API call and added error message parsing

* Undo translation

* Add flag to print quantities as well

* Added printing notes

* Added quantity conversion

* Increse feed

* Fixed that checkbox was undefined, as dialog was already closed

* Added padding

* Formatting

* Added note about user permission

* Fixed error when using notes instead of products

* Review

- Default FEATURE_FLAG_THERMAL_PRINTER to disabled
- Added missing localization strings (and slightly adjusted one)

* Fixed merge conflicts

Co-authored-by: Bernd Bestel <bernd@berrnd.de>
2021-06-18 20:45:42 +02:00
Bernd Bestel
fe59fac1c3 Fixed client side webhook runner (references #1500) 2021-06-13 08:40:16 +02:00
Bernd Bestel
26979a4321 Set HTMLPurifier cache path (fixes #1497) 2021-06-12 20:56:58 +02:00
Kendell R
a0e5f45da3 More night mode improvements (#1336)
* More night mode improvements

* Update grocy_night_mode.css

* Update extensions.js

* Update grocy_night_mode.css

* Update public/css/grocy_night_mode.css
2021-06-12 20:39:08 +02:00
Bernd Bestel
739379fabb Fixed stock entry grocycode download (references #1500) 2021-06-12 20:15:48 +02:00
Bernd Bestel
96fff2e5f4 Added changelog for #1500 2021-06-12 17:21:48 +02:00
Katharina Bogad
2471e78188 Grocycode, label printing (#1500)
* Grocycode: Productpicker, StockService

* Grocycode: Datamatrix generation

* Grocycode: Display in UI, make Images downloadable

* Grocycode: Do not show on product card

* Grocycode: Stockentry Label view

* Grocycode: Webhooks & Labelprinter Feature

* Grocycode: Manual Label printing

* Grocycode: Print Label from product form

* Quagga2: use zxing for DataMatrix recognition

* Grocycode: Default settings for label printing

* Prepare merge of master

* Grocycode: docs

* Docs: label printing webhook

* Review

- "grocy" is currently written lower-case everywhere, so let's do this also for "grocycode"
- Unified phrases / capitalization
- Minor UI adjustments (mainly context menu item ordering / ordering/spacing on product edit page)
- Documented API changes for Swagger UI (grocy.openapi.json)
- Reverted German localizations (those are managed via Transifex; would cause conflicts when manually edited - will import them later there)
- Reverted a somehow messed up localization string (productform/help text for `cumulate_min_stock_amount_of_sub_products`)
- Suppress deprecation warnings when generating Datamatrix PNG (otherwise the PNG is invalid, https://github.com/jucksearm/php-barcode/issues/3)
- Default `FEATURE_FLAG_LABELPRINTER` to disabled

Co-authored-by: Bernd Bestel <bernd@berrnd.de>
2021-06-12 17:21:12 +02:00
Bernd Bestel
d23fda245e Fixed that the number picker up/down buttons did not work when the input field was empty or contained an invalid number 2021-03-31 22:26:27 +02:00
Bernd Bestel
791a17fcad Default shopping list item amount to 1 2021-03-31 22:22:28 +02:00
Bernd Bestel
dabc48fed3 Typo 2021-03-31 22:15:41 +02:00
rozgonik
980778e599 Fix untranslateable hint values (#1435) 2021-03-31 22:14:42 +02:00
Kai
68c5fd0617 Update README.md (#1432) 2021-03-31 22:14:05 +02:00
Edward Betts
7bbcec91aa Correct spelling (#1420) 2021-03-31 22:12:51 +02:00
Bernd Bestel
c483c34598 Display calories always per single serving (fixes #1359) 2021-02-21 20:57:34 +01:00
Bernd Bestel
906a9db628 Fixed embedded /transfer from /stockentries (fixes #1303) 2021-02-21 19:36:37 +01:00
Bernd Bestel
5583074001 Don't initialise numeric Userfields with 1.0 (fixes #1312) 2021-02-21 19:14:49 +01:00
Bernd Bestel
e4c8f6b023 Don't require 1 to be in stock for "Only check if any amount is in stock" 2021-02-21 19:10:10 +01:00
Bernd Bestel
4555bf3b63 Enforce product barcodes to be unique (references #1205) 2021-02-21 18:55:48 +01:00
Bernd Bestel
2aca551692 Fixed product page QU conversion hint pluralisation (fixes #1352) 2021-02-21 18:24:04 +01:00
Bernd Bestel
f5eff8ab49 Include due_type = "Expiration date" products in /stock/volatile API endpoint (fixes #1372) 2021-02-21 18:18:34 +01:00
Bernd Bestel
36f5fb23e9 Added changelog for #1347 2021-02-21 18:13:16 +01:00
Lauri Niskanen
33dcd17fbd Fix rounding error on total value calculation (#1347)
* Fix rounding error on total value calculation

* Remove unused 'amountSum' calculation
2021-02-21 18:10:41 +01:00
Bernd Bestel
3d82c9abbd Disabled platform-check (references #1285) 2021-02-18 12:14:57 +01:00
Bernd Bestel
779ac31ffe Added changelog for #1332 2021-02-14 12:56:21 +01:00
Lauri Niskanen
0a6c7d73a7 Hide unsuitable fields from printed recipe pages (#1332)
* Hide unsuitable fields from printed recipe pages

Resolves #1330.

* Use proper total energy label in the recipe page
2021-02-14 12:55:01 +01:00
Bernd Bestel
fc05044353 Added changelog for #1331 2021-02-14 12:51:40 +01:00
Kendell R
55ac768521 Night mode improvements (#1331)
* Night mode readability improvements

* Update grocy_night_mode.css

* Update grocy_night_mode.css
2021-02-14 12:49:44 +01:00
Bernd Bestel
a455a01204 Fixed print layout display handling (fixes #1272) 2021-01-30 13:11:40 +01:00
Bernd Bestel
8b963ae0f1 Merge branch 'master' of https://github.com/grocy/grocy 2021-01-30 13:07:16 +01:00
Bernd Bestel
a1adc80c29 Fixed consuming Scan Mode timing (fixes #1292) 2021-01-30 13:06:44 +01:00
Marius Boro
760914bf82 Update grocy_night_mode.css (#1269)
Night mode updates for Grocy 3
2021-01-30 12:49:51 +01:00
Bernd Bestel
42689ecefe Added changelog for #1297 2021-01-30 12:49:17 +01:00
Marc Ole Bulling
c889416c0a Fix for #1289 and #1261 (#1297) 2021-01-30 12:47:12 +01:00
Bernd Bestel
bfb5525ec1 Added changelog for #1286 2021-01-30 12:41:09 +01:00
Marc Ole Bulling
20380faeb3 Fix for #1271 (#1286) 2021-01-30 12:39:10 +01:00
Bernd Bestel
5ecd3a585e Added changelog for #1269 2021-01-12 18:14:32 +01:00
Bernd Bestel
bfa3347a20 Fixed that editing stock entries was not possible (fixes #1268) 2021-01-12 18:04:20 +01:00
Bernd Bestel
e42f4b405d Fixed PHP warning (fixes #1267) 2021-01-12 10:40:14 +01:00
Bernd Bestel
27169e1428 Fixed constant typo (fixes #1260) 2021-01-06 09:31:36 +01:00
347 changed files with 15962 additions and 7581 deletions

View File

@@ -10,7 +10,7 @@ for /f "tokens=*" %%a in ('jq .Version versiontemp.json --raw-output') do set ve
del versiontemp.json
del "%releasePath%\grocy_%version%.zip"
7za a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build.bat -xr!composer.json -xr!composer.lock -xr!package.json -xr!yarn.lock
7za a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build.bat -xr!composer.json -xr!composer.lock -xr!package.json -xr!yarn.lock -xr!docs
7za a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
7za rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
7za d "%releasePath%\grocy_%version%.zip" data\*.* data\storage data\viewcache\*

View File

@@ -3,7 +3,7 @@ name: Bug report
about: If you've found something that does not work, please report it to help improve
grocy
title: 'Bug: '
labels: bug, ui-bug
labels: bug
assignees: ''
---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 111 KiB

95
.php-cs-fixer.php Normal file
View File

@@ -0,0 +1,95 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude(['vendor'])
->ignoreVCSIgnored(true)
->files()->name('*.php')
->in(__DIR__)
;
$cfg = new PhpCsFixer\Config();
return $cfg
->setRules([
'@PSR2' => true,
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'combine_consecutive_unsets' => true,
'class_attributes_separation' => true,
'multiline_whitespace_before_semicolons' => false,
'single_quote' => true,
// 'blank_line_after_opening_tag' => true,
// 'blank_line_before_return' => true,
'braces' => [
'allow_single_line_closure' => true,
'position_after_anonymous_constructs' => 'same',
'position_after_control_structures' => 'next',
'position_after_functions_and_oop_constructs' => 'next',
],
// 'cast_spaces' => true,
// 'class_definition' => array('singleLine' => true),
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => true,
'function_typehint_space' => true,
'single_line_comment_style' => ['comment_types' => ['hash']],
'include' => true,
'lowercase_cast' => true,
// 'native_function_casing' => true,
// 'new_with_braces' => true,
// 'no_blank_lines_after_class_opening' => true,
// 'no_blank_lines_after_phpdoc' => true,
// 'no_empty_comment' => true,
// 'no_empty_phpdoc' => true,
// 'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
// 'no_mixed_echo_print' => array('use' => 'echo'),
'no_multiline_whitespace_around_double_arrow' => true,
// 'no_short_bool_cast' => true,
// 'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_around_offset' => true,
// 'no_trailing_comma_in_list_call' => true,
// 'no_trailing_comma_in_singleline_array' => true,
// 'no_unneeded_control_parentheses' => true,
// 'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
// 'normalize_index_brace' => true,
'object_operator_without_whitespace' => true,
// 'php_unit_fqcn_annotation' => true,
// 'phpdoc_align' => true,
// 'phpdoc_annotation_without_dot' => true,
// 'phpdoc_indent' => true,
// 'phpdoc_inline_tag' => true,
// 'phpdoc_no_access' => true,
// 'phpdoc_no_alias_tag' => true,
// 'phpdoc_no_empty_return' => true,
// 'phpdoc_no_package' => true,
// 'phpdoc_no_useless_inheritdoc' => true,
// 'phpdoc_return_self_reference' => true,
// 'phpdoc_scalar' => true,
// 'phpdoc_separation' => true,
// 'phpdoc_single_line_var_spacing' => true,
// 'phpdoc_summary' => true,
// 'phpdoc_to_comment' => true,
// 'phpdoc_trim' => true,
// 'phpdoc_types' => true,
// 'phpdoc_var_without_name' => true,
// 'pre_increment' => true,
// 'return_type_declaration' => true,
// 'self_accessor' => true,
// 'short_scalar_cast' => true,
'single_blank_line_before_namespace' => true,
// 'single_class_element_per_statement' => true,
// 'space_after_semicolon' => true,
// 'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
// 'trailing_comma_in_multiline_array' => true,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'whitespace_after_comma_in_array' => true,
])
->setIndent("\t")
->setLineEnding("\n")
->setUsingCache(false)
->setFinder($finder)
;

97
.php_cs
View File

@@ -1,97 +0,0 @@
<?php
return PhpCsFixer\Config::create()
->setRules(array(
'@PSR2' => true,
'array_indentation' => true,
'array_syntax' => array('syntax' => 'short'),
'combine_consecutive_unsets' => true,
'method_separation' => true,
'no_multiline_whitespace_before_semicolons' => true,
'single_quote' => true,
'binary_operator_spaces' => array(
'align_double_arrow' => false,
'align_equals' => false,
),
// 'blank_line_after_opening_tag' => true,
// 'blank_line_before_return' => true,
'braces' => array(
'allow_single_line_closure' => true,
'position_after_anonymous_constructs' => 'same',
'position_after_control_structures' => 'next',
'position_after_functions_and_oop_constructs' => 'next',
),
// 'cast_spaces' => true,
// 'class_definition' => array('singleLine' => true),
'concat_space' => array('spacing' => 'one'),
'declare_equal_normalize' => true,
'function_typehint_space' => true,
'hash_to_slash_comment' => true,
'include' => true,
'lowercase_cast' => true,
// 'native_function_casing' => true,
// 'new_with_braces' => true,
// 'no_blank_lines_after_class_opening' => true,
// 'no_blank_lines_after_phpdoc' => true,
// 'no_empty_comment' => true,
// 'no_empty_phpdoc' => true,
// 'no_empty_statement' => true,
'no_extra_consecutive_blank_lines' => array(
'extra',
'parenthesis_brace_block',
'square_brace_block',
'throw',
'use',
),
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
// 'no_mixed_echo_print' => array('use' => 'echo'),
'no_multiline_whitespace_around_double_arrow' => true,
// 'no_short_bool_cast' => true,
// 'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_around_offset' => true,
// 'no_trailing_comma_in_list_call' => true,
// 'no_trailing_comma_in_singleline_array' => true,
// 'no_unneeded_control_parentheses' => true,
// 'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
// 'normalize_index_brace' => true,
'object_operator_without_whitespace' => true,
// 'php_unit_fqcn_annotation' => true,
// 'phpdoc_align' => true,
// 'phpdoc_annotation_without_dot' => true,
// 'phpdoc_indent' => true,
// 'phpdoc_inline_tag' => true,
// 'phpdoc_no_access' => true,
// 'phpdoc_no_alias_tag' => true,
// 'phpdoc_no_empty_return' => true,
// 'phpdoc_no_package' => true,
// 'phpdoc_no_useless_inheritdoc' => true,
// 'phpdoc_return_self_reference' => true,
// 'phpdoc_scalar' => true,
// 'phpdoc_separation' => true,
// 'phpdoc_single_line_var_spacing' => true,
// 'phpdoc_summary' => true,
// 'phpdoc_to_comment' => true,
// 'phpdoc_trim' => true,
// 'phpdoc_types' => true,
// 'phpdoc_var_without_name' => true,
// 'pre_increment' => true,
// 'return_type_declaration' => true,
// 'self_accessor' => true,
// 'short_scalar_cast' => true,
'single_blank_line_before_namespace' => true,
// 'single_class_element_per_statement' => true,
// 'space_after_semicolon' => true,
// 'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
// 'trailing_comma_in_multiline_array' => true,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'whitespace_after_comma_in_array' => true,
))
->setIndent("\t")
->setLineEnding("\n")
;

View File

@@ -14,4 +14,4 @@
"php-cs-fixer.formatHtml": true,
"php-cs-fixer.autoFixBySemicolon": true,
"php-cs-fixer.onsave": true,
}
}

View File

@@ -1,7 +1,7 @@
<div align="center">
<img alt="Logo" height="50" src="https://raw.githubusercontent.com/grocy/grocy/master/public/img/grocy_logo.svg?sanitize=true"/>
<img alt="Logo" height="50" src="https://raw.githubusercontent.com/grocy/grocy/master/public/img/grocy_logo.svg?sanitize=true" />
<h3>ERP beyond your fridge</h3>
<h5> grocy is a web-based self-hosted groceries & household management solution for your home</h5>
<h4>grocy is a web-based self-hosted groceries & household management solution for your home<br>Created by <a href="https://github.com/berrnd">@berrnd</a></h4>
</div>
-----
@@ -21,10 +21,10 @@ Please don't send me private messages regarding grocy help. I check the issue tr
See the website for a list of community contributed Add-ons / Tools: [https://grocy.info/addons](https://grocy.info/addons)
## Motivation
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# Windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
## How to install
> Checkout [grocy-desktop](https://github.com/grocy/grocy-desktop), if you want to run grocy without having to manage a webserver just like a normal ("indows) desktop application.
> Checkout [grocy-desktop](https://github.com/grocy/grocy-desktop), if you want to run grocy without having to manage a webserver just like a normal (Windows) desktop application.
>
> Directly download the [latest release](https://releases.grocy.info/latest-desktop) - the installation is nothing more than just clicking 2 times "next".
@@ -38,7 +38,7 @@ grocy is technically a pretty simple PHP application, so the basic notes to get
- Include `try_files $uri /index.php$is_args$query_string;` in your location block if you use nginx
- Or disable URL rewriting (see the option `DISABLE_URL_REWRITING` in `data/config.php`)
- Based on user reports, the minmimum required/working runtime is PHP 7.2 with SQLite 3.9.0
- However, I don't really care about supporting old runtime stuff, currently everything is only tested against (means 100 % works with) PHP 7.4 with SQLite 3.27.2
- However, I don't really care about supporting old runtime stuff, currently everything is only tested against (means 100 % works with) PHP 8.0 with SQLite 3.27.2
- &rarr; Default login is user `admin` with password `admin`, please change the password immediately (user menu at the top right corner)
Alternatively clone this repository (the `release` branch always references the latest released version, or checkout the latest tagged revision) and install Composer and Yarn dependencies manually.
@@ -65,7 +65,7 @@ _RTL languages are unfortunately not yet supported._
## Things worth to know
### REST API & data model documentation
### REST API
See the integrated Swagger UI instance on [/api](https://demo.grocy.info/api).
### Barcode readers & camera scanning

20
app.php
View File

@@ -5,6 +5,7 @@ use Grocy\Helpers\UrlManager;
use Grocy\Middleware\CorsMiddleware;
use Psr\Container\ContainerInterface as Container;
use Slim\Factory\AppFactory;
use Slim\Views\Blade;
// Load composer dependencies
require_once __DIR__ . '/vendor/autoload.php';
@@ -14,11 +15,20 @@ require_once GROCY_DATAPATH . '/config.php';
require_once __DIR__ . '/config-dist.php'; // For not in own config defined values we use the default ones
require_once __DIR__ . '/helpers/ConfigurationValidator.php';
// Error reporting definitions
if (GROCY_MODE === 'dev')
{
error_reporting(E_ALL);
}
else
{
error_reporting(E_ALL ^ (E_NOTICE | E_WARNING | E_DEPRECATED));
}
// Definitions for dev/demo/prerelease mode
if ((GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease') && !defined('GROCY_USER_ID'))
{
define('GROCY_USER_ID', 1);
define('GROCY_SHOW_AUTH_VIEWS', true);
}
// Definitions for disabled authentication mode
@@ -28,8 +38,6 @@ if (GROCY_DISABLE_AUTH === true)
{
define('GROCY_USER_ID', 1);
}
define('GROCY_SHOW_AUTH_VIEWS', false);
}
// Check if any invalid entries in config.php have been made
@@ -54,11 +62,13 @@ $app = AppFactory::create();
$container = $app->getContainer();
$container->set('view', function (Container $container) {
return new Slim\Views\Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache');
return new Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache');
});
$container->set('UrlManager', function (Container $container) {
return new UrlManager(GROCY_BASE_URL);
});
$container->set('ApiKeyHeaderName', function (Container $container) {
return 'GROCY-API-KEY';
});
@@ -91,4 +101,6 @@ $errorMiddleware->setDefaultErrorHandler(
);
$app->add(new CorsMiddleware($app->getResponseFactory()));
ob_clean(); // No response output before here
$app->run();

View File

@@ -0,0 +1,125 @@
> ⚠️ 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 (although currently PHP 7.2 should still work).
### New feature: grocycode / label printer support
#### (Own) Product/stock entry/chores/batteries labels/barcodes
- 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/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_LABEL_PRINTER` (defaults to disabled)
- Label printer communication happens via WebHooks - see the new `LABEL_PRINTER*` `config.php` options
- 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/tree/v3.1.0/docs/grocycode.md
- https://github.com/grocy/grocy/tree/v3.1.0/docs/label-printing.md
- (Thanks a lot @mistressofjellyfish for the initial work on this)
### New feature: Meal plan sections
- Split the meal plan into sections like Breakfast/Lunch/Dinner
- => New button "Configure sections" on the meal plan page to configure the sections (top right corner)
- => Each meal plan entry can be assigned to a section
### New feature: Shopping list thermal printer support
- The shopping list can now be printed on a thermal printer
- 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)
- (Thanks a lot @Forceu)
### Stock improvements/fixes
- 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 (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)
- Fixed that the transfer page was not fully populated when opening it from the stock entries page
- Fixed that undoing a consume/open action from the success notification on the stock entries page was not possible
- Fixed that adding a barcode to a product didn't save the selected quantity unit when the product only has a single one
- Fixed that the store information on a stock entry was lost when transferring a partial amount to a different location
- Fixed that the "Spoil rate" on the product card was wrong in some cases
- Fixed that the stock journal showed always the products default location (instead of the location of the transaction)
- Fixed that the aggregated amount of parent products was wrong when indirect quantity unit conversions were used
### 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)
- Fixed that the displayed last unit/total price was wrong when the used quantity unit was not the products stock QU
- Fixed that the "Add as barcode to existing product" productpicker workflow did not work
### Recipe improvements/fixes
- 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
- Fixed that the amount of self produced products with tare weight handling enabled was wrong ("Produces product" recipe option)
- Fixed that the ingredient amount calculation for included/nested recipes was (for most cases) wrong
- Fixed that the ingredient amount was wrong when using a to the product indirectly related quantity unit
- Fixed that the calories amount calculation was wrong when quantity unit conversions were involved
### Meal plan improvements/fixes
- Improved the meal plan page loading time (drastically when having a big history of meal plan entries)
- 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
- It's now possible to track any addtional info on a chore execution by using Userfields
- => Configure the desired Userfields for the entity `chores_log`
- => The on chore execution tracking entered information is then visible on the corresponding chore journal entry
- Fixed that tracking chores with "Done by" a different user was not possible
### Userfield improvements/fixes
- Userfields can now be configured as mandatory (new Userfield option, defaults to disabled)
- Fixed that numeric Userfields were initialised with `1.0`
- Fixed that shortcuts (up/down key) and the format did not work correctly when using multiple date/time Userfields per object
- Fixed that Userfields were not saved when adding a product or a recipe (only on editing)
### General & other improvements/fixes
- LDAP authentication improvements / OpenLDAP support (thanks @tank0226)
- A read only service account can now be used for binding
- 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)
- Fixed that the number picker up/down buttons did not work when the input field was empty or contained an invalid number
- Fixed that links and embeds (e.g. YouTube videos) did not work in the text editor
- Fixed that the "Manage users" and "Manage API keys" menu was not shown when using reverse proxy authentication
### 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 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
- Fixed that `PUT/DELETE /objects/{entity}/{objectId}` produced an internal server error when the given object id was invalid (now returns `400 Bad Request`)
- Fixed that hyphens in filter values did not work
- Fixed that cyrillic letters were not allowed in filter values

View File

@@ -1,17 +1,20 @@
{
"require": {
"php": ">=7.4",
"php": ">=8.0",
"slim/slim": "^4.0",
"slim/psr7": "^1.0",
"slim/http": "^1.0",
"php-di/php-di": "^6.0",
"rubellum/slim-blade-view": "^0.1.1",
"morris/lessql": "^0.4.1",
"berrnd/slim-blade-view": "^1.0.0",
"morris/lessql": "^1.0",
"gettext/gettext": "^4.8",
"eluceo/ical": "^0.16.0",
"eluceo/ical": "^2.2.0",
"erusev/parsedown": "^1.7",
"gumlet/php-image-resize": "^1.9",
"ezyang/htmlpurifier": "^4.13"
"gumlet/php-image-resize": "^2.0",
"ezyang/htmlpurifier": "^4.13",
"interficieis/php-barcode": "^2.0.2",
"guzzlehttp/guzzle": "^7.0",
"mike42/escpos-php": "^3.0"
},
"autoload": {
"psr-4": {
@@ -23,5 +26,8 @@
"files": [
"helpers/extensions.php"
]
},
"config": {
"platform-check": false
}
}

1759
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -77,10 +77,13 @@ Setting('AUTH_CLASS', 'Grocy\Middleware\DefaultAuthMiddleware');
// the name of the HTTP header which your reverse proxy uses to pass the username (on successful authentication)
Setting('REVERSE_PROXY_AUTH_HEADER', 'REMOTE_USER');
// When using LdapAuthMiddleware
Setting('LDAP_DOMAIN', ''); // Example value "local"
// LDAP options when using LdapAuthMiddleware
Setting('LDAP_ADDRESS', ''); // Example value "ldap://vm-dc2019.local.berrnd.net"
Setting('LDAP_BASE_DN', ''); // Example value "OU=OU_Users,DC=local,DC=berrnd,DC=net"
Setting('LDAP_BASE_DN', ''); // Example value "DC=local,DC=berrnd,DC=net"
Setting('LDAP_BIND_DN', ''); // Example value "CN=grocy_bind_account,OU=service_accounts,DC=local,DC=berrnd,DC=net"
Setting('LDAP_BIND_PW', ''); // Password for the above account
Setting('LDAP_USER_FILTER', ''); // Example value "(OU=grocy_users)"
Setting('LDAP_UID_ATTR', ''); // Windows AD: "sAMAccountName", OpenLDAP: "uid", GLAuth: "cn"
// Set this to true if you want to disable the ability to scan a barcode via the device camera (Browser API)
Setting('DISABLE_BROWSER_BARCODE_CAMERA_SCANNING', false);
@@ -95,6 +98,34 @@ Setting('MEAL_PLAN_FIRST_DAY_OF_WEEK', '');
// see the file controllers/Users/User.php for possible values
Setting('DEFAULT_PERMISSIONS', ['ADMIN']);
// 1D (=> Code128) or 2D (=> DataMatrix)
Setting('GROCYCODE_TYPE', '1D');
// Label printer settings
// This is the URI that grocy will POST to when asked to print a label
Setting('LABEL_PRINTER_WEBHOOK', '');
// This setting decides whether the webhook will be called server- or clientside
// If the machine grocy runs on has a network connection to the host the webhook receiver is on, this is probably a good idea
// If, for example, grocy runs in the cloud and your printer daemon runs locally to you, set this to false to let your browser call the webhook instead
Setting('LABEL_PRINTER_RUN_SERVER', true);
// Additional parameters supplied to the webhook
Setting('LABEL_PRINTER_PARAMS', ['font_family' => 'Source Sans Pro (Regular)']);
// TRUE to use JSON or FALSE to use normal POST request variables
Setting('LABEL_PRINTER_HOOK_JSON', false);
// Thermal printer options
// Thermal printers are receipt printers, not regular printers,
// the printer must support the ESC/POS protocol, see https://github.com/mike42/escpos-php
Setting('TPRINTER_IS_NETWORK_PRINTER', false); // Set to true if it's' a network printer
Setting('TPRINTER_PRINT_QUANTITY_NAME', true); // Set to false if you do not want to print the quantity names (related to the shopping list)
Setting('TPRINTER_PRINT_NOTES', true); // Set to false if you do not want to print notes (related to the shopping list)
Setting('TPRINTER_IP', '127.0.0.1'); // IP of the network printer (does only matter if it's a network printer)
Setting('TPRINTER_PORT', 9100); // Port of the network printer
Setting('TPRINTER_CONNECTOR', '/dev/usb/lp0'); // Printer device (does only matter if you use a locally attached printer)
// For USB on Linux this is often '/dev/usb/lp0', for serial printers it could be similar to '/dev/ttyS0'
// Make sure that the user that runs the webserver has permissions to write to the printer - on Linux add your webserver user to the LP group with usermod -a -G lp www-data
// Default user settings
// These settings can be changed per user, here the defaults
// are defined which are used when the user has not changed the setting so far
@@ -117,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);
@@ -124,7 +156,7 @@ DefaultUserSetting('stock_default_consume_amount_use_quick_consume_amount', fals
DefaultUserSetting('scan_mode_consume_enabled', false);
DefaultUserSetting('scan_mode_purchase_enabled', false);
DefaultUserSetting('show_icon_on_stock_overview_page_when_product_is_on_shopping_list', true);
DefaultUserSetting('show_purchased_date_on_purchase', false); // Wheter the purchased date should be editable on purchase (defaults to today otherwise)
DefaultUserSetting('show_purchased_date_on_purchase', false); // Whether the purchased date should be editable on purchase (defaults to today otherwise)
DefaultUserSetting('show_warning_on_purchase_when_due_date_is_earlier_than_next', true); // Show a warning on purchase when the due date of the purchased product is earlier than the next due date in stock
// Shopping list settings
@@ -143,9 +175,8 @@ DefaultUserSetting('batteries_due_soon_days', 5);
// Tasks settings
DefaultUserSetting('tasks_due_soon_days', 5);
// If the page should be automatically reloaded when there was
// an external change
DefaultUserSetting('auto_reload_on_db_change', true);
// If the page should be automatically reloaded when there was an external change
DefaultUserSetting('auto_reload_on_db_change', false);
// Show a clock in the header next to the logo or not
DefaultUserSetting('show_clock_in_header', false);
@@ -159,6 +190,7 @@ DefaultUserSetting('quagga2_patchsize', 'medium');
DefaultUserSetting('quagga2_frequency', 10);
DefaultUserSetting('quagga2_debug', true);
// Feature flags
// grocy was initially about "stock management for your household", many other things
// came and still come by, because they are useful - here you can disable the parts
@@ -172,6 +204,7 @@ Setting('FEATURE_FLAG_TASKS', true);
Setting('FEATURE_FLAG_BATTERIES', true);
Setting('FEATURE_FLAG_EQUIPMENT', true);
Setting('FEATURE_FLAG_CALENDAR', true);
Setting('FEATURE_FLAG_LABEL_PRINTER', false);
// Sub feature flags
Setting('FEATURE_FLAG_STOCK_PRICE_TRACKING', true);
@@ -182,7 +215,8 @@ Setting('FEATURE_FLAG_STOCK_PRODUCT_FREEZING', true);
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD', true); // Activate the number pad in due date fields on (supported) mobile browsers
Setting('FEATURE_FLAG_SHOPPINGLIST_MULTIPLE_LISTS', true);
Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
Setting('FEATURE_FLAG_THERMAL_PRINTER', false);
// Feature settings
Setting('FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true); // When set to true, opened items will be counted as missing for calculating if a product is below its minimum stock amount
Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automaticaly (if the device has one)
Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automatically (if the device has one)

View File

@@ -6,20 +6,22 @@ use LessQL\Result;
class BaseApiController extends BaseController
{
const PATTERN_FIELD = '[A-Za-z_][A-Za-z0-9_]+';
const PATTERN_OPERATOR = '!?(=|~|<|>|(>=)|(<=)|(§))';
const PATTERN_VALUE = '[A-Za-z\x{0400}-\x{04FF}_0-9.$#^|-]+';
protected $OpenApiSpec = null;
const PATTERN_FIELD = '[A-Za-z_][A-Za-z0-9_]+';
const PATTERN_OPERATOR = '!?(=|~|<|>|(>=)|(<=)|(§))';
const PATTERN_VALUE = '[A-Za-z_0-9.$#^|]+';
public function __construct(\DI\Container $container)
protected function ApiResponse(\Psr\Http\Message\ResponseInterface $response, $data, $cache = false)
{
parent::__construct($container);
}
if ($cache)
{
$response = $response->withHeader('Cache-Control', 'max-age=2592000');
}
protected function ApiResponse(\Psr\Http\Message\ResponseInterface $response, $data)
{
$response->getBody()->write(json_encode($data));
$response->getBody()->write(json_encode($data, JSON_NUMERIC_CHECK));
return $response;
}
@@ -83,7 +85,7 @@ class BaseApiController extends BaseController
preg_match(
'/(?P<field>' . self::PATTERN_FIELD . ')'
. '(?P<op>' . self::PATTERN_OPERATOR . ')'
. '(?P<value>' . self::PATTERN_VALUE . ')/',
. '(?P<value>' . self::PATTERN_VALUE . ')/u',
$q,
$matches
);

View File

@@ -11,6 +11,7 @@ use Grocy\Services\ChoresService;
use Grocy\Services\DatabaseService;
use Grocy\Services\FilesService;
use Grocy\Services\LocalizationService;
use Grocy\Services\PrintService;
use Grocy\Services\RecipesService;
use Grocy\Services\SessionService;
use Grocy\Services\StockService;
@@ -20,14 +21,14 @@ use Grocy\Services\UsersService;
class BaseController
{
protected $AppContainer;
public function __construct(\DI\Container $container)
{
$this->AppContainer = $container;
$this->View = $container->get('view');
}
protected $AppContainer;
protected function getApiKeyService()
{
return ApiKeyService::getInstance();
@@ -93,6 +94,11 @@ class BaseController
return StockService::getInstance();
}
protected function getPrintService()
{
return PrintService::getInstance();
}
protected function getTasksService()
{
return TasksService::getInstance();
@@ -123,7 +129,7 @@ class BaseController
$this->View->set('__n', function ($number, $singularForm, $pluralForm) use ($localizationService) {
return $localizationService->__n($number, $singularForm, $pluralForm);
});
$this->View->set('GettextPo', $localizationService->GetPoAsJsonString());
$this->View->set('LocalizationStrings', $localizationService->GetPoAsJsonString());
// TODO: Better handle this generically based on the current language (header in .po file?)
$dir = 'ltr';
@@ -157,18 +163,18 @@ class BaseController
if (GROCY_AUTHENTICATED)
{
$this->View->set('permissions', User::PermissionList());
}
$decimalPlacesAmounts = intval($this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'stock_decimal_places_amounts'));
if ($decimalPlacesAmounts <= 0)
{
$defaultMinAmount = 1;
$decimalPlacesAmounts = intval($this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'stock_decimal_places_amounts'));
if ($decimalPlacesAmounts <= 0)
{
$defaultMinAmount = 1;
}
else
{
$defaultMinAmount = '0.' . str_repeat('0', $decimalPlacesAmounts - 1) . '1';
}
$this->View->set('DEFAULT_MIN_AMOUNT', $defaultMinAmount);
}
else
{
$defaultMinAmount = '0.' . str_repeat('0', $decimalPlacesAmounts - 1) . '1';
}
$this->View->set('DEFAULT_MIN_AMOUNT', $defaultMinAmount);
return $this->View->render($response, $page, $data);
}
@@ -203,9 +209,13 @@ class BaseController
if (self::$htmlPurifierInstance == null)
{
$htmlPurifierConfig = \HTMLPurifier_Config::createDefault();
$htmlPurifierConfig->set('HTML.Allowed', 'div,b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[width|height|alt|src],table[border|width|style],tbody,tr,td,th,blockquote');
$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,*[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]);
$htmlPurifierConfig->set('URI.AllowedSchemes', ['data' => true, 'http' => true, 'https' => true]);
$htmlPurifierConfig->set('URI.SafeIframeRegexp', '%^.*%'); // Allow any iframe source
self::$htmlPurifierInstance = new \HTMLPurifier($htmlPurifierConfig);
}
@@ -213,15 +223,18 @@ class BaseController
$requestBody = $request->getParsedBody();
foreach ($requestBody as $key => &$value)
{
// HTMLPurifier removes boolean values (true/false), so explicitly keep them
// HTMLPurifier removes boolean values (true/false) and arrays, so explicitly keep them
// Maybe also possible through HTMLPurifier config (http://htmlpurifier.org/live/configdoc/plain.html)
if (!is_bool($value))
if (!is_bool($value) && !is_array($value))
{
$value = self::$htmlPurifierInstance->purify($value);
}
// Allow some special chars
$value = str_replace('&amp;', '&', $value);
if (!is_array($value))
{
$value = str_replace('&amp;', '&', $value);
}
}
return $requestBody;

View File

@@ -3,6 +3,8 @@
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Helpers\WebhookRunner;
use Grocy\Helpers\Grocycode;
class BatteriesApiController extends BaseApiController
{
@@ -32,7 +34,6 @@ class BatteriesApiController extends BaseApiController
try
{
$trackedTime = date('Y-m-d H:i:s');
if (array_key_exists('tracked_time', $requestBody) && IsIsoDateTime($requestBody['tracked_time']))
{
$trackedTime = $requestBody['tracked_time'];
@@ -62,8 +63,27 @@ class BatteriesApiController extends BaseApiController
}
}
public function __construct(\DI\Container $container)
public function BatteryPrintLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
parent::__construct($container);
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());
}
}
}

View File

@@ -2,8 +2,12 @@
namespace Grocy\Controllers;
use Grocy\Helpers\Grocycode;
class BatteriesController extends BaseController
{
use GrocycodeTrait;
public function BatteriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if (isset($request->getQueryParams()['include_disabled']))
@@ -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,8 +96,9 @@ class BatteriesController extends BaseController
]);
}
public function __construct(\DI\Container $container)
public function BatteryGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
parent::__construct($container);
$gc = new Grocycode(Grocycode::BATTERY, $args['batteryId']);
return $this->ServeGrocycodeImage($request, $response, $gc);
}
}

View File

@@ -2,44 +2,54 @@
namespace Grocy\Controllers;
use Eluceo\iCal\Domain\Entity\Calendar;
use Eluceo\iCal\Domain\Entity\Event;
use Eluceo\iCal\Domain\ValueObject\Date;
use Eluceo\iCal\Domain\ValueObject\DateTime;
use Eluceo\iCal\Domain\ValueObject\SingleDay;
use Eluceo\iCal\Domain\ValueObject\TimeSpan;
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
class CalendarApiController extends BaseApiController
{
public function Ical(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
try
{
$vCalendar = new \Eluceo\iCal\Component\Calendar('grocy');
$events = $this->getCalendarService()->GetEvents();
$vCalendar = new Calendar();
foreach ($events as $event)
{
$date = new \DateTime($event['start']);
$date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
if ($event['date_format'] === 'date')
{
$date->setTime(23, 59, 59);
}
$description = '';
if (isset($event['description']))
{
$description = $event['description'];
}
$vEvent = new \Eluceo\iCal\Component\Event();
$vEvent->setDtStart($date)
->setDtEnd($date)
->setSummary($event['title'])
->setDescription($description)
->setNoTime($event['date_format'] === 'date' || (isset($event['allDay']) && $event['allDay']))
->setUseTimezone(true);
if ($event['date_format'] === 'date' || (isset($event['allDay']) && $event['allDay']))
{
// All-day event
$date = new Date(\DateTimeImmutable::createFromFormat('Y-m-d', $event['start']));
$vEventOccurrence = new SingleDay($date);
}
else
{
// Time-point event
$start = new DateTime(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $event['start']), false);
$end = new DateTime(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $event['start']), false);
$vEventOccurrence = new TimeSpan($start, $end);
}
$vCalendar->addComponent($vEvent);
$vEvent = new Event();
$vEvent->setOccurrence($vEventOccurrence)
->setSummary($event['title'])
->setDescription($description);
$vCalendar->addEvent($vEvent);
}
$response->write($vCalendar->render());
$response->write((new CalendarFactory())->createCalendar($vCalendar));
$response = $response->withHeader('Content-Type', 'text/calendar; charset=utf-8');
return $response->withHeader('Content-Disposition', 'attachment; filename="grocy.ics"');
}
@@ -62,9 +72,4 @@ class CalendarApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -10,9 +10,4 @@ class CalendarController extends BaseController
'fullcalendarEventSources' => $this->getCalendarService()->GetEvents()
]);
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -3,6 +3,8 @@
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Helpers\WebhookRunner;
use Grocy\Helpers\Grocycode;
class ChoresApiController extends BaseApiController
{
@@ -66,14 +68,12 @@ class ChoresApiController extends BaseApiController
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION);
$trackedTime = date('Y-m-d H:i:s');
if (array_key_exists('tracked_time', $requestBody) && (IsIsoDateTime($requestBody['tracked_time']) || IsIsoDate($requestBody['tracked_time'])))
{
$trackedTime = $requestBody['tracked_time'];
}
$doneBy = GROCY_USER_ID;
if (array_key_exists('done_by', $requestBody) && !empty($requestBody['done_by']))
{
$doneBy = $requestBody['done_by'];
@@ -81,7 +81,7 @@ class ChoresApiController extends BaseApiController
if ($doneBy != GROCY_USER_ID)
{
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION_EXECUTION);
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION);
}
$choreExecutionId = $this->getChoresService()->TrackChore($args['choreId'], $trackedTime, $doneBy);
@@ -108,8 +108,27 @@ class ChoresApiController extends BaseApiController
}
}
public function __construct(\DI\Container $container)
public function ChorePrintLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
parent::__construct($container);
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());
}
}
}

View File

@@ -2,8 +2,12 @@
namespace Grocy\Controllers;
use Grocy\Helpers\Grocycode;
class ChoresController extends BaseController
{
use GrocycodeTrait;
public function ChoreEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$usersService = $this->getUsersService();
@@ -59,10 +63,29 @@ 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')
'users' => $this->getDatabase()->users()->orderBy('username'),
'userfields' => $this->getUserfieldsService()->GetFields('chores_log'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('chores_log')
]);
}
@@ -85,12 +108,14 @@ class ChoresController extends BaseController
{
return $this->renderPage($response, 'choretracking', [
'chores' => $this->getDatabase()->chores()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'users' => $this->getDatabase()->users()->orderBy('username')
'users' => $this->getDatabase()->users()->orderBy('username'),
'userfields' => $this->getUserfieldsService()->GetFields('chores_log'),
]);
}
public function __construct(\DI\Container $container)
public function ChoreGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
parent::__construct($container);
$gc = new Grocycode(Grocycode::CHORE, $args['choreId']);
return $this->ServeGrocycodeImage($request, $response, $gc);
}
}

View File

@@ -33,9 +33,4 @@ class EquipmentController extends BaseController
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('equipment')
]);
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -11,17 +11,17 @@ use Throwable;
class ExceptionController extends BaseApiController
{
/**
* @var \Slim\App
*/
private $app;
public function __construct(\Slim\App $app, \DI\Container $container)
{
parent::__construct($container);
$this->app = $app;
}
/**
* @var \Slim\App
*/
private $app;
public function __invoke(ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails, bool $logErrors, bool $logErrorDetails, ?LoggerInterface $logger = null)
{
$response = $this->app->getResponseFactory()->createResponse();
@@ -59,7 +59,10 @@ class ExceptionController extends BaseApiController
if ($exception instanceof HttpNotFoundException)
{
define('GROCY_AUTHENTICATED', false);
if (!defined('GROCY_AUTHENTICATED'))
{
define('GROCY_AUTHENTICATED', false);
}
return $this->renderPage($response->withStatus(404), 'errors/404', [
'exception' => $exception

View File

@@ -11,6 +11,11 @@ class FilesApiController extends BaseApiController
{
try
{
if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{
throw new \Exception('Invalid file group');
}
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = base64_decode($args['fileName']);
@@ -20,12 +25,7 @@ class FilesApiController extends BaseApiController
throw new \Exception('Invalid filename');
}
$filePath = $this->getFilesService()->GetFilePath($args['group'], $fileName);
if (file_exists($filePath))
{
unlink($filePath);
}
$this->getFilesService()->DeleteFile($args['group'], $fileName);
return $this->EmptyApiResponse($response);
}
@@ -39,8 +39,12 @@ class FilesApiController extends BaseApiController
{
try
{
$fileName = $this->checkFileName($args['fileName']);
if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{
throw new \Exception('Invalid file group');
}
$fileName = $this->checkFileName($args['fileName']);
$filePath = $this->getFilePath($args['group'], $fileName, $request->getQueryParams());
if (file_exists($filePath))
@@ -65,9 +69,13 @@ class FilesApiController extends BaseApiController
{
try
{
if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{
throw new \Exception('Invalid file group');
}
$fileInfo = explode('_', $args['fileName']);
$fileName = $this->checkFileName($fileInfo[1]);
$filePath = $this->getFilePath($args['group'], base64_decode($fileInfo[0]), $request->getQueryParams());
if (file_exists($filePath))
@@ -92,9 +100,14 @@ class FilesApiController extends BaseApiController
{
try
{
$fileName = $this->checkFileName($args['fileName']);
if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{
throw new \Exception('Invalid file group');
}
$fileName = $this->checkFileName($args['fileName']);
$data = $request->getBody()->getContents();
file_put_contents($this->getFilesService()->GetFilePath($args['group'], $fileName), $data);
return $this->EmptyApiResponse($response);
@@ -105,11 +118,6 @@ class FilesApiController extends BaseApiController
}
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
/**
* @param string $fileName base64-encoded file-name
* @return false|string the decoded file-name
@@ -138,7 +146,6 @@ class FilesApiController extends BaseApiController
protected function getFilePath(string $group, string $fileName, array $queryParams = [])
{
$forceServeAs = null;
if (isset($queryParams['force_serve_as']) && !empty($queryParams['force_serve_as']))
{
$forceServeAs = $queryParams['force_serve_as'];
@@ -147,14 +154,12 @@ class FilesApiController extends BaseApiController
if ($forceServeAs == FilesService::FILE_SERVE_TYPE_PICTURE)
{
$bestFitHeight = null;
if (isset($queryParams['best_fit_height']) && !empty($queryParams['best_fit_height']) && is_numeric($queryParams['best_fit_height']))
{
$bestFitHeight = $queryParams['best_fit_height'];
}
$bestFitWidth = null;
if (isset($queryParams['best_fit_width']) && !empty($queryParams['best_fit_width']) && is_numeric($queryParams['best_fit_width']))
{
$bestFitWidth = $queryParams['best_fit_width'];

View File

@@ -58,6 +58,11 @@ class GenericEntityApiController extends BaseApiController
}
$row = $this->getDatabase()->{$args['entity']}($args['objectId']);
if ($row == null)
{
return $this->GenericErrorResponse($response, 'Object not found', 400);
}
$row->delete();
$success = $row->isClean();
@@ -90,6 +95,11 @@ class GenericEntityApiController extends BaseApiController
}
$row = $this->getDatabase()->{$args['entity']}($args['objectId']);
if ($row == null)
{
return $this->GenericErrorResponse($response, 'Object not found', 400);
}
$row->update($requestBody);
$success = $row->isClean();
@@ -111,14 +121,12 @@ class GenericEntityApiController extends BaseApiController
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoListing($args['entity']))
{
$userfields = $this->getUserfieldsService()->GetValues($args['entity'], $args['objectId']);
if (count($userfields) === 0)
{
$userfields = null;
}
$object = $this->getDatabase()->{$args['entity']}($args['objectId']);
if ($object == null)
{
return $this->GenericErrorResponse($response, 'Object not found', 404);
@@ -136,33 +144,39 @@ class GenericEntityApiController extends BaseApiController
public function GetObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$objects = $this->queryData($this->getDatabase()->{$args['entity']}(), $request->getQueryParams());
$allUserfields = $this->getUserfieldsService()->GetAllValues($args['entity']);
foreach ($objects as $object)
{
$userfields = FindAllObjectsInArrayByPropertyValue($allUserfields, 'object_id', $object->id);
$userfieldKeyValuePairs = null;
if (count($userfields) > 0)
{
foreach ($userfields as $userfield)
{
$userfieldKeyValuePairs[$userfield->name] = $userfield->value;
}
}
$object->userfields = $userfieldKeyValuePairs;
}
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoListing($args['entity']))
{
return $this->ApiResponse($response, $objects);
}
else
if (!$this->IsValidExposedEntity($args['entity']) || $this->IsEntityWithNoListing($args['entity']))
{
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
$objects = $this->queryData($this->getDatabase()->{$args['entity']}(), $request->getQueryParams());
$userfields = $this->getUserfieldsService()->GetFields($args['entity']);
if (count($userfields) > 0)
{
$allUserfieldValues = $this->getUserfieldsService()->GetAllValues($args['entity']);
foreach ($objects as $object)
{
$userfieldKeyValuePairs = null;
foreach ($userfields as $userfield)
{
$value = FindObjectInArrayByPropertyValue($allUserfieldValues, 'object_id', $object->id);
if ($value)
{
$userfieldKeyValuePairs[$userfield->name] = $value->value;
}
else
{
$userfieldKeyValuePairs[$userfield->name] = null;
}
}
$object->userfields = $userfieldKeyValuePairs;
}
}
return $this->ApiResponse($response, $objects);
}
public function GetUserfields(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
@@ -199,33 +213,28 @@ class GenericEntityApiController extends BaseApiController
}
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
private function IsEntityWithEditRequiresAdmin($entity)
{
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityEditRequiresAdmin->enum);
return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityEditRequiresAdmin->enum);
}
private function IsEntityWithNoListing($entity)
{
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityNoListing->enum);
return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityNoListing->enum);
}
private function IsEntityWithNoEdit($entity)
{
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityNoEdit->enum);
return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityNoEdit->enum);
}
private function IsEntityWithNoDelete($entity)
{
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityNoDelete->enum);
return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityNoDelete->enum);
}
private function IsValidExposedEntity($entity)
{
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntity->enum);
return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntity->enum);
}
}

View File

@@ -91,9 +91,4 @@ class GenericEntityController extends BaseController
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('userentity-' . $args['userentityName'])
]);
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Grocy\Controllers;
use Grocy\Helpers\Grocycode;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use jucksearm\barcode\lib\BarcodeFactory;
use jucksearm\barcode\lib\DatamatrixFactory;
trait GrocycodeTrait
{
public function ServeGrocycodeImage(ServerRequestInterface $request, ResponseInterface $response, Grocycode $grocycode)
{
$size = $request->getQueryParam('size', null);
if (GROCY_GROCYCODE_TYPE == '2D')
{
$png = (new DatamatrixFactory())->setCode((string) $grocycode)->setSize($size)->getDatamatrixPngData();
}
else
{
$png = (new BarcodeFactory())->setType('C128')->setCode((string) $grocycode)->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;
}
}

View File

@@ -6,11 +6,6 @@ use Grocy\Services\SessionService;
class LoginController extends BaseController
{
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
public function LoginPage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'login');

View File

@@ -36,30 +36,45 @@ class OpenApiController extends BaseApiController
$spec->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->get('UrlManager')->ConstructUrl('/manageapikeys'), $spec->info->description);
$spec->servers[0]->url = $this->AppContainer->get('UrlManager')->ConstructUrl('/api');
$spec->components->internalSchemas->ExposedEntity_NotIncludingNotEditable = clone $spec->components->internalSchemas->StringEnumTemplate;
foreach ($spec->components->internalSchemas->ExposedEntity->enum as $value)
$spec->components->schemas->ExposedEntity_IncludingUserEntities = clone $spec->components->schemas->ExposedEntity;
foreach ($this->getUserfieldsService()->GetEntities() as $userEntity)
{
if (!in_array($value, $spec->components->internalSchemas->ExposedEntityNoEdit->enum))
array_push($spec->components->schemas->ExposedEntity_IncludingUserEntities->enum, $userEntity);
}
$spec->components->schemas->ExposedEntity_NotIncludingNotEditable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->schemas->ExposedEntity->enum as $value)
{
if (!in_array($value, $spec->components->schemas->ExposedEntityNoEdit->enum))
{
array_push($spec->components->internalSchemas->ExposedEntity_NotIncludingNotEditable->enum, $value);
array_push($spec->components->schemas->ExposedEntity_NotIncludingNotEditable->enum, $value);
}
}
$spec->components->internalSchemas->ExposedEntity_NotIncludingNotDeletable = clone $spec->components->internalSchemas->StringEnumTemplate;
foreach ($spec->components->internalSchemas->ExposedEntity->enum as $value)
$spec->components->schemas->ExposedEntity_IncludingUserEntities_NotIncludingNotEditable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->schemas->ExposedEntity_IncludingUserEntities->enum as $value)
{
if (!in_array($value, $spec->components->internalSchemas->ExposedEntityNoDelete->enum))
if (!in_array($value, $spec->components->schemas->ExposedEntityNoEdit->enum))
{
array_push($spec->components->internalSchemas->ExposedEntity_NotIncludingNotDeletable->enum, $value);
array_push($spec->components->schemas->ExposedEntity_IncludingUserEntities_NotIncludingNotEditable->enum, $value);
}
}
$spec->components->internalSchemas->ExposedEntity_NotIncludingNotListable = clone $spec->components->internalSchemas->StringEnumTemplate;
foreach ($spec->components->internalSchemas->ExposedEntity->enum as $value)
$spec->components->schemas->ExposedEntity_NotIncludingNotDeletable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->schemas->ExposedEntity->enum as $value)
{
if (!in_array($value, $spec->components->internalSchemas->ExposedEntityNoListing->enum))
if (!in_array($value, $spec->components->schemas->ExposedEntityNoDelete->enum))
{
array_push($spec->components->internalSchemas->ExposedEntity_NotIncludingNotListable->enum, $value);
array_push($spec->components->schemas->ExposedEntity_NotIncludingNotDeletable->enum, $value);
}
}
$spec->components->schemas->ExposedEntity_NotIncludingNotListable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->schemas->ExposedEntity->enum as $value)
{
if (!in_array($value, $spec->components->schemas->ExposedEntityNoListing->enum))
{
array_push($spec->components->schemas->ExposedEntity_NotIncludingNotListable->enum, $value);
}
}
@@ -70,9 +85,4 @@ class OpenApiController extends BaseApiController
{
return $this->render($response, 'openapiui');
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Services\StockService;
class PrintApiController extends BaseApiController
{
public function PrintShoppingListThermal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
try
{
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST);
$params = $request->getQueryParams();
$listId = 1;
if (isset($params['list']))
{
$listId = $params['list'];
}
$printHeader = true;
if (isset($params['printHeader']))
{
$printHeader = ($params['printHeader'] === 'true');
}
$items = $this->getStockService()->GetShoppinglistInPrintableStrings($listId);
return $this->ApiResponse($response, $this->getPrintService()->printShoppingList($printHeader, $items));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -1,70 +1,79 @@
<?php
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
class RecipesApiController extends BaseApiController
{
public function AddNotFulfilledProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD);
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
$excludedProductIds = null;
if ($requestBody !== null && array_key_exists('excludedProductIds', $requestBody))
{
$excludedProductIds = $requestBody['excludedProductIds'];
}
$this->getRecipesService()->AddNotFulfilledProductsToShoppingList($args['recipeId'], $excludedProductIds);
return $this->EmptyApiResponse($response);
}
public function ConsumeRecipe(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_STOCK_CONSUME);
try
{
$this->getRecipesService()->ConsumeRecipe($args['recipeId']);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function GetRecipeFulfillment(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
try
{
if (!isset($args['recipeId']))
{
return $this->FilteredApiResponse($response, $this->getRecipesService()->GetRecipesResolved(), $request->getQueryParams());
}
$recipeResolved = FindObjectInArrayByPropertyValue($this->getRecipesService()->GetRecipesResolved(), 'recipe_id', $args['recipeId']);
if (!$recipeResolved)
{
throw new \Exception('Recipe does not exist');
}
else
{
return $this->ApiResponse($response, $recipeResolved);
}
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}
<?php
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
class RecipesApiController extends BaseApiController
{
public function AddNotFulfilledProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD);
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
$excludedProductIds = null;
if ($requestBody !== null && array_key_exists('excludedProductIds', $requestBody))
{
$excludedProductIds = $requestBody['excludedProductIds'];
}
$this->getRecipesService()->AddNotFulfilledProductsToShoppingList($args['recipeId'], $excludedProductIds);
return $this->EmptyApiResponse($response);
}
public function ConsumeRecipe(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_STOCK_CONSUME);
try
{
$this->getRecipesService()->ConsumeRecipe($args['recipeId']);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function GetRecipeFulfillment(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
try
{
if (!isset($args['recipeId']))
{
return $this->FilteredApiResponse($response, $this->getRecipesService()->GetRecipesResolved(), $request->getQueryParams());
}
$recipeResolved = FindObjectInArrayByPropertyValue($this->getRecipesService()->GetRecipesResolved(), 'recipe_id', $args['recipeId']);
if (!$recipeResolved)
{
throw new \Exception('Recipe does not exist');
}
else
{
return $this->ApiResponse($response, $recipeResolved);
}
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
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());
}
}
}

View File

@@ -8,11 +8,23 @@ class RecipesController extends BaseController
{
public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$start = date('Y-m-d');
if (isset($request->getQueryParams()['start']) && IsIsoDate($request->getQueryParams()['start']))
{
$start = $request->getQueryParams()['start'];
}
$days = 6;
if (isset($request->getQueryParams()['days']) && filter_var($request->getQueryParams()['days'], FILTER_VALIDATE_INT) !== false)
{
$days = $request->getQueryParams()['days'];
}
$mealPlanWhereTimespan = "day BETWEEN DATE('$start') AND DATE('$start', '+$days days')";
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
$events = [];
foreach ($this->getDatabase()->meal_plan() as $mealPlanEntry)
foreach ($this->getDatabase()->meal_plan()->where($mealPlanWhereTimespan) as $mealPlanEntry)
{
$recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']);
$title = '';
@@ -23,7 +35,6 @@ class RecipesController extends BaseController
}
$productDetails = null;
if ($mealPlanEntry['product_id'] !== null)
{
$productDetails = $this->getStockService()->GetProductDetails($mealPlanEntry['product_id']);
@@ -44,18 +55,20 @@ class RecipesController extends BaseController
return $this->renderPage($response, 'mealplan', [
'fullcalendarEventSources' => $events,
'recipes' => $recipes,
'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(),
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(),
'internalRecipes' => $this->getDatabase()->recipes()->where("id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)")->fetchAll(),
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved2("recipe_id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)"),
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
'mealplanSections' => $this->getDatabase()->meal_plan_sections()->orderBy('sort_number'),
'usedMealplanSections' => $this->getDatabase()->meal_plan_sections()->where("id IN (SELECT section_id FROM meal_plan WHERE $mealPlanWhereTimespan)")->orderBy('sort_number')
]);
}
public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE');
$recipesResolved = $this->getRecipesService()->GetRecipesResolved();
$recipesResolved = $this->getRecipesService()->GetRecipesResolved('recipe_id > 0');
$selectedRecipe = null;
@@ -157,7 +170,7 @@ class RecipesController extends BaseController
'mode' => 'create',
'recipe' => $this->getDatabase()->recipes($args['recipeId']),
'recipePos' => new \stdClass(),
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]);
@@ -180,8 +193,27 @@ class RecipesController extends BaseController
return $this->renderPage($response, 'recipessettings');
}
public function __construct(\DI\Container $container)
public function MealPlanSectionEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
parent::__construct($container);
if ($args['sectionId'] == 'new')
{
return $this->renderPage($response, 'mealplansectionform', [
'mode' => 'create'
]);
}
else
{
return $this->renderPage($response, 'mealplansectionform', [
'mealplanSection' => $this->getDatabase()->meal_plan_sections($args['sectionId']),
'mode' => 'edit'
]);
}
}
public function MealPlanSectionsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'mealplansections', [
'mealplanSections' => $this->getDatabase()->meal_plan_sections()->where('id > 0')->orderBy('sort_number')
]);
}
}

View File

@@ -4,6 +4,8 @@ namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Services\StockService;
use Grocy\Helpers\WebhookRunner;
use Grocy\Helpers\Grocycode;
class StockApiController extends BaseApiController
{
@@ -138,8 +140,14 @@ class StockApiController extends BaseApiController
{
$transactionType = $requestBody['transactiontype'];
}
$runPrinterWebhook = false;
if (array_key_exists('print_stock_label', $requestBody) && intval($requestBody['print_stock_label']))
{
$runPrinterWebhook = intval($requestBody['print_stock_label']);
}
$transactionId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId, $shoppingLocationId, $unusedTransactionId, $runPrinterWebhook);
$transactionId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId, $shoppingLocationId);
$args['transactionId'] = $transactionId;
return $this->StockTransactions($request, $response, $args);
}
@@ -172,6 +180,7 @@ class StockApiController extends BaseApiController
$listId = 1;
$amount = 1;
$quId = -1;
$productId = null;
$note = null;
@@ -195,12 +204,17 @@ class StockApiController extends BaseApiController
$note = $requestBody['note'];
}
if (array_key_exists('qu_id', $requestBody) && !empty($requestBody['qu_id']))
{
$quId = $requestBody['qu_id'];
}
if ($productId == null)
{
throw new \Exception('No product id was supplied');
}
$this->getStockService()->AddProductToShoppingList($productId, $amount, $note, $listId);
$this->getStockService()->AddProductToShoppingList($productId, $amount, $quId, $note, $listId);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
@@ -604,6 +618,60 @@ class StockApiController extends BaseApiController
return $this->FilteredApiResponse($response, $this->getStockService()->GetProductStockLocations($args['productId'], $allowSubproductSubstitution), $request->getQueryParams());
}
public function ProductPrintLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
try
{
$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)
{
(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)
{
try
{
$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)
{
$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)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function RemoveProductFromShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_DELETE);
@@ -794,9 +862,4 @@ class StockApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -2,14 +2,17 @@
namespace Grocy\Controllers;
use Grocy\Helpers\Grocycode;
use Grocy\Services\RecipesService;
class StockController extends BaseController
{
use GrocycodeTrait;
public function Consume(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'consume', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'products' => $this->getDatabase()->products()->where('active = 1')->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)')->orderBy('name'),
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
@@ -32,14 +35,31 @@ 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(),
'transactionTypes' => GetClassConstants('\Grocy\Services\StockService', 'TRANSACTION_TYPE_'),
'transactionTypes' => GetClassConstants('\Grocy\Services\StockService', 'TRANSACTION_TYPE_')
]);
}
@@ -170,6 +190,12 @@ class StockController extends BaseController
}
}
public function ProductGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$gc = new Grocycode(Grocycode::PRODUCT, $args['productId']);
return $this->ServeGrocycodeImage($request, $response, $gc);
}
public function ProductGroupEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['productGroupId'] == 'new')
@@ -201,15 +227,19 @@ class StockController extends BaseController
public function ProductsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if (isset($request->getQueryParams()['include_disabled']))
$products = $this->getDatabase()->products();
if (!isset($request->getQueryParams()['include_disabled']))
{
$products = $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE');
$products = $products->where('active = 1');
}
else
if (isset($request->getQueryParams()['only_in_stock']))
{
$products = $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE');
$products = $products->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)');
}
$products = $products->orderBy('name', 'COLLATE NOCASE');
return $this->renderPage($response, 'products', [
'products' => $products,
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
@@ -428,6 +458,22 @@ class StockController extends BaseController
]);
}
public function StockEntryGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$stockEntry = $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch();
$gc = new Grocycode(Grocycode::PRODUCT, $stockEntry->product_id, [$stockEntry->stock_id]);
return $this->ServeGrocycodeImage($request, $response, $gc);
}
public function StockEntryGrocycodeLabel(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$stockEntry = $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch();
return $this->renderPage($response, 'stockentrylabel', [
'stockEntry' => $stockEntry,
'product' => $this->getDatabase()->products($stockEntry->product_id),
]);
}
public function StockSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'stocksettings', [
@@ -458,7 +504,7 @@ class StockController extends BaseController
public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'transfer', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'products' => $this->getDatabase()->products()->where('active = 1')->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)')->orderBy('name', 'COLLATE NOCASE'),
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
@@ -466,11 +512,6 @@ class StockController extends BaseController
]);
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
public function JournalSummary(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$entries = $this->getDatabase()->uihelper_stock_journal_summary();

View File

@@ -85,8 +85,8 @@ class SystemApiController extends BaseApiController
}
}
public function __construct(\DI\Container $container)
public function GetLocalizationStrings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
parent::__construct($container);
return $this->ApiResponse($response, json_decode($this->getLocalizationService()->GetPoAsJsonString()), true);
}
}

View File

@@ -35,11 +35,6 @@ class SystemController extends BaseController
return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl($this->GetEntryPageRelative()));
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
/**
* Get the entry page of the application based on the value of the entry page setting.
*

View File

@@ -49,9 +49,4 @@ class TasksApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -83,9 +83,4 @@ class TasksController extends BaseController
{
return $this->renderPage($response, 'taskssettings');
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

View File

@@ -10,13 +10,17 @@ class User
const PERMISSION_ADMIN = 'ADMIN';
const PERMISSION_BATTERIES = 'BATTERIES';
const PERMISSION_BATTERIES_TRACK_CHARGE_CYCLE = 'BATTERIES_TRACK_CHARGE_CYCLE';
const PERMISSION_BATTERIES_UNDO_CHARGE_CYCLE = 'BATTERIES_UNDO_CHARGE_CYCLE';
const PERMISSION_CALENDAR = 'CALENDAR';
const PERMISSION_CHORES = 'CHORES';
const PERMISSION_CHORE_TRACK_EXECUTION = 'CHORE_TRACK_EXECUTION';
const PERMISSION_CHORE_UNDO_EXECUTION = 'CHORE_UNDO_EXECUTION';
const PERMISSION_EQUIPMENT = 'EQUIPMENT';
@@ -24,30 +28,50 @@ class User
const PERMISSION_MASTER_DATA_EDIT = 'MASTER_DATA_EDIT';
const PERMISSION_RECIPES = 'RECIPES';
const PERMISSION_RECIPES_MEALPLAN = 'RECIPES_MEALPLAN';
const PERMISSION_SHOPPINGLIST = 'SHOPPINGLIST';
const PERMISSION_SHOPPINGLIST_ITEMS_ADD = 'SHOPPINGLIST_ITEMS_ADD';
const PERMISSION_SHOPPINGLIST_ITEMS_DELETE = 'SHOPPINGLIST_ITEMS_DELETE';
const PERMISSION_STOCK = 'STOCK';
const PERMISSION_STOCK_CONSUME = 'STOCK_CONSUME';
const PERMISSION_STOCK_EDIT = 'STOCK_EDIT';
const PERMISSION_STOCK_INVENTORY = 'STOCK_INVENTORY';
const PERMISSION_STOCK_OPEN = 'STOCK_OPEN';
const PERMISSION_STOCK_PURCHASE = 'STOCK_PURCHASE';
const PERMISSION_STOCK_TRANSFER = 'STOCK_TRANSFER';
const PERMISSION_TASKS = 'TASKS';
const PERMISSION_TASKS_MARK_COMPLETED = 'TASKS_MARK_COMPLETED';
const PERMISSION_TASKS_UNDO_EXECUTION = 'TASKS_UNDO_EXECUTION';
const PERMISSION_USERS = 'USERS';
const PERMISSION_USERS_CREATE = 'USERS_CREATE';
const PERMISSION_USERS_EDIT = 'USERS_EDIT';
const PERMISSION_USERS_EDIT_SELF = 'USERS_EDIT_SELF';
const PERMISSION_USERS_READ = 'USERS_READ';
public function __construct()
{
$this->db = DatabaseService::getInstance()->GetDbConnection();
}
/**
* @var \LessQL\Database|null
*/
@@ -59,11 +83,6 @@ class User
return $user->getPermissionList();
}
public function __construct()
{
$this->db = DatabaseService::getInstance()->GetDbConnection();
}
public static function checkPermission($request, string ...$permissions): void
{
$user = new self();

View File

@@ -231,9 +231,4 @@ class UsersApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}

66
docs/grocycode.md Normal file
View File

@@ -0,0 +1,66 @@
grocycode
==========
grocycode is, in essence, a simple way to reference to arbitrary grocy entities.
Each grocycode includes a magic, an entitiy identifier, an id and an ordered set of extra data.
It is supported to be entered anywhere grocy expects one to read a barcode, but can also reference
grocy-internal properties like specific stock entries, or specific batteries.
Serialization
----
There are three mandatory parts in a grocycode:
1. The magic `grcy`
2. An entity identifer matching the regular expression `[a-z]+` (that is, lowercase english alphabet without any fancy accents, minimum length 1 character).
3. An object identifer matching the regular expression `[0-9]+`
Optionally, any number of further data without format restrictions besides not containing any double colons [0] may be appended.
These parts are then linearly appended, seperated by a double colon `:`.
Entity Identifers
----
Currently, there are three different entity types defined:
- `p` for Products
- `b` for Batteries
- `c` for Chores
Example
----
In this example, we encode a *Product* with ID *13*, which results in `grcy:p:13` when serialized.
Product grocycodes
----
Product grocycodes extend the data format to include an optional stock id, thus may reference a specific stock entry directly.
Example: `grcy:p:13:60bf8b5244b04`
Battery grocycodes
----
Currently, Battery grocycodes do not define any extra fields.
Chore grocycodes
----
Currently, Chore grocycodes do not define any extra fields.
Visual Encoding
----
Grocy uses DataMatrix 2D (or alternatively Code128 1D) Barcodes to encode grocycodes into a visual representation. In principle, there is no problem with using
other encoding formats like QR codes; however DataMatrix uses less space for the same information and redundancy and is a bit
easier read by 2D barcode scanners, especially on non-flat surfaces.
You can pick up cheap-ish used scanners from ebay (about 45€ in germany). Make sure to set them to the correct keyboard emulation,
so that the double colons get entered correctly.
Notes
---
[0]: Obviously, it needs to be encoded into some usable visual representation and then read. So probably you only want to encode stuff that can be typed on a keyboard.

40
docs/label-printing.md Normal file
View File

@@ -0,0 +1,40 @@
Label 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?
---
Label printers come in all shapes and forms, and your particular one is probably not the one used by the author of this feature. Also, grocy may does not have a
direct connection to a local label printer (e.g. grocy is hosted in a cloud vps). Thus, a lightweight implementation is provided by grocy: whenever something
should print, a POST request to a configured URL is made. The target then is responsible for label printing.
Reference implementation
---
The webhook was developed and tested against a Brother QL-600 label printer, using endless 62mm label paper. The webhook provider implementation was
implemented into [a fork of brother_ql_web](https://github.com/mistressofjellyfish/brother_ql_web).
Webhook request
---
Requests can be configured to be sent server-side (that is, from the machine hosting grocy through GuzzleHttp) or by an AJAX request directly from the browser.
The latter is neccesary for situations where the grocy hosting machine cannot reach your label printer, however server-side requests are a bit faster and
tend to be more stable.
Both methods fire this request upon printing:
```
POST /your/printing/api/endpoint HTTP/1.1
product=<productname>&grocycode=grocy:x:xxx&due_date=DD:%2021-06-09&...
```
If specified, the request body may also be JSON encoded, however the fields stay the same.
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.

View File

@@ -52,6 +52,9 @@
},
{
"name": "Files"
},
{
"name": "Print"
}
],
"paths": {
@@ -181,6 +184,27 @@
}
}
},
"/system/localization-strings": {
"get": {
"summary": "Returns all localization strings (in the by the user desired language)",
"tags": [
"System"
],
"responses": {
"200": {
"description": "The operation was successful",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "A gettext JSON representation"
}
}
}
}
}
}
},
"/system/log-missing-localization": {
"post": {
"summary": "Logs a missing localization string",
@@ -229,7 +253,7 @@
"required": true,
"description": "A valid entity name",
"schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotListable"
"$ref": "#/components/schemas/ExposedEntity_NotIncludingNotListable"
}
},
{
@@ -318,7 +342,7 @@
"required": true,
"description": "A valid entity name",
"schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotEditable"
"$ref": "#/components/schemas/ExposedEntity_NotIncludingNotEditable"
}
}
],
@@ -402,7 +426,7 @@
"required": true,
"description": "A valid entity name",
"schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotListable"
"$ref": "#/components/schemas/ExposedEntity_NotIncludingNotListable"
}
},
{
@@ -486,7 +510,7 @@
"required": true,
"description": "A valid entity name",
"schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotEditable"
"$ref": "#/components/schemas/ExposedEntity_NotIncludingNotEditable"
}
},
{
@@ -563,7 +587,7 @@
"required": true,
"description": "A valid entity name",
"schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotDeletable"
"$ref": "#/components/schemas/ExposedEntity_NotIncludingNotDeletable"
}
},
{
@@ -606,7 +630,7 @@
"required": true,
"description": "A valid entity name",
"schema": {
"$ref": "#/components/internalSchemas/ExposedEntity"
"$ref": "#/components/schemas/ExposedEntity_IncludingUserEntities"
}
},
{
@@ -655,7 +679,7 @@
"required": true,
"description": "A valid entity name",
"schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotEditable"
"$ref": "#/components/schemas/ExposedEntity_IncludingUserEntities_NotIncludingNotEditable"
}
},
{
@@ -710,7 +734,7 @@
"required": true,
"description": "The file group",
"schema": {
"type": "string"
"$ref": "#/components/schemas/FileGroups"
}
},
{
@@ -790,7 +814,7 @@
"required": true,
"description": "The file group",
"schema": {
"type": "string"
"$ref": "#/components/schemas/FileGroups"
}
},
{
@@ -841,7 +865,7 @@
"required": true,
"description": "The file group",
"schema": {
"type": "string"
"$ref": "#/components/schemas/FileGroups"
}
},
{
@@ -1402,7 +1426,7 @@
"in": "path",
"name": "entryId",
"required": true,
"description": "A valid stock row id",
"description": "A valid stock entry id",
"schema": {
"type": "integer"
}
@@ -1441,7 +1465,7 @@
"in": "path",
"name": "entryId",
"required": true,
"description": "A valid stock row id",
"description": "A valid stock entry id",
"schema": {
"type": "integer"
}
@@ -1529,6 +1553,48 @@
}
}
},
"/stock/entry/{entryId}/printlabel": {
"get": {
"summary": "Prints the grocycode / stock entry label of the given entry on the configured label printer",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "entryId",
"required": true,
"description": "A valid stock entry 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 stock entry, error on WebHook execution)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error400"
}
}
}
}
}
}
},
"/stock/volatile": {
"get": {
"summary": "Returns all products which are due soon, overdue, expired or currently missing",
@@ -1834,7 +1900,7 @@
"description": "The due date of the product to add, when omitted, the current date is used"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
"$ref": "#/components/schemas/StockTransactionType"
},
"price": {
"type": "number",
@@ -1850,6 +1916,10 @@
"type": "number",
"format": "integer",
"description": "If omitted, no store will be affected"
},
"print_stock_label": {
"type": "boolean",
"description": "True when the stock entry label should be printed"
}
},
"example": {
@@ -1918,7 +1988,7 @@
"description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
"$ref": "#/components/schemas/StockTransactionType"
},
"spoiled": {
"type": "boolean",
@@ -2213,6 +2283,48 @@
}
}
},
"/stock/products/{productId}/printlabel": {
"get": {
"summary": "Prints the grocycode label of the given product on the configured label printer",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "productId",
"required": true,
"description": "A valid product 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 product, error on WebHook execution)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error400"
}
}
}
}
}
}
},
"/stock/products/{productIdToKeep}/merge/{productIdToRemove}": {
"post": {
"summary": "Merges two products into one",
@@ -2332,7 +2444,7 @@
"description": "The due date of the product to add, when omitted, the current date is used"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
"$ref": "#/components/schemas/StockTransactionType"
},
"price": {
"type": "number",
@@ -2411,7 +2523,7 @@
"description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
"$ref": "#/components/schemas/StockTransactionType"
},
"spoiled": {
"type": "boolean",
@@ -3350,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",
@@ -3576,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",
@@ -3756,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",
@@ -3778,13 +4022,13 @@
],
"responses": {
"200": {
"description": "An array of Task objects",
"description": "An array of CurrentTaskResponse objects",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Task"
"$ref": "#/components/schemas/CurrentTaskResponse"
}
}
}
@@ -3942,84 +4186,66 @@
}
}
}
},
"/print/shoppinglist/thermal": {
"get": {
"summary": "Prints the shoppinglist with a thermal printer",
"tags": [
"Print"
],
"parameters": [
{
"in": "query",
"name": "list",
"required": false,
"description": "Shopping list id",
"schema": {
"type": "integer",
"default": 1
}
},
{
"in": "query",
"name": "printHeader",
"required": false,
"description": "Prints grocy logo if true",
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Returns OK if the printing was successful",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "The operation was not successful",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error400"
}
}
}
}
}
}
}
},
"components": {
"internalSchemas": {
"ExposedEntity": {
"type": "string",
"enum": [
"products",
"chores",
"product_barcodes",
"batteries",
"locations",
"quantity_units",
"quantity_unit_conversions",
"shopping_list",
"shopping_lists",
"shopping_locations",
"recipes",
"recipes_pos",
"recipes_nestings",
"tasks",
"task_categories",
"product_groups",
"equipment",
"api_keys",
"userfields",
"userentities",
"userobjects",
"meal_plan",
"stock_log",
"stock",
"stock_current_locations",
"api_keys"
]
},
"ExposedEntityNoListing": {
"type": "string",
"enum": [
"api_keys"
]
},
"ExposedEntityNoEdit": {
"type": "string",
"enum": [
"stock_log",
"api_keys",
"stock",
"stock_current_locations"
]
},
"ExposedEntityNoDelete": {
"type": "string",
"enum": [
"stock_log",
"stock",
"stock_current_locations"
]
},
"ExposedEntityEditRequiresAdmin": {
"type": "string",
"enum": [
"api_keys"
]
},
"StockTransactionType": {
"type": "string",
"enum": [
"purchase",
"consume",
"inventory-correction",
"product-opened"
]
},
"StringEnumTemplate": {
"type": "string",
"enum": []
}
},
"schemas": {
"Product": {
"type": "object",
@@ -4386,6 +4612,10 @@
"spoil_rate_percent": {
"type": "number",
"format": "number"
},
"has_childs": {
"type": "boolean",
"description": "True when the product is a parent products of others"
}
},
"example": {
@@ -4716,6 +4946,9 @@
"id": {
"type": "integer"
},
"shopping_list_id": {
"type": "integer"
},
"product_id": {
"type": "integer"
},
@@ -4900,7 +5133,7 @@
"type": "string"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
"$ref": "#/components/schemas/StockTransactionType"
},
"row_created_timestamp": {
"type": "string",
@@ -4948,7 +5181,7 @@
"default": false
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
"$ref": "#/components/schemas/StockTransactionType"
},
"row_created_timestamp": {
"type": "string",
@@ -4998,7 +5231,7 @@
"type": "string"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
"$ref": "#/components/schemas/StockTransactionType"
}
},
"example": {
@@ -5087,14 +5320,26 @@
"chore_id": {
"type": "integer"
},
"chore_name": {
"type": "string"
},
"last_tracked_time": {
"type": "string",
"format": "date-time"
},
"track_date_only": {
"type": "boolean"
},
"next_estimated_execution_time": {
"type": "string",
"format": "date-time",
"description": "The next estimated execution time of this chore, 2999-12-31 23:59:59 when the given chore has a period_type of manually"
},
"next_execution_assigned_to_user_id": {
"type": "integer"
},
"next_execution_assigned_user": {
"$ref": "#/components/schemas/UserDto"
}
}
},
@@ -5196,6 +5441,65 @@
}
}
},
"TaskCategory": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"row_created_timestamp": {
"type": "string",
"format": "date-time"
}
}
},
"CurrentTaskResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"due_date": {
"type": "string",
"format": "date-time"
},
"done": {
"type": "integer"
},
"done_timestamp": {
"type": "string",
"format": "date-time"
},
"category_id": {
"type": "integer"
},
"assigned_to_user_id": {
"type": "integer"
},
"row_created_timestamp": {
"type": "string",
"format": "date-time"
},
"assigned_to_user": {
"$ref": "#/components/schemas/UserDto"
},
"category": {
"$ref": "#/components/schemas/TaskCategory"
}
}
},
"DbChangedTimeResponse": {
"type": "object",
"properties": {
@@ -5246,6 +5550,94 @@
"type": "string"
}
}
},
"ExposedEntity": {
"type": "string",
"enum": [
"products",
"chores",
"product_barcodes",
"batteries",
"locations",
"quantity_units",
"quantity_unit_conversions",
"shopping_list",
"shopping_lists",
"shopping_locations",
"recipes",
"recipes_pos",
"recipes_nestings",
"tasks",
"task_categories",
"product_groups",
"equipment",
"api_keys",
"userfields",
"userentities",
"userobjects",
"meal_plan",
"stock_log",
"stock",
"stock_current_locations",
"chores_log",
"meal_plan_sections"
]
},
"ExposedEntityNoListing": {
"type": "string",
"enum": [
"api_keys"
]
},
"ExposedEntityNoEdit": {
"type": "string",
"enum": [
"stock_log",
"api_keys",
"stock",
"stock_current_locations",
"chores_log"
]
},
"ExposedEntityNoDelete": {
"type": "string",
"enum": [
"stock_log",
"stock",
"stock_current_locations",
"chores_log"
]
},
"ExposedEntityEditRequiresAdmin": {
"type": "string",
"enum": [
"api_keys"
]
},
"StockTransactionType": {
"type": "string",
"enum": [
"purchase",
"consume",
"inventory-correction",
"product-opened"
]
},
"FileGroups": {
"type": "string",
"enum": [
"equipmentmanuals",
"recipepictures",
"productpictures",
"userfiles",
"userpictures"
]
},
"StringEnumTemplate": {
"type": "string",
"enum": [
""
]
}
},
"securitySchemes": {

View File

@@ -4,6 +4,12 @@ namespace Grocy\Helpers;
abstract class BaseBarcodeLookupPlugin
{
final public function __construct($locations, $quantityUnits)
{
$this->Locations = $locations;
$this->QuantityUnits = $quantityUnits;
}
protected $Locations;
protected $QuantityUnits;
@@ -50,28 +56,24 @@ abstract class BaseBarcodeLookupPlugin
// Check referenced entity ids are valid
$locationId = $pluginOutput['location_id'];
if (FindObjectInArrayByPropertyValue($this->Locations, 'id', $locationId) === null)
{
throw new \Exception("Location $locationId is not a valid location id");
}
$quIdPurchase = $pluginOutput['qu_id_purchase'];
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdPurchase) === null)
{
throw new \Exception("Location $quIdPurchase is not a valid quantity unit id");
}
$quIdStock = $pluginOutput['qu_id_stock'];
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdStock) === null)
{
throw new \Exception("Location $quIdStock is not a valid quantity unit id");
}
$quFactor = $pluginOutput['qu_factor_purchase_to_stock'];
if (empty($quFactor) || !is_numeric($quFactor))
{
throw new \Exception('Quantity unit factor is empty or not a number');
@@ -80,11 +82,5 @@ abstract class BaseBarcodeLookupPlugin
return $pluginOutput;
}
final public function __construct($locations, $quantityUnits)
{
$this->Locations = $locations;
$this->QuantityUnits = $quantityUnits;
}
abstract protected function ExecuteLookup($barcode);
}

146
helpers/Grocycode.php Normal file
View File

@@ -0,0 +1,146 @@
<?php
namespace Grocy\Helpers;
/**
* A class that abstracts grocycode.
*
* grocycode is a simple, easily serializable format to reference
* stuff within grocy. It consists of n (n ≥ 3) double-colon seperated parts:
*
* 1. The magic `grcy`
* 2. A type identifer, must match `[a-z]+` (i.e. only lowercase ascii, minimum length 1 character)
* 3. An object id
* 4. Any number of further data fields, double-colon seperated.
*
* @author Katharina Bogad <katharina@hacked.xyz>
*/
class Grocycode
{
public const PRODUCT = 'p';
public const BATTERY = 'b';
public const CHORE = 'c';
public const MAGIC = 'grcy';
/**
* Constructs a new instance of the Grocycode class.
*
* Because php doesn't support overloading, this is a proxy
* to either setFromCode($code) or setFromData($type, $id, $extra_data = []).
*/
public function __construct(...$args)
{
$argc = count($args);
if ($argc == 1)
{
$this->setFromCode($args[0]);
return;
}
elseif ($argc == 2 || $argc == 3)
{
if ($argc == 2)
{
$args[] = [];
}
$this->setFromData($args[0], $args[1], $args[2]);
return;
}
throw new \Exception('No suitable overload found.');
}
/**
* An array that registers all valid grocycode types. Register yours here by appending to this array.
*/
public static $Items = [self::PRODUCT, self::BATTERY, self::CHORE];
private $type;
private $id;
private $extra_data = [];
/**
* Validates a grocycode.
*
* Returns true, if a supplied $code is a valid grocycode, false otherwise.
*
* @return bool
*/
public static function Validate(string $code)
{
try
{
$gc = new self($code);
return true;
}
catch (Exception $e)
{
return false;
}
}
public function GetId()
{
return $this->id;
}
public function GetExtraData()
{
return $this->extra_data;
}
public function GetType()
{
return $this->type;
}
public function __toString(): string
{
$arr = array_merge([self::MAGIC, $this->type, $this->id], $this->extra_data);
return implode(':', $arr);
}
/**
* Parses a grocycode.
*/
private function setFromCode($code)
{
$parts = array_reverse(explode(':', $barcode));
if (array_pop($parts) != self::MAGIC)
{
throw new \Exception('Not a grocycode');
}
if (!in_array($this->type = array_pop($parts), self::$Items))
{
throw new \Exception('Unknown grocycode type');
}
$this->id = array_pop($parts);
$this->extra_data = array_reverse($parse);
}
/**
* Constructs a grocycode from data.
*/
private function setFromData($type, $id, $extra_data = [])
{
if (!is_array($extra_data))
{
throw new \Exception('Extra data must be array of string');
}
if (!in_array($type, self::$Items))
{
throw new \Exception('Unknown grocycode type');
}
$this->type = $type;
$this->id = $id;
$this->extra_data = $extra_data;
}
}

View File

@@ -4,7 +4,7 @@ class ERequirementNotMet extends Exception
{
}
const REQUIRED_PHP_EXTENSIONS = ['fileinfo', 'pdo_sqlite', 'gd', 'ctype'];
const REQUIRED_PHP_EXTENSIONS = ['fileinfo', 'pdo_sqlite', 'gd', 'ctype', 'json', 'intl', 'zlib'];
const REQUIRED_SQLITE_VERSION = '3.9.0';
class PrerequisiteChecker

View File

@@ -4,6 +4,18 @@ namespace Grocy\Helpers;
class UrlManager
{
public function __construct(string $basePath)
{
if ($basePath === '/')
{
$this->BasePath = $this->GetBaseUrl();
}
else
{
$this->BasePath = $basePath;
}
}
protected $BasePath;
public function ConstructUrl($relativePath, $isResource = false)
@@ -18,18 +30,6 @@ class UrlManager
}
}
public function __construct(string $basePath)
{
if ($basePath === '/')
{
$this->BasePath = $this->GetBaseUrl();
}
else
{
$this->BasePath = $basePath;
}
}
private function GetBaseUrl()
{
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)

48
helpers/WebhookRunner.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace Grocy\Helpers;
use GuzzleHttp\Client;
use GuzzleHttp\ExceptionRequestException;
use Psr\Http\Message\ResponseInterface;
class WebhookRunner
{
public function __construct()
{
$this->client = new Client(['timeout' => 2.0]);
}
private $client;
public function run($url, $args, $json = false)
{
$reqArgs = [];
if ($json)
{
$reqArgs = ['json' => $args];
}
else
{
$reqArgs = ['form_params' => $args];
}
try
{
file_put_contents('php://stderr', 'Running Webhook: ' . $url . "\n" . print_r($reqArgs, true));
$this->client->request('POST', $url, $reqArgs);
}
catch (RequestException $e)
{
file_put_contents('php://stderr', 'Webhook failed: ' . $url . "\n" . $e->getMessage());
}
}
public function runAll($urls, $args)
{
foreach ($urls as $url)
{
$this->run($url, $args);
}
}
}

View File

@@ -4,8 +4,7 @@ function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
{
foreach ($array as $object)
{
if ($object->{$propertyName}
== $propertyValue)
if ($object->{$propertyName} == $propertyValue)
{
return $object;
}
@@ -17,37 +16,28 @@ function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyValue, $operator = '==')
{
$returnArray = [];
foreach ($array as $object)
{
switch ($operator)
{
case '==':
if ($object->{$propertyName}
== $propertyValue)
if ($object->{$propertyName} == $propertyValue)
{
$returnArray[] = $object;
}
break;
case '>':
if ($object->{$propertyName}
> $propertyValue)
if ($object->{$propertyName} > $propertyValue)
{
$returnArray[] = $object;
}
break;
case '<':
if ($object->{$propertyName}
< $propertyValue)
if ($object->{$propertyName} < $propertyValue)
{
$returnArray[] = $object;
}
break;
}
}
@@ -58,7 +48,6 @@ function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyVa
function FindAllItemsInArrayByValue($array, $value, $operator = '==')
{
$returnArray = [];
foreach ($array as $item)
{
switch ($operator)
@@ -69,7 +58,6 @@ function FindAllItemsInArrayByValue($array, $value, $operator = '==')
{
$returnArray[] = $item;
}
break;
case '>':
@@ -77,7 +65,6 @@ function FindAllItemsInArrayByValue($array, $value, $operator = '==')
{
$returnArray[] = $item;
}
break;
case '<':
@@ -85,7 +72,6 @@ function FindAllItemsInArrayByValue($array, $value, $operator = '==')
{
$returnArray[] = $item;
}
break;
}
}
@@ -96,7 +82,6 @@ function FindAllItemsInArrayByValue($array, $value, $operator = '==')
function SumArrayValue($array, $propertyName)
{
$sum = 0;
foreach ($array as $object)
{
$sum += floatval($object->{$propertyName});
@@ -124,7 +109,6 @@ function GetClassConstants($className, $prefix = null)
function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$randomString = '';
for ($i = 0; $i < $length; $i++)
{
$randomString .= $allowedChars[rand(0, strlen($allowedChars) - 1)];
@@ -156,6 +140,11 @@ function BoolToString(bool $bool)
return $bool ? 'true' : 'false';
}
function BoolToInt(bool $bool)
{
return $bool ? 1 : 0;
}
function ExternalSettingValue(string $value)
{
$tvalue = rtrim($value, "\r\n");
@@ -185,7 +174,8 @@ function Setting(string $name, $value)
define('GROCY_' . $name, ExternalSettingValue(file_get_contents($settingOverrideFile)));
}
elseif (getenv('GROCY_' . $name) !== false)
{ // An environment variable with the same name and prefix GROCY_ overwrites the given setting
{
// An environment variable with the same name and prefix GROCY_ overwrites the given setting
define('GROCY_' . $name, ExternalSettingValue(getenv('GROCY_' . $name)));
}
else

View File

@@ -400,3 +400,12 @@ msgstr ""
msgid "Finnish"
msgstr "Finsky"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -1,6 +1,7 @@
#
# Translators:
# Radim Kabeláč <radim.ekk@gmail.com>, 2020
# Jarda Tesar <intossh@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -8,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-31 19:11+0000\n"
"Last-Translator: Radim Kabeláč <radim.ekk@gmail.com>, 2020\n"
"Last-Translator: Jarda Tesar <intossh@gmail.com>, 2021\n"
"Language-Team: Czech (https://www.transifex.com/grocy/teams/93189/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -107,16 +108,16 @@ msgstr "Čínština (Tradiční)"
# Chinese (China)
msgid "zh_CN"
msgstr ""
msgstr "Čínština (Zjednodušená)"
# Hebrew (Israel)
msgid "he_IL"
msgstr ""
msgstr "Hebrejština"
# Tamil
msgid "ta"
msgstr ""
msgstr "Tamilština"
# Finnish
msgid "fi"
msgstr ""
msgstr "Finština"

View File

@@ -1,6 +1,7 @@
#
# Translators:
# Radim Kabeláč <radim.ekk@gmail.com>, 2020
# Jarda Tesar <intossh@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -8,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: Radim Kabeláč <radim.ekk@gmail.com>, 2020\n"
"Last-Translator: Jarda Tesar <intossh@gmail.com>, 2021\n"
"Language-Team: Czech (https://www.transifex.com/grocy/teams/93189/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -43,11 +44,11 @@ msgstr "BATERIE_VRACENI_NABIJECI_CYKLUS"
# Track charge cycle
msgid "BATTERIES_TRACK_CHARGE_CYCLE"
msgstr ""
msgstr "BATERIE_SLEDOVANI_NABIJECI_CYKLUS"
# Track execution
msgid "CHORE_TRACK_EXECUTION"
msgstr ""
msgstr "POVINNOST_SLEDOVANI_VYKONANI"
# Undo execution
msgid "CHORE_UNDO_EXECUTION"

View File

@@ -8,6 +8,7 @@
# Adam Kroupa <mavi222@seznam.cz>, 2020
# Radim Kabeláč <radim.ekk@gmail.com>, 2020
# Jaroslav Lichtblau <jlichtblau@seznam.cz>, 2020
# Jarda Tesar <intossh@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -15,7 +16,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: Jaroslav Lichtblau <jlichtblau@seznam.cz>, 2020\n"
"Last-Translator: Jarda Tesar <intossh@gmail.com>, 2021\n"
"Language-Team: Czech (https://www.transifex.com/grocy/teams/93189/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -352,8 +353,8 @@ msgstr "Opravdu chcete smazat obchod \"%s\"?"
msgid "Manage API keys"
msgstr "Spravovat API klíče"
msgid "REST API & data model documentation"
msgstr "REST API & dokumentace datových modelů"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "API klíče"
@@ -655,6 +656,9 @@ 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 ""
"Opravdu si přejete spotřebovat všechny suroviny potřebné v receptu \"%s\" "
"(suroviny označené \"Zkontrolovat pouze zda je v zásobě jakékoliv množství\""
" budou ignorované)?"
msgid "Removed all ingredients of recipe \"%s\" from stock"
msgstr "Odebrány všechny suroviny z receptu \"%s\" ze zásob"
@@ -896,7 +900,7 @@ msgid "Add all list items to stock"
msgstr "Přidat všechny položky seznamu do zásob"
msgid "Add this item to stock"
msgstr ""
msgstr "Přidat tuto položku do zásob"
msgid "Adding shopping list item %1$s of %2$s"
msgstr "Přidána položka nákupního seznamu %1$s z %2$s"
@@ -1975,7 +1979,7 @@ msgid "Save & continue to add quantity unit conversions & barcodes"
msgstr "Uložit a pokračovat do přidání měrných jednotek a čárových kódů"
msgid "Save & return to products"
msgstr "Uložit a pokračovat v editaci"
msgstr "Uložit a vrátit se do produktů"
msgid "Save & continue to add conversions"
msgstr "Uložit a pokračovat do přidání konverzí"
@@ -2106,7 +2110,8 @@ msgstr "Výchozí počet dní spotřeby"
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 ""
msgid "Default due days after opened"
@@ -2315,3 +2320,183 @@ msgstr "Nastavení tisku"
msgid "A product or a note is required"
msgstr "Je vyžadován produkt nebo poznámka"
msgid "grocycode"
msgstr ""
msgid "Download"
msgstr ""
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr ""
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr ""
msgid "Single label"
msgstr ""
msgid "Label per unit"
msgstr ""
msgid "Allow label printing per unit"
msgstr ""
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
msgid "Error while executing WebHook"
msgstr ""
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
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] ""
msgstr[2] ""
msgstr[3] ""
msgid "%s year"
msgid_plural "%s years"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -1,6 +1,7 @@
#
# Translators:
# Troels Siggaard <troels@siggaard.com>, 2019
# klavslund <klavslund@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -8,7 +9,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: Troels Siggaard <troels@siggaard.com>, 2019\n"
"Last-Translator: klavslund <klavslund@gmail.com>, 2021\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,7 +22,7 @@ msgid "no-assignment"
msgstr "ingen-tildeling"
msgid "who-least-did-first"
msgstr "hvem-mindst-gjorde-først"
msgstr "hvem-gjorde-mindst-først"
msgid "random"
msgstr "tilfældig"

View File

@@ -5,6 +5,7 @@
# Rasmus Bojsen <rasmus@bojsen.cn>, 2019
# Brian Moos Lindberg <brian@blueeel.dk>, 2019
# Mihai Marinescu <mihai@marinescu.dk>, 2020
# klavslund <klavslund@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -12,7 +13,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: Mihai Marinescu <mihai@marinescu.dk>, 2020\n"
"Last-Translator: klavslund <klavslund@gmail.com>, 2021\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -376,13 +377,22 @@ msgid "Korean"
msgstr "Koreansk"
msgid "Chinese (China)"
msgstr ""
msgstr "Kinesisk"
msgid "Hebrew (Israel)"
msgstr ""
msgstr "Hebraisk"
msgid "Tamil"
msgstr ""
msgstr "Tamilsk"
msgid "Finnish"
msgstr "Finsk"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

122
localization/da/locales.po Normal file
View File

@@ -0,0 +1,122 @@
#
# Translators:
# klavslund <klavslund@gmail.com>, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2020-08-31 19:11+0000\n"
"Last-Translator: klavslund <klavslund@gmail.com>, 2021\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: da\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/locales\n"
# Czech
msgid "cs"
msgstr "cs"
# Danish
msgid "da"
msgstr "da"
# German
msgid "de"
msgstr "de"
# Greek
msgid "el_GR"
msgstr "el_GR"
# English
msgid "en"
msgstr "en"
# English (Great Britain)
msgid "en_GB"
msgstr "en_GB"
# Spanish
msgid "es"
msgstr "es"
# French
msgid "fr"
msgstr "fr"
# Hungarian
msgid "hu"
msgstr "hu"
# Italian
msgid "it"
msgstr "it"
# Japanese
msgid "ja"
msgstr "ja"
# Korean
msgid "ko_KR"
msgstr "ko_KR"
# Dutch
msgid "nl"
msgstr "nl"
# Norwegian
msgid "no"
msgstr "no"
# Polish
msgid "pl"
msgstr "pl"
# Portuguese (Brazil)
msgid "pt_BR"
msgstr "pt_BR"
# Portuguese (Portugal)
msgid "pt_PT"
msgstr "pt_PT"
# Russian
msgid "ru"
msgstr "ru"
# Slovak
msgid "sk_SK"
msgstr "sk_SK"
# Swedish
msgid "sv_SE"
msgstr "sv_SE"
# Turkish
msgid "tr"
msgstr "tr"
# Chinese (Taiwan)
msgid "zh_TW"
msgstr "zh_TW"
# Chinese (China)
msgid "zh_CN"
msgstr "zh_CN"
# Hebrew (Israel)
msgid "he_IL"
msgstr "he_IL"
# Tamil
msgid "ta"
msgstr "ta"
# Finnish
msgid "fi"
msgstr "fi"

View File

@@ -0,0 +1,139 @@
#
# Translators:
# Mihai Marinescu <mihai@marinescu.dk>, 2020
# klavslund <klavslund@gmail.com>, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"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: klavslund <klavslund@gmail.com>, 2021\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: da\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/permissions\n"
# All permissions
msgid "ADMIN"
msgstr "ADMIN"
# Create users
msgid "USERS_CREATE"
msgstr "USERS_CREATE"
# Edit users (including passwords)
msgid "USERS_EDIT"
msgstr "USERS_EDIT"
# Show users
msgid "USERS_READ"
msgstr "USERS_READ"
# Edit own user data / change own password
msgid "USERS_EDIT_SELF"
msgstr "USERS_EDIT_SELF"
# Undo charge cycle
msgid "BATTERIES_UNDO_CHARGE_CYCLE"
msgstr "BATTERIES_UNDO_CHARGE_CYCLE"
# Track charge cycle
msgid "BATTERIES_TRACK_CHARGE_CYCLE"
msgstr "BATTERIES_TRACK_CHARGE_CYCLE"
# Track execution
msgid "CHORE_TRACK_EXECUTION"
msgstr "CHORE_TRACK_EXECUTION"
# Undo execution
msgid "CHORE_UNDO_EXECUTION"
msgstr "CHORE_UNDO_EXECUTION"
# Edit master data
msgid "MASTER_DATA_EDIT"
msgstr "MASTER_DATA_EDIT"
# Undo execution
msgid "TASKS_UNDO_EXECUTION"
msgstr "TASKS_UNDO_EXECUTION"
# Mark completed
msgid "TASKS_MARK_COMPLETED"
msgstr "TASKS_MARK_COMPLETED"
# Edit stock entries
msgid "STOCK_EDIT"
msgstr "STOCK_EDIT"
# Transfer
msgid "STOCK_TRANSFER"
msgstr "STOCK_TRANSFER"
# Inventory
msgid "STOCK_INVENTORY"
msgstr "STOCK_INVENTORY"
# Consume
msgid "STOCK_CONSUME"
msgstr "STOCK_CONSUME"
# Open products
msgid "STOCK_OPEN"
msgstr "STOCK_OPEN"
# Purchase
msgid "STOCK_PURCHASE"
msgstr "STOCK_PURCHASE"
# Add items
msgid "SHOPPINGLIST_ITEMS_ADD"
msgstr "SHOPPINGLIST_ITEMS_ADD"
# Remove items
msgid "SHOPPINGLIST_ITEMS_DELETE"
msgstr "SHOPPINGLIST_ITEMS_DELETE"
# User management
msgid "USERS"
msgstr "BRUGERE"
# Stock
msgid "STOCK"
msgstr "BEHOLDNING"
# Shopping list
msgid "SHOPPINGLIST"
msgstr "INDKØBSLISTE"
# Chores
msgid "CHORES"
msgstr "GØREMÅL"
# Batteries
msgid "BATTERIES"
msgstr "BATTERIER"
# Tasks
msgid "TASKS"
msgstr "OPGAVER"
# Recipes
msgid "RECIPES"
msgstr "OPSKRIFTER"
# Equipment
msgid "EQUIPMENT"
msgstr "UDSTYR"
# Calendar
msgid "CALENDAR"
msgstr "KALENDER"
# Meal plan
msgid "RECIPES_MEALPLAN"
msgstr "RECIPES_MEALPLAN"

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
# Translators:
# Brian Moos Lindberg <brian@blueeel.dk>, 2019
# Mihai Marinescu <mihai@marinescu.dk>, 2020
# klavslund <klavslund@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -9,7 +10,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:43+0000\n"
"Last-Translator: Mihai Marinescu <mihai@marinescu.dk>, 2020\n"
"Last-Translator: klavslund <klavslund@gmail.com>, 2021\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -60,7 +61,7 @@ msgstr "link"
# Link (with title)
msgid "link-with-title"
msgstr ""
msgstr "link-med-overskrift"
# File
msgid "file"

View File

@@ -1,6 +1,6 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Bernd Bestel <bernd@berrnd.de>, 2021
#
msgid ""
msgstr ""
@@ -8,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: Bernd Bestel <bernd@berrnd.de>, 2019\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 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"

View File

@@ -1,6 +1,6 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Bernd Bestel <bernd@berrnd.de>, 2021
#
msgid ""
msgstr ""
@@ -8,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: Bernd Bestel <bernd@berrnd.de>, 2019\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 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"

View File

@@ -1,6 +1,6 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2020
# Bernd Bestel <bernd@berrnd.de>, 2021
#
msgid ""
msgstr ""
@@ -8,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: Bernd Bestel <bernd@berrnd.de>, 2020\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 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"
@@ -382,3 +382,12 @@ msgstr "Tamil"
msgid "Finnish"
msgstr "Finnisch"
msgid "Breakfast"
msgstr "Frühstück"
msgid "Lunch"
msgstr "Mittagessen"
msgid "Dinner"
msgstr "Abendessen"

View File

@@ -1,6 +1,7 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2020
# @RubenKelevra <ruben@freifunk-nrw.de>, 2021
# Bernd Bestel <bernd@berrnd.de>, 2021
#
msgid ""
msgstr ""
@@ -8,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: Bernd Bestel <bernd@berrnd.de>, 2020\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 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"

View File

@@ -1,8 +1,8 @@
#
# Translators:
# Hagen Tasche <github@fvbor.de>, 2020
# Luca RHK <luca@rhk-in.de>, 2020
# Tobias Wolter <mumpfpuffel@gmail.com>, 2020
# @RubenKelevra <ruben@freifunk-nrw.de>, 2021
# Bernd Bestel <bernd@berrnd.de>, 2021
#
msgid ""
@@ -337,8 +337,8 @@ 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"
@@ -1977,10 +1977,10 @@ 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 "Bestandseinträge für dieses Produkt"
@@ -2135,11 +2135,13 @@ 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 dieses Produkt als geöffnet markiert wurde, wird das Fälligkeitsdatum "
"durch heute + diese Anzahl von Tagen ersetzt (ein Wert von 0 deaktiviert "
"dies) "
"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-Fälligkeitstage nach dem Öffnen"
@@ -2374,3 +2376,189 @@ msgstr "Druckoptionen"
msgid "A product or a note is required"
msgstr "Ein Produkt oder eine Notiz ist erforderlich"
msgid "grocycode"
msgstr "grocycode"
msgid "Download"
msgstr "Herunterladen"
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr "%s grocycode herunterladen"
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 ""
"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"
msgid "DD"
msgstr "MHD"
msgid "Print on label printer"
msgstr "Auf Etikettendrucker drucken"
msgid "Stock entry label"
msgstr "Bestandseintrag-Etikett"
msgid "No label"
msgstr "Kein Etikett"
msgid "Single label"
msgstr "Ein Etikett pro Einkauf"
msgid "Label per unit"
msgstr "Ein Etikett pro Einheit"
msgid "Allow label printing per unit"
msgstr "Erlaube Etikettendruck pro Einheit"
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
"Erlaube Etikettendruck pro Einheit nach Umrechnung - Beispiel: 1 gekaufte "
"Packung, die 10 Einheiten hinzufügt, druckt 10 Etiketten"
msgid "Error while executing WebHook"
msgstr "Fehler bei WebHook-Ausführung"
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr "%s grocycode auf Etikettendrucker drucken"
msgid "Open stock entry label in new window"
msgstr "Bestandseintrag-Etikett in neuem Fenster öffnen"
msgid "Thermal printer"
msgstr "Thermodrucker"
msgid "Printing"
msgstr "Drucken"
msgid "Connecting to printer..."
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"
msgid "Configure sections"
msgstr "Abschnitte konfigurieren"
msgid "Meal plan sections"
msgstr "Speiseplan-Abschnitte"
msgid "Create meal plan section"
msgstr "Speiseplan-Abschnitt erstellen"
msgid "Sections will be ordered by that number on the meal plan"
msgstr "Die Abschnitte werden nach dieser Nummer auf dem Speiseplan sortiert"
msgid "Edit meal plan section"
msgstr "Speiseplan-Abschnitt bearbeiten"
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr "Speiseplan-Abschnitt \"%s\" wirklich löschen?"
msgid "Section"
msgstr "Abschnitt"

View File

@@ -377,3 +377,12 @@ msgstr ""
msgid "Finnish"
msgstr ""
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -384,3 +384,12 @@ msgstr ""
msgid "Finnish"
msgstr ""
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -340,8 +340,8 @@ msgstr "Είστε βέβαιοι ότι θα διαγράψετε το κατά
msgid "Manage API keys"
msgstr "Διαχείριση κλειδιών API"
msgid "REST API & data model documentation"
msgstr "REST API & τεκμηρίωση μοντέλου δεδομένων"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "Κλειδιά API"
@@ -2100,7 +2100,8 @@ msgstr ""
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 ""
msgid "Default due days after opened"
@@ -2301,3 +2302,179 @@ msgstr ""
msgid "A product or a note is required"
msgstr ""
msgid "grocycode"
msgstr ""
msgid "Download"
msgstr ""
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr ""
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr ""
msgid "Single label"
msgstr ""
msgid "Label per unit"
msgstr ""
msgid "Allow label printing per unit"
msgstr ""
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
msgid "Error while executing WebHook"
msgstr ""
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -3,6 +3,7 @@
# Jonathan Adams <jonathan@connockadams.uk>, 2020
# duck. <me@duck.me.uk>, 2020
# John Coles <john@johncoles.com>, 2020
# Chris H <cjh861@outlook.com>, 2021
#
msgid ""
msgstr ""
@@ -10,7 +11,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: John Coles <john@johncoles.com>, 2020\n"
"Last-Translator: Chris H <cjh861@outlook.com>, 2021\n"
"Language-Team: English (United Kingdom) (https://www.transifex.com/grocy/teams/93189/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -374,13 +375,22 @@ msgid "Korean"
msgstr "Korean"
msgid "Chinese (China)"
msgstr ""
msgstr "Chinese (China)"
msgid "Hebrew (Israel)"
msgstr ""
msgstr "Hebrew (Israel)"
msgid "Tamil"
msgstr ""
msgstr "Tamil"
msgid "Finnish"
msgstr "Finnish"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -334,8 +334,8 @@ msgstr "Are you sure to delete shop \"%s\"?"
msgid "Manage API keys"
msgstr "Manage API keys"
msgid "REST API & data model documentation"
msgstr "REST API & data model documentation"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "API keys"
@@ -2060,7 +2060,8 @@ msgstr ""
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 ""
msgid "Default due days after opened"
@@ -2261,3 +2262,179 @@ msgstr ""
msgid "A product or a note is required"
msgstr ""
msgid "grocycode"
msgstr ""
msgid "Download"
msgstr ""
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr ""
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr ""
msgid "Single label"
msgstr ""
msgid "Label per unit"
msgstr ""
msgid "Allow label printing per unit"
msgstr ""
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
msgid "Error while executing WebHook"
msgstr ""
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -119,13 +119,13 @@ msgid "Warranty ends"
msgstr "Final de la garantía"
msgid "TV remote control"
msgstr "Control remoto del televisor"
msgstr "Mando a distancia de la televisión"
msgid "Alarm clock"
msgstr "Despertador"
msgid "Heat remote control"
msgstr "Control remoto de la calefacción"
msgstr "Mando a distancia de la calefacción"
msgid "Lawn mowed in the garden"
msgstr "Cortar el césped del jardín"
@@ -387,3 +387,12 @@ msgstr "Tamil"
msgid "Finnish"
msgstr "Finés"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -10,6 +10,7 @@
# Alan Pucheta <apucheta@protonmail.com>, 2020
# Enrique Lapenta <enriquelap20@gmail.com>, 2020
# Alberto Martin <ami232@gmail.com>, 2021
# Adrián R.G. <adrirgrillo@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -17,7 +18,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: Alberto Martin <ami232@gmail.com>, 2021\n"
"Last-Translator: Adrián R.G. <adrirgrillo@gmail.com>, 2021\n"
"Language-Team: Spanish (https://www.transifex.com/grocy/teams/93189/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -345,8 +346,8 @@ msgstr "¿Está seguro de querer eliminar la tienda \"%s\"?"
msgid "Manage API keys"
msgstr "Administrar las claves de API"
msgid "REST API & data model documentation"
msgstr "Documentación de la API REST y modelo de datos"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "Claves de API"
@@ -889,7 +890,7 @@ 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 ""
"El primer artículo de esta lista será selecionado según la regla "
"El primer artículo de esta lista será seleccionado según la regla "
"predeterminada, que es \"Abiertos primero, luego el próximo en llegar a su "
"fecha límite y luego el que se adquirió primero\""
@@ -971,7 +972,7 @@ msgid "Uncheck ingredients to not put them on the shopping list"
msgstr "Desmarca ingredientes para no añadirlos a la lista de la compra"
msgid "This is for statistical purposes only"
msgstr "lo para fines estadísticos"
msgstr "Esto es solo para fines estadísticos"
msgid "You have to select a recipe"
msgstr "Debe de seleccionar una receta"
@@ -1359,7 +1360,7 @@ msgid "Override for product"
msgstr "Conversión particular para el producto"
msgid "This equals %1$s %2$s"
msgstr "Esto es igual a %1$s %2$"
msgstr "Esto es igual a %1$s %2$s"
msgid "Edit QU conversion"
msgstr "Modificar la conversión de unidades"
@@ -2058,7 +2059,7 @@ msgid ""
"This is the default quantity unit used when adding this product to the "
"shopping list"
msgstr ""
"Esta es la unidad de cantidad predeterminada para añadir este producto a la "
"Esta es la unidad de cantidad predeterminada al añadir este producto a la "
"lista de la compra"
msgid ""
@@ -2078,6 +2079,9 @@ msgid ""
"automatically populated (by product and/or barcode defaults), the "
"transaction is automatically submitted"
msgstr ""
"Si está activado, al cambiar/escanear un producto cuyos todos sus campos "
"pueden ser rellenados automáticamente (con los valores predeterminados para "
"el producto o código de barras), la transacción se realiza automáticamente"
msgid "Quick consume amount"
msgstr "Cantidad de consumo rápido"
@@ -2136,11 +2140,9 @@ msgstr "Número predeterminado de días hasta la fecha límite"
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 ""
"Cuando este producto se marque como abierto, la fecha límite será "
"reemplazada por la fecha de hoy más este número de días (un valor de 0 "
"desactiva esto)"
msgid "Default due days after opened"
msgstr ""
@@ -2376,3 +2378,179 @@ msgstr "Opciones de impresión"
msgid "A product or a note is required"
msgstr "Hace falta un producto o una nota"
msgid "grocycode"
msgstr ""
msgid "Download"
msgstr ""
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr ""
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr ""
msgid "Single label"
msgstr ""
msgid "Label per unit"
msgstr ""
msgid "Allow label printing per unit"
msgstr ""
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
msgid "Error while executing WebHook"
msgstr ""
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -385,3 +385,12 @@ msgstr "Tamili"
msgid "Finnish"
msgstr "Suomi"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -392,3 +392,12 @@ msgstr "Tamoul"
msgid "Finnish"
msgstr "Finnois"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -11,7 +11,6 @@
# Antonin DESFONTAINES <antonin.desfontaines@outlook.com>, 2019
# Adrien Guillement <adrien.guillement@gmail.com>, 2019
# Matthias Baumgartner <dersoistargate@gmail.com>, 2019
# Zkryvix <angelo.frangione@gmail.com>, 2020
# Guillaume RICHARD <giz.richard@gmail.com>, 2020
# Bastien SOL <agentcobra57@gmail.com>, 2020
# Bruno D'agen <iamlionem@gmail.com>, 2020
@@ -22,15 +21,18 @@
# S Hugeee <sebsebsebseb007@gmail.com>, 2020
# Renaud Martinet <me+github@renaudmarti.net>, 2020
# Pierre Dumoulin <dumoulinpierre@icloud.com>, 2020
# Michel Baie <tristan@lesbringuier.net>, 2020
# Tristan <tristan@lesbringuier.net>, 2020
# Jordan COUTON <couton.jordan@gmail.com>, 2020
# Juan RODRIGUEZ <juansero29@gmail.com>, 2020
# Clément CHABANNE <clementchabanne@gmail.com>, 2020
# Daniel Nautré <daniel.nautre@gmail.com>, 2020
# patate douce <poubel125@gmail.com>, 2020
# nerdinator <florian.dupret@gmail.com>, 2020
# C P <anoxy78@gmail.com>, 2020
# voslin <web@frugier.net>, 2021
# Julien Pidoux <julien@pidoux.me>, 2021
# Zorvalt - <zorvalt@protonmail.ch>, 2021
# Zkryvix <angelo.frangione@gmail.com>, 2021
# patate douce <poubel125@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -38,7 +40,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: voslin <web@frugier.net>, 2021\n"
"Last-Translator: patate douce <poubel125@gmail.com>, 2021\n"
"Language-Team: French (https://www.transifex.com/grocy/teams/93189/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -120,7 +122,7 @@ msgid "Stores"
msgstr "Magasins"
msgid "Quantity units"
msgstr "Formats"
msgstr "Unités"
msgid "Chores"
msgstr "Corvées"
@@ -150,7 +152,7 @@ msgid "Best before"
msgstr "DLUO / DLC"
msgid "OK"
msgstr "Ok"
msgstr "OK"
msgid "Product overview"
msgstr "Aperçu du produit"
@@ -364,8 +366,8 @@ msgstr "Voulez-vous vraiment supprimer le magasin \"%s\"?"
msgid "Manage API keys"
msgstr "Gérer les clefs API"
msgid "REST API & data model documentation"
msgstr "Documentation sur l'API REST & le modèle des données"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "Clefs API"
@@ -894,7 +896,7 @@ msgid "Add all list items to stock"
msgstr "Ajouter toute la liste dans le stock"
msgid "Add this item to stock"
msgstr ""
msgstr "Ajouter ce produit au stock"
msgid "Adding shopping list item %1$s of %2$s"
msgstr "Ajout :%1$s de %2$s à la liste de courses"
@@ -2081,6 +2083,9 @@ msgid ""
"automatically populated (by product and/or barcode defaults), the "
"transaction is automatically submitted"
msgstr ""
"Lorsqu'activée, après avoir changé/scanné un produit et si tous les champs "
"ont pu être automatiquement remplis (par les valeurs par défaut du produit "
"ou code barre), la transaction est envoyée automatiquement"
msgid "Quick consume amount"
msgstr "Quantité consommée par défaut"
@@ -2138,11 +2143,9 @@ msgstr "Date de péremption par défaut"
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 ""
"Lorsque ce produit est marqué comme ouvert, la date \"À consommer de "
"préférence avant\" sera remplacée par aujourd'hui + le nombre de jours (une "
"valeur de 0 désactive ceci)"
msgid "Default due days after opened"
msgstr "Date de péremption après ouverture par défaut"
@@ -2372,4 +2375,182 @@ msgid "Print options"
msgstr "Options d'impression"
msgid "A product or a note is required"
msgstr "Un produit ou une note est nécessaire"
msgid "grocycode"
msgstr "grocycode"
msgid "Download"
msgstr "Télécharger"
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr "Imprimer sur une imprimante d'étiquette"
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr "Pas d'étiquette"
msgid "Single label"
msgstr "Etiquette unique"
msgid "Label per unit"
msgstr "Etiquette par unité"
msgid "Allow label printing per unit"
msgstr "Permettre l'impression d'étiquette par unité"
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
"Permettre l'impression d'une étiquette par unité (après conversion). 1 achat"
" qui ajoute 10 produits imprime 10 étiquettes"
msgid "Error while executing WebHook"
msgstr "Erreur lors de l'exécution de WebHook"
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -2,7 +2,7 @@
# Translators:
# Adi Zarko <kapkapon@gmail.com>, 2020
# Netanel Lazarovich <natylaza89@gmail.com>, 2020
# Yaron Shahrabani <sh.yaron@gmail.com>, 2020
# Yaron Shahrabani <sh.yaron@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -10,7 +10,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: Yaron Shahrabani <sh.yaron@gmail.com>, 2020\n"
"Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>, 2021\n"
"Language-Team: Hebrew (Israel) (https://www.transifex.com/grocy/teams/93189/he_IL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -400,3 +400,12 @@ msgstr "טמילית"
msgid "Finnish"
msgstr "פינית"
msgid "Breakfast"
msgstr "ארוחת בוקר"
msgid "Lunch"
msgstr "ארוחת צהריים"
msgid "Dinner"
msgstr "ארוחת ערב"

View File

@@ -343,8 +343,8 @@ msgstr "למחוק את החנות „%s”?"
msgid "Manage API keys"
msgstr "ניהול מפתחות API"
msgid "REST API & data model documentation"
msgstr "תיעוד דגם נתונים ו־REST API"
msgid "REST API browser"
msgstr "דפדפן REST API"
msgid "API keys"
msgstr "מפתחות API"
@@ -2085,10 +2085,11 @@ msgstr "ימים בתוקף עד כבררת מחדל"
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 ""
"כאשר המוצר הזה סומן כפתור, מועד בתוקף עד יוחלף ביום הנוכחי + כמות כזו של "
"ימים (0 משבית את ההתנהגות הזאת)"
"כאשר המוצר הזה מסומן כפתוח, תאריך התפוגה יוחלף ביום הנוכחי + כמות כזאת של "
"ימים, אך רק אם התאריך שמתקבל אינו מאוחר מתאריך התפוגה המקורי (0 ישבית את זה)"
msgid "Default due days after opened"
msgstr "ימים לתום התוקף לאחר הפתיחה כבררת מחדל"
@@ -2313,3 +2314,189 @@ msgstr "אפשרויות הדפסה"
msgid "A product or a note is required"
msgstr "נדרש מוצר או הערה"
msgid "grocycode"
msgstr "קוד grocy"
msgid "Download"
msgstr "הורדה"
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr "הורדת קוד grocy של %s"
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 ""
"קוד grocy הוא ייחוס ייחודי עבור %s בעותק ה־grocy שלך - יש להדפיס אותו על "
"תווית ולסרוק אותו כמו כל ברקוד אחד."
# Abbreviation for "due date"
msgid "DD"
msgstr "פג"
msgid "Print on label printer"
msgstr "הדפסה במדפסת מדבקות"
msgid "Stock entry label"
msgstr "תווית רשומת מלאי"
msgid "No label"
msgstr "ללא תווית"
msgid "Single label"
msgstr "תווית בודדה"
msgid "Label per unit"
msgstr "תווית לכל יחידה"
msgid "Allow label printing per unit"
msgstr "לאפשר הדפסת תוויות ליחידה"
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
"לאפשר הדפסה של תווית אחת ליחידה ברכישה (לאחר המרה) - למשל: אריזה אחת שנרכשה "
"ומוסיפה 10 יחידות למלאי תוביל להדפסת 10 תוויות"
msgid "Error while executing WebHook"
msgstr "שגיאה בהפעלת התליית רשת"
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr "הדפסת קוד grocy של %s במדפסת מדבקות"
msgid "Open stock entry label in new window"
msgstr "פתיחת תווית רשומת מלאי בחלון חדש"
msgid "Thermal printer"
msgstr "מדפסת תרמית"
msgid "Printing"
msgstr "מתבצעת הדפסה"
msgid "Connecting to printer..."
msgstr "מתבצעת התחברות למדפסת…"
msgid "Unable to print"
msgstr "לא ניתן להדפיס"
msgid "Only done items"
msgstr "רק פריטים שהושלמו"
msgid "Show only in-stock products"
msgstr "להציג מוצרים מהמלאי בלבד"
msgid "Product description"
msgstr "תיאור מוצר"
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr "%1$s ל%2$s"
msgid "Mark this item as undone"
msgstr "סימון הביטול הזה כלא בוצע"
msgid "Mandatory"
msgstr "חובה"
msgid "Mandatory Userfield"
msgstr "שדה משתמש חובה"
msgid "When enabled, then this field must be filled on the destination form"
msgstr "כאשר פעיל, חובה למלא את השדה הזה בטופס היעד"
msgid "In-stock products"
msgstr "מוצרים במלאי"
msgid "Timestamp"
msgstr "חותמת זמן"
msgid "Should not be frozen"
msgstr "לא אמור להיות קפוא"
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr "כאשר פעיל, בהעברת המוצר הזה להקפאה, תופיע אזהרה"
msgid "This product shouldn't be frozen"
msgstr "מוצר זה לא אמור להיות קפוא"
msgid "Copy all meal plan entries of %s"
msgstr "העתקת כל רשומות תכנית הארוחות של %s"
msgid "A date is required"
msgstr "נדרש תאריך"
msgid "Day"
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] "חודשיים"
msgstr[2] "%s חודשים"
msgstr[3] "%s חודשים"
msgid "%s year"
msgid_plural "%s years"
msgstr[0] "שנה"
msgstr[1] "שנתיים"
msgstr[2] "%s שנים"
msgstr[3] "%s שנים"
msgid "Display product"
msgstr "הצגת מוצר"
msgid "Copy recipe"
msgstr "העתקת מתכון"
msgid "Copy of %s"
msgstr "עותק של %s"
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 "רשומה במלאי"
msgid "Configure sections"
msgstr "הגדרת סעיפים"
msgid "Meal plan sections"
msgstr "סעיפי תכנית ארוחות"
msgid "Create meal plan section"
msgstr "יצירת סעיף תכנית ארוחות"
msgid "Sections will be ordered by that number on the meal plan"
msgstr "הסעיפים יסודרו לפי המספר על תכנית הארוחות"
msgid "Edit meal plan section"
msgstr "עריכת סעיף תכנית ארוחות"
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr "למחוק את הסעיף „%s” מתכנית הארוחות?"
msgid "Section"
msgstr "סעיף"

View File

@@ -386,3 +386,12 @@ msgstr "Tamil"
msgid "Finnish"
msgstr "Finn"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -385,3 +385,12 @@ msgstr "Tamil"
msgid "Finnish"
msgstr "Finlandese"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -4,6 +4,7 @@
# Bernd Bestel <bernd@berrnd.de>, 2019
# Matteo Piotto <matteo.piotto@welaika.com>, 2019
# 42d76af3cd20bc399c7e8a413695959f, 2019
# Matteo Piccina <altermatte@gmail.com>, 2021
# Antonino Ursino <ninus_@libero.it>, 2021
#
msgid ""
@@ -71,10 +72,10 @@ msgid "Batteries overview"
msgstr "Riepilogo delle batterie"
msgid "Purchase"
msgstr "Acquisti"
msgstr "Acquista"
msgid "Consume"
msgstr "Consumi"
msgstr "Consuma"
msgid "Inventory"
msgstr "Inventario"
@@ -338,8 +339,8 @@ msgstr "Sei sicuro di voler eliminare il negozio \"%s\"?"
msgid "Manage API keys"
msgstr "Gestisci le chiavi API"
msgid "REST API & data model documentation"
msgstr "REST API & Documentazione del modello di dati"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "Chiavi API"
@@ -733,7 +734,7 @@ msgid "Status"
msgstr "Stato"
msgid "Below min. stock amount"
msgstr "Sotto min. quantità in dispensa"
msgstr "Sotto quantità min. in dispensa"
msgid "Expiring soon"
msgstr "In scadenza a breve"
@@ -742,7 +743,7 @@ msgid "Already expired"
msgstr "Già scaduto"
msgid "Due soon"
msgstr "Da fare subito"
msgstr "In scadenza"
msgid "Overdue"
msgstr "In ritardo"
@@ -1615,7 +1616,7 @@ msgstr ""
"essere programmata solo ogni %s anni"
msgid "Transfer"
msgstr "Trasferire"
msgstr "Trasferisci"
msgid "From location"
msgstr "Dalla posizione"
@@ -2077,6 +2078,9 @@ msgid ""
"automatically populated (by product and/or barcode defaults), the "
"transaction is automatically submitted"
msgstr ""
"Quando abilitato, dopo aver modificato/scansionato un prodotto e se tutti i "
"campi possono essere riempiti automaticamente (per prodotto e/o per codice a"
" barre predefiniti), la transazione è inviata automaticamente"
msgid "Quick consume amount"
msgstr "Quantità di consumo rapida"
@@ -2137,11 +2141,9 @@ msgstr "Giorni di scadenza predefiniti"
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 ""
"Quando questo prodotto è stato contrassegnato come aperto, la data di "
"scadenza verrà sostituita da oggi + questo numero di giorni (un valore di 0 "
"lo disabilita)"
msgid "Default due days after opened"
msgstr "Giorni di scadenza predefiniti dopo l'apertura"
@@ -2376,3 +2378,182 @@ msgstr "Opzioni di stampa"
msgid "A product or a note is required"
msgstr "È richiesto un prodotto o una nota"
msgid "grocycode"
msgstr "codicegrocy"
msgid "Download"
msgstr "Scarica"
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr "Scad."
msgid "Print on label printer"
msgstr "Stampa su stampante per etichette"
msgid "Stock entry label"
msgstr "Etichetta della voce di dispensa"
msgid "No label"
msgstr "Senza etichetta"
msgid "Single label"
msgstr "Etichetta singola"
msgid "Label per unit"
msgstr "Etichetta per unità"
msgid "Allow label printing per unit"
msgstr "Consenti la stampa di etichette per unità"
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
"Consenti la stampa di un'etichetta per unità al momento dell'acquisto (dopo "
"la conversione) - ad es. 1 confezione acquistata aggiungendo 10 pezzi in "
"dispensa stamperebbe 10 etichette"
msgid "Error while executing WebHook"
msgstr "Errore durante l'esecuzione di WebHook"
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr "Stampante termica"
msgid "Printing"
msgstr "In stampa"
msgid "Connecting to printer..."
msgstr "Connessione alla stampante..."
msgid "Unable to print"
msgstr "Impossibile stampare"
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -376,3 +376,12 @@ msgstr ""
msgid "Finnish"
msgstr ""
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -2184,3 +2184,71 @@ msgstr ""
msgid "A product or a note is required"
msgstr ""
msgid "grocycode"
msgstr ""
msgid "Download"
msgstr ""
msgid "Download stock entry 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"
msgstr ""
# Abbreviation for "due date"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr ""
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr ""
msgid "Single label"
msgstr ""
msgid "Label per unit"
msgstr ""
msgid "Allow label printing per unit"
msgstr ""
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
msgid "Error while executing WebHook"
msgstr ""
msgid "Print product grocycode on label printer"
msgstr ""
msgid "Print stock entry grocycode on label printer"
msgstr ""
msgid "Open stock entry print label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""

View File

@@ -374,3 +374,12 @@ msgstr ""
msgid "Finnish"
msgstr ""
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -1,7 +1,7 @@
#
# Translators:
# CW kim <cw2002.kim@gmail.com>, 2020
# ks <idaksha@outlook.com>, 2020
# 신강수 <idaksha@outlook.com>, 2020
# CW kim <cw2002.kim@gmail.com>, 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: 2019-05-01 17:42+0000\n"
"Last-Translator: ks <idaksha@outlook.com>, 2020\n"
"Last-Translator: CW kim <cw2002.kim@gmail.com>, 2021\n"
"Language-Team: Korean (Korea) (https://www.transifex.com/grocy/teams/93189/ko_KR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -325,8 +325,8 @@ msgstr "\"%s\" 저장소를 삭제 하시겠습니까? "
msgid "Manage API keys"
msgstr "API키 관리"
msgid "REST API & data model documentation"
msgstr "REST API 및 데이터 모델 문서"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "API 키"
@@ -1889,7 +1889,7 @@ msgid "Stock entries for this product"
msgstr ""
msgid "Edit shopping list"
msgstr ""
msgstr "구매 목록 수정"
msgid "Save & continue to add quantity unit conversions & barcodes"
msgstr ""
@@ -2021,7 +2021,8 @@ msgstr ""
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 ""
msgid "Default due days after opened"
@@ -2219,4 +2220,178 @@ msgid "Print options"
msgstr ""
msgid "A product or a note is required"
msgstr "제품 또는 메모가 필요합니다."
msgid "grocycode"
msgstr ""
msgid "Download"
msgstr ""
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr ""
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr ""
msgid "Single label"
msgstr ""
msgid "Label per unit"
msgstr ""
msgid "Allow label printing per unit"
msgstr ""
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
msgid "Error while executing WebHook"
msgstr ""
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
msgstr ""
msgid "Add recipe"
msgstr ""
msgid "Copy this day"
msgstr ""
msgid "Date range"
msgstr ""
msgid "%s month"
msgid_plural "%s months"
msgstr[0] ""
msgid "%s year"
msgid_plural "%s years"
msgstr[0] ""
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -393,3 +393,12 @@ msgstr "Tamil"
msgid "Finnish"
msgstr "Fins"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

View File

@@ -1,7 +1,7 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Boding Clockchain <joost_nl@live.nl>, 2020
# BodingClockchian <joost_nl@live.nl>, 2020
#
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: 2019-05-01 17:42+0000\n"
"Last-Translator: Boding Clockchain <joost_nl@live.nl>, 2020\n"
"Last-Translator: BodingClockchian <joost_nl@live.nl>, 2020\n"
"Language-Team: Dutch (https://www.transifex.com/grocy/teams/93189/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
#
# Translators:
# Marius Borø <blizzwave@gmail.com>, 2019
# Marius Borø <blizzwave@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -8,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: Marius Borø <blizzwave@gmail.com>, 2019\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -18,13 +18,13 @@ msgstr ""
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr "Ingen tildeling"
msgstr "no-assignment"
msgid "who-least-did-first"
msgstr " Hvem gjorde minst først"
msgstr "who-least-did-first"
msgid "random"
msgstr "Tilfeldig"
msgstr "random"
msgid "in-alphabetical-order"
msgstr "I alfabetisk rekkefølge"
msgstr "in-alphabetical-order"

View File

@@ -1,7 +1,6 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Marius Borø <blizzwave@gmail.com>, 2019
# Tor Eirik Trandal <teitrand@hotmail.com>, 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: Marius Borø <blizzwave@gmail.com>, 2019\n"
"Last-Translator: Tor Eirik Trandal <teitrand@hotmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -19,19 +18,19 @@ msgstr ""
"X-Domain: grocy/chore_types\n"
msgid "manually"
msgstr "Manuell"
msgstr "manuelt"
msgid "dynamic-regular"
msgstr "Automatisk"
msgstr "dynamisk-vanlig"
msgid "daily"
msgstr "Daglig"
msgstr "daglig"
msgid "weekly"
msgstr "Ukentlig"
msgstr "ukentlig"
msgid "monthly"
msgstr "Månedlig"
msgstr "månedlig"
msgid "yearly"
msgstr "årlig"

View File

@@ -1,6 +1,7 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Marius Borø <blizzwave@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -8,7 +9,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: Bernd Bestel <bernd@berrnd.de>, 2019\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -27,22 +28,7 @@ msgid "moment_locale"
msgstr "nb"
msgid "datatables_localization"
msgstr ""
"{\"sEmptyTable\":\"Det finnes ingen data i tabellen\",\"sInfo\":\"_START_ "
"fra _END_ til _TOTAL_ skriv\",\"sInfoEmpty\":\"Ingen data "
"tilgjengelign\",\"sInfoFiltered\":\"(filtrert fra _MAX_ "
"skriv)\",\"sInfoPostFix\":\"\",\"sInfoThousands\":\".\",\"sLengthMenu\":\"_MENU_"
" per side\",\"sLoadingRecords\":\"Laster ..\",\"sProcessing\":\"Vennligst "
"vent ..\",\"sSearch\":\"Søk\",\"sZeroRecords\":\"Ingen oppføringer "
"tilgjengelig\",\"oPaginate\":{\"sFirst\":\"Første\",\"sPrevious\":\"Bakover\",\"sNext\":\"Neste\",\"sLast\":\"Siste\"},\"oAria\":{\"sSortAscending\":\":"
" Sortér stigende\",\"sSortDescending\":\": Sortér "
"synkende\"},\"select\":{\"rows\":{\"0\":\"klikk på en linje for å "
"velge\",\"1\":\"1 linje valgt\",\"_\":\" linger "
"valgt\"}},\"buttons\":{\"print\":\"Print\",\"colvis\":\"Søyle\",\"copy\":\"Kopi\",\"copyTitle\":\"Kopier"
" til utklippstavlen\",\"copyKeys\":\"Trykk <i>ctrl</i> eller <i>⌘</i> + "
"<i>C</i> for å kopiere tabell<br> til utklipptavlen.<br><br>For å avbryte, "
"klikke på meldingen eller trykk på ESC.\",\"copySuccess\":{\"1\":\"1 Kolonne"
" kopiert\",\"_\":\" kolonne kopiert\"}}}"
msgstr "datatables_localization"
msgid "summernote_locale"
msgstr "nb-NO"

View File

@@ -1,8 +1,8 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Marius Borø <blizzwave@gmail.com>, 2020
# Ruben Sperre <ruben@rsperre.net>, 2020
# Marius Borø <blizzwave@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -10,7 +10,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: Ruben Sperre <ruben@rsperre.net>, 2020\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -330,7 +330,7 @@ msgid "current release"
msgstr "Nåværende versjon"
msgid "not yet released"
msgstr "enda ikke gitt ut"
msgstr "ikke gitt ut enda"
msgid "Portuguese (Brazil)"
msgstr "Portugisisk (Brasil)"
@@ -355,11 +355,11 @@ msgstr "Portugisisk (Portugal)"
# Use a in your country well known supermarket name
msgid "DemoSupermarket1"
msgstr "DemoButikk1"
msgstr "Rema 1000"
# Use a in your country well known supermarket name
msgid "DemoSupermarket2"
msgstr "DemoButikk2"
msgstr "Kiwi"
msgid "Japanese"
msgstr "Japansk"
@@ -368,19 +368,28 @@ msgid "Chinese (Taiwan)"
msgstr "Kinesisk (Taiwan)"
msgid "Greek"
msgstr ""
msgstr "Gresk"
msgid "Korean"
msgstr ""
msgstr "Koreansk"
msgid "Chinese (China)"
msgstr ""
msgstr "Kinesisk (China)"
msgid "Hebrew (Israel)"
msgstr ""
msgstr "Hebraisk (Israel)"
msgid "Tamil"
msgstr ""
msgstr "Tamilsk"
msgid "Finnish"
msgstr "Finsk"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

123
localization/no/locales.po Normal file
View File

@@ -0,0 +1,123 @@
#
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2020
# Marius Borø <blizzwave@gmail.com>, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2020-08-31 19:11+0000\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: no\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/locales\n"
# Czech
msgid "cs"
msgstr "cs"
# Danish
msgid "da"
msgstr "da"
# German
msgid "de"
msgstr "de"
# Greek
msgid "el_GR"
msgstr "el_GR"
# English
msgid "en"
msgstr "no"
# English (Great Britain)
msgid "en_GB"
msgstr "en_GB"
# Spanish
msgid "es"
msgstr "es"
# French
msgid "fr"
msgstr "fr"
# Hungarian
msgid "hu"
msgstr "hu"
# Italian
msgid "it"
msgstr "it"
# Japanese
msgid "ja"
msgstr "ja"
# Korean
msgid "ko_KR"
msgstr "ko_KR"
# Dutch
msgid "nl"
msgstr "nl"
# Norwegian
msgid "no"
msgstr "no"
# Polish
msgid "pl"
msgstr "pl"
# Portuguese (Brazil)
msgid "pt_BR"
msgstr "pt_BR"
# Portuguese (Portugal)
msgid "pt_PT"
msgstr "pt_PT"
# Russian
msgid "ru"
msgstr "ru"
# Slovak
msgid "sk_SK"
msgstr "sk_SK"
# Swedish
msgid "sv_SE"
msgstr "sv_SE"
# Turkish
msgid "tr"
msgstr "tr"
# Chinese (Taiwan)
msgid "zh_TW"
msgstr "zh_TW"
# Chinese (China)
msgid "zh_CN"
msgstr "zh_CN"
# Hebrew (Israel)
msgid "he_IL"
msgstr "he_IL"
# Tamil
msgid "ta"
msgstr "ta"
# Finnish
msgid "fi"
msgstr "fi"

View File

@@ -0,0 +1,139 @@
#
# Translators:
# Tor Eirik Trandal <teitrand@hotmail.com>, 2020
# Marius Borø <blizzwave@gmail.com>, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"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: Marius Borø <blizzwave@gmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: no\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/permissions\n"
# All permissions
msgid "ADMIN"
msgstr "ADMIN"
# Create users
msgid "USERS_CREATE"
msgstr "USERS_CREATE"
# Edit users (including passwords)
msgid "USERS_EDIT"
msgstr "USERS_EDIT"
# Show users
msgid "USERS_READ"
msgstr "USERS_READ"
# Edit own user data / change own password
msgid "USERS_EDIT_SELF"
msgstr "USERS_EDIT_SELF"
# Undo charge cycle
msgid "BATTERIES_UNDO_CHARGE_CYCLE"
msgstr "BATTERIES_UNDO_CHARGE_CYCLE"
# Track charge cycle
msgid "BATTERIES_TRACK_CHARGE_CYCLE"
msgstr "BATTERIES_TRACK_CHARGE_CYCLE"
# Track execution
msgid "CHORE_TRACK_EXECUTION"
msgstr "CHORE_TRACK_EXECUTION"
# Undo execution
msgid "CHORE_UNDO_EXECUTION"
msgstr "CHORE_TRACK_EXECUTION"
# Edit master data
msgid "MASTER_DATA_EDIT"
msgstr "MASTER_DATA_EDIT"
# Undo execution
msgid "TASKS_UNDO_EXECUTION"
msgstr "TASKS_UNDO_EXECUTION"
# Mark completed
msgid "TASKS_MARK_COMPLETED"
msgstr "TASKS_MARK_COMPLETED"
# Edit stock entries
msgid "STOCK_EDIT"
msgstr "STOCK_EDIT"
# Transfer
msgid "STOCK_TRANSFER"
msgstr "STOCK_TRANSFER"
# Inventory
msgid "STOCK_INVENTORY"
msgstr "STOCK_INVENTORY"
# Consume
msgid "STOCK_CONSUME"
msgstr "STOCK_CONSUME"
# Open products
msgid "STOCK_OPEN"
msgstr "STOCK_OPEN"
# Purchase
msgid "STOCK_PURCHASE"
msgstr "STOCK_PURCHASE"
# Add items
msgid "SHOPPINGLIST_ITEMS_ADD"
msgstr "SHOPPINGLIST_ITEMS_ADD"
# Remove items
msgid "SHOPPINGLIST_ITEMS_DELETE"
msgstr "SHOPPINGLIST_ITEMS_DELETE"
# User management
msgid "USERS"
msgstr "USERS"
# Stock
msgid "STOCK"
msgstr "STOCK"
# Shopping list
msgid "SHOPPINGLIST"
msgstr "SHOPPINGLIST"
# Chores
msgid "CHORES"
msgstr "CHORES"
# Batteries
msgid "BATTERIES"
msgstr "BATTERIES"
# Tasks
msgid "TASKS"
msgstr "TASKS"
# Recipes
msgid "RECIPES"
msgstr "RECIPES"
# Equipment
msgid "EQUIPMENT"
msgstr "EQUIPMENT"
# Calendar
msgid "CALENDAR"
msgstr "CALENDAR"
# Meal plan
msgid "RECIPES_MEALPLAN"
msgstr "RECIPES_MEALPLAN"

View File

@@ -1,6 +1,6 @@
#
# Translators:
# Marius Borø <blizzwave@gmail.com>, 2020
# Marius Borø <blizzwave@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -8,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: Marius Borø <blizzwave@gmail.com>, 2020\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -18,28 +18,28 @@ msgstr ""
"X-Domain: grocy/stock_transaction_types\n"
msgid "purchase"
msgstr "innkjøp"
msgstr "Innkjøp"
msgid "transfer_from"
msgstr "overføre_fra"
msgstr "Overført fra"
msgid "transfer_to"
msgstr "overføre_til"
msgstr "Overført til"
msgid "consume"
msgstr "forbruk"
msgstr "Forbruk"
msgid "inventory-correction"
msgstr "beholdningsantall_korreksjon"
msgstr "Beholdningsantall korreksjon"
msgid "product-opened"
msgstr "produkt_åpnet"
msgstr "Produkt åpnet"
msgid "stock-edit-old"
msgstr "beholdning_endre_gammel"
msgstr "Beholdning gammel endring"
msgid "stock-edit-new"
msgstr "beholdning_endre_ny"
msgstr "Beholdning ny endring"
msgid "self-production"
msgstr "egenproduksjon"
msgstr "Egenproduksjon"

View File

@@ -2,9 +2,9 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Andreas Henden <chairman2s.ah@gmail.com>, 2019
# Marius Borø <blizzwave@gmail.com>, 2020
# Ruben Sperre <ruben@rsperre.net>, 2020
# Tor Eirik Trandal <teitrand@hotmail.com>, 2020
# Marius Borø <blizzwave@gmail.com>, 2021
# Tor Eirik Trandal <teitrand@hotmail.com>, 2021
#
msgid ""
msgstr ""
@@ -12,7 +12,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: Tor Eirik Trandal <teitrand@hotmail.com>, 2020\n"
"Last-Translator: Tor Eirik Trandal <teitrand@hotmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -41,13 +41,13 @@ msgstr[1] "%s produkter har gått ut på dato"
msgid "%s product is overdue"
msgid_plural "%s products are overdue"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produkt er forbi best før dato"
msgstr[1] "%s produkter er forbi best før dato"
msgid "%s product is below defined min. stock amount"
msgid_plural "%s products are below defined min. stock amount"
msgstr[0] "%s produkt er under satt minimum for beholdningen"
msgstr[1] "%s produkter er under satt minimum for beholdningen"
msgstr[0] "%s produkt er under satt min. for beholdningen"
msgstr[1] "%s produkter er under satt min. for beholdningen"
msgid "Product"
msgstr "Produkt"
@@ -209,7 +209,7 @@ msgid "Default best before days"
msgstr "Forhåndsatt antall dager best før"
msgid "Default quantity unit purchase"
msgstr ""
msgstr "Standard forpakning kjøpt"
msgid "Quantity unit stock"
msgstr "Forpakning beholdning"
@@ -335,8 +335,8 @@ msgstr "Er du sikker på at du vil slette butikken"
msgid "Manage API keys"
msgstr "Administrer API-Keys"
msgid "REST API & data model documentation"
msgstr "REST-API & datamodell-dokumentasjon"
msgid "REST API browser"
msgstr ""
msgid "API keys"
msgstr "API-Keys"
@@ -615,12 +615,14 @@ msgid "Quantity unit"
msgstr "Forpakning"
msgid "Only check if any amount is in stock"
msgstr ""
msgstr "Kun sjekk om noe er i beholdningen"
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 ""
"Er du sikker på at du vil konsumere alle ingredienser for oppskrift \"%s\"?\n"
"(ingredienser merket med \"kun sjekk on noe er i beholdningen\" vil bli ignorert)"
msgid "Removed all ingredients of recipe \"%s\" from stock"
msgstr "Fjern alle ingredienser for \"%s\" oppskriften fra beholdningen."
@@ -650,7 +652,7 @@ msgid "Due"
msgstr "Forfall"
msgid "Assigned to"
msgstr " Tildelt til"
msgstr " Tildelt"
msgid "Mark task \"%s\" as completed"
msgstr "Merk oppgave \"%s\" som ferdig"
@@ -674,7 +676,7 @@ msgid "Edit task"
msgstr "Endre oppgave"
msgid "Are you sure to delete task \"%s\"?"
msgstr "Er du sikker du ønsker slette oppgave \"%s\"?"
msgstr "Er du sikker du ønsker å slette oppgave \"%s\"?"
msgid "%s task is due to be done"
msgid_plural "%s tasks are due to be done"
@@ -776,7 +778,7 @@ msgid "Image of product %s"
msgstr "Bilde av produkt %s"
msgid "Deletion not possible"
msgstr ""
msgstr "Ikke mulig å slette"
msgid "Equipment"
msgstr "Instruksjonsmanualer"
@@ -797,7 +799,7 @@ msgid "Create equipment"
msgstr "Opprett instruksjonmanualer for et utstyr"
msgid "The current file will be deleted on save"
msgstr ""
msgstr "Nåværende fil vil bli slettet når du lagrer"
msgid "No picture available"
msgstr "Ingen bilde tilgjengelig"
@@ -860,7 +862,7 @@ msgid "Add all list items to stock"
msgstr "Legg alle produktene i listen til beholdningen"
msgid "Add this item to stock"
msgstr ""
msgstr "Legg til dette produktet til i beholdningen"
msgid "Adding shopping list item %1$s of %2$s"
msgstr "Legger til handleliste ting %1$s av %2$s"
@@ -872,6 +874,8 @@ 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 ""
"Det første produktet i denne listen vil bli valgt i henhold til regel "
"\"Åpnet først, kommende utgangsdato først, først inn først ut\" "
msgid "Mark %1$s of %2$s as open"
msgstr "Merk %1$s av %2$s som åpen"
@@ -892,7 +896,7 @@ msgid "%s opened"
msgstr "%s åpnet"
msgid "Product due"
msgstr ""
msgstr "Produkt forfalt"
msgid "Task due"
msgstr "Tidsfrist for oppgave"
@@ -1006,7 +1010,7 @@ msgid "Gallery"
msgstr "Bildegalleri"
msgid "The current picture will be deleted on save"
msgstr ""
msgstr "Nåværende produktbilde vil bli slettet når du lagrer"
msgid "Journal for this battery"
msgstr "Logg for dette batteriet"
@@ -1031,6 +1035,8 @@ msgstr "Produkttelling"
msgid ""
"Type a new product name or barcode and hit TAB or ENTER to start a workflow"
msgstr ""
"Skriv inn nytt produkt navn eller strekkode, trykk så TAB eller ENTER for å "
"starte prosessen"
msgid ""
"This will be used as the default setting when adding this product as a "
@@ -1228,7 +1234,7 @@ msgid "Booking has subsequent dependent bookings, undo not possible"
msgstr "Registrering har flere registreringer under seg, angre er ikke mulig"
msgid "per serving"
msgstr "pr. servering"
msgstr "pr. porsjon"
msgid "Never"
msgstr "Aldri"
@@ -1245,7 +1251,7 @@ msgstr ""
"fjernet!"
msgid "Undo task"
msgstr ""
msgstr "Angre oppgave"
msgid "Due date rollover"
msgstr "Forfallsdato rollover"
@@ -1269,13 +1275,13 @@ msgstr "Alle lokasjoner"
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 ""
msgstr "Her kan du printe ut beholdningsoversikt basert på plassering"
msgid "this location"
msgstr "kun denne lokasjonen"
msgid "Consumed amount"
msgstr ""
msgstr "Forbrukt mengde"
msgid "Time of printing"
msgstr "Tidspunkt for utskrift"
@@ -1317,7 +1323,7 @@ msgid "This cannot be equal to %s"
msgstr "Dette kan ikke være samme som %s"
msgid "This means 1 %1$s is the same as %2$s %3$s"
msgstr "Dette betyr at 1 %1$s er det samme som %2$s%3$s"
msgstr "Dette betyr at 1 %1$s er det samme som %2$s %3$s"
msgid "QU conversions"
msgstr "Forpakningskonvertering"
@@ -1329,7 +1335,7 @@ msgid "Override for product"
msgstr "Overstyr for product"
msgid "This equals %1$s %2$s"
msgstr ""
msgstr "Dette er lik %1$s%2$s"
msgid "Edit QU conversion"
msgstr "Endre forpakningskonvertering"
@@ -1521,9 +1527,9 @@ msgid ""
"this product, means the sub product will never be \"missing\", only this "
"product"
msgstr ""
"Hvis aktivert vil minimum beholdningsnivå av under produkter bli akkumulert "
"inn i dette produktet. Dette betyr at underordnede produktet aldri vil "
"\"mangle\", kunne dette produktet."
"Hvis aktivert vil minimum beholdningsnivå av underordnede produkter bli "
"akkumulert inn i dette produktet. Dette betyr at det underordnede produktet "
"aldri vil \"mangle\", kunne dette produktet."
msgid "Are you sure to remove this conversion?"
msgstr "Er du sikker på at du ønsker å fjerne denne konverteringen?"
@@ -1597,7 +1603,7 @@ msgid "There are no units available at this location"
msgstr "Det er ingen enheter tilgjengelig på denne lokasjonen"
msgid "Amount: %1$s; Due on %2$s; Bought on %3$s"
msgstr ""
msgstr "Mengde: %1$s; Best før %2$s; Kjøpt %3$s"
msgid "Transfered %1$s of %2$s from %3$s to %4$s"
msgstr "Flyttet %1$s %2$s fra %3$s til %4$s"
@@ -1643,6 +1649,8 @@ msgid ""
"When a product is selected, one unit (per serving in stock quantity unit) "
"will be added to stock on consuming this recipe"
msgstr ""
"Når et produkt er valgt vil en enhet (per porsjon i beholdningsantall) bli "
"lagt til beholdningen når denne oppskriften bli konsumert"
msgid "Produces product"
msgstr "Lager produkt"
@@ -1790,7 +1798,7 @@ msgid "means %1$s per %2$s"
msgstr "betyr %1$s per %2$s"
msgid "Create inverse QU conversion"
msgstr "Lager invertert QU konvertering"
msgstr "Opprett invertert forpaknings konvertering"
msgid "Create recipe"
msgstr "Lag oppskrift"
@@ -1806,7 +1814,7 @@ msgid "Save & return to recipes"
msgstr "Lagre & returner til oppskrifter"
msgid "Stock value"
msgstr ""
msgstr "Beholdningsverdi"
msgid "Average price"
msgstr "Gjennomsnittlig pris"
@@ -1831,19 +1839,24 @@ msgstr "Endre strekkode"
msgid "Not enough in stock (not included in costs), %s ingredient missing"
msgstr ""
"Ikke nok igjen i beholdningen (ikke inkludert kost), %s ingrediens mangler"
msgid ""
"Based on the prices of the default consume rule which is \"Opened first, "
"then first due first, then first in first out\""
msgstr ""
"Basert på prisene til standardregel \"Åpnet først, kommende utgangsdato "
"først, først inn først ut\""
msgid ""
"Not enough in stock (not included in costs), %1$s missing, %2$s already on "
"shopping list"
msgstr ""
"Ikke nok igjen i beholdningen (ikke inkludert kost), %1$s mangler, %2$s "
"allerede på handlelisten"
msgid "Quantity unit stock cannot be changed after first purchase"
msgstr ""
msgstr "Forpakning i beholdning kan ikke endres etter første kjøp"
msgid "Clear filter"
msgstr "Tøm filter"
@@ -1852,7 +1865,7 @@ msgid "Permissions for user %s"
msgstr "Tillatelser for bruker %s"
msgid "Are you sure you want to remove full permissions for yourself?"
msgstr ""
msgstr "Er du sikker på at du ønsker å fjerne alle rettigheter for deg selv?"
msgid "Permissions saved"
msgstr "Tillatelser lagret"
@@ -1861,413 +1874,632 @@ msgid "You are not allowed to view this page"
msgstr "Du har ikke tillatelse til å vise denne siden"
msgid "Page not found"
msgstr ""
msgstr "Page not found"
msgid "Unauthorized"
msgstr ""
msgstr "Unauthorized"
msgid "Error source"
msgstr ""
msgstr "Error source"
msgid "Error message"
msgstr ""
msgstr "Error message"
msgid "Stack trace"
msgstr ""
msgstr "Stack trace"
msgid "Easy error info copy & paste (for reporting)"
msgstr ""
msgstr "Easy error info copy & paste (for reporting)"
msgid "This page does not exist"
msgstr ""
msgstr "This page does not exist"
msgid "You will be redirected to the default page in %s seconds"
msgstr ""
msgstr "You will be redirected to the default page in %s seconds"
msgid "Server error"
msgstr ""
msgstr "Server error"
msgid "A server error occured while processing your request"
msgstr ""
msgstr "A server error occured while processing your request"
msgid "If you think this is a bug, please report it"
msgstr ""
msgstr "If you think this is a bug, please report it"
msgid "Language"
msgstr ""
msgstr "Språk"
msgid "User settings"
msgstr ""
msgstr "Brukerinnstillinger"
msgid "Default"
msgstr ""
msgstr "Standard"
msgid "Stock journal summary"
msgstr ""
msgstr "Beholdningslogg sammendrag"
msgid "Journal summary"
msgstr ""
msgstr "Logg sammendrag"
msgid "Journal summary for this product"
msgstr ""
msgstr "Logg sammendrag for dette produktet"
msgid "Consume exact amount"
msgstr ""
msgstr "Konsumer definert mengde"
msgid "Value"
msgstr ""
msgstr "Verdi"
msgid "%s total value"
msgstr ""
msgstr "%s total beholdningsverdi"
msgid ""
"Show purchased date on purchase and inventory page (otherwise the purchased "
"date defaults to today)"
msgstr ""
"Vis innkjøpsdato på innkjøp og beholdningssidene (ellers vil innkjøpsdato "
"settes til i dag)"
msgid "Common"
msgstr ""
msgstr "Felles"
msgid "Decimal places allowed for amounts"
msgstr ""
msgstr "Desimaler kan brukes på antall"
msgid "Decimal places allowed for prices"
msgstr ""
msgstr "Desimaler kan brukes på priser"
msgid "Stock entries for this product"
msgstr ""
msgstr "Beholdningsoppføringer for dette produktet"
msgid "Edit shopping list"
msgstr ""
msgstr "Endre handleliste"
msgid "Save & continue to add quantity unit conversions & barcodes"
msgstr ""
"Lagre og fortsett for å legg til forpaknings konvertering og strekkoder"
msgid "Save & return to products"
msgstr ""
msgstr "Lagre og gå tilbake til produkter"
msgid "Save & continue to add conversions"
msgstr ""
msgstr "Lagre og fortsett å legge til konverteringer"
msgid "Save & return to quantity units"
msgstr ""
msgstr "Lagre og gå tilbake til forpakningstyper"
msgid "price"
msgstr ""
msgstr "pris"
msgid "New stock amount"
msgstr ""
msgstr "Ny beholdingsmengde"
msgid "Price per stock unit"
msgstr ""
msgstr "Pris per beholdningstype"
msgid "Table options"
msgstr ""
msgstr "Tabellalternativer"
msgid "This product is currently on a shopping list"
msgstr ""
msgstr "Dette produktet er allerede på en handleliste"
msgid "Undo transaction"
msgstr ""
msgstr "Angre transaksjonen"
msgid "Transaction type"
msgstr ""
msgstr "Transaksjonstype"
msgid "Transaction time"
msgstr ""
msgstr "Transaksjonstid"
msgid "Chore journal"
msgstr ""
msgstr "Statistikk husarbeid"
msgid "Track chore execution"
msgstr ""
msgstr "Registrerte husarbeidsoppgave"
msgid "Mark task as completed"
msgstr ""
msgstr "Merk oppgave som ferdig"
msgid "Track charge cycle"
msgstr ""
msgstr "Registrer lasesyklus"
msgid "Battery journal"
msgstr ""
msgstr "Batterilogg"
msgid "This product has a picture"
msgstr ""
msgstr "Dette produktet har et bilde"
msgid "Consume this stock entry as spoiled"
msgstr ""
msgstr "Konsumer denne som dårlig / råtten"
msgid "Configure user permissions"
msgstr ""
msgstr "Konfigurer brukerrettigheter"
msgid "Show a QR-Code for this API key"
msgstr ""
msgstr "Vis en QR-Kode for denne API nøkkelen"
msgid ""
"This is the default quantity unit used when adding this product to the "
"shopping list"
msgstr ""
"Dette er standard forpakningstørrelse som brukes når du legger dette "
"produktet til handlelisten"
msgid ""
"Show a warning when the due date of the purchased product is earlier than "
"the next due date in stock"
msgstr ""
"Vis en advarsel når forfallsdatoen for det kjøpte produktet er tidligere enn"
" neste forfallsdato i beholdningen"
msgid "This is due earlier than already in-stock items"
msgstr ""
msgstr "Denne forfaller tidligere enn produkter allerede i beholdningen"
msgid ""
"When enabled, after changing/scanning a product and if all fields could be "
"automatically populated (by product and/or barcode defaults), the "
"transaction is automatically submitted"
msgstr ""
"Når aktivert, ved endring / skanning av et produkt, og hvis alle felt "
"automatisk kan fylles ut (etter standardinnstillinger for produkt og / eller"
" strekkode), sendes transaksjonen automatisk"
msgid "Quick consume amount"
msgstr ""
msgstr "Hurtigkonsumer"
msgid ""
"This amount is used for the \"quick consume/open buttons\" on the stock "
"overview page (related to quantity unit stock)"
msgstr ""
"Dette beløpet brukes til \"hurtigkonsumere / åpne-knappene\" på "
"beholdningsoversikten (relatert til forpakninger lager)"
msgid "Copy"
msgstr ""
msgstr "Kopier"
msgid "Are you sure to remove this barcode?"
msgstr ""
msgstr "Er du sikker på at du vil fjerne denne strekkoden?"
msgid "Due date type"
msgstr ""
msgstr "Forfallsdatotype"
msgid ""
"Based on the selected type, the highlighting on the stock overview page will"
" be different"
msgstr ""
"Basert på valgt type vil uthevingen på beholdningsoversikten være annerledes"
msgid ""
"Means that the product is maybe still safe to be consumed after its due date"
" is reached"
msgstr ""
"Betyr at produktet fremdeles er trygt å konsumere etter at forfallsdato"
msgid "Expiration date"
msgstr ""
msgstr "Utløpsdato"
msgid ""
"Means that the product is not safe to be consumed after its due date is "
"reached"
msgstr ""
msgstr "Betyr at produktet ikke er trygt å konsumere etter at utløpsdato"
msgid ""
"For purchases this amount of days will be added to today for the due date "
"suggestion"
msgstr ""
"For kjøp blir dette antall dager lagt til i dag for forslaget på "
"forfallsdato"
msgid "-1 means that this product will be never overdue"
msgstr ""
msgstr "-1 betyr at dette produktet aldri forfaller"
msgid "Default due days"
msgstr ""
msgstr "Standard forfallsdager"
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 ""
msgid "Default due days after opened"
msgstr ""
msgstr "Standard forfallsdager etter åpning"
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 ""
"Når du flytter dette produktet til en fryser (når du fryser det), vil "
"forfallsdatoen erstattes av i dag + dette antall dager"
msgid "Default due days after freezing"
msgstr ""
msgstr "Standard forfallsdager etter frysing"
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 ""
"Når du flytter dette produktet fra en fryser (når du tiner det), vil "
"forfallsdatoen erstattes med i dag + dette antall dager"
msgid "Default due days after thawing"
msgstr ""
msgstr "Standard forfallsdager etter tining"
msgid "Next due date"
msgstr ""
msgstr "Neste forfallsdato"
msgid "%s product is due"
msgid_plural "%s products are due"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produkt forfaller"
msgstr[1] "%s produkter forfaller"
msgid "Due date"
msgstr ""
msgstr "Forfallsdato"
msgid "Never overdue"
msgstr ""
msgstr "Forfaller aldri"
msgid "%s product is expired"
msgid_plural "%s products are expired"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produkt har forfalt"
msgstr[1] "%s produkter er utløpt på dato"
msgid "Expired"
msgstr ""
msgstr "Utgått"
msgid "Due soon days"
msgstr ""
msgstr "Forfaller snart dager"
msgid "Add overdue/expired products"
msgstr ""
msgstr "Legg til forfalte produkter"
msgid ""
"Products with tare weight enabled are currently not supported for transfer"
msgstr ""
"Produkter med taravekt aktivert støttes for øyeblikket ikke for overføring"
msgid ""
"This cannot be lower than %1$s or equal %2$s and needs to be a valid number "
"with max. %3$s decimal places"
msgstr ""
"Dette kan ikke være lavere enn %1$s eller likt %2$s og må være et gyldig "
"tall med maks %3$s desimaler"
msgid ""
"This must be between %1$s and %2$s, cannot equal %3$s and needs to be a "
"valid number with max. %4$s decimal places"
msgstr ""
"Dette må være mellom%1$s og %2$s kan ikke være samme som %3$s og må være et "
"gyldig tall med maks %4$s desimaler"
msgid ""
"This cannot be lower than %1$s and needs to be a valid number with max. %2$s"
" decimal places"
msgstr ""
"Dette kan ikke være lavere enn %1$s og må være et gyldig tall med maks %2$s "
"desimaler"
msgid ""
"This must between %1$s and %2$s and needs to be a valid number with max. "
"%3$s decimal places"
msgstr ""
"Dette må være mellom %1$s og %2$s og må være et gyldig nummer med maks %3$s "
"desimaler"
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 ""
"Gjør gjennomføringen automatisk med den siste prisen og beløpet på "
"handlelistpunktet, hvis produktet er satt til \"Standard forfallsdato\""
msgid ""
"When moving products from/to a freezer location, the products due date is "
"automatically adjusted according to the product settings"
msgstr ""
"Når du flytter produkter fra / til en fryser, justeres produktets "
"forfallsdato automatisk i henhold til produktinnstillingene"
msgid "This is the internal field name, e. g. for the API"
msgstr ""
msgstr "Dette er det interne feltnavnet, f.eks. g. for API"
msgid "This is used to display the field on the frontend"
msgstr ""
msgstr "Dette brukes til å vise feltet på framsiden"
msgid "Multiple Userfields will be ordered by that number on the input form"
msgstr ""
msgstr "Flere brukerfelt vil bli sortert etter nummeret på inndata feltet"
msgid "Sort number"
msgstr ""
msgstr "Sorteringsnummer"
msgid "Download file"
msgstr ""
msgstr "Last ned fil"
msgid "Use the products \"Quick consume amount\""
msgstr ""
msgstr "Bruk produktets \"Hurtigkonsumer\""
msgid "Disabled"
msgstr ""
msgstr "Avslått"
msgid ""
"This also removes any stock amount, the journal and all other references of "
"this product - consider disabling it instead, if you want to keep that and "
"just hide the product."
msgstr ""
"Dette fjerner beholdningsantall, logg og alle andre referanser til dette "
"produktet - vurdere å deaktivere det i stedet, hvis du vil beholde det og "
"bare skjule produktet."
msgid "Show disabled"
msgstr ""
msgstr "Vis avslåtte"
msgid "Never show on stock overview"
msgstr ""
msgstr "Vis aldri på beholdningsoversikt"
msgid "None"
msgstr ""
msgstr "Ingen"
msgid "Group by"
msgstr ""
msgstr "Grupperes etter"
msgid "Ingredient group"
msgstr ""
msgstr "Ingrediensgruppe"
msgid "Reset"
msgstr ""
msgstr "Reset"
msgid "Are you sure to reset the table options?"
msgstr ""
msgstr "Er du sikker på at du ønsker resette tabelen?"
msgid "Hide/view columns"
msgstr ""
msgstr "Skjul / vis kolonner"
msgid ""
"A different amount/unit can then be used below while for stock fulfillment "
"checking it is sufficient when any amount of the product in stock"
msgstr ""
"En annen mengde / enhet kan deretter brukes nedenfor, mens det er "
"tilstrekkelig når en hvilken som helst mengde av produktet er i beholdningen"
msgid "Last price (Unit)"
msgstr ""
msgstr "Siste pris (Enhet)"
msgid "Last price (Total)"
msgstr ""
msgstr "Siste pris (Total)"
msgid "Show header"
msgstr ""
msgstr "Vis overskrift"
msgid "Group by product group"
msgstr ""
msgstr "Gruppèr etter produktgruppe"
msgid "Table"
msgstr ""
msgstr "Tabel"
msgid "Layout type"
msgstr ""
msgstr "Layout type"
msgid "Merge this product with another one"
msgstr ""
msgstr "Slå sammen dette produktet med et annet"
msgid "Merge products"
msgstr ""
msgstr "Slå sammen produkter"
msgid "Product to keep"
msgstr ""
msgstr "Produkt som skal beholdes"
msgid "Product to remove"
msgstr ""
msgstr "Produkt som skal fjernes"
msgid "Error while merging products"
msgstr ""
msgstr "Feil under sammenslåing av produkter"
msgid "After merging, this product will be kept"
msgstr ""
msgstr "Etter sammenslåing så vil dette produktet bli beholdt"
msgid ""
"After merging, all occurences of this product will be replaced by \"Product "
"to keep\" (means this product will not exist anymore)"
msgstr ""
"Etter sammenslåing vil alle forekomster av dette produktet bli erstattet av "
"\"Produkt å beholde\" (dette betyr at produktet ikke vil eksistere lenger)"
msgid "Merge"
msgstr ""
msgstr "Sammenslå"
msgid "Title"
msgstr ""
msgstr "Tittel"
msgid "Link"
msgstr ""
msgstr "Link"
msgid ""
"The stock overview page lists all products which are currently in-stock or "
"below their min. stock amount - enable this to hide this product there "
"always"
msgstr ""
"Beholdningsoversikten viser alle produkter som er i beholdningen eller under"
" min. beholdningsantall - aktiver dette for å alltid skjule dette produktet"
msgid "Print options"
msgstr ""
msgstr "Printevalg"
msgid "A product or a note is required"
msgstr "Et produkt eller en merknad kreves"
msgid "grocycode"
msgstr ""
msgid "Download"
msgstr ""
# Example: Download *Product* grocycode
msgid "Download %s grocycode"
msgstr ""
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"
msgid "DD"
msgstr ""
msgid "Print on label printer"
msgstr ""
msgid "Stock entry label"
msgstr ""
msgid "No label"
msgstr ""
msgid "Single label"
msgstr ""
msgid "Label per unit"
msgstr ""
msgid "Allow label printing per unit"
msgstr ""
msgid ""
"Allow printing of one label per unit on purchase (after conversion) - e.g. 1"
" purchased pack adding 10 pieces of stock would print 10 labels"
msgstr ""
msgid "Error while executing WebHook"
msgstr ""
# Example: Print *Product* grocycode on label printer
msgid "Print %s grocycode on label printer"
msgstr ""
msgid "Open stock entry label in new window"
msgstr ""
msgid "Thermal printer"
msgstr ""
msgid "Printing"
msgstr ""
msgid "Connecting to printer..."
msgstr ""
msgid "Unable to print"
msgstr ""
msgid "Only done items"
msgstr ""
msgid "Show only in-stock products"
msgstr ""
msgid "Product description"
msgstr ""
# Example: *3.21 USD* per *Pack*
msgid "%1$s per %2$s"
msgstr ""
msgid "Mark this item as undone"
msgstr ""
msgid "Mandatory"
msgstr ""
msgid "Mandatory Userfield"
msgstr ""
msgid "When enabled, then this field must be filled on the destination form"
msgstr ""
msgid "In-stock products"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Should not be frozen"
msgstr ""
msgid ""
"When enabled, on moving this product to a freezer location (so when freezing"
" it), a warning will be shown"
msgstr ""
msgid "This product shouldn't be frozen"
msgstr ""
msgid "Copy all meal plan entries of %s"
msgstr ""
msgid "A date is required"
msgstr ""
msgid "Day"
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 ""
msgid "Configure sections"
msgstr ""
msgid "Meal plan sections"
msgstr ""
msgid "Create meal plan section"
msgstr ""
msgid "Sections will be ordered by that number on the meal plan"
msgstr ""
msgid "Edit meal plan section"
msgstr ""
msgid "Are you sure to delete meal plan section \"%s\"?"
msgstr ""
msgid "Section"
msgstr ""

View File

@@ -1,6 +1,6 @@
#
# Translators:
# Marius Borø <blizzwave@gmail.com>, 2019
# Marius Borø <blizzwave@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -8,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:43+0000\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2019\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2021\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -59,12 +59,12 @@ msgstr "link"
# Link (with title)
msgid "link-with-title"
msgstr ""
msgstr "link-with-title"
# File
msgid "file"
msgstr ""
msgstr "file"
# Image
msgid "image"
msgstr ""
msgstr "image"

View File

@@ -402,3 +402,12 @@ msgstr "Tamilski"
msgid "Finnish"
msgstr "Fiński"
msgid "Breakfast"
msgstr ""
msgid "Lunch"
msgstr ""
msgid "Dinner"
msgstr ""

Some files were not shown because too many files have changed in this diff Show More