mirror of
				https://github.com/grocy/grocy.git
				synced 2025-10-31 18:49:38 +00:00 
			
		
		
		
	Compare commits
	
		
			379 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e92d74f5c3 | ||
|  | 2b3516dadd | ||
|  | 8041dd9c26 | ||
|  | 2f7b78bc40 | ||
|  | 61fc6e05f4 | ||
|  | 367a3e52de | ||
|  | a3617cffb8 | ||
|  | ff341d8547 | ||
|  | 01fab6999f | ||
|  | b6152ce874 | ||
|  | ca5df3b217 | ||
|  | dd48be595c | ||
|  | 306d0f7da6 | ||
|  | c61c37e67a | ||
|  | f7f90238f2 | ||
|  | 589ad36855 | ||
|  | 5da24d2d4f | ||
|  | f7f2bf3fc0 | ||
|  | 34d1bdd53f | ||
|  | e021c93d22 | ||
|  | 2ff5faacc0 | ||
|  | a489190e81 | ||
|  | a403bb687a | ||
|  | 5966a3d678 | ||
|  | c71e46191f | ||
|  | 862fd7c644 | ||
|  | 10ea9c44fd | ||
|  | 816ca6460f | ||
|  | b6d60c4e34 | ||
|  | 6f67619784 | ||
|  | db0b48e7ae | ||
|  | 973f07b360 | ||
|  | 0f73d849eb | ||
|  | 1a6849ad37 | ||
|  | 8f31f891fd | ||
|  | 83985e9f21 | ||
|  | 04e9ba8e34 | ||
|  | 960ee919f9 | ||
|  | f4534a4bfb | ||
|  | 89553b7fa0 | ||
|  | 364f6b2051 | ||
|  | fe83e2fa6f | ||
|  | 1f3dd58ddf | ||
|  | da98efa833 | ||
|  | 3e6cf545d7 | ||
|  | 1080c3486c | ||
|  | cd7b6b686d | ||
|  | b84e6da0dd | ||
|  | fc3a4c6899 | ||
|  | 12a2cb0bdf | ||
|  | 57a0864465 | ||
|  | b3da837ede | ||
|  | 3de3e03ab3 | ||
|  | 78865a9d3c | ||
|  | 5b3230d63d | ||
|  | 04c93d937e | ||
|  | 5318e79f55 | ||
|  | 7bf4421d44 | ||
|  | 366152c049 | ||
|  | 70c00e81d9 | ||
|  | f13abf483e | ||
|  | 0e723a0a9b | ||
|  | 4a35477c35 | ||
|  | df7d360516 | ||
|  | 03eaa6c79f | ||
|  | 132999ce36 | ||
|  | 188407e3c7 | ||
|  | 8cf68ade30 | ||
|  | d62657c698 | ||
|  | 3262e534dc | ||
|  | 6202e8bda7 | ||
|  | 9984e8f218 | ||
|  | b0c91f6ad1 | ||
|  | 9e24586190 | ||
|  | 7ba6fc875b | ||
|  | 3b10906e78 | ||
|  | ebd24bf30e | ||
|  | ebd9b1b851 | ||
|  | b242a5de52 | ||
|  | 81ec011095 | ||
|  | 2a371cc081 | ||
|  | edb986ce24 | ||
|  | f90faca62e | ||
|  | 6090ac621e | ||
|  | ae58606d04 | ||
|  | bb9caf9cc9 | ||
|  | 9dd57decdf | ||
|  | f1fc0ee549 | ||
|  | fcdeb33426 | ||
|  | 44cd26ae77 | ||
|  | 04f34ea6b0 | ||
|  | e5fb609c8e | ||
|  | c675b534ef | ||
|  | 6c74881f95 | ||
|  | 756ec319cc | ||
|  | ba2d32be60 | ||
|  | 7cc09cec67 | ||
|  | 8b815fce93 | ||
|  | f1c78659be | ||
|  | 5c79a80f7a | ||
|  | f451e65278 | ||
|  | 176333df5b | ||
|  | d4227d2e41 | ||
|  | 0bbd2d9880 | ||
|  | b81316bd60 | ||
|  | d11dcb38fe | ||
|  | 77d82f22dc | ||
|  | be326a5211 | ||
|  | 83624eaf27 | ||
|  | 055619d275 | ||
|  | cda3dde120 | ||
|  | 5a0b862d22 | ||
|  | bb5fd8360b | ||
|  | d7180bd7b2 | ||
|  | 8c9b0dedb2 | ||
|  | 9c2c2c1fa2 | ||
|  | 596dc9e36d | ||
|  | b2019ba42d | ||
|  | 003d4a567a | ||
|  | 5112e0f551 | ||
|  | 8008fcdc65 | ||
|  | 8d41dcc650 | ||
|  | 037d024862 | ||
|  | 03ca5cd45b | ||
|  | 60d47bef84 | ||
|  | 98a7bcb044 | ||
|  | 7401971884 | ||
|  | 067a10e1b2 | ||
|  | ddfe33fab6 | ||
|  | 2a0ec30bb0 | ||
|  | 8540fc44f3 | ||
|  | 66095738e3 | ||
|  | e472711d23 | ||
|  | 8e054a4981 | ||
|  | feb28211d8 | ||
|  | 06f25b7006 | ||
|  | f85a67a1ff | ||
|  | 6fe0100927 | ||
|  | bcb359e317 | ||
|  | 4075067a10 | ||
|  | bd3c63218b | ||
|  | 27daf384da | ||
|  | 905fc0f357 | ||
|  | 9cd0e4ab2d | ||
|  | 6b38cd450f | ||
|  | bb60f5f043 | ||
|  | e777be4d3b | ||
|  | 8a71d55f0f | ||
|  | b01b49d10c | ||
|  | 496594d898 | ||
|  | 1d5e82c341 | ||
|  | a9b696f41c | ||
|  | e50b1eb359 | ||
|  | 92e0245387 | ||
|  | 67d0d3c3d6 | ||
|  | 23bcbc23e9 | ||
|  | 085d9a0bc7 | ||
|  | 368df142cf | ||
|  | d38edabb14 | ||
|  | 4426a10e2e | ||
|  | 931dc9d243 | ||
|  | c5b8893008 | ||
|  | c27f41aee4 | ||
|  | ef043b38ce | ||
|  | bb261f99c4 | ||
|  | 48ca0f2ac7 | ||
|  | b7f0b06684 | ||
|  | 324487d395 | ||
|  | 9a8c61497b | ||
|  | bc7afe4bdd | ||
|  | bb5dcb2434 | ||
|  | 71b9d11ff5 | ||
|  | 3e73a44576 | ||
|  | dedfe3a854 | ||
|  | c4b0ef4d49 | ||
|  | 339d81318f | ||
|  | 282ee0885b | ||
|  | 5833364e51 | ||
|  | 525f1705d1 | ||
|  | 5a13cb5ffe | ||
|  | e830805443 | ||
|  | ca3f28b615 | ||
|  | 6081b8ee67 | ||
|  | 7eef4acd81 | ||
|  | 678579e933 | ||
|  | 4cc2d39063 | ||
|  | 14cc153422 | ||
|  | f5b5c4c7e1 | ||
|  | 88b76a52a5 | ||
|  | a4a25af460 | ||
|  | 41a72d11da | ||
|  | c8236b101b | ||
|  | ef1df0a446 | ||
|  | 5c4953b9b2 | ||
|  | ccaf2411fe | ||
|  | bce8bd6b35 | ||
|  | 66c07887cb | ||
|  | be99880ce4 | ||
|  | e026609972 | ||
|  | 3474f55866 | ||
|  | f583810d5c | ||
|  | 419445f5ae | ||
|  | c64eb27ca1 | ||
|  | f4eb5196f7 | ||
|  | 9e493430d8 | ||
|  | 7690eedd70 | ||
|  | aaa270a52f | ||
|  | 6f47a5415c | ||
|  | 42c1709633 | ||
|  | 4685ff4145 | ||
|  | 249b01d7a8 | ||
|  | bcbdf58376 | ||
|  | 7f8540ff4e | ||
|  | b52ab91606 | ||
|  | 7246ac55b6 | ||
|  | 848931da21 | ||
|  | bf4092e746 | ||
|  | 7cee18c926 | ||
|  | e9a4b43268 | ||
|  | b1522742cc | ||
|  | ecdaaab789 | ||
|  | 3379942086 | ||
|  | 12eaa8c074 | ||
|  | c6310d636d | ||
|  | 9bedc6a138 | ||
|  | 70dbc6018f | ||
|  | 3afeb44b1d | ||
|  | 3131b8965e | ||
|  | bbc2fc9e42 | ||
|  | 3b4141eb4d | ||
|  | 5f826be82c | ||
|  | db9ee93d2b | ||
|  | 1eabd29105 | ||
|  | dc05c56440 | ||
|  | cb88ab2080 | ||
|  | 254e1a9bc1 | ||
|  | 0fc7c297bf | ||
|  | 82bfb6a3c3 | ||
|  | 277c622475 | ||
|  | 091a0f3efe | ||
|  | 823c76aa08 | ||
|  | 37dee2a50b | ||
|  | ea0f5101ec | ||
|  | be650d093d | ||
|  | 734814d96b | ||
|  | d9246b9b42 | ||
|  | 70e7e630c3 | ||
|  | 71fc49252f | ||
|  | aa0771877f | ||
|  | e5a4d11c0b | ||
|  | 909949a9e1 | ||
|  | f018696219 | ||
|  | 347a47d0d2 | ||
|  | c3de4b86b0 | ||
|  | 594e77ca41 | ||
|  | 31ce7a13ea | ||
|  | 5d762001c8 | ||
|  | bc3d339d9c | ||
|  | 33e5ed9ddc | ||
|  | 2d712b0ef7 | ||
|  | 13d81a4e4b | ||
|  | 8d917aee12 | ||
|  | 09b2cfc46a | ||
|  | 789e475207 | ||
|  | eec5105e5b | ||
|  | 82f7b2109c | ||
|  | 840dd58c03 | ||
|  | 37d1377f99 | ||
|  | 882a3545e5 | ||
|  | 778191fd11 | ||
|  | 71701804ea | ||
|  | 306c404362 | ||
|  | 4fab4f87d3 | ||
|  | 54717a81b1 | ||
|  | eca299454b | ||
|  | c58083f84a | ||
|  | ecf96252b9 | ||
|  | 92e648490a | ||
|  | 6dd3c26ddd | ||
|  | 02ea26b090 | ||
|  | 0954b5a741 | ||
|  | 02b6c3b721 | ||
|  | 6fa4e13ba2 | ||
|  | 9837f79f9c | ||
|  | 6e4cd22118 | ||
|  | ca00dd8e2d | ||
|  | 5455ec7bde | ||
|  | 2e7af1b050 | ||
|  | 89bae8d25e | ||
|  | 5b5c272909 | ||
|  | 3e394a3840 | ||
|  | ab8094e1c0 | ||
|  | bbb5f1c7c7 | ||
|  | b607f188af | ||
|  | 9ab1a674fe | ||
|  | 2f0a1391b7 | ||
|  | a9a1358b08 | ||
|  | 4853174d03 | ||
|  | 538d789366 | ||
|  | 0751919b82 | ||
|  | 99b2a84667 | ||
|  | 9bd6aac09c | ||
|  | 7be35a90c1 | ||
|  | eae5b8bad9 | ||
|  | 0c85342404 | ||
|  | 9ddcdb3ab2 | ||
|  | 1c537cf5da | ||
|  | 607a90cccc | ||
|  | 3d1c6fc5f0 | ||
|  | df1d3677e8 | ||
|  | 4da2ac9b35 | ||
|  | b4ae7d8538 | ||
|  | 870b679e0e | ||
|  | 4656a85732 | ||
|  | 4949913ccb | ||
|  | 580bd5ac0c | ||
|  | 2bf3448d18 | ||
|  | e9bc51ca3d | ||
|  | 5ddae116e0 | ||
|  | 13566bc6fd | ||
|  | 642f95a3f8 | ||
|  | 5a1d21ef31 | ||
|  | 7dcd39f82f | ||
|  | 655aa89bd6 | ||
|  | feb88ab685 | ||
|  | 79b4bad014 | ||
|  | bcd5092427 | ||
|  | 554a83fa01 | ||
|  | 57acb62520 | ||
|  | 52ed5f2285 | ||
|  | dd1d253ea5 | ||
|  | e40979a874 | ||
|  | 2ddbc2656b | ||
|  | 7351fce395 | ||
|  | e4b0bbf7f7 | ||
|  | f71121e6b2 | ||
|  | 9114dec695 | ||
|  | 2d5ed67ae1 | ||
|  | dad6322bac | ||
|  | 05a9406a32 | ||
|  | 2a3822e781 | ||
|  | c69b3a2936 | ||
|  | 6d4b86a730 | ||
|  | 9c2ee58433 | ||
|  | f84593882d | ||
|  | 1241261ca4 | ||
|  | ebe92335a6 | ||
|  | 35f2f33ae3 | ||
|  | f0f84b304b | ||
|  | 23146417e6 | ||
|  | bd3155d39b | ||
|  | b5fe0a642b | ||
|  | b4b29878db | ||
|  | 9e68d38df8 | ||
|  | 574d363d7c | ||
|  | 69a011bc86 | ||
|  | fe969c57c4 | ||
|  | 88d8b72c57 | ||
|  | 5639797c8d | ||
|  | 049a9cee06 | ||
|  | 14faf57a9e | ||
|  | e19b548eff | ||
|  | e3d84c40f7 | ||
|  | 50d49219a5 | ||
|  | 96209c852c | ||
|  | 8e40c50cc1 | ||
|  | 4b0f0141c9 | ||
|  | d1bd21a601 | ||
|  | c6925ba4c3 | ||
|  | 52e311d847 | ||
|  | f2f18d260d | ||
|  | 1d293741ba | ||
|  | 5db288fc3c | ||
|  | d628f9b3ca | ||
|  | fe8a6d96e4 | ||
|  | bd16b8c851 | ||
|  | c4a22c18f7 | ||
|  | e38c24f9ed | ||
|  | 83a7534a74 | 
							
								
								
									
										203
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										203
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,203 +1,4 @@ | |||||||
| ## Ignore Visual Studio temporary files, build results, and | /public/node_modules | ||||||
| ## files generated by popular Visual Studio add-ons. |  | ||||||
|  |  | ||||||
| # User-specific files |  | ||||||
| *.suo |  | ||||||
| *.user |  | ||||||
| *.userosscache |  | ||||||
| *.sln.docstates |  | ||||||
|  |  | ||||||
| # User-specific files (MonoDevelop/Xamarin Studio) |  | ||||||
| *.userprefs |  | ||||||
|  |  | ||||||
| # Build results |  | ||||||
| [Dd]ebug/ |  | ||||||
| [Dd]ebugPublic/ |  | ||||||
| [Rr]elease/ |  | ||||||
| [Rr]eleases/ |  | ||||||
| x64/ |  | ||||||
| x86/ |  | ||||||
| build/ |  | ||||||
| bld/ |  | ||||||
| [Bb]in/ |  | ||||||
| [Oo]bj/ |  | ||||||
|  |  | ||||||
| # Visual Studo 2015 cache/options directory |  | ||||||
| .vs/ |  | ||||||
|  |  | ||||||
| # MSTest test Results |  | ||||||
| [Tt]est[Rr]esult*/ |  | ||||||
| [Bb]uild[Ll]og.* |  | ||||||
|  |  | ||||||
| # NUNIT |  | ||||||
| *.VisualState.xml |  | ||||||
| TestResult.xml |  | ||||||
|  |  | ||||||
| # Build Results of an ATL Project |  | ||||||
| [Dd]ebugPS/ |  | ||||||
| [Rr]eleasePS/ |  | ||||||
| dlldata.c |  | ||||||
|  |  | ||||||
| *_i.c |  | ||||||
| *_p.c |  | ||||||
| *_i.h |  | ||||||
| *.ilk |  | ||||||
| *.meta |  | ||||||
| *.obj |  | ||||||
| *.pch |  | ||||||
| *.pdb |  | ||||||
| *.pgc |  | ||||||
| *.pgd |  | ||||||
| *.rsp |  | ||||||
| *.sbr |  | ||||||
| *.tlb |  | ||||||
| *.tli |  | ||||||
| *.tlh |  | ||||||
| *.tmp |  | ||||||
| *.tmp_proj |  | ||||||
| *.log |  | ||||||
| *.vspscc |  | ||||||
| *.vssscc |  | ||||||
| .builds |  | ||||||
| *.pidb |  | ||||||
| *.svclog |  | ||||||
| *.scc |  | ||||||
|  |  | ||||||
| # Chutzpah Test files |  | ||||||
| _Chutzpah* |  | ||||||
|  |  | ||||||
| # Visual C++ cache files |  | ||||||
| ipch/ |  | ||||||
| *.aps |  | ||||||
| *.ncb |  | ||||||
| *.opensdf |  | ||||||
| *.sdf |  | ||||||
| *.cachefile |  | ||||||
|  |  | ||||||
| # Visual Studio profiler |  | ||||||
| *.psess |  | ||||||
| *.vsp |  | ||||||
| *.vspx |  | ||||||
|  |  | ||||||
| # TFS 2012 Local Workspace |  | ||||||
| $tf/ |  | ||||||
|  |  | ||||||
| # Guidance Automation Toolkit |  | ||||||
| *.gpState |  | ||||||
|  |  | ||||||
| # ReSharper is a .NET coding add-in |  | ||||||
| _ReSharper*/ |  | ||||||
| *.[Rr]e[Ss]harper |  | ||||||
| *.DotSettings.user |  | ||||||
|  |  | ||||||
| # JustCode is a .NET coding addin-in |  | ||||||
| .JustCode |  | ||||||
|  |  | ||||||
| # TeamCity is a build add-in |  | ||||||
| _TeamCity* |  | ||||||
|  |  | ||||||
| # DotCover is a Code Coverage Tool |  | ||||||
| *.dotCover |  | ||||||
|  |  | ||||||
| # NCrunch |  | ||||||
| _NCrunch_* |  | ||||||
| .*crunch*.local.xml |  | ||||||
|  |  | ||||||
| # MightyMoose |  | ||||||
| *.mm.* |  | ||||||
| AutoTest.Net/ |  | ||||||
|  |  | ||||||
| # Web workbench (sass) |  | ||||||
| .sass-cache/ |  | ||||||
|  |  | ||||||
| # Installshield output folder |  | ||||||
| [Ee]xpress/ |  | ||||||
|  |  | ||||||
| # DocProject is a documentation generator add-in |  | ||||||
| DocProject/buildhelp/ |  | ||||||
| DocProject/Help/*.HxT |  | ||||||
| DocProject/Help/*.HxC |  | ||||||
| DocProject/Help/*.hhc |  | ||||||
| DocProject/Help/*.hhk |  | ||||||
| DocProject/Help/*.hhp |  | ||||||
| DocProject/Help/Html2 |  | ||||||
| DocProject/Help/html |  | ||||||
|  |  | ||||||
| # Click-Once directory |  | ||||||
| publish/ |  | ||||||
|  |  | ||||||
| # Publish Web Output |  | ||||||
| *.[Pp]ublish.xml |  | ||||||
| *.azurePubxml |  | ||||||
| # TODO: Comment the next line if you want to checkin your web deploy settings  |  | ||||||
| # but database connection strings (with potential passwords) will be unencrypted |  | ||||||
| *.pubxml |  | ||||||
| *.publishproj |  | ||||||
|  |  | ||||||
| # NuGet Packages |  | ||||||
| *.nupkg |  | ||||||
| # The packages folder can be ignored because of Package Restore |  | ||||||
| **/packages/* |  | ||||||
| # except build/, which is used as an MSBuild target. |  | ||||||
| !**/packages/build/ |  | ||||||
| # Uncomment if necessary however generally it will be regenerated when needed |  | ||||||
| #!**/packages/repositories.config |  | ||||||
|  |  | ||||||
| # Windows Azure Build Output |  | ||||||
| csx/ |  | ||||||
| *.build.csdef |  | ||||||
|  |  | ||||||
| # Windows Store app package directory |  | ||||||
| AppPackages/ |  | ||||||
|  |  | ||||||
| # Others |  | ||||||
| *.[Cc]ache |  | ||||||
| ClientBin/ |  | ||||||
| [Ss]tyle[Cc]op.* |  | ||||||
| ~$* |  | ||||||
| *~ |  | ||||||
| *.dbmdl |  | ||||||
| *.dbproj.schemaview |  | ||||||
| *.pfx |  | ||||||
| *.publishsettings |  | ||||||
| node_modules/ |  | ||||||
| bower_components/ |  | ||||||
|  |  | ||||||
| # RIA/Silverlight projects |  | ||||||
| Generated_Code/ |  | ||||||
|  |  | ||||||
| # Backup & report files from converting an old project file |  | ||||||
| # to a newer Visual Studio version. Backup files are not needed, |  | ||||||
| # because we have git ;-) |  | ||||||
| _UpgradeReport_Files/ |  | ||||||
| Backup*/ |  | ||||||
| UpgradeLog*.XML |  | ||||||
| UpgradeLog*.htm |  | ||||||
|  |  | ||||||
| # SQL Server files |  | ||||||
| *.mdf |  | ||||||
| *.ldf |  | ||||||
|  |  | ||||||
| # Business Intelligence projects |  | ||||||
| *.rdl.data |  | ||||||
| *.bim.layout |  | ||||||
| *.bim_*.settings |  | ||||||
|  |  | ||||||
| # Microsoft Fakes |  | ||||||
| FakesAssemblies/ |  | ||||||
|  |  | ||||||
| # Node.js Tools for Visual Studio |  | ||||||
| .ntvs_analysis.dat |  | ||||||
|  |  | ||||||
| # Visual Studio 6 build log |  | ||||||
| *.plg |  | ||||||
|  |  | ||||||
| # Visual Studio 6 workspace options file |  | ||||||
| *.opt |  | ||||||
|  |  | ||||||
| /bower_components |  | ||||||
| /vendor | /vendor | ||||||
| /.release | /.release | ||||||
| /config.php | embedded.txt | ||||||
| /composer.phar |  | ||||||
| /composer.lock |  | ||||||
							
								
								
									
										37
									
								
								.tx/config
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.tx/config
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | [main] | ||||||
|  | host = https://www.transifex.com | ||||||
|  |  | ||||||
|  | [grocy.stringsphp] | ||||||
|  | file_filter = localization/<lang>/strings.php | ||||||
|  | minimum_perc = 0 | ||||||
|  | source_file = localization/en/strings.php | ||||||
|  | source_lang = en | ||||||
|  | type = PHP_ARRAY | ||||||
|  |  | ||||||
|  | [grocy.stock_transaction_typesphp] | ||||||
|  | file_filter = localization/<lang>/stock_transaction_types.php | ||||||
|  | minimum_perc = 0 | ||||||
|  | source_file = localization/en/stock_transaction_types.php | ||||||
|  | source_lang = en | ||||||
|  | type = PHP_ARRAY | ||||||
|  |  | ||||||
|  | [grocy.chore_typesphp] | ||||||
|  | file_filter = localization/<lang>/chore_types.php | ||||||
|  | minimum_perc = 0 | ||||||
|  | source_file = localization/en/chore_types.php | ||||||
|  | source_lang = en | ||||||
|  | type = PHP_ARRAY | ||||||
|  |  | ||||||
|  | [grocy.component_translationsphp] | ||||||
|  | file_filter = localization/<lang>/component_translations.php | ||||||
|  | minimum_perc = 0 | ||||||
|  | source_file = localization/en/component_translations.php | ||||||
|  | source_lang = en | ||||||
|  | type = PHP_ARRAY | ||||||
|  |  | ||||||
|  | [grocy.demo_dataphp] | ||||||
|  | file_filter = localization/<lang>/demo_data.php | ||||||
|  | minimum_perc = 0 | ||||||
|  | source_file = localization/en/demo_data.php | ||||||
|  | source_lang = en | ||||||
|  | type = PHP_ARRAY | ||||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  | 	"phpserver.relativePath": "public" | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								.yarnrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.yarnrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | --modules-folder public/node_modules | ||||||
|  | --install.production true | ||||||
|  | --install.ignore-scripts true | ||||||
|  | --install.ignore-optional true | ||||||
| @@ -1,83 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| class GrocyDbMigrator |  | ||||||
| { |  | ||||||
| 	public static function MigrateDb(PDO $pdo) |  | ||||||
| 	{ |  | ||||||
| 		self::ExecuteMigrationWhenNeeded($pdo, 1, " |  | ||||||
| 			CREATE TABLE products ( |  | ||||||
| 				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, |  | ||||||
| 				name TEXT NOT NULL UNIQUE, |  | ||||||
| 				description TEXT, |  | ||||||
| 				location_id INTEGER NOT NULL, |  | ||||||
| 				qu_id_purchase INTEGER NOT NULL, |  | ||||||
| 				qu_id_stock INTEGER NOT NULL, |  | ||||||
| 				qu_factor_purchase_to_stock REAL NOT NULL, |  | ||||||
| 				barcode TEXT UNIQUE, |  | ||||||
| 				created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) |  | ||||||
| 			)" |  | ||||||
| 		); |  | ||||||
|  |  | ||||||
| 		self::ExecuteMigrationWhenNeeded($pdo, 2, " |  | ||||||
| 			CREATE TABLE locations ( |  | ||||||
| 				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, |  | ||||||
| 				name TEXT NOT NULL UNIQUE, |  | ||||||
| 				description TEXT, |  | ||||||
| 				created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) |  | ||||||
| 			)" |  | ||||||
| 		); |  | ||||||
|  |  | ||||||
| 		self::ExecuteMigrationWhenNeeded($pdo, 3, " |  | ||||||
| 			CREATE TABLE quantity_units ( |  | ||||||
| 				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, |  | ||||||
| 				name TEXT NOT NULL UNIQUE, |  | ||||||
| 				description TEXT, |  | ||||||
| 				created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) |  | ||||||
| 			)" |  | ||||||
| 		); |  | ||||||
|  |  | ||||||
| 		self::ExecuteMigrationWhenNeeded($pdo, 4, " |  | ||||||
| 			CREATE TABLE stock ( |  | ||||||
| 				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, |  | ||||||
| 				product_id INTEGER NOT NULL, |  | ||||||
| 				amount INTEGER NOT NULL, |  | ||||||
| 				best_before_date DATE, |  | ||||||
| 				purchased_date DATE DEFAULT (datetime('now', 'localtime')), |  | ||||||
| 				stock_id TEXT NOT NULL |  | ||||||
| 			)" |  | ||||||
| 		); |  | ||||||
|  |  | ||||||
| 		self::ExecuteMigrationWhenNeeded($pdo, 5, " |  | ||||||
| 			CREATE TABLE consumptions ( |  | ||||||
| 				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, |  | ||||||
| 				product_id INTEGER NOT NULL, |  | ||||||
| 				amount INTEGER NOT NULL, |  | ||||||
| 				best_before_date DATE, |  | ||||||
| 				purchased_date DATE, |  | ||||||
| 				used_date DATE DEFAULT (datetime('now', 'localtime')), |  | ||||||
| 				spoiled INTEGER NOT NULL DEFAULT 0, |  | ||||||
| 				stock_id TEXT NOT NULL |  | ||||||
| 			)" |  | ||||||
| 		); |  | ||||||
|  |  | ||||||
| 		self::ExecuteMigrationWhenNeeded($pdo, 6, " |  | ||||||
| 			INSERT INTO locations (name, description) VALUES ('DefaultLocation', 'This is the first default location, edit or delete it'); |  | ||||||
| 			INSERT INTO quantity_units (name, description) VALUES ('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it'); |  | ||||||
| 			INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1); |  | ||||||
| 			INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1);" |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	private static function ExecuteMigrationWhenNeeded(PDO $pdo, int $migrationId, string $sql) |  | ||||||
| 	{ |  | ||||||
| 		if ($pdo->query("SELECT COUNT(*) FROM migrations WHERE migration = $migrationId")->fetchColumn() == 0) |  | ||||||
| 		{ |  | ||||||
| 			if ($pdo->exec(utf8_encode($sql)) === false) |  | ||||||
| 			{ |  | ||||||
| 				throw new Exception($pdo->errorInfo()); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			$pdo->exec('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')'); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| class GrocyDemoDataGenerator |  | ||||||
| { |  | ||||||
| 	public static function PopulateDemoData(PDO $pdo) |  | ||||||
| 	{ |  | ||||||
| 		$sql = " |  | ||||||
| 			UPDATE locations SET name = 'Vorratskammer', description = '' WHERE id = 1; |  | ||||||
| 			INSERT INTO locations (name) VALUES ('S<><53>igkeitenschrank'); |  | ||||||
| 			INSERT INTO locations (name) VALUES ('Konvervenschrank'); |  | ||||||
|  |  | ||||||
| 			UPDATE quantity_units SET name = 'St<53>ck' WHERE id = 1; |  | ||||||
| 			INSERT INTO quantity_units (name) VALUES ('Packung'); |  | ||||||
|  |  | ||||||
| 			INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gummib<69>rchen', 2, 2, 2, 1); |  | ||||||
| 			INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Chips', 2, 2, 2, 1); |  | ||||||
| 			INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Eier', 1, 2, 1, 10); |  | ||||||
|  |  | ||||||
| 			INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (3, 5, date('now', '+180 day'), '".uniqid()."'); |  | ||||||
| 			INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (4, 5, date('now', '+180 day'), '".uniqid()."'); |  | ||||||
| 			INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (5, 5, date('now', '+25 day'), '".uniqid()."'); |  | ||||||
| 		"; |  | ||||||
|  |  | ||||||
| 		if ($pdo->exec(utf8_encode($sql)) === false) |  | ||||||
| 		{ |  | ||||||
| 			throw new Exception($pdo->errorInfo()); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,89 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| class GrocyLogicStock |  | ||||||
| { |  | ||||||
| 	public static function GetCurrentStock() |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnectionRaw(); |  | ||||||
| 		return $db->query('SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date from stock GROUP BY product_id ORDER BY MIN(best_before_date) ASC')->fetchAll(PDO::FETCH_OBJ); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public static function GetProductDetails(int $productId) |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 		$product = $db->products($productId); |  | ||||||
| 		$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount'); |  | ||||||
| 		$productLastPurchased = $db->stock()->where('product_id', $productId)->max('purchased_date'); |  | ||||||
| 		$productLastUsed = $db->consumptions()->where('product_id', $productId)->max('used_date'); |  | ||||||
| 		$quPurchase = $db->quantity_units($product->qu_id_purchase); |  | ||||||
| 		$quStock = $db->quantity_units($product->qu_id_stock); |  | ||||||
|  |  | ||||||
| 		return array( |  | ||||||
| 			'product' => $product, |  | ||||||
| 			'last_purchased' => $productLastPurchased, |  | ||||||
| 			'last_used' => $productLastUsed, |  | ||||||
| 			'stock_amount' => $productStockAmount, |  | ||||||
| 			'quantity_unit_purchase' => $quPurchase, |  | ||||||
| 			'quantity_unit_stock' => $quStock |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public static function ConsumeProduct(int $productId, int $amount, bool $spoiled) |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 		$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount'); |  | ||||||
| 		$potentialStockEntries = $db->stock()->where('product_id', $productId)->orderBy('purchased_date', 'ASC')->fetchAll(); //FIFO |  | ||||||
|  |  | ||||||
| 		if ($amount > $productStockAmount) |  | ||||||
| 		{ |  | ||||||
| 			return false; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		foreach ($potentialStockEntries as $stockEntry) |  | ||||||
| 		{ |  | ||||||
| 			if ($amount == 0) |  | ||||||
| 			{ |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if ($amount >= $stockEntry->amount) //Take the whole stock entry |  | ||||||
| 			{ |  | ||||||
| 				$consumptionRow = $db->consumptions()->createRow(array( |  | ||||||
| 					'product_id' => $stockEntry->product_id, |  | ||||||
| 					'amount' => $stockEntry->amount, |  | ||||||
| 					'best_before_date' => $stockEntry->best_before_date, |  | ||||||
| 					'purchased_date' => $stockEntry->purchased_date, |  | ||||||
| 					'spoiled' => $spoiled, |  | ||||||
| 					'stock_id' => $stockEntry->stock_id |  | ||||||
| 				)); |  | ||||||
| 				$consumptionRow->save(); |  | ||||||
|  |  | ||||||
| 				$amount -= $stockEntry->amount; |  | ||||||
| 				$stockEntry->delete(); |  | ||||||
| 			} |  | ||||||
| 			else //Stock entry amount is > than needed amount -> split the stock entry resp. update the amount |  | ||||||
| 			{ |  | ||||||
| 				$consumptionRow = $db->consumptions()->createRow(array( |  | ||||||
| 					'product_id' => $stockEntry->product_id, |  | ||||||
| 					'amount' => $amount, |  | ||||||
| 					'best_before_date' => $stockEntry->best_before_date, |  | ||||||
| 					'purchased_date' => $stockEntry->purchased_date, |  | ||||||
| 					'spoiled' => $spoiled, |  | ||||||
| 					'stock_id' => $stockEntry->stock_id |  | ||||||
| 				)); |  | ||||||
| 				$consumptionRow->save(); |  | ||||||
|  |  | ||||||
| 				$restStockAmount = $stockEntry->amount - $amount; |  | ||||||
| 				$amount = 0; |  | ||||||
|  |  | ||||||
| 				$stockEntry->update(array( |  | ||||||
| 					'amount' => $restStockAmount |  | ||||||
| 				)); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| class GrocyPhpHelper |  | ||||||
| { |  | ||||||
| 	public static function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue) |  | ||||||
| 	{ |  | ||||||
| 		foreach($array as $object) |  | ||||||
| 		{ |  | ||||||
| 			if($object->{$propertyName} == $propertyValue) |  | ||||||
| 			{ |  | ||||||
| 				return $object; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return null; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										99
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,20 +1,99 @@ | |||||||
| # grocy | # grocy | ||||||
| ERP beyond your fridge | ERP beyond your fridge | ||||||
|  |  | ||||||
|  | ## Give it a try | ||||||
|  | - Public demo of the latest stable version → [https://demo.grocy.info](https://demo.grocy.info) | ||||||
|  | - Public demo of the latest pre-release version (current master branch) → [https://demo-prerelease.grocy.info](https://demo-prerelease.grocy.info) | ||||||
|  |  | ||||||
| ## Motivation | ## 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 houshold management"-thing. | 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! | ||||||
|  |  | ||||||
| ## What it is about |  | ||||||
| For now my main focus is on stock management, ERP your fridge! |  | ||||||
|  |  | ||||||
| # Give it a try |  | ||||||
| Public demo of the latest version → [https://grocy.projectdemos.berrnd.org](https://grocy.projectdemos.berrnd.org)  |  | ||||||
|  |  | ||||||
| ## How to install | ## How to install | ||||||
| Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP enabled webserver, copy `config-dist.php` to `config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go. Alternatively clone this repository and install Composer and Bower dependencies manually. | > **NEW** | ||||||
|  | > | ||||||
|  | > 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/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"... | ||||||
|  |  | ||||||
| ## Todo | 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). | ||||||
| A lot... |  | ||||||
|  | Alternatively clone this repository and install Composer and Yarn dependencies manually. | ||||||
|  |  | ||||||
|  | If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block. | ||||||
|  |  | ||||||
|  | If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php` (`Setting('DISABLE_URL_REWRITING', true);`). | ||||||
|  |  | ||||||
|  | ## How to run using Docker | ||||||
|  |  | ||||||
|  | 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`. | ||||||
|  |  | ||||||
|  | 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. | ||||||
|  | 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. | ||||||
|  |  | ||||||
|  | ## Things worth to know | ||||||
|  |  | ||||||
|  | ### REST API & data model documentation | ||||||
|  | See the integrated Swagger UI instance on [/api](https://demo-en.grocy.info/api). | ||||||
|  |  | ||||||
|  | ### Barcode readers | ||||||
|  | Some fields also allow to select a value by scanning a barcode. It works best when your barcode reader prefixes every barcode with a letter which is normally not part of a item name (I use a `$`) and sends a `TAB` after a scan. | ||||||
|  |  | ||||||
|  | ### Input shorthands for date fields | ||||||
|  | For (productivity) reasons all date (and time) input fields use the ISO-8601 format regardless of localization. | ||||||
|  | The following shorthands are available: | ||||||
|  | - `MMDD` gets expanded to the given day on the current year, if > today, or to the given day next year, if < today, in proper notation | ||||||
|  |   - Example: `0517` will be converted to `2018-05-17` | ||||||
|  | - `YYYYMMDD` gets expanded to the proper ISO-8601 notation | ||||||
|  |   - Example: `20190417` will be converted to `2019-04-17` | ||||||
|  | - `YYYYMMe` or `YYYYMM+` gets expanded to the end of the given month in the given year in proper notation | ||||||
|  |   - Example: `201807e` will be converted to `2018-07-31` | ||||||
|  | - `x` gets expanded to `2999-12-31` (which I use for products which never expire) | ||||||
|  | - Down/up arrow keys will increase/decrease the date by one day | ||||||
|  | - Right/left arrow keys will increase/decrease the date by 1 week | ||||||
|  |  | ||||||
|  | ### Keyboard shorthands for buttons | ||||||
|  | Wherever a button contains a bold highlighted letter, this is a shortcut key. | ||||||
|  | Example: Button "Add as new **p**roduct" can be "pressed" by using the `P` key on your keyboard. | ||||||
|  |  | ||||||
|  | ### Barcode lookup via external services | ||||||
|  | Products can be directly added to the database via looking them up against external services by a barcode. | ||||||
|  | This is currently only possible through the REST API. | ||||||
|  | There is no plugin included for any service, see the reference implementation in `data/plugins/DemoBarcodeLookupPlugin.php`. | ||||||
|  |  | ||||||
|  | ### Database migrations | ||||||
|  | Database schema migration is automatically done when visiting the root (`/`) route (click on the logo in the left upper edge). | ||||||
|  |  | ||||||
|  | ### 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 | ||||||
|  |  | ||||||
|  | ### Demo mode | ||||||
|  | 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/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). | ||||||
|  |  | ||||||
|  | ## Screenshots | ||||||
|  | #### Dashboard | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #### Purchase - with barcode scan | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #### Consume - with manual search | ||||||
|  |  | ||||||
|  |  | ||||||
| ## License | ## License | ||||||
| The MIT License (MIT) | The MIT License (MIT) | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								app.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | use \Psr\Http\Message\ServerRequestInterface as Request; | ||||||
|  | use \Psr\Http\Message\ResponseInterface as Response; | ||||||
|  |  | ||||||
|  | use \Grocy\Helpers\UrlManager; | ||||||
|  | use \Grocy\Controllers\LoginController; | ||||||
|  |  | ||||||
|  | // Definitions for embedded mode | ||||||
|  | if (file_exists(__DIR__ . '/embedded.txt')) | ||||||
|  | { | ||||||
|  | 	define('GROCY_IS_EMBEDDED_INSTALL', true); | ||||||
|  | 	define('GROCY_DATAPATH', file_get_contents(__DIR__ . '/embedded.txt')); | ||||||
|  | 	define('GROCY_USER_ID', 1); | ||||||
|  | } | ||||||
|  | else | ||||||
|  | { | ||||||
|  | 	define('GROCY_IS_EMBEDDED_INSTALL', false); | ||||||
|  | 	define('GROCY_DATAPATH', __DIR__ . '/data'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Definitions for demo mode | ||||||
|  | if (file_exists(GROCY_DATAPATH . '/demo.txt')) | ||||||
|  | { | ||||||
|  | 	define('GROCY_IS_DEMO_INSTALL', true); | ||||||
|  | 	if (!defined('GROCY_USER_ID')) | ||||||
|  | 	{ | ||||||
|  | 		define('GROCY_USER_ID', 1); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | else | ||||||
|  | { | ||||||
|  | 	define('GROCY_IS_DEMO_INSTALL', false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Load composer dependencies | ||||||
|  | require_once __DIR__ . '/vendor/autoload.php'; | ||||||
|  |  | ||||||
|  | // Load config files | ||||||
|  | require_once GROCY_DATAPATH . '/config.php'; | ||||||
|  | require_once __DIR__ . '/config-dist.php'; //For not in own config defined values we use the default ones | ||||||
|  |  | ||||||
|  | // Setup base application | ||||||
|  | $appContainer = new \Slim\Container([ | ||||||
|  | 	'settings' => [ | ||||||
|  | 		'displayErrorDetails' => true, | ||||||
|  | 		'determineRouteBeforeAppMiddleware' => true | ||||||
|  | 	], | ||||||
|  | 	'view' => function($container) | ||||||
|  | 	{ | ||||||
|  | 		return new \Slim\Views\Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache'); | ||||||
|  | 	}, | ||||||
|  | 	'LoginControllerInstance' => function($container) | ||||||
|  | 	{ | ||||||
|  | 		return new LoginController($container, 'grocy_session'); | ||||||
|  | 	}, | ||||||
|  | 	'UrlManager' => function($container) | ||||||
|  | 	{ | ||||||
|  | 		return new UrlManager(GROCY_BASE_URL); | ||||||
|  | 	}, | ||||||
|  | 	'ApiKeyHeaderName' => function($container) | ||||||
|  | 	{ | ||||||
|  | 		return 'GROCY-API-KEY'; | ||||||
|  | 	} | ||||||
|  | ]); | ||||||
|  | $app = new \Slim\App($appContainer); | ||||||
|  |  | ||||||
|  | // Load routes from separate file | ||||||
|  | require_once __DIR__ . '/routes.php'; | ||||||
|  |  | ||||||
|  | $app->run(); | ||||||
							
								
								
									
										21
									
								
								bower.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								bower.json
									
									
									
									
									
								
							| @@ -1,21 +0,0 @@ | |||||||
| { |  | ||||||
| 	"name": "asp.net", |  | ||||||
| 	"private": true, |  | ||||||
| 	"dependencies": { |  | ||||||
| 		"bootstrap": "3.3.7", |  | ||||||
| 		"font-awesome": "4.7.0", |  | ||||||
| 		"bootbox": "4.4.0", |  | ||||||
| 		"jquery.serializeJSON": "2.7.2", |  | ||||||
| 		"bootstrap-validator": "0.11.9", |  | ||||||
| 		"bootstrap-datepicker": "1.6.4", |  | ||||||
| 		"moment": "2.18.1", |  | ||||||
| 		"bootstrap-combobox": "1.1.8", |  | ||||||
| 		"datatables.net": "1.10.13", |  | ||||||
| 		"datatables.net-bs": "2.1.1", |  | ||||||
| 		"datatables.net-responsive": "2.1.1", |  | ||||||
| 		"datatables.net-responsive-bs": "2.1.1", |  | ||||||
| 		"jquery-timeago": "1.5.4", |  | ||||||
| 		"toastr": "2.1.3", |  | ||||||
| 		"tagmanager": "3.0.2" |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -4,8 +4,10 @@ if %projectPath:~-1%==\ set projectPath=%projectPath:~0,-1% | |||||||
| set releasePath=%projectPath%\.release | set releasePath=%projectPath%\.release | ||||||
| mkdir "%releasePath%" | mkdir "%releasePath%" | ||||||
|  |  | ||||||
| for /f "tokens=*" %%a in ('type version.txt') do set version=%%a | for /f "tokens=*" %%a in ('build_tools\jq.exe .Version version.json --raw-output') do set version=%%a | ||||||
|  |  | ||||||
| del "%releasePath%\grocy_%version%.zip" | 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!composer.phar -xr!grocy.phpproj -xr!grocy.phpproj.user -xr!grocy.sln | "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" d "%releasePath%\grocy_%version%.zip" data\add_before_end_body.html data\demo.txt data\grocy.db data\.gitignore config.php bower.json | "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\* | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								build_tools/jq.exe
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								build_tools/jq.exe
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1,8 +1,20 @@ | |||||||
| { | { | ||||||
|     "require": { | 	"require": { | ||||||
|         "slim/slim": "^3.8", | 		"php": ">=7.2", | ||||||
|         "slim/php-view": "^2.2", | 		"slim/slim": "^3.8", | ||||||
|         "morris/lessql": "^0.3.4", | 		"morris/lessql": "^0.3.4", | ||||||
|         "tuupola/slim-basic-auth": "^2.2" | 		"rubellum/slim-blade-view": "^0.1.1", | ||||||
|     } | 		"tuupola/cors-middleware": "^0.7.0" | ||||||
|  | 	}, | ||||||
|  | 	"autoload": { | ||||||
|  | 		"psr-4": { | ||||||
|  | 			"Grocy\\Services\\": "services/", | ||||||
|  | 			"Grocy\\Controllers\\": "controllers/", | ||||||
|  | 			"Grocy\\Middleware\\": "middleware/", | ||||||
|  | 			"Grocy\\Helpers\\": "helpers/" | ||||||
|  | 		}, | ||||||
|  | 		"files": [ | ||||||
|  | 			"helpers/extensions.php" | ||||||
|  | 		] | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1576
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1576
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,4 +1,55 @@ | |||||||
| <?php | <?php | ||||||
|  |  | ||||||
| define('HTTP_USER', 'admin'); | # Either "production", "dev" or "prerelease" | ||||||
| define('HTTP_PASSWORD', 'admin'); | Setting('MODE', 'production'); | ||||||
|  |  | ||||||
|  | # Either "en" or "de" or the filename (without extension) of | ||||||
|  | # one of the other available localization files in the "/localization" directory | ||||||
|  | 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', '$'); | ||||||
|  |  | ||||||
|  | # 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 | ||||||
|  | Setting('BASE_URL', '/'); | ||||||
|  |  | ||||||
|  | # The plugin to use for external barcode lookups, | ||||||
|  | # must be the filename without .php extension and must be located in /data/plugins, | ||||||
|  | # see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation | ||||||
|  | Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin'); | ||||||
|  |  | ||||||
|  | # If, however, your webserver does not support URL rewriting, | ||||||
|  | # set this to true | ||||||
|  | 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 | ||||||
|  |  | ||||||
|  | # Night mode related | ||||||
|  | DefaultUserSetting('night_mode_enabled', false); // If night mode is enabled always | ||||||
|  | DefaultUserSetting('auto_night_mode_enabled', false); // If night mode is enabled automatically when inside a given time range (see the two settings below) | ||||||
|  | DefaultUserSetting('auto_night_mode_time_range_from', "20:00"); // Format HH:mm | ||||||
|  | DefaultUserSetting('auto_night_mode_time_range_to', "07:00"); // Format HH:mm | ||||||
|  | DefaultUserSetting('auto_night_mode_time_range_goes_over_midnight', true); // If the time range above goes over midnight | ||||||
|  | DefaultUserSetting('currently_inside_night_mode_range', false); // If we're currently inside of night mode time range (this is not user configurable, but stored as a user setting because it's evaluated client side to be able to use the client time instead of the maybe different server time) | ||||||
|  | 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) | ||||||
|  |  | ||||||
|  | # 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); | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								controllers/BaseApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								controllers/BaseApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | class BaseApiController extends BaseController | ||||||
|  | { | ||||||
|  |  | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json')); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $OpenApiSpec; | ||||||
|  |  | ||||||
|  | 	protected function ApiResponse($data) | ||||||
|  | 	{ | ||||||
|  | 		return json_encode($data); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected function VoidApiActionResponse($response, $success = true, $status = 200, $errorMessage = '') | ||||||
|  | 	{ | ||||||
|  | 		return $response->withStatus($status)->withJson(array( | ||||||
|  | 			'success' => $success, | ||||||
|  | 			'error_message' => $errorMessage | ||||||
|  | 		)); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								controllers/BaseController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								controllers/BaseController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\DatabaseService; | ||||||
|  | use \Grocy\Services\ApplicationService; | ||||||
|  | use \Grocy\Services\LocalizationService; | ||||||
|  | use \Grocy\Services\UsersService; | ||||||
|  |  | ||||||
|  | class BaseController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) { | ||||||
|  | 		$databaseService = new DatabaseService(); | ||||||
|  | 		$this->Database = $databaseService->GetDbConnection(); | ||||||
|  | 		 | ||||||
|  | 		$localizationService = new LocalizationService(GROCY_CULTURE); | ||||||
|  | 		$this->LocalizationService = $localizationService; | ||||||
|  |  | ||||||
|  | 		if (GROCY_MODE === 'prerelease') | ||||||
|  | 		{ | ||||||
|  | 			$commitHash = trim(exec('git log --pretty="%h" -n1 HEAD')); | ||||||
|  | 			$commitDate = trim(exec('git log --date=iso --pretty="%cd" -n1 HEAD')); | ||||||
|  | 			 | ||||||
|  | 			$container->view->set('version', "pre-release-$commitHash"); | ||||||
|  | 			$container->view->set('releaseDate', \substr($commitDate, 0, 19)); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			$applicationService = new ApplicationService(); | ||||||
|  | 			$versionInfo = $applicationService->GetInstalledVersion(); | ||||||
|  | 			$container->view->set('version', $versionInfo->Version); | ||||||
|  | 			$container->view->set('releaseDate', $versionInfo->ReleaseDate); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations()); | ||||||
|  | 		$container->view->set('L', function($text, ...$placeholderValues) use($localizationService) | ||||||
|  | 		{ | ||||||
|  | 			return $localizationService->Localize($text, ...$placeholderValues); | ||||||
|  | 		}); | ||||||
|  | 		$container->view->set('U', function($relativePath, $isResource = false) use($container) | ||||||
|  | 		{ | ||||||
|  | 			return $container->UrlManager->ConstructUrl($relativePath, $isResource); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		$embedded = false; | ||||||
|  | 		if (isset($container->request->getQueryParams()['embedded'])) | ||||||
|  | 		{ | ||||||
|  | 			$embedded = true; | ||||||
|  | 		} | ||||||
|  | 		$container->view->set('embedded', $embedded); | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$usersService = new UsersService(); | ||||||
|  | 			if (defined('GROCY_USER_ID')) | ||||||
|  | 			{ | ||||||
|  | 				$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			// Happens when database is not initialised or migrated... | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$this->AppContainer = $container; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $AppContainer; | ||||||
|  | 	protected $Database; | ||||||
|  | 	protected $LocalizationService; | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								controllers/BatteriesApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								controllers/BatteriesApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\BatteriesService; | ||||||
|  |  | ||||||
|  | class BatteriesApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->BatteriesService = new BatteriesService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $BatteriesService; | ||||||
|  |  | ||||||
|  | 	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']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$chargeCycleId = $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime); | ||||||
|  | 			return $this->ApiResponse(array('charge_cycle_id' => $chargeCycleId)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function BatteryDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			return $this->ApiResponse($this->BatteriesService->GetBatteryDetails($args['batteryId'])); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								controllers/BatteriesController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								controllers/BatteriesController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\BatteriesService; | ||||||
|  |  | ||||||
|  | class BatteriesController extends BaseController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->BatteriesService = new BatteriesService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $BatteriesService; | ||||||
|  |  | ||||||
|  | 	public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'batteriesoverview', [ | ||||||
|  | 			'batteries' => $this->Database->batteries()->orderBy('name'), | ||||||
|  | 			'current' => $this->BatteriesService->GetCurrent(), | ||||||
|  | 			'nextXDays' => 5 | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'batterytracking', [ | ||||||
|  | 			'batteries' =>  $this->Database->batteries()->orderBy('name') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function BatteryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['batteryId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'batteryform', [ | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'batteryform', [ | ||||||
|  | 				'battery' =>  $this->Database->batteries($args['batteryId']), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										81
									
								
								controllers/CalendarController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								controllers/CalendarController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\StockService; | ||||||
|  | use \Grocy\Services\TasksService; | ||||||
|  | use \Grocy\Services\ChoresService; | ||||||
|  | use \Grocy\Services\BatteriesService; | ||||||
|  |  | ||||||
|  | class CalendarController extends BaseController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->StockService = new StockService(); | ||||||
|  | 		$this->TasksService = new TasksService(); | ||||||
|  | 		$this->ChoresService = new ChoresService(); | ||||||
|  | 		$this->BatteriesService = new BatteriesService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $StockService; | ||||||
|  | 	protected $TasksService; | ||||||
|  | 	protected $ChoresService; | ||||||
|  | 	protected $BatteriesService; | ||||||
|  |  | ||||||
|  | 	public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$products = $this->Database->products(); | ||||||
|  | 		$titlePrefix = $this->LocalizationService->Localize('Product expires') . ': '; | ||||||
|  | 		$stockEvents = array(); | ||||||
|  | 		foreach($this->StockService->GetCurrentStock() as $currentStockEntry) | ||||||
|  | 		{ | ||||||
|  | 			$stockEvents[] = array( | ||||||
|  | 				'title' => $titlePrefix . FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name, | ||||||
|  | 				'start' => $currentStockEntry->best_before_date | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$titlePrefix = $this->LocalizationService->Localize('Task due') . ': '; | ||||||
|  | 		$taskEvents = array(); | ||||||
|  | 		foreach($this->TasksService->GetCurrent() as $currentTaskEntry) | ||||||
|  | 		{ | ||||||
|  | 			$taskEvents[] = array( | ||||||
|  | 				'title' => $titlePrefix . $currentTaskEntry->name, | ||||||
|  | 				'start' => $currentTaskEntry->due_date | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$chores = $this->Database->chores(); | ||||||
|  | 		$titlePrefix = $this->LocalizationService->Localize('Chore due') . ': '; | ||||||
|  | 		$choreEvents = array(); | ||||||
|  | 		foreach($this->ChoresService->GetCurrent() as $currentChoreEntry) | ||||||
|  | 		{ | ||||||
|  | 			$choreEvents[] = array( | ||||||
|  | 				'title' => $titlePrefix . FindObjectInArrayByPropertyValue($chores, 'id', $currentChoreEntry->chore_id)->name, | ||||||
|  | 				'start' => $currentChoreEntry->next_estimated_execution_time | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$batteries = $this->Database->batteries(); | ||||||
|  | 		$titlePrefix = $this->LocalizationService->Localize('Battery charge cycle due') . ': '; | ||||||
|  | 		$batteryEvents = array(); | ||||||
|  | 		foreach($this->BatteriesService->GetCurrent() as $currentBatteryEntry) | ||||||
|  | 		{ | ||||||
|  | 			$batteryEvents[] = array( | ||||||
|  | 				'title' => $titlePrefix . FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name, | ||||||
|  | 				'start' => $currentBatteryEntry->next_estimated_charge_time | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$fullcalendarEventSources = array(); | ||||||
|  | 		$fullcalendarEventSources[] = $stockEvents; | ||||||
|  | 		$fullcalendarEventSources[] = $taskEvents; | ||||||
|  | 		$fullcalendarEventSources[] = $choreEvents; | ||||||
|  | 		$fullcalendarEventSources[] = $batteryEvents; | ||||||
|  |  | ||||||
|  | 		return $this->AppContainer->view->render($response, 'calendar', [ | ||||||
|  | 			'fullcalendarEventSources' => $fullcalendarEventSources | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								controllers/ChoresApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								controllers/ChoresApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\ChoresService; | ||||||
|  |  | ||||||
|  | class ChoresApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->ChoresService = new ChoresService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $ChoresService; | ||||||
|  |  | ||||||
|  | 	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']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$choreExecutionId = $this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy); | ||||||
|  | 			return $this->ApiResponse(array('chore_execution_id' => $choreExecutionId)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ChoreDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			return $this->ApiResponse($this->ChoresService->GetChoreDetails($args['choreId'])); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								controllers/ChoresController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								controllers/ChoresController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\ChoresService; | ||||||
|  |  | ||||||
|  | class ChoresController extends BaseController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->ChoresService = new ChoresService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $ChoresService; | ||||||
|  |  | ||||||
|  | 	public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'choresoverview', [ | ||||||
|  | 			'chores' => $this->Database->chores()->orderBy('name'), | ||||||
|  | 			'currentChores' => $this->ChoresService->GetCurrent(), | ||||||
|  | 			'nextXDays' => 5 | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'choretracking', [ | ||||||
|  | 			'chores' => $this->Database->chores()->orderBy('name'), | ||||||
|  | 			'users' => $this->Database->users()->orderBy('username') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ChoreEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['choreId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'choreform', [ | ||||||
|  | 				'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'), | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'choreform', [ | ||||||
|  | 				'chore' =>  $this->Database->chores($args['choreId']), | ||||||
|  | 				'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								controllers/EquipmentController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								controllers/EquipmentController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class EquipmentController extends BaseController | ||||||
|  | { | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function EditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['equipmentId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'equipmentform', [ | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'equipmentform', [ | ||||||
|  | 				'equipment' =>  $this->Database->equipment($args['equipmentId']), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										99
									
								
								controllers/FilesApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								controllers/FilesApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\FilesService; | ||||||
|  |  | ||||||
|  | class FilesApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->FilesService = new FilesService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $FilesService; | ||||||
|  |  | ||||||
|  | 	public function UploadFile(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name'])) | ||||||
|  | 			{ | ||||||
|  | 				$fileName = $request->getQueryParams()['file_name']; | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				throw new \Exception('file_name query parameter missing or contains an invalid filename'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			$data = $request->getBody()->getContents(); | ||||||
|  | 			file_put_contents($this->FilesService->GetFilePath($args['group'], $fileName), $data); | ||||||
|  |  | ||||||
|  | 			return $this->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ServeFile(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name'])) | ||||||
|  | 			{ | ||||||
|  | 				$fileName = $request->getQueryParams()['file_name']; | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				throw new \Exception('file_name query parameter missing or contains an invalid filename'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			$filePath = $this->FilesService->GetFilePath($args['group'], $fileName); | ||||||
|  |  | ||||||
|  | 			if (file_exists($filePath)) | ||||||
|  | 			{ | ||||||
|  | 				$response->write(file_get_contents($filePath)); | ||||||
|  | 				$response = $response->withHeader('Content-Type', mime_content_type($filePath)); | ||||||
|  | 				return $response->withHeader('Content-Disposition', 'inline; filename="' . $fileName . '"'); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				return $this->VoidApiActionResponse($response, false, 404, 'File not found'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function DeleteFile(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name'])) | ||||||
|  | 			{ | ||||||
|  | 				$fileName = $request->getQueryParams()['file_name']; | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				throw new \Exception('file_name query parameter missing or contains an invalid filename'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			$filePath = $this->FilesService->GetFilePath($args['group'], $fileName); | ||||||
|  | 			if (file_exists($filePath)) | ||||||
|  | 			{ | ||||||
|  | 				unlink($filePath); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return $this->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								controllers/GenericEntityApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								controllers/GenericEntityApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | class GenericEntityApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function AddObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($this->IsValidEntity($args['entity'])) | ||||||
|  | 		{ | ||||||
|  | 			$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody()); | ||||||
|  | 			$newRow->save(); | ||||||
|  | 			$success = $newRow->isClean(); | ||||||
|  | 			return $this->ApiResponse(array('success' => $success)); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function EditObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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)); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function DeleteObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($this->IsValidEntity($args['entity'])) | ||||||
|  | 		{ | ||||||
|  | 			$row = $this->Database->{$args['entity']}($args['objectId']); | ||||||
|  | 			$row->delete(); | ||||||
|  | 			$success = $row->isClean(); | ||||||
|  | 			return $this->ApiResponse(array('success' => $success)); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private function IsValidEntity($entity) | ||||||
|  | 	{ | ||||||
|  | 		return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private function IsEntityWithPreventedListing($entity) | ||||||
|  | 	{ | ||||||
|  | 		return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntitiesPreventListing->enum); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								controllers/LoginController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								controllers/LoginController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\SessionService; | ||||||
|  | use \Grocy\Services\DatabaseMigrationService; | ||||||
|  | use \Grocy\Services\DemoDataGeneratorService; | ||||||
|  |  | ||||||
|  | class LoginController extends BaseController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container, string $sessionCookieName) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->SessionService = new SessionService(); | ||||||
|  | 		$this->SessionCookieName = $sessionCookieName; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $SessionService; | ||||||
|  | 	protected $SessionCookieName; | ||||||
|  |  | ||||||
|  | 	public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$postParams = $request->getParsedBody(); | ||||||
|  | 		if (isset($postParams['username']) && isset($postParams['password'])) | ||||||
|  | 		{ | ||||||
|  | 			$user = $this->Database->users()->where('username', $postParams['username'])->fetch(); | ||||||
|  | 			$inputPassword = $postParams['password']; | ||||||
|  | 			$stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on'; | ||||||
|  |  | ||||||
|  | 			if ($user !== null && password_verify($inputPassword, $user->password)) | ||||||
|  | 			{ | ||||||
|  | 				$sessionKey = $this->SessionService->CreateSession($user->id, $stayLoggedInPermanently); | ||||||
|  | 				setcookie($this->SessionCookieName, $sessionKey, intval(time() + 31220640000)); // Cookie expires in 999 years, but session validity is up to SessionService | ||||||
|  |  | ||||||
|  | 				if (password_needs_rehash($user->password, PASSWORD_DEFAULT)) | ||||||
|  | 				{ | ||||||
|  | 					$user->update(array( | ||||||
|  | 						'password' => password_hash($inputPassword, PASSWORD_DEFAULT) | ||||||
|  | 					)); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/')); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login?invalid=true')); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login?invalid=true')); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function LoginPage(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'login'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$this->SessionService->RemoveSession($_COOKIE[$this->SessionCookieName]); | ||||||
|  | 		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; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								controllers/OpenApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								controllers/OpenApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\ApplicationService; | ||||||
|  | use \Grocy\Services\ApiKeyService; | ||||||
|  |  | ||||||
|  | class OpenApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->ApiKeyService = new ApiKeyService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $ApiKeyService; | ||||||
|  |  | ||||||
|  | 	public function DocumentationUi(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'openapiui'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function DocumentationSpec(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$applicationService = new ApplicationService(); | ||||||
|  |  | ||||||
|  | 		$versionInfo = $applicationService->GetInstalledVersion(); | ||||||
|  | 		$this->OpenApiSpec->info->version = $versionInfo->Version; | ||||||
|  | 		$this->OpenApiSpec->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->UrlManager->ConstructUrl('/manageapikeys'), $this->OpenApiSpec->info->description); | ||||||
|  | 		$this->OpenApiSpec->servers[0]->url = $this->AppContainer->UrlManager->ConstructUrl('/api'); | ||||||
|  |  | ||||||
|  | 		return $this->ApiResponse($this->OpenApiSpec); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'manageapikeys', [ | ||||||
|  | 			'apiKeys' => $this->Database->api_keys(), | ||||||
|  | 			'users' => $this->Database->users() | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function CreateNewApiKey(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$newApiKey = $this->ApiKeyService->CreateApiKey(); | ||||||
|  | 		$newApiKeyId = $this->ApiKeyService->GetApiKeyId($newApiKey); | ||||||
|  | 		return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl("/manageapikeys?CreatedApiKeyId=$newApiKeyId")); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								controllers/RecipesApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								controllers/RecipesApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\RecipesService; | ||||||
|  |  | ||||||
|  | class RecipesApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->RecipesService = new RecipesService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $RecipesService; | ||||||
|  |  | ||||||
|  | 	public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId']); | ||||||
|  | 		return $this->VoidApiActionResponse($response); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ConsumeRecipe(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$this->RecipesService->ConsumeRecipe($args['recipeId']); | ||||||
|  | 			return $this->VoidApiActionResponse($response); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										102
									
								
								controllers/RecipesController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								controllers/RecipesController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\RecipesService; | ||||||
|  |  | ||||||
|  | class RecipesController extends BaseController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->RecipesService = new RecipesService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $RecipesService; | ||||||
|  |  | ||||||
|  | 	public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$recipes = $this->Database->recipes()->orderBy('name'); | ||||||
|  |  | ||||||
|  | 		$selectedRecipe = null; | ||||||
|  | 		$selectedRecipePositions = null; | ||||||
|  | 		if (isset($request->getQueryParams()['recipe'])) | ||||||
|  | 		{ | ||||||
|  | 			$selectedRecipe = $this->Database->recipes($request->getQueryParams()['recipe']); | ||||||
|  | 			$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $request->getQueryParams()['recipe'])->orderBy('ingredient_group'); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			foreach ($recipes as $recipe) | ||||||
|  | 			{ | ||||||
|  | 				$selectedRecipe = $recipe; | ||||||
|  | 				$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $recipe->id)->orderBy('ingredient_group'); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$selectedRecipeSubRecipes = $this->Database->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name')->fetchAll(); | ||||||
|  | 		$selectedRecipeSubRecipesPositions = $this->Database->recipes_pos()->where('recipe_id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('ingredient_group')->fetchAll(); | ||||||
|  |  | ||||||
|  | 		return $this->AppContainer->view->render($response, 'recipes', [ | ||||||
|  | 			'recipes' => $recipes, | ||||||
|  | 			'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(), | ||||||
|  | 			'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(), | ||||||
|  | 			'selectedRecipe' => $selectedRecipe, | ||||||
|  | 			'selectedRecipePositions' => $selectedRecipePositions, | ||||||
|  | 			'products' => $this->Database->products(), | ||||||
|  | 			'quantityunits' => $this->Database->quantity_units(), | ||||||
|  | 			'selectedRecipeSubRecipes' => $selectedRecipeSubRecipes, | ||||||
|  | 			'selectedRecipeSubRecipesPositions' => $selectedRecipeSubRecipesPositions | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function RecipeEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$recipeId = $args['recipeId']; | ||||||
|  | 		if ($recipeId  == 'new') | ||||||
|  | 		{ | ||||||
|  | 			$newRecipe = $this->Database->recipes()->createRow(array( | ||||||
|  | 				'name' => $this->LocalizationService->Localize('New recipe') | ||||||
|  | 			)); | ||||||
|  | 			$newRecipe->save(); | ||||||
|  |  | ||||||
|  | 			$recipeId = $this->Database->lastInsertId(); | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return $this->AppContainer->view->render($response, 'recipeform', [ | ||||||
|  | 			'recipe' =>  $this->Database->recipes($recipeId), | ||||||
|  | 			'recipePositions' =>  $this->Database->recipes_pos()->where('recipe_id', $recipeId), | ||||||
|  | 			'mode' => 'edit', | ||||||
|  | 			'products' => $this->Database->products(), | ||||||
|  | 			'quantityunits' => $this->Database->quantity_units(), | ||||||
|  | 			'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(), | ||||||
|  | 			'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(), | ||||||
|  | 			'recipes' =>  $this->Database->recipes()->orderBy('name'), | ||||||
|  | 			'recipeNestings' =>  $this->Database->recipes_nestings()->where('recipe_id', $recipeId) | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function RecipePosEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['recipePosId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'recipeposform', [ | ||||||
|  | 				'mode' => 'create', | ||||||
|  | 				'recipe' => $this->Database->recipes($args['recipeId']), | ||||||
|  | 				'products' => $this->Database->products()->orderBy('name'), | ||||||
|  | 				'quantityUnits' => $this->Database->quantity_units()->orderBy('name') | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'recipeposform', [ | ||||||
|  | 				'mode' => 'edit', | ||||||
|  | 				'recipe' =>  $this->Database->recipes($args['recipeId']), | ||||||
|  | 				'recipePos' => $this->Database->recipes_pos($args['recipePosId']), | ||||||
|  | 				'products' => $this->Database->products()->orderBy('name'), | ||||||
|  | 				'quantityUnits' => $this->Database->quantity_units()->orderBy('name') | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										211
									
								
								controllers/StockApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								controllers/StockApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\StockService; | ||||||
|  |  | ||||||
|  | class StockApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->StockService = new StockService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $StockService; | ||||||
|  |  | ||||||
|  | 	public function ProductDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			return $this->ApiResponse($this->StockService->GetProductDetails($args['productId'])); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ProductPriceHistory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			return $this->ApiResponse($this->StockService->GetProductPriceHistory($args['productId'])); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $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']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$bookingId = $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price); | ||||||
|  | 			return $this->ApiResponse(array('booking_id' => $bookingId)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$spoiled = false; | ||||||
|  | 		if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1') | ||||||
|  | 		{ | ||||||
|  | 			$spoiled = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$transactionType = StockService::TRANSACTION_TYPE_CONSUME; | ||||||
|  | 		if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype'])) | ||||||
|  | 		{ | ||||||
|  | 			$transactionType = $request->getQueryParams()['transactiontype']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$specificStockEntryId = "default"; | ||||||
|  | 		if (isset($request->getQueryParams()['stock_entry_id']) && !empty($request->getQueryParams()['stock_entry_id'])) | ||||||
|  | 		{ | ||||||
|  | 			$specificStockEntryId = $request->getQueryParams()['stock_entry_id']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$bookingId = $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType, $specificStockEntryId); | ||||||
|  | 			return $this->ApiResponse(array('booking_id' => $bookingId)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $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']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$bookingId = $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate); | ||||||
|  | 			return $this->ApiResponse(array('booking_id' => $bookingId)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function OpenProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$specificStockEntryId = "default"; | ||||||
|  | 		if (isset($request->getQueryParams()['stock_entry_id']) && !empty($request->getQueryParams()['stock_entry_id'])) | ||||||
|  | 		{ | ||||||
|  | 			$specificStockEntryId = $request->getQueryParams()['stock_entry_id']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$bookingId = $this->StockService->OpenProduct($args['productId'], $args['amount'], $specificStockEntryId); | ||||||
|  | 			return $this->ApiResponse(array('booking_id' => $bookingId)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function CurrentStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->ApiResponse($this->StockService->GetCurrentStock()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function CurrentVolatilStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$nextXDays = 5; | ||||||
|  | 		if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days'])) | ||||||
|  | 		{ | ||||||
|  | 			$nextXDays = $request->getQueryParams()['expiring_days']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		$expiringProducts = $this->StockService->GetExpiringProducts($nextXDays, true); | ||||||
|  | 		$expiredProducts = $this->StockService->GetExpiringProducts(-1); | ||||||
|  | 		$missingProducts = $this->StockService->GetMissingProducts(); | ||||||
|  | 		return $this->ApiResponse(array( | ||||||
|  | 			 'expiring_products' => $expiringProducts, | ||||||
|  | 			 'expired_products' => $expiredProducts, | ||||||
|  | 			 'missing_products' => $missingProducts | ||||||
|  | 		)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function AddMissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$this->StockService->AddMissingProductsToShoppingList(); | ||||||
|  | 		return $this->VoidApiActionResponse($response); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ClearShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$this->StockService->ClearShoppingList(); | ||||||
|  | 		return $this->VoidApiActionResponse($response); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ExternalBarcodeLookup(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$addFoundProduct = false; | ||||||
|  | 			if (isset($request->getQueryParams()['add']) && ($request->getQueryParams()['add'] === 'true' || $request->getQueryParams()['add'] === 1)) | ||||||
|  | 			{ | ||||||
|  | 				$addFoundProduct = true; | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			return $this->ApiResponse($this->StockService->ExternalBarcodeLookup($args['barcode'], $addFoundProduct)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $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->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ProductStockEntries(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->ApiResponse($this->StockService->GetProductStockEntries($args['productId'])); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										204
									
								
								controllers/StockController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								controllers/StockController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\StockService; | ||||||
|  |  | ||||||
|  | class StockController extends BaseController | ||||||
|  | { | ||||||
|  |  | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->StockService = new StockService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $StockService; | ||||||
|  |  | ||||||
|  | 	public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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(), | ||||||
|  | 			'missingProducts' => $this->StockService->GetMissingProducts(), | ||||||
|  | 			'nextXDays' => 5, | ||||||
|  | 			'productGroups' => $this->Database->product_groups()->orderBy('name') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'shoppinglist', [ | ||||||
|  | 			'listItems' => $this->Database->shopping_list(), | ||||||
|  | 			'products' => $this->Database->products()->orderBy('name'), | ||||||
|  | 			'quantityunits' => $this->Database->quantity_units()->orderBy('name'), | ||||||
|  | 			'missingProducts' => $this->StockService->GetMissingProducts(), | ||||||
|  | 			'productGroups' => $this->Database->product_groups()->orderBy('name') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ProductsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ProductEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['productId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'productform', [ | ||||||
|  | 				'locations' =>  $this->Database->locations()->orderBy('name'), | ||||||
|  | 				'quantityunits' =>  $this->Database->quantity_units()->orderBy('name'), | ||||||
|  | 				'productgroups' => $this->Database->product_groups()->orderBy('name'), | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'productform', [ | ||||||
|  | 				'product' =>  $this->Database->products($args['productId']), | ||||||
|  | 				'locations' =>  $this->Database->locations()->orderBy('name'), | ||||||
|  | 				'quantityunits' =>  $this->Database->quantity_units()->orderBy('name'), | ||||||
|  | 				'productgroups' => $this->Database->product_groups()->orderBy('name'), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function LocationEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['locationId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'locationform', [ | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'locationform', [ | ||||||
|  | 				'location' =>  $this->Database->locations($args['locationId']), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ProductGroupEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['productGroupId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'productgroupform', [ | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'productgroupform', [ | ||||||
|  | 				'group' =>  $this->Database->product_groups($args['productGroupId']), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['quantityunitId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'quantityunitform', [ | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'quantityunitform', [ | ||||||
|  | 				'quantityunit' =>  $this->Database->quantity_units($args['quantityunitId']), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function ShoppingListItemEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['itemId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'shoppinglistform', [ | ||||||
|  | 				'products' =>  $this->Database->products()->orderBy('name'), | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'shoppinglistform', [ | ||||||
|  | 				'listItem' =>  $this->Database->shopping_list($args['itemId']), | ||||||
|  | 				'products' =>  $this->Database->products()->orderBy('name'), | ||||||
|  | 				'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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								controllers/SystemApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								controllers/SystemApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\DatabaseService; | ||||||
|  |  | ||||||
|  | class SystemApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->DatabaseService = new DatabaseService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $DatabaseService; | ||||||
|  |  | ||||||
|  | 	public function GetDbChangedTime(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->ApiResponse(array( | ||||||
|  | 			'changed_time' => $this->DatabaseService->GetDbChangedTime() | ||||||
|  | 		)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function LogMissingLocalization(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if (GROCY_MODE === 'dev') | ||||||
|  | 		{ | ||||||
|  | 			try | ||||||
|  | 			{ | ||||||
|  | 				$requestBody = $request->getParsedBody(); | ||||||
|  |  | ||||||
|  | 				$this->LocalizationService->LogMissingLocalization(GROCY_CULTURE, $requestBody['text']); | ||||||
|  | 				return $this->ApiResponse(array('success' => true)); | ||||||
|  | 			} | ||||||
|  | 			catch (\Exception $ex) | ||||||
|  | 			{ | ||||||
|  | 				return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 			} | ||||||
|  | 		}	 | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								controllers/TasksApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								controllers/TasksApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\TasksService; | ||||||
|  |  | ||||||
|  | class TasksApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->TasksService = new TasksService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $TasksService; | ||||||
|  |  | ||||||
|  | 	public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->ApiResponse($this->TasksService->GetCurrent()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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']; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$this->TasksService->MarkTaskAsCompleted($args['taskId'], $doneTime); | ||||||
|  | 			return $this->VoidApiActionResponse($response); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								controllers/TasksController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								controllers/TasksController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\TasksService; | ||||||
|  |  | ||||||
|  | class TasksController extends BaseController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->TasksService = new TasksService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $TasksService; | ||||||
|  |  | ||||||
|  | 	public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if (isset($request->getQueryParams()['include_done'])) | ||||||
|  | 		{ | ||||||
|  | 			$tasks = $this->Database->tasks()->orderBy('name'); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			$tasks = $this->TasksService->GetCurrent(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return $this->AppContainer->view->render($response, 'tasks', [ | ||||||
|  | 			'tasks' => $tasks, | ||||||
|  | 			'nextXDays' => 5, | ||||||
|  | 			'taskCategories' => $this->Database->task_categories()->orderBy('name'), | ||||||
|  | 			'users' => $this->Database->users() | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function TaskEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['taskId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'taskform', [ | ||||||
|  | 				'mode' => 'create', | ||||||
|  | 				'taskCategories' => $this->Database->task_categories()->orderBy('name'), | ||||||
|  | 				'users' => $this->Database->users()->orderBy('username') | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'taskform', [ | ||||||
|  | 				'task' =>  $this->Database->tasks($args['taskId']), | ||||||
|  | 				'mode' => 'edit', | ||||||
|  | 				'taskCategories' => $this->Database->task_categories()->orderBy('name'), | ||||||
|  | 				'users' => $this->Database->users()->orderBy('username') | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function TaskCategoryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['categoryId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'taskcategoryform', [ | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'taskcategoryform', [ | ||||||
|  | 				'category' =>  $this->Database->task_categories($args['categoryId']), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										99
									
								
								controllers/UsersApiController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								controllers/UsersApiController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\UsersService; | ||||||
|  |  | ||||||
|  | class UsersApiController extends BaseApiController | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->UsersService = new UsersService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $UsersService; | ||||||
|  |  | ||||||
|  | 	public function GetUsers(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			return $this->ApiResponse($this->UsersService->GetUsersAsDto()); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function CreateUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$requestBody = $request->getParsedBody(); | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$this->UsersService->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']); | ||||||
|  | 			return $this->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function DeleteUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$this->UsersService->DeleteUser($args['userId']); | ||||||
|  | 			return $this->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function EditUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		$requestBody = $request->getParsedBody(); | ||||||
|  |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$this->UsersService->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']); | ||||||
|  | 			return $this->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function GetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$value = $this->UsersService->GetUserSetting(GROCY_USER_ID, $args['settingKey']); | ||||||
|  | 			return $this->ApiResponse(array('value' => $value)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function SetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			$requestBody = $request->getParsedBody(); | ||||||
|  |  | ||||||
|  | 			$value = $this->UsersService->SetUserSetting(GROCY_USER_ID, $args['settingKey'], $requestBody['value']); | ||||||
|  | 			return $this->ApiResponse(array('success' => true)); | ||||||
|  | 		} | ||||||
|  | 		catch (\Exception $ex) | ||||||
|  | 		{ | ||||||
|  | 			return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								controllers/UsersController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								controllers/UsersController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Controllers; | ||||||
|  |  | ||||||
|  | class UsersController extends BaseController | ||||||
|  | { | ||||||
|  | 	public function UsersList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		return $this->AppContainer->view->render($response, 'users', [ | ||||||
|  | 			'users' => $this->Database->users()->orderBy('username') | ||||||
|  | 		]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public function UserEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) | ||||||
|  | 	{ | ||||||
|  | 		if ($args['userId'] == 'new') | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'userform', [ | ||||||
|  | 				'mode' => 'create' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $this->AppContainer->view->render($response, 'userform', [ | ||||||
|  | 				'user' =>  $this->Database->users($args['userId']), | ||||||
|  | 				'mode' => 'edit' | ||||||
|  | 			]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								data/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								data/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,4 @@ | |||||||
| * | * | ||||||
| !.gitignore | !.gitignore | ||||||
|  | !viewcache | ||||||
|  | !plugins | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								data/plugins/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								data/plugins/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | * | ||||||
|  | !.gitignore | ||||||
|  | !DemoBarcodeLookupPlugin.php | ||||||
							
								
								
									
										78
									
								
								data/plugins/DemoBarcodeLookupPlugin.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								data/plugins/DemoBarcodeLookupPlugin.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | use \Grocy\Helpers\BaseBarcodeLookupPlugin; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	This class must extend BaseBarcodeLookupPlugin (in namespace \Grocy\Helpers) | ||||||
|  | */ | ||||||
|  | class DemoBarcodeLookupPlugin extends BaseBarcodeLookupPlugin | ||||||
|  | { | ||||||
|  | 	/* | ||||||
|  | 		To use this plugin, configure it in data/config.php like this: | ||||||
|  | 		Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin'); | ||||||
|  | 	*/ | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		To try it: | ||||||
|  | 		Call the API function at /api/stock/external-barcode-lookup/{barcode} | ||||||
|  |  | ||||||
|  | 		When you also add ?add=true as a query parameter to the API call, | ||||||
|  | 		on a successful lookup the product is added to the database and in the output | ||||||
|  | 		the new product id is included (automatically, nothing to do here in the plugin) | ||||||
|  | 	*/ | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		Provided references: | ||||||
|  |  | ||||||
|  | 		$this->Locations contains all locations | ||||||
|  | 		$this->QuantityUnits contains all quantity units | ||||||
|  | 	*/ | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		Useful hints: | ||||||
|  |  | ||||||
|  | 		Get a quantity unit by name: | ||||||
|  | 		$quantityUnit = FindObjectInArrayByPropertyValue($this->QuantityUnits, 'name', 'Piece'); | ||||||
|  |  | ||||||
|  | 		Get a location by name: | ||||||
|  | 		$location = FindObjectInArrayByPropertyValue($this->Locations, 'name', 'Fridge'); | ||||||
|  | 	*/ | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		This class must implement the protected abstract function ExecuteLookup($barcode), | ||||||
|  | 		which is called with the barcode that needs to be looked up and must return an | ||||||
|  | 		associative array of the product model or null, when nothing was found for the barcode. | ||||||
|  |  | ||||||
|  | 		The returned array must contain at least these properties: | ||||||
|  | 		array( | ||||||
|  | 			'name' => '', | ||||||
|  | 			'location_id' => 1, // A valid id of a location object, check against $this->Locations | ||||||
|  | 			'qu_id_purchase' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits | ||||||
|  | 			'qu_id_stock' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits | ||||||
|  | 			'qu_factor_purchase_to_stock' => 1, // Normally 1 when quantity unit stock and purchase is the same | ||||||
|  | 			'barcode' => $barcode // The barcode of the product, maybe just pass through $barcode or manipulate it if necessary | ||||||
|  | 		) | ||||||
|  | 	*/ | ||||||
|  | 	protected function ExecuteLookup($barcode) | ||||||
|  | 	{ | ||||||
|  | 		if ($barcode === 'x') // Demonstration when nothing is found | ||||||
|  | 		{ | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		elseif ($barcode === 'e') // Demonstration when an error occurred | ||||||
|  | 		{ | ||||||
|  | 			throw new \Exception('This is the error message from the plugin...'); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return array( | ||||||
|  | 				'name' => 'LookedUpProduct_' . RandomString(5), | ||||||
|  | 				'location_id' => $this->Locations[0]->id, | ||||||
|  | 				'qu_id_purchase' => $this->QuantityUnits[0]->id, | ||||||
|  | 				'qu_id_stock' => $this->QuantityUnits[0]->id, | ||||||
|  | 				'qu_factor_purchase_to_stock' => 1, | ||||||
|  | 				'barcode' => $barcode | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								data/viewcache/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								data/viewcache/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | * | ||||||
|  | !.gitignore | ||||||
							
								
								
									
										77
									
								
								grocy.js
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								grocy.js
									
									
									
									
									
								
							| @@ -1,77 +0,0 @@ | |||||||
| var Grocy = {}; |  | ||||||
|  |  | ||||||
| $(function() |  | ||||||
| { |  | ||||||
| 	var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ContentPage + "']"); |  | ||||||
| 	menuItem.addClass('active'); |  | ||||||
|  |  | ||||||
| 	$.timeago.settings.allowFuture = true; |  | ||||||
| 	$('time.timeago').timeago(); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| Grocy.FetchJson = function(url, success, error) |  | ||||||
| { |  | ||||||
| 	var xhr = new XMLHttpRequest(); |  | ||||||
|  |  | ||||||
| 	xhr.onreadystatechange = function() |  | ||||||
| 	{ |  | ||||||
| 		if (xhr.readyState === XMLHttpRequest.DONE) |  | ||||||
| 		{ |  | ||||||
| 			if (xhr.status === 200) |  | ||||||
| 			{ |  | ||||||
| 				if (success) |  | ||||||
| 				{ |  | ||||||
| 					success(JSON.parse(xhr.responseText)); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				if (error) |  | ||||||
| 				{ |  | ||||||
| 					error(xhr); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	xhr.open('GET', url, true); |  | ||||||
| 	xhr.send(); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| Grocy.PostJson = function(url, jsonData, success, error) |  | ||||||
| { |  | ||||||
| 	var xhr = new XMLHttpRequest(); |  | ||||||
|  |  | ||||||
| 	xhr.onreadystatechange = function() |  | ||||||
| 	{ |  | ||||||
| 		if (xhr.readyState === XMLHttpRequest.DONE) |  | ||||||
| 		{ |  | ||||||
| 			if (xhr.status === 200) |  | ||||||
| 			{ |  | ||||||
| 				if (success) |  | ||||||
| 				{ |  | ||||||
| 					success(JSON.parse(xhr.responseText)); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				if (error) |  | ||||||
| 				{ |  | ||||||
| 					error(xhr); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	xhr.open('POST', url, true); |  | ||||||
| 	xhr.setRequestHeader('Content-type', 'application/json'); |  | ||||||
| 	xhr.send(JSON.stringify(jsonData)); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| Grocy.EmptyElementWhenMatches = function(selector, text) |  | ||||||
| { |  | ||||||
| 	if ($(selector).text() === text) |  | ||||||
| 	{ |  | ||||||
| 		$(selector).text(''); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
							
								
								
									
										2620
									
								
								grocy.openapi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2620
									
								
								grocy.openapi.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										63
									
								
								grocy.php
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								grocy.php
									
									
									
									
									
								
							| @@ -1,63 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| class Grocy |  | ||||||
| { |  | ||||||
| 	private static $DbConnectionRaw; |  | ||||||
| 	/** |  | ||||||
| 	 * @return PDO |  | ||||||
| 	 */ |  | ||||||
| 	public static function GetDbConnectionRaw() |  | ||||||
| 	{ |  | ||||||
| 		if (self::$DbConnectionRaw == null) |  | ||||||
| 		{ |  | ||||||
| 			$newDb = !file_exists(__DIR__ . '/data/grocy.db'); |  | ||||||
| 			$pdo = new PDO('sqlite:' . __DIR__ . '/data/grocy.db'); |  | ||||||
| 			$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |  | ||||||
|  |  | ||||||
| 			if ($newDb) |  | ||||||
| 			{ |  | ||||||
| 				$pdo->exec("CREATE TABLE migrations (migration INTEGER NOT NULL UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')), PRIMARY KEY(migration)) WITHOUT ROWID"); |  | ||||||
| 				GrocyDbMigrator::MigrateDb($pdo); |  | ||||||
|  |  | ||||||
| 				if (self::IsDemoInstallation()) |  | ||||||
| 				{ |  | ||||||
| 					GrocyDemoDataGenerator::PopulateDemoData($pdo); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			self::$DbConnectionRaw = $pdo; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return self::$DbConnectionRaw; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	private static $DbConnection; |  | ||||||
| 	/** |  | ||||||
| 	 * @return LessQL\Database |  | ||||||
| 	 */ |  | ||||||
| 	public static function GetDbConnection() |  | ||||||
| 	{ |  | ||||||
| 		if (self::$DbConnection == null) |  | ||||||
| 		{ |  | ||||||
| 			self::$DbConnection = new LessQL\Database(self::GetDbConnectionRaw()); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return self::$DbConnection; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public static function IsDemoInstallation() |  | ||||||
| 	{ |  | ||||||
| 		return file_exists(__DIR__ . '/data/demo.txt'); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	private static $InstalledVersion; |  | ||||||
| 	public static function GetInstalledVersion() |  | ||||||
| 	{ |  | ||||||
| 		if (self::$InstalledVersion == null) |  | ||||||
| 		{ |  | ||||||
| 			self::$InstalledVersion = file_get_contents(__DIR__ . '/version.txt'); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return self::$InstalledVersion; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,63 +0,0 @@ | |||||||
| <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |  | ||||||
|   <PropertyGroup> |  | ||||||
|     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |  | ||||||
|     <Name>grocy</Name> |  | ||||||
|     <ProjectGuid>edb77631-5196-4860-baeb-bca8900a4b6d</ProjectGuid> |  | ||||||
|     <OutputType>Library</OutputType> |  | ||||||
|     <RootNamespace> |  | ||||||
|     </RootNamespace> |  | ||||||
|     <ProjectTypeGuids>{A0786B88-2ADB-4C21-ABE8-AA2D79766269}</ProjectTypeGuids> |  | ||||||
|     <AssemblyName>grocy</AssemblyName> |  | ||||||
|   </PropertyGroup> |  | ||||||
|   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> |  | ||||||
|     <IncludeDebugInformation>true</IncludeDebugInformation> |  | ||||||
|   </PropertyGroup> |  | ||||||
|   <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> |  | ||||||
|     <IncludeDebugInformation>false</IncludeDebugInformation> |  | ||||||
|   </PropertyGroup> |  | ||||||
|   <ItemGroup> |  | ||||||
|     <Compile Include="config-dist.php" /> |  | ||||||
|     <Compile Include="Grocy.php" /> |  | ||||||
|     <Compile Include="GrocyLogicStock.php" /> |  | ||||||
|     <Compile Include="GrocyDemoDataGenerator.php" /> |  | ||||||
|     <Compile Include="GrocyPhpHelper.php" /> |  | ||||||
|     <Compile Include="GrocyDbMigrator.php" /> |  | ||||||
|     <Compile Include="index.php" /> |  | ||||||
|     <Compile Include="views\consumption.php" /> |  | ||||||
|     <Compile Include="views\purchase.php" /> |  | ||||||
|     <Compile Include="views\quantityunitform.php" /> |  | ||||||
|     <Compile Include="views\locationform.php" /> |  | ||||||
|     <Compile Include="views\productform.php" /> |  | ||||||
|     <Compile Include="views\locations.php" /> |  | ||||||
|     <Compile Include="views\quantityunits.php" /> |  | ||||||
|     <Compile Include="views\products.php" /> |  | ||||||
|     <Compile Include="views\dashboard.php" /> |  | ||||||
|     <Compile Include="views\layout.php" /> |  | ||||||
|   </ItemGroup> |  | ||||||
|   <ItemGroup> |  | ||||||
|     <Content Include="bower.json" /> |  | ||||||
|     <None Include="build.bat" /> |  | ||||||
|     <Content Include="composer.json" /> |  | ||||||
|     <Content Include="grocy.js" /> |  | ||||||
|     <None Include="README.md" /> |  | ||||||
|     <Content Include="README.html"> |  | ||||||
|       <SubType>Content</SubType> |  | ||||||
|       <DependentUpon>README.md</DependentUpon> |  | ||||||
|     </Content> |  | ||||||
|     <Content Include="robots.txt" /> |  | ||||||
|     <Content Include="style.css" /> |  | ||||||
|     <Content Include="version.txt" /> |  | ||||||
|     <Content Include="views\consumption.js" /> |  | ||||||
|     <Content Include="views\dashboard.js" /> |  | ||||||
|     <Content Include="views\purchase.js" /> |  | ||||||
|     <Content Include="views\quantityunitform.js" /> |  | ||||||
|     <Content Include="views\locationform.js" /> |  | ||||||
|     <Content Include="views\productform.js" /> |  | ||||||
|     <Content Include="views\locations.js" /> |  | ||||||
|     <Content Include="views\quantityunits.js" /> |  | ||||||
|     <Content Include="views\products.js" /> |  | ||||||
|   </ItemGroup> |  | ||||||
|   <ItemGroup> |  | ||||||
|     <Folder Include="views\" /> |  | ||||||
|   </ItemGroup> |  | ||||||
| </Project> |  | ||||||
							
								
								
									
										20
									
								
								grocy.sln
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								grocy.sln
									
									
									
									
									
								
							| @@ -1,20 +0,0 @@ | |||||||
|  |  | ||||||
| Microsoft Visual Studio Solution File, Format Version 12.00 |  | ||||||
| # Visual Studio 15 |  | ||||||
| VisualStudioVersion = 15.0.26403.3 |  | ||||||
| MinimumVisualStudioVersion = 10.0.40219.1 |  | ||||||
| Project("{A0786B88-2ADB-4C21-ABE8-AA2D79766269}") = "grocy", "grocy.phpproj", "{EDB77631-5196-4860-BAEB-BCA8900A4B6D}" |  | ||||||
| EndProject |  | ||||||
| Global |  | ||||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution |  | ||||||
| 		Debug|Any CPU = Debug|Any CPU |  | ||||||
| 		Release|Any CPU = Release|Any CPU |  | ||||||
| 	EndGlobalSection |  | ||||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution |  | ||||||
| 		{EDB77631-5196-4860-BAEB-BCA8900A4B6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{EDB77631-5196-4860-BAEB-BCA8900A4B6D}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 	EndGlobalSection |  | ||||||
| 	GlobalSection(SolutionProperties) = preSolution |  | ||||||
| 		HideSolutionNode = FALSE |  | ||||||
| 	EndGlobalSection |  | ||||||
| EndGlobal |  | ||||||
							
								
								
									
										80
									
								
								helpers/BaseBarcodeLookupPlugin.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								helpers/BaseBarcodeLookupPlugin.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Helpers; | ||||||
|  |  | ||||||
|  | abstract class BaseBarcodeLookupPlugin | ||||||
|  | { | ||||||
|  | 	final public function __construct($locations, $quantityUnits) | ||||||
|  | 	{ | ||||||
|  | 		$this->Locations = $locations; | ||||||
|  | 		$this->QuantityUnits = $quantityUnits; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $Locations; | ||||||
|  | 	protected $QuantityUnits; | ||||||
|  |  | ||||||
|  | 	abstract protected function ExecuteLookup($barcode); | ||||||
|  |  | ||||||
|  | 	final public function Lookup($barcode) | ||||||
|  | 	{ | ||||||
|  | 		$pluginOutput = $this->ExecuteLookup($barcode); | ||||||
|  |  | ||||||
|  | 		if ($pluginOutput === null) | ||||||
|  | 		{ | ||||||
|  | 			return $pluginOutput; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Plugin must return an associative array | ||||||
|  | 		if (!is_array($pluginOutput)) | ||||||
|  | 		{ | ||||||
|  | 			throw new \Exception('Plugin output must be an associative array'); | ||||||
|  | 		} | ||||||
|  | 		if (!IsAssociativeArray($pluginOutput)) // $pluginOutput is at least an indexed array here | ||||||
|  | 		{ | ||||||
|  | 			throw new \Exception('Plugin output must be an associative array'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Check for minimum needed properties | ||||||
|  | 		$minimunNeededProperties = array( | ||||||
|  | 			'name', | ||||||
|  | 			'location_id', | ||||||
|  | 			'qu_id_purchase', | ||||||
|  | 			'qu_id_stock', | ||||||
|  | 			'qu_factor_purchase_to_stock', | ||||||
|  | 			'barcode' | ||||||
|  | 		); | ||||||
|  | 		foreach ($minimunNeededProperties as $prop) | ||||||
|  | 		{ | ||||||
|  | 			if (!array_key_exists($prop, $pluginOutput)) | ||||||
|  | 			{ | ||||||
|  | 				throw new \Exception("Plugin output does not provide needed property $prop"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// $pluginOutput contains all needed properties here | ||||||
|  |  | ||||||
|  | 		// Check referenced entity ids are valid | ||||||
|  | 		$locationId = $pluginOutput['location_id']; | ||||||
|  | 		if (FindObjectInArrayByPropertyValue($this->Locations, 'id', $locationId) === null) | ||||||
|  | 		{ | ||||||
|  | 			throw new \Exception("Location $locationId is not a valid location id"); | ||||||
|  | 		} | ||||||
|  | 		$quIdPurchase = $pluginOutput['qu_id_purchase']; | ||||||
|  | 		if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdPurchase) === null) | ||||||
|  | 		{ | ||||||
|  | 			throw new \Exception("Location $quIdPurchase is not a valid quantity unit id"); | ||||||
|  | 		} | ||||||
|  | 		$quIdStock = $pluginOutput['qu_id_stock']; | ||||||
|  | 		if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdStock) === null) | ||||||
|  | 		{ | ||||||
|  | 			throw new \Exception("Location $quIdStock is not a valid quantity unit id"); | ||||||
|  | 		} | ||||||
|  | 		$quFactor = $pluginOutput['qu_factor_purchase_to_stock']; | ||||||
|  | 		if (empty($quFactor) || !is_numeric($quFactor)) | ||||||
|  | 		{ | ||||||
|  | 			throw new \Exception('Quantity unit factor is empty or not a number'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return $pluginOutput; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								helpers/UrlManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								helpers/UrlManager.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Helpers; | ||||||
|  |  | ||||||
|  | class UrlManager | ||||||
|  | { | ||||||
|  | 	public function __construct(string $basePath) | ||||||
|  | 	{ | ||||||
|  | 		if ($basePath === '/') | ||||||
|  | 		{ | ||||||
|  | 			$this->BasePath = $this->GetBaseUrl(); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			$this->BasePath = $basePath; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $BasePath; | ||||||
|  |  | ||||||
|  | 	public function ConstructUrl($relativePath, $isResource = false) | ||||||
|  | 	{ | ||||||
|  | 		if (GROCY_DISABLE_URL_REWRITING === false || $isResource === true) | ||||||
|  | 		{ | ||||||
|  | 			return rtrim($this->BasePath, '/') . $relativePath; | ||||||
|  | 		} | ||||||
|  | 		else // Is not a resource and URL rewriting is disabled | ||||||
|  | 		{ | ||||||
|  | 			return rtrim($this->BasePath, '/') . '/index.php' . $relativePath; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private function GetBaseUrl() | ||||||
|  | 	{ | ||||||
|  | 		if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) | ||||||
|  | 		{ | ||||||
|  | 			$_SERVER['HTTPS'] = 'on'; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]"; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										201
									
								
								helpers/extensions.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								helpers/extensions.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue) | ||||||
|  | { | ||||||
|  | 	foreach($array as $object) | ||||||
|  | 	{ | ||||||
|  | 		if($object->{$propertyName} == $propertyValue) | ||||||
|  | 		{ | ||||||
|  | 			return $object; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyValue, $operator = '==') | ||||||
|  | { | ||||||
|  | 	$returnArray = array(); | ||||||
|  |  | ||||||
|  | 	foreach($array as $object) | ||||||
|  | 	{ | ||||||
|  | 		switch($operator) | ||||||
|  | 		{ | ||||||
|  | 			case '==': | ||||||
|  | 				if($object->{$propertyName} == $propertyValue) | ||||||
|  | 				{ | ||||||
|  | 					$returnArray[] = $object; | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 			case '>': | ||||||
|  | 				if($object->{$propertyName} > $propertyValue) | ||||||
|  | 				{ | ||||||
|  | 					$returnArray[] = $object; | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 			case '<': | ||||||
|  | 				if($object->{$propertyName} < $propertyValue) | ||||||
|  | 				{ | ||||||
|  | 					$returnArray[] = $object; | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return $returnArray; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function FindAllItemsInArrayByValue($array, $value, $operator = '==') | ||||||
|  | { | ||||||
|  | 	$returnArray = array(); | ||||||
|  |  | ||||||
|  | 	foreach($array as $item) | ||||||
|  | 	{ | ||||||
|  | 		switch($operator) | ||||||
|  | 		{ | ||||||
|  | 			case '==': | ||||||
|  | 				if($item == $value) | ||||||
|  | 				{ | ||||||
|  | 					$returnArray[] = $item; | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 			case '>': | ||||||
|  | 				if($item > $value) | ||||||
|  | 				{ | ||||||
|  | 					$returnArray[] = $item; | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 			case '<': | ||||||
|  | 				if($item < $value) | ||||||
|  | 				{ | ||||||
|  | 					$returnArray[] = $item; | ||||||
|  | 				} | ||||||
|  | 				break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return $returnArray; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function SumArrayValue($array, $propertyName) | ||||||
|  | { | ||||||
|  | 	$sum = 0; | ||||||
|  | 	foreach($array as $object) | ||||||
|  | 	{ | ||||||
|  | 		$sum += $object->{$propertyName}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return $sum; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function GetClassConstants($className) | ||||||
|  | { | ||||||
|  | 	$r = new ReflectionClass($className); | ||||||
|  | 	return $r->getConstants(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') | ||||||
|  | { | ||||||
|  | 	$randomString = ''; | ||||||
|  | 	for ($i = 0; $i < $length; $i++) | ||||||
|  | 	{ | ||||||
|  | 		$randomString .= $allowedChars[rand(0, strlen($allowedChars) - 1)]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return $randomString; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function IsAssociativeArray(array $array) | ||||||
|  | { | ||||||
|  | 	$keys = array_keys($array); | ||||||
|  | 	return array_keys($keys) !== $keys; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function IsIsoDate($dateString) | ||||||
|  | { | ||||||
|  | 	$d = DateTime::createFromFormat('Y-m-d', $dateString); | ||||||
|  | 	return $d && $d->format('Y-m-d') === $dateString; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function IsIsoDateTime($dateTimeString) | ||||||
|  | { | ||||||
|  | 	$d = DateTime::createFromFormat('Y-m-d H:i:s', $dateTimeString); | ||||||
|  | 	return $d && $d->format('Y-m-d H:i:s') === $dateTimeString; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function BoolToString(bool $bool) | ||||||
|  | { | ||||||
|  | 	return $bool ? 'true' : 'false'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function Setting(string $name, $value) | ||||||
|  | { | ||||||
|  | 	if (!defined('GROCY_' . $name)) | ||||||
|  | 	{ | ||||||
|  | 		// The content of a $name.txt file in /data/settingoverrides can overwrite the given setting (for embedded mode) | ||||||
|  | 		$settingOverrideFile = GROCY_DATAPATH . '/settingoverrides/' . $name . '.txt'; | ||||||
|  | 		if (file_exists($settingOverrideFile)) | ||||||
|  | 		{ | ||||||
|  | 			define('GROCY_' . $name, file_get_contents($settingOverrideFile)); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			define('GROCY_' . $name, $value); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | global $GROCY_DEFAULT_USER_SETTINGS; | ||||||
|  | $GROCY_DEFAULT_USER_SETTINGS = array(); | ||||||
|  | function DefaultUserSetting(string $name, $value) | ||||||
|  | { | ||||||
|  | 	global $GROCY_DEFAULT_USER_SETTINGS; | ||||||
|  | 	if (!array_key_exists($name, $GROCY_DEFAULT_USER_SETTINGS)) | ||||||
|  | 	{ | ||||||
|  | 		$GROCY_DEFAULT_USER_SETTINGS[$name] = $value; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function GetUserDisplayName($user) | ||||||
|  | { | ||||||
|  | 	$displayName = ''; | ||||||
|  | 	 | ||||||
|  | 	if (empty($user->first_name) && !empty($user->last_name)) | ||||||
|  | 	{ | ||||||
|  | 		$displayName = $user->last_name; | ||||||
|  | 	} | ||||||
|  | 	elseif (empty($user->last_name) && !empty($user->first_name)) | ||||||
|  | 	{ | ||||||
|  | 		$displayName = $user->first_name; | ||||||
|  | 	} | ||||||
|  | 	elseif (!empty($user->last_name) && !empty($user->first_name)) | ||||||
|  | 	{ | ||||||
|  | 		$displayName = $user->first_name . ' ' . $user->last_name; | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		$displayName = $user->username; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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)) | ||||||
|  | 	{ | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
							
								
								
									
										259
									
								
								index.php
									
									
									
									
									
								
							
							
						
						
									
										259
									
								
								index.php
									
									
									
									
									
								
							| @@ -1,259 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| use \Psr\Http\Message\ServerRequestInterface as Request; |  | ||||||
| use \Psr\Http\Message\ResponseInterface as Response; |  | ||||||
| use Slim\Views\PhpRenderer; |  | ||||||
|  |  | ||||||
| require_once 'vendor/autoload.php'; |  | ||||||
| require_once 'config.php'; |  | ||||||
| require_once 'Grocy.php'; |  | ||||||
| require_once 'GrocyDbMigrator.php'; |  | ||||||
| require_once 'GrocyDemoDataGenerator.php'; |  | ||||||
| require_once 'GrocyLogicStock.php'; |  | ||||||
| require_once 'GrocyPhpHelper.php'; |  | ||||||
|  |  | ||||||
| $app = new \Slim\App(new \Slim\Container([ |  | ||||||
| 	'settings' => [ |  | ||||||
| 		'displayErrorDetails' => true, |  | ||||||
| 	], |  | ||||||
| ])); |  | ||||||
| $container = $app->getContainer(); |  | ||||||
| $container['renderer'] = new PhpRenderer('./views'); |  | ||||||
|  |  | ||||||
| if (!Grocy::IsDemoInstallation()) |  | ||||||
| { |  | ||||||
| 	$isHttpsReverseProxied = !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'; |  | ||||||
| 	$app->add(new \Slim\Middleware\HttpBasicAuthentication([ |  | ||||||
| 		'realm' => 'grocy', |  | ||||||
| 		'secure' => !$isHttpsReverseProxied, |  | ||||||
| 		'users' => [ |  | ||||||
| 			HTTP_USER => HTTP_PASSWORD |  | ||||||
| 		] |  | ||||||
| 	])); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| $app->get('/', function(Request $request, Response $response) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 		'title' => 'Dashboard', |  | ||||||
| 		'contentPage' => 'dashboard.php', |  | ||||||
| 		'products' => $db->products(), |  | ||||||
| 		'currentStock' => GrocyLogicStock::GetCurrentStock() |  | ||||||
| 	]); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/purchase', function(Request $request, Response $response) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 		'title' => 'Purchase', |  | ||||||
| 		'contentPage' => 'purchase.php', |  | ||||||
| 		'products' => $db->products() |  | ||||||
| 	]); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/consumption', function(Request $request, Response $response) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 		'title' => 'Consumption', |  | ||||||
| 		'contentPage' => 'consumption.php', |  | ||||||
| 		'products' => $db->products() |  | ||||||
| 	]); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/products', function(Request $request, Response $response) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 		'title' => 'Products', |  | ||||||
| 		'contentPage' => 'products.php', |  | ||||||
| 		'products' => $db->products(), |  | ||||||
| 		'locations' => $db->locations(), |  | ||||||
| 		'quantityunits' => $db->quantity_units() |  | ||||||
| 	]); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/locations', function(Request $request, Response $response) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 		'title' => 'Locations', |  | ||||||
| 		'contentPage' => 'locations.php', |  | ||||||
| 		'locations' => $db->locations() |  | ||||||
| 	]); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/quantityunits', function(Request $request, Response $response) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 		'title' => 'Quantity units', |  | ||||||
| 		'contentPage' => 'quantityunits.php', |  | ||||||
| 		'quantityunits' => $db->quantity_units() |  | ||||||
| 	]); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/product/{productId}', function(Request $request, Response $response, $args) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	if ($args['productId'] == 'new') |  | ||||||
| 	{ |  | ||||||
| 		return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 			'title' => 'Create product', |  | ||||||
| 			'contentPage' => 'productform.php', |  | ||||||
| 			'locations' => $db->locations(), |  | ||||||
| 			'quantityunits' => $db->quantity_units(), |  | ||||||
| 			'mode' => 'create' |  | ||||||
| 		]); |  | ||||||
| 	} |  | ||||||
| 	else |  | ||||||
| 	{ |  | ||||||
| 		return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 			'title' => 'Edit product', |  | ||||||
| 			'contentPage' => 'productform.php', |  | ||||||
| 			'product' => $db->products($args['productId']), |  | ||||||
| 			'locations' => $db->locations(), |  | ||||||
| 			'quantityunits' => $db->quantity_units(), |  | ||||||
| 			'mode' => 'edit' |  | ||||||
| 		]); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/location/{locationId}', function(Request $request, Response $response, $args) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	if ($args['locationId'] == 'new') |  | ||||||
| 	{ |  | ||||||
| 		return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 			'title' => 'Create location', |  | ||||||
| 			'contentPage' => 'locationform.php', |  | ||||||
| 			'mode' => 'create' |  | ||||||
| 		]); |  | ||||||
| 	} |  | ||||||
| 	else |  | ||||||
| 	{ |  | ||||||
| 		return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 			'title' => 'Edit location', |  | ||||||
| 			'contentPage' => 'locationform.php', |  | ||||||
| 			'location' => $db->locations($args['locationId']), |  | ||||||
| 			'mode' => 'edit' |  | ||||||
| 		]); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->get('/quantityunit/{quantityunitId}', function(Request $request, Response $response, $args) |  | ||||||
| { |  | ||||||
| 	$db = Grocy::GetDbConnection(); |  | ||||||
|  |  | ||||||
| 	if ($args['quantityunitId'] == 'new') |  | ||||||
| 	{ |  | ||||||
| 		return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 			'title' => 'Create quantity unit', |  | ||||||
| 			'contentPage' => 'quantityunitform.php', |  | ||||||
| 			'mode' => 'create' |  | ||||||
| 		]); |  | ||||||
| 	} |  | ||||||
| 	else |  | ||||||
| 	{ |  | ||||||
| 		return $this->renderer->render($response, '/layout.php', [ |  | ||||||
| 			'title' => 'Edit quantity unit', |  | ||||||
| 			'contentPage' => 'quantityunitform.php', |  | ||||||
| 			'quantityunit' => $db->quantity_units($args['quantityunitId']), |  | ||||||
| 			'mode' => 'edit' |  | ||||||
| 		]); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->group('/api', function() |  | ||||||
| { |  | ||||||
| 	$this->get('/get-objects/{entity}', function(Request $request, Response $response, $args) |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnection(); |  | ||||||
| 		echo json_encode($db->{$args['entity']}()); |  | ||||||
|  |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->get('/get-object/{entity}/{objectId}', function(Request $request, Response $response, $args) |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnection(); |  | ||||||
| 		echo json_encode($db->{$args['entity']}($args['objectId'])); |  | ||||||
|  |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->post('/add-object/{entity}', function(Request $request, Response $response, $args) |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnection(); |  | ||||||
| 		$newRow = $db->{$args['entity']}()->createRow($request->getParsedBody()); |  | ||||||
| 		$newRow->save(); |  | ||||||
| 		$success = $newRow->isClean(); |  | ||||||
| 		echo json_encode(array('success' => $success)); |  | ||||||
|  |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->post('/edit-object/{entity}/{objectId}', function(Request $request, Response $response, $args) |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnection(); |  | ||||||
| 		$row = $db->{$args['entity']}($args['objectId']); |  | ||||||
| 		$row->update($request->getParsedBody()); |  | ||||||
| 		$success = $row->isClean(); |  | ||||||
| 		echo json_encode(array('success' => $success)); |  | ||||||
|  |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->get('/delete-object/{entity}/{objectId}', function(Request $request, Response $response, $args) |  | ||||||
| 	{ |  | ||||||
| 		$db = Grocy::GetDbConnection(); |  | ||||||
| 		$row = $db->{$args['entity']}($args['objectId']); |  | ||||||
| 		$row->delete(); |  | ||||||
| 		$success = $row->isClean(); |  | ||||||
| 		echo json_encode(array('success' => $success)); |  | ||||||
|  |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->get('/stock/get-product-details/{productId}', function(Request $request, Response $response, $args) |  | ||||||
| 	{ |  | ||||||
| 		echo json_encode(GrocyLogicStock::GetProductDetails($args['productId'])); |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->get('/stock/get-current-stock', function(Request $request, Response $response) |  | ||||||
| 	{ |  | ||||||
| 		echo json_encode(GrocyLogicStock::GetCurrentStock()); |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->get('/stock/consume-product/{productId}/{amount}', function(Request $request, Response $response, $args) |  | ||||||
| 	{ |  | ||||||
| 		$spoiled = false; |  | ||||||
| 		if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1') |  | ||||||
| 		{ |  | ||||||
| 			$spoiled = true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		echo json_encode(array('success' => GrocyLogicStock::ConsumeProduct($args['productId'], $args['amount'], $spoiled))); |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	$this->get('/helper/uniqid', function(Request $request, Response $response) |  | ||||||
| 	{ |  | ||||||
| 		echo json_encode(array('uniqid' => uniqid())); |  | ||||||
| 		return $response->withHeader('Content-Type', 'application/json'); |  | ||||||
| 	}); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $app->run(); |  | ||||||
							
								
								
									
										6
									
								
								localization/de/chore_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								localization/de/chore_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'manually' => 'Manuell', | ||||||
|  | 	'dynamic-regular' => 'Dynamisch regelmäßig' | ||||||
|  | ); | ||||||
							
								
								
									
										10
									
								
								localization/de/component_translations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								localization/de/component_translations.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'timeago_locale' => 'de', | ||||||
|  | 	'timeago_nan' => 'vor NaN Jahren', | ||||||
|  | 	'moment_locale' => 'de', | ||||||
|  | 	'datatables_localization' => '{"sEmptyTable":"Keine Daten in der Tabelle vorhanden","sInfo":"_START_ bis _END_ von _TOTAL_ Einträgen","sInfoEmpty":"Keine Daten vorhanden","sInfoFiltered":"(gefiltert von _MAX_ Einträgen)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ Einträge anzeigen","sLoadingRecords":"Wird geladen ..","sProcessing":"Bitte warten ..","sSearch":"Suchen","sZeroRecords":"Keine Einträge vorhanden","oPaginate":{"sFirst":"Erste","sPrevious":"Zurück","sNext":"Nächste","sLast":"Letzte"},"oAria":{"sSortAscending":": aktivieren, um Spalte aufsteigend zu sortieren","sSortDescending":": aktivieren, um Spalte absteigend zu sortieren"},"select":{"rows":{"0":"Zum Auswählen auf eine Zeile klicken","1":"1 Zeile ausgewählt","_":"%d Zeilen ausgewählt"}},"buttons":{"print":"Drucken","colvis":"Spalten","copy":"Kopieren","copyTitle":"In Zwischenablage kopieren","copyKeys":"Taste <i>ctrl</i> oder <i>⌘</i> + <i>C</i> um Tabelle<br>in Zwischenspeicher zu kopieren.<br><br>Um abzubrechen die Nachricht anklicken oder Escape drücken.","copySuccess":{"1":"1 Spalte kopiert","_":"%d Spalten kopiert"}}}', | ||||||
|  | 	'summernote_locale' => 'de-DE', | ||||||
|  | 	'fullcalendar_locale' => 'de' | ||||||
|  | ); | ||||||
							
								
								
									
										87
									
								
								localization/de/demo_data.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								localization/de/demo_data.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Cookies' => 'Cookies', | ||||||
|  | 	'Chocolate' => 'Schokolade', | ||||||
|  | 	'Pantry' => 'Vorratskammer', | ||||||
|  | 	'Candy cupboard' => 'Süßigkeitenschrank', | ||||||
|  | 	'Tinned food cupboard' => 'Konservenschrank', | ||||||
|  | 	'Fridge' => 'Kühlschrank', | ||||||
|  | 	'Piece' => 'Stück', | ||||||
|  | 	'Pieces' => 'Stücke', | ||||||
|  | 	'Pack' => 'Packung', | ||||||
|  | 	'Packs' => 'Packungen', | ||||||
|  | 	'Glass' => 'Glas', | ||||||
|  | 	'Glasses' => 'Gläser', | ||||||
|  | 	'Tin' => 'Dose', | ||||||
|  | 	'Tins' => 'Dosen', | ||||||
|  | 	'Can' => 'Becher', | ||||||
|  | 	'Cans' => 'Becher', | ||||||
|  | 	'Bunch' => 'Bund', | ||||||
|  | 	'Bunches' => 'Bunde', | ||||||
|  | 	'Gummy bears' => 'Gummibärchen', | ||||||
|  | 	'Crisps' => 'Chips', | ||||||
|  | 	'Eggs' => 'Eier', | ||||||
|  | 	'Noodles' => 'Nudeln', | ||||||
|  | 	'Pickles' => 'Essiggurken', | ||||||
|  | 	'Gulash soup' => 'Gulaschsuppe', | ||||||
|  | 	'Yogurt' => 'Joghurt', | ||||||
|  | 	'Cheese' => 'Käse', | ||||||
|  | 	'Cold cuts' => 'Aufschnitt', | ||||||
|  | 	'Paprika' => 'Paprika', | ||||||
|  | 	'Cucumber' => 'Gurke', | ||||||
|  | 	'Radish' => 'Radieschen', | ||||||
|  | 	'Tomato' => 'Tomaten', | ||||||
|  | 	'Changed towels in the bathroom' => 'Handtücher im Bad gewechselt', | ||||||
|  | 	'Cleaned the kitchen floor' => 'Küchenboden gewischt', | ||||||
|  | 	'Warranty ends' => 'Garantie endet', | ||||||
|  | 	'TV remote control' => 'TV Fernbedienung', | ||||||
|  | 	'Alarm clock' => 'Wecker', | ||||||
|  | 	'Heat remote control' => 'Fernbedienung Heizung', | ||||||
|  | 	'Lawn mowed in the garden' => 'Rasen im Garten gemäht', | ||||||
|  | 	'Some good snacks' => 'Paar gute Snacks', | ||||||
|  | 	'Pizza dough' => 'Pizzateig', | ||||||
|  | 	'Sieved tomatoes' => 'Passierte Tomaten', | ||||||
|  | 	'Salami' => 'Salami', | ||||||
|  | 	'Toast' => 'Toast', | ||||||
|  | 	'Minced meat' => 'Hackfleisch', | ||||||
|  | 	'Pizza' => 'Pizza', | ||||||
|  | 	'Spaghetti bolognese' => 'Spaghetti Bolognese', | ||||||
|  | 	'Sandwiches' => 'Belegte Toasts', | ||||||
|  | 	'English' => 'Englisch', | ||||||
|  | 	'German' => 'Deutsch', | ||||||
|  | 	'Italian' => 'Italienisch', | ||||||
|  | 	'Demo in different language' => 'Demo in anderer Sprache', | ||||||
|  | 	'This is the note content of the recipe ingredient' => 'Dies ist der Inhalt der Notiz der Zutat', | ||||||
|  | 	'Demo User' => 'Demo Benutzer', | ||||||
|  | 	'Gram' => 'Gramm', | ||||||
|  | 	'Grams' => 'Gramm', | ||||||
|  | 	'Flour' => 'Mehl', | ||||||
|  | 	'Pancakes' => 'Pfannkuchen', | ||||||
|  | 	'Sugar' => 'Zucker', | ||||||
|  | 	'Home' => 'Zuhause', | ||||||
|  | 	'Life' => 'Leben', | ||||||
|  | 	'Projects' => 'Projekte', | ||||||
|  | 	'Repair the garage door' => 'Garagentor reparieren', | ||||||
|  | 	'Fork and improve grocy' => 'grocy forken und verbessern', | ||||||
|  | 	'Find a solution for what to do when I forget the door keys' => 'Eine Lösung für "Haustürschlüssel vergessen" finden', | ||||||
|  | 	'Sweets' => 'Süßigkeiten', | ||||||
|  | 	'Bakery products' => 'Bäckerei Produkte', | ||||||
|  | 	'Tinned food' => 'Konservern', | ||||||
|  | 	'Butchery products' => 'Metzgerei', | ||||||
|  | 	'Vegetables/Fruits' => 'Obst/Gemüse', | ||||||
|  | 	'Refrigerated products' => 'Kühlregal', | ||||||
|  | 	'Coffee machine' => 'Kaffeemaschine', | ||||||
|  | 	'Dishwasher' => 'Spülmaschine', | ||||||
|  | 	'Liter' => 'Liter', | ||||||
|  | 	'Liters' => 'Liter', | ||||||
|  | 	'Bottle' => 'Flasche', | ||||||
|  | 	'Bottles' => 'Flaschen', | ||||||
|  | 	'Milk' => 'Milch', | ||||||
|  | 	'Chocolate sauce' => 'Schokoladensoße', | ||||||
|  | 	'Milliliters' => 'Milliliter', | ||||||
|  | 	'Milliliter' => 'Milliliter', | ||||||
|  | 	'Bottom' => 'Boden', | ||||||
|  | 	'Topping' => 'Belag', | ||||||
|  | 	'French' => 'Französisch' | ||||||
|  | ); | ||||||
							
								
								
									
										8
									
								
								localization/de/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								localization/de/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'purchase' => 'Einkauf', | ||||||
|  | 	'consume' => 'Verbrauch', | ||||||
|  | 	'inventory-correction' => 'Inventur-Korrektur', | ||||||
|  | 	'product-opened' => 'Produkt geöffnet' | ||||||
|  | ); | ||||||
							
								
								
									
										329
									
								
								localization/de/strings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								localization/de/strings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Stock overview' => 'Bestand', | ||||||
|  | 	'#1 products expiring within the next #2 days' => '#1 Produkte laufen innerhalb der nächsten #2 Tage ab', | ||||||
|  | 	'#1 products are already expired' => '#1 Produkte sind bereits abgelaufen', | ||||||
|  | 	'#1 products are below defined min. stock amount' => '#1 Produkte sind unter Mindestbestand', | ||||||
|  | 	'Product' => 'Produkt', | ||||||
|  | 	'Amount' => 'Menge', | ||||||
|  | 	'Next best before date' => 'Nächstes MHD', | ||||||
|  | 	'Logout' => 'Abmelden', | ||||||
|  | 	'Chores overview' => 'Hausarbeiten', | ||||||
|  | 	'Batteries overview' => 'Batterien', | ||||||
|  | 	'Purchase' => 'Einkauf', | ||||||
|  | 	'Consume' => 'Verbrauch', | ||||||
|  | 	'Inventory' => 'Inventur', | ||||||
|  | 	'Shopping list' => 'Einkaufszettel', | ||||||
|  | 	'Chore tracking' => 'Hausarbeiten-Ausführung', | ||||||
|  | 	'Battery tracking' => 'Batterie-Ladzyklus', | ||||||
|  | 	'Products' => 'Produkte', | ||||||
|  | 	'Locations' => 'Standorte', | ||||||
|  | 	'Quantity units' => 'Mengeneinheiten', | ||||||
|  | 	'Chores' => 'Hausarbeiten', | ||||||
|  | 	'Batteries' => 'Batterien', | ||||||
|  | 	'Chore' => 'Hausarbeit', | ||||||
|  | 	'Next estimated tracking' => 'Nächste geplante Ausführung', | ||||||
|  | 	'Last tracked' => 'Zuletzt ausgeführt', | ||||||
|  | 	'Battery' => 'Batterie', | ||||||
|  | 	'Last charged' => 'Zuletzt geladen', | ||||||
|  | 	'Next planned charge cycle' => 'Nächster geplanter Ladezyklus', | ||||||
|  | 	'Best before' => 'MHD', | ||||||
|  | 	'OK' => 'OK', | ||||||
|  | 	'Product overview' => 'Produktübersicht', | ||||||
|  | 	'Stock quantity unit' => 'Mengeneinheit Bestand', | ||||||
|  | 	'Stock amount' => 'Bestand', | ||||||
|  | 	'Last purchased' => 'Zuletzt gekauft', | ||||||
|  | 	'Last used' => 'Zuletzt benutzt', | ||||||
|  | 	'Spoiled' => 'Verdorben', | ||||||
|  | 	'Barcode lookup is disabled' => 'Barcode-Suche ist deaktiviert', | ||||||
|  | 	'will be added to the list of barcodes for the selected product on submit' => 'wird der Liste der Barcodes für das ausgewählte Produkt beim Speichern hinzugefügt', | ||||||
|  | 	'New amount' => 'Neue Menge', | ||||||
|  | 	'Note' => 'Notiz', | ||||||
|  | 	'Tracked time' => 'Ausführungszeit', | ||||||
|  | 	'Chore overview' => 'Hausarbeit Übersicht', | ||||||
|  | 	'Tracked count' => 'Ausführungsanzahl', | ||||||
|  | 	'Battery overview' => 'Batterie Übersicht', | ||||||
|  | 	'Charge cycles count' => 'Ladezyklen', | ||||||
|  | 	'Create shopping list item' => 'Einkaufszettel Eintrag erstellen', | ||||||
|  | 	'Edit shopping list item' => 'Einkaufszettel Eintrag bearbeiten', | ||||||
|  | 	'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 Einheiten wurden automatisch hinzugefügt und gelten zusätzlich der hier eingegebenen Menge', | ||||||
|  | 	'Save' => 'Speichern', | ||||||
|  | 	'Add' => 'Hinzufügen', | ||||||
|  | 	'Name' => 'Name', | ||||||
|  | 	'Location' => 'Standort', | ||||||
|  | 	'Min. stock amount' => 'Mindestbestand', | ||||||
|  | 	'QU purchase' => 'ME Einkauf', | ||||||
|  | 	'QU stock' => 'ME Bestand', | ||||||
|  | 	'QU factor' => 'ME-Faktor', | ||||||
|  | 	'Description' => 'Beschreibung', | ||||||
|  | 	'Create product' => 'Produkt erstellen', | ||||||
|  | 	'Barcode(s)' => 'Barcode(s)', | ||||||
|  | 	'Minimum stock amount' => 'Mindestbestand', | ||||||
|  | 	'Default best before days' => 'Standard Haltbarkeit in Tagen', | ||||||
|  | 	'Quantity unit purchase' => 'Mengeneinheit Einkauf', | ||||||
|  | 	'Quantity unit stock' => 'Mengeneinheit Bestand', | ||||||
|  | 	'Factor purchase to stock quantity unit' => 'Faktor Mengeneinheit Einkauf zu Mengeneinheit Bestand', | ||||||
|  | 	'Create location' => 'Standort erstellen', | ||||||
|  | 	'Create quantity unit' => 'Mengeneinheit erstellen', | ||||||
|  | 	'Period type' => 'Periodentyp', | ||||||
|  | 	'Period days' => 'Tage/Periode', | ||||||
|  | 	'Create chore' => 'Hausarbeit erstellen', | ||||||
|  | 	'Used in' => 'Benutzt in', | ||||||
|  | 	'Create battery' => 'Batterie erstellen', | ||||||
|  | 	'Edit battery' => 'Batterie bearbeiten', | ||||||
|  | 	'Edit chore' => 'Hausarbeit bearbeiten', | ||||||
|  | 	'Edit quantity unit' => 'Mengeneinheit bearbeiten', | ||||||
|  | 	'Edit product' => 'Produkt bearbeiten', | ||||||
|  | 	'Edit location' => 'Standort bearbeiten', | ||||||
|  | 	'Record data' => 'Daten erfassen', | ||||||
|  | 	'Manage master data' => 'Stammdaten verwalten', | ||||||
|  | 	'This will apply to added products' => 'Dies gilt für hinzugefügte Produkte', | ||||||
|  | 	'never' => 'nie', | ||||||
|  | 	'Add products that are below defined min. stock amount' => 'Produkte unter Mindestbestand hinzufügen', | ||||||
|  | 	'For purchases this amount of days will be added to today for the best before date suggestion' => 'Bei Einkäufen wird hierauf basierend das MHD vorausgefüllt', | ||||||
|  | 	'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Das bedeutet 1 #1 im Einkauf entsprechen #2 #3 im Bestand', | ||||||
|  | 	'Login' => 'Anmelden', | ||||||
|  | 	'Username' => 'Benutzername', | ||||||
|  | 	'Password' => 'Passwort', | ||||||
|  | 	'Invalid credentials, please try again' => 'Ungültige Zugangsdaten, bitte versuche es erneut', | ||||||
|  | 	'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?', | ||||||
|  | 	'Yes' => 'Ja', | ||||||
|  | 	'No' => 'Nein', | ||||||
|  | 	'Are you sure to delete chore "#1"?' => 'Hausarbeit "#1" wirklich löschen?', | ||||||
|  | 	'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?', | ||||||
|  | 	'Create or assign product' => 'Produkt erstellen oder verknüpfen', | ||||||
|  | 	'Cancel' => 'Abbrechen', | ||||||
|  | 	'Add as new product' => 'Als neues Produkt hinzufügen', | ||||||
|  | 	'Add as barcode to existing product' => 'Barcode vorhandenem Produkt zuweisen', | ||||||
|  | 	'Add as new product and prefill barcode' => 'Neues Produkt erstellen und Barcode vorbelegen', | ||||||
|  | 	'Are you sure to delete quantity unit "#1"?' => 'Mengeneinheit "#1" wirklich löschen?', | ||||||
|  | 	'Are you sure to delete product "#1"?' => 'Produkt "#1" wirklich löschen?', | ||||||
|  | 	'Are you sure to delete location "#1"?' => 'Standort "#1" wirklich löschen?', | ||||||
|  | 	'Manage API keys' => 'API-Keys verwalten', | ||||||
|  | 	'REST API & data model documentation' => 'REST-API & Datenmodell Dokumentation', | ||||||
|  | 	'API keys' => 'API-Keys', | ||||||
|  | 	'Create new API key' => 'Neuen API-Key erstellen', | ||||||
|  | 	'API key' => 'API-Key', | ||||||
|  | 	'Expires' => 'Läuft ab', | ||||||
|  | 	'Created' => 'Erstellt', | ||||||
|  | 	'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig', | ||||||
|  | 	'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt', | ||||||
|  | 	'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt', | ||||||
|  | 	'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Hausarbeit #1 Tage nach der letzten Ausführung geplant wird', | ||||||
|  | 	'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt', | ||||||
|  | 	'About grocy' => 'Über grocy', | ||||||
|  | 	'Close' => 'Schließen', | ||||||
|  | 	'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden', | ||||||
|  | 	'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig', | ||||||
|  | 	'#1 chores are due to be done within the next #2 days' => '#1 Hausarbeiten stehen in den nächsten #2 Tagen an', | ||||||
|  | 	'#1 chores are overdue to be done' => '#1 Hausarbeiten sind überfällig', | ||||||
|  | 	'Released on' => 'Veröffentlicht am', | ||||||
|  | 	'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2', | ||||||
|  | 	'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt', | ||||||
|  | 	'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand', | ||||||
|  | 	'Tracked execution of chore #1 on #2' => 'Ausführung von #1 am #2 erfasst', | ||||||
|  | 	'Tracked charge cycle of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst', | ||||||
|  | 	'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1', | ||||||
|  | 	'All' => 'Alle', | ||||||
|  | 	'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1', | ||||||
|  | 	'Track execution of chore #1' => 'Erfasse eine Ausführung von #1', | ||||||
|  | 	'Filter by location' => 'Nach Standort filtern', | ||||||
|  | 	'Search' => 'Suche', | ||||||
|  | 	'Not logged in' => 'Nicht angemeldet', | ||||||
|  | 	'You have to select a product' => 'Ein Produkt muss ausgewählt werden', | ||||||
|  | 	'You have to select a chore' => 'Eine Hausarbeit muss ausgewählt werden', | ||||||
|  | 	'You have to select a battery' => 'Eine Batterie muss ausgewählt werden', | ||||||
|  | 	'A name is required' => 'Ein Name ist erforderlich', | ||||||
|  | 	'A location is required' => 'Ein Standort ist erforderlich', | ||||||
|  | 	'The amount cannot be lower than #1' => 'Die Menge darf nicht kleiner als #1 sein', | ||||||
|  | 	'This cannot be negative' => 'Dies darf nicht negativ sein', | ||||||
|  | 	'A quantity unit is required' => 'Eine Mengeneinheit muss ausgewählt werden', | ||||||
|  | 	'A period type is required' => 'Eine Periodentyp muss ausgewählt werden', | ||||||
|  | 	'A best before date is required and must be later than today' => 'Ein Mindesthaltbarkeitsdatum ist erforderlich und muss später als heute sein', | ||||||
|  | 	'Settings' => 'Einstellungen', | ||||||
|  | 	'This can only be before now' => 'Dies kann nur vor jetzt sein', | ||||||
|  | 	'Calendar' => 'Kalender', | ||||||
|  | 	'Recipes' => 'Rezepte', | ||||||
|  | 	'Edit recipe' => 'Rezept bearbeiten', | ||||||
|  | 	'New recipe' => 'Neues Rezept', | ||||||
|  | 	'Ingredients list' => 'Zutatenliste', | ||||||
|  | 	'Add recipe ingredient' => 'Rezeptzutat hinzufügen', | ||||||
|  | 	'Edit recipe ingredient' => 'Rezeptzutat bearbeiten', | ||||||
|  | 	'Are you sure to delete recipe "#1"?' => 'Rezept "#1" wirklich löschen?', | ||||||
|  | 	'Are you sure to delete recipe ingredient "#1"?' => 'Rezeptzutat "#1" wirklich löschen?', | ||||||
|  | 	'Are you sure to empty the shopping list?' => 'Sicher, dass den Einkaufszettel geleert werden soll?', | ||||||
|  | 	'Clear list' => 'Liste leeren', | ||||||
|  | 	'Requirements fulfilled' => 'Bedarf im Bestand', | ||||||
|  | 	'Put missing products on shopping list' => 'Fehlende Produkte auf den Einkaufszettel setzen', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing' => 'Nicht ausreichend im Bestand, #1 Zutaten fehlen', | ||||||
|  | 	'Enough in stock' => 'Bestand reicht aus', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Bestand nicht ausreichend, #1 Zutaten fehlen, stehen aber bereits auf dem Einkaufszettel', | ||||||
|  | 	'Expand to fullscreen' => 'Auf ganzen Bildschirm vergrößern', | ||||||
|  | 	'Ingredients' => 'Zutaten', | ||||||
|  | 	'Preparation' => 'Zubereitung', | ||||||
|  | 	'Recipe' => 'Rezept', | ||||||
|  | 	'Not enough in stock, #1 missing, #2 already on shopping list' => 'Nicht ausreichend im Bestand, #1 fehlen, #2 stehen bereits auf dem Einkaufszettel', | ||||||
|  | 	'Show notes' => 'Notizen anzeigen', | ||||||
|  | 	'Put missing amount on shopping list' => 'Fehlende Menge auf den Einkaufszettel setzen', | ||||||
|  | 	'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Sicher alle fehlenden Zutaten für Rezept "#1" auf die Einkaufsliste zu setzen?', | ||||||
|  | 	'Added for recipe #1' => 'Hinzugefügt für Rezept #1', | ||||||
|  | 	'Manage users' => 'Benutzer verwalten', | ||||||
|  | 	'User' => 'Benutzer', | ||||||
|  | 	'Users' => 'Benutzer', | ||||||
|  | 	'Are you sure to delete user "#1"?' => 'Benutzer "#1" wirklich löschen?', | ||||||
|  | 	'Create user' => 'Benutzer erstellen', | ||||||
|  | 	'Edit user' => 'Benutzer bearbeiten', | ||||||
|  | 	'First name' => 'Vorname', | ||||||
|  | 	'Last name' => 'Nachname', | ||||||
|  | 	'A username is required' => 'Ein Benutzername ist erforderlich', | ||||||
|  | 	'Confirm password' => 'Passwort bestätigen', | ||||||
|  | 	'Passwords do not match' => 'Passwörter stimmen nicht überein', | ||||||
|  | 	'Change password' => 'Passwort ändern', | ||||||
|  | 	'Done by' => 'Ausgeführt von', | ||||||
|  | 	'Last done by' => 'Zuletzt ausgeführt von', | ||||||
|  | 	'Unknown' => 'Unbekannt', | ||||||
|  | 	'Filter by chore' => 'Nach Hausarbeit filtern', | ||||||
|  | 	'Chores journal' => 'Hausarbeitenjournal', | ||||||
|  | 	'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind', | ||||||
|  | 	'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)', | ||||||
|  | 	'Last price' => 'Letzter Preis', | ||||||
|  | 	'Price history' => 'Preisentwicklung', | ||||||
|  | 	'No price history available' => 'Keine Preisdaten verfügbar', | ||||||
|  | 	'Price' => 'Preis', | ||||||
|  | 	'in #1 per purchase quantity unit' => 'in #1 pro Einkaufsmengeneinheit', | ||||||
|  | 	'The price cannot be lower than #1' => 'Der Preis darf nicht niedriger als #1 sein', | ||||||
|  | 	'#1 product expires within the next #2 days' => '#1 Produkt läuft innerhalb der nächsten #2 Tage ab', | ||||||
|  | 	'#1 product is already expired' => '#1 Produkt ist bereits abgelaufen', | ||||||
|  | 	'#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand', | ||||||
|  | 	'Unit' => 'Einheit', | ||||||
|  | 	'Units' => 'Einheiten', | ||||||
|  | 	'#1 chore is due to be done within the next #2 days' => '#1 Hausarbeit steht in den nächsten #2 Tagen an', | ||||||
|  | 	'#1 chore is overdue to be done' => '#1 Hausarbeit ist überfällig', | ||||||
|  | 	'#1 battery is due to be charged within the next #2 days' => '#1 Batterie muss in den nächsten #2 Tagen geladen werden', | ||||||
|  | 	'#1 battery is overdue to be charged' => '#1 Batterie ist überfällig', | ||||||
|  | 	'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 Einheit wurde automatisch hinzugefügt und gilt zusätzlich der hier eingegebenen Menge', | ||||||
|  | 	'in singular form' => 'in der Einzahl', | ||||||
|  | 	'in plural form' => 'in der Mehrzahl', | ||||||
|  | 	'Never expires' => 'Läuft nie ab', | ||||||
|  | 	'This cannot be lower than #1' => 'Dies darf nicht kleiner als #1 sein', | ||||||
|  | 	'-1 means that this product never expires' => '-1 bedeuet, dass dieses Produkt niemals abläuft', | ||||||
|  | 	'Quantity unit' => 'Mengeneinheit', | ||||||
|  | 	'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Nur prüfen, ob eine einzelne Einheit vorrätig ist (eine abweichende Mengeneinheit kann dann oben verwendet werden)', | ||||||
|  | 	'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Sicher, dass alle Zutaten die vom Rezept "#1" benötigt werden aus dem Bestand entfernt werden sollen (Zutaten markiert mit "nur prüfen, ob eine einzelne Einheit vorrätig ist" werden ignoriert)?', | ||||||
|  | 	'Removed all ingredients of recipe "#1" from stock' => 'Alle Zutaten, die vom Rezept "#1" benötigt werden, wurdem aus dem Bestand entfernt', | ||||||
|  | 	'Consume all ingredients needed by this recipe' => 'Alle Zutaten, die von diesem Rezept benötigt werden, aus dem Bestand enternen', | ||||||
|  | 	'Click to show technical details' => 'Klick um technische Details anzuzeigen', | ||||||
|  | 	'Error while saving, probably this item already exists' => 'Fehler beim Speichern, möglicherweise existiert das Element bereits', | ||||||
|  | 	'Error details' => 'Fehlerdetails', | ||||||
|  | 	'Tasks' => 'Aufgaben', | ||||||
|  | 	'Show done tasks' => 'Erledigte Aufgaben anzeigen', | ||||||
|  | 	'Task' => 'Aufgabe', | ||||||
|  | 	'Due' => 'Fällig', | ||||||
|  | 	'Assigned to' => 'Zugewiesen an', | ||||||
|  | 	'Mark task "#1" as completed' => 'Aufgabe "#1" als erledigt markieren', | ||||||
|  | 	'Uncategorized' => 'Nicht kategorisiert', | ||||||
|  | 	'Task categories' => 'Aufgabenkategorien', | ||||||
|  | 	'Create task' => 'Aufgabe erstellen', | ||||||
|  | 	'A due date is required' => 'Ein Fälligkeitsdatum ist erforderlich', | ||||||
|  | 	'Category' => 'Kategorie', | ||||||
|  | 	'Edit task' => 'Aufgabe bearbeiten', | ||||||
|  | 	'Are you sure to delete task "#1"?' => 'Aufgabe "#1" wirklich löschen?', | ||||||
|  | 	'#1 task is due to be done within the next #2 days' => '#1 Aufgabe steht in den nächsten #2 Tagen an', | ||||||
|  | 	'#1 tasks are due to be done within the next #2 days' => '#1 Aufgaben stehen in den nächsten #2 Tagen an', | ||||||
|  | 	'#1 task is overdue to be done' => '#1 Aufgabe ist überfällig', | ||||||
|  | 	'#1 tasks are overdue to be done' => '#1 Aufgaben sind überfällig', | ||||||
|  | 	'Edit task category' => 'Aufgabenkategorie bearbeiten', | ||||||
|  | 	'Create task category' => 'Aufgabenkategorie erstellen', | ||||||
|  | 	'Product groups' => 'Produktgruppen', | ||||||
|  | 	'Ungrouped' => 'Ungruppiert', | ||||||
|  | 	'Create product group' => 'Produktgruppe erstellen', | ||||||
|  | 	'Edit product group' => 'Produktgruppe bearbeiten', | ||||||
|  | 	'Product group' => 'Produktgruppe', | ||||||
|  | 	'Are you sure to delete product group "#1"?' => 'Produktgruppe "#1" wirklich löschen?', | ||||||
|  | 	'Stay logged in permanently' => 'Dauerhaft angemeldet bleiben', | ||||||
|  | 	'When not set, you will get logged out at latest after 30 days' => 'Wenn nicht gesetzt, wirst du spätestens nach 30 Tagen automatisch abgemeldet', | ||||||
|  | 	'Filter by status' => 'Nach Status filtern', | ||||||
|  | 	'Below min. stock amount' => 'Unter Mindestbestand', | ||||||
|  | 	'Expiring soon' => 'Bald ablaufend', | ||||||
|  | 	'Already expired' => 'Bereits abgelaufen', | ||||||
|  | 	'Due soon' => 'Bald fällig', | ||||||
|  | 	'Overdue' => 'Überfällig', | ||||||
|  | 	'View settings' => 'Ansichtseinstellungen', | ||||||
|  | 	'Auto reload on external changes' => 'Autom. akt. bei externen Änderungen', | ||||||
|  | 	'Enable night mode' => 'Nachtmodus aktivieren', | ||||||
|  | 	'Auto enable in time range' => 'Autom. akt. in diesem Zeitraum', | ||||||
|  | 	'From' => 'Von', | ||||||
|  | 	'in format' => 'im Format', | ||||||
|  | 	'To' => 'Bis', | ||||||
|  | 	'Time range goes over midnight' => 'Zeitraum geht über Mitternacht', | ||||||
|  | 	'Product picture' => 'Produktbild', | ||||||
|  | 	'No file selected' => 'Keine Datei ausgewählt', | ||||||
|  | 	'If you don\'t select a file, the current picture will not be altered' => 'Wenn du keine Datei auswählst, wird das aktuelle Bild nicht verändert', | ||||||
|  | 	'Current picture' => 'Aktuelles Bild', | ||||||
|  | 	'Delete' => 'Löschen', | ||||||
|  | 	'The current picture will be deleted when you save the product' => 'Das aktuelle Bild wird beim Speichern des Produkts gelöscht', | ||||||
|  | 	'Select file' => 'Datei auswählen', | ||||||
|  | 	'Image of product #1' => 'Bild des Produkts #1', | ||||||
|  | 	'This product cannot be deleted because it is in stock, please remove the stock amount first.' => 'Dieses Produkt kann nicht gelöscht werden, da es auf Lager ist, bitte  zuerst den Bestand entfernen.', | ||||||
|  | 	'Delete not possible' => 'Löschen nicht möglich', | ||||||
|  | 	'Equipment' => 'Ausstattung', | ||||||
|  | 	'Instruction manual' => 'Bedienungsanleitung', | ||||||
|  | 	'The selected equipment has no instruction manual' => 'Das ausgewählte Gerät hat keine Bedienungsanleitung', | ||||||
|  | 	'Notes' => 'Notizen', | ||||||
|  | 	'Edit equipment' => 'Geräte bearbeiten', | ||||||
|  | 	'Create equipment' => 'Geräte erstellen', | ||||||
|  | 	'If you don\'t select a file, the current instruction manual will not be altered' => 'Wenn du keine Datei auswählst, wird die aktuelle Bedienungsanleitung nicht verändert', | ||||||
|  | 	'Current instruction manual' => 'Aktuelle Bedienungsanleitung', | ||||||
|  | 	'No instruction manual available' => 'Keine Bedienungsanleitung vorhanden', | ||||||
|  | 	'The current instruction manual will be deleted when you save the equipment' => 'Die aktuelle Bedienungsanleitung wird beim Speichern des Geräts gelöscht', | ||||||
|  | 	'No picture available' => 'Kein Bild vorhanden', | ||||||
|  | 	'Filter by product group' => 'Nach Produktgruppe filtern', | ||||||
|  | 	'Presets for new products' => 'Vorgaben für neue Produkte', | ||||||
|  | 	'Included recipes' => 'Enthaltene Rezepte', | ||||||
|  | 	'A recipe is required' => 'Ein Rezept ist erforderlich', | ||||||
|  | 	'Add included recipe' => 'Enthaltenes Rezept hinzufügen', | ||||||
|  | 	'Edit included recipe' => 'Enthaltenes Rezept bearbeiten', | ||||||
|  | 	'Group' => 'Gruppe', | ||||||
|  | 	'This will be used as a headline to group ingredients together' => 'Dies wird als Überschrift verwendet, um  Zutaten zusammenzufassen', | ||||||
|  | 	'Journal' => 'Journal', | ||||||
|  | 	'Stock journal' => 'Bestandsjournal', | ||||||
|  | 	'Filter by product' => 'Nach Produkt filtern', | ||||||
|  | 	'Booking time' => 'Buchungszeit', | ||||||
|  | 	'Booking type' => 'Buchungsart', | ||||||
|  | 	'Undo booking' => 'Buchung rückgängig machen', | ||||||
|  | 	'Undone on' => 'Rückgängig gemacht am', | ||||||
|  | 	'Batteries journal' => 'Batteriejournal', | ||||||
|  | 	'Filter by battery' => 'Nach Batterie filtern', | ||||||
|  | 	'Undo charge cycle' => 'Ladezyklus rückgängig machen', | ||||||
|  | 	'Undo chore execution' => 'Ausführung rückgängig machen', | ||||||
|  | 	'Chore execution successfully undone' => 'Ausführung erfolgreich rückgängig gemacht', | ||||||
|  | 	'Undo' => 'Rückgängig machen', | ||||||
|  | 	'Booking successfully undone' => 'Buchung erfolgreich rückgängig gemacht', | ||||||
|  | 	'Charge cycle successfully undone' => 'Ladezyklus erfolgreich rückgängig gemacht', | ||||||
|  | 	'This cannot be negative and must be an integral number' => 'Diese darf nicht negativ und muss eine ganze Zahl sein', | ||||||
|  | 	'Disable stock fulfillment checking for this ingredient' => 'Bestandsprüfung für diese Zutat deaktivieren', | ||||||
|  | 	'Add all list items to stock' => 'Alle Einträge zum Bestand hinzufügen', | ||||||
|  | 	'Add #3 #1 of #2 to stock' => 'Füge #3 #1 of #2 dem Bestand hinzu', | ||||||
|  | 	'Adding shopping list item #1 of #2' => 'Bearbeite Einkausfzettel-Eintrag #1 von #2', | ||||||
|  | 	'Use a specific stock item' => 'Einen bestimmten Bestandseintrag verwenden', | ||||||
|  | 	'The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"' => 'Der erste Eintrag in dieser Liste würde von der Standardregel "Zuerst ablaufende zuerst, dann First In - First Out" ausgewählt werden', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => '#3 #1 von #2 als geöffnet markieren', | ||||||
|  | 	'When a product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)' => 'Wenn ein Produkt als geöffnet markiert wurde, wird das Mindesthaltbarkeitsdatum durch heute + diese Anzahl von Tagen ersetzt (ein Wert von 0 deaktiviert dies)', | ||||||
|  | 	'Default best before days after opened' => 'Standard Haltbarkeit in Tagen nach dem Öffnen', | ||||||
|  | 	'Marked #1 #2 of #3 as opened' => '#1 #2 von #3 als geöffnet markiert', | ||||||
|  | 	'Mark as opened' => 'Als geöffnet markieren', | ||||||
|  | 	'Expires on #1; Bought on #2' => 'Läuft ab am #1; Gekauft am #2', | ||||||
|  | 	'Not opened' => 'Nicht geöffnet', | ||||||
|  | 	'Opened' => 'Geöffnet', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => '#3 #1 von #2 als geöffnet markieren', | ||||||
|  | 	'#1 opened' => '#1 geöffnet', | ||||||
|  | 	'Product expires' => 'Produkt läuft ab', | ||||||
|  | 	'Task due' => 'Aufgabe fällig', | ||||||
|  | 	'Chore due' => 'Hausarbeit fällig', | ||||||
|  | 	'Battery charge cycle due' => 'Battery-Ladezyklus fällig', | ||||||
|  | 	'Show clock in header' => 'Uhr in der Kopfzeile anzeigen', | ||||||
|  | 	'Stock settings' => 'Bestandseinstellungen', | ||||||
|  | 	'Shopping list to stock workflow' => 'Einkaufsliste -> Bestand 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' => 'Buchung automatisch ausführen, wenn das Produkt "Standard Haltbarkeit in Tagen" hinterlegt hat (als Preis wird der letzte Preis verwendet)' | ||||||
|  | ); | ||||||
							
								
								
									
										6
									
								
								localization/en/chore_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								localization/en/chore_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'manually' => 'Manually', | ||||||
|  | 	'dynamic-regular' => 'Dynamic regular' | ||||||
|  | ); | ||||||
							
								
								
									
										10
									
								
								localization/en/component_translations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								localization/en/component_translations.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'timeago_locale' => 'en', | ||||||
|  | 	'timeago_nan' => 'NaN years ago', | ||||||
|  | 	'moment_locale' => 'x', | ||||||
|  | 	'datatables_localization' => '{"sEmptyTable":"No data available in table","sInfo":"Showing _START_ to _END_ of _TOTAL_ entries","sInfoEmpty":"Showing 0 to 0 of 0 entries","sInfoFiltered":"(filtered from _MAX_ total entries)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Show _MENU_ entries","sLoadingRecords":"Loading...","sProcessing":"Processing...","sSearch":"Search:","sZeroRecords":"No matching records found","oPaginate":{"sFirst":"First","sLast":"Last","sNext":"Next","sPrevious":"Previous"},"oAria":{"sSortAscending":": activate to sort column ascending","sSortDescending":": activate to sort column descending"}}', | ||||||
|  | 	'summernote_locale' => 'x', | ||||||
|  | 	'fullcalendar_locale' => 'x' | ||||||
|  | ); | ||||||
							
								
								
									
										87
									
								
								localization/en/demo_data.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								localization/en/demo_data.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Cookies' => 'Cookies', | ||||||
|  | 	'Chocolate' => 'Chocolate', | ||||||
|  | 	'Pantry' => 'Pantry', | ||||||
|  | 	'Candy cupboard' => 'Candy cupboard', | ||||||
|  | 	'Tinned food cupboard' => 'Tinned food cupboard', | ||||||
|  | 	'Fridge' => 'Fridge', | ||||||
|  | 	'Piece' => 'Piece', | ||||||
|  | 	'Pieces' => 'Pieces', | ||||||
|  | 	'Pack' => 'Pack', | ||||||
|  | 	'Packs' => 'Packs', | ||||||
|  | 	'Glass' => 'Glass', | ||||||
|  | 	'Glasses' => 'Glasses', | ||||||
|  | 	'Tin' => 'Tin', | ||||||
|  | 	'Tins' => 'Tins', | ||||||
|  | 	'Can' => 'Can', | ||||||
|  | 	'Cans' => 'Cans', | ||||||
|  | 	'Bunch' => 'Bunch', | ||||||
|  | 	'Bunches' => 'Bunches', | ||||||
|  | 	'Gummy bears' => 'Gummy bears', | ||||||
|  | 	'Crisps' => 'Crisps', | ||||||
|  | 	'Eggs' => 'Eggs', | ||||||
|  | 	'Noodles' => 'Noodles', | ||||||
|  | 	'Pickles' => 'Pickles', | ||||||
|  | 	'Gulash soup' => 'Gulash soup', | ||||||
|  | 	'Yogurt' => 'Yogurt', | ||||||
|  | 	'Cheese' => 'Cheese', | ||||||
|  | 	'Cold cuts' => 'Cold cuts', | ||||||
|  | 	'Paprika' => 'Paprika', | ||||||
|  | 	'Cucumber' => 'Cucumber', | ||||||
|  | 	'Radish' => 'Radish', | ||||||
|  | 	'Tomato' => 'Tomato', | ||||||
|  | 	'Changed towels in the bathroom' => 'Changed towels in the bathroom', | ||||||
|  | 	'Cleaned the kitchen floor' => 'Cleaned the kitchen floor', | ||||||
|  | 	'Warranty ends' => 'Warranty ends', | ||||||
|  | 	'TV remote control' => 'TV remote control', | ||||||
|  | 	'Alarm clock' => 'Alarm clock', | ||||||
|  | 	'Heat remote control' => 'Heat remote control', | ||||||
|  | 	'Lawn mowed in the garden' => 'Lawn mowed in the garden', | ||||||
|  | 	'Some good snacks' => 'Some good snacks', | ||||||
|  | 	'Pizza dough' => 'Pizza dough', | ||||||
|  | 	'Sieved tomatoes' => 'Sieved tomatoes', | ||||||
|  | 	'Salami' => 'Salami', | ||||||
|  | 	'Toast' => 'Toast', | ||||||
|  | 	'Minced meat' => 'Minced meat', | ||||||
|  | 	'Pizza' => 'Pizza', | ||||||
|  | 	'Spaghetti bolognese' => 'Spaghetti bolognese', | ||||||
|  | 	'Sandwiches' => 'Sandwiches', | ||||||
|  | 	'English' => 'English', | ||||||
|  | 	'German' => 'German', | ||||||
|  | 	'Italian' => 'Italian', | ||||||
|  | 	'Demo in different language' => 'Demo in different language', | ||||||
|  | 	'This is the note content of the recipe ingredient' => 'This is the note content of the recipe ingredient', | ||||||
|  | 	'Demo User' => 'Demo User', | ||||||
|  | 	'Gram' => 'Gram', | ||||||
|  | 	'Grams' => 'Grams', | ||||||
|  | 	'Flour' => 'Flour', | ||||||
|  | 	'Pancakes' => 'Pancakes', | ||||||
|  | 	'Sugar' => 'Sugar', | ||||||
|  | 	'Home' => 'Home', | ||||||
|  | 	'Life' => 'Life', | ||||||
|  | 	'Projects' => 'Projects', | ||||||
|  | 	'Repair the garage door' => 'Repair the garage door', | ||||||
|  | 	'Fork and improve grocy' => 'Fork and improve grocy', | ||||||
|  | 	'Find a solution for what to do when I forget the door keys' => 'Find a solution for what to do when I forget the door keys', | ||||||
|  | 	'Sweets' => 'Sweets', | ||||||
|  | 	'Bakery products' => 'Bakery products', | ||||||
|  | 	'Tinned food' => 'Tinned food', | ||||||
|  | 	'Butchery products' => 'Butchery products', | ||||||
|  | 	'Vegetables/Fruits' => 'Vegetables/Fruits', | ||||||
|  | 	'Refrigerated products' => 'Refrigerated products', | ||||||
|  | 	'Coffee machine' => 'Coffee machine', | ||||||
|  | 	'Dishwasher' => 'Dishwasher', | ||||||
|  | 	'Liter' => 'Liter', | ||||||
|  | 	'Liters' => 'Liters', | ||||||
|  | 	'Bottle' => 'Bottle', | ||||||
|  | 	'Bottles' => 'Bottles', | ||||||
|  | 	'Milk' => 'Milk', | ||||||
|  | 	'Chocolate sauce' => 'Chocolate sauce', | ||||||
|  | 	'Milliliters' => 'Milliliters', | ||||||
|  | 	'Milliliter' => 'Milliliter', | ||||||
|  | 	'Bottom' => 'Bottom', | ||||||
|  | 	'Topping' => 'Topping', | ||||||
|  | 	'French' => 'French' | ||||||
|  | ); | ||||||
							
								
								
									
										8
									
								
								localization/en/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								localization/en/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'purchase' => 'Purchase', | ||||||
|  | 	'consume' => 'Consume', | ||||||
|  | 	'inventory-correction' => 'Inventory correction', | ||||||
|  | 	'product-opened' => 'Product opened' | ||||||
|  | ); | ||||||
							
								
								
									
										329
									
								
								localization/en/strings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								localization/en/strings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Stock overview' => 'Stock overview', | ||||||
|  | 	'#1 products expiring within the next #2 days' => '#1 products expiring within the next #2 days', | ||||||
|  | 	'#1 products are already expired' => '#1 products are already expired', | ||||||
|  | 	'#1 products are below defined min. stock amount' => '#1 products are below defined min. stock amount', | ||||||
|  | 	'Product' => 'Product', | ||||||
|  | 	'Amount' => 'Amount', | ||||||
|  | 	'Next best before date' => 'Next best before date', | ||||||
|  | 	'Logout' => 'Logout', | ||||||
|  | 	'Chores overview' => 'Chores overview', | ||||||
|  | 	'Batteries overview' => 'Batteries overview', | ||||||
|  | 	'Purchase' => 'Purchase', | ||||||
|  | 	'Consume' => 'Consume', | ||||||
|  | 	'Inventory' => 'Inventory', | ||||||
|  | 	'Shopping list' => 'Shopping list', | ||||||
|  | 	'Chore tracking' => 'Chore tracking', | ||||||
|  | 	'Battery tracking' => 'Battery tracking', | ||||||
|  | 	'Products' => 'Products', | ||||||
|  | 	'Locations' => 'Locations', | ||||||
|  | 	'Quantity units' => 'Quantity units', | ||||||
|  | 	'Chores' => 'Chores', | ||||||
|  | 	'Batteries' => 'Batteries', | ||||||
|  | 	'Chore' => 'Chore', | ||||||
|  | 	'Next estimated tracking' => 'Next estimated tracking', | ||||||
|  | 	'Last tracked' => 'Last tracked', | ||||||
|  | 	'Battery' => 'Battery', | ||||||
|  | 	'Last charged' => 'Last charged', | ||||||
|  | 	'Next planned charge cycle' => 'Next planned charge cycle', | ||||||
|  | 	'Best before' => 'Best before', | ||||||
|  | 	'OK' => 'OK', | ||||||
|  | 	'Product overview' => 'Product overview', | ||||||
|  | 	'Stock quantity unit' => 'Stock quantity unit', | ||||||
|  | 	'Stock amount' => 'Stock amount', | ||||||
|  | 	'Last purchased' => 'Last purchased', | ||||||
|  | 	'Last used' => 'Last used', | ||||||
|  | 	'Spoiled' => 'Spoiled', | ||||||
|  | 	'Barcode lookup is disabled' => 'Barcode lookup is disabled', | ||||||
|  | 	'will be added to the list of barcodes for the selected product on submit' => 'will be added to the list of barcodes for the selected product on submit', | ||||||
|  | 	'New amount' => 'New amount', | ||||||
|  | 	'Note' => 'Note', | ||||||
|  | 	'Tracked time' => 'Tracked time', | ||||||
|  | 	'Chore overview' => 'Chore overview', | ||||||
|  | 	'Tracked count' => 'Tracked count', | ||||||
|  | 	'Battery overview' => 'Battery overview', | ||||||
|  | 	'Charge cycles count' => 'Charge cycles count', | ||||||
|  | 	'Create shopping list item' => 'Create shopping list item', | ||||||
|  | 	'Edit shopping list item' => 'Edit shopping list item', | ||||||
|  | 	'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 units were automatically added and will apply in addition to the amount entered here', | ||||||
|  | 	'Save' => 'Save', | ||||||
|  | 	'Add' => 'Add', | ||||||
|  | 	'Name' => 'Name', | ||||||
|  | 	'Location' => 'Location', | ||||||
|  | 	'Min. stock amount' => 'Min. stock amount', | ||||||
|  | 	'QU purchase' => 'QU purchase', | ||||||
|  | 	'QU stock' => 'QU stock', | ||||||
|  | 	'QU factor' => 'QU factor', | ||||||
|  | 	'Description' => 'Description', | ||||||
|  | 	'Create product' => 'Create product', | ||||||
|  | 	'Barcode(s)' => 'Barcode(s)', | ||||||
|  | 	'Minimum stock amount' => 'Minimum stock amount', | ||||||
|  | 	'Default best before days' => 'Default best before days', | ||||||
|  | 	'Quantity unit purchase' => 'Quantity unit purchase', | ||||||
|  | 	'Quantity unit stock' => 'Quantity unit stock', | ||||||
|  | 	'Factor purchase to stock quantity unit' => 'Factor purchase to stock quantity unit', | ||||||
|  | 	'Create location' => 'Create location', | ||||||
|  | 	'Create quantity unit' => 'Create quantity unit', | ||||||
|  | 	'Period type' => 'Period type', | ||||||
|  | 	'Period days' => 'Period days', | ||||||
|  | 	'Create chore' => 'Create chore', | ||||||
|  | 	'Used in' => 'Used in', | ||||||
|  | 	'Create battery' => 'Create battery', | ||||||
|  | 	'Edit battery' => 'Edit battery', | ||||||
|  | 	'Edit chore' => 'Edit chore', | ||||||
|  | 	'Edit quantity unit' => 'Edit quantity unit', | ||||||
|  | 	'Edit product' => 'Edit product', | ||||||
|  | 	'Edit location' => 'Edit location', | ||||||
|  | 	'Record data' => 'Record data', | ||||||
|  | 	'Manage master data' => 'Manage master data', | ||||||
|  | 	'This will apply to added products' => 'This will apply to added products', | ||||||
|  | 	'never' => 'never', | ||||||
|  | 	'Add products that are below defined min. stock amount' => 'Add products that are below defined min. stock amount', | ||||||
|  | 	'For purchases this amount of days will be added to today for the best before date suggestion' => 'For purchases this amount of days will be added to today for the best before date suggestion', | ||||||
|  | 	'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'This means 1 #1 purchased will be converted into #2 #3 in stock', | ||||||
|  | 	'Login' => 'Login', | ||||||
|  | 	'Username' => 'Username', | ||||||
|  | 	'Password' => 'Password', | ||||||
|  | 	'Invalid credentials, please try again' => 'Invalid credentials, please try again', | ||||||
|  | 	'Are you sure to delete battery "#1"?' => 'Are you sure to delete battery "#1"?', | ||||||
|  | 	'Yes' => 'Yes', | ||||||
|  | 	'No' => 'No', | ||||||
|  | 	'Are you sure to delete chore "#1"?' => 'Are you sure to delete chore "#1"?', | ||||||
|  | 	'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" could not be resolved to a product, how do you want to proceed?', | ||||||
|  | 	'Create or assign product' => 'Create or assign product', | ||||||
|  | 	'Cancel' => 'Cancel', | ||||||
|  | 	'Add as new product' => 'Add as new product', | ||||||
|  | 	'Add as barcode to existing product' => 'Add as barcode to existing product', | ||||||
|  | 	'Add as new product and prefill barcode' => 'Add as new product and prefill barcode', | ||||||
|  | 	'Are you sure to delete quantity unit "#1"?' => 'Are you sure to delete quantity unit "#1"?', | ||||||
|  | 	'Are you sure to delete product "#1"?' => 'Are you sure to delete product "#1"?', | ||||||
|  | 	'Are you sure to delete location "#1"?' => 'Are you sure to delete location "#1"?', | ||||||
|  | 	'Manage API keys' => 'Manage API keys', | ||||||
|  | 	'REST API & data model documentation' => 'REST API & data model documentation', | ||||||
|  | 	'API keys' => 'API keys', | ||||||
|  | 	'Create new API key' => 'Create new API key', | ||||||
|  | 	'API key' => 'API key', | ||||||
|  | 	'Expires' => 'Expires', | ||||||
|  | 	'Created' => 'Created', | ||||||
|  | 	'This product is not in stock' => 'This product is not in stock', | ||||||
|  | 	'This means #1 will be added to stock' => 'This means #1 will be added to stock', | ||||||
|  | 	'This means #1 will be removed from stock' => 'This means #1 will be removed from stock', | ||||||
|  | 	'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked', | ||||||
|  | 	'Removed #1 #2 of #3 from stock' => 'Removed #1 #2 of #3 from stock', | ||||||
|  | 	'About grocy' => 'About grocy', | ||||||
|  | 	'Close' => 'Close', | ||||||
|  | 	'#1 batteries are due to be charged within the next #2 days' => '#1 batteries are due to be charged within the next #2 days', | ||||||
|  | 	'#1 batteries are overdue to be charged' => '#1 batteries are overdue to be charged', | ||||||
|  | 	'#1 chores are due to be done within the next #2 days' => '#1 chores are due to be done within the next #2 days', | ||||||
|  | 	'#1 chores are overdue to be done' => '#1 chores are overdue to be done', | ||||||
|  | 	'Released on' => 'Released on', | ||||||
|  | 	'Consume #3 #1 of #2' => 'Consume #3 #1 of #2', | ||||||
|  | 	'Added #1 #2 of #3 to stock' => 'Added #1 #2 of #3 to stock', | ||||||
|  | 	'Stock amount of #1 is now #2 #3' => 'Stock amount of #1 is now #2 #3', | ||||||
|  | 	'Tracked execution of chore #1 on #2' => 'Tracked execution of chore #1 on #2', | ||||||
|  | 	'Tracked charge cycle of battery #1 on #2' => 'Tracked charge cycle of battery #1 on #2', | ||||||
|  | 	'Consume all #1 which are currently in stock' => 'Consume all #1 which are currently in stock', | ||||||
|  | 	'All' => 'All', | ||||||
|  | 	'Track charge cycle of battery #1' => 'Track charge cycle of battery #1', | ||||||
|  | 	'Track execution of chore #1' => 'Track execution of chore #1', | ||||||
|  | 	'Filter by location' => 'Filter by location', | ||||||
|  | 	'Search' => 'Search', | ||||||
|  | 	'Not logged in' => 'Not logged in', | ||||||
|  | 	'You have to select a product' => 'You have to select a product', | ||||||
|  | 	'You have to select a chore' => 'You have to select a chore', | ||||||
|  | 	'You have to select a battery' => 'You have to select a battery', | ||||||
|  | 	'A name is required' => 'A name is required', | ||||||
|  | 	'A location is required' => 'A location is required', | ||||||
|  | 	'The amount cannot be lower than #1' => 'The amount cannot be lower than #1', | ||||||
|  | 	'This cannot be negative' => 'This cannot be negative', | ||||||
|  | 	'A quantity unit is required' => 'A quantity unit is required', | ||||||
|  | 	'A period type is required' => 'A period type is required', | ||||||
|  | 	'A best before date is required and must be later than today' => 'A best before date is required and must be later than today', | ||||||
|  | 	'Settings' => 'Settings', | ||||||
|  | 	'This can only be before now' => 'This can only be before now', | ||||||
|  | 	'Calendar' => 'Calendar', | ||||||
|  | 	'Recipes' => 'Recipes', | ||||||
|  | 	'Edit recipe' => 'Edit recipe', | ||||||
|  | 	'New recipe' => 'New recipe', | ||||||
|  | 	'Ingredients list' => 'Ingredients list', | ||||||
|  | 	'Add recipe ingredient' => 'Add recipe ingredient', | ||||||
|  | 	'Edit recipe ingredient' => 'Edit recipe ingredient', | ||||||
|  | 	'Are you sure to delete recipe "#1"?' => 'Are you sure to delete recipe "#1"?', | ||||||
|  | 	'Are you sure to delete recipe ingredient "#1"?' => 'Are you sure to delete recipe ingredient "#1"?', | ||||||
|  | 	'Are you sure to empty the shopping list?' => 'Are you sure to empty the shopping list?', | ||||||
|  | 	'Clear list' => 'Clear list', | ||||||
|  | 	'Requirements fulfilled' => 'Requirements fulfilled', | ||||||
|  | 	'Put missing products on shopping list' => 'Put missing products on shopping list', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing' => 'Not enough in stock, #1 ingredients missing', | ||||||
|  | 	'Enough in stock' => 'Enough in stock', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Not enough in stock, #1 ingredients missing but already on the shopping list', | ||||||
|  | 	'Expand to fullscreen' => 'Expand to fullscreen', | ||||||
|  | 	'Ingredients' => 'Ingredients', | ||||||
|  | 	'Preparation' => 'Preparation', | ||||||
|  | 	'Recipe' => 'Recipe', | ||||||
|  | 	'Not enough in stock, #1 missing, #2 already on shopping list' => 'Not enough in stock, #1 missing, #2 already on shopping list', | ||||||
|  | 	'Show notes' => 'Show notes', | ||||||
|  | 	'Put missing amount on shopping list' => 'Put missing amount on shopping list', | ||||||
|  | 	'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?', | ||||||
|  | 	'Added for recipe #1' => 'Added for recipe #1', | ||||||
|  | 	'Manage users' => 'Manage users', | ||||||
|  | 	'User' => 'User', | ||||||
|  | 	'Users' => 'Users', | ||||||
|  | 	'Are you sure to delete user "#1"?' => 'Are you sure to delete user "#1"?', | ||||||
|  | 	'Create user' => 'Create user', | ||||||
|  | 	'Edit user' => 'Edit user', | ||||||
|  | 	'First name' => 'First name', | ||||||
|  | 	'Last name' => 'Last name', | ||||||
|  | 	'A username is required' => 'A username is required', | ||||||
|  | 	'Confirm password' => 'Confirm password', | ||||||
|  | 	'Passwords do not match' => 'Passwords do not match', | ||||||
|  | 	'Change password' => 'Change password', | ||||||
|  | 	'Done by' => 'Done by', | ||||||
|  | 	'Last done by' => 'Last done by', | ||||||
|  | 	'Unknown' => 'Unknown', | ||||||
|  | 	'Filter by chore' => 'Filter by chore', | ||||||
|  | 	'Chores journal' => 'Chores journal', | ||||||
|  | 	'0 means suggestions for the next charge cycle are disabled' => '0 means suggestions for the next charge cycle are disabled', | ||||||
|  | 	'Charge cycle interval (days)' => 'Charge cycle interval (days)', | ||||||
|  | 	'Last price' => 'Last price', | ||||||
|  | 	'Price history' => 'Price history', | ||||||
|  | 	'No price history available' => 'No price history available', | ||||||
|  | 	'Price' => 'Price', | ||||||
|  | 	'in #1 per purchase quantity unit' => 'in #1 per purchase quantity unit', | ||||||
|  | 	'The price cannot be lower than #1' => 'The price cannot be lower than #1', | ||||||
|  | 	'#1 product expires within the next #2 days' => '#1 product expires within the next #2 days', | ||||||
|  | 	'#1 product is already expired' => '#1 product is already expired', | ||||||
|  | 	'#1 product is below defined min. stock amount' => '#1 product is below defined min. stock amount', | ||||||
|  | 	'Unit' => 'Unit', | ||||||
|  | 	'Units' => 'Units', | ||||||
|  | 	'#1 chore is due to be done within the next #2 days' => '#1 chore is due to be done within the next #2 days', | ||||||
|  | 	'#1 chore is overdue to be done' => '#1 chore is overdue to be done', | ||||||
|  | 	'#1 battery is due to be charged within the next #2 days' => '#1 battery is due to be charged within the next #2 days', | ||||||
|  | 	'#1 battery is overdue to be charged' => '#1 battery is overdue to be charged', | ||||||
|  | 	'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 unit was automatically added and will apply in addition to the amount entered here', | ||||||
|  | 	'in singular form' => 'in singular form', | ||||||
|  | 	'in plural form' => 'in plural form', | ||||||
|  | 	'Never expires' => 'Never expires', | ||||||
|  | 	'This cannot be lower than #1' => 'This cannot be lower than #1', | ||||||
|  | 	'-1 means that this product never expires' => '-1 means that this product never expires', | ||||||
|  | 	'Quantity unit' => 'Quantity unit', | ||||||
|  | 	'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Only check if a single unit is in stock (a different quantity can then be used above)', | ||||||
|  | 	'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?', | ||||||
|  | 	'Removed all ingredients of recipe "#1" from stock' => 'Removed all ingredients of recipe "#1" from stock', | ||||||
|  | 	'Consume all ingredients needed by this recipe' => 'Consume all ingredients needed by this recipe', | ||||||
|  | 	'Click to show technical details' => 'Click to show technical details', | ||||||
|  | 	'Error while saving, probably this item already exists' => 'Error while saving, probably this item already exists', | ||||||
|  | 	'Error details' => 'Error details', | ||||||
|  | 	'Tasks' => 'Tasks', | ||||||
|  | 	'Show done tasks' => 'Show done tasks', | ||||||
|  | 	'Task' => 'Task', | ||||||
|  | 	'Due' => 'Due', | ||||||
|  | 	'Assigned to' => 'Assigned to', | ||||||
|  | 	'Mark task "#1" as completed' => 'Mark task "#1" as completed', | ||||||
|  | 	'Uncategorized' => 'Uncategorized', | ||||||
|  | 	'Task categories' => 'Task categories', | ||||||
|  | 	'Create task' => 'Create task', | ||||||
|  | 	'A due date is required' => 'A due date is required', | ||||||
|  | 	'Category' => 'Category', | ||||||
|  | 	'Edit task' => 'Edit task', | ||||||
|  | 	'Are you sure to delete task "#1"?' => 'Are you sure to delete task "#1"?', | ||||||
|  | 	'#1 task is due to be done within the next #2 days' => '#1 task is due to be done within the next #2 days', | ||||||
|  | 	'#1 tasks are due to be done within the next #2 days' => '#1 tasks are due to be done within the next #2 days', | ||||||
|  | 	'#1 task is overdue to be done' => '#1 task is overdue to be done', | ||||||
|  | 	'#1 tasks are overdue to be done' => '#1 tasks are overdue to be done', | ||||||
|  | 	'Edit task category' => 'Edit task category', | ||||||
|  | 	'Create task category' => 'Create task category', | ||||||
|  | 	'Product groups' => 'Product groups', | ||||||
|  | 	'Ungrouped' => 'Ungrouped', | ||||||
|  | 	'Create product group' => 'Create product group', | ||||||
|  | 	'Edit product group' => 'Edit product group', | ||||||
|  | 	'Product group' => 'Product group', | ||||||
|  | 	'Are you sure to delete product group "#1"?' => 'Are you sure to delete product group "#1"?', | ||||||
|  | 	'Stay logged in permanently' => 'Stay logged in permanently', | ||||||
|  | 	'When not set, you will get logged out at latest after 30 days' => 'When not set, you will get logged out at latest after 30 days', | ||||||
|  | 	'Filter by status' => 'Filter by status', | ||||||
|  | 	'Below min. stock amount' => 'Below min. stock amount', | ||||||
|  | 	'Expiring soon' => 'Expiring soon', | ||||||
|  | 	'Already expired' => 'Already expired', | ||||||
|  | 	'Due soon' => 'Due soon', | ||||||
|  | 	'Overdue' => 'Overdue', | ||||||
|  | 	'View settings' => 'View settings', | ||||||
|  | 	'Auto reload on external changes' => 'Auto reload on external changes', | ||||||
|  | 	'Enable night mode' => 'Enable night mode', | ||||||
|  | 	'Auto enable in time range' => 'Auto enable in time range', | ||||||
|  | 	'From' => 'From', | ||||||
|  | 	'in format' => 'in format', | ||||||
|  | 	'To' => 'To', | ||||||
|  | 	'Time range goes over midnight' => 'Time range goes over midnight', | ||||||
|  | 	'Product picture' => 'Product picture', | ||||||
|  | 	'No file selected' => 'No file selected', | ||||||
|  | 	'If you don\'t select a file, the current picture will not be altered' => 'If you don\'t select a file, the current picture will not be altered', | ||||||
|  | 	'Current picture' => 'Current picture', | ||||||
|  | 	'Delete' => 'Delete', | ||||||
|  | 	'The current picture will be deleted when you save the product' => 'The current picture will be deleted when you save the product', | ||||||
|  | 	'Select file' => 'Select file', | ||||||
|  | 	'Image of product #1' => 'Image of product #1', | ||||||
|  | 	'This product cannot be deleted because it is in stock, please remove the stock amount first.' => 'This product cannot be deleted because it is in stock, please remove the stock amount first.', | ||||||
|  | 	'Delete not possible' => 'Delete not possible', | ||||||
|  | 	'Equipment' => 'Equipment', | ||||||
|  | 	'Instruction manual' => 'Instruction manual', | ||||||
|  | 	'The selected equipment has no instruction manual' => 'The selected equipment has no instruction manual', | ||||||
|  | 	'Notes' => 'Notes', | ||||||
|  | 	'Edit equipment' => 'Edit equipment', | ||||||
|  | 	'Create equipment' => 'Create equipment', | ||||||
|  | 	'If you don\'t select a file, the current instruction manual will not be altered' => 'If you don\'t select a file, the current instruction manual will not be altered', | ||||||
|  | 	'Current instruction manual' => 'Current instruction manual', | ||||||
|  | 	'No instruction manual available' => 'No instruction manual available', | ||||||
|  | 	'The current instruction manual will be deleted when you save the equipment' => 'The current instruction manual will be deleted when you save the equipment', | ||||||
|  | 	'No picture available' => 'No picture available', | ||||||
|  | 	'Filter by product group' => 'Filter by product group', | ||||||
|  | 	'Presets for new products' => 'Presets for new products', | ||||||
|  | 	'Included recipes' => 'Included recipes', | ||||||
|  | 	'A recipe is required' => 'A recipe is required', | ||||||
|  | 	'Add included recipe' => 'Add included recipe', | ||||||
|  | 	'Edit included recipe' => 'Edit included recipe', | ||||||
|  | 	'Group' => 'Group', | ||||||
|  | 	'This will be used as a headline to group ingredients together' => 'This will be used as a headline to group ingredients together', | ||||||
|  | 	'Journal' => 'Journal', | ||||||
|  | 	'Stock journal' => 'Stock journal', | ||||||
|  | 	'Filter by product' => 'Filter by product', | ||||||
|  | 	'Booking time' => 'Booking time', | ||||||
|  | 	'Booking type' => 'Booking type', | ||||||
|  | 	'Undo booking' => 'Undo booking', | ||||||
|  | 	'Undone on' => 'Undone on', | ||||||
|  | 	'Batteries journal' => 'Batteries journal', | ||||||
|  | 	'Filter by battery' => 'Filter by battery', | ||||||
|  | 	'Undo charge cycle' => 'Undo charge cycle', | ||||||
|  | 	'Undo chore execution' => 'Undo chore execution', | ||||||
|  | 	'Chore execution successfully undone' => 'Chore execution successfully undone', | ||||||
|  | 	'Undo' => 'Undo', | ||||||
|  | 	'Booking successfully undone' => 'Booking successfully undone', | ||||||
|  | 	'Charge cycle successfully undone' => 'Charge cycle successfully undone', | ||||||
|  | 	'This cannot be negative and must be an integral number' => 'This cannot be negative and must be an integral number', | ||||||
|  | 	'Disable stock fulfillment checking for this ingredient' => 'Disable stock fulfillment checking for this ingredient', | ||||||
|  | 	'Add all list items to stock' => 'Add all list items to stock', | ||||||
|  | 	'Add #3 #1 of #2 to stock' => 'Add #3 #1 of #2 to stock', | ||||||
|  | 	'Adding shopping list item #1 of #2' => 'Adding shopping list item #1 of #2', | ||||||
|  | 	'Use a specific stock item' => 'Use a specific stock item', | ||||||
|  | 	'The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"' => 'The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Mark #3 #1 of #2 as open', | ||||||
|  | 	'When a product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)' => 'When a product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)', | ||||||
|  | 	'Default best before days after opened' => 'Default best before days after opened', | ||||||
|  | 	'Marked #1 #2 of #3 as opened' => 'Marked #1 #2 of #3 as opened', | ||||||
|  | 	'Mark as opened' => 'Mark as opened', | ||||||
|  | 	'Expires on #1; Bought on #2' => 'Expires on #1; Bought on #2', | ||||||
|  | 	'Not opened' => 'Not opened', | ||||||
|  | 	'Opened' => 'Opened', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Mark #3 #1 of #2 as open', | ||||||
|  | 	'#1 opened' => '#1 opened', | ||||||
|  | 	'Product expires' => 'Product expires', | ||||||
|  | 	'Task due' => 'Task due', | ||||||
|  | 	'Chore due' => 'Chore due', | ||||||
|  | 	'Battery charge cycle due' => 'Battery charge cycle due', | ||||||
|  | 	'Show clock in header' => 'Show clock in header', | ||||||
|  | 	'Stock settings' => 'Stock settings', | ||||||
|  | 	'Shopping list to stock workflow' => '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' => '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' | ||||||
|  | ); | ||||||
							
								
								
									
										6
									
								
								localization/fr/chore_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								localization/fr/chore_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'manually' => 'Manuelle', | ||||||
|  | 	'dynamic-regular' => 'Régulière-dynamique' | ||||||
|  | ); | ||||||
							
								
								
									
										10
									
								
								localization/fr/component_translations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								localization/fr/component_translations.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'timeago_locale' => 'fr', | ||||||
|  | 	'timeago_nan' => 'Il y a NaN années', | ||||||
|  | 	'moment_locale' => 'fr', | ||||||
|  | 	'datatables_localization' => '{"sProcessing":"Traitement en cours...","sSearch":"Rechercher :","sLengthMenu":"Afficher _MENU_ éléments","sInfo":"Affichage de l\'élément _START_ à _END_ sur _TOTAL_ éléments","sInfoEmpty":"Affichage de l\'élément 0 à 0 sur 0 élément","sInfoFiltered":"(filtré de _MAX_ éléments au total)","sInfoPostFix":"","sLoadingRecords":"Chargement en cours...","sZeroRecords":"Aucun élément à afficher","sEmptyTable":"Aucune donnée disponible dans le tableau","oPaginate":{"sFirst":"Premier","sPrevious":"Précédent","sNext":"Suivant","sLast":"Dernier"},"oAria":{"sSortAscending":": activer pour trier la colonne par ordre croissant","sSortDescending":": activer pour trier la colonne par ordre décroissant"}}', | ||||||
|  | 	'summernote_locale' => 'fr-FR', | ||||||
|  | 	'fullcalendar_locale' => 'fr' | ||||||
|  | ); | ||||||
							
								
								
									
										87
									
								
								localization/fr/demo_data.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								localization/fr/demo_data.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Cookies' => 'Cookies', | ||||||
|  | 	'Chocolate' => 'Chocolat', | ||||||
|  | 	'Pantry' => 'Garde-manger', | ||||||
|  | 	'Candy cupboard' => 'Boîte de bonbons', | ||||||
|  | 	'Tinned food cupboard' => 'Conserve de nourriture', | ||||||
|  | 	'Fridge' => 'Réfrigérateur', | ||||||
|  | 	'Piece' => 'Pièce', | ||||||
|  | 	'Pieces' => 'Pièces', | ||||||
|  | 	'Pack' => 'Pack', | ||||||
|  | 	'Packs' => 'Packs', | ||||||
|  | 	'Glass' => 'Verre', | ||||||
|  | 	'Glasses' => 'Verres', | ||||||
|  | 	'Tin' => 'Pot', | ||||||
|  | 	'Tins' => 'Pots', | ||||||
|  | 	'Can' => 'Canette', | ||||||
|  | 	'Cans' => 'Canettes', | ||||||
|  | 	'Bunch' => 'Brunch', | ||||||
|  | 	'Bunches' => 'Brunchs', | ||||||
|  | 	'Gummy bears' => 'Oursons en gélatine', | ||||||
|  | 	'Crisps' => 'Chips', | ||||||
|  | 	'Eggs' => 'Oeufs', | ||||||
|  | 	'Noodles' => 'Nouilles', | ||||||
|  | 	'Pickles' => 'Cornichons', | ||||||
|  | 	'Gulash soup' => 'Soupe de goulache', | ||||||
|  | 	'Yogurt' => 'Yaourt', | ||||||
|  | 	'Cheese' => 'Fromage', | ||||||
|  | 	'Cold cuts' => 'Charcuterie', | ||||||
|  | 	'Paprika' => 'Paprika', | ||||||
|  | 	'Cucumber' => 'Concombre', | ||||||
|  | 	'Radish' => 'Radis', | ||||||
|  | 	'Tomato' => 'Tomate', | ||||||
|  | 	'Changed towels in the bathroom' => 'Changement des serviettes dans la salle de bain', | ||||||
|  | 	'Cleaned the kitchen floor' => 'Nettoyage du sol de la cuisine', | ||||||
|  | 	'Warranty ends' => 'Fin de garantie', | ||||||
|  | 	'TV remote control' => 'Télécommande de la TV', | ||||||
|  | 	'Alarm clock' => 'Réveil', | ||||||
|  | 	'Heat remote control' => 'Télécommande du chauffage', | ||||||
|  | 	'Lawn mowed in the garden' => 'Jardin tondu', | ||||||
|  | 	'Some good snacks' => 'Quelques bons snacks', | ||||||
|  | 	'Pizza dough' => 'Pâte à pizza', | ||||||
|  | 	'Sieved tomatoes' => 'Sauce tomate', | ||||||
|  | 	'Salami' => 'Salami', | ||||||
|  | 	'Toast' => 'Pain grillé', | ||||||
|  | 	'Minced meat' => 'Viande hachée', | ||||||
|  | 	'Pizza' => 'PIzza', | ||||||
|  | 	'Spaghetti bolognese' => 'Spaghetti bolognaise', | ||||||
|  | 	'Sandwiches' => 'Sandwiches', | ||||||
|  | 	'English' => 'Anglais', | ||||||
|  | 	'German' => 'Allemand', | ||||||
|  | 	'Italian' => 'Italien', | ||||||
|  | 	'Demo in different language' => 'Démo dans une langue différente', | ||||||
|  | 	'This is the note content of the recipe ingredient' => 'Ceci est le contenu de la note concernant l\'ingrédient de la recette', | ||||||
|  | 	'Demo User' => 'Utilisateur de démonstration', | ||||||
|  | 	'Gram' => 'Gramme', | ||||||
|  | 	'Grams' => 'Grammes', | ||||||
|  | 	'Flour' => 'Farine', | ||||||
|  | 	'Pancakes' => 'Crêpes', | ||||||
|  | 	'Sugar' => 'Sucre', | ||||||
|  | 	'Home' => 'Domicile', | ||||||
|  | 	'Life' => 'Vie', | ||||||
|  | 	'Projects' => 'Projets', | ||||||
|  | 	'Repair the garage door' => 'Réparer la porte du garage', | ||||||
|  | 	'Fork and improve grocy' => 'Forker et améliorer grocy', | ||||||
|  | 	'Find a solution for what to do when I forget the door keys' => 'Trouver une solution pour savoir quoi faire quand j\'oublie les clefs de la porte', | ||||||
|  | 	'Sweets' => 'Bonbons', | ||||||
|  | 	'Bakery products' => 'Produits de la boulangerie', | ||||||
|  | 	'Tinned food' => 'Conserve', | ||||||
|  | 	'Butchery products' => 'Produits de la boucherie', | ||||||
|  | 	'Vegetables/Fruits' => 'Légumes/Fruits', | ||||||
|  | 	'Refrigerated products' => 'Produits réfrigérés', | ||||||
|  | 	'Coffee machine' => 'Machie à café', | ||||||
|  | 	'Dishwasher' => 'Lave-vaisselle', | ||||||
|  | 	'Liter' => 'Litière', | ||||||
|  | 	'Liters' => 'Litières', | ||||||
|  | 	'Bottle' => 'Bouteille', | ||||||
|  | 	'Bottles' => 'Bouteilles', | ||||||
|  | 	'Milk' => 'Lait', | ||||||
|  | 	'Chocolate sauce' => 'Coulis de chocolat', | ||||||
|  | 	'Milliliters' => 'Millilitres', | ||||||
|  | 	'Milliliter' => 'Millilitre', | ||||||
|  | 	'Bottom' => 'Dessous', | ||||||
|  | 	'Topping' => 'Garniture', | ||||||
|  | 	'French' => 'Français' | ||||||
|  | ); | ||||||
							
								
								
									
										8
									
								
								localization/fr/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								localization/fr/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'purchase' => 'Achat', | ||||||
|  | 	'consume' => 'Consommation', | ||||||
|  | 	'inventory-correction' => 'Correction d\'inventaire', | ||||||
|  | 	'product-opened' => 'Produit ouvert' | ||||||
|  | ); | ||||||
							
								
								
									
										329
									
								
								localization/fr/strings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								localization/fr/strings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Stock overview' => 'Aperçu du stock', | ||||||
|  | 	'#1 products expiring within the next #2 days' => '#1 produits se périment dans les #2 jours', | ||||||
|  | 	'#1 products are already expired' => '#1 produits sont périmés', | ||||||
|  | 	'#1 products are below defined min. stock amount' => '#1 produits sont sous le seuil de stock minimum', | ||||||
|  | 	'Product' => 'Produit', | ||||||
|  | 	'Amount' => 'Quantité', | ||||||
|  | 	'Next best before date' => 'Prochaine date de péremption', | ||||||
|  | 	'Logout' => 'Se déconnecter', | ||||||
|  | 	'Chores overview' => 'Aperçu des corvées', | ||||||
|  | 	'Batteries overview' => 'Batteries', | ||||||
|  | 	'Purchase' => 'Achat', | ||||||
|  | 	'Consume' => 'Consommation', | ||||||
|  | 	'Inventory' => 'Inventaire', | ||||||
|  | 	'Shopping list' => 'Liste de courses', | ||||||
|  | 	'Chore tracking' => 'Suivi des corvées', | ||||||
|  | 	'Battery tracking' => 'Suivi des batteries', | ||||||
|  | 	'Products' => 'Produits', | ||||||
|  | 	'Locations' => 'Emplacements', | ||||||
|  | 	'Quantity units' => 'Formats', | ||||||
|  | 	'Chores' => 'Corvées', | ||||||
|  | 	'Batteries' => 'Batteries', | ||||||
|  | 	'Chore' => 'Corvée', | ||||||
|  | 	'Next estimated tracking' => 'Prochaine occurrence estimée', | ||||||
|  | 	'Last tracked' => 'Dernière réalisation', | ||||||
|  | 	'Battery' => 'Batterie', | ||||||
|  | 	'Last charged' => 'Dernier chargement', | ||||||
|  | 	'Next planned charge cycle' => 'Prochaine charge planifiée', | ||||||
|  | 	'Best before' => 'Date d\'expiration', | ||||||
|  | 	'OK' => 'Ok', | ||||||
|  | 	'Product overview' => 'Aperçu du produit', | ||||||
|  | 	'Stock quantity unit' => 'Format de stockage', | ||||||
|  | 	'Stock amount' => 'Quantité en stock', | ||||||
|  | 	'Last purchased' => 'Dernier achat', | ||||||
|  | 	'Last used' => 'Dernière utilisation', | ||||||
|  | 	'Spoiled' => 'Périmé', | ||||||
|  | 	'Barcode lookup is disabled' => 'La recherche par code barres est désactivée', | ||||||
|  | 	'will be added to the list of barcodes for the selected product on submit' => 'sera ajouté à la liste des codes barres du produit sélectionné à l\'envoi', | ||||||
|  | 	'New amount' => 'Nouvelle quantité', | ||||||
|  | 	'Note' => 'Note', | ||||||
|  | 	'Tracked time' => 'Réalisé le', | ||||||
|  | 	'Chore overview' => 'Aperçu de la corvée', | ||||||
|  | 	'Tracked count' => 'Nombre de réalisations', | ||||||
|  | 	'Battery overview' => 'Aperçu des batteries', | ||||||
|  | 	'Charge cycles count' => 'Nombre de charges', | ||||||
|  | 	'Create shopping list item' => 'Créer une liste de courses', | ||||||
|  | 	'Edit shopping list item' => 'Modifier une liste de courses', | ||||||
|  | 	'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 unités seront automatiquement ajoutées en plus de la quantité renseignée ici', | ||||||
|  | 	'Save' => 'Sauvegarder', | ||||||
|  | 	'Add' => 'Ajouter', | ||||||
|  | 	'Name' => 'Nom', | ||||||
|  | 	'Location' => 'Emplacement', | ||||||
|  | 	'Min. stock amount' => 'Quantité minimum en stock', | ||||||
|  | 	'QU purchase' => 'Format achat', | ||||||
|  | 	'QU stock' => 'Format stock', | ||||||
|  | 	'QU factor' => 'Facteur format', | ||||||
|  | 	'Description' => 'Description', | ||||||
|  | 	'Create product' => 'Créer un produit', | ||||||
|  | 	'Barcode(s)' => 'Code(s) barres', | ||||||
|  | 	'Minimum stock amount' => 'Quantité minimum en stock', | ||||||
|  | 	'Default best before days' => 'Jours avant péremption par défaut', | ||||||
|  | 	'Quantity unit purchase' => 'Format à l\'achat', | ||||||
|  | 	'Quantity unit stock' => 'Format au stockage', | ||||||
|  | 	'Factor purchase to stock quantity unit' => 'Facteur entre la quantité à l\'achat et la quantité en stock', | ||||||
|  | 	'Create location' => 'Créer un emplacement', | ||||||
|  | 	'Create quantity unit' => 'Créer un format', | ||||||
|  | 	'Period type' => 'Type de période', | ||||||
|  | 	'Period days' => 'Jours dans la période', | ||||||
|  | 	'Create chore' => 'Créer une corvée', | ||||||
|  | 	'Used in' => 'Utilisé dans', | ||||||
|  | 	'Create battery' => 'Créer une batterie', | ||||||
|  | 	'Edit battery' => 'Modifier une batterie', | ||||||
|  | 	'Edit chore' => 'Modifier une corvée', | ||||||
|  | 	'Edit quantity unit' => 'Modifier le format', | ||||||
|  | 	'Edit product' => 'Modifier le produit', | ||||||
|  | 	'Edit location' => 'Modifier l\'emplacement', | ||||||
|  | 	'Record data' => 'Enregistrer les données', | ||||||
|  | 	'Manage master data' => 'Gérer les données', | ||||||
|  | 	'This will apply to added products' => 'Sera appliqué aux produits ajoutés', | ||||||
|  | 	'never' => 'jamais', | ||||||
|  | 	'Add products that are below defined min. stock amount' => 'Ajouter les produits qui sont en dessous du seuil de stock minimum', | ||||||
|  | 	'For purchases this amount of days will be added to today for the best before date suggestion' => 'A l\'achat, ce nombre de jours sera ajouté à la date de péremption suggérée', | ||||||
|  | 	'This means 1 #1 purchased will be converted into #2 #3 in stock' => '1 #1 acheté sera converti en #2 #3 dans le stock', | ||||||
|  | 	'Login' => 'Se connecter', | ||||||
|  | 	'Username' => 'Identifiant', | ||||||
|  | 	'Password' => 'Mot de passe', | ||||||
|  | 	'Invalid credentials, please try again' => 'Identifiants invalides, merci de réessayer', | ||||||
|  | 	'Are you sure to delete battery "#1"?' => 'Êtes vous sûr de vouloir supprimer la batterie "#1" ?', | ||||||
|  | 	'Yes' => 'Oui', | ||||||
|  | 	'No' => 'Non', | ||||||
|  | 	'Are you sure to delete chore "#1"?' => 'Voulez-vous vraiment supprimer la corvée "#1" ?', | ||||||
|  | 	'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" n\'a pas été retrouvé en tant que produit, comment voulez-vous procéder ?', | ||||||
|  | 	'Create or assign product' => 'Créer ou assigner à un produit', | ||||||
|  | 	'Cancel' => 'Annuler', | ||||||
|  | 	'Add as new product' => 'Ajouter un nouveau produit', | ||||||
|  | 	'Add as barcode to existing product' => 'Ajouter en tant que code-barres à un produit existant', | ||||||
|  | 	'Add as new product and prefill barcode' => 'Ajouter un nouveau produit et pré-renseigner le code-barres', | ||||||
|  | 	'Are you sure to delete quantity unit "#1"?' => 'Voulez-vous vraiment supprimer le format "#1" ?', | ||||||
|  | 	'Are you sure to delete product "#1"?' => 'Voulez-vous vraiment supprimer le produit "#1" ?', | ||||||
|  | 	'Are you sure to delete location "#1"?' => 'Voulez-vous vraiment supprimer l\'emplacement "#1" ?', | ||||||
|  | 	'Manage API keys' => 'Gérer les clefs API', | ||||||
|  | 	'REST API & data model documentation' => 'Documentation sur l\'API REST & le modèle des données', | ||||||
|  | 	'API keys' => 'Clefs API', | ||||||
|  | 	'Create new API key' => 'Créer une nouvelle clef API', | ||||||
|  | 	'API key' => 'Clef API', | ||||||
|  | 	'Expires' => 'Valide jusqu\'à', | ||||||
|  | 	'Created' => 'Créée', | ||||||
|  | 	'This product is not in stock' => 'Ce produit n\'est pas en stock', | ||||||
|  | 	'This means #1 will be added to stock' => '#1 sera ajouté au stock', | ||||||
|  | 	'This means #1 will be removed from stock' => '#1 sera supprimé du stock', | ||||||
|  | 	'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'La prochaine exécution de cette corvée sera programmée #1 jours après sa dernière exécution', | ||||||
|  | 	'Removed #1 #2 of #3 from stock' => '#1 #2 de #3 supprimés du stock', | ||||||
|  | 	'About grocy' => 'À propos de grocy', | ||||||
|  | 	'Close' => 'Fermer', | ||||||
|  | 	'#1 batteries are due to be charged within the next #2 days' => '#1 batteries doivent être rechargées dans les #2 prochains jours', | ||||||
|  | 	'#1 batteries are overdue to be charged' => '#1 batteries n\'ont pas été rechargées à temps', | ||||||
|  | 	'#1 chores are due to be done within the next #2 days' => '#1 corvées doivent être réalisées dans les #2 prochains jours', | ||||||
|  | 	'#1 chores are overdue to be done' => '#1 corvées n\'ont pas été réalisées à temps', | ||||||
|  | 	'Released on' => 'Date de sortie', | ||||||
|  | 	'Consume #3 #1 of #2' => 'Consommer #3 #1 de #2', | ||||||
|  | 	'Added #1 #2 of #3 to stock' => '#1 #2 de #3 ajoutés au stock', | ||||||
|  | 	'Stock amount of #1 is now #2 #3' => 'La quantité en stock de #1 est maintenant de #2 #3', | ||||||
|  | 	'Tracked execution of chore #1 on #2' => 'La corvée "#1" a été réalisée le #2', | ||||||
|  | 	'Tracked charge cycle of battery #1 on #2' => 'La batterie "#1" a été rechargée le #2', | ||||||
|  | 	'Consume all #1 which are currently in stock' => 'Consommer tous les #1 actuellement en stock', | ||||||
|  | 	'All' => 'Tout', | ||||||
|  | 	'Track charge cycle of battery #1' => 'Indiquer le rechargement de la batterie #1', | ||||||
|  | 	'Track execution of chore #1' => 'Indiquer la réalisation de la corvée #1', | ||||||
|  | 	'Filter by location' => 'Filtrer par emplacement', | ||||||
|  | 	'Search' => 'Recherche', | ||||||
|  | 	'Not logged in' => 'Non connecté', | ||||||
|  | 	'You have to select a product' => 'Vous devez sélectionner un produit', | ||||||
|  | 	'You have to select a chore' => 'Vous devez sélectionner une corvée', | ||||||
|  | 	'You have to select a battery' => 'Vous devez sélectionner une batterie', | ||||||
|  | 	'A name is required' => 'Un nom est requis', | ||||||
|  | 	'A location is required' => 'Un emplacement est requis', | ||||||
|  | 	'The amount cannot be lower than #1' => 'La quantité ne peut être inférieure à #1', | ||||||
|  | 	'This cannot be negative' => 'Ne peut être négatif', | ||||||
|  | 	'A quantity unit is required' => 'Un format est requis', | ||||||
|  | 	'A period type is required' => 'Un type de période est requis', | ||||||
|  | 	'A best before date is required and must be later than today' => 'Une date de péremption est requise et doit être supérieure à la date du jour', | ||||||
|  | 	'Settings' => 'Paramètres', | ||||||
|  | 	'This can only be before now' => 'Ne peut être qu\'antérieur à maintenant', | ||||||
|  | 	'Calendar' => 'Calendrier', | ||||||
|  | 	'Recipes' => 'Recettes', | ||||||
|  | 	'Edit recipe' => 'Modifier une recette', | ||||||
|  | 	'New recipe' => 'Nouvelle recette', | ||||||
|  | 	'Ingredients list' => 'Liste des ingrédients', | ||||||
|  | 	'Add recipe ingredient' => 'Ajouter un ingrédient dans la recette', | ||||||
|  | 	'Edit recipe ingredient' => 'Modifier un ingrédient dans la recette', | ||||||
|  | 	'Are you sure to delete recipe "#1"?' => 'Voulez-vous vraiment supprimer la recette "#1" ?', | ||||||
|  | 	'Are you sure to delete recipe ingredient "#1"?' => 'Voulez-vous vraiment supprimer l\'ingrédient "#1" de la recette ?', | ||||||
|  | 	'Are you sure to empty the shopping list?' => 'Voulez-vous vraiment vider la liste de courses ?', | ||||||
|  | 	'Clear list' => 'Vider la liste', | ||||||
|  | 	'Requirements fulfilled' => 'Prérequis remplis', | ||||||
|  | 	'Put missing products on shopping list' => 'Ajouter les produits manquants dans la liste de courses', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing' => 'Pas assez en stock, #1 ingrédients manquants', | ||||||
|  | 	'Enough in stock' => 'Il y en a assez en stock', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Pas assez en stock, #1 ingrédients manquants mais déjà dans la liste de courses', | ||||||
|  | 	'Expand to fullscreen' => 'Mettre en plein écran', | ||||||
|  | 	'Ingredients' => 'Ingrédients', | ||||||
|  | 	'Preparation' => 'Préparation', | ||||||
|  | 	'Recipe' => 'Recette', | ||||||
|  | 	'Not enough in stock, #1 missing, #2 already on shopping list' => 'Pas assez en stock, #1 manquant et #2 déjà dans la liste de courses', | ||||||
|  | 	'Show notes' => 'Afficher les notes', | ||||||
|  | 	'Put missing amount on shopping list' => 'Ajouter la quantité manquante dans la liste de courses', | ||||||
|  | 	'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Voulez-vous vraiment ajouter tous les ingrédients manquants de la recette "#1" dans la liste de courses ?', | ||||||
|  | 	'Added for recipe #1' => 'Ajoutés pour la recette #1', | ||||||
|  | 	'Manage users' => 'Gérer les utilisateurs', | ||||||
|  | 	'User' => 'Utilisateur', | ||||||
|  | 	'Users' => 'Utilisateurs', | ||||||
|  | 	'Are you sure to delete user "#1"?' => 'Voulez-vous vraiment supprimer l\'utilisateur "#1" ?', | ||||||
|  | 	'Create user' => 'Créer un utilisateur', | ||||||
|  | 	'Edit user' => 'Modifier un utilisateur', | ||||||
|  | 	'First name' => 'Prénom', | ||||||
|  | 	'Last name' => 'Nom', | ||||||
|  | 	'A username is required' => 'Un nom d\'utilisateur est requis', | ||||||
|  | 	'Confirm password' => 'Confirmation du mot de passe', | ||||||
|  | 	'Passwords do not match' => 'Les mots de passe ne sont pas identiques', | ||||||
|  | 	'Change password' => 'Changer le mot de passe', | ||||||
|  | 	'Done by' => 'Fait par', | ||||||
|  | 	'Last done by' => 'Dernière réalisation par', | ||||||
|  | 	'Unknown' => 'Inconnu', | ||||||
|  | 	'Filter by chore' => 'Filtrer par corvée', | ||||||
|  | 	'Chores journal' => 'Journal des corvées', | ||||||
|  | 	'0 means suggestions for the next charge cycle are disabled' => '0 implique que les suggestions du prochain cycle de charge seront désactivées', | ||||||
|  | 	'Charge cycle interval (days)' => 'Intervalle du cycle de charge (jours)', | ||||||
|  | 	'Last price' => 'Dernier prix', | ||||||
|  | 	'Price history' => 'Historique des prix', | ||||||
|  | 	'No price history available' => 'Aucun historique disponible', | ||||||
|  | 	'Price' => 'Prix', | ||||||
|  | 	'in #1 per purchase quantity unit' => 'en #1 par quantité achetée (au format d\'achat)', | ||||||
|  | 	'The price cannot be lower than #1' => 'Le prix ne peut être inférieur à #1', | ||||||
|  | 	'#1 product expires within the next #2 days' => '#1 produit se périme dans les #2 prochains jours', | ||||||
|  | 	'#1 product is already expired' => '#1 produit est périmé', | ||||||
|  | 	'#1 product is below defined min. stock amount' => '#1 produit est sous le seuil de stock minimum', | ||||||
|  | 	'Unit' => 'Unité', | ||||||
|  | 	'Units' => 'Unités', | ||||||
|  | 	'#1 chore is due to be done within the next #2 days' => '#1 corvée doit être réalisée dans les #2 prochains jours', | ||||||
|  | 	'#1 chore is overdue to be done' => '#1 corvée n\'a pas été réalisée à temps', | ||||||
|  | 	'#1 battery is due to be charged within the next #2 days' => '#1 batterie doit être rechargée dans les #2 prochains jours', | ||||||
|  | 	'#1 battery is overdue to be charged' => '#1 batterie n\'a pas été rechargée à temps', | ||||||
|  | 	'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 unité a automatiquement été ajoutée et sera appliquée en plus à la quantité entrée ici', | ||||||
|  | 	'in singular form' => 'Au singulier', | ||||||
|  | 	'in plural form' => 'Au pluriel', | ||||||
|  | 	'Never expires' => 'Ne périme jamais', | ||||||
|  | 	'This cannot be lower than #1' => 'Ne peut être inférieur à #1', | ||||||
|  | 	'-1 means that this product never expires' => '-1 implique que ce produit ne périme jamais', | ||||||
|  | 	'Quantity unit' => 'Format', | ||||||
|  | 	'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Vérifier uniquement si une unité est en stock (une quantité différente peut alors être utilisée au dessus)', | ||||||
|  | 	'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Voulez-vous vraiment consommer tous les ingrédients requis par la recette "#1" (les ingrédients avec l\'option "Vérifier uniquement si une unité est en stock" seront ignorés) ?', | ||||||
|  | 	'Removed all ingredients of recipe "#1" from stock' => 'Enlever tous les ingrédients de la recette "#1" du stock', | ||||||
|  | 	'Consume all ingredients needed by this recipe' => 'Consommer tous les ingrédients requis par cette recette', | ||||||
|  | 	'Click to show technical details' => 'Cliquer pour afficher les détails techniques', | ||||||
|  | 	'Error while saving, probably this item already exists' => 'Erreur à l\'enregistrement, cet objet existe déjà', | ||||||
|  | 	'Error details' => 'Détails sur l\'erreur', | ||||||
|  | 	'Tasks' => 'Tâches', | ||||||
|  | 	'Show done tasks' => 'Afficher les tâches terminées', | ||||||
|  | 	'Task' => 'Tâche', | ||||||
|  | 	'Due' => 'À faire', | ||||||
|  | 	'Assigned to' => 'Assigné à', | ||||||
|  | 	'Mark task "#1" as completed' => 'Indiquer la tâche "#1" comme terminée', | ||||||
|  | 	'Uncategorized' => 'Sans catégorie', | ||||||
|  | 	'Task categories' => 'Catégories de tâche', | ||||||
|  | 	'Create task' => 'Créer une tâche', | ||||||
|  | 	'A due date is required' => 'Une date de réalisation est requise', | ||||||
|  | 	'Category' => 'Catégorie', | ||||||
|  | 	'Edit task' => 'Modifier la tâche', | ||||||
|  | 	'Are you sure to delete task "#1"?' => 'Voulez-vous vraiment supprimer la tâche "#1" ?', | ||||||
|  | 	'#1 task is due to be done within the next #2 days' => '#1 tâche doit être réalisée dans les #2 prochains jours', | ||||||
|  | 	'#1 tasks are due to be done within the next #2 days' => '#1 tâches doivent être réalisées dans les #2 prochains jours', | ||||||
|  | 	'#1 task is overdue to be done' => '#1 tâche n\'a pas été réalisée à temps', | ||||||
|  | 	'#1 tasks are overdue to be done' => '#1 tâches n\'ont pas été réalisées à temps', | ||||||
|  | 	'Edit task category' => 'Modifier la catégorie de tâche', | ||||||
|  | 	'Create task category' => 'Créer une catégorie de tâche', | ||||||
|  | 	'Product groups' => 'Groupes de produit', | ||||||
|  | 	'Ungrouped' => 'Sans groupe', | ||||||
|  | 	'Create product group' => 'Créer un groupe de produit', | ||||||
|  | 	'Edit product group' => 'Modifier le groupe de produit', | ||||||
|  | 	'Product group' => 'Groupe de produit', | ||||||
|  | 	'Are you sure to delete product group "#1"?' => 'Voulez-vous vraiment supprimer le groupe de produit "#1" ?', | ||||||
|  | 	'Stay logged in permanently' => 'Rester connecté de manière permanente', | ||||||
|  | 	'When not set, you will get logged out at latest after 30 days' => 'Si non défini, vous serez déconnecté après au moins 30 jours', | ||||||
|  | 	'Filter by status' => 'Filtrer par statut', | ||||||
|  | 	'Below min. stock amount' => 'En dessous du seuil de stock minimum', | ||||||
|  | 	'Expiring soon' => 'Expire bientôt', | ||||||
|  | 	'Already expired' => 'Déjà périmé', | ||||||
|  | 	'Due soon' => 'À réaliser bientôt', | ||||||
|  | 	'Overdue' => 'En retard', | ||||||
|  | 	'View settings' => 'Voir les paramètres', | ||||||
|  | 	'Auto reload on external changes' => 'Mettre à jour automatiquement lors d\'un changement externe', | ||||||
|  | 	'Enable night mode' => 'Activer le mode nuit', | ||||||
|  | 	'Auto enable in time range' => 'Activer automatiquement pendant la période', | ||||||
|  | 	'From' => 'De', | ||||||
|  | 	'in format' => 'Au format', | ||||||
|  | 	'To' => 'à', | ||||||
|  | 	'Time range goes over midnight' => 'La période inclus minuit', | ||||||
|  | 	'Product picture' => 'Photo du produit', | ||||||
|  | 	'No file selected' => 'Aucun fichier sélectionné', | ||||||
|  | 	'If you don\'t select a file, the current picture will not be altered' => 'Si vous ne sélectionnez pas de photo, l\'actuelle sera conservée', | ||||||
|  | 	'Current picture' => 'Photo actuelle', | ||||||
|  | 	'Delete' => 'Supprimer', | ||||||
|  | 	'The current picture will be deleted when you save the product' => 'La photo actuelle va être supprimée lors de la sauvegarde du produit', | ||||||
|  | 	'Select file' => 'Sélectionner un fichier', | ||||||
|  | 	'Image of product #1' => 'Photo du produit #1', | ||||||
|  | 	'This product cannot be deleted because it is in stock, please remove the stock amount first.' => 'Ce produit ne peut être supprimé puisqu\'il est en stock. Merci d\'enlever la quantité en stock avant.', | ||||||
|  | 	'Delete not possible' => 'Impossible de supprimer', | ||||||
|  | 	'Equipment' => 'Équipement', | ||||||
|  | 	'Instruction manual' => 'Manuel d\'utilisation', | ||||||
|  | 	'The selected equipment has no instruction manual' => 'L\'équipement sélectionné n\'a pas de manuel d\'utilisation', | ||||||
|  | 	'Notes' => 'Notes', | ||||||
|  | 	'Edit equipment' => 'Modifier un équipement', | ||||||
|  | 	'Create equipment' => 'Créer un équipement', | ||||||
|  | 	'If you don\'t select a file, the current instruction manual will not be altered' => 'Si vous ne sélectionnez pas de fichier, le manuel actuel ne sera pas modifié', | ||||||
|  | 	'Current instruction manual' => 'Manuel d\'utilisation actuel', | ||||||
|  | 	'No instruction manual available' => 'Aucun manuel d\'utilisation disponible', | ||||||
|  | 	'The current instruction manual will be deleted when you save the equipment' => 'Le manuel d\'utilisation actuel sera supprimé lors de la sauvegarde de cet équipement', | ||||||
|  | 	'No picture available' => 'Aucune photo disponible', | ||||||
|  | 	'Filter by product group' => 'Filtrer par groupe de produits', | ||||||
|  | 	'Presets for new products' => 'Modèle pour les nouveaux produits', | ||||||
|  | 	'Included recipes' => 'Recettes incluses', | ||||||
|  | 	'A recipe is required' => 'Une recette est requise', | ||||||
|  | 	'Add included recipe' => 'Ajouter une recette incluse', | ||||||
|  | 	'Edit included recipe' => 'Supprimer une recette incluse', | ||||||
|  | 	'Group' => 'Groupe', | ||||||
|  | 	'This will be used as a headline to group ingredients together' => 'Cela sera utilisé comme titre pour regrouper les ingrédients ensemble', | ||||||
|  | 	'Journal' => 'Journal', | ||||||
|  | 	'Stock journal' => 'Journal du stock', | ||||||
|  | 	'Filter by product' => 'Filtrer par produit', | ||||||
|  | 	'Booking time' => 'Temps de réservation', | ||||||
|  | 	'Booking type' => 'Type de réservation', | ||||||
|  | 	'Undo booking' => 'Annuler la réservation', | ||||||
|  | 	'Undone on' => 'Annulé le', | ||||||
|  | 	'Batteries journal' => 'Journal des batteries', | ||||||
|  | 	'Filter by battery' => 'Filtrer par batterie', | ||||||
|  | 	'Undo charge cycle' => 'Annuler le cycle de charge', | ||||||
|  | 	'Undo chore execution' => 'Annuler la réalisation de la corvée', | ||||||
|  | 	'Chore execution successfully undone' => 'La réalisation de la corvée a bien été annulée', | ||||||
|  | 	'Undo' => 'Annuler', | ||||||
|  | 	'Booking successfully undone' => 'Réservation annulée', | ||||||
|  | 	'Charge cycle successfully undone' => 'Le cycle de charge a bien été annulé', | ||||||
|  | 	'This cannot be negative and must be an integral number' => 'Ne peut être négatif et doit être un nombre entier', | ||||||
|  | 	'Disable stock fulfillment checking for this ingredient' => 'Désactiver la vérification du stock pour cet ingrédient', | ||||||
|  | 	'Add all list items to stock' => 'Ajouter toute la liste dans le stock', | ||||||
|  | 	'Add #3 #1 of #2 to stock' => 'Ajouter #3 #1 de #2 au stock', | ||||||
|  | 	'Adding shopping list item #1 of #2' => 'Ajout du produit #1 sur #2 de la liste de courses', | ||||||
|  | 	'Use a specific stock item' => 'Utiliser un objet spécifique du stock', | ||||||
|  | 	'The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"' => 'Le premier élément de cette liste sera sélectionné par la règle par défaut qui est "Le premier arrivant à péremption en premier, puis premier entré premier sorti"', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Indiquer #3 #1 de #2 comme ouvert', | ||||||
|  | 	'When a product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)' => 'Quand un produit est marqué comme ouvert, la date de péremption sera remplacée par la date du jour + ce nombre de jours (une valeur de 0 désactive ce changement)', | ||||||
|  | 	'Default best before days after opened' => 'Date de péremption en jours par défaut après ouverture', | ||||||
|  | 	'Marked #1 #2 of #3 as opened' => '#1 #2 de #3 indiqués comme ouverts', | ||||||
|  | 	'Mark as opened' => 'Indiquer comme ouvert', | ||||||
|  | 	'Expires on #1; Bought on #2' => 'Périme le #1; Acheté le #2', | ||||||
|  | 	'Not opened' => 'Non ouvert', | ||||||
|  | 	'Opened' => 'Ouvert', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Indiquer #3 #1 de #2 comme ouvert', | ||||||
|  | 	'#1 opened' => '#1 ouvert', | ||||||
|  | 	'Product expires' => 'Expiration du produit', | ||||||
|  | 	'Task due' => 'Tâche à réaliser', | ||||||
|  | 	'Chore due' => 'Corvée à réaliser', | ||||||
|  | 	'Battery charge cycle due' => 'Rechargement à réaliser', | ||||||
|  | 	'Show clock in header' => 'Afficher l\'horloge dans l\'en-tête', | ||||||
|  | 	'Stock settings' => 'Paramètres du stock', | ||||||
|  | 	'Shopping list to stock workflow' => 'Transition de la liste de courses vers le stock', | ||||||
|  | 	'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' => 'Réaliser automatiquement les achats en utilisant le dernier prix connu et la quantité indiquée dans la liste, si le premier a une date de péremption par défaut renseignée' | ||||||
|  | ); | ||||||
							
								
								
									
										6
									
								
								localization/it/chore_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								localization/it/chore_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'manually' => 'Manualmente', | ||||||
|  | 	'dynamic-regular' => 'Regolatore dinamico' | ||||||
|  | ); | ||||||
							
								
								
									
										10
									
								
								localization/it/component_translations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								localization/it/component_translations.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'timeago_locale' => 'it', | ||||||
|  | 	'timeago_nan' => 'NaN anni fa', | ||||||
|  | 	'moment_locale' => 'it', | ||||||
|  | 	'datatables_localization' => '{"sEmptyTable":"Nessun dato disponibile","sInfo":"Mostrando da _START_ a _END_ di _TOTAL_ voci","sInfoEmpty":"Mostrando da 0 a 0 di 0 voci","sInfoFiltered":"(Filtrato da _MAX_ voci totali)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Mostra _MENU_ voci","sLoadingRecords":"Caricando...","sProcessing":"Calcolando...","sSearch":"Cerca:","sZeroRecords":"Nessun risultato trovato","oPaginate":{"sFirst":"Prima","sLast":"Ultima","sNext":"Prossima","sPrevious":"Precedente"},"oAria":{"sSortAscending":": ordine crescente","sSortDescending":": ordine decrescente"}}', | ||||||
|  | 	'summernote_locale' => 'it-IT', | ||||||
|  | 	'fullcalendar_locale' => 'fr' | ||||||
|  | ); | ||||||
							
								
								
									
										87
									
								
								localization/it/demo_data.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								localization/it/demo_data.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Cookies' => 'Biscotti', | ||||||
|  | 	'Chocolate' => 'Cioccolato', | ||||||
|  | 	'Pantry' => 'Vorratskammer', | ||||||
|  | 	'Candy cupboard' => 'Süßigkeitenschrank', | ||||||
|  | 	'Tinned food cupboard' => 'Konservenschrank', | ||||||
|  | 	'Fridge' => 'Kühlschrank', | ||||||
|  | 	'Piece' => 'Pezzo', | ||||||
|  | 	'Pieces' => 'Pezzi', | ||||||
|  | 	'Pack' => 'Pacco', | ||||||
|  | 	'Packs' => 'Pacchi', | ||||||
|  | 	'Glass' => 'Bicchiere', | ||||||
|  | 	'Glasses' => 'Bicchieri', | ||||||
|  | 	'Tin' => 'Scatola', | ||||||
|  | 	'Tins' => 'Scatole', | ||||||
|  | 	'Can' => 'Lattina', | ||||||
|  | 	'Cans' => 'Lattine', | ||||||
|  | 	'Bunch' => 'Cespo', | ||||||
|  | 	'Bunches' => 'Cespi', | ||||||
|  | 	'Gummy bears' => 'Caramelle', | ||||||
|  | 	'Crisps' => 'Patatine', | ||||||
|  | 	'Eggs' => 'Uova', | ||||||
|  | 	'Noodles' => 'Spaghetti', | ||||||
|  | 	'Pickles' => 'Marmellata', | ||||||
|  | 	'Gulash soup' => 'Dado', | ||||||
|  | 	'Yogurt' => 'Yogurt', | ||||||
|  | 	'Cheese' => 'Parmigiano', | ||||||
|  | 	'Cold cuts' => 'Pancetta', | ||||||
|  | 	'Paprika' => 'Pepe', | ||||||
|  | 	'Cucumber' => 'Zucchine', | ||||||
|  | 	'Radish' => 'Radicchio', | ||||||
|  | 	'Tomato' => 'Pomodori', | ||||||
|  | 	'Changed towels in the bathroom' => 'Cambiare asciugamani in bagno', | ||||||
|  | 	'Cleaned the kitchen floor' => 'Pulire la cucina', | ||||||
|  | 	'Warranty ends' => 'Scadenza dalla garanzia', | ||||||
|  | 	'TV remote control' => 'Telecomando', | ||||||
|  | 	'Alarm clock' => 'Sveglia', | ||||||
|  | 	'Heat remote control' => 'Termostato', | ||||||
|  | 	'Lawn mowed in the garden' => 'Lawn mowed in the garden', | ||||||
|  | 	'Some good snacks' => 'Some good snacks', | ||||||
|  | 	'Pizza dough' => 'Pizza dough', | ||||||
|  | 	'Sieved tomatoes' => 'Sieved tomatoes', | ||||||
|  | 	'Salami' => 'Salami', | ||||||
|  | 	'Toast' => 'Toast', | ||||||
|  | 	'Minced meat' => 'Minced meat', | ||||||
|  | 	'Pizza' => 'Pizza', | ||||||
|  | 	'Spaghetti bolognese' => 'Spaghetti bolognese', | ||||||
|  | 	'Sandwiches' => 'Sandwiches', | ||||||
|  | 	'English' => 'English', | ||||||
|  | 	'German' => 'German', | ||||||
|  | 	'Italian' => 'Italian', | ||||||
|  | 	'Demo in different language' => 'Demo in different language', | ||||||
|  | 	'This is the note content of the recipe ingredient' => 'This is the note content of the recipe ingredient', | ||||||
|  | 	'Demo User' => 'Demo User', | ||||||
|  | 	'Gram' => 'Gram', | ||||||
|  | 	'Grams' => 'Grams', | ||||||
|  | 	'Flour' => 'Flour', | ||||||
|  | 	'Pancakes' => 'Pancakes', | ||||||
|  | 	'Sugar' => 'Sugar', | ||||||
|  | 	'Home' => 'Home', | ||||||
|  | 	'Life' => 'Life', | ||||||
|  | 	'Projects' => 'Projects', | ||||||
|  | 	'Repair the garage door' => 'Repair the garage door', | ||||||
|  | 	'Fork and improve grocy' => 'Fork and improve grocy', | ||||||
|  | 	'Find a solution for what to do when I forget the door keys' => 'Find a solution for what to do when I forget the door keys', | ||||||
|  | 	'Sweets' => 'Sweets', | ||||||
|  | 	'Bakery products' => 'Bakery products', | ||||||
|  | 	'Tinned food' => 'Tinned food', | ||||||
|  | 	'Butchery products' => 'Butchery products', | ||||||
|  | 	'Vegetables/Fruits' => 'Vegetables/Fruits', | ||||||
|  | 	'Refrigerated products' => 'Refrigerated products', | ||||||
|  | 	'Coffee machine' => 'Coffee machine', | ||||||
|  | 	'Dishwasher' => 'Dishwasher', | ||||||
|  | 	'Liter' => 'Liter', | ||||||
|  | 	'Liters' => 'Liters', | ||||||
|  | 	'Bottle' => 'Bottle', | ||||||
|  | 	'Bottles' => 'Bottles', | ||||||
|  | 	'Milk' => 'Milk', | ||||||
|  | 	'Chocolate sauce' => 'Chocolate sauce', | ||||||
|  | 	'Milliliters' => 'Milliliters', | ||||||
|  | 	'Milliliter' => 'Milliliter', | ||||||
|  | 	'Bottom' => 'Bottom', | ||||||
|  | 	'Topping' => 'Topping', | ||||||
|  | 	'French' => 'French' | ||||||
|  | ); | ||||||
							
								
								
									
										8
									
								
								localization/it/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								localization/it/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'purchase' => 'Purchase', | ||||||
|  | 	'consume' => 'Consume', | ||||||
|  | 	'inventory-correction' => 'Inventory correction', | ||||||
|  | 	'product-opened' => 'Product opened' | ||||||
|  | ); | ||||||
							
								
								
									
										329
									
								
								localization/it/strings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								localization/it/strings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Stock overview' => 'Dispensa', | ||||||
|  | 	'#1 products expiring within the next #2 days' => '#1 prodotti scadranno tra #2 giorni', | ||||||
|  | 	'#1 products are already expired' => '#1 prodotti scaduti', | ||||||
|  | 	'#1 products are below defined min. stock amount' => '#1 prodotti sotto il limite minimo', | ||||||
|  | 	'Product' => 'prodotto', | ||||||
|  | 	'Amount' => 'quantità', | ||||||
|  | 	'Next best before date' => 'Prossima data di scadenza', | ||||||
|  | 	'Logout' => 'Logout', | ||||||
|  | 	'Chores overview' => 'Riepilogo delle abitudini', | ||||||
|  | 	'Batteries overview' => 'Riepilogo delle batterie', | ||||||
|  | 	'Purchase' => 'Acquisti', | ||||||
|  | 	'Consume' => 'Consumi', | ||||||
|  | 	'Inventory' => 'Inventario', | ||||||
|  | 	'Shopping list' => 'Lista della spesa', | ||||||
|  | 	'Chore tracking' => 'Dati abitudini', | ||||||
|  | 	'Battery tracking' => 'Dati batterie', | ||||||
|  | 	'Products' => 'Prodotti', | ||||||
|  | 	'Locations' => 'Posizioni', | ||||||
|  | 	'Quantity units' => 'Unità di misura', | ||||||
|  | 	'Chores' => 'Abitudini', | ||||||
|  | 	'Batteries' => 'Batterie', | ||||||
|  | 	'Chore' => 'Abitudine', | ||||||
|  | 	'Next estimated tracking' => 'Prossima esecuzione', | ||||||
|  | 	'Last tracked' => 'Ultima esecuzione', | ||||||
|  | 	'Battery' => 'Batterie', | ||||||
|  | 	'Last charged' => 'Ultima ricarica', | ||||||
|  | 	'Next planned charge cycle' => 'Prossima ricarica', | ||||||
|  | 	'Best before' => 'Data di scadenza', | ||||||
|  | 	'OK' => 'OK', | ||||||
|  | 	'Product overview' => 'Riepilogo dei prodotti', | ||||||
|  | 	'Stock quantity unit' => 'Unità di misura', | ||||||
|  | 	'Stock amount' => 'Quantità', | ||||||
|  | 	'Last purchased' => 'Ultimo acquisto', | ||||||
|  | 	'Last used' => 'Ultimo utilizzo', | ||||||
|  | 	'Spoiled' => 'Scaduto', | ||||||
|  | 	'Barcode lookup is disabled' => 'I codici a barre sono disabilitati', | ||||||
|  | 	'will be added to the list of barcodes for the selected product on submit' => 'sarà aggiunto alla lista dei codici a barre per questo prodotto', | ||||||
|  | 	'New amount' => 'Nuova quantità', | ||||||
|  | 	'Note' => 'Nota', | ||||||
|  | 	'Tracked time' => 'Ora di esecuzione', | ||||||
|  | 	'Chore overview' => 'Riepilogo dell\'abitudine', | ||||||
|  | 	'Tracked count' => 'Numero di esecuzioni', | ||||||
|  | 	'Battery overview' => 'Riepilogo della batteria', | ||||||
|  | 	'Charge cycles count' => 'Numero di ricariche', | ||||||
|  | 	'Create shopping list item' => 'Aggiungi un prodotto alla lista della spesa', | ||||||
|  | 	'Edit shopping list item' => 'Modifica un\'entrata della lista della spesa', | ||||||
|  | 	'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 sono state aggiunte automaticamente', | ||||||
|  | 	'Save' => 'Salva', | ||||||
|  | 	'Add' => 'Aggiungi', | ||||||
|  | 	'Name' => 'Nome', | ||||||
|  | 	'Location' => 'Posizione', | ||||||
|  | 	'Min. stock amount' => 'Quantità minima', | ||||||
|  | 	'QU purchase' => 'Unità di acquisto', | ||||||
|  | 	'QU stock' => 'Unità di dispensa', | ||||||
|  | 	'QU factor' => 'Fattore di conversione', | ||||||
|  | 	'Description' => 'Descrizione', | ||||||
|  | 	'Create product' => 'Aggiungi prodotto', | ||||||
|  | 	'Barcode(s)' => 'Codice a barre', | ||||||
|  | 	'Minimum stock amount' => 'Quantità minima', | ||||||
|  | 	'Default best before days' => 'Data di scadenza standard in giorni', | ||||||
|  | 	'Quantity unit purchase' => 'Unità di acquisto', | ||||||
|  | 	'Quantity unit stock' => 'Unità di dispensa', | ||||||
|  | 	'Factor purchase to stock quantity unit' => 'Fattore di conversione tra quantità di acquisto e di dispensa', | ||||||
|  | 	'Create location' => 'Aggiungi posizione', | ||||||
|  | 	'Create quantity unit' => 'Aggiungi unità di misura', | ||||||
|  | 	'Period type' => 'Tipo di ripetizione', | ||||||
|  | 	'Period days' => 'Periodo in giorni', | ||||||
|  | 	'Create chore' => 'Aggiungi abitudine', | ||||||
|  | 	'Used in' => 'Usato in', | ||||||
|  | 	'Create battery' => 'Aggiungi batteria', | ||||||
|  | 	'Edit battery' => 'Modifica batteria', | ||||||
|  | 	'Edit chore' => 'Modifica abitudine', | ||||||
|  | 	'Edit quantity unit' => 'Modifica unità di misura', | ||||||
|  | 	'Edit product' => 'Modifica prodotto', | ||||||
|  | 	'Edit location' => 'Modifica posizione', | ||||||
|  | 	'Record data' => 'Registra dati', | ||||||
|  | 	'Manage master data' => 'Gestisci dati', | ||||||
|  | 	'This will apply to added products' => 'Verrà applicato ai prodotti aggiunti', | ||||||
|  | 	'never' => 'mai', | ||||||
|  | 	'Add products that are below defined min. stock amount' => 'Aggiungi prodotti sotti il limite minimo', | ||||||
|  | 	'For purchases this amount of days will be added to today for the best before date suggestion' => 'Questo numero di giorni verrà aggiunto alla data di acquisto per la data di scadenza', | ||||||
|  | 	'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Questo significa che 1 #1 acquistato diventerà #2 #3 in dispensa', | ||||||
|  | 	'Login' => 'Login', | ||||||
|  | 	'Username' => 'Username', | ||||||
|  | 	'Password' => 'Password', | ||||||
|  | 	'Invalid credentials, please try again' => 'Credenziali non valide, per favore riprova', | ||||||
|  | 	'Are you sure to delete battery "#1"?' => 'Sei sicuro di voler eliminare la batteria "#1"?', | ||||||
|  | 	'Yes' => 'Si', | ||||||
|  | 	'No' => 'No', | ||||||
|  | 	'Are you sure to delete chore "#1"?' => 'Sei sicuro di voler eliminare l\'abitudine "#1"?', | ||||||
|  | 	'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" non è stato associato a nessun prodotto, vuoi procedere?', | ||||||
|  | 	'Create or assign product' => 'Aggiungi o assegna prodotto', | ||||||
|  | 	'Cancel' => 'Annulla', | ||||||
|  | 	'Add as new product' => 'Aggiungi come nuovo prodotto', | ||||||
|  | 	'Add as barcode to existing product' => 'Assegna il codice a barre ad un prodotto', | ||||||
|  | 	'Add as new product and prefill barcode' => 'Aggiungi come nuovo prodotto ed assegna il codice a barre', | ||||||
|  | 	'Are you sure to delete quantity unit "#1"?' => 'Sei sicuro di voler eliminare l\'unità di misura "#1"?', | ||||||
|  | 	'Are you sure to delete product "#1"?' => 'Sei sicuro di voler eliminare il prodotto "#1"?', | ||||||
|  | 	'Are you sure to delete location "#1"?' => 'Sei sicuro di voler eliminare la posizione "#1"?', | ||||||
|  | 	'Manage API keys' => 'Gestisci le chiavi API', | ||||||
|  | 	'REST API & data model documentation' => 'REST API & Documentazione del modello di dati', | ||||||
|  | 	'API keys' => 'Chiavi API', | ||||||
|  | 	'Create new API key' => 'Crea nuova chiave API', | ||||||
|  | 	'API key' => 'Chiave API', | ||||||
|  | 	'Expires' => 'Scade il', | ||||||
|  | 	'Created' => 'Creata il', | ||||||
|  | 	'This product is not in stock' => 'Questo prodotto non è in dispensa', | ||||||
|  | 	'This means #1 will be added to stock' => '#1 sarà aggiunto alla dispensa', | ||||||
|  | 	'This means #1 will be removed from stock' => '#1 sarà rimosso dalla dispensa', | ||||||
|  | 	'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'L\'esecuzione dell\'abitudine è #1 giorni dopo la precedente', | ||||||
|  | 	'Removed #1 #2 of #3 from stock' => '#1 #2 su #3 rimossi dalla dispensa', | ||||||
|  | 	'About grocy' => 'Riguardo grocy', | ||||||
|  | 	'Close' => 'Chiudi', | ||||||
|  | 	'#1 batteries are due to be charged within the next #2 days' => '#1 batterie da ricaricare entro #2 giorni', | ||||||
|  | 	'#1 batteries are overdue to be charged' => '#1 batterie devono essere ricaricate', | ||||||
|  | 	'#1 chores are due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni', | ||||||
|  | 	'#1 chores are overdue to be done' => '#1 abitudini da eseguire', | ||||||
|  | 	'Released on' => 'Rilasciato il', | ||||||
|  | 	'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2', | ||||||
|  | 	'Added #1 #2 of #3 to stock' => 'Aggiunti #1 #2 di #3', | ||||||
|  | 	'Stock amount of #1 is now #2 #3' => 'La quantità in dispensa di #1 è ora #2 #3', | ||||||
|  | 	'Tracked execution of chore #1 on #2' => 'Esecuzione dell\'abitudine #1 registrata il #2', | ||||||
|  | 	'Tracked charge cycle of battery #1 on #2' => 'Ricarica della batteria #1 effettuata il #2', | ||||||
|  | 	'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa', | ||||||
|  | 	'All' => 'Tutto', | ||||||
|  | 	'Track charge cycle of battery #1' => 'Registra la ricarica della batteria #1', | ||||||
|  | 	'Track execution of chore #1' => 'Registra l\'esecuzione dell\'abitudine #1', | ||||||
|  | 	'Filter by location' => 'Filtra per posizione', | ||||||
|  | 	'Search' => 'Cerca', | ||||||
|  | 	'Not logged in' => 'Non autenticato', | ||||||
|  | 	'You have to select a product' => 'Devi selezionare un prodotto', | ||||||
|  | 	'You have to select a chore' => 'Devi selezionare un\'abitudine', | ||||||
|  | 	'You have to select a battery' => 'Devi selezionare una batteria', | ||||||
|  | 	'A name is required' => 'Inserisci un nome', | ||||||
|  | 	'A location is required' => 'Inserisci la posizione', | ||||||
|  | 	'The amount cannot be lower than #1' => 'La quantità non può essere minore di #1', | ||||||
|  | 	'This cannot be negative' => 'Il numero non può essere negativo', | ||||||
|  | 	'A quantity unit is required' => 'Inserisci un\'unità di misura', | ||||||
|  | 	'A period type is required' => 'Seleziona un tipo di ripetizione', | ||||||
|  | 	'A best before date is required and must be later than today' => 'A best before date is required and must be later than today', | ||||||
|  | 	'Settings' => 'Settings', | ||||||
|  | 	'This can only be before now' => 'This can only be before now', | ||||||
|  | 	'Calendar' => 'Calendar', | ||||||
|  | 	'Recipes' => 'Recipes', | ||||||
|  | 	'Edit recipe' => 'Edit recipe', | ||||||
|  | 	'New recipe' => 'New recipe', | ||||||
|  | 	'Ingredients list' => 'Ingredients list', | ||||||
|  | 	'Add recipe ingredient' => 'Add recipe ingredient', | ||||||
|  | 	'Edit recipe ingredient' => 'Edit recipe ingredient', | ||||||
|  | 	'Are you sure to delete recipe "#1"?' => 'Are you sure to delete recipe "#1"?', | ||||||
|  | 	'Are you sure to delete recipe ingredient "#1"?' => 'Are you sure to delete recipe ingredient "#1"?', | ||||||
|  | 	'Are you sure to empty the shopping list?' => 'Are you sure to empty the shopping list?', | ||||||
|  | 	'Clear list' => 'Clear list', | ||||||
|  | 	'Requirements fulfilled' => 'Requirements fulfilled', | ||||||
|  | 	'Put missing products on shopping list' => 'Put missing products on shopping list', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing' => 'Not enough in stock, #1 ingredients missing', | ||||||
|  | 	'Enough in stock' => 'Enough in stock', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Not enough in stock, #1 ingredients missing but already on the shopping list', | ||||||
|  | 	'Expand to fullscreen' => 'Expand to fullscreen', | ||||||
|  | 	'Ingredients' => 'Ingredients', | ||||||
|  | 	'Preparation' => 'Preparation', | ||||||
|  | 	'Recipe' => 'Recipe', | ||||||
|  | 	'Not enough in stock, #1 missing, #2 already on shopping list' => 'Not enough in stock, #1 missing, #2 already on shopping list', | ||||||
|  | 	'Show notes' => 'Show notes', | ||||||
|  | 	'Put missing amount on shopping list' => 'Put missing amount on shopping list', | ||||||
|  | 	'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?', | ||||||
|  | 	'Added for recipe #1' => 'Added for recipe #1', | ||||||
|  | 	'Manage users' => 'Manage users', | ||||||
|  | 	'User' => 'User', | ||||||
|  | 	'Users' => 'Users', | ||||||
|  | 	'Are you sure to delete user "#1"?' => 'Are you sure to delete user "#1"?', | ||||||
|  | 	'Create user' => 'Create user', | ||||||
|  | 	'Edit user' => 'Edit user', | ||||||
|  | 	'First name' => 'First name', | ||||||
|  | 	'Last name' => 'Last name', | ||||||
|  | 	'A username is required' => 'A username is required', | ||||||
|  | 	'Confirm password' => 'Confirm password', | ||||||
|  | 	'Passwords do not match' => 'Passwords do not match', | ||||||
|  | 	'Change password' => 'Change password', | ||||||
|  | 	'Done by' => 'Done by', | ||||||
|  | 	'Last done by' => 'Last done by', | ||||||
|  | 	'Unknown' => 'Unknown', | ||||||
|  | 	'Filter by chore' => 'Filter by chore', | ||||||
|  | 	'Chores journal' => 'Chores journal', | ||||||
|  | 	'0 means suggestions for the next charge cycle are disabled' => '0 means suggestions for the next charge cycle are disabled', | ||||||
|  | 	'Charge cycle interval (days)' => 'Charge cycle interval (days)', | ||||||
|  | 	'Last price' => 'Last price', | ||||||
|  | 	'Price history' => 'Price history', | ||||||
|  | 	'No price history available' => 'No price history available', | ||||||
|  | 	'Price' => 'Price', | ||||||
|  | 	'in #1 per purchase quantity unit' => 'in #1 per purchase quantity unit', | ||||||
|  | 	'The price cannot be lower than #1' => 'The price cannot be lower than #1', | ||||||
|  | 	'#1 product expires within the next #2 days' => '#1 product expires within the next #2 days', | ||||||
|  | 	'#1 product is already expired' => '#1 product is already expired', | ||||||
|  | 	'#1 product is below defined min. stock amount' => '#1 product is below defined min. stock amount', | ||||||
|  | 	'Unit' => 'Unit', | ||||||
|  | 	'Units' => 'Units', | ||||||
|  | 	'#1 chore is due to be done within the next #2 days' => '#1 chore is due to be done within the next #2 days', | ||||||
|  | 	'#1 chore is overdue to be done' => '#1 chore is overdue to be done', | ||||||
|  | 	'#1 battery is due to be charged within the next #2 days' => '#1 battery is due to be charged within the next #2 days', | ||||||
|  | 	'#1 battery is overdue to be charged' => '#1 battery is overdue to be charged', | ||||||
|  | 	'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 unit was automatically added and will apply in addition to the amount entered here', | ||||||
|  | 	'in singular form' => 'in singular form', | ||||||
|  | 	'in plural form' => 'in plural form', | ||||||
|  | 	'Never expires' => 'Never expires', | ||||||
|  | 	'This cannot be lower than #1' => 'This cannot be lower than #1', | ||||||
|  | 	'-1 means that this product never expires' => '-1 means that this product never expires', | ||||||
|  | 	'Quantity unit' => 'Quantity unit', | ||||||
|  | 	'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Only check if a single unit is in stock (a different quantity can then be used above)', | ||||||
|  | 	'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?', | ||||||
|  | 	'Removed all ingredients of recipe "#1" from stock' => 'Removed all ingredients of recipe "#1" from stock', | ||||||
|  | 	'Consume all ingredients needed by this recipe' => 'Consume all ingredients needed by this recipe', | ||||||
|  | 	'Click to show technical details' => 'Click to show technical details', | ||||||
|  | 	'Error while saving, probably this item already exists' => 'Error while saving, probably this item already exists', | ||||||
|  | 	'Error details' => 'Error details', | ||||||
|  | 	'Tasks' => 'Tasks', | ||||||
|  | 	'Show done tasks' => 'Show done tasks', | ||||||
|  | 	'Task' => 'Task', | ||||||
|  | 	'Due' => 'Due', | ||||||
|  | 	'Assigned to' => 'Assigned to', | ||||||
|  | 	'Mark task "#1" as completed' => 'Mark task "#1" as completed', | ||||||
|  | 	'Uncategorized' => 'Uncategorized', | ||||||
|  | 	'Task categories' => 'Task categories', | ||||||
|  | 	'Create task' => 'Create task', | ||||||
|  | 	'A due date is required' => 'A due date is required', | ||||||
|  | 	'Category' => 'Category', | ||||||
|  | 	'Edit task' => 'Edit task', | ||||||
|  | 	'Are you sure to delete task "#1"?' => 'Are you sure to delete task "#1"?', | ||||||
|  | 	'#1 task is due to be done within the next #2 days' => '#1 task is due to be done within the next #2 days', | ||||||
|  | 	'#1 tasks are due to be done within the next #2 days' => '#1 tasks are due to be done within the next #2 days', | ||||||
|  | 	'#1 task is overdue to be done' => '#1 task is overdue to be done', | ||||||
|  | 	'#1 tasks are overdue to be done' => '#1 tasks are overdue to be done', | ||||||
|  | 	'Edit task category' => 'Edit task category', | ||||||
|  | 	'Create task category' => 'Create task category', | ||||||
|  | 	'Product groups' => 'Product groups', | ||||||
|  | 	'Ungrouped' => 'Ungrouped', | ||||||
|  | 	'Create product group' => 'Create product group', | ||||||
|  | 	'Edit product group' => 'Edit product group', | ||||||
|  | 	'Product group' => 'Product group', | ||||||
|  | 	'Are you sure to delete product group "#1"?' => 'Are you sure to delete product group "#1"?', | ||||||
|  | 	'Stay logged in permanently' => 'Stay logged in permanently', | ||||||
|  | 	'When not set, you will get logged out at latest after 30 days' => 'When not set, you will get logged out at latest after 30 days', | ||||||
|  | 	'Filter by status' => 'Filter by status', | ||||||
|  | 	'Below min. stock amount' => 'Below min. stock amount', | ||||||
|  | 	'Expiring soon' => 'Expiring soon', | ||||||
|  | 	'Already expired' => 'Already expired', | ||||||
|  | 	'Due soon' => 'Due soon', | ||||||
|  | 	'Overdue' => 'Overdue', | ||||||
|  | 	'View settings' => 'View settings', | ||||||
|  | 	'Auto reload on external changes' => 'Auto reload on external changes', | ||||||
|  | 	'Enable night mode' => 'Enable night mode', | ||||||
|  | 	'Auto enable in time range' => 'Auto enable in time range', | ||||||
|  | 	'From' => 'From', | ||||||
|  | 	'in format' => 'in format', | ||||||
|  | 	'To' => 'To', | ||||||
|  | 	'Time range goes over midnight' => 'Time range goes over midnight', | ||||||
|  | 	'Product picture' => 'Product picture', | ||||||
|  | 	'No file selected' => 'No file selected', | ||||||
|  | 	'If you don\'t select a file, the current picture will not be altered' => 'If you don\'t select a file, the current picture will not be altered', | ||||||
|  | 	'Current picture' => 'Current picture', | ||||||
|  | 	'Delete' => 'Delete', | ||||||
|  | 	'The current picture will be deleted when you save the product' => 'The current picture will be deleted when you save the product', | ||||||
|  | 	'Select file' => 'Select file', | ||||||
|  | 	'Image of product #1' => 'Image of product #1', | ||||||
|  | 	'This product cannot be deleted because it is in stock, please remove the stock amount first.' => 'This product cannot be deleted because it is in stock, please remove the stock amount first.', | ||||||
|  | 	'Delete not possible' => 'Delete not possible', | ||||||
|  | 	'Equipment' => 'Equipment', | ||||||
|  | 	'Instruction manual' => 'Instruction manual', | ||||||
|  | 	'The selected equipment has no instruction manual' => 'The selected equipment has no instruction manual', | ||||||
|  | 	'Notes' => 'Notes', | ||||||
|  | 	'Edit equipment' => 'Edit equipment', | ||||||
|  | 	'Create equipment' => 'Create equipment', | ||||||
|  | 	'If you don\'t select a file, the current instruction manual will not be altered' => 'If you don\'t select a file, the current instruction manual will not be altered', | ||||||
|  | 	'Current instruction manual' => 'Current instruction manual', | ||||||
|  | 	'No instruction manual available' => 'No instruction manual available', | ||||||
|  | 	'The current instruction manual will be deleted when you save the equipment' => 'The current instruction manual will be deleted when you save the equipment', | ||||||
|  | 	'No picture available' => 'No picture available', | ||||||
|  | 	'Filter by product group' => 'Filter by product group', | ||||||
|  | 	'Presets for new products' => 'Presets for new products', | ||||||
|  | 	'Included recipes' => 'Included recipes', | ||||||
|  | 	'A recipe is required' => 'A recipe is required', | ||||||
|  | 	'Add included recipe' => 'Add included recipe', | ||||||
|  | 	'Edit included recipe' => 'Edit included recipe', | ||||||
|  | 	'Group' => 'Group', | ||||||
|  | 	'This will be used as a headline to group ingredients together' => 'This will be used as a headline to group ingredients together', | ||||||
|  | 	'Journal' => 'Journal', | ||||||
|  | 	'Stock journal' => 'Stock journal', | ||||||
|  | 	'Filter by product' => 'Filter by product', | ||||||
|  | 	'Booking time' => 'Booking time', | ||||||
|  | 	'Booking type' => 'Booking type', | ||||||
|  | 	'Undo booking' => 'Undo booking', | ||||||
|  | 	'Undone on' => 'Undone on', | ||||||
|  | 	'Batteries journal' => 'Batteries journal', | ||||||
|  | 	'Filter by battery' => 'Filter by battery', | ||||||
|  | 	'Undo charge cycle' => 'Undo charge cycle', | ||||||
|  | 	'Undo chore execution' => 'Undo chore execution', | ||||||
|  | 	'Chore execution successfully undone' => 'Chore execution successfully undone', | ||||||
|  | 	'Undo' => 'Undo', | ||||||
|  | 	'Booking successfully undone' => 'Booking successfully undone', | ||||||
|  | 	'Charge cycle successfully undone' => 'Charge cycle successfully undone', | ||||||
|  | 	'This cannot be negative and must be an integral number' => 'This cannot be negative and must be an integral number', | ||||||
|  | 	'Disable stock fulfillment checking for this ingredient' => 'Disable stock fulfillment checking for this ingredient', | ||||||
|  | 	'Add all list items to stock' => 'Add all list items to stock', | ||||||
|  | 	'Add #3 #1 of #2 to stock' => 'Add #3 #1 of #2 to stock', | ||||||
|  | 	'Adding shopping list item #1 of #2' => 'Adding shopping list item #1 of #2', | ||||||
|  | 	'Use a specific stock item' => 'Use a specific stock item', | ||||||
|  | 	'The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"' => 'The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Mark #3 #1 of #2 as open', | ||||||
|  | 	'When a product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)' => 'When a product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)', | ||||||
|  | 	'Default best before days after opened' => 'Default best before days after opened', | ||||||
|  | 	'Marked #1 #2 of #3 as opened' => 'Marked #1 #2 of #3 as opened', | ||||||
|  | 	'Mark as opened' => 'Mark as opened', | ||||||
|  | 	'Expires on #1; Bought on #2' => 'Expires on #1; Bought on #2', | ||||||
|  | 	'Not opened' => 'Not opened', | ||||||
|  | 	'Opened' => 'Opened', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Mark #3 #1 of #2 as open', | ||||||
|  | 	'#1 opened' => '#1 opened', | ||||||
|  | 	'Product expires' => 'Product expires', | ||||||
|  | 	'Task due' => 'Task due', | ||||||
|  | 	'Chore due' => 'Chore due', | ||||||
|  | 	'Battery charge cycle due' => 'Battery charge cycle due', | ||||||
|  | 	'Show clock in header' => 'Show clock in header', | ||||||
|  | 	'Stock settings' => 'Stock settings', | ||||||
|  | 	'Shopping list to stock workflow' => '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' => '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' | ||||||
|  | ); | ||||||
							
								
								
									
										6
									
								
								localization/no/chore_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								localization/no/chore_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'manually' => 'Manuel', | ||||||
|  | 	'dynamic-regular' => 'Automatisk' | ||||||
|  | ); | ||||||
							
								
								
									
										10
									
								
								localization/no/component_translations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								localization/no/component_translations.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'timeago_locale' => 'no', | ||||||
|  | 	'timeago_nan' => 'for NaN År', | ||||||
|  | 	'moment_locale' => 'nb', | ||||||
|  | 	'datatables_localization' => '{"sEmptyTable":"Det finnes ingen data i tabellen","sInfo":"_START_ fra _END_ til _TOTAL_ skriv","sInfoEmpty":"Ingen data tilgjengelign","sInfoFiltered":"(filtrert fra _MAX_ skriv)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ registrer deg","sLoadingRecords":"Laster ..","sProcessing":"Vennligst vent ..","sSearch":"Søk","sZeroRecords":"Ingen oppføringer tilgjengelig","oPaginate":{"sFirst":"Første","sPrevious":"Bakover","sNext":"Neste","sLast":"Siste"},"oAria":{"sSortAscending":": Sortér stigende","sSortDescending":": Sortér synkende"},"select":{"rows":{"0":"klikk på en linje for å velge","1":"1 linje valgt","_":"%d linger valgt"}},"buttons":{"print":"Print","colvis":"Søyle","copy":"Kopi","copyTitle":"Kopier til utklippstavlen","copyKeys":"Trykk <i>ctrl</i> eller <i>⌘</i> + <i>C</i> for å kopiere tabell<br> til utklipptavlen.<br><br>For å avbryte, klikke på meldingen eller trykk på ESC.","copySuccess":{"1":"1 Kolonne kopiert","_":"%d kolonne kopiert"}}}', | ||||||
|  | 	'summernote_locale' => 'nb-NO', | ||||||
|  | 	'fullcalendar_locale' => 'nb' | ||||||
|  | ); | ||||||
							
								
								
									
										87
									
								
								localization/no/demo_data.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								localization/no/demo_data.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Cookies' => 'Cookies', | ||||||
|  | 	'Chocolate' => 'Sjokolade', | ||||||
|  | 	'Pantry' => 'Spiskammers', | ||||||
|  | 	'Candy cupboard' => 'Godteriskapet', | ||||||
|  | 	'Tinned food cupboard' => 'Boksematskapet', | ||||||
|  | 	'Fridge' => 'Kjøleskapet', | ||||||
|  | 	'Piece' => 'Ett', | ||||||
|  | 	'Pieces' => 'Flere', | ||||||
|  | 	'Pack' => 'Pakke', | ||||||
|  | 	'Packs' => 'Pakker', | ||||||
|  | 	'Glass' => 'Glass', | ||||||
|  | 	'Glasses' => 'Glass', | ||||||
|  | 	'Tin' => 'Hermetikkboks', | ||||||
|  | 	'Tins' => 'Hermetikkbokser', | ||||||
|  | 	'Can' => 'Boks', | ||||||
|  | 	'Cans' => 'Bokser', | ||||||
|  | 	'Bunch' => 'Klase', | ||||||
|  | 	'Bunches' => 'Klaser', | ||||||
|  | 	'Gummy bears' => 'Vingummibjørner', | ||||||
|  | 	'Crisps' => 'Chips', | ||||||
|  | 	'Eggs' => 'Egg', | ||||||
|  | 	'Noodles' => 'Nuddler', | ||||||
|  | 	'Pickles' => 'Sur agurk', | ||||||
|  | 	'Gulash soup' => 'Gulasj suppe', | ||||||
|  | 	'Yogurt' => 'Yoghurt', | ||||||
|  | 	'Cheese' => 'Ost', | ||||||
|  | 	'Cold cuts' => 'Kjøttpålegg', | ||||||
|  | 	'Paprika' => 'Paprika', | ||||||
|  | 	'Cucumber' => 'Agurk', | ||||||
|  | 	'Radish' => 'Reddik', | ||||||
|  | 	'Tomato' => 'Tomat', | ||||||
|  | 	'Changed towels in the bathroom' => 'Bytt handklær på badet', | ||||||
|  | 	'Cleaned the kitchen floor' => 'Vasket kjøkkengulvet', | ||||||
|  | 	'Warranty ends' => 'Garanti utgår', | ||||||
|  | 	'TV remote control' => 'Fjernkontroll for TV', | ||||||
|  | 	'Alarm clock' => 'Alarmklokke', | ||||||
|  | 	'Heat remote control' => 'Fjernkontroll for termostat', | ||||||
|  | 	'Lawn mowed in the garden' => 'Kuttet gresset i hagen', | ||||||
|  | 	'Some good snacks' => 'Noen gode snacks', | ||||||
|  | 	'Pizza dough' => 'Pizzadeig', | ||||||
|  | 	'Sieved tomatoes' => 'Tomatpuré', | ||||||
|  | 	'Salami' => 'Salami', | ||||||
|  | 	'Toast' => 'Ristet brød', | ||||||
|  | 	'Minced meat' => 'Kjøttdeig', | ||||||
|  | 	'Pizza' => 'Pizza', | ||||||
|  | 	'Spaghetti bolognese' => 'Spaghetti Bolognese', | ||||||
|  | 	'Sandwiches' => 'Smørbrød', | ||||||
|  | 	'English' => 'Engelsk', | ||||||
|  | 	'German' => 'Tysk', | ||||||
|  | 	'Italian' => 'Italiensk', | ||||||
|  | 	'Demo in different language' => 'Demo i annet språk', | ||||||
|  | 	'This is the note content of the recipe ingredient' => 'Dette er notisen for ingrediensen i oppskriften', | ||||||
|  | 	'Demo User' => 'Demo Bruker', | ||||||
|  | 	'Gram' => 'Gram', | ||||||
|  | 	'Grams' => 'Gram', | ||||||
|  | 	'Flour' => 'Mel', | ||||||
|  | 	'Pancakes' => 'Pannekaker', | ||||||
|  | 	'Sugar' => 'Sukker', | ||||||
|  | 	'Home' => 'Hus', | ||||||
|  | 	'Life' => 'Livstil', | ||||||
|  | 	'Projects' => 'Projekter', | ||||||
|  | 	'Repair the garage door' => 'Reparere garasjedøren', | ||||||
|  | 	'Fork and improve grocy' => 'Fork og forbedre grocy', | ||||||
|  | 	'Find a solution for what to do when I forget the door keys' => 'Finne på løsning for hva jeg skal gjøre når jeg mister dørnøklene', | ||||||
|  | 	'Sweets' => 'Godteri', | ||||||
|  | 	'Bakery products' => 'Bakevarer', | ||||||
|  | 	'Tinned food' => 'Boksemat', | ||||||
|  | 	'Butchery products' => 'Kjøtt/ Ferskvare', | ||||||
|  | 	'Vegetables/Fruits' => 'Frukt/ Grønnsaker', | ||||||
|  | 	'Refrigerated products' => 'Frysedisk', | ||||||
|  | 	'Coffee machine' => 'Kaffetrakter', | ||||||
|  | 	'Dishwasher' => 'Oppvaskmaskin', | ||||||
|  | 	'Liter' => 'Liter', | ||||||
|  | 	'Liters' => 'Liter', | ||||||
|  | 	'Bottle' => 'Flaske', | ||||||
|  | 	'Bottles' => 'Flasker', | ||||||
|  | 	'Milk' => 'Melk', | ||||||
|  | 	'Chocolate sauce' => 'Sjokoladesaus', | ||||||
|  | 	'Milliliters' => 'Milliliter', | ||||||
|  | 	'Milliliter' => 'Milliliter', | ||||||
|  | 	'Bottom' => 'Bunn', | ||||||
|  | 	'Topping' => 'Topping', | ||||||
|  | 	'French' => 'Fransk' | ||||||
|  | ); | ||||||
							
								
								
									
										8
									
								
								localization/no/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								localization/no/stock_transaction_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'purchase' => 'Innkjøp', | ||||||
|  | 	'consume' => 'Forbruke produkt', | ||||||
|  | 	'inventory-correction' => 'Korreksjon av husholdnings ', | ||||||
|  | 	'product-opened' => 'Produkt åpnet' | ||||||
|  | ); | ||||||
							
								
								
									
										329
									
								
								localization/no/strings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								localization/no/strings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return array( | ||||||
|  | 	'Stock overview' => 'Husholdning', | ||||||
|  | 	'#1 products expiring within the next #2 days' => '#1 Produkt som går ut på dato innen de neste #2 dagene', | ||||||
|  | 	'#1 products are already expired' => '#1 Produkt som har gått ut på dato', | ||||||
|  | 	'#1 products are below defined min. stock amount' => '#1 Produkt under minimum husholdningsnivå', | ||||||
|  | 	'Product' => 'Produkt', | ||||||
|  | 	'Amount' => 'Antall', | ||||||
|  | 	'Next best before date' => 'Kommende best før dato', | ||||||
|  | 	'Logout' => 'Logg ut', | ||||||
|  | 	'Chores overview' => 'Oversikt husarbeid', | ||||||
|  | 	'Batteries overview' => 'Oversikt batteri', | ||||||
|  | 	'Purchase' => 'Innkjøp', | ||||||
|  | 	'Consume' => 'Forbruk produkt', | ||||||
|  | 	'Inventory' => 'Endre husholdning', | ||||||
|  | 	'Shopping list' => 'Handleliste', | ||||||
|  | 	'Chore tracking' => 'Logge husarbeid', | ||||||
|  | 	'Battery tracking' => 'Batteri ladesyklus', | ||||||
|  | 	'Products' => 'Produkter', | ||||||
|  | 	'Locations' => 'Lokasjoner', | ||||||
|  | 	'Quantity units' => 'Forpakning', | ||||||
|  | 	'Chores' => 'Husarbeid', | ||||||
|  | 	'Batteries' => 'Batterier', | ||||||
|  | 	'Chore' => 'Husarbeid', | ||||||
|  | 	'Next estimated tracking' => 'Neste handling', | ||||||
|  | 	'Last tracked' => 'Sist logget', | ||||||
|  | 	'Battery' => 'Batteri', | ||||||
|  | 	'Last charged' => 'Sist ladet', | ||||||
|  | 	'Next planned charge cycle' => 'Neste planlagte ladesyklus', | ||||||
|  | 	'Best before' => 'Best før', | ||||||
|  | 	'OK' => 'OK', | ||||||
|  | 	'Product overview' => 'Produkt oversikt', | ||||||
|  | 	'Stock quantity unit' => 'Forpakningstype i husholdningen', | ||||||
|  | 	'Stock amount' => 'Husholdning', | ||||||
|  | 	'Last purchased' => 'Sist kjøpt', | ||||||
|  | 	'Last used' => 'Sist brukt', | ||||||
|  | 	'Spoiled' => 'Produkt har gått ut på dato', | ||||||
|  | 	'Barcode lookup is disabled' => 'Strekkodesøk deaktivert', | ||||||
|  | 	'will be added to the list of barcodes for the selected product on submit' => 'Blir lagt til liste over strekkoder når produkt blir lagt inn.', | ||||||
|  | 	'New amount' => 'Nytt antall', | ||||||
|  | 	'Note' => 'Info', | ||||||
|  | 	'Tracked time' => 'Tid utført/ ladet', | ||||||
|  | 	'Chore overview' => 'Oversikt husarbeid', | ||||||
|  | 	'Tracked count' => 'Antall utførelser/ ladninger', | ||||||
|  | 	'Battery overview' => 'Batteri oversikt', | ||||||
|  | 	'Charge cycles count' => 'Antall ladesykluser', | ||||||
|  | 	'Create shopping list item' => 'Opprett handelisteoppføring', | ||||||
|  | 	'Edit shopping list item' => 'Endre på handlelistoppføring', | ||||||
|  | 	'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 enheter ble automatisk lagt til i tillegg til hva som blir skrevet inn her', | ||||||
|  | 	'Save' => 'Lagre', | ||||||
|  | 	'Add' => 'Legg til', | ||||||
|  | 	'Name' => 'Navn', | ||||||
|  | 	'Location' => 'Lokasjon', | ||||||
|  | 	'Min. stock amount' => 'Minimumsantall for husholdningen', | ||||||
|  | 	'QU purchase' => 'Forpakingsfaktor innkjøp', | ||||||
|  | 	'QU stock' => 'Forpakingsfaktor husholdning', | ||||||
|  | 	'QU factor' => 'Forpakingsfaktor', | ||||||
|  | 	'Description' => 'Beskrivelse', | ||||||
|  | 	'Create product' => 'Opprett produkt', | ||||||
|  | 	'Barcode(s)' => 'Strekkode(r)', | ||||||
|  | 	'Minimum stock amount' => 'Minimumsantall for husholdningen', | ||||||
|  | 	'Default best before days' => 'Standard antall dager best før', | ||||||
|  | 	'Quantity unit purchase' => 'Forpakning kjøpt', | ||||||
|  | 	'Quantity unit stock' => 'Forpakning husholdning', | ||||||
|  | 	'Factor purchase to stock quantity unit' => 'Innkjøpsfaktor for forpakning', | ||||||
|  | 	'Create location' => 'Opprett lokasjon', | ||||||
|  | 	'Create quantity unit' => 'Opprett forpakning', | ||||||
|  | 	'Period type' => 'Gjentakelse', | ||||||
|  | 	'Period days' => 'Antall dager for gjentakelse', | ||||||
|  | 	'Create chore' => 'Opprett husarbeid oppgave', | ||||||
|  | 	'Used in' => 'Brukt', | ||||||
|  | 	'Create battery' => 'Opprett batteri', | ||||||
|  | 	'Edit battery' => 'Endre batteri', | ||||||
|  | 	'Edit chore' => 'Endre husarbeid oppgave', | ||||||
|  | 	'Edit quantity unit' => 'Endre forpakning', | ||||||
|  | 	'Edit product' => 'Endre produkt', | ||||||
|  | 	'Edit location' => 'Endre lokasjon', | ||||||
|  | 	'Record data' => 'Logg handlinger', | ||||||
|  | 	'Manage master data' => 'Administrer masterdata', | ||||||
|  | 	'This will apply to added products' => 'Dette vil gjelde for produkt som blir lagt til', | ||||||
|  | 	'never' => 'aldri', | ||||||
|  | 	'Add products that are below defined min. stock amount' => 'Legg til produkt som er under minimumsnivå for husholdningen', | ||||||
|  | 	'For purchases this amount of days will be added to today for the best before date suggestion' => 'Når innkjøp gjøres vil dette bli brukt som antall dager "best før"', | ||||||
|  | 	'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Dette betyr at 1 #1 innkjøp vil bli omgjort til #2 #3 husholdning', | ||||||
|  | 	'Login' => 'Logg inn', | ||||||
|  | 	'Username' => 'Brukernavn', | ||||||
|  | 	'Password' => 'Passord', | ||||||
|  | 	'Invalid credentials, please try again' => 'Feil brukernavn og/eller passord, prøv igjen', | ||||||
|  | 	'Are you sure to delete battery "#1"?' => 'Er du sikker du ønsker å slette batteri "#1"?', | ||||||
|  | 	'Yes' => 'Ja', | ||||||
|  | 	'No' => 'Nei', | ||||||
|  | 	'Are you sure to delete chore "#1"?' => 'Er du sikker på du ønsker å slette husarbeid oppgave "#1"?', | ||||||
|  | 	'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" kunne ikke bli tildelt et produkt, hvordan ønsker du å fortsette?', | ||||||
|  | 	'Create or assign product' => 'Opprett eller tildel til et produkt', | ||||||
|  | 	'Cancel' => 'Avbryt', | ||||||
|  | 	'Add as new product' => 'Legg til som nytt produkt', | ||||||
|  | 	'Add as barcode to existing product' => 'Legg til strekkode til allerede eksisterende produkt', | ||||||
|  | 	'Add as new product and prefill barcode' => 'Legg til som nytt produkt med forhåndsfylt strekkode', | ||||||
|  | 	'Are you sure to delete quantity unit "#1"?' => 'Er du sikker du ønsker å slette forpakning "#1"?', | ||||||
|  | 	'Are you sure to delete product "#1"?' => 'Er du sikker du ønsker å slette produkt "#1"?', | ||||||
|  | 	'Are you sure to delete location "#1"?' => 'Er du sikker du ønsker å slette lokasjon "#1"?', | ||||||
|  | 	'Manage API keys' => 'Administrer API-Keys', | ||||||
|  | 	'REST API & data model documentation' => 'REST-API & Datamodell Dokumentasjon', | ||||||
|  | 	'API keys' => 'API-Keys', | ||||||
|  | 	'Create new API key' => 'Opprett ny API-Key', | ||||||
|  | 	'API key' => 'API-Key', | ||||||
|  | 	'Expires' => 'Går ut', | ||||||
|  | 	'Created' => 'Opprettet', | ||||||
|  | 	'This product is not in stock' => 'Dette produktet er ikke i husholdningen', | ||||||
|  | 	'This means #1 will be added to stock' => 'Dette betyr at #1 vil bli lagt til i husholdningen', | ||||||
|  | 	'This means #1 will be removed from stock' => 'Dette betyr at #1 vil bli fjernet fra husholdningen', | ||||||
|  | 	'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Dette betyr at det er estimert at den nye utførelsen av denne husarbeid oppgaven er logget #1 dag etter den sist var logget', | ||||||
|  | 	'Removed #1 #2 of #3 from stock' => 'Fjernet #1 #2 #3 fra husholdningen', | ||||||
|  | 	'About grocy' => 'Om Grocy', | ||||||
|  | 	'Close' => 'Lukk', | ||||||
|  | 	'#1 batteries are due to be charged within the next #2 days' => '#1 batteri må lades innen de #2 neste dagene', | ||||||
|  | 	'#1 batteries are overdue to be charged' => '#1 batteri har gått over fristen for å bli ladet opp', | ||||||
|  | 	'#1 chores are due to be done within the next #2 days' => '#1 husarbeids oppgaver skal gjøres inne de #2 neste dagene', | ||||||
|  | 	'#1 chores are overdue to be done' => '#1 husarbeids oppgaver har gått over fristen for utførelse', | ||||||
|  | 	'Released on' => 'Utgitt', | ||||||
|  | 	'Consume #3 #1 of #2' => 'Forbruk #3 #1 av #2', | ||||||
|  | 	'Added #1 #2 of #3 to stock' => '#1 #2 #3 lagt til i husholdningen', | ||||||
|  | 	'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3', | ||||||
|  | 	'Tracked execution of chore #1 on #2' => 'Utførte husarbeid oppgave "#1" den #2', | ||||||
|  | 	'Tracked charge cycle of battery #1 on #2' => 'Ladet #1 den #2', | ||||||
|  | 	'Consume all #1 which are currently in stock' => 'Forbruk alle #1 som er i husholdningen', | ||||||
|  | 	'All' => 'Alle', | ||||||
|  | 	'Track charge cycle of battery #1' => '#1 ladet', | ||||||
|  | 	'Track execution of chore #1' => 'Utfør husarbeidsoppgave "#1"', | ||||||
|  | 	'Filter by location' => 'Filtrér etter lokasjon', | ||||||
|  | 	'Search' => 'Søk', | ||||||
|  | 	'Not logged in' => 'Ikke logget inn', | ||||||
|  | 	'You have to select a product' => 'Du må velge et produkt', | ||||||
|  | 	'You have to select a chore' => 'Du må velge en husarbeids oppgave', | ||||||
|  | 	'You have to select a battery' => 'Du må velge et batteri', | ||||||
|  | 	'A name is required' => 'Vennligst fyll inn et navn', | ||||||
|  | 	'A location is required' => 'En lokasjon kreves', | ||||||
|  | 	'The amount cannot be lower than #1' => 'Antallet kan ikke være lavere enn #1', | ||||||
|  | 	'This cannot be negative' => 'Dette kan ikke være negativt', | ||||||
|  | 	'A quantity unit is required' => 'Forpakning antall/størrelse kreves', | ||||||
|  | 	'A period type is required' => 'En periodetype kreves', | ||||||
|  | 	'A best before date is required and must be later than today' => 'En best før dato kreves, denne må være senere enn i dag', | ||||||
|  | 	'Settings' => 'Innstillinger', | ||||||
|  | 	'This can only be before now' => 'Dette kan kun være før nå', | ||||||
|  | 	'Calendar' => 'Kalender', | ||||||
|  | 	'Recipes' => 'Oppskrifter', | ||||||
|  | 	'Edit recipe' => 'Endre oppskrift', | ||||||
|  | 	'New recipe' => 'Ny oppskrift', | ||||||
|  | 	'Ingredients list' => 'Liste over ingredienser', | ||||||
|  | 	'Add recipe ingredient' => 'Legg ingrediens til oppskrift', | ||||||
|  | 	'Edit recipe ingredient' => 'Endre ingrediens i oppskrift', | ||||||
|  | 	'Are you sure to delete recipe "#1"?' => 'Er du sikker du ønsker å slette oppskrift "#1"?', | ||||||
|  | 	'Are you sure to delete recipe ingredient "#1"?' => 'Er du sikker du ønsker å slette ingrediens "#1" fra oppskriften?', | ||||||
|  | 	'Are you sure to empty the shopping list?' => 'Er du sikker du ønsker å slette handlelisten?', | ||||||
|  | 	'Clear list' => 'Slett handleliste', | ||||||
|  | 	'Requirements fulfilled' => 'Har jeg alt jeg trenger for denne oppskriften?', | ||||||
|  | 	'Put missing products on shopping list' => 'Legg manglende produkter til handlelisten', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing' => 'Ikke nok i husholdningen, #1 ingredienser mangler', | ||||||
|  | 	'Enough in stock' => 'Nok i husholdningen', | ||||||
|  | 	'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Ikke nok i husholdningen, #1 ingrediens mangler, men denne er på handelisten', | ||||||
|  | 	'Expand to fullscreen' => 'Full skjerm', | ||||||
|  | 	'Ingredients' => 'Ingredienser', | ||||||
|  | 	'Preparation' => 'Forberedelse / Slik gjør du', | ||||||
|  | 	'Recipe' => 'Oppskrift', | ||||||
|  | 	'Not enough in stock, #1 missing, #2 already on shopping list' => 'Ikke nok i husholdningen, mangler #1, er #2 på handlelisten', | ||||||
|  | 	'Show notes' => 'Vis notater', | ||||||
|  | 	'Put missing amount on shopping list' => 'Legg manglende til handlelisten', | ||||||
|  | 	'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Er du sikker du ønsker å legge alle manglende ingredienser til oppskrift "#1"?', | ||||||
|  | 	'Added for recipe #1' => 'Lagt til fra oppskrift "#1"', | ||||||
|  | 	'Manage users' => 'Administrer brukere', | ||||||
|  | 	'User' => 'Bruker', | ||||||
|  | 	'Users' => 'Brukere', | ||||||
|  | 	'Are you sure to delete user "#1"?' => 'Er du sikker på du ønsker å slette bruker, "#1"?', | ||||||
|  | 	'Create user' => 'Legg til bruker', | ||||||
|  | 	'Edit user' => 'Endre på bruker', | ||||||
|  | 	'First name' => 'Fornavn', | ||||||
|  | 	'Last name' => 'Etternavn', | ||||||
|  | 	'A username is required' => 'Et brukernavn er nødvendig', | ||||||
|  | 	'Confirm password' => 'Bekreft passord', | ||||||
|  | 	'Passwords do not match' => 'Passord er ikke like', | ||||||
|  | 	'Change password' => 'Endre passord', | ||||||
|  | 	'Done by' => 'Utført av', | ||||||
|  | 	'Last done by' => 'Sist utført av', | ||||||
|  | 	'Unknown' => 'Ukjent', | ||||||
|  | 	'Filter by chore' => 'Filtrér husarbeid', | ||||||
|  | 	'Chores journal' => 'Statistikk husarbeid', | ||||||
|  | 	'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått', | ||||||
|  | 	'Charge cycle interval (days)' => 'Ladesyklysintervall (dager)', | ||||||
|  | 	'Last price' => 'Siste pris', | ||||||
|  | 	'Price history' => 'Prishistorikk', | ||||||
|  | 	'No price history available' => 'Ingen prishistorikk tilgjengelig', | ||||||
|  | 	'Price' => 'Pris', | ||||||
|  | 	'in #1 per purchase quantity unit' => 'I #1 per kjøpt forpakning ', | ||||||
|  | 	'The price cannot be lower than #1' => 'Prisen kan ikke være lavere enn #1', | ||||||
|  | 	'#1 product expires within the next #2 days' => '#1 Produkt går ut på dato innen de #2  neste dagene', | ||||||
|  | 	'#1 product is already expired' => '#1 Produkt er allerede gått ut på dato', | ||||||
|  | 	'#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå', | ||||||
|  | 	'Unit' => 'Enhet', | ||||||
|  | 	'Units' => 'Enheter', | ||||||
|  | 	'#1 chore is due to be done within the next #2 days' => '#1 husarbeid oppgave skal gjøres inne de #2 neste dagene', | ||||||
|  | 	'#1 chore is overdue to be done' => '#1 husarbeid oppgave har gått over fristen for utførelse', | ||||||
|  | 	'#1 battery is due to be charged within the next #2 days' => '#1 Batteri må lades innen #2 dager', | ||||||
|  | 	'#1 battery is overdue to be charged' => '#1 Batteri har gått over fristen for å lades', | ||||||
|  | 	'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 enhet ble automatisk lagt til i tillegg til hva som blir skrevet inn her', | ||||||
|  | 	'in singular form' => 'I entall', | ||||||
|  | 	'in plural form' => 'I flertall', | ||||||
|  | 	'Never expires' => 'Går ikke ut på dato', | ||||||
|  | 	'This cannot be lower than #1' => 'Dette kan ikke være lavere enn #1', | ||||||
|  | 	'-1 means that this product never expires' => 'Ved å skrive -1 vil produktet ikke gå ut på dato', | ||||||
|  | 	'Quantity unit' => 'Forpakning', | ||||||
|  | 	'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Ønsker du å bruke mindre/større enn normal forpakningsstørrelse?', | ||||||
|  | 	'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Er du sikker du ønsker å forbruke alle ingredienser for "#1" oppskriften? (Ingredienser merket med "Ønsker du å bruke mindre/større enn normal forpakningsstørrelse??" blir ignorert', | ||||||
|  | 	'Removed all ingredients of recipe "#1" from stock' => 'Fjern alle ingredienser for "#1" oppskriften fra husholdningen.', | ||||||
|  | 	'Consume all ingredients needed by this recipe' => 'Forbruk alle ingredienser for denne oppskriften', | ||||||
|  | 	'Click to show technical details' => 'Klikk for å vise teknisk informasjon', | ||||||
|  | 	'Error while saving, probably this item already exists' => 'Kunne ikke lagre, produkt er lagt til fra før', | ||||||
|  | 	'Error details' => 'Detaljer om feil', | ||||||
|  | 	'Tasks' => 'Oppgaver', | ||||||
|  | 	'Show done tasks' => 'Vis ferdige oppgaver', | ||||||
|  | 	'Task' => 'Oppgave', | ||||||
|  | 	'Due' => 'Forfall', | ||||||
|  | 	'Assigned to' => 'Tildelt', | ||||||
|  | 	'Mark task "#1" as completed' => 'Merk oppgave "#1" som ferdig', | ||||||
|  | 	'Uncategorized' => 'Mangler kategori', | ||||||
|  | 	'Task categories' => 'Oppgave kategorier', | ||||||
|  | 	'Create task' => 'Opprett en oppgave', | ||||||
|  | 	'A due date is required' => 'En forfallsdato kreves', | ||||||
|  | 	'Category' => 'Kategori', | ||||||
|  | 	'Edit task' => 'Endre oppgave', | ||||||
|  | 	'Are you sure to delete task "#1"?' => 'Er du sikker du ønsker slette oppgave "#1"?', | ||||||
|  | 	'#1 task is due to be done within the next #2 days' => '#1 oppgave har utførelse forfall innen de neste #2 dagene', | ||||||
|  | 	'#1 tasks are due to be done within the next #2 days' => '#1 oppgaver har utførelse forfall innen de neste #2 dagene', | ||||||
|  | 	'#1 task is overdue to be done' => '#1 oppgave har forfalt utførelse dato', | ||||||
|  | 	'#1 tasks are overdue to be done' => '#1 oppgaver har forfalt utførelse dato', | ||||||
|  | 	'Edit task category' => 'Endre oppgave kategori', | ||||||
|  | 	'Create task category' => 'Opprett oppgave kategori', | ||||||
|  | 	'Product groups' => 'Produktgrupper', | ||||||
|  | 	'Ungrouped' => 'Mangler gruppe', | ||||||
|  | 	'Create product group' => 'Opprett produkt gruppe', | ||||||
|  | 	'Edit product group' => 'Endre produkt gruppe', | ||||||
|  | 	'Product group' => 'Produktgruppe', | ||||||
|  | 	'Are you sure to delete product group "#1"?' => 'Er du sikker du ønsker å slette produktgruppe "#1"?', | ||||||
|  | 	'Stay logged in permanently' => 'Alltid være innlogget', | ||||||
|  | 	'When not set, you will get logged out at latest after 30 days' => 'Når den ikke er satt vil du bli logget ut etter 30 dager', | ||||||
|  | 	'Filter by status' => 'Filtrér etter status', | ||||||
|  | 	'Below min. stock amount' => 'Under under minimum husholdningsnivå', | ||||||
|  | 	'Expiring soon' => 'Går snart ut på dato', | ||||||
|  | 	'Already expired' => 'Utgått på dato', | ||||||
|  | 	'Due soon' => 'Forfaller snart', | ||||||
|  | 	'Overdue' => 'Forfalt', | ||||||
|  | 	'View settings' => 'Se instillinger', | ||||||
|  | 	'Auto reload on external changes' => 'Automatisk fornying ved ekstern endring', | ||||||
|  | 	'Enable night mode' => 'Aktiver nattmodus', | ||||||
|  | 	'Auto enable in time range' => 'Automatisk aktivering i tidsrommet', | ||||||
|  | 	'From' => 'Fra', | ||||||
|  | 	'in format' => 'format', | ||||||
|  | 	'To' => 'Til', | ||||||
|  | 	'Time range goes over midnight' => 'Tidsrommet går over midnatt', | ||||||
|  | 	'Product picture' => 'Produktbilde', | ||||||
|  | 	'No file selected' => 'Produktbilde ikke valgt', | ||||||
|  | 	'If you don\'t select a file, the current picture will not be altered' => 'Hvis du ikke velger et bilde, vil nåværende produktbilde bli værende', | ||||||
|  | 	'Current picture' => 'Nåværende produktbilde', | ||||||
|  | 	'Delete' => 'Slett', | ||||||
|  | 	'The current picture will be deleted when you save the product' => 'Nåværende produktbilde vil bli slettet når du lagrer produktet', | ||||||
|  | 	'Select file' => 'Velg produktbilde', | ||||||
|  | 	'Image of product #1' => 'Bilde av produkt #1', | ||||||
|  | 	'This product cannot be deleted because it is in stock, please remove the stock amount first.' => 'Dette produktet kan ikke slettes fordi det er gjenværende produkter i husholdningen', | ||||||
|  | 	'Delete not possible' => 'Ikke mulig å slette', | ||||||
|  | 	'Equipment' => 'Instruksjonmanualer', | ||||||
|  | 	'Instruction manual' => 'Instruksjonsmanual', | ||||||
|  | 	'The selected equipment has no instruction manual' => 'Merket utstyr har ingen instruksjonsmanual', | ||||||
|  | 	'Notes' => 'Notater', | ||||||
|  | 	'Edit equipment' => 'Endre instruksjonmanualer for utstyr', | ||||||
|  | 	'Create equipment' => 'Opprett instruksjonmanualer for utstyr', | ||||||
|  | 	'If you don\'t select a file, the current instruction manual will not be altered' => 'Hvis du ikke velger en instruksjonsmanual, vil nåværende instruksjonsmanual ikke bli endret', | ||||||
|  | 	'Current instruction manual' => 'Nåværende instruksjonsmanual', | ||||||
|  | 	'No instruction manual available' => 'Ingen instruksjonsmanual tilgjengelig', | ||||||
|  | 	'The current instruction manual will be deleted when you save the equipment' => 'Nåværende instruksjonsmanual vil bli slettet når du lagrer utstyret', | ||||||
|  | 	'No picture available' => 'Ingen bilde tilgjengelig', | ||||||
|  | 	'Filter by product group' => 'Filtrér etter produktgruppe', | ||||||
|  | 	'Presets for new products' => 'Standard for nye produkter', | ||||||
|  | 	'Included recipes' => 'Inkludert oppskrift', | ||||||
|  | 	'A recipe is required' => 'En oppskrift kreves', | ||||||
|  | 	'Add included recipe' => 'Legg til inkludert oppskrift', | ||||||
|  | 	'Edit included recipe' => 'Endre inkludert oppskrift', | ||||||
|  | 	'Group' => 'Gruppe', | ||||||
|  | 	'This will be used as a headline to group ingredients together' => 'Dette vil bli brukt som overskrift for gruppering av ingredienser', | ||||||
|  | 	'Journal' => 'Logg', | ||||||
|  | 	'Stock journal' => 'Husholdningslogg', | ||||||
|  | 	'Filter by product' => 'Filtrér etter produkt', | ||||||
|  | 	'Booking time' => 'Tid logget', | ||||||
|  | 	'Booking type' => 'Booking type', | ||||||
|  | 	'Undo booking' => 'Angre booking', | ||||||
|  | 	'Undone on' => 'Angret den', | ||||||
|  | 	'Batteries journal' => 'Batterilogg', | ||||||
|  | 	'Filter by battery' => 'Filtrér etter batteri', | ||||||
|  | 	'Undo charge cycle' => 'Angre ladesyklus', | ||||||
|  | 	'Undo chore execution' => 'Fjerne utførelse av husarbeidsoppgave', | ||||||
|  | 	'Chore execution successfully undone' => 'Husarbeid fjernet', | ||||||
|  | 	'Undo' => 'Angre', | ||||||
|  | 	'Booking successfully undone' => 'Booking fjernet', | ||||||
|  | 	'Charge cycle successfully undone' => 'Ladesyklus fjernet', | ||||||
|  | 	'This cannot be negative and must be an integral number' => 'Tallet kan ikke være negativ og må være et helt tall', | ||||||
|  | 	'Disable stock fulfillment checking for this ingredient' => 'Skru av husholdning sjekk for denne ingrediensen ', | ||||||
|  | 	'Add all list items to stock' => 'Legg alle produktene i listen til husholdningen', | ||||||
|  | 	'Add #3 #1 of #2 to stock' => 'Legg til #3 #1 av #2 til husholdningen', | ||||||
|  | 	'Adding shopping list item #1 of #2' => 'Legger til produkt #1 av #2 fra handlelisten', | ||||||
|  | 	'Use a specific stock item' => 'Velg et bestemt produkt', | ||||||
|  | 	'The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"' => 'Første produkt på listen vil bli konsumert først i henhold til standard regelen. "Går ut på dato først. Deretter først inn, først ut".', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Merk #3 #1 av #2 som åpnet', | ||||||
|  | 	'When a product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)' => 'Når et produkt blir merket som åpnet endres best før datoen fra i dag + dette antallet av dager. (Et 0 vil deaktivere dette)', | ||||||
|  | 	'Default best before days after opened' => 'Standard best før dager etter åpnet', | ||||||
|  | 	'Marked #1 #2 of #3 as opened' => 'Merk #1 #2 av #3 som åpnet', | ||||||
|  | 	'Mark as opened' => 'Merk som åpnet', | ||||||
|  | 	'Expires on #1; Bought on #2' => 'Går ut på dato #1; Kjøpt #2', | ||||||
|  | 	'Not opened' => 'Ikke åpnet', | ||||||
|  | 	'Opened' => 'Åpnet', | ||||||
|  | 	'Mark #3 #1 of #2 as open' => 'Merk #3 #1 av #2 som åpnet', | ||||||
|  | 	'#1 opened' => '#1 åpnet', | ||||||
|  | 	'Product expires' => 'Produkt går ut på dato', | ||||||
|  | 	'Task due' => 'Tidsfrist for oppgave', | ||||||
|  | 	'Chore due' => 'Tidsfrist for husarbeid', | ||||||
|  | 	'Battery charge cycle due' => 'Batteri må lades', | ||||||
|  | 	'Show clock in header' => 'Vis klokken på toppen av siden', | ||||||
|  | 	'Stock settings' => 'Husholdningsinnstillinger', | ||||||
|  | 	'Shopping list to stock workflow' => 'Arbeidsflyt fra handleliste til husholding', | ||||||
|  | 	'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' => 'Legg produkter automatisk til fra handlelisten. Dette vil bruke sist innkjøpspris og forusetter at "Standard antall dager best før" er satt' | ||||||
|  | ); | ||||||
							
								
								
									
										72
									
								
								middleware/ApiKeyAuthMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								middleware/ApiKeyAuthMiddleware.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Middleware; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\SessionService; | ||||||
|  | use \Grocy\Services\ApiKeyService; | ||||||
|  |  | ||||||
|  | class ApiKeyAuthMiddleware extends BaseMiddleware | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container, string $sessionCookieName, string $apiKeyHeaderName) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->SessionCookieName = $sessionCookieName; | ||||||
|  | 		$this->ApiKeyHeaderName = $apiKeyHeaderName; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $SessionCookieName; | ||||||
|  | 	protected $ApiKeyHeaderName; | ||||||
|  |  | ||||||
|  | 	public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next) | ||||||
|  | 	{ | ||||||
|  | 		$route = $request->getAttribute('route'); | ||||||
|  | 		$routeName = $route->getName(); | ||||||
|  |  | ||||||
|  | 		if (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL) | ||||||
|  | 		{ | ||||||
|  | 			define('GROCY_AUTHENTICATED', true); | ||||||
|  | 			$response = $next($request, $response); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			$validSession = true; | ||||||
|  | 			$validApiKey = true; | ||||||
|  | 			 | ||||||
|  | 			$sessionService = new SessionService(); | ||||||
|  | 			if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) | ||||||
|  | 			{ | ||||||
|  | 				$validSession = false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			$apiKeyService = new ApiKeyService(); | ||||||
|  | 			if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName))) | ||||||
|  | 			{ | ||||||
|  | 				$validApiKey = false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (!$validSession && !$validApiKey) | ||||||
|  | 			{ | ||||||
|  | 				define('GROCY_AUTHENTICATED', false); | ||||||
|  | 				$response = $response->withStatus(401); | ||||||
|  | 			} | ||||||
|  | 			elseif ($validApiKey) | ||||||
|  | 			{ | ||||||
|  | 				$user = $apiKeyService->GetUserByApiKey($request->getHeaderLine($this->ApiKeyHeaderName)); | ||||||
|  | 				define('GROCY_AUTHENTICATED', true); | ||||||
|  | 				define('GROCY_USER_ID', $user->id); | ||||||
|  |  | ||||||
|  | 				$response = $next($request, $response); | ||||||
|  | 			} | ||||||
|  | 			elseif ($validSession) | ||||||
|  | 			{ | ||||||
|  | 				$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]); | ||||||
|  | 				define('GROCY_AUTHENTICATED', true); | ||||||
|  | 				define('GROCY_USER_ID', $user->id); | ||||||
|  |  | ||||||
|  | 				$response = $next($request, $response); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return $response; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								middleware/BaseMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								middleware/BaseMiddleware.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Middleware; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\ApplicationService; | ||||||
|  |  | ||||||
|  | class BaseMiddleware | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container) | ||||||
|  | 	{ | ||||||
|  | 		$this->AppContainer = $container; | ||||||
|  | 		$this->ApplicationService = new ApplicationService(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $AppContainer; | ||||||
|  | 	protected $ApplicationService; | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								middleware/JsonMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								middleware/JsonMiddleware.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Middleware; | ||||||
|  |  | ||||||
|  | class JsonMiddleware extends BaseMiddleware | ||||||
|  | { | ||||||
|  | 	public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next) | ||||||
|  | 	{ | ||||||
|  | 		$response = $next($request, $response); | ||||||
|  |  | ||||||
|  | 		if ($response->hasHeader('Content-Disposition')) | ||||||
|  | 		{ | ||||||
|  | 			return $response; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			return $response->withHeader('Content-Type', 'application/json'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								middleware/SessionAuthMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								middleware/SessionAuthMiddleware.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | namespace Grocy\Middleware; | ||||||
|  |  | ||||||
|  | use \Grocy\Services\SessionService; | ||||||
|  | use \Grocy\Services\LocalizationService; | ||||||
|  |  | ||||||
|  | class SessionAuthMiddleware extends BaseMiddleware | ||||||
|  | { | ||||||
|  | 	public function __construct(\Slim\Container $container, string $sessionCookieName) | ||||||
|  | 	{ | ||||||
|  | 		parent::__construct($container); | ||||||
|  | 		$this->SessionCookieName = $sessionCookieName; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected $SessionCookieName; | ||||||
|  |  | ||||||
|  | 	public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next) | ||||||
|  | 	{ | ||||||
|  | 		$route = $request->getAttribute('route'); | ||||||
|  | 		$routeName = $route->getName(); | ||||||
|  | 		$sessionService = new SessionService(); | ||||||
|  |  | ||||||
|  | 		if ($routeName === 'root') | ||||||
|  | 		{ | ||||||
|  | 			$response = $next($request, $response); | ||||||
|  | 		} | ||||||
|  | 		elseif (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL) | ||||||
|  | 		{ | ||||||
|  | 			$user = $sessionService->GetDefaultUser(); | ||||||
|  | 			define('GROCY_AUTHENTICATED', true); | ||||||
|  | 			define('GROCY_USER_USERNAME', $user->username); | ||||||
|  |  | ||||||
|  | 			$response = $next($request, $response); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login') | ||||||
|  | 			{ | ||||||
|  | 				define('GROCY_AUTHENTICATED', false); | ||||||
|  | 				$response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login')); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				if ($routeName !== 'login') | ||||||
|  | 				{ | ||||||
|  | 					$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]); | ||||||
|  | 					define('GROCY_AUTHENTICATED', true); | ||||||
|  | 					define('GROCY_USER_USERNAME', $user->username); | ||||||
|  | 					define('GROCY_USER_ID', $user->id); | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					define('GROCY_AUTHENTICATED', false); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				$response = $next($request, $response); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return $response; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								migrations/0001.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migrations/0001.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | CREATE TABLE products ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	name TEXT NOT NULL UNIQUE, | ||||||
|  | 	description TEXT, | ||||||
|  | 	location_id INTEGER NOT NULL, | ||||||
|  | 	qu_id_purchase INTEGER NOT NULL, | ||||||
|  | 	qu_id_stock INTEGER NOT NULL, | ||||||
|  | 	qu_factor_purchase_to_stock REAL NOT NULL, | ||||||
|  | 	barcode TEXT, | ||||||
|  | 	min_stock_amount INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	default_best_before_days INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										6
									
								
								migrations/0002.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								migrations/0002.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | CREATE TABLE locations ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	name TEXT NOT NULL UNIQUE, | ||||||
|  | 	description TEXT, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										6
									
								
								migrations/0003.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								migrations/0003.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | CREATE TABLE quantity_units ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	name TEXT NOT NULL UNIQUE, | ||||||
|  | 	description TEXT, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										9
									
								
								migrations/0004.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								migrations/0004.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | CREATE TABLE stock ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	product_id INTEGER NOT NULL, | ||||||
|  | 	amount INTEGER NOT NULL, | ||||||
|  | 	best_before_date DATE, | ||||||
|  | 	purchased_date DATE DEFAULT (datetime('now', 'localtime')), | ||||||
|  | 	stock_id TEXT NOT NULL, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										12
									
								
								migrations/0005.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								migrations/0005.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | CREATE TABLE stock_log ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	product_id INTEGER NOT NULL, | ||||||
|  | 	amount INTEGER NOT NULL, | ||||||
|  | 	best_before_date DATE, | ||||||
|  | 	purchased_date DATE, | ||||||
|  | 	used_date DATE, | ||||||
|  | 	spoiled INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	stock_id TEXT NOT NULL, | ||||||
|  | 	transaction_type TEXT NOT NULL, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										19
									
								
								migrations/0006.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								migrations/0006.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | INSERT INTO locations | ||||||
|  | 	(name, description) | ||||||
|  | VALUES | ||||||
|  | 	('DefaultLocation', 'This is the first default location, edit or delete it'); | ||||||
|  |  | ||||||
|  | INSERT INTO quantity_units | ||||||
|  | 	(name, description) | ||||||
|  | VALUES | ||||||
|  | 	('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it'); | ||||||
|  |  | ||||||
|  | INSERT INTO products | ||||||
|  | 	(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) | ||||||
|  | VALUES | ||||||
|  | 	('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1); | ||||||
|  |  | ||||||
|  | INSERT INTO products | ||||||
|  | 	(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) | ||||||
|  | VALUES | ||||||
|  | 	('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1); | ||||||
							
								
								
									
										9
									
								
								migrations/0007.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								migrations/0007.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | CREATE VIEW stock_missing_products | ||||||
|  | AS | ||||||
|  | SELECT p.id, MAX(p.name) AS name, p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing | ||||||
|  | FROM products p | ||||||
|  | LEFT JOIN stock s | ||||||
|  | 	ON p.id = s.product_id | ||||||
|  | WHERE p.min_stock_amount != 0 | ||||||
|  | GROUP BY p.id | ||||||
|  | HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount | ||||||
							
								
								
									
										5
									
								
								migrations/0008.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								migrations/0008.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | CREATE VIEW stock_current | ||||||
|  | AS | ||||||
|  | SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date | ||||||
|  | FROM stock | ||||||
|  | GROUP BY product_id | ||||||
							
								
								
									
										7
									
								
								migrations/0009.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								migrations/0009.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | CREATE TABLE shopping_list ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	product_id INTEGER NOT NULL UNIQUE, | ||||||
|  | 	amount INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	amount_autoadded INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										8
									
								
								migrations/0010.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								migrations/0010.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | CREATE TABLE habits ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	name TEXT NOT NULL UNIQUE, | ||||||
|  | 	description TEXT, | ||||||
|  | 	period_type TEXT NOT NULL, | ||||||
|  | 	period_days INTEGER, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										6
									
								
								migrations/0011.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								migrations/0011.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | CREATE TABLE habits_log ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	habit_id INTEGER NOT NULL, | ||||||
|  | 	tracked_time DATETIME, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										5
									
								
								migrations/0012.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								migrations/0012.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | CREATE VIEW habits_current | ||||||
|  | AS | ||||||
|  | SELECT habit_id, MAX(tracked_time) AS last_tracked_time | ||||||
|  | FROM habits_log | ||||||
|  | GROUP BY habit_id | ||||||
							
								
								
									
										8
									
								
								migrations/0013.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								migrations/0013.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | CREATE TABLE batteries ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	name TEXT NOT NULL UNIQUE, | ||||||
|  | 	description TEXT, | ||||||
|  | 	used_in TEXT, | ||||||
|  | 	charge_interval_days INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										6
									
								
								migrations/0014.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								migrations/0014.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | CREATE TABLE battery_charge_cycles ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	battery_id TEXT NOT NULL, | ||||||
|  | 	tracked_time DATETIME, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										5
									
								
								migrations/0015.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								migrations/0015.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | CREATE VIEW batteries_current | ||||||
|  | AS | ||||||
|  | SELECT battery_id, MAX(tracked_time) AS last_tracked_time | ||||||
|  | FROM battery_charge_cycles | ||||||
|  | GROUP BY battery_id | ||||||
							
								
								
									
										1
									
								
								migrations/0016.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								migrations/0016.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ALTER TABLE shopping_list RENAME TO shopping_list_old | ||||||
							
								
								
									
										8
									
								
								migrations/0017.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								migrations/0017.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | CREATE TABLE shopping_list ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	product_id INTEGER, | ||||||
|  | 	note TEXT, | ||||||
|  | 	amount INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	amount_autoadded INTEGER NOT NULL DEFAULT 0, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										4
									
								
								migrations/0018.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								migrations/0018.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | INSERT INTO shopping_list | ||||||
|  | 	(product_id, amount, amount_autoadded, row_created_timestamp) | ||||||
|  | SELECT product_id, amount, amount_autoadded, row_created_timestamp | ||||||
|  | FROM shopping_list_old | ||||||
							
								
								
									
										1
									
								
								migrations/0019.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								migrations/0019.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | DROP TABLE shopping_list_old | ||||||
							
								
								
									
										6
									
								
								migrations/0020.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								migrations/0020.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | CREATE TABLE sessions ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, | ||||||
|  | 	session_key TEXT NOT NULL UNIQUE, | ||||||
|  | 	expires DATETIME, | ||||||
|  | 	row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) | ||||||
|  | ) | ||||||
							
								
								
									
										11
									
								
								migrations/0021.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								migrations/0021.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | DELETE FROM locations | ||||||
|  | WHERE name = 'DefaultLocation'; | ||||||
|  |  | ||||||
|  | DELETE FROM quantity_units | ||||||
|  | WHERE name = 'DefaultQuantityUnit'; | ||||||
|  |  | ||||||
|  | DELETE FROM products | ||||||
|  | WHERE name = 'DefaultProduct1'; | ||||||
|  |  | ||||||
|  | DELETE FROM products | ||||||
|  | WHERE name = 'DefaultProduct2'; | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user