Compare commits

...

258 Commits

Author SHA1 Message Date
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
Bernd Bestel
b9f0470d76 Prepared next release 2018-12-30 09:21:11 +01:00
Bernd Bestel
bdcd176f81 Make config.php settings also available as environment variables (closes #118) 2018-12-23 16:45:16 +01:00
Bernd Bestel
04dacacd73 Pull translations from Transifex for new release 2018-11-27 17:30:40 +01:00
Bernd Bestel
a9502d1ddb Fix wrong translation string 2018-11-26 20:03:14 +01:00
Bernd Bestel
5c25e91984 Add possibility to skip items in "all shopping list items to stock workflow" (closes #116) 2018-11-26 20:02:01 +01:00
Bernd Bestel
02163c4305 Properly refresh opened product amount after consume on stock overview page (fixes #114) 2018-11-26 19:47:07 +01:00
Bernd Bestel
c3731b3200 Sort recipes fulfillment column by amount of missing products (closes #113) 2018-11-26 19:40:29 +01:00
Bernd Bestel
5cdf2c14d3 Fix shopping list edit form - product was not prefilled (fixes #115) 2018-11-26 19:37:17 +01:00
Bernd Bestel
e92d74f5c3 This is v1.23.0 2018-11-24 20:41:45 +01:00
Bernd Bestel
2b3516dadd Pull translations from Transifex 2018-11-24 20:39:42 +01:00
Bernd Bestel
8041dd9c26 Support single quotes in demo data 2018-11-24 20:39:17 +01:00
Bernd Bestel
2f7b78bc40 Update dependencies for next release 2018-11-24 20:18:50 +01:00
Bernd Bestel
61fc6e05f4 Add an agenda view to calendar 2018-11-24 20:18:13 +01:00
Bernd Bestel
367a3e52de Properly sort recipes fulfillment column (closes #113) 2018-11-24 19:59:24 +01:00
Bernd Bestel
a3617cffb8 Small different UI improvements
Show the cursor as pointer on stock overview page while hovering the product name cell to highlight that the productcard can be opened
Show a waiting cursor and disable all form inputs while doing background XHR calls to highlight that the user should wait
Place the search field (to search a table) on all pages to the left most place
2018-11-24 19:40:50 +01:00
Bernd Bestel
ff341d8547 Typo... 2018-11-21 22:17:51 +01:00
Bernd Bestel
01fab6999f Add new demo translation string 2018-11-21 22:16:02 +01:00
Bernd Bestel
b6152ce874 Pull translations from Transifex 2018-11-21 21:54:15 +01:00
Bernd Bestel
ca5df3b217 Hopefully finally fix the navbar scrollbar problems now 2018-11-21 21:36:22 +01:00
Bernd Bestel
dd48be595c Remove "debug" JS output 2018-11-21 19:16:06 +01:00
Bernd Bestel
306d0f7da6 Add "one click shopping list to stock workflow" (closes #110) 2018-11-21 19:08:36 +01:00
Bernd Bestel
c61c37e67a Properly initialize show_clock_in_header user setting control after page reload 2018-11-20 19:34:06 +01:00
Bernd Bestel
f7f90238f2 Show optionally a clock in the header (closes #109) 2018-11-20 19:23:48 +01:00
Bernd Bestel
589ad36855 Remove unneeded translation string 2018-11-18 15:52:48 +01:00
Bernd Bestel
5da24d2d4f Added first version of calendar (closes #42) 2018-11-18 15:39:43 +01:00
Bernd Bestel
f7f2bf3fc0 Various small UI improvements for the shopping list 2018-11-18 14:19:50 +01:00
Bernd Bestel
34d1bdd53f Added missing translation string 2018-11-18 14:15:11 +01:00
Bernd Bestel
e021c93d22 Make clear than picking a specific stock item on consume always means an amount of 1 2018-11-18 13:46:23 +01:00
Bernd Bestel
2ff5faacc0 Prevent opening more products than are unopened in stock 2018-11-18 13:35:21 +01:00
Bernd Bestel
a489190e81 Add some opened products to demo 2018-11-18 13:21:57 +01:00
Bernd Bestel
a403bb687a Show amount of opened products on stock overview page and in product card 2018-11-18 13:17:36 +01:00
Bernd Bestel
5966a3d678 Improve journal UI when undoing a transaction 2018-11-18 12:58:15 +01:00
Bernd Bestel
c71e46191f Optimized stock logic 2018-11-18 12:34:05 +01:00
Bernd Bestel
862fd7c644 Added missing translation string 2018-11-17 19:46:59 +01:00
Bernd Bestel
10ea9c44fd Make it possible to mark a product as opened (closes #86) 2018-11-17 19:39:37 +01:00
Bernd Bestel
816ca6460f Make it possible to pick a specific stock item on consume (closes #62) 2018-11-17 17:51:35 +01:00
Bernd Bestel
b6d60c4e34 Fix datetimepicker did not trigger a change event (fixes #108) 2018-11-17 15:00:51 +01:00
Bernd Bestel
6f67619784 Finish "shopping list to stock workflow" (closes #98) 2018-11-17 14:50:52 +01:00
Bernd Bestel
db0b48e7ae Fix GitHub project links 2018-11-17 12:58:44 +01:00
Bernd Bestel
973f07b360 Add embedded mode (hides menu and so on), maybe need for #98 2018-11-17 12:57:35 +01:00
Bernd Bestel
0f73d849eb Fixed equipment edit button always pointed to the first equipment item (fixes #107) 2018-11-17 08:26:31 +01:00
Bernd Bestel
1a6849ad37 Moved docker related things to https://github.com/grocy/grocy-docker (this now closes #101, thanks @talmai) 2018-11-16 07:58:03 +01:00
Bernd Bestel
8f31f891fd Add a "shopping list item to stock flow" (references #98) 2018-11-14 21:40:17 +01:00
Bernd Bestel
83985e9f21 Migrate translations to use Transifex 2018-11-03 14:29:14 +01:00
Bernd Bestel
04e9ba8e34 Allow fraction numbers for recipe ingredients when not checked against stock and add option to not check stock for a recipe position (closes #105) 2018-11-02 19:53:01 +01:00
Bernd Bestel
960ee919f9 Mention minimum SQLite version (closes #104) 2018-10-29 22:09:44 +01:00
Bernd Bestel
f4534a4bfb Update dependencies for next release 2018-10-27 17:58:05 +02:00
Bernd Bestel
89553b7fa0 Workaround for datepicker problem (fixes #100) 2018-10-27 17:56:53 +02:00
Bernd Bestel
364f6b2051 Add journal and undo UI for stock bookings, chore executions and battery charge cycles (closes #63, closes #97) 2018-10-27 17:26:00 +02:00
Bernd Bestel
fe83e2fa6f Created API endpoints to undo battery charge cycles and chore executions (references #63) 2018-10-27 10:55:30 +02:00
Bernd Bestel
1f3dd58ddf Properly display line breaks of recipe positions and shopping list item notes 2018-10-27 10:39:52 +02:00
Bernd Bestel
da98efa833 Finalize nested recipes / group recipe positions feature (closes #77) 2018-10-27 10:37:31 +02:00
Bernd Bestel
3e6cf545d7 Finalize stock booking undo API (references #63 and #97) 2018-10-27 10:19:06 +02:00
Bernd Bestel
1080c3486c Created first version of an API endpoint to undo stock bookings (references #63 and #97) 2018-10-26 22:28:58 +02:00
Bernd Bestel
cd7b6b686d Fix API keys cannot be deleted (fixes #99) 2018-10-26 20:12:48 +02:00
Bernd Bestel
b84e6da0dd Added recipe position groups (references #77) 2018-10-25 22:45:44 +02:00
Bernd Bestel
fc3a4c6899 Finish first version of nested recipes feature (references #77) 2018-10-25 20:36:29 +02:00
Bernd Bestel
12a2cb0bdf Created basis edit UI for nested recipes (references #77) 2018-10-23 19:36:39 +02:00
Bernd Bestel
57a0864465 Show productcard as a popup on stockoverview page instead of only the product picture (closes #93) 2018-10-22 19:28:59 +02:00
Bernd Bestel
b3da837ede Added link to item edit page for all item cards (product/chore/battery) (references #93) 2018-10-22 19:13:08 +02:00
Bernd Bestel
3de3e03ab3 Created database logic for nested recipes (references #77) 2018-10-21 15:02:52 +02:00
Bernd Bestel
78865a9d3c Fix product groups were empty on product presets page (references #92) 2018-10-20 15:22:32 +02:00
Bernd Bestel
5b3230d63d Properly name the quantity unit field on product presets page (references #92) 2018-10-20 15:18:58 +02:00
Bernd Bestel
04c93d937e Make presets for new products configurable (closes #92) 2018-10-20 14:55:49 +02:00
Bernd Bestel
5318e79f55 Properly pluralize quantity unit in success message on purchase/consume/inventory page 2018-10-20 14:09:19 +02:00
Bernd Bestel
7bf4421d44 Improve best before date comparison on stockoverview page (fixes #87) 2018-10-20 14:04:09 +02:00
Bernd Bestel
366152c049 Next attempt to fix the scroll issue of navigation section (references #90) 2018-10-20 11:25:22 +02:00
Bernd Bestel
70c00e81d9 Cascade changes of stock quantity unit of products to recipe positions (fixes #91) 2018-10-20 11:17:51 +02:00
Talmai Oliveira
f13abf483e Grocy docker patch fix (#94)
fixed:

Warning: Use of undefined constant GROCY_USER_ID - assumed
'GROCY_USER_ID' (this will throw an Error in a future version of PHP) in
/www/controllers/BaseController.php on line 47
2018-10-20 09:07:05 +02:00
Bernd Bestel
0e723a0a9b Fixed reported login problem ("PHP Warning: date() expects parameter 2 to be integer, float given") 2018-10-16 18:21:38 +02:00
Bernd Bestel
4a35477c35 Don't auto reload when database has changed in "fullscreen-card-mode" (fixes #88) 2018-10-13 09:18:16 +02:00
Bernd Bestel
df7d360516 Update screenshots 2018-10-11 10:33:38 +02:00
Bernd Bestel
03eaa6c79f Add possibility to filter by product group on stock overview page 2018-10-06 18:19:31 +02:00
Bernd Bestel
132999ce36 Show the little "product has an image icon" also on products page (references #58) 2018-10-06 18:08:25 +02:00
Bernd Bestel
188407e3c7 Merge branch 'master' of https://github.com/berrnd/grocy 2018-10-06 18:02:30 +02:00
Bernd Bestel
8cf68ade30 Fix missing translation (references #85) 2018-10-06 18:02:13 +02:00
Marius Boro
d62657c698 Better translation (#84)
* Update no.php

* Update no.php

minor fix
2018-10-06 17:57:21 +02:00
Bernd Bestel
3262e534dc Hotfix (will be included in v1.21.0 release): Fixed a syntax error in norwegian localization file 2018-10-06 12:08:44 +02:00
380 changed files with 37807 additions and 4134 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,6 +0,0 @@
.git
.vscode
.gitignore
build.bat
Dockerfile
.DS_store

38
.tx/config Normal file
View File

@@ -0,0 +1,38 @@
[main]
host = https://www.transifex.com
[grocy.chore_types]
file_filter = localization/<lang>/chore_types.po
source_file = localization/chore_types.pot
source_lang = en
type = PO
[grocy.component_translations]
file_filter = localization/<lang>/component_translations.po
source_file = localization/component_translations.pot
source_lang = en
type = PO
[grocy.demo_data]
file_filter = localization/<lang>/demo_data.po
source_file = localization/demo_data.pot
source_lang = en
type = PO
[grocy.stock_transaction_types]
file_filter = localization/<lang>/stock_transaction_types.po
source_file = localization/stock_transaction_types.pot
source_lang = en
type = PO
[grocy.strings]
file_filter = localization/<lang>/strings.po
source_file = localization/strings.pot
source_lang = en
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

@@ -1,58 +0,0 @@
FROM php:7.2-fpm-alpine
MAINTAINER Talmai Oliveira <to@talm.ai>
RUN apk update && \
apk upgrade && \
apk add --update yarn git &&\
mkdir -p /www && \
# Set environments
sed -i "s|;*daemonize\s*=\s*yes|daemonize = no|g" /usr/local/etc/php-fpm.conf && \
sed -i "s|;*listen\s*=\s*127.0.0.1:9000|listen = 9000|g" /usr/local/etc/php-fpm.conf && \
sed -i "s|;*listen\s*=\s*/||g" /usr/local/etc/php-fpm.conf && \
# sed -i "s|;*log_level\s*=\s*notice|log_level = debug|g" /usr/local/etc/php-fpm.conf && \
sed -i "s|;*chdir\s*=\s*/var/www|chdir = /www|g" /usr/local/etc/php-fpm.d/www.conf && \
# sed -i "s|;*access.log\s*=\s*log/\$pool.access.log|access.log = \$pool.access.log|g" /usr/local/etc/php-fpm.d/www.conf && \
# sed -i "s|;*pm.status_path\s*=\s*/status|pm.status_path = /status|g" /usr/local/etc/php-fpm.d/www.conf && \
# sed -i "s|;*memory_limit =.*|memory_limit = ${PHP_MEMORY_LIMIT}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*upload_max_filesize =.*|upload_max_filesize = ${MAX_UPLOAD}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*max_file_uploads =.*|max_file_uploads = ${PHP_MAX_FILE_UPLOAD}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*post_max_size =.*|post_max_size = ${PHP_MAX_POST}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*cgi.fix_pathinfo=.*|cgi.fix_pathinfo= 0|i" /usr/local/etc/php.ini && \
wget https://raw.githubusercontent.com/composer/getcomposer.org/1b137f8bf6db3e79a38a5bc45324414a6b1f9df2/web/installer -O - -q | php -- --quiet && \
# Cleaning up
rm -rf /var/cache/apk/*
COPY public /www/public
COPY info.php /www/public
COPY controllers /www/controllers
COPY data /www/data
COPY helpers /www/helpers
COPY localization/ /www/localization
COPY middleware/ /www/middleware
COPY migrations/ /www/migrations
COPY publication_assets/ /www/publication_assets
COPY services/ /www/services
COPY views/ /www/views
COPY .yarnrc /www/
COPY *.php /www/
COPY *.json /www/
COPY composer.* /root/.composer/
COPY *yarn* /www/
COPY *.sh /www/
# run php composer.phar with -vvv for extra debug information
RUN cd /var/www/html && \
php composer.phar --working-dir=/www/ -n install && \
cp /www/config-dist.php /www/data/config.php && \
cd /www && \
yarn install && \
chown www-data:www-data -R /www/
# Set Workdir
WORKDIR /www/public
# Expose volumes
VOLUME ["/www"]
# Expose ports
EXPOSE 9000

View File

@@ -1,32 +0,0 @@
FROM alpine:latest
MAINTAINER Talmai Oliveira <to@talm.ai>
RUN apk update && \
apk upgrade && \
apk add --update openssl nginx && \
mkdir -p /etc/nginx/certificates && \
mkdir -p /var/run/nginx && \
mkdir -p /usr/share/nginx/html && \
openssl req \
-x509 \
-newkey rsa:2048 \
-keyout /etc/nginx/certificates/key.pem \
-out /etc/nginx/certificates/cert.pem \
-days 365 \
-nodes \
-subj /CN=localhost && \
rm -rf /var/cache/apk/*
COPY docker_nginx/nginx.conf /etc/nginx/nginx.conf
COPY docker_nginx/common.conf /etc/nginx/common.conf
COPY docker_nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
COPY docker_nginx/conf.d/ssl.conf /etc/nginx/conf.d/ssl.conf
# Expose volumes
VOLUME ["/etc/nginx/conf.d", "/var/log/nginx"]
# Expose ports
EXPOSE 80 443
# Entry point
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

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!
@@ -13,9 +16,9 @@ A household needs to be managed. I did this so far (almost 10 years) with my fir
>
> There is now grocy-desktop if you want to run grocy without a webserver just like a normal (windows) desktop application.
>
> See https://github.com/berrnd/grocy-desktop or directly download the [latest release](https://releases.grocy.info/latest-desktop) - the installation is nothing more than just clicking 2 times "next"...
> See https://github.com/grocy/grocy-desktop or directly download the [latest release](https://releases.grocy.info/latest-desktop) - the installation is nothing more than just clicking 2 times "next"...
Just unpack the [latest release](https://releases.grocy.info/latest) on your PHP (SQLite extension required, currently only tested with PHP 7.2) enabled webserver (webservers root should point to the `public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go, (to make writable `chown -R www-data:www-data data/`). Default login is user `admin` with password `admin`, please change the password immediately (see user menu).
Just unpack the [latest release](https://releases.grocy.info/latest) on your PHP (SQLite (3.8.3 or higher) extension required, currently only tested with PHP 7.2) enabled webserver (webservers root should point to the `public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go, (to make writable `chown -R www-data:www-data data/`). Default login is user `admin` with password `admin`, please change the password immediately (see user menu).
Alternatively clone this repository and install Composer and Yarn dependencies manually.
@@ -25,13 +28,7 @@ If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REW
## How to run using Docker
The docker images build are based on [Alpine](https://hub.docker.com/_/alpine/), with an extremelly low footprint (less than 10 MB for nginx, and less than 70MB for grocy with php-fm. That number is eventually bumped up to 353MB after all the dependencies are downloaded, however). Anyhow, to run using docker just do the following:
```
> docker-compose up
```
And grocy should be accessible via `http(s)://localhost/`. The https option will work. However, since the certificate is self-signed, most browsers will complain.
See [grocy/grocy-docker](https://github.com/grocy/grocy-docker) for instructions.
## How to update
Just overwrite everything with the latest release while keeping the `data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (the default from values `config-dist.php` will be used for not in `data/config.php` defined settings). Just to be sure, please empty `data/viewcache`.
@@ -39,7 +36,9 @@ Just overwrite everything with the latest release while keeping the `data` direc
If you run grocy on Linux, there is also `update.sh` (remember to make the script executable, `chmod +x update.sh` and ensure that you have `unzip` installed) which does exactly this and additionally creates a backup (`.tgz` archive) of the current installation in `data/backups` (backups older than 60 days will be deleted during the update).
## Localization
grocy is fully localizable - the default language is English (integrated into code), a German localization is always maintained by me. There is one file per language in the `localization` directory, if you want to create a translation, it's best to copy `localization/de.php` to a new one (e. g. `localization/it.php`) and translating all strings there. (Language can be changed in `data/config.php`, e. g. `Setting('CULTURE', 'it');`)
grocy is fully localizable - the default language is English (integrated into code), a German localization is always maintained by me.
You can easily help translating grocy at https://www.transifex.com/grocy/grocy, if your language is incomplete or not available yet.
(Language can be changed in `data/config.php`, e. g. `Setting('CULTURE', 'it');`)
### Maintaining your own localization
As the German translation will always be the most complete one, for maintaining your localization it would be easiest when you compare your localization with the German one with a diff tool of your choice.
@@ -77,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,19 +87,24 @@ Database schema migration is automatically done when visiting the root (`/`) rou
When the file `data/demo.txt` exists, the application will work in a demo mode which means authentication is disabled and some demo data will be generated during the database schema migration.
### Embedded mode
When the file `embedded.txt` exists, it must contain a valid and writable path which will be used as the data directory instead of `data` and authentication will be disabled (used in [grocy-desktop](https://github.com/berrnd/grocy-desktop)).
When the file `embedded.txt` exists, it must contain a valid and writable path which will be used as the data directory instead of `data` and authentication will be disabled (used in [grocy-desktop](https://github.com/grocy/grocy-desktop)).
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/berrnd/grocy/raw/master/publication_assets/dashboard.png "Dashboard")
![Dashboard](https://github.com/grocy/grocy/raw/master/publication_assets/dashboard.png "Dashboard")
#### Purchase - with barcode scan
![Purchase - with barcode scan](https://github.com/berrnd/grocy/raw/master/publication_assets/purchase-with-barcode.gif "purchase-with-barcode")
![Purchase - with barcode scan](https://github.com/grocy/grocy/raw/master/publication_assets/purchase-with-barcode.gif "purchase-with-barcode")
#### Consume - with manual search
![Consume - with manual search](https://github.com/berrnd/grocy/raw/master/publication_assets/consume.gif "consume")
![Consume - with manual search](https://github.com/grocy/grocy/raw/master/publication_assets/consume.gif "consume")
## License
The MIT License (MIT)

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 on 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 on 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 @@
- 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": {

673
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,19 @@
<?php
# Settings can also be overwritten in two ways
#
# First priority
# A .txt file with the same name as the setting in /data/settingoverrides
# the content of the file is used as the setting value
#
# Second priority
# An environment variable with the same name as the setting and prefix "GROCY_"
# so for example "GROCY_BASE_URL"
#
# Third priority
# The settings defined here below
# Either "production", "dev" or "prerelease"
Setting('MODE', 'production');
@@ -9,12 +23,13 @@ Setting('CULTURE', 'en');
# 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,
@@ -27,6 +42,8 @@ Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
Setting('DISABLE_URL_REWRITING', false);
# Default user settings
# These settings can be changed per user, here the defaults
# are defined which are used when the user has not changed the setting so far
@@ -39,6 +56,48 @@ 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
DefaultUserSetting('auto_reload_on_db_change', true);
# Show a clock in the header next to the logo or not
DefaultUserSetting('show_clock_in_header', false);
# Shopping list to stock workflow:
# 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,19 +32,49 @@ 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);
});
try {
$embedded = false;
if (isset($container->request->getQueryParams()['embedded']))
{
$embedded = true;
}
$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();
$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID));
if (defined('GROCY_USER_ID'))
{
$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
{
$this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
return $this->VoidApiActionResponse($response);
$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($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());
}
}
@@ -49,4 +51,17 @@ class BatteriesApiController extends BaseApiController
{
return $this->ApiResponse($this->BatteriesService->GetCurrent());
}
public function UndoChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->ApiResponse($this->BatteriesService->UndoChargeCycle($args['chargeCycleId']));
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\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,15 +53,30 @@ 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')
]);
}
}
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'batteriesjournal', [
'chargeCycles' => $this->Database->battery_charge_cycles()->orderBy('tracked_time', 'DESC'),
'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

@@ -0,0 +1,23 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\CalendarService;
class CalendarController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->CalendarService = new CalendarService();
}
protected $CalendarService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'calendar', [
'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
{
$this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->VoidApiActionResponse($response);
$trackedTime = date('Y-m-d H:i:s');
if (array_key_exists('tracked_time', $requestBody) && IsIsoDateTime($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($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());
}
}
@@ -55,4 +57,17 @@ class ChoresApiController extends BaseApiController
{
return $this->ApiResponse($this->ChoresService->GetCurrent());
}
public function UndoChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->ApiResponse($this->ChoresService->UndoChoreExecution($args['executionId']));
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\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,13 +43,15 @@ 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')
]);
}
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choresanalysis', [
return $this->AppContainer->view->render($response, 'choresjournal', [
'choresLog' => $this->Database->chores_log()->orderBy('tracked_time', 'DESC'),
'chores' => $this->Database->chores()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
@@ -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,29 +2,39 @@
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']))
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
{
return $this->ApiResponse($this->Database->{$args['entity']}());
}
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');
}
}
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($this->IsValidEntity($args['entity']))
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
{
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
}
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());
}
}
@@ -77,4 +149,9 @@ class GenericEntityApiController extends BaseApiController
{
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum);
}
private function IsEntityWithPreventedListing($entity)
{
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntitiesPreventListing->enum);
}
}

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, 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

@@ -16,8 +16,16 @@ class RecipesApiController extends BaseApiController
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId']);
return $this->VoidApiActionResponse($response);
$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)
@@ -25,11 +33,11 @@ class RecipesApiController extends BaseApiController
try
{
$this->RecipesService->ConsumeRecipe($args['recipeId']);
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());
}
}
}

View File

@@ -3,6 +3,7 @@
namespace Grocy\Controllers;
use \Grocy\Services\RecipesService;
use \Grocy\Services\UserfieldsService;
class RecipesController extends BaseController
{
@@ -10,39 +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']);
$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);
$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_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()
'quantityunits' => $this->Database->quantity_units(),
'selectedRecipeSubRecipes' => $selectedRecipeSubRecipes,
'selectedRecipeSubRecipesPositions' => $selectedRecipeSubRecipesPositions,
'includedRecipeIdsAbsolute' => $includedRecipeIdsAbsolute,
'selectedRecipeTotalCosts' => FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->costs,
'userfields' => $this->UserfieldsService->GetFields('recipes'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('recipes')
]);
}
@@ -51,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();
@@ -65,8 +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()
'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')
]);
}
@@ -92,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,82 +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
{
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
return $this->VoidApiActionResponse($response);
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'];
}
$requestBody = $request->getParsedBody();
try
{
$this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
return $this->VoidApiActionResponse($response);
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
{
$this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
return $this->VoidApiActionResponse($response);
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)
{
$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)');
}
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->GenericErrorResponse($response, $ex->getMessage());
}
}
@@ -138,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)
@@ -162,7 +302,25 @@ class StockApiController extends BaseApiController
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function UndoBooking(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->ApiResponse($this->StockService->UndoBooking($args['bookingId']));
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function ProductStockEntries(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->StockService->GetProductStockEntries($args['productId']));
}
}

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,51 +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
'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
]);
}
@@ -63,6 +85,17 @@ class StockController extends BaseController
{
return $this->AppContainer->view->render($response, 'products', [
'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'),
'userfields' => $this->UserfieldsService->GetFields('products'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('products')
]);
}
public function StockSettings(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'stocksettings', [
'locations' => $this->Database->locations()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'productGroups' => $this->Database->product_groups()->orderBy('name')
@@ -72,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')
]);
}
@@ -98,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'
]);
}
@@ -108,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'
]);
}
@@ -118,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')
]);
}
}
@@ -135,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')
]);
}
}
@@ -152,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()
]);
}
}
@@ -168,18 +220,44 @@ 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'),
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'shoppinglistform', [
return $this->AppContainer->view->render($response, 'shoppinglistitemform', [
'listItem' => $this->Database->shopping_list($args['itemId']),
'products' => $this->Database->products()->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', [
'shoppingList' => $this->Database->shopping_lists($args['listId']),
'mode' => 'edit'
]);
}
}
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'stockjournal', [
'stockLog' => $this->Database->stock_log()->orderBy('row_created_timestamp', 'DESC'),
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
]);
}
}

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,22 @@ 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());
}
}
}

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

View File

@@ -1,30 +0,0 @@
# Usage:
# docker-compose build && docker-compose up
version: '2'
services:
grocy-nginx:
build:
context: .
dockerfile: Dockerfile-grocy-nginx
depends_on:
- grocy
ports:
- '80:80'
- '443:443'
volumes_from:
- grocy
container_name: grocy-nginx
grocy:
build:
context: .
dockerfile: Dockerfile-grocy
expose:
- 9000
environment:
PHP_MEMORY_LIMIT: 512M
MAX_UPLOAD: 50M
PHP_MAX_FILE_UPLOAD: 200
PHP_MAX_POST: 100M
container_name: grocy

View File

@@ -1,28 +0,0 @@
index index.php index.html index.htm;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.php$ {
fastcgi_pass grocy:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}

View File

@@ -1,8 +0,0 @@
server {
listen 80 default_server;
server_name _;
root /www/public; # see: volumes_from
include /etc/nginx/common.conf;
}

View File

@@ -1,20 +0,0 @@
server {
listen 443 ssl;
server_name _;
root /www/public; # see: volumes_from
ssl_certificate /etc/nginx/certificates/cert.pem;
ssl_certificate_key /etc/nginx/certificates/key.pem;
error_log /var/log/nginx/error.log;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
include /etc/nginx/common.conf;
}

View File

@@ -1,42 +0,0 @@
user nobody;
worker_processes 1;
pid /var/run/nginx/nginx.pid;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
sendfile on;
#tcp_nopush on;
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 50M;
large_client_header_buffers 2 1k;
gzip on;
gzip_comp_level 2;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/x-javascript text/xml text/css application/xml;
access_log on;
include /etc/nginx/conf.d/*.conf;
}

File diff suppressed because it is too large Load Diff

View File

@@ -138,6 +138,10 @@ function Setting(string $name, $value)
{
define('GROCY_' . $name, file_get_contents($settingOverrideFile));
}
elseif (getenv('GROCY_' . $name) !== false) // An environment variable with the same name and prefix GROCY_ overwrites the given setting
{
define('GROCY_' . $name, getenv('GROCY_' . $name));
}
else
{
define('GROCY_' . $name, $value);
@@ -180,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

@@ -1,6 +0,0 @@
<?php
// Show all information, defaults to INFO_ALL
phpinfo();
?>

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 ""

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