Compare commits

...

200 Commits

Author SHA1 Message Date
Bernd Bestel
001d5c5d1d Prepared next release 2019-07-06 20:43:30 +02:00
Bernd Bestel
b4d2e2a20a Added the possibility to undo a task (closes #252) 2019-07-06 20:34:01 +02:00
Bernd Bestel
914dde4609 Added a new config.php setting CALENDAR_FIRST_DAY_OF_WEEK to be able to change the first day of a week used for calendar views (closes #256) 2019-07-06 20:19:21 +02:00
Bernd Bestel
1eb1aa8b11 Added a "consume this recipe"-button to the meal plan (and also a button to consume all recipes for a whole week) (closes #283) 2019-07-06 20:02:40 +02:00
Bernd Bestel
09b23847b5 Added a new config.php setting DISABLE_AUTH to be able to disable authentication / the login screen (closes #246) 2019-07-06 18:29:18 +02:00
Bernd Bestel
8c205941c7 Added that products can now also be consumed as spoiled from the stock overview page (option in the more/context menu per line) (closes #251) 2019-07-06 18:15:53 +02:00
Bernd Bestel
b24683f954 Added the possibility to mark a shopping list item as "done" (closes #257) 2019-07-06 17:56:59 +02:00
Bernd Bestel
e4d26bb8fd Make it possible to switch shopping list items between shopping lists (closes #284) 2019-07-06 17:31:17 +02:00
Bernd Bestel
c6c10c87e4 Improved date display for dates of today and no time
Instead of the hours since midnight now just "Today" will be shown
2019-07-06 17:19:28 +02:00
Bernd Bestel
df529c3c0b Show 2999-12-31 as "Never" everywhere (closes #296) 2019-07-06 15:43:54 +02:00
Bernd Bestel
482a520062 Slightly modified new recipe stock fulfillment API endpoints (references #289) 2019-07-06 15:28:49 +02:00
Bernd Bestel
ddef58e2a9 Merge pull request #289 from Aerex/add-resolved-recipes-endpoint
Add requirement fulfillment recipes endpoint
2019-07-06 15:02:20 +02:00
Bernd Bestel
d34c7b0a87 Created the changelog for the next version 2019-07-06 14:56:56 +02:00
Bernd Bestel
b76e51ba41 Fixed nested recipes costs calculation (fixes #299) 2019-07-06 14:48:46 +02:00
Bernd Bestel
67cfd0ba5f Remove the internal meal plan recipe when removing a meal plan entry (fixes #298) 2019-07-06 13:57:49 +02:00
Bernd Bestel
3fcede0b7c Fix that "Track date only" cannot be tracked <> today (fixes #300) 2019-07-06 13:32:40 +02:00
Bernd Bestel
0c0e8c6957 Fixed the consume success message on stock overview page (fixes #302) 2019-07-06 13:13:38 +02:00
Aerex
a01a80578c Merge branch 'master' into add-resolved-recipes-endpoint 2019-06-20 23:40:55 -05:00
grocy
7a51fb77b0 feat: Added recipes/requirements route
- feat: Added requirements route to allow clients to access the requirements fulfilments of recipes
2019-06-20 23:11:57 -05:00
Bernd Bestel
511c95070e Minor typos... 2019-06-09 09:34:21 +02:00
Bernd Bestel
eec6270cb7 Prepared next release 2019-06-09 09:30:18 +02:00
Bernd Bestel
7c7c5db28c Fixed that a entered barcode on the product edit page was only saved when "adding" it to the barcodes list by pressing TAB (closes #269) 2019-06-09 09:21:56 +02:00
Bernd Bestel
6b98fa85d3 Improved meal plan entry reference when deleting an entry (references #255) 2019-06-09 09:10:29 +02:00
Bernd Bestel
825be63b93 Fixed single quotes problem in all pickers (fixes #264) 2019-06-09 09:06:44 +02:00
Bernd Bestel
a56f8be19e Fixed page reloads with unsaved form data and "Auto reload on external changes" enabled (fixes #265) 2019-06-09 08:58:46 +02:00
Bernd Bestel
f736c4de44 Added changelog with current changes 2019-06-08 17:03:17 +02:00
Bernd Bestel
56217804b7 Always include en_GB localizations regardless of completion (because there aren't real translations needed, fixes #276) 2019-06-08 16:56:37 +02:00
Bernd Bestel
43710b5062 Properly show the API error when it failed to undo a stock booking (fixes #267) 2019-06-08 16:53:31 +02:00
Bernd Bestel
b1adaa24cf Fix session problem on 32 bit systems when using "stay logged in permanently" (fixes #278) 2019-06-08 16:47:45 +02:00
Bernd Bestel
9e7d62b62d Make sure to hide all tooltips before removing an element (fixes #260) 2019-06-08 16:39:35 +02:00
Bernd Bestel
9f2481a6a8 Fixed an issue when a plural translation is empty/null (can be the case for quantity units, fixes #259) 2019-06-08 16:32:23 +02:00
Bernd Bestel
2b77bc6ae6 Fixed deleting meal plan entries did not work (fixes #255) 2019-06-08 16:30:45 +02:00
Bernd Bestel
91116ee768 Make sure user settings variable is populated always (fixes #277) 2019-06-08 15:54:56 +02:00
Bernd Bestel
167e57ef3f Merge pull request #272 from BlizzWave/patch-23
Update grocy_night_mode.css
2019-05-30 19:42:05 +02:00
Marius Boro
3321bcd683 Update grocy_night_mode.css
Fixed unreadable text
2019-05-30 14:32:27 +02:00
Bernd Bestel
41b8b5d11e Prepared new release 2019-05-16 22:25:46 +02:00
Bernd Bestel
0a4ea6861a Fixed quotes were not escaped properly for contains search in dropdowns (fixes #249) 2019-05-16 22:20:01 +02:00
Bernd Bestel
8f9c3c66f7 Don't load userfieldsform.js when not needed 2019-05-16 22:14:04 +02:00
Bernd Bestel
d412b81eae Hotfix (will be included in v2.4.0 release): Fixed a potential problem when the plural form of a translation (e. g. plural forms for user defined quantity units) is NULL 2019-05-10 21:22:53 +02:00
Bernd Bestel
55e5fd7bf1 Prepared next release 2019-05-10 21:08:42 +02:00
Bernd Bestel
1787690795 Added missing translations string 2019-05-10 21:04:28 +02:00
Bernd Bestel
3eb8b5f381 Pulled translations from Transifex 2019-05-10 21:01:58 +02:00
Bernd Bestel
d8cefeffb7 Added missing translations strings 2019-05-10 20:57:49 +02:00
Bernd Bestel
5da50d1e1e Updated dependencies 2019-05-10 20:56:03 +02:00
Bernd Bestel
de89fee5bb Fixed wrong amount was displayed in popup after consume 2019-05-10 20:43:57 +02:00
Bernd Bestel
338c6c0a9d Added a new userfield type "preset-list" (closes #239) 2019-05-07 21:24:59 +02:00
Bernd Bestel
8504eb9b38 Finished first version of meal planning (for now closes #146) 2019-05-07 19:48:14 +02:00
Bernd Bestel
3f53919ddd Also allow placeholders for plural translations (references #161) 2019-05-07 19:42:35 +02:00
Bernd Bestel
57233dba1a Added first basic version of meal planning (references #146) 2019-05-06 19:38:47 +02:00
Bernd Bestel
e240260f9f Unify translations strings which include an amount + quantity unit (references #161) 2019-05-05 14:31:27 +02:00
Bernd Bestel
dd148a8fc3 Use named arguments for all gettext strings which have more than 1 argument (again closes #161) 2019-05-05 14:13:50 +02:00
Bernd Bestel
153ac61867 Fix deleting user have not worked (fixes #237) 2019-05-04 23:10:01 +02:00
Bernd Bestel
9ef55f1f01 Make it possible to track a chore execution without the time part, only the day 2019-05-04 16:13:05 +02:00
Bernd Bestel
98fcd767b3 Fixed chore edit form - period type hint did not work (references #151) 2019-05-04 15:40:26 +02:00
Bernd Bestel
fe8bb43996 Fixed userfields did not load (references #176) 2019-05-04 15:21:50 +02:00
Bernd Bestel
328d96ed60 Only show plural forms text field on quantity unit edit page when the current language has more than 2 plural forms to prevent confusion (references #161) 2019-05-04 15:15:03 +02:00
Bernd Bestel
970e5edfa3 Fixed an issue with translations string without an placeholder (references #161) 2019-05-04 15:13:01 +02:00
Bernd Bestel
12ba99f649 Added "variable amount" for recipe ingredients (closes #181) 2019-05-04 14:50:15 +02:00
Bernd Bestel
314e434fd1 Updated translations 2019-05-04 13:43:14 +02:00
Bernd Bestel
29f69c92e9 Update OpenAPI object schemas and added more examples (closes #218) 2019-05-04 13:33:44 +02:00
Bernd Bestel
0eb974bd92 Make it possible to customize the default amount for purchase/consume (closes #215) 2019-05-04 13:19:34 +02:00
Bernd Bestel
bcd6dd4b20 Forgot to save a file on the last commit... 2019-05-03 22:18:24 +02:00
Bernd Bestel
6cecb2ca7b Make it possible to enter up to 4 decimal places for price fields (this now closes #225) 2019-05-03 22:15:30 +02:00
Bernd Bestel
bcae9f9292 Added price field on inventory page (for added products) (references #225) 2019-05-03 22:11:20 +02:00
Bernd Bestel
8138dd43ac Added barcode as a column to master data / products page (closes #234) 2019-05-03 21:41:31 +02:00
Bernd Bestel
78a230c5d5 Fixed again a problem with productpicker workflows and disabled URL rewriting (closes #232) 2019-05-03 20:22:48 +02:00
Bernd Bestel
4c2cf4944d Added a feature flag to also be able to hide all stock related UI elements and routes (closes #228) 2019-05-03 20:03:04 +02:00
Bernd Bestel
bd296f8fe1 Fixed a problem where the current stock fulfillment amount is not correctly displayed (references #161) 2019-05-03 19:51:08 +02:00
Bernd Bestel
24680154d8 Properly display/round recipe ingredients amounts (closes #230) 2019-05-03 19:36:27 +02:00
Bernd Bestel
dfd501c515 Don't load or save userfields if there are none (references #176) 2019-05-03 19:29:12 +02:00
Bernd Bestel
dae5bb2b34 Make it possible to filter recipes by stock availability (closes #231) 2019-05-03 19:22:58 +02:00
Bernd Bestel
595171afa5 Properly show and handle that the new amount during inventory cannot equal the current stock amount (this now closes #224) 2019-05-03 19:08:54 +02:00
Bernd Bestel
7fc4992b3a Fixed API response for /api/stock/products/{productId}/inventory when the new amount equals the current stock amount (references #224) 2019-05-03 18:37:02 +02:00
Bernd Bestel
618ff5609f Fixed success message on inventory page showed the previous amount instead of the new one (references #224) 2019-05-03 18:32:59 +02:00
Bernd Bestel
94c6e634b8 Fixed the "Add as barcode to existing product" productpicker workflow failed to add the barcode to the given product (fixes #222) 2019-05-03 18:29:09 +02:00
Bernd Bestel
b310aa55c5 Prevent productpicker workflow modal from being opened twice (fixes #229) 2019-05-02 21:46:15 +02:00
Bernd Bestel
3cf8ebeb89 Fixed product picker workflow URLs were wrong when running grocy in a subdirectory and with disabled URL rewriting (again fixes #219) 2019-05-02 21:33:59 +02:00
Bernd Bestel
ae156ed0e6 Removed accidentally added strings 2019-05-02 20:31:47 +02:00
Bernd Bestel
4912dd56d1 Finished migration to use gettext (this now closes #161) 2019-05-02 20:20:18 +02:00
Bernd Bestel
5d3f248d94 Pulled translations from Transifex (references #161) 2019-05-01 20:38:25 +02:00
Bernd Bestel
9b2dba2397 Migrated (hopefully) all translations to PO/Gettext (references #161) 2019-05-01 20:19:18 +02:00
Bernd Bestel
40b5afe926 Backup current state of LocalizationService.php (references #161)
This will serve as a permanent backup for the migration algorithm used to migrate all current translation files to PO/Gettext
2019-05-01 20:04:06 +02:00
Bernd Bestel
6ceb6e3643 Backup all current translations from Transifex (references #161)
This will serve as a permanent backup of the current translations before migrating them to PO/gettext
2019-05-01 19:34:09 +02:00
Bernd Bestel
a999112a21 Added a link to the new grocy subreddit (references grocy/grocy-website#1) 2019-04-28 21:18:35 +02:00
Bernd Bestel
310cdd7d4c Pulled translations from Transifex 2019-04-28 19:17:31 +02:00
Bernd Bestel
77f094810b Added changelog with current changes for next release 2019-04-28 09:55:43 +02:00
Bernd Bestel
b6b8c76d3a Fixed product picker workflows URLs were wrong when running grocy in a subdirectory (fixes #219) 2019-04-27 15:17:55 +02:00
Bernd Bestel
6442665f6c Just some technical corrections for the new sv_SE translation 2019-04-23 09:49:25 +02:00
Bernd Bestel
04ca6edbd3 Pulled translations from transifex 2019-04-23 09:40:04 +02:00
Bernd Bestel
c5993ad994 Finalize user-defined-fields (closes #176) 2019-04-23 09:06:18 +02:00
Bernd Bestel
72a3f63546 Extended contributing info to use pull requests for changes 2019-04-22 23:13:57 +02:00
Bernd Bestel
eae8536c9b Added contribution info 2019-04-22 22:43:07 +02:00
Bernd Bestel
fc11da3c3f Started working on user-defined-fields for all entities (references #176) 2019-04-22 22:16:35 +02:00
Bernd Bestel
77f3b80540 Implemented daily/weekly/monthly recurrence patterns for chores (closes #151) 2019-04-22 14:01:31 +02:00
Bernd Bestel
d72fe69a17 Show more info in product card (closes #173) 2019-04-22 10:11:58 +02:00
Bernd Bestel
162adeb359 Improve API related changes regarding multiple shopping lists (references #190) 2019-04-22 08:21:57 +02:00
Bernd Bestel
49d16b458d Added missing localization strings 2019-04-20 19:25:59 +02:00
Bernd Bestel
cdd02efcc6 Implemented multiple/named shopping lists (closes #190) 2019-04-20 17:04:40 +02:00
Bernd Bestel
c1674d33b4 Make "next X days" configurable (closes #175) 2019-04-20 15:30:45 +02:00
Bernd Bestel
41988aa1ee Updated version.json 2019-04-06 16:16:44 +02:00
Bernd Bestel
18b8712369 Updated dependencies for next release 2019-04-06 16:13:19 +02:00
Bernd Bestel
42a9d5af2b Pulled translations from Transifex 2019-04-06 16:03:43 +02:00
Bernd Bestel
c4d377ce4e Added new changelog 2019-04-06 16:00:17 +02:00
Bernd Bestel
50a782c8c0 Added missing translation strings 2019-04-06 16:00:02 +02:00
Bernd Bestel
fa6f09679f Made "Disable stock fulfillment checking for this ingredient" a default option per product (closes #182) 2019-04-06 15:42:40 +02:00
Bernd Bestel
25c257bb2c Allow partial minimum stock amount when enabled (closes #203) 2019-04-06 15:27:00 +02:00
Bernd Bestel
0496ae9e00 Added an info for product-add-workflows (closes #192) 2019-04-06 15:15:20 +02:00
Bernd Bestel
45ae386005 Fixed context menu is not visible when the table height is smaller than the menu height (fixes #195) 2019-04-06 14:41:43 +02:00
Bernd Bestel
b6e80580ed Make it possible to provide a different location for added product during inventory (closes #183) 2019-04-05 21:26:44 +02:00
Bernd Bestel
886e272c03 Show product count per group on the product groups page and added a link to the products page filtered by the current product group (closes #174) 2019-04-05 21:08:30 +02:00
Bernd Bestel
40cc0ff280 Revert changes in file public/js/grocy.js of commit "Fixed differences in highlighting for expiring/expired items in header vs table on stock overview page (fixes #198)"
This reverts changes in file public/js/grocy.js of commit 12082b52ab.
2019-04-05 18:59:58 +02:00
Bernd Bestel
3a0bb913d5 Improved form input padding (fixes #180) 2019-04-05 18:50:46 +02:00
Bernd Bestel
12082b52ab Fixed differences in highlighting for expiring/expired items in header vs table on stock overview page (fixes #198) 2019-04-05 18:41:21 +02:00
Bernd Bestel
2d3df2024a Fixed "Mark as open" button is disabled on stock overview page when current stock amount == 1 (fixes #197) 2019-04-05 18:01:21 +02:00
Bernd Bestel
a9c0539305 Updated version.json 2019-03-10 16:29:02 +01:00
Bernd Bestel
5b304c5e97 Added new changelog and update translations 2019-03-10 16:27:34 +01:00
Bernd Bestel
1b26a6277b Make demo product locations a little bit more realistic 2019-03-10 16:18:24 +01:00
Bernd Bestel
353a65d41c Tried to fix inventory form validation again (closes #167) 2019-03-10 16:08:15 +01:00
Bernd Bestel
bfcd05473a Allow best before date on purchase/inventory to be today or in the past, but display a hint when so (closes #172) 2019-03-10 16:02:13 +01:00
Bernd Bestel
3d485d4850 Fixed datetimepicker had no validation message displayed (references #172) 2019-03-10 15:40:19 +01:00
Bernd Bestel
9d02fbc13c Again some changes for the new product-by-barcode API method (references #171) 2019-03-10 14:53:06 +01:00
Bernd Bestel
27ba2bfd55 Fixed display errors when consuming partial amounts from stock overview page (fixes #170) 2019-03-10 14:10:25 +01:00
Bernd Bestel
61f582554f Return a proper API response when booking amount for consume/open is > current stock amount (references #170) 2019-03-10 13:50:28 +01:00
Bernd Bestel
75241fc61f Just some changes for the new product-by-barcode API method (references #171) 2019-03-10 13:43:58 +01:00
Bernd Bestel
91289588b5 Merge pull request #171 from matejdro/barcode_fetch
Add fetch by barcode API method
2019-03-10 13:28:36 +01:00
Matej Drobnič
3f4a5cc0d6 Add fetch by barcode API method 2019-03-10 12:20:31 +01:00
Bernd Bestel
e693460894 Finalized next version 2019-03-09 17:19:02 +01:00
Bernd Bestel
47a6260d27 Show the plural form of stock QU unit when showing purchase to stock conversion factor on purchase page when QU units are different (references #169) 2019-03-09 17:04:34 +01:00
Bernd Bestel
bfd29def8d Show purchase to stock conversion factor on purchase page when QU units are different (closes #169) 2019-03-09 17:00:57 +01:00
Bernd Bestel
cd0ca4a67c Prepared next release 2019-03-09 16:42:46 +01:00
Bernd Bestel
659d60b235 Make it possible to show the changelog directly via /about?tab=changelog 2019-03-09 16:25:23 +01:00
Bernd Bestel
8efcb79ed7 Updated screenshots 2019-03-09 16:18:20 +01:00
Bernd Bestel
5ba55823c9 Clarify that the CURRENCY setting should be the ISO 4217 code of the currency (to work with the JS toLocaleString function) 2019-03-09 16:04:03 +01:00
Bernd Bestel
6de4b120b3 Include changelog as markdown files and show it in the about dialog 2019-03-09 15:54:16 +01:00
Bernd Bestel
8fec262184 Added missing translation strings 2019-03-09 13:14:36 +01:00
Bernd Bestel
bd483ec8b0 Added a context menu to all stock overview page items 2019-03-09 13:11:50 +01:00
Bernd Bestel
4d215edbd0 Clear displayed quantity unit after purchase/consume/inventory bookings 2019-03-09 12:31:45 +01:00
Bernd Bestel
a3d4fd834f Added a small right border to separate button columns in all tables better 2019-03-09 10:49:26 +01:00
Bernd Bestel
2d0c0bf34f Fixed cancel button color in unknown product resolve dialog 2019-03-09 10:41:03 +01:00
Bernd Bestel
0f03420808 Removed unnecessary table column heading for button columns 2019-03-09 10:40:38 +01:00
Bernd Bestel
ba319dc6f1 Fixed equipment page edit/delete button icons were not visible 2019-03-08 22:22:05 +01:00
Bernd Bestel
c10890205c Don't load not existing / not need localization JS files (this now closes #165) 2019-03-08 22:18:42 +01:00
Bernd Bestel
643f6272e4 Fixed file name case sensitivity issue with tagmanager jQuery Plugin (references 165) 2019-03-08 22:13:47 +01:00
Bernd Bestel
cad5e9ef79 Tried to fix inventory form validation (references #167) 2019-03-08 22:03:59 +01:00
Bernd Bestel
01fdfe1a0c Fixed recipe ingredient notes were not displayed on the recipes page 2019-03-07 22:20:08 +01:00
Bernd Bestel
acd1e7337c Prepared next release 2019-03-06 19:38:22 +01:00
Bernd Bestel
3586bf77c3 Added info about Feature Flags 2019-03-06 19:37:55 +01:00
Bernd Bestel
ff886fea61 Fixed termination after first command 2019-03-06 19:29:17 +01:00
Bernd Bestel
5bd00d8be7 Only download at least 90 percent completed translation files from Transifex 2019-03-06 19:25:35 +01:00
Bernd Bestel
9f36a599a4 Changed the about dialog slightly 2019-03-06 19:23:53 +01:00
Bernd Bestel
ff15e81abe Fixed a rounding regarding stock amount comparison when a (scaled) recipe needs a decimal amount (closes #164) 2019-03-06 19:05:32 +01:00
Bernd Bestel
e8b471f572 Fixed termination after first command 2019-03-06 18:42:51 +01:00
Bernd Bestel
2d8ad24887 Implemented that included recipes can have a serving amount (closes #163 and references #127) 2019-03-05 23:45:04 +01:00
Bernd Bestel
45db9ff90c Added scripts for Transifex upload/download & updated translations 2019-03-05 21:22:54 +01:00
Bernd Bestel
c813a6500d Removed embedded binary tools - not needed to be in the repo here 2019-03-05 21:11:15 +01:00
Bernd Bestel
1077149784 Hide the form validation feedback icons introduced in Bootstrap 4.2.0 - a colored border is enough 2019-03-05 20:15:29 +01:00
Bernd Bestel
e08dfb408c Reorganized dev scripts and tools 2019-03-05 19:54:38 +01:00
Bernd Bestel
fcdeda91d9 Show the iCal sharing link instead of directly open it (references #141) 2019-03-05 19:36:14 +01:00
Bernd Bestel
4932b9c6d2 Improved recipe header display on small screens 2019-03-05 19:22:16 +01:00
Bernd Bestel
7f2942d414 Also filter gallery items when searching for recipes (references #147) 2019-03-05 19:17:10 +01:00
Bernd Bestel
3a1c5efcfd Fix gallery tab was not selected by query parameter (references #147) 2019-03-05 18:04:16 +01:00
Bernd Bestel
816eb03147 Added a "recipe gallery" view (closes #147) 2019-03-05 17:59:33 +01:00
Bernd Bestel
8504429f5f Implemented tare weight handling (closes #132) 2019-03-05 17:51:50 +01:00
Bernd Bestel
90291fdbca Added possibility to export the calendar in iCal format (closes #141) 2019-03-04 17:44:48 +01:00
Bernd Bestel
77b0bc675c Varios small UI changes / improve UI consistency 2019-03-04 17:43:12 +01:00
Bernd Bestel
8020f92d6b Track on consume for which recipe it was (closes #64 and references #64) 2019-03-03 18:20:06 +01:00
Bernd Bestel
38825c70da Added missing translations strings & pulled translations from Transifex 2019-03-03 17:01:35 +01:00
Bernd Bestel
bb5daa5f8b Display total cost of recipes based on last purchase prices (closes #128) 2019-03-03 16:33:48 +01:00
Bernd Bestel
8c11d0f15d Add an option per recipe to not check against the shopping list when adding missing recipe ingredients, only against stock (closes #154) 2019-03-03 15:23:39 +01:00
Bernd Bestel
5b544f76a5 Merge pull request #160 from d-Rickyy-b/patch-1
FIX: Typo in config-dist.php
2019-03-03 15:00:29 +01:00
Bernd Bestel
6f93da9b5f Only list missing products in the "recipe put everything on the shopping list"-dialog (references #125) 2019-03-03 14:57:36 +01:00
Bernd Bestel
e9ef7ea6d8 Allow recipe ingredients to be ignored when putting them on the shopping list (closes #125) 2019-03-03 14:49:46 +01:00
Rico
54a23019b8 FIX: Typo in config-dist.php 2019-03-03 14:47:38 +01:00
Bernd Bestel
89ad25c904 Implemented scalable recipes (closes #127) 2019-03-03 13:27:10 +01:00
Bernd Bestel
ee38d626aa Added all application icon variants (closes #159) 2019-03-02 08:56:53 +01:00
Bernd Bestel
40b60bed85 Added apple-touch-icon tag (references #159) 2019-03-01 22:37:15 +01:00
Bernd Bestel
b89643ddb1 Allow different locations per product in stock (closes #124)
Kind of basic for now, a different location can be set on purchase, the filters on the stock overview page handles different locations
2019-03-01 20:25:01 +01:00
Bernd Bestel
32e878afc9 Added feature flags to disable/hide parts not needed (closes #157)
grocy was initally about stock management, so this is always enabled, the rest can be disabled
2019-03-01 19:33:33 +01:00
Bernd Bestel
9974305ad4 Hide relative date when clearing the datetimepicker (this now closes #143) 2019-02-09 14:48:13 +01:00
Bernd Bestel
a3b2d03d68 Fixed datetimepicker only worked once when not selecting a date
References #143
Using a self-patched version of tempusdominus/bootstrap-4 for now, see 2cf725fed9 and tempusdominus/bootstrap-4#34
2019-02-09 14:44:37 +01:00
Bernd Bestel
01e9e3f5ce Move about dialog into separate view and add API endpoint for system info 2019-02-09 13:41:40 +01:00
Bernd Bestel
b5ac319a90 Pull translations from Transifex 2019-02-03 17:02:40 +01:00
Bernd Bestel
ad09630dbe Make it possible to add pictures to recipes (closes #136) 2019-01-26 20:06:01 +01:00
Bernd Bestel
03720940d4 Resize embedded iframes to content height (this should fix #130) 2019-01-26 19:31:41 +01:00
Bernd Bestel
dfc05e0bec Allow optional partial units of products in stock (closes #123) 2019-01-26 14:17:02 +01:00
Bernd Bestel
9e139e2b73 Remove missing translation and improve database migration for #149 2019-01-26 14:12:18 +01:00
Bernd Bestel
5c622b9512 Fix product form was not validated when changing location (fixes #144) 2019-01-26 13:13:38 +01:00
Bernd Bestel
eec3515b6d Don't use a second hidden amount field for shopping list entries which were added by "Add missing products to shopping list" (fixes #149) 2019-01-26 13:09:01 +01:00
Bernd Bestel
276bc94cc6 More improvements on the REST API (references #139) 2019-01-21 22:13:42 +01:00
Bernd Bestel
bfa59dd29c Rework API to be more RESTful (references #139) 2019-01-19 14:51:51 +01:00
Chris Forkner
0ce8d706a6 API - A bit more RESTful (#140)
* Restful routes

* Change public/viewjs to match API routes

* Move the GET and POST together. Fixed Typos. PUT for object/user edits.

* Verb-less Generic Entity Interactions

* Create Grocy.Api.Put

* Create Grocy.Api.Delete

* Fix Volatile Slim Error order in routes and adjust to english noun
2019-01-19 08:37:21 +01:00
Bernd Bestel
98d95f80df Prepared next release 2019-01-10 21:02:11 +01:00
Bernd Bestel
a72afa7174 Forgot to save before pushing last commit... 2019-01-05 21:14:23 +01:00
Bernd Bestel
0d145bbf1e Respect QU purchase factor when adding or comparing recipe ingredients to shopping list entries (fixes #131) 2019-01-05 21:11:36 +01:00
Bernd Bestel
f6cf26009d Better API response when request body is not valid JSON (references #126) 2019-01-05 20:39:22 +01:00
Bernd Bestel
c042657dd8 Fix location edit form (edit did not work) 2019-01-05 20:38:27 +01:00
Bernd Bestel
43c9ab7734 Fixed a problem with newer SQLite versions (references and fixes #133) 2019-01-05 20:28:21 +01:00
Bernd Bestel
f6649d51bd Finalize loading speed improvements for all data tables (this now closes #120)
This is a workaround for now. The tables are still DOM sourced because of too big dependencies between server side rendering and frontend JS code. The tables are initially load while tbody is hidden, this results in a speedup by around 65 %.
2019-01-05 20:06:35 +01:00
Bernd Bestel
2e265ac70a Again some changes for stock overview table loading speed workaround (references #120) 2019-01-05 16:27:26 +01:00
Bernd Bestel
30e54997b2 Workaround to improve stock overview table loading time (references #120) 2019-01-05 14:27:40 +01:00
392 changed files with 36443 additions and 5513 deletions

View File

@@ -0,0 +1,16 @@
set projectPath=%~dp0
if %projectPath:~-1%==\ set projectPath=%projectPath:~0,-1%
set projectPath=%projectPath%\..
set releasePath=%projectPath%\.release
mkdir "%releasePath%"
copy "%projectPath%\version.json" versiontemp.json
for /f "tokens=*" %%a in ('jq .Version versiontemp.json --raw-output') do set version=%%a
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 -xr!publication_assets
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

@@ -0,0 +1,4 @@
pushd ..
call composer install
yarn install
popd

View File

@@ -0,0 +1,4 @@
pushd ..
tx pull --all --minimum-perc=90
tx pull --language en_GB
popd

View File

@@ -0,0 +1,3 @@
pushd ..
tx push --source
popd

View File

@@ -0,0 +1,4 @@
pushd ..
call composer update
yarn upgrade
popd

View File

@@ -1,37 +1,38 @@
[main]
host = https://www.transifex.com
[grocy.stringsphp]
file_filter = localization/<lang>/strings.php
minimum_perc = 0
source_file = localization/en/strings.php
[grocy.chore_types]
file_filter = localization/<lang>/chore_types.po
source_file = localization/chore_types.pot
source_lang = en
type = PHP_ARRAY
type = PO
[grocy.stock_transaction_typesphp]
file_filter = localization/<lang>/stock_transaction_types.php
minimum_perc = 0
source_file = localization/en/stock_transaction_types.php
[grocy.component_translations]
file_filter = localization/<lang>/component_translations.po
source_file = localization/component_translations.pot
source_lang = en
type = PHP_ARRAY
type = PO
[grocy.chore_typesphp]
file_filter = localization/<lang>/chore_types.php
minimum_perc = 0
source_file = localization/en/chore_types.php
[grocy.demo_data]
file_filter = localization/<lang>/demo_data.po
source_file = localization/demo_data.pot
source_lang = en
type = PHP_ARRAY
type = PO
[grocy.component_translationsphp]
file_filter = localization/<lang>/component_translations.php
minimum_perc = 0
source_file = localization/en/component_translations.php
[grocy.stock_transaction_types]
file_filter = localization/<lang>/stock_transaction_types.po
source_file = localization/stock_transaction_types.pot
source_lang = en
type = PHP_ARRAY
type = PO
[grocy.demo_dataphp]
file_filter = localization/<lang>/demo_data.php
minimum_perc = 0
source_file = localization/en/demo_data.php
[grocy.strings]
file_filter = localization/<lang>/strings.po
source_file = localization/strings.pot
source_lang = en
type = PHP_ARRAY
type = PO
[grocy.userfield_types]
file_filter = localization/<lang>/userfield_types.po
source_file = localization/userfield_types.pot
source_lang = en
type = PO

View File

@@ -5,6 +5,9 @@ ERP beyond your fridge
- Public demo of the latest stable version &rarr; [https://demo.grocy.info](https://demo.grocy.info)
- Public demo of the latest pre-release version (current master branch) &rarr; [https://demo-prerelease.grocy.info](https://demo-prerelease.grocy.info)
## Getting in touch
There is the [r/grocy subreddit](https://www.reddit.com/r/grocy) to connect with other grocy users. If you've found something that does not work or if you have an idea for an improvement or new things which you would find useful, feel free to open an issue in the [issue tracker](https://github.com/grocy/grocy/issues) here.
## 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!
@@ -73,6 +76,9 @@ There is no plugin included for any service, see the reference implementation in
### Database migrations
Database schema migration is automatically done when visiting the root (`/`) route (click on the logo in the left upper edge).
### Disable certain features
If you don't use certain feature sets of grocy (for example if you don't need "Chores"), there are feature flags per major feature set to hide/disable the related UI elements (see `config-dist.php`)
### Adding your own CSS or JS without to have to modify the application itself
- When the file `data/custom_js.html` exists, the contents of the file will be added just before `</body>` (end of body) on every page
- When the file `data/custom_css.html` exists, the contents of the file will be added just before `</head>` (end of head) on every page
@@ -85,6 +91,11 @@ When the file `embedded.txt` exists, it must contain a valid and writable path w
In embedded mode, settings can be overridden by text files in `data/settingoverrides`, the file name must be `<SettingName>.txt` (e. g. `BASE_URL.txt`) and the content must be the setting value (normally one single line).
## Contributing
Any help is more than appreciated. Feel free to pick any open unassigned issue and submit a pull request, but please leave a short comment or assign the issue yourself, to avoid working on the same thing.
New ideas are also very welcome, feel free to open an issue to discuss them.
## Screenshots
#### Dashboard
![Dashboard](https://github.com/grocy/grocy/raw/master/publication_assets/dashboard.png "Dashboard")

View File

@@ -40,6 +40,15 @@ require_once __DIR__ . '/vendor/autoload.php';
require_once GROCY_DATAPATH . '/config.php';
require_once __DIR__ . '/config-dist.php'; //For not in own config defined values we use the default ones
// Definitions for disabled authentication mode
if (GROCY_DISABLE_AUTH === true)
{
if (!defined('GROCY_USER_ID'))
{
define('GROCY_USER_ID', 1);
}
}
// Setup base application
$appContainer = new \Slim\Container([
'settings' => [

View File

@@ -1,13 +0,0 @@
set projectPath=%~dp0
if %projectPath:~-1%==\ set projectPath=%projectPath:~0,-1%
set releasePath=%projectPath%\.release
mkdir "%releasePath%"
for /f "tokens=*" %%a in ('build_tools\jq.exe .Version version.json --raw-output') do set version=%%a
del "%releasePath%\grocy_%version%.zip"
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!package.json -xr!yarn.lock -xr!publication_assets
"build_tools\7za.exe" a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
"build_tools\7za.exe" rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\storage data\viewcache\*

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
- Added a login screen and switched to cookie/session based authentication instead of HTTP-basic-auth

View File

@@ -0,0 +1,2 @@
- New feature: Habit tracking
- Fixed an issue which prevented that the databse is correctly created on unix systems

View File

@@ -0,0 +1,2 @@
- New feature: Rechargeable battery management
- Improved productivity of input forms

View File

@@ -0,0 +1 @@
- Improved sidebar responsiveness

View File

@@ -0,0 +1,2 @@
- Allow to add anything to the shopping list, not only products
- Major project refactoring

View File

@@ -0,0 +1 @@
- grocy is now fully localizable and ships by default with English and German translations

View File

@@ -0,0 +1,2 @@
- New configuration option "BASE_URL" to define base installation URL (should make subdirectory installations possible, see #3)
- Added some missing translations

View File

@@ -0,0 +1 @@
- Fixed login form didn't respect the configured BASE_URL

View File

@@ -0,0 +1 @@
- Documented the REST API and data model, see the integrated instance of Swagger UI at [/api](https://demo-en.grocy.info/api)

View File

@@ -0,0 +1 @@
- Added validation of all API requests and improved Swagger/OpenAPI description

View File

@@ -0,0 +1 @@
- Basic features, mainly about a interface to record grocery purchases and consumptions

View File

@@ -0,0 +1 @@
- Added a plugin system for looking up products against external services by barcode, see #6 for reference

View File

@@ -0,0 +1,4 @@
- It's now possible to consume products directly from stock overview with one click
- Added due/overdue info on bateries- and habits overview (like on stock overview)
- Reworked general page layout and improved responsiveness (see #9 and thanks @d-Rickyy-b)
- Translations fixes

View File

@@ -0,0 +1 @@
- Added an option to not use URL rewriting (for webservers which, however, don't support URL rewriting)

View File

@@ -0,0 +1,2 @@
- On the stockoverview it's now possible to filter the products by location
- All dropdowns are now sorted alphabetically

View File

@@ -0,0 +1 @@
- Bug fix for location filtering on stock overview page did not work in all browsers

View File

@@ -0,0 +1,3 @@
- Upgraded Bootstrap and some other dependencies (grocy now looks even better!)
- Added Italian translation (thanks @davidoskky)
- => Demo for this language available at: https://demo-it.grocy.info

View File

@@ -0,0 +1,5 @@
This was released shortly after the last release to fix a small regression bug, original changes from Version 1.13.0:
- Upgraded Bootstrap and some other dependencies (grocy now looks even better!)
- Added Italian translation (thanks @davidoskky)
- => Demo for this language available at: https://demo-it.grocy.info

View File

@@ -0,0 +1,13 @@
- New feature: **Recipes**
- Organize a list of products, amounts and a description into recipes and see at a glance if everything needed is in stock or put the missing things with one click on the shopping list
- Try it live on the demo page: => https://demo-en.grocy.info/recipes
- Added norwegian translation (thanks @BlizzWave)
- Demo available at: => https://demo-no.grocy.info
- A lot of small UI improvements
- Columns in tables can now be reordered
- Show a calendar on the shopping list page (useful, at least for me)
- Table column ordering and sorting is now remembered
- Sidebar collapse state is now remembered
- Fixed datetimepicker border
- Keep the parent sidebar menu item expanded if the active page is a sub menu item
- Custom JS/CSS file names have changed [see README](https://github.com/berrnd/grocy#adding-your-own-css-or-js-without-to-have-to-modify-the-application-itself)

View File

@@ -0,0 +1,9 @@
- New related project: **grocy-desktop**
- => https://github.com/berrnd/grocy-desktop
- Run grocy without a webserver just like a normal (windows) desktop application
- New "embedded mode" for grocy to help running in "desktop application mode" [see README](https://github.com/berrnd/grocy#embedded-mode)
- New datepicker shorthands and improvements
- `YYYYMMe` or `YYYYMM+` gets expanded to the end of the given month in the given year in proper notation
- Changed: `MMDD` will be expanded to the given day next year if > today
- [see README](https://github.com/berrnd/grocy#input-shorthands-for-date-fields)
- Some other small bug fixes

View File

@@ -0,0 +1,8 @@
- Replaced the single user (so far defined in `/data/config.php`) with a multi-user management
- The currently defined user will automatically be migrated, please remove `HTTP_USER` and `HTTP_PASSWORD` from your config file afterwards
- For this it was necessary to delete all sessions and API keys during the migration
- Added an update script (`/update.sh`) to make updates (on Linux machines) easier
- See also ["How to update" in README](https://github.com/berrnd/grocy#how-to-update)
- Added the possibility to track who did a habit
- Added a rudimentary habit analysis possibility
- Different small UI, code and translation improvements

View File

@@ -0,0 +1 @@
- General improvements, the work goes on...

View File

@@ -0,0 +1,4 @@
- Basic product price tracking (can be entered on purchase, a little price history chart is shown in the product card - right side on purchase/consume/etc. pages)
- Proper pluralization of everything (for quantity units you can enter the plural form in master data)
- On all overview pages the statistics shown in the header are now updated when doing changes directly on the page (e. g. consuming a product)
- Lots of small fixes and improvements (form validation, translations - thanks for keeping the norwegian translation always updated @BlizzWave, other small bugs)

View File

@@ -0,0 +1,5 @@
- The complete row is now refreshed on changes on all overview pages
- Added a checkbox to set the "never expires date" in best before date inputs (alternative to shortcut "x")
- Recipes can now have arbitrary quantity units and stock is only checked for one unit then (imagine you have sugar in "Packs" in stock but your recipe "Pancakes" needs 200 grams)
- Added a "consume this recipe button" to remove all ingredients of a recipe from stock with one click
- Other small UI changes/improvements

View File

@@ -0,0 +1 @@
- Some smaller UI bug fixes and enhancements (thanks again for all the testing @BlizzWave)

View File

@@ -0,0 +1,6 @@
- New feature: Tasks / To-do list
- Renamed habits to chores as this is more what it is about
- Products can now be organized in product groups, this group is also used to group the items on the shopping list (you can use this to optimize your way in the supermarket for example)
- Added an option to stay logged in permanently (checkbox on the login page)
- When the database was changed externally, the current page is automatically reloaded when there was no input for at least 50 seconds
- Fixed some minor UI bugs

View File

@@ -0,0 +1,2 @@
- The colored info bars on top of all (overview)pages can now be clicked to filter the table accordingly
- Fixed some minor mostly UI related bugs

View File

@@ -0,0 +1,2 @@
- Important bug fix: All forms were submitted twice when using ENTER instead of the OK/Save button
- Norwegian translation updates (thanks @BlizzWave )

View File

@@ -0,0 +1,3 @@
- New optional "Night Mode" (thanks a lot @BlizzWave, can also be activated automatically by a time range - see the new dropdown menu next to the user menu)
- Docker support (thanks @talmai)
- Fixed some minor UI bugs

View File

@@ -0,0 +1,6 @@
- New feature: Equipment
- Manage all your household equipment/devices in one place and have the information/instruction manual at hand when needed
- New feature: Products can now have pictures
- Add them in the product edit page
- Will be shown in the productcard (purchase/consume/etc. pages) and when you click the product name on the stock overview page (a little image icon next to the product name indicates if the product has an image)
- Recipes and the new equipment edit page now have a little editor with text formatting capabilities

View File

@@ -0,0 +1,14 @@
- Added a journal for stock bookings, chore executions and battery charge cycles
- => Button in each line on the overview pages or the "Journal" button next to the headline on every overview page
- Added the possibility to undo any stock booking, chore execution and battery charge cycle
- => Button in the success popup while booking a purchase/consume/etc. or on the new journal pages (see above)
- Presets for new products are now configurable
- => "Presets for new products" button next to the headline on the products list page
- Recipes can now be nested (include a recipe into another one)
- Recipe ingredients can now be grouped together which will result in headlines per group in the rendered recipe
- => Group can be set on the recipe position edit page, demo recipe is "Pizza")
- On the stock overview page, the product card is now shown when clicking the product name
- Added option to filter by product group on stock overview page
- When auto reloading on external changes is enabled, the page is not reloaded when there is a fullscreen card active (recipe/equipment instruction manual)
- On the product-/chore-/batterycard there is now a link to the edit page of the corresponding item
- Some other minor bug fixes

View File

@@ -0,0 +1,18 @@
- New feature: "Shopping list to stock workflow"
- Add a single shopping list item or all at once to stock directly from the shopping list
- There are new "stock settings" under settings menu in the top right corner
- You can enable there, that all products which have "Default best before days" set, are added without confirmation in this workflow
- => This means, you can add the whole shopping list to stock with one click, if you want
- Improved stock handling
- On consume, a specific stock item can now be picked
- A stock item can now be marked as "opened" (on the consume page or directly from stock overview, visible in the product card and on the stock overview page)
- New feature: Calendar
- Shows all upcoming product expirations, due chores, due tasks and due battery charge cycles
- New translation: French (thanks all the translators)
- As for all languages, a demo is available at: https://demo-fr.grocy.info
- Small other improvements
- Allow fraction numbers for recipe ingredients when not checked against stock and add an option to not check stock for a recipe position
- The current time can now be shown in the header (see the settings menu next to the user icon)
- Changed: Docker related things are now in a separate repository: https://github.com/grocy/grocy-docker
- Changed: Translations are now managed with Transifex: https://www.transifex.com/grocy/grocy

View File

@@ -0,0 +1 @@
- Form validation and barcode input handling improvements

View File

@@ -0,0 +1,2 @@
- Added a skip button when adding all shopping list items in "Shopping list to stock workflow"
- Fixed some minor UI related bugs

View File

@@ -0,0 +1 @@
- All `config.php` settings can now also be set via environment variables (for [grocy-docker](https://github.com/grocy/grocy-docker))

View File

@@ -0,0 +1,5 @@
- Fixed a SQL error during database migration when using SQLite >= 3.25.2
- Improved data tables loading time
- Location edit form did not work (master data)
- Quantity unit "purchase to stock factor" was not respected when putting a recipe on the shopping list or when comparing the already on the shopping list amount
- Better API response for POST routes when there is no or invalid JSON request body content

View File

@@ -0,0 +1,23 @@
- Breaking change: The API has been completely reworked, please review [the documentation](https://demo-en.grocy.info/api) before updating when you are using the API
- New feature: Tare weight handling
- An option per product
- Imagine this: You have flour in jars, the jar weighs 500 grams, currently there are 1000 grams in stock, the new weight including the jar is 1100 grams - grocy can now calculate the used amount on consume/purchase/inventory automatically, you only have to enter the weighed amount including the jar (demo product to showcase this "Flour")
- Recipe improvements
- Recipes are now scalable - define per recipe for how much servings it is, change the desired servings on the fly when the recipe is displayed, ingredient amounts are scaled accordingly
- The cost of a recipe is now displayed based on the last purchase price per ingredient (recipe scaling also applies)
- When putting all missing recipe ingredients on the shopping list, it is now possible to ignore certain ingredients (in the popup when clicking the "Put missing items on shopping list" button)
- A new option per recipe to not check against the amount already on the shopping list when putting all missing ingredients on it (by default, only the amount not already on the shopping list is added, when this is enabled, always the whole missing amount will be put on the shopping list)
- On consume, there can now be tracked for which recipe it was, this is also tracked automatically when using the "Consume all ingredients needed by this recipe" button (for future statistical purposes)
- Recipes can now have pictures
- New "gallery view" for recipes (demo available at https://demo-en.grocy.info/recipes?tab=gallery)
- Stock improvements
- It is now optionally possible to have partial units in stock (option per product)
- On purchase, a different location can now be assigned (imagine you have two freezers, by default you store your pizza there, but sometimes there)
- New translations: (thanks all the translators)
- Spanish (demo available at https://demo-es.grocy.info)
- Turkish (demo available at https://demo-tr.grocy.info)
- Other improvements
- The calendar can now be shared/integrated in iCal format (button in the header on the calendar page)
- Added feature flags to hide/disable certain parts of grocy when you don't use them (for example hide "Chores" and all related UI elements, when you don't use it, see `config-dist.php`)
- Added a "Apple Touch Icon" and a "Web App Manifest" which should improve grocy on mobile devices and also enables "Add to Home screen" on major mobile browsers
- A lot of other minor small and bigger UI improvements

View File

@@ -0,0 +1,10 @@
- Some small UI fixes & improvements
- Recipe ingredient notes were not displayed
- Edit/delete buttons on the equipment page had no icons
- Improved the overview pages "action buttons column" (e. g. hide more rarely used actions behind a context/dropdown menu)
- The "purchase to stock conversion factor" is now displayed on the purchase page when QU units are different (above the amount field)
- Some JS files were not loaded correctly on case sensitive file systems
- The changelog is now included as markdown files (in `/changelog` directory, one file per release with a filename in format `<ReleaseNumber>_<Version>_<ReleaseDateIso>.md`) and shown in the about dialog
- Please review your `CURRENCY` setting in `data/config.php`, see also `config-dist.php` - this should be the ISO 4217 code of the currency to properly work with the JS `toLocaleString` function
- New translation: (thanks all the translators)
- Russian (demo available at https://demo-ru.grocy.info)

View File

@@ -0,0 +1,5 @@
- New API method to get a product by its barcode (`/stock/products/by-barcode/{barcode}`, thanks @matejdro)
- The best before date on the purchase and inventory page can now also be today or earlier, but when so, a short hint is displayed
- Fixed some UI bugs
- When consuming a product with "Allow partial units in stock" enabled from the stock overview page, the displayed amount after the stock booking was wrong
- The inventory form was not validated with certain click paths

View File

@@ -0,0 +1,17 @@
- Stock improvements
- A different location can now also be set during inventory (as for purchases)
- A partial minimum stock amount can now be set when "Allow partial units in stock" is enabled (product option)
- Recipe improvements
- There is now a default per product for "Disable stock fulfillment checking for this ingredient" (ingredient option, default can be defined as a product option)
- Some small UI fixes & improvements
- THe "Mark as open" button on the stock overview page was disabled when the current stock amount was exactly 1
- The number in the "x products expiring within the next 5 days" badge was incorrect for products expiring exactly in 5 days
- On the product groups page there is now a new column which displays the product count per group (+ a link to the products page filtered by that product group)
- Added a message to clarify that in product dropdowns also something unknown can be entered to start a workflow
- Some other small CSS fixes (context menus were not fully displayed when the parent container was to small, improved padding for text inputs)
- As always: Updated translations (thanks all the translators)
### Self promotion
[grocy-desktop](https://github.com/grocy/grocy-desktop) is now also available through the Microsoft Store
<a href="//www.microsoft.com/store/apps/9nwb1trnnksf?cid=storebadge&ocid=badge"><img src="https://assets.windowsphone.com/85864462-9c82-451e-9355-a3d5f874397a/English_get-it-from-MS_InvariantCulture_Default.png" alt="Get it from Microsoft" width="150px" /></a>

View File

@@ -0,0 +1,34 @@
- New feature: Userfields
- Attach any custom field to any entity (Products, Locations, Euqipment, etc.)
- Userfields can have types (Text, Number, Date, etc.)
- Will be shown / can be filled on the edit page of the corresponding entity and will also optionally show in the corresponding tables (inclcudes overview pages)
- => Can be configured under Master data / Userfields
- New feature: Meal planning
- Simple approach for the beginning (more to come): A week view where you can add recipes for each day (new menu entry in the sidebar, below calendar)
- Of course it's also possible to put missing things directly on the shopping list from there, also for a complete week at once
- General improvements
- The "expires soon" or "due soon" days (yelllow bar at the top of each overview page) can now be configured
- => New settings page for each area under the settings icon at the top right
- Stock improvements
- It's now possible to have multiple / named shopping lists
- Automations still use the default shopping list and also the default shopping list cannot be deleted
- More information on the product card like "Spoil rate" or "Average shelf life"
- It's now possible to set a price for added products during inventory
- It's now possible to customize the default amount for purchase/consume (see stock settings under the settings icon on the top right)
- Chores improvements
- New recurrence patterns - chores can now also be "scheduled" to repat daily/weekly/monthly
- It's now possible to track the day of a chore execution only (without the time, option per chore)
- Recipe improvements
- It's now possible to enter a "variable amount" (e. g. if a recipe needs "1 - 2 cups"), the original amount is still used for stock fulfillment checking (if enabled for that recipe ingredient)
- New translations: (thanks all the translators)
- Swedish (demo available at https://demo-sv.grocy.info)
- Polish (demo available at https://demo-pl.grocy.info)
- Internal improvement: Localizations are now handled via gettext, both on server and client side
- Mainly to properly handle languages with more than 2 plural forms
- This involved some string changes which results in a needed (re)translation of about 20 strings (excluding demo data)
- Also applies to quantity units, n-plural forms can be entered on the quantity unit edit page
- It's not required to install the PHP gettext extension, on both, server and client, managed implementations of gettext are used ([oscarotero/Gettext](https://github.com/oscarotero/Gettext) & [oscarotero/gettext-translator](https://github.com/oscarotero/gettext-translator))
- Some other small fixes and improvements
- The "Add as barcode to existing product" productpicker workflow failed to add the barcode to the given product
- Recipes can now be filter by stock availability
- Added a feature flag (`config.php` setting) to also be able to hide all stock related UI elements and routes

View File

@@ -0,0 +1,2 @@
- Fixed a performance problem for loading data tables related to the new Userfields feature
- Fixed that when using single quotes in a product name did not trigger the workflow popup

View File

@@ -0,0 +1,11 @@
- Fixed that deleting meal plan entries did not work
- Fixed a problem that the user settings were not properly initialized for the frontend JS part when not logged only (so potentially affected only the login page)
- Fixed an issue that the shopping list did not load when a plural translation for a quantity unit was missing
- Fixed that tooltips were visible forever when consuming all products on the stock overview page
- Fixed that login did not work when "Stay logged in permanently" was set and grocy runs on a 32-bit system (thanks @matejdro)
- Fixed page reloads when "Auto reload on external changes" is enabled and there is unsaved form data (the detection did not work for forms in modal dialogs, e. g. when adding a entry to the meal plan)
- Fixed (again) that the product picker did not work properly when the product name contains single quotes
- Fixed that a entered barcode on the product edit page was only saved when "adding" it to the barcodes list by pressing `TAB` (is now automatically added to the list also when just leaving the field)
- Improved that errors/messages from the API are shown properly when undoing a stock booking is not possible (stock journal page)
- Improved night mode CSS (done by @BlizzWave, thanks!)
- A new localization for `en_GB` is now always included - nothing is really translated there, it's only about component "translations" that e. g. the first day of the week is correct for calendars

View File

@@ -0,0 +1 @@
- Add possibility to have multiple barcodes per product

View File

@@ -0,0 +1,16 @@
- Fixed the messed up message/toast after consuming a product from the stock overview page
- Fixed that "Track date only" chores were always tracked today, regardless of the given date
- Fixed that the "week costs" were wrong after removing a meal plan entry
- Fixed wrong recipes costs calculation with nested recipes when the base recipe servings are > 1 (also affected the meal plan when adding such a recipe there)
- Fixed consuming recipes did not consume ingredients of the nested recipes
- Improved recipes API - added new endpoints to get stock fulfillment information (thanks @Aerex)
- Improved date display for products that never expires (instead of "2999-12-31" now just "Never" will be shown)
- Improved date display for dates of today and no time (instead of the hours since midnight now just "Today" will be shown)
- Improved shopping list handling
- Items can now be switched between lists (there is a shopping list dropdown on the item edit page)
- Items can now be marked as "done" (new check mark button per item, when clicked, the item will be displayed greyed out, when clicked again the item will be displayed normally again)
- Improved that products can now also be consumed as spoiled from the stock overview page (option in the more/context menu per line)
- Added a "consume this recipe"-button to the meal plan (and also a button to consume all recipes for a whole week)
- Added the possibility to undo a task (new button per task, only visible when task is already completed) and also a corresponding API endpoint
- Added a new `config.php` setting `DISABLE_AUTH` to be able to disable authentication / the login screen, defaults to `false`
- Added a new `config.php` setting `CALENDAR_FIRST_DAY_OF_WEEK` to be able to change the first day of a week used for calendar views (meal plan for example) in the frontend, defaults to locale default

View File

@@ -0,0 +1 @@
- Ready to ERP your fridge!

View File

@@ -0,0 +1 @@
- Added flow to directly add products and barcodes from purchase and inventory view

View File

@@ -0,0 +1,2 @@
* New feature: Shopping list (which is also automatically filled based on defined min. stock amount)
* Small UI changes for better productivity

View File

@@ -0,0 +1 @@
- Added a flow to add a new product with prefilled barcode

View File

@@ -0,0 +1 @@
- Added a favicon and more productivity improvements

View File

@@ -1,10 +1,13 @@
{
"require": {
"php": ">=7.2",
"slim/slim": "^3.8",
"morris/lessql": "^0.3.4",
"slim/slim": "^3.12.1",
"morris/lessql": "^0.4.1",
"rubellum/slim-blade-view": "^0.1.1",
"tuupola/cors-middleware": "^0.7.0"
"tuupola/cors-middleware": "^0.9.4",
"eluceo/ical": "^0.15.0",
"erusev/parsedown": "^1.7.3",
"gettext/gettext": "^4.6.2"
},
"autoload": {
"psr-4": {

657
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,14 +21,20 @@ Setting('MODE', 'production');
# one of the other available localization files in the "/localization" directory
Setting('CULTURE', 'en');
# This is used to define the first day of a week for calendar views in the frontend,
# leave empty to use the locale default
# Needs to be a number where Sunday = 0, Monday = 1 and so forth
Setting('CALENDAR_FIRST_DAY_OF_WEEK', '');
# To keep it simple: grocy does not handle any currency conversions,
# this here is used to format all money values,
# so can be anything (e. g. "USD" OR "$", doesn't matter...)
Setting('CURRENCY', '$');
# so doesn't matter really matter, but should be the
# ISO 4217 code of the currency ("USD", "EUR", "GBP", etc.)
Setting('CURRENCY', 'USD');
# The base url of your installation,
# should be just "/" when running directly under the root of a (sub)domain
# or for example "https:/example.com/grocy" when using a subdirectory
# or for example "https://example.com/grocy" when using a subdirectory
Setting('BASE_URL', '/');
# The plugin to use for external barcode lookups,
@@ -40,6 +46,12 @@ Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
# set this to true
Setting('DISABLE_URL_REWRITING', false);
# Set this to true if you want to disable authentication / the login screen,
# places where user context is needed will then use the default (first existing) user
Setting('DISABLE_AUTH', false);
# Default user settings
# These settings can be changed per user, here the defaults
@@ -52,9 +64,23 @@ DefaultUserSetting('auto_night_mode_time_range_from', "20:00"); // Format HH:mm
DefaultUserSetting('auto_night_mode_time_range_to', "07:00"); // Format HH:mm
DefaultUserSetting('auto_night_mode_time_range_goes_over_midnight', true); // If the time range above goes over midnight
DefaultUserSetting('currently_inside_night_mode_range', false); // If we're currently inside of night mode time range (this is not user configurable, but stored as a user setting because it's evaluated client side to be able to use the client time instead of the maybe different server time)
# Stock settings
DefaultUserSetting('product_presets_location_id', -1); // Default location id for new products (-1 means no location is preset)
DefaultUserSetting('product_presets_product_group_id', -1); // Default product group id for new products (-1 means no product group is preset)
DefaultUserSetting('product_presets_qu_id', -1); // Default quantity unit id for new products (-1 means no quantity unit is preset)
DefaultUserSetting('stock_expring_soon_days', 5);
DefaultUserSetting('stock_default_purchase_amount', 0);
DefaultUserSetting('stock_default_consume_amount', 1);
# Chores settings
DefaultUserSetting('chores_due_soon_days', 5);
# Batteries settings
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
@@ -67,3 +93,20 @@ DefaultUserSetting('show_clock_in_header', false);
# Automatically do the booking using the last price and the amount
# of the shopping list item, if the product has "Default best before days" set
DefaultUserSetting('shopping_list_to_stock_workflow_auto_submit_when_prefilled', false);
# 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
# which you don't need to have a less cluttered UI
# (set the setting to "false" to disable the corresponding part, which should be self explanatory)
Setting('FEATURE_FLAG_STOCK', true);
Setting('FEATURE_FLAG_SHOPPINGLIST', true);
Setting('FEATURE_FLAG_RECIPES', true);
Setting('FEATURE_FLAG_CHORES', true);
Setting('FEATURE_FLAG_TASKS', true);
Setting('FEATURE_FLAG_BATTERIES', true);
Setting('FEATURE_FLAG_EQUIPMENT', true);
Setting('FEATURE_FLAG_CALENDAR', true);

View File

@@ -18,10 +18,14 @@ class BaseApiController extends BaseController
return json_encode($data);
}
protected function VoidApiActionResponse($response, $success = true, $status = 200, $errorMessage = '')
protected function EmptyApiResponse($response, $status = 204)
{
return $response->withStatus($status);
}
protected function GenericErrorResponse($response, $errorMessage, $status = 400)
{
return $response->withStatus($status)->withJson(array(
'success' => $success,
'error_message' => $errorMessage
));
}

View File

@@ -32,11 +32,16 @@ class BaseController
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
}
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
$container->view->set('__t', function(string $text, ...$placeholderValues) use($localizationService)
{
return $localizationService->Localize($text, ...$placeholderValues);
return $localizationService->__t($text, $placeholderValues);
});
$container->view->set('__n', function($number, $singularForm, $pluralForm) use($localizationService)
{
return $localizationService->__n($number, $singularForm, $pluralForm);
});
$container->view->set('GettextPo', $localizationService->GetPoAsJsonString());
$container->view->set('U', function($relativePath, $isResource = false) use($container)
{
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
@@ -49,6 +54,16 @@ class BaseController
}
$container->view->set('embedded', $embedded);
$constants = get_defined_constants();
foreach ($constants as $constant => $value)
{
if (substr($constant, 0, 19) !== 'GROCY_FEATURE_FLAG_')
{
unset($constants[$constant]);
}
}
$container->view->set('featureFlags', $constants);
try
{
$usersService = new UsersService();
@@ -56,6 +71,10 @@ class BaseController
{
$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID));
}
else
{
$container->view->set('userSettings', null);
}
}
catch (\Exception $ex)
{

View File

@@ -16,20 +16,22 @@ class BatteriesApiController extends BaseApiController
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$trackedTime = date('Y-m-d H:i:s');
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
{
$trackedTime = $request->getQueryParams()['tracked_time'];
}
$requestBody = $request->getParsedBody();
try
{
$trackedTime = date('Y-m-d H:i:s');
if (array_key_exists('tracked_time', $requestBody) && IsIsoDateTime($requestBody['tracked_time']))
{
$trackedTime = $requestBody['tracked_time'];
}
$chargeCycleId = $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
return $this->ApiResponse(array('charge_cycle_id' => $chargeCycleId));
return $this->ApiResponse($this->Database->battery_charge_cycles($chargeCycleId));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -41,7 +43,7 @@ class BatteriesApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -55,11 +57,11 @@ class BatteriesApiController extends BaseApiController
try
{
$this->ApiResponse($this->BatteriesService->UndoChargeCycle($args['chargeCycleId']));
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -3,6 +3,8 @@
namespace Grocy\Controllers;
use \Grocy\Services\BatteriesService;
use \Grocy\Services\UsersService;
use \Grocy\Services\UserfieldsService;
class BatteriesController extends BaseController
{
@@ -10,16 +12,23 @@ class BatteriesController extends BaseController
{
parent::__construct($container);
$this->BatteriesService = new BatteriesService();
$this->UserfieldsService = new UserfieldsService();
}
protected $BatteriesService;
protected $UserfieldsService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$usersService = new UsersService();
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['batteries_due_soon_days'];
return $this->AppContainer->view->render($response, 'batteriesoverview', [
'batteries' => $this->Database->batteries()->orderBy('name'),
'current' => $this->BatteriesService->GetCurrent(),
'nextXDays' => 5
'nextXDays' => $nextXDays,
'userfields' => $this->UserfieldsService->GetFields('batteries'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('batteries')
]);
}
@@ -33,7 +42,9 @@ class BatteriesController extends BaseController
public function BatteriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'batteries', [
'batteries' => $this->Database->batteries()->orderBy('name')
'batteries' => $this->Database->batteries()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('batteries'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('batteries')
]);
}
@@ -42,14 +53,16 @@ class BatteriesController extends BaseController
if ($args['batteryId'] == 'new')
{
return $this->AppContainer->view->render($response, 'batteryform', [
'mode' => 'create'
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('batteries')
]);
}
else
{
return $this->AppContainer->view->render($response, 'batteryform', [
'battery' => $this->Database->batteries($args['batteryId']),
'mode' => 'edit'
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('batteries')
]);
}
}
@@ -61,4 +74,9 @@ class BatteriesController extends BaseController
'batteries' => $this->Database->batteries()->orderBy('name')
]);
}
public function BatteriesSettings(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'batteriessettings');
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\CalendarService;
use \Grocy\Services\ApiKeyService;
class CalendarApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->CalendarService = new CalendarService();
$this->ApiKeyService = new ApiKeyService();
}
protected $CalendarService;
protected $ApiKeyService;
public function Ical(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$vCalendar = new \Eluceo\iCal\Component\Calendar('grocy');
$events = $this->CalendarService->GetEvents();
foreach($events as $event)
{
$vEvent = new \Eluceo\iCal\Component\Event();
$vEvent->setDtStart(new \DateTime($event['start']))
->setDtEnd(new \DateTime($event['start']))
->setSummary($event['title'])
->setNoTime($event['date_format'] === 'date')
->setUseUtc(false);
$vCalendar->addComponent($vEvent);
}
$response->write($vCalendar->render());
$response = $response->withHeader('Content-Type', 'text/calendar; charset=utf-8');
return $response->withHeader('Content-Disposition', 'attachment; filename="grocy.ics"');
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function IcalSharingLink(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse(array(
'url' => $this->AppContainer->UrlManager->ConstructUrl('/api/calendar/ical?secret=' . $this->ApiKeyService->GetOrCreateApiKey(ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL))
));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -2,80 +2,22 @@
namespace Grocy\Controllers;
use \Grocy\Services\StockService;
use \Grocy\Services\TasksService;
use \Grocy\Services\ChoresService;
use \Grocy\Services\BatteriesService;
use \Grocy\Services\CalendarService;
class CalendarController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->StockService = new StockService();
$this->TasksService = new TasksService();
$this->ChoresService = new ChoresService();
$this->BatteriesService = new BatteriesService();
$this->CalendarService = new CalendarService();
}
protected $StockService;
protected $TasksService;
protected $ChoresService;
protected $BatteriesService;
protected $CalendarService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$products = $this->Database->products();
$titlePrefix = $this->LocalizationService->Localize('Product expires') . ': ';
$stockEvents = array();
foreach($this->StockService->GetCurrentStock() as $currentStockEntry)
{
$stockEvents[] = array(
'title' => $titlePrefix . FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name,
'start' => $currentStockEntry->best_before_date
);
}
$titlePrefix = $this->LocalizationService->Localize('Task due') . ': ';
$taskEvents = array();
foreach($this->TasksService->GetCurrent() as $currentTaskEntry)
{
$taskEvents[] = array(
'title' => $titlePrefix . $currentTaskEntry->name,
'start' => $currentTaskEntry->due_date
);
}
$chores = $this->Database->chores();
$titlePrefix = $this->LocalizationService->Localize('Chore due') . ': ';
$choreEvents = array();
foreach($this->ChoresService->GetCurrent() as $currentChoreEntry)
{
$choreEvents[] = array(
'title' => $titlePrefix . FindObjectInArrayByPropertyValue($chores, 'id', $currentChoreEntry->chore_id)->name,
'start' => $currentChoreEntry->next_estimated_execution_time
);
}
$batteries = $this->Database->batteries();
$titlePrefix = $this->LocalizationService->Localize('Battery charge cycle due') . ': ';
$batteryEvents = array();
foreach($this->BatteriesService->GetCurrent() as $currentBatteryEntry)
{
$batteryEvents[] = array(
'title' => $titlePrefix . FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name,
'start' => $currentBatteryEntry->next_estimated_charge_time
);
}
$fullcalendarEventSources = array();
$fullcalendarEventSources[] = $stockEvents;
$fullcalendarEventSources[] = $taskEvents;
$fullcalendarEventSources[] = $choreEvents;
$fullcalendarEventSources[] = $batteryEvents;
return $this->AppContainer->view->render($response, 'calendar', [
'fullcalendarEventSources' => $fullcalendarEventSources
'fullcalendarEventSources' => $this->CalendarService->GetEvents()
]);
}
}

View File

@@ -16,26 +16,28 @@ class ChoresApiController extends BaseApiController
public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$trackedTime = date('Y-m-d H:i:s');
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
{
$trackedTime = $request->getQueryParams()['tracked_time'];
}
$doneBy = GROCY_USER_ID;
if (isset($request->getQueryParams()['done_by']) && !empty($request->getQueryParams()['done_by']))
{
$doneBy = $request->getQueryParams()['done_by'];
}
$requestBody = $request->getParsedBody();
try
{
$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'];
}
$choreExecutionId = $this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->ApiResponse(array('chore_execution_id' => $choreExecutionId));
return $this->ApiResponse($this->Database->chores_log($choreExecutionId));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -47,7 +49,7 @@ class ChoresApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -61,11 +63,11 @@ class ChoresApiController extends BaseApiController
try
{
$this->ApiResponse($this->ChoresService->UndoChoreExecution($args['executionId']));
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -3,6 +3,8 @@
namespace Grocy\Controllers;
use \Grocy\Services\ChoresService;
use \Grocy\Services\UsersService;
use \Grocy\Services\UserfieldsService;
class ChoresController extends BaseController
{
@@ -10,16 +12,23 @@ class ChoresController extends BaseController
{
parent::__construct($container);
$this->ChoresService = new ChoresService();
$this->UserfieldsService = new UserfieldsService();
}
protected $ChoresService;
protected $UserfieldsService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$usersService = new UsersService();
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['chores_due_soon_days'];
return $this->AppContainer->view->render($response, 'choresoverview', [
'chores' => $this->Database->chores()->orderBy('name'),
'currentChores' => $this->ChoresService->GetCurrent(),
'nextXDays' => 5
'nextXDays' => $nextXDays,
'userfields' => $this->UserfieldsService->GetFields('chores'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('chores')
]);
}
@@ -34,7 +43,9 @@ class ChoresController extends BaseController
public function ChoresList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'chores', [
'chores' => $this->Database->chores()->orderBy('name')
'chores' => $this->Database->chores()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('chores'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('chores')
]);
}
@@ -53,7 +64,8 @@ class ChoresController extends BaseController
{
return $this->AppContainer->view->render($response, 'choreform', [
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'mode' => 'create'
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('chores')
]);
}
else
@@ -61,8 +73,14 @@ class ChoresController extends BaseController
return $this->AppContainer->view->render($response, 'choreform', [
'chore' => $this->Database->chores($args['choreId']),
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'mode' => 'edit'
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('chores')
]);
}
}
public function ChoresSettings(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choressettings');
}
}

View File

@@ -2,13 +2,24 @@
namespace Grocy\Controllers;
use \Grocy\Services\UserfieldsService;
class EquipmentController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->UserfieldsService = new UserfieldsService();
}
protected $UserfieldsService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'equipment', [
'equipment' => $this->Database->equipment()->orderBy('name')
'equipment' => $this->Database->equipment()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('equipment'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('equipment')
]);
}
@@ -17,14 +28,16 @@ class EquipmentController extends BaseController
if ($args['equipmentId'] == 'new')
{
return $this->AppContainer->view->render($response, 'equipmentform', [
'mode' => 'create'
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('equipment')
]);
}
else
{
return $this->AppContainer->view->render($response, 'equipmentform', [
'equipment' => $this->Database->equipment($args['equipmentId']),
'mode' => 'edit'
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('equipment')
]);
}
}

View File

@@ -18,23 +18,23 @@ class FilesApiController extends BaseApiController
{
try
{
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = $request->getQueryParams()['file_name'];
$fileName = base64_decode($args['fileName']);
}
else
{
throw new \Exception('file_name query parameter missing or contains an invalid filename');
throw new \Exception('Invalid filename');
}
$data = $request->getBody()->getContents();
file_put_contents($this->FilesService->GetFilePath($args['group'], $fileName), $data);
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -42,13 +42,13 @@ class FilesApiController extends BaseApiController
{
try
{
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = $request->getQueryParams()['file_name'];
$fileName = base64_decode($args['fileName']);
}
else
{
throw new \Exception('file_name query parameter missing or contains an invalid filename');
throw new \Exception('Invalid filename');
}
$filePath = $this->FilesService->GetFilePath($args['group'], $fileName);
@@ -61,12 +61,12 @@ class FilesApiController extends BaseApiController
}
else
{
return $this->VoidApiActionResponse($response, false, 404, 'File not found');
return $this->GenericErrorResponse($response, 'File not found', 404);
}
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -74,13 +74,13 @@ class FilesApiController extends BaseApiController
{
try
{
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = $request->getQueryParams()['file_name'];
$fileName = base64_decode($args['fileName']);
}
else
{
throw new \Exception('file_name query parameter missing or contains an invalid filename');
throw new \Exception('Invalid filename');
}
$filePath = $this->FilesService->GetFilePath($args['group'], $fileName);
@@ -89,11 +89,11 @@ class FilesApiController extends BaseApiController
unlink($filePath);
}
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -2,8 +2,18 @@
namespace Grocy\Controllers;
use \Grocy\Services\UserfieldsService;
class GenericEntityApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->UserfieldsService = new UserfieldsService();
}
protected $UserfieldsService;
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
@@ -12,7 +22,7 @@ class GenericEntityApiController extends BaseApiController
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
}
@@ -24,7 +34,7 @@ class GenericEntityApiController extends BaseApiController
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
}
@@ -32,14 +42,30 @@ class GenericEntityApiController extends BaseApiController
{
if ($this->IsValidEntity($args['entity']))
{
$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody());
$newRow->save();
$success = $newRow->isClean();
return $this->ApiResponse(array('success' => $success));
$requestBody = $request->getParsedBody();
try
{
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
$newRow = $this->Database->{$args['entity']}()->createRow($requestBody);
$newRow->save();
$success = $newRow->isClean();
return $this->ApiResponse(array(
'created_object_id' => $this->Database->lastInsertId()
));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
}
@@ -47,14 +73,28 @@ class GenericEntityApiController extends BaseApiController
{
if ($this->IsValidEntity($args['entity']))
{
$row = $this->Database->{$args['entity']}($args['objectId']);
$row->update($request->getParsedBody());
$success = $row->isClean();
return $this->ApiResponse(array('success' => $success));
$requestBody = $request->getParsedBody();
try
{
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
$row = $this->Database->{$args['entity']}($args['objectId']);
$row->update($requestBody);
$success = $row->isClean();
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
}
@@ -65,11 +105,43 @@ class GenericEntityApiController extends BaseApiController
$row = $this->Database->{$args['entity']}($args['objectId']);
$row->delete();
$success = $row->isClean();
return $this->ApiResponse(array('success' => $success));
return $this->EmptyApiResponse($response);
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function GetUserfields(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->UserfieldsService->GetValues($args['entity'], $args['objectId']));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function SetUserfields(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();
try
{
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
$this->UserfieldsService->SetValues($args['entity'], $args['objectId'], $requestBody);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\UserfieldsService;
class GenericEntityController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->UserfieldsService = new UserfieldsService();
}
protected $UserfieldsService;
public function UserfieldsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'userfields', [
'userfields' => $this->UserfieldsService->GetAllFields(),
'entities' => $this->UserfieldsService->GetEntities()
]);
}
public function UserfieldEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['userfieldId'] == 'new')
{
return $this->AppContainer->view->render($response, 'userfieldform', [
'mode' => 'create',
'userfieldTypes' => $this->UserfieldsService->GetFieldTypes(),
'entities' => $this->UserfieldsService->GetEntities()
]);
}
else
{
return $this->AppContainer->view->render($response, 'userfieldform', [
'mode' => 'edit',
'userfield' => $this->UserfieldsService->GetField($args['userfieldId']),
'userfieldTypes' => $this->UserfieldsService->GetFieldTypes(),
'entities' => $this->UserfieldsService->GetEntities()
]);
}
}
}

View File

@@ -30,7 +30,7 @@ class LoginController extends BaseController
if ($user !== null && password_verify($inputPassword, $user->password))
{
$sessionKey = $this->SessionService->CreateSession($user->id, $stayLoggedInPermanently);
setcookie($this->SessionCookieName, $sessionKey, intval(time() + 31220640000)); // Cookie expires in 999 years, but session validity is up to SessionService
setcookie($this->SessionCookieName, $sessionKey, PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX>>32); // Cookie expires never, but session validity is up to SessionService
if (password_needs_rehash($user->password, PASSWORD_DEFAULT))
{
@@ -63,21 +63,6 @@ class LoginController extends BaseController
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
}
public function Root(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
// Schema migration is done here
$databaseMigrationService = new DatabaseMigrationService();
$databaseMigrationService->MigrateDatabase();
if (GROCY_IS_DEMO_INSTALL)
{
$demoDataGeneratorService = new DemoDataGeneratorService();
$demoDataGeneratorService->PopulateDemoData();
}
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/stockoverview'));
}
public function GetSessionCookieName()
{
return $this->SessionCookieName;

View File

@@ -1,35 +1,68 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\RecipesService;
class RecipesApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->RecipesService = new RecipesService();
}
protected $RecipesService;
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId']);
return $this->VoidApiActionResponse($response);
}
public function ConsumeRecipe(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->RecipesService->ConsumeRecipe($args['recipeId']);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
}
<?php
namespace Grocy\Controllers;
use \Grocy\Services\RecipesService;
class RecipesApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->RecipesService = new RecipesService();
}
protected $RecipesService;
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();
$excludedProductIds = null;
if ($requestBody !== null && array_key_exists('excludedProductIds', $requestBody))
{
$excludedProductIds = $requestBody['excludedProductIds'];
}
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId'], $excludedProductIds);
return $this->EmptyApiResponse($response);
}
public function ConsumeRecipe(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->RecipesService->ConsumeRecipe($args['recipeId']);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function GetRecipeFulfillment(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
if(!isset($args['recipeId']))
{
return $this->ApiResponse($this->RecipesService->GetRecipesResolved());
}
$recipeResolved = FindObjectInArrayByPropertyValue($this->RecipesService->GetRecipesResolved(), 'recipe_id', $args['recipeId']);
if(!$recipeResolved)
{
throw new \Exception('Recipe does not exist');
}
else
{
return $this->ApiResponse($recipeResolved);
}
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -3,6 +3,7 @@
namespace Grocy\Controllers;
use \Grocy\Services\RecipesService;
use \Grocy\Services\UserfieldsService;
class RecipesController extends BaseController
{
@@ -10,44 +11,65 @@ class RecipesController extends BaseController
{
parent::__construct($container);
$this->RecipesService = new RecipesService();
$this->UserfieldsService = new UserfieldsService();
}
protected $RecipesService;
protected $UserfieldsService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$recipes = $this->Database->recipes()->orderBy('name');
if (isset($request->getQueryParams()['include-internal']))
{
$recipes = $this->Database->recipes()->orderBy('name');
}
else
{
$recipes = $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name');
}
$recipesResolved = $this->RecipesService->GetRecipesResolved();
$selectedRecipe = null;
$selectedRecipePositions = null;
$selectedRecipePositionsResolved = null;
if (isset($request->getQueryParams()['recipe']))
{
$selectedRecipe = $this->Database->recipes($request->getQueryParams()['recipe']);
$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $request->getQueryParams()['recipe'])->orderBy('ingredient_group');
$selectedRecipePositionsResolved = $this->Database->recipes_pos_resolved()->where('recipe_id', $request->getQueryParams()['recipe'])->orderBy('ingredient_group');
}
else
{
foreach ($recipes as $recipe)
{
$selectedRecipe = $recipe;
$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $recipe->id)->orderBy('ingredient_group');
$selectedRecipePositionsResolved = $this->Database->recipes_pos_resolved()->where('recipe_id', $recipe->id)->orderBy('ingredient_group');
break;
}
}
$selectedRecipeSubRecipes = $this->Database->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name')->fetchAll();
$selectedRecipeSubRecipesPositions = $this->Database->recipes_pos()->where('recipe_id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('ingredient_group')->fetchAll();
$selectedRecipeSubRecipesPositions = $this->Database->recipes_pos_resolved()->where('recipe_id = :1', $selectedRecipe->id)->orderBy('ingredient_group')->fetchAll();
$includedRecipeIdsAbsolute = array();
$includedRecipeIdsAbsolute[] = $selectedRecipe->id;
foreach($selectedRecipeSubRecipes as $subRecipe)
{
$includedRecipeIdsAbsolute[] = $subRecipe->id;
}
return $this->AppContainer->view->render($response, 'recipes', [
'recipes' => $recipes,
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(),
'recipesResolved' => $recipesResolved,
'recipePositionsResolved' => $this->Database->recipes_pos_resolved(),
'selectedRecipe' => $selectedRecipe,
'selectedRecipePositions' => $selectedRecipePositions,
'selectedRecipePositionsResolved' => $selectedRecipePositionsResolved,
'products' => $this->Database->products(),
'quantityunits' => $this->Database->quantity_units(),
'selectedRecipeSubRecipes' => $selectedRecipeSubRecipes,
'selectedRecipeSubRecipesPositions' => $selectedRecipeSubRecipesPositions
'selectedRecipeSubRecipesPositions' => $selectedRecipeSubRecipesPositions,
'includedRecipeIdsAbsolute' => $includedRecipeIdsAbsolute,
'selectedRecipeTotalCosts' => FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->costs,
'userfields' => $this->UserfieldsService->GetFields('recipes'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('recipes')
]);
}
@@ -56,8 +78,8 @@ class RecipesController extends BaseController
$recipeId = $args['recipeId'];
if ($recipeId == 'new')
{
$newRecipe = $this->Database->recipes()->createRow(array(
'name' => $this->LocalizationService->Localize('New recipe')
$newRecipe = $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->createRow(array(
'name' => $this->LocalizationService->__t('New recipe')
));
$newRecipe->save();
@@ -70,10 +92,11 @@ class RecipesController extends BaseController
'mode' => 'edit',
'products' => $this->Database->products(),
'quantityunits' => $this->Database->quantity_units(),
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(),
'recipes' => $this->Database->recipes()->orderBy('name'),
'recipeNestings' => $this->Database->recipes_nestings()->where('recipe_id', $recipeId)
'recipePositionsResolved' => $this->RecipesService->GetRecipesPosResolved(),
'recipesResolved' => $this->RecipesService->GetRecipesResolved(),
'recipes' => $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name'),
'recipeNestings' => $this->Database->recipes_nestings()->where('recipe_id', $recipeId),
'userfields' => $this->UserfieldsService->GetFields('recipes')
]);
}
@@ -99,4 +122,29 @@ class RecipesController extends BaseController
]);
}
}
public function MealPlan(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$recipes = $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
$events = array();
foreach($this->Database->meal_plan() as $mealPlanEntry)
{
$events[] = array(
'id' => $mealPlanEntry['id'],
'title' => FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id'])->name,
'start' => $mealPlanEntry['day'],
'date_format' => 'date',
'recipe' => json_encode(FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id'])),
'mealPlanEntry' => json_encode($mealPlanEntry)
);
}
return $this->AppContainer->view->render($response, 'mealplan', [
'fullcalendarEventSources' => $events,
'recipes' => $recipes,
'internalRecipes' => $this->Database->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(),
'recipesResolved' => $this->RecipesService->GetRecipesResolved()
]);
}
}

View File

@@ -22,7 +22,20 @@ class StockApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function ProductDetailsByBarcode(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$productId = $this->StockService->GetProductIdFromBarcode($args['barcode']);
return $this->ApiResponse($this->StockService->GetProductDetails($productId));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -34,107 +47,179 @@ class StockApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$bestBeforeDate = date('Y-m-d');
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']) && IsIsoDate($request->getQueryParams()['bestbeforedate']))
{
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
}
$price = null;
if (isset($request->getQueryParams()['price']) && !empty($request->getQueryParams()['price']) && is_numeric($request->getQueryParams()['price']))
{
$price = $request->getQueryParams()['price'];
}
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
{
$transactionType = $request->getQueryParams()['transactiontype'];
}
$requestBody = $request->getParsedBody();
try
{
$bookingId = $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
return $this->ApiResponse(array('booking_id' => $bookingId));
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
if (!array_key_exists('amount', $requestBody))
{
throw new \Exception('An amount is required');
}
$bestBeforeDate = date('Y-m-d');
if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date']))
{
$bestBeforeDate = $requestBody['best_before_date'];
}
$price = null;
if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price']))
{
$price = $requestBody['price'];
}
$locationId = null;
if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id']))
{
$locationId = $requestBody['location_id'];
}
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
{
$transactionType = $requestBody['transactiontype'];
}
$bookingId = $this->StockService->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price, $locationId);
return $this->ApiResponse($this->Database->stock_log($bookingId));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$spoiled = false;
if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1')
{
$spoiled = true;
}
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
{
$transactionType = $request->getQueryParams()['transactiontype'];
}
$specificStockEntryId = "default";
if (isset($request->getQueryParams()['stock_entry_id']) && !empty($request->getQueryParams()['stock_entry_id']))
{
$specificStockEntryId = $request->getQueryParams()['stock_entry_id'];
}
$requestBody = $request->getParsedBody();
try
{
$bookingId = $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType, $specificStockEntryId);
return $this->ApiResponse(array('booking_id' => $bookingId));
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
if (!array_key_exists('amount', $requestBody))
{
throw new \Exception('An amount is required');
}
$spoiled = false;
if (array_key_exists('spoiled', $requestBody))
{
$spoiled = $requestBody['spoiled'];
}
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
{
$transactionType = $requestBody['transactiontype'];
}
$specificStockEntryId = 'default';
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
{
$specificStockEntryId = $requestBody['stock_entry_id'];
}
$recipeId = null;
if (array_key_exists('recipe_id', $requestBody) && is_numeric($requestBody['recipe_id']))
{
$recipeId = $requestBody['recipe_id'];
}
$bookingId = $this->StockService->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId);
return $this->ApiResponse($this->Database->stock_log($bookingId));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$bestBeforeDate = date('Y-m-d');
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']) && IsIsoDate($request->getQueryParams()['bestbeforedate']))
{
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
}
$requestBody = $request->getParsedBody();
try
{
$bookingId = $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
return $this->ApiResponse(array('booking_id' => $bookingId));
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
if (!array_key_exists('new_amount', $requestBody))
{
throw new \Exception('An new amount is required');
}
$bestBeforeDate = date('Y-m-d');
if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date']))
{
$bestBeforeDate = $requestBody['best_before_date'];
}
$locationId = null;
if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id']))
{
$locationId = $requestBody['location_id'];
}
$price = null;
if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price']))
{
$price = $requestBody['price'];
}
$bookingId = $this->StockService->InventoryProduct($args['productId'], $requestBody['new_amount'], $bestBeforeDate, $locationId, $price);
return $this->ApiResponse($this->Database->stock_log($bookingId));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function OpenProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$specificStockEntryId = "default";
if (isset($request->getQueryParams()['stock_entry_id']) && !empty($request->getQueryParams()['stock_entry_id']))
{
$specificStockEntryId = $request->getQueryParams()['stock_entry_id'];
}
$requestBody = $request->getParsedBody();
try
{
$bookingId = $this->StockService->OpenProduct($args['productId'], $args['amount'], $specificStockEntryId);
return $this->ApiResponse(array('booking_id' => $bookingId));
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
if (!array_key_exists('amount', $requestBody))
{
throw new \Exception('An amount is required');
}
$specificStockEntryId = 'default';
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
{
$specificStockEntryId = $requestBody['stock_entry_id'];
}
$bookingId = $this->StockService->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId);
return $this->ApiResponse($this->Database->stock_log($bookingId));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -163,14 +248,44 @@ class StockApiController extends BaseApiController
public function AddMissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->StockService->AddMissingProductsToShoppingList();
return $this->VoidApiActionResponse($response);
try
{
$requestBody = $request->getParsedBody();
$listId = 1;
if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id']))
{
$listId = intval($requestBody['list_id']);
}
$this->StockService->AddMissingProductsToShoppingList($listId);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function ClearShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->StockService->ClearShoppingList();
return $this->VoidApiActionResponse($response);
try
{
$requestBody = $request->getParsedBody();
$listId = 1;
if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id']))
{
$listId = intval($requestBody['list_id']);
}
$this->StockService->ClearShoppingList($listId);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function ExternalBarcodeLookup(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
@@ -187,7 +302,7 @@ class StockApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -196,11 +311,11 @@ class StockApiController extends BaseApiController
try
{
$this->ApiResponse($this->StockService->UndoBooking($args['bookingId']));
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}

View File

@@ -3,6 +3,8 @@
namespace Grocy\Controllers;
use \Grocy\Services\StockService;
use \Grocy\Services\UsersService;
use \Grocy\Services\UserfieldsService;
class StockController extends BaseController
{
@@ -11,52 +13,71 @@ class StockController extends BaseController
{
parent::__construct($container);
$this->StockService = new StockService();
$this->UserfieldsService = new UserfieldsService();
}
protected $StockService;
protected $UserfieldsService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$usersService = new UsersService();
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expring_soon_days'];
return $this->AppContainer->view->render($response, 'stockoverview', [
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'locations' => $this->Database->locations()->orderBy('name'),
'currentStock' => $this->StockService->GetCurrentStock(),
'currentStockLocations' => $this->StockService->GetCurrentStockLocations(),
'missingProducts' => $this->StockService->GetMissingProducts(),
'nextXDays' => 5,
'productGroups' => $this->Database->product_groups()->orderBy('name')
'nextXDays' => $nextXDays,
'productGroups' => $this->Database->product_groups()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('products'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('products')
]);
}
public function Purchase(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'purchase', [
'products' => $this->Database->products()->orderBy('name')
'products' => $this->Database->products()->orderBy('name'),
'locations' => $this->Database->locations()->orderBy('name')
]);
}
public function Consume(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'consume', [
'products' => $this->Database->products()->orderBy('name')
'products' => $this->Database->products()->orderBy('name'),
'recipes' => $this->Database->recipes()->orderBy('name')
]);
}
public function Inventory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'inventory', [
'products' => $this->Database->products()->orderBy('name')
'products' => $this->Database->products()->orderBy('name'),
'locations' => $this->Database->locations()->orderBy('name')
]);
}
public function ShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$listId = 1;
if (isset($request->getQueryParams()['list']))
{
$listId = $request->getQueryParams()['list'];
}
return $this->AppContainer->view->render($response, 'shoppinglist', [
'listItems' => $this->Database->shopping_list(),
'listItems' => $this->Database->shopping_list()->where('shopping_list_id = :1', $listId),
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'missingProducts' => $this->StockService->GetMissingProducts(),
'productGroups' => $this->Database->product_groups()->orderBy('name')
'productGroups' => $this->Database->product_groups()->orderBy('name'),
'shoppingLists' => $this->Database->shopping_lists()->orderBy('name'),
'selectedShoppingListId' => $listId
]);
}
@@ -66,7 +87,9 @@ class StockController extends BaseController
'products' => $this->Database->products()->orderBy('name'),
'locations' => $this->Database->locations()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'productGroups' => $this->Database->product_groups()->orderBy('name')
'productGroups' => $this->Database->product_groups()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('products'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('products')
]);
}
@@ -82,21 +105,28 @@ class StockController extends BaseController
public function LocationsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'locations', [
'locations' => $this->Database->locations()->orderBy('name')
'locations' => $this->Database->locations()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('locations'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('locations')
]);
}
public function ProductGroupsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'productgroups', [
'productGroups' => $this->Database->product_groups()->orderBy('name')
'productGroups' => $this->Database->product_groups()->orderBy('name'),
'products' => $this->Database->products()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('product_groups'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('product_groups')
]);
}
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'quantityunits', [
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('quantity_units'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('quantity_units')
]);
}
@@ -108,6 +138,7 @@ class StockController extends BaseController
'locations' => $this->Database->locations()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'productgroups' => $this->Database->product_groups()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('products'),
'mode' => 'create'
]);
}
@@ -118,6 +149,7 @@ class StockController extends BaseController
'locations' => $this->Database->locations()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'productgroups' => $this->Database->product_groups()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('products'),
'mode' => 'edit'
]);
}
@@ -128,14 +160,16 @@ class StockController extends BaseController
if ($args['locationId'] == 'new')
{
return $this->AppContainer->view->render($response, 'locationform', [
'mode' => 'create'
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('locations')
]);
}
else
{
return $this->AppContainer->view->render($response, 'locationform', [
'location' => $this->Database->locations($args['locationId']),
'mode' => 'edit'
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('locations')
]);
}
}
@@ -145,14 +179,16 @@ class StockController extends BaseController
if ($args['productGroupId'] == 'new')
{
return $this->AppContainer->view->render($response, 'productgroupform', [
'mode' => 'create'
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('product_groups')
]);
}
else
{
return $this->AppContainer->view->render($response, 'productgroupform', [
'group' => $this->Database->product_groups($args['productGroupId']),
'mode' => 'edit'
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('product_groups')
]);
}
}
@@ -162,14 +198,20 @@ class StockController extends BaseController
if ($args['quantityunitId'] == 'new')
{
return $this->AppContainer->view->render($response, 'quantityunitform', [
'mode' => 'create'
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('quantity_units'),
'pluralCount' => $this->LocalizationService->GetPluralCount(),
'pluralRule' => $this->LocalizationService->GetPluralDefinition()
]);
}
else
{
return $this->AppContainer->view->render($response, 'quantityunitform', [
'quantityunit' => $this->Database->quantity_units($args['quantityunitId']),
'mode' => 'edit'
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('quantity_units'),
'pluralCount' => $this->LocalizationService->GetPluralCount(),
'pluralRule' => $this->LocalizationService->GetPluralDefinition()
]);
}
}
@@ -178,16 +220,35 @@ class StockController extends BaseController
{
if ($args['itemId'] == 'new')
{
return $this->AppContainer->view->render($response, 'shoppinglistform', [
return $this->AppContainer->view->render($response, 'shoppinglistitemform', [
'products' => $this->Database->products()->orderBy('name'),
'shoppingLists' => $this->Database->shopping_lists()->orderBy('name'),
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'shoppinglistitemform', [
'listItem' => $this->Database->shopping_list($args['itemId']),
'products' => $this->Database->products()->orderBy('name'),
'shoppingLists' => $this->Database->shopping_lists()->orderBy('name'),
'mode' => 'edit'
]);
}
}
public function ShoppingListEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['listId'] == 'new')
{
return $this->AppContainer->view->render($response, 'shoppinglistform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'shoppinglistform', [
'listItem' => $this->Database->shopping_list($args['itemId']),
'products' => $this->Database->products()->orderBy('name'),
'shoppingList' => $this->Database->shopping_lists($args['listId']),
'mode' => 'edit'
]);
}

View File

@@ -3,6 +3,7 @@
namespace Grocy\Controllers;
use \Grocy\Services\DatabaseService;
use \Grocy\Services\ApplicationService;
class SystemApiController extends BaseApiController
{
@@ -10,9 +11,11 @@ class SystemApiController extends BaseApiController
{
parent::__construct($container);
$this->DatabaseService = new DatabaseService();
$this->ApplicationService = new ApplicationService();
}
protected $DatabaseService;
protected $ApplicationService;
public function GetDbChangedTime(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
@@ -29,13 +32,18 @@ class SystemApiController extends BaseApiController
{
$requestBody = $request->getParsedBody();
$this->LocalizationService->LogMissingLocalization(GROCY_CULTURE, $requestBody['text']);
return $this->ApiResponse(array('success' => true));
$this->LocalizationService->CheckAndAddMissingTranslationToPot($requestBody['text']);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}
public function GetSystemInfo(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->ApplicationService->GetSystemInfo());
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\ApplicationService;
use \Grocy\Services\DatabaseMigrationService;
use \Grocy\Services\DemoDataGeneratorService;
class SystemController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->ApplicationService = new ApplicationService();
}
protected $ApplicationService;
public function Root(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
// Schema migration is done here
$databaseMigrationService = new DatabaseMigrationService();
$databaseMigrationService->MigrateDatabase();
if (GROCY_IS_DEMO_INSTALL)
{
$demoDataGeneratorService = new DemoDataGeneratorService();
$demoDataGeneratorService->PopulateDemoData();
}
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl($this->GetEntryPageRelative()));
}
public function About(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'about', [
'system_info' => $this->ApplicationService->GetSystemInfo(),
'changelog' => $this->ApplicationService->GetChangelog()
]);
}
private function GetEntryPageRelative()
{
$entryPage = '/stockoverview';
if (!GROCY_FEATURE_FLAG_STOCK)
{
$entryPage = '/choresoverview';
}
if (!GROCY_FEATURE_FLAG_CHORES)
{
$entryPage = '/batteriesoverview';
}
if (!GROCY_FEATURE_FLAG_BATTERIES)
{
$entryPage = '/equipment';
}
return $entryPage;
}
}

View File

@@ -21,20 +21,35 @@ class TasksApiController extends BaseApiController
public function MarkTaskAsCompleted(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$doneTime = date('Y-m-d H:i:s');
if (isset($request->getQueryParams()['done_time']) && !empty($request->getQueryParams()['done_time']) && IsIsoDateTime($request->getQueryParams()['done_time']))
{
$doneTime = $request->getQueryParams()['done_time'];
}
$requestBody = $request->getParsedBody();
try
{
$doneTime = date('Y-m-d H:i:s');
if (array_key_exists('done_time', $requestBody) && IsIsoDateTime($requestBody['done_time']))
{
$doneTime = $requestBody['done_time'];
}
$this->TasksService->MarkTaskAsCompleted($args['taskId'], $doneTime);
return $this->VoidApiActionResponse($response);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function UndoTask(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->TasksService->UndoTask($args['taskId']);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -3,6 +3,8 @@
namespace Grocy\Controllers;
use \Grocy\Services\TasksService;
use \Grocy\Services\UsersService;
use \Grocy\Services\UserfieldsService;
class TasksController extends BaseController
{
@@ -10,9 +12,11 @@ class TasksController extends BaseController
{
parent::__construct($container);
$this->TasksService = new TasksService();
$this->UserfieldsService = new UserfieldsService();
}
protected $TasksService;
protected $UserfieldsService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
@@ -25,11 +29,16 @@ class TasksController extends BaseController
$tasks = $this->TasksService->GetCurrent();
}
$usersService = new UsersService();
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['tasks_due_soon_days'];
return $this->AppContainer->view->render($response, 'tasks', [
'tasks' => $tasks,
'nextXDays' => 5,
'nextXDays' => $nextXDays,
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
'users' => $this->Database->users()
'users' => $this->Database->users(),
'userfields' => $this->UserfieldsService->GetFields('tasks'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('tasks')
]);
}
@@ -40,7 +49,8 @@ class TasksController extends BaseController
return $this->AppContainer->view->render($response, 'taskform', [
'mode' => 'create',
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
'users' => $this->Database->users()->orderBy('username'),
'userfields' => $this->UserfieldsService->GetFields('tasks')
]);
}
else
@@ -49,7 +59,8 @@ class TasksController extends BaseController
'task' => $this->Database->tasks($args['taskId']),
'mode' => 'edit',
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
'users' => $this->Database->users()->orderBy('username'),
'userfields' => $this->UserfieldsService->GetFields('tasks')
]);
}
}
@@ -57,7 +68,9 @@ class TasksController extends BaseController
public function TaskCategoriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'taskcategories', [
'taskCategories' => $this->Database->task_categories()->orderBy('name')
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
'userfields' => $this->UserfieldsService->GetFields('task_categories'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('task_categories')
]);
}
@@ -66,15 +79,22 @@ class TasksController extends BaseController
if ($args['categoryId'] == 'new')
{
return $this->AppContainer->view->render($response, 'taskcategoryform', [
'mode' => 'create'
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('task_categories')
]);
}
else
{
return $this->AppContainer->view->render($response, 'taskcategoryform', [
'category' => $this->Database->task_categories($args['categoryId']),
'mode' => 'edit'
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('task_categories')
]);
}
}
public function TasksSettings(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'taskssettings');
}
}

View File

@@ -22,7 +22,7 @@ class UsersApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -32,12 +32,17 @@ class UsersApiController extends BaseApiController
try
{
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
$this->UsersService->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']);
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -46,11 +51,11 @@ class UsersApiController extends BaseApiController
try
{
$this->UsersService->DeleteUser($args['userId']);
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -61,11 +66,11 @@ class UsersApiController extends BaseApiController
try
{
$this->UsersService->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']);
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -78,7 +83,7 @@ class UsersApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -89,11 +94,11 @@ class UsersApiController extends BaseApiController
$requestBody = $request->getParsedBody();
$value = $this->UsersService->SetUserSetting(GROCY_USER_ID, $args['settingKey'], $requestBody['value']);
return $this->ApiResponse(array('success' => true));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@@ -1,2 +0,0 @@
*
!.gitignore

File diff suppressed because it is too large Load Diff

View File

@@ -184,16 +184,6 @@ function GetUserDisplayName($user)
return $displayName;
}
function Pluralize($number, $singularForm, $pluralForm)
{
$text = $singularForm;
if ($number != 1 && $pluralForm !== null && !empty($pluralForm))
{
$text = $pluralForm;
}
return $text;
}
function IsValidFileName($fileName)
{
if(preg_match('=^[^/?*;:{}\\\\]+\.[^/?*;:{}\\\\]+$=', $fileName))

View File

@@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Translation migration from old PHP array files\n"
"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
"Language: en\n"
"X-Domain: grocy/chore_types\n"
msgid "manually"
msgstr ""
msgid "dynamic-regular"
msgstr ""
msgid "daily"
msgstr ""
msgid "weekly"
msgstr ""
msgid "monthly"
msgstr ""

View File

@@ -0,0 +1,31 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Translation migration from old PHP array files\n"
"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
"Language: en\n"
"X-Domain: grocy/component_translations\n"
msgid "timeago_locale"
msgstr ""
msgid "timeago_nan"
msgstr ""
msgid "moment_locale"
msgstr ""
msgid "datatables_localization"
msgstr ""
msgid "summernote_locale"
msgstr ""
msgid "fullcalendar_locale"
msgstr ""

View File

@@ -0,0 +1,32 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
#
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: 2019-05-01 17:42+0000\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\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/chore_types\n"
msgid "manually"
msgstr "Manuelt"
msgid "dynamic-regular"
msgstr ""
msgid "daily"
msgstr ""
msgid "weekly"
msgstr ""
msgid "monthly"
msgstr ""

View File

@@ -0,0 +1,32 @@
#
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: 2019-05-01 17:42+0000\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/component_translations\n"
msgid "timeago_locale"
msgstr ""
msgid "timeago_nan"
msgstr ""
msgid "moment_locale"
msgstr ""
msgid "datatables_localization"
msgstr ""
msgid "summernote_locale"
msgstr ""
msgid "fullcalendar_locale"
msgstr ""

View File

@@ -0,0 +1,283 @@
# Translators:
# dark159123 <r.j.hansen@protonmail.com>, 2019
#
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: 2019-05-01 17:42+0000\n"
"Last-Translator: dark159123 <r.j.hansen@protonmail.com>, 2019\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/demo_data\n"
msgid "Cookies"
msgstr "Småkager"
msgid "Chocolate"
msgstr "chokolade"
msgid "Pantry"
msgstr "Spisekammer"
msgid "Candy cupboard"
msgstr "Slik skuffe"
msgid "Tinned food cupboard"
msgstr "Dåsemadsskab"
msgid "Fridge"
msgstr "Køleskab"
msgid "Piece"
msgid_plural "Pieces"
msgstr[0] ""
msgstr[1] ""
msgid "Pack"
msgid_plural "Packs"
msgstr[0] ""
msgstr[1] ""
msgid "Glass"
msgid_plural "Glasses"
msgstr[0] ""
msgstr[1] ""
msgid "Tin"
msgid_plural "Tins"
msgstr[0] ""
msgstr[1] ""
msgid "Can"
msgid_plural "Cans"
msgstr[0] ""
msgstr[1] ""
msgid "Bunch"
msgid_plural "Bunches"
msgstr[0] ""
msgstr[1] ""
msgid "Gummy bears"
msgstr "Vingummi bamser"
msgid "Crisps"
msgstr "Chips"
msgid "Eggs"
msgstr "Æg"
msgid "Noodles"
msgstr "Nudler"
msgid "Pickles"
msgstr "Syltede agurker"
msgid "Gulash soup"
msgstr "Gulash"
msgid "Yogurt"
msgstr "Yoghurt"
msgid "Cheese"
msgstr "Ost"
msgid "Cold cuts"
msgstr "Pålæg"
msgid "Paprika"
msgstr "Paprika"
msgid "Cucumber"
msgstr "Agurk"
msgid "Radish"
msgstr "Radisse"
msgid "Tomato"
msgstr "Tomat"
msgid "Changed towels in the bathroom"
msgstr "Skiftede håndklæder i badeværelset"
msgid "Cleaned the kitchen floor"
msgstr "Gjorde køkkengulvet rent"
msgid "Warranty ends"
msgstr "Reklamationsret udløber"
msgid "TV remote control"
msgstr "Fjernbetjening"
msgid "Alarm clock"
msgstr "Vægge ur"
msgid "Heat remote control"
msgstr "Varmefjernbetjening"
msgid "Lawn mowed in the garden"
msgstr "Græs slået"
msgid "Some good snacks"
msgstr "Nogle gode snacks"
msgid "Pizza dough"
msgstr "Pizza dej"
msgid "Sieved tomatoes"
msgstr "Sigtede tomater"
msgid "Salami"
msgstr "Salami"
msgid "Toast"
msgstr "Toast"
msgid "Minced meat"
msgstr "Hakkekød"
msgid "Pizza"
msgstr "Pizza"
msgid "Spaghetti bolognese"
msgstr "Spaghetti bolognese"
msgid "Sandwiches"
msgstr "Sandwiches"
msgid "English"
msgstr "Engelsk"
msgid "German"
msgstr "Tysk"
msgid "Italian"
msgstr "Italiænsk"
msgid "Demo in different language"
msgstr "Demo i et andet sprog"
msgid "This is the note content of the recipe ingredient"
msgstr "Dette er indholdet af opskrift ingrediensens notefelt"
msgid "Demo User"
msgstr "Demo Bruger"
msgid "Gram"
msgid_plural "Grams"
msgstr[0] ""
msgstr[1] ""
msgid "Flour"
msgstr "Mel"
msgid "Pancakes"
msgstr "Pandekager"
msgid "Sugar"
msgstr "Sukker"
msgid "Home"
msgstr "Hjem"
msgid "Life"
msgstr "Liv"
msgid "Projects"
msgstr "Projekter"
msgid "Repair the garage door"
msgstr "Reparér garagedøren"
msgid "Fork and improve grocy"
msgstr "Fork og forbedre grocy"
msgid "Find a solution for what to do when I forget the door keys"
msgstr "Find en løsning for når jeg glemmer husnøglen"
msgid "Sweets"
msgstr "Slik"
msgid "Bakery products"
msgstr "Bageriprodukter"
msgid "Tinned food"
msgstr "Dåsemad"
msgid "Butchery products"
msgstr "Slagteriprodukter"
msgid "Vegetables/Fruits"
msgstr "Frugt og grønt"
msgid "Refrigerated products"
msgstr "Nedkølede produkter"
msgid "Coffee machine"
msgstr "Kaffemaskine"
msgid "Dishwasher"
msgstr "Opvasker"
msgid "Liter"
msgstr "Liter"
msgid "Liters"
msgstr "Liter"
msgid "Bottle"
msgstr "Flaske"
msgid "Bottles"
msgstr "Flasker"
msgid "Milk"
msgstr "Mælk"
msgid "Chocolate sauce"
msgstr "Chokoladesauce"
msgid "Milliliters"
msgstr "Milliliter"
msgid "Milliliter"
msgstr "Milliliter"
msgid "Bottom"
msgstr "Bund"
msgid "Topping"
msgstr "Topping"
msgid "French"
msgstr "Fransk"
msgid "Turkish"
msgstr ""
msgid "Spanish"
msgstr ""
msgid "Russian"
msgstr ""
msgid "The thing which happens on the 5th of every month"
msgstr ""
msgid "The thing which happens daily"
msgstr ""
msgid "The thing which happens on Mondays and Wednesdays"
msgstr ""
msgid "Swedish"
msgstr ""
msgid "Polish"
msgstr ""

View File

@@ -0,0 +1,29 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
#
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: 2019-05-01 17:42+0000\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\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/stock_transaction_types\n"
msgid "purchase"
msgstr "Køb"
msgid "consume"
msgstr "Brug"
msgid "inventory-correction"
msgstr "Beholdningsrettelse"
msgid "product-opened"
msgstr "Produkt åbnet"

1252
localization/da/strings.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
#
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: 2019-05-01 17:43+0000\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/userfield_types\n"
msgid "text-single-line"
msgstr ""
msgid "text-multi-line"
msgstr ""
msgid "number-integral"
msgstr ""
msgid "number-decimal"
msgstr ""
msgid "date"
msgstr ""
msgid "datetime"
msgstr ""
msgid "checkbox"
msgstr ""

View File

@@ -1,6 +0,0 @@
<?php
return array(
'manually' => 'Manuell',
'dynamic-regular' => 'Dynamisch regelmäßig'
);

View File

@@ -0,0 +1,32 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
#
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: 2019-05-01 17:42+0000\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\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"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_types\n"
msgid "manually"
msgstr "Manuell"
msgid "dynamic-regular"
msgstr "Dynamisch regelmäßig"
msgid "daily"
msgstr "Täglich"
msgid "weekly"
msgstr "Wöchentlich"
msgid "monthly"
msgstr "Monatlich"

View File

@@ -1,10 +0,0 @@
<?php
return array(
'timeago_locale' => 'de',
'timeago_nan' => 'vor NaN Jahren',
'moment_locale' => 'de',
'datatables_localization' => '{"sEmptyTable":"Keine Daten in der Tabelle vorhanden","sInfo":"_START_ bis _END_ von _TOTAL_ Einträgen","sInfoEmpty":"Keine Daten vorhanden","sInfoFiltered":"(gefiltert von _MAX_ Einträgen)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ Einträge anzeigen","sLoadingRecords":"Wird geladen ..","sProcessing":"Bitte warten ..","sSearch":"Suchen","sZeroRecords":"Keine Einträge vorhanden","oPaginate":{"sFirst":"Erste","sPrevious":"Zurück","sNext":"Nächste","sLast":"Letzte"},"oAria":{"sSortAscending":": aktivieren, um Spalte aufsteigend zu sortieren","sSortDescending":": aktivieren, um Spalte absteigend zu sortieren"},"select":{"rows":{"0":"Zum Auswählen auf eine Zeile klicken","1":"1 Zeile ausgewählt","_":"%d Zeilen ausgewählt"}},"buttons":{"print":"Drucken","colvis":"Spalten","copy":"Kopieren","copyTitle":"In Zwischenablage kopieren","copyKeys":"Taste <i>ctrl</i> oder <i>⌘</i> + <i>C</i> um Tabelle<br>in Zwischenspeicher zu kopieren.<br><br>Um abzubrechen die Nachricht anklicken oder Escape drücken.","copySuccess":{"1":"1 Spalte kopiert","_":"%d Spalten kopiert"}}}',
'summernote_locale' => 'de-DE',
'fullcalendar_locale' => 'de'
);

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