Compare commits

...

480 Commits
4.1.0 ... 4.2.0

Author SHA1 Message Date
James Cole
956019ff4a Merge branch 'release/4.2.0' 2016-11-27 16:00:47 +01:00
James Cole
8279cf0e88 New version. 2016-11-27 15:59:13 +01:00
James Cole
43c32abfe8 Various code cleanup. 2016-11-26 13:02:44 +01:00
James Cole
0e66939408 Various code cleanup. 2016-11-26 10:53:20 +01:00
James Cole
22d2a523fb Some minor code fixes. 2016-11-26 10:39:05 +01:00
James Cole
bc825a8603 Remove unused code. 2016-11-26 09:29:41 +01:00
James Cole
c9cfda34a1 Remove duplicate code. 2016-11-26 09:21:49 +01:00
James Cole
e8dfbff73f Various code cleanup. 2016-11-26 09:16:06 +01:00
James Cole
62e41f1997 Remove TODO annotations 2016-11-26 09:07:16 +01:00
James Cole
8c9f90f1b4 Some code cleanup. 2016-11-26 09:01:00 +01:00
James Cole
1453a78e49 Remove todo annotations. 2016-11-26 08:55:26 +01:00
James Cole
7efaf51595 Merge pull request #428 from JC5/l10n_develop
New Crowdin translations
2016-11-26 08:41:35 +01:00
James Cole
6bc6674ab1 Some code simplification. 2016-11-26 08:41:15 +01:00
James Cole
d6c7ff0ccb Chart for budget report will also include split journals. 2016-11-26 07:18:20 +01:00
James Cole
28f655dba1 This code makes sure the budget report also includes split expenses. 2016-11-26 07:09:02 +01:00
James Cole
6a3de12894 Approved. Step name: Proofread 2016-11-25 23:01:03 +01:00
James Cole
c7940333ec Approved. Step name: Proofread 2016-11-25 23:01:01 +01:00
James Cole
8860378757 Fix budget in split journals. 2016-11-25 19:06:06 +01:00
James Cole
728fda0116 This allows the user to set the “default” currency for an asset account (#305). It doesn’t do anything other than this yet. 2016-11-25 18:00:29 +01:00
James Cole
0c72e1831f Merge branch 'develop' of https://github.com/JC5/firefly-iii into develop
* 'develop' of https://github.com/JC5/firefly-iii:
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  Approved. Step name: Proofread
  Translated
  New translations
  New translations
2016-11-25 17:43:38 +01:00
James Cole
7da21976ec Merge pull request #425 from JC5/l10n_develop
New Crowdin translations
2016-11-25 17:43:17 +01:00
James Cole
b739859c64 Expand journal meta with soft delete. This pushes Firefly to 4.2.0. 2016-11-25 17:42:45 +01:00
James Cole
d25665f843 New translations 2016-11-25 17:02:24 +01:00
James Cole
1f41f7bd0f New translations 2016-11-25 17:02:20 +01:00
James Cole
dd8638ca98 New translations 2016-11-25 17:02:17 +01:00
James Cole
4ba9ff05b0 New translations 2016-11-25 17:02:12 +01:00
James Cole
618aad5432 New translations 2016-11-25 17:02:08 +01:00
James Cole
e46fc7501e New translations 2016-11-25 17:02:06 +01:00
James Cole
7b91e98d46 New translations 2016-11-25 17:01:58 +01:00
James Cole
85be218f92 New translations 2016-11-25 17:01:55 +01:00
James Cole
71206e395e New translations 2016-11-25 17:01:53 +01:00
James Cole
9b2d2e16b0 New translations 2016-11-25 17:01:48 +01:00
James Cole
5ae01b382e Approved. Step name: Proofread 2016-11-25 17:01:40 +01:00
James Cole
d92a0753a6 Translated 2016-11-25 17:01:35 +01:00
James Cole
f937a74507 New translations 2016-11-25 17:01:27 +01:00
James Cole
c3584ad20c New translations 2016-11-25 17:01:25 +01:00
James Cole
c049d5cfa6 Various small fixes. 2016-11-25 16:55:04 +01:00
James Cole
6c9990e0be Various Javascript related fixes. 2016-11-25 16:54:13 +01:00
James Cole
b34e4cd31b This fixes #422 2016-11-25 16:52:43 +01:00
James Cole
7852b8a785 Make sure ff does not create accounts when balance is said to be 0. 2016-11-25 16:26:03 +01:00
James Cole
6eeb60db5c Multiply sum by -1. 2016-11-24 21:37:09 +01:00
James Cole
d076cfc08f Attempt to fix issue #417 2016-11-24 21:35:23 +01:00
James Cole
68a93ff97c Made fonts local 2016-11-24 19:26:47 +01:00
James Cole
295dcb4f65 Whoops ;) 2016-11-24 19:20:04 +01:00
James Cole
d9849f60c0 Parse error 2016-11-24 19:16:15 +01:00
James Cole
7ebb68e36c This fixes #419 2016-11-24 19:15:16 +01:00
James Cole
f029f7607b Rewrote all email messages. 2016-11-22 21:21:11 +01:00
James Cole
2ba5733ebc Merge pull request #415 from JC5/l10n_develop
New Crowdin translations
2016-11-22 19:31:51 +01:00
James Cole
3fe1d1d368 New translations 2016-11-22 19:12:25 +01:00
James Cole
438c372583 New translations 2016-11-22 19:12:19 +01:00
James Cole
797aa4858e New translations 2016-11-22 19:12:13 +01:00
James Cole
8c858cd066 New translations 2016-11-22 19:12:05 +01:00
James Cole
85aebd39b9 New translations 2016-11-22 19:12:00 +01:00
James Cole
9a5a037424 Approved. Step name: Proofread 2016-11-22 19:11:48 +01:00
James Cole
7d557cbf91 New translations 2016-11-22 19:11:30 +01:00
James Cole
dbbc85a576 Hide some boxes when the user has no bills. 2016-11-22 19:10:38 +01:00
James Cole
eb78cf20c2 This fixes #414 2016-11-22 19:10:17 +01:00
James Cole
4a99399952 Fix chart for account/all overview. 2016-11-21 20:23:25 +01:00
James Cole
6075d75ee2 Fix debug code [skip ci] 2016-11-21 20:15:59 +01:00
James Cole
f4c56fee66 Merge pull request #412 from JC5/l10n_develop
New Crowdin translations
2016-11-20 19:32:54 +01:00
James Cole
04c59304da Add sorting to a report table [skip ci] 2016-11-20 19:11:10 +01:00
James Cole
4b3c31a11a Ignore deleted transactions. [skip ci] 2016-11-20 19:03:08 +01:00
James Cole
14576d2753 Approved. Step name: Proofread 2016-11-20 18:51:13 +01:00
James Cole
72ca1c20c7 Merge pull request #411 from JC5/l10n_develop
New Crowdin translations
2016-11-20 18:45:16 +01:00
James Cole
93645819b8 New translations 2016-11-20 18:41:48 +01:00
James Cole
39468f871b New translations 2016-11-20 18:41:42 +01:00
James Cole
faa47781d2 New translations 2016-11-20 18:41:37 +01:00
James Cole
2c196bab6d New translations 2016-11-20 18:41:30 +01:00
James Cole
9ae71075ef New translations 2016-11-20 18:41:26 +01:00
James Cole
0013cdfa78 Translated 2016-11-20 18:41:14 +01:00
James Cole
52f3f64f7b New translations 2016-11-20 18:41:03 +01:00
James Cole
670fa77dd7 New tests. 2016-11-20 18:34:49 +01:00
James Cole
8baea2feb9 Code for #385 2016-11-20 18:31:29 +01:00
James Cole
c56f937521 Improved sorting in various views. 2016-11-20 17:36:11 +01:00
James Cole
0b613c3b8c Improve sortability in various lists. 2016-11-20 15:30:16 +01:00
James Cole
78f297e18f Fixed some display bugs for split journals. 2016-11-20 14:17:16 +01:00
James Cole
bd8a285d6d Merge pull request #410 from JC5/l10n_develop
New Crowdin translations
2016-11-20 13:06:14 +01:00
James Cole
b44602fd55 New translations 2016-11-20 13:01:38 +01:00
James Cole
41238903e1 New translations 2016-11-20 13:01:33 +01:00
James Cole
a0c88e9b33 New translations 2016-11-20 13:01:30 +01:00
James Cole
5d184aa53e New translations 2016-11-20 13:01:23 +01:00
James Cole
9f9bf86a9f New translations 2016-11-20 13:01:19 +01:00
James Cole
53af9345eb Approved. Step name: Proofread 2016-11-20 13:01:10 +01:00
James Cole
da6bcf04df New translations 2016-11-20 13:00:58 +01:00
James Cole
ec4ec1a147 New (not implemented) tests. 2016-11-20 12:53:04 +01:00
James Cole
350e0b08b1 This implements #377 2016-11-20 12:51:33 +01:00
James Cole
9340ca09e6 Fixed #408 2016-11-20 12:08:43 +01:00
James Cole
a1cef5c339 Found a bug in the import routine where "default accounts" (an account type no longer used by default) is found. 2016-11-20 11:44:27 +01:00
James Cole
94875adb6c Various code cleanup. 2016-11-20 11:43:19 +01:00
James Cole
75a524c656 Added debug code for a possible import issue. 2016-11-20 11:40:05 +01:00
James Cole
e1e94a788c Register and use interface. 2016-11-20 08:57:48 +01:00
James Cole
8417f45d02 Fixed some tests. 2016-11-20 08:54:52 +01:00
James Cole
685310a368 First account controller tests 2016-11-20 08:46:02 +01:00
James Cole
45e7a4576a Extend some test stuff. 2016-11-20 08:30:25 +01:00
James Cole
f8c5c15655 Updated some tests. 2016-11-20 07:24:18 +01:00
James Cole
26190524f4 Skeletons for test 2016-11-19 20:30:30 +01:00
James Cole
5d901a7ecb Remove local development file. [skip ci] 2016-11-19 18:21:48 +01:00
James Cole
929d8b3adc Merge branch 'release/4.1.7' 2016-11-19 16:47:02 +01:00
James Cole
cd6e37b9cb Update composer.lock file. [skip ci] 2016-11-19 16:45:33 +01:00
James Cole
b647386541 New release imminent. [skip ci] 2016-11-19 16:44:17 +01:00
James Cole
174fd88435 Merge pull request #409 from JC5/l10n_develop
New Crowdin translations
2016-11-19 16:43:44 +01:00
James Cole
cc9211b7c2 Translated 2016-11-19 16:40:48 +01:00
James Cole
a9795fb095 Update ignore file [skip ci] 2016-11-19 16:34:52 +01:00
James Cole
8554aae21e Update read me file [skip ci] 2016-11-19 16:31:39 +01:00
James Cole
5a2ef36f2a Fix travis script. 2016-11-19 16:28:04 +01:00
James Cole
01e3f91ece Do not test hhvm. Fix script. 2016-11-19 16:17:04 +01:00
James Cole
7ec9c090cc Fix test script. 2016-11-19 16:13:57 +01:00
James Cole
b057d69f8e Correct travis configuration. 2016-11-19 16:07:02 +01:00
James Cole
ff4e1838bc Update travis configuration. 2016-11-19 16:03:59 +01:00
James Cole
e4ecd0b7ff Empty travis CI file. 2016-11-19 16:01:19 +01:00
James Cole
1ba35f73e1 Fixed a test 2016-11-19 16:00:20 +01:00
James Cole
240f3c126b Restored some tests. 2016-11-19 15:55:49 +01:00
James Cole
23925a0076 Enabled cache [skip ci] 2016-11-19 15:17:00 +01:00
James Cole
50b72cf229 More chart optimisations. 2016-11-19 13:37:44 +01:00
James Cole
ee6b72afa5 Fix some bugs related to cash accounts. 2016-11-19 12:57:35 +01:00
James Cole
781621960d Make sure chart is displayed. 2016-11-19 09:26:32 +01:00
James Cole
e15ea04186 Join two charts, simpler code. 2016-11-19 07:27:54 +01:00
James Cole
73f0cc705b Code cleanup. 2016-11-18 20:06:08 +01:00
James Cole
0c072c7d51 Some code cleanup. 2016-11-18 19:58:06 +01:00
James Cole
884bed85a1 Update composer file. 2016-11-18 19:54:21 +01:00
James Cole
a319264428 fixed #406 2016-11-18 18:58:48 +01:00
James Cole
6506e70a91 Merge pull request #407 from JC5/l10n_develop
New Crowdin translations
2016-11-18 10:03:08 +01:00
James Cole
e6fcb19db7 New translations 2016-11-18 02:01:10 +01:00
James Cole
a3fba53182 Translated 2016-11-18 02:01:03 +01:00
James Cole
f8438dd9d3 New translations 2016-11-18 01:51:04 +01:00
James Cole
028a0dcae1 New translations 2016-11-18 01:41:05 +01:00
James Cole
b4a06b5bbd Translated 2016-11-18 01:41:01 +01:00
James Cole
4fe1a5d527 New translations 2016-11-18 01:30:59 +01:00
James Cole
865930c5b2 New translations 2016-11-18 01:30:58 +01:00
James Cole
96b4e2c196 New translations 2016-11-18 01:20:59 +01:00
James Cole
b7e7c7e9e2 New translations 2016-11-18 01:11:02 +01:00
James Cole
7771669db7 New translations 2016-11-18 01:01:09 +01:00
James Cole
ef59eb6e1f Translated 2016-11-18 01:01:05 +01:00
James Cole
a14b2bc5a7 Translated 2016-11-18 01:01:04 +01:00
James Cole
47349589cb New translations 2016-11-18 00:51:00 +01:00
James Cole
79afe84f30 Merge pull request #405 from JC5/l10n_develop
New Crowdin translations
2016-11-17 20:35:32 +01:00
James Cole
171187b25c New translations 2016-11-17 20:12:21 +01:00
James Cole
7f1b661e61 New translations 2016-11-17 20:12:18 +01:00
James Cole
2c2a3a5475 New translations 2016-11-17 20:12:08 +01:00
James Cole
1677ca9619 New translations 2016-11-17 20:12:00 +01:00
James Cole
204da3e846 New translations 2016-11-17 20:11:55 +01:00
James Cole
f36d423b1e New translations 2016-11-17 20:11:46 +01:00
James Cole
79c7280046 New translations 2016-11-17 20:11:32 +01:00
James Cole
e10fc4a854 Forgotten a translation. 2016-11-17 20:04:53 +01:00
James Cole
5088df103f Finished category report. 2016-11-17 20:02:55 +01:00
James Cole
13b96f6136 New text for translations. 2016-11-16 20:59:32 +01:00
James Cole
757662ca4b Removed duplicate code. 2016-11-16 20:59:21 +01:00
James Cole
4ef324cf24 Optimized chart code. 2016-11-16 20:35:25 +01:00
James Cole
cb02e0ee71 Merge pull request #404 from JC5/l10n_develop
New Crowdin translations
2016-11-16 18:59:17 +01:00
James Cole
ec3a90688e New translations 2016-11-16 15:01:33 +01:00
James Cole
6dcecdcc64 New translations 2016-11-16 06:40:59 +01:00
James Cole
25d917240d Translated 2016-11-16 06:30:58 +01:00
James Cole
0906915a87 New translations 2016-11-16 06:30:57 +01:00
James Cole
9c92a94177 Translated 2016-11-16 06:20:52 +01:00
James Cole
1b125ecd22 New translations 2016-11-16 04:50:52 +01:00
James Cole
25a2bcd76e New translations 2016-11-16 04:20:53 +01:00
James Cole
b2e09f4240 New translations 2016-11-15 23:11:13 +01:00
James Cole
560165850f New translations 2016-11-15 23:01:17 +01:00
James Cole
0bb07e1eeb Small extension of category report. 2016-11-13 20:18:01 +01:00
James Cole
0c0f2109f6 Fix chart size [skip ci] 2016-11-13 11:31:48 +01:00
James Cole
f546670342 Merge pull request #403 from JC5/l10n_develop
New Crowdin translations
2016-11-12 21:58:23 +01:00
James Cole
eecb6c6679 Optimize again for account name [skip ci] 2016-11-12 21:08:14 +01:00
James Cole
750b9d8038 Reduce number of queries. 2016-11-12 21:06:48 +01:00
James Cole
9ce28fdd2e Remove unused route. [skip ci] 2016-11-12 20:54:05 +01:00
James Cole
07af64ada5 New translations 2016-11-12 20:51:40 +01:00
James Cole
a0ab0ec902 New translations 2016-11-12 20:51:33 +01:00
James Cole
752f8582aa New translations 2016-11-12 20:51:27 +01:00
James Cole
4d0eed8c9b New translations 2016-11-12 20:51:23 +01:00
James Cole
0d7a8305f3 New translations 2016-11-12 20:51:17 +01:00
James Cole
2e8c0ec537 Approved. Step name: Proofread 2016-11-12 20:51:12 +01:00
James Cole
3155ec9e2b New translations 2016-11-12 20:51:01 +01:00
James Cole
7bbca7f6a8 Final code check for something with the debug bar [skip ci] 2016-11-12 20:48:29 +01:00
James Cole
f7579db4ad Clean up config [skip ci] 2016-11-12 20:41:15 +01:00
James Cole
2f47c58df5 New string for translation [skip ci] 2016-11-12 20:30:39 +01:00
James Cole
7e7ac264d2 Fixed category chart 2016-11-12 20:29:16 +01:00
James Cole
98d6c90e90 Removed some duplicate code. 2016-11-12 19:22:03 +01:00
James Cole
da49afa37b Removed duplicate code. 2016-11-12 19:12:16 +01:00
James Cole
64364c3e77 Sending of error email message is optional but enabled. 2016-11-12 19:11:59 +01:00
James Cole
b6f0fd1949 Translated 2016-11-12 19:11:04 +01:00
James Cole
0663a18f3a Translated 2016-11-12 19:11:03 +01:00
James Cole
c5928897eb New translations 2016-11-12 19:01:05 +01:00
James Cole
570373e875 Merge pull request #402 from JC5/l10n_develop
New Crowdin translations
2016-11-12 19:00:18 +01:00
James Cole
228afc2eea New translations 2016-11-12 12:31:45 +01:00
James Cole
6b61621d6a New translations 2016-11-12 12:31:42 +01:00
James Cole
424133fa83 New translations 2016-11-12 12:31:35 +01:00
James Cole
02e30c1fcc New translations 2016-11-12 12:31:28 +01:00
James Cole
e17a9d559b New translations 2016-11-12 12:31:25 +01:00
James Cole
36744377f6 New translations 2016-11-12 12:31:07 +01:00
James Cole
7c479f73c0 New translations 2016-11-12 12:31:00 +01:00
James Cole
85b3c4683b Fix redraw bug in category report. 2016-11-12 12:23:55 +01:00
James Cole
c5d2fabfec Optimize some views for category report 2016-11-12 12:12:11 +01:00
James Cole
a294f757ff Fixes all charts in future category report. 2016-11-12 10:14:20 +01:00
James Cole
04515da0bc Fixed the charts 2016-11-12 07:02:32 +01:00
James Cole
6d60d64a82 Some extended code for the category report. 2016-11-12 06:48:38 +01:00
James Cole
32b5a84a0c Fixes #401 2016-11-12 06:34:54 +01:00
James Cole
4b42ef0db8 Fixes #398 2016-11-12 06:27:48 +01:00
James Cole
abc7b9912d Merge branch 'develop' of https://github.com/JC5/firefly-iii into develop
* 'develop' of https://github.com/JC5/firefly-iii:
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
2016-11-11 20:53:07 +01:00
James Cole
727717931a Do something fancy with colours. 2016-11-11 20:52:48 +01:00
James Cole
1d66b16468 Merge pull request #397 from JC5/l10n_develop
New Crowdin translations
2016-11-11 11:26:59 +01:00
James Cole
b918429c43 New translations 2016-11-10 06:30:56 +01:00
James Cole
888273d4a0 New translations 2016-11-10 06:30:54 +01:00
James Cole
31b5d5ba72 New translations 2016-11-10 06:30:47 +01:00
James Cole
b148d0868e New translations 2016-11-10 06:30:39 +01:00
James Cole
c1491383a8 New translations 2016-11-10 06:30:36 +01:00
James Cole
5f07918682 New translations 2016-11-10 06:30:17 +01:00
James Cole
8de6bd7ceb New translations 2016-11-10 06:30:10 +01:00
James Cole
5d4f1bc76d First working example of category report. No content, just place holders. #396 2016-11-10 06:23:21 +01:00
James Cole
8583b574ac Multi year was not visible. 2016-11-09 21:46:32 +01:00
James Cole
3600e1b5e7 Extend report capability for issue #396 and related report issues. 2016-11-09 21:36:54 +01:00
James Cole
fe57648349 Allow report options to be pulled using AJAX. Ajax is cool. 2016-11-09 19:25:09 +01:00
James Cole
f0e0cdb49b New website 2016-11-09 11:04:14 +01:00
James Cole
cf69333c6d This fixes #395 2016-11-08 21:34:13 +01:00
James Cole
3f7e16d270 Merge pull request #394 from JC5/l10n_develop
New Crowdin translations
2016-11-08 20:50:57 +01:00
James Cole
a63f1638f4 Upgrade some libraries for #391 2016-11-08 20:50:05 +01:00
James Cole
8ec2a3a391 New translations 2016-11-08 20:41:15 +01:00
James Cole
d875f0e580 New translations 2016-11-08 20:41:12 +01:00
James Cole
729534b4f3 New translations 2016-11-08 20:41:06 +01:00
James Cole
bd6a56a55e New translations 2016-11-08 20:41:04 +01:00
James Cole
96976db350 New translations 2016-11-08 20:40:58 +01:00
James Cole
8735190461 New translations 2016-11-08 20:40:55 +01:00
James Cole
709a14e5c9 New translations 2016-11-08 20:40:53 +01:00
James Cole
c89d2a52b5 New translations 2016-11-08 20:40:48 +01:00
James Cole
6084d16ea8 New translations 2016-11-08 20:40:45 +01:00
James Cole
1688fdb786 Approved. Step name: Proofread 2016-11-08 20:40:38 +01:00
James Cole
6cfb5ee2e9 Approved. Step name: Proofread 2016-11-08 20:40:33 +01:00
James Cole
2db560ed7d New translations 2016-11-08 20:40:27 +01:00
James Cole
45567cdf65 New translations 2016-11-08 20:40:24 +01:00
James Cole
508ad5157b New translations 2016-11-08 20:40:21 +01:00
James Cole
8fc41e0226 Fixes #390 2016-11-08 20:37:53 +01:00
James Cole
a08dfe1e3c Add interface for journal collector. 2016-11-08 20:36:09 +01:00
James Cole
49cc8a97a3 Clean up code, fixes #392 2016-11-08 20:35:30 +01:00
James Cole
5b8583dd2b Make sure scripts don't crash when no database present. 2016-11-07 20:25:09 +01:00
James Cole
f653bc5f6e Expand firefly config. 2016-11-07 18:49:35 +01:00
James Cole
a6a9794fc7 Merge branch 'release/4.1.6' into develop 2016-11-06 16:18:20 +01:00
James Cole
fdb8f61e37 Merge branch 'release/4.1.6' 2016-11-06 16:18:19 +01:00
James Cole
69422cc796 Code for 4.1.6 2016-11-06 16:17:22 +01:00
James Cole
5f9a9bc89a Change log for 4.1.6 2016-11-06 16:16:05 +01:00
James Cole
4d0d05e0f8 Merge pull request #383 from JC5/l10n_develop
New Crowdin translations
2016-11-06 16:14:05 +01:00
James Cole
0113fedbd4 Translated 2016-11-06 15:10:25 +01:00
James Cole
a7d35cd1c3 Fix multi year report. [skip ci] 2016-11-06 15:09:44 +01:00
James Cole
43600fe6cb Merge pull request #382 from JC5/l10n_develop
New Crowdin translations
2016-11-06 15:09:23 +01:00
James Cole
0b5e25960f Fix small JS bug. 2016-11-06 15:04:35 +01:00
James Cole
0c8a1b51e9 Quick bug fix: missing class. 2016-11-06 15:01:04 +01:00
James Cole
cb49f5e8d8 New translations 2016-11-06 15:01:02 +01:00
James Cole
a0e3088ca3 New translations 2016-11-06 15:00:54 +01:00
James Cole
b86be6f52f New translations 2016-11-06 15:00:47 +01:00
James Cole
4c573e1300 New translations 2016-11-06 15:00:43 +01:00
James Cole
1a3d77f117 New translations 2016-11-06 15:00:36 +01:00
James Cole
2656da13b1 Approved. Step name: Proofread 2016-11-06 15:00:29 +01:00
James Cole
d272ebd95c New translations 2016-11-06 15:00:18 +01:00
James Cole
7612f1f91a Small changes to twig files. 2016-11-06 14:52:48 +01:00
James Cole
22a2fe3f61 Improved search. 2016-11-06 14:52:31 +01:00
James Cole
1ebb59b352 Remove .twig extension. [skip ci] 2016-11-06 08:11:43 +01:00
James Cole
77e2cf40df Removed more getJournals functions in favour of the collector. 2016-11-06 08:08:06 +01:00
James Cole
0edffd8ea1 Lighter icon [skip ci] 2016-11-05 18:57:45 +01:00
James Cole
ee6e047596 Do not order the count query. 2016-11-05 18:55:09 +01:00
James Cole
bd55636b3f Add repository move info. 2016-11-05 18:51:26 +01:00
James Cole
b24e97a449 Update version and change log. 2016-11-05 18:50:13 +01:00
James Cole
d45355fc3f Merge branch 'develop' of https://github.com/JC5/firefly-iii into develop
* 'develop' of https://github.com/JC5/firefly-iii:
  Translated
2016-11-05 18:46:10 +01:00
James Cole
b2206f640a Merge pull request #381 from JC5/l10n_develop
New Crowdin translations
2016-11-05 18:45:42 +01:00
James Cole
962cad33e2 Code cleanup. 2016-11-05 18:43:18 +01:00
James Cole
d65214b75a Translated 2016-11-05 18:40:12 +01:00
James Cole
7b4c151df5 Merge pull request #380 from JC5/l10n_develop
New Crowdin translations
2016-11-05 18:30:52 +01:00
James Cole
28d6f51961 New translations 2016-11-05 18:10:55 +01:00
James Cole
d2f9deb82b New translations 2016-11-05 18:10:48 +01:00
James Cole
d9b05b5f59 New translations 2016-11-05 18:10:42 +01:00
James Cole
a8f4b33c57 New translations 2016-11-05 18:10:39 +01:00
James Cole
ee849ea12f New translations 2016-11-05 18:10:32 +01:00
James Cole
f9d3cf231f Approved. Step name: Proofread 2016-11-05 18:10:27 +01:00
James Cole
0713ca7709 New translations 2016-11-05 18:10:17 +01:00
James Cole
1e2124c5ed Moved more stuff to the journal collector. 2016-11-05 18:08:44 +01:00
James Cole
37435da459 Moved more stuff to the journal collector. 2016-11-05 17:47:50 +01:00
James Cole
05dbd30bbd Rename another collector. 2016-11-05 17:17:56 +01:00
James Cole
4b947638a7 Merge pull request #379 from JC5/l10n_develop
New Crowdin translations
2016-11-05 15:16:25 +01:00
James Cole
3d113b9aae New translations 2016-11-05 14:30:14 +01:00
James Cole
d1b3681bf3 New translations 2016-11-05 14:20:16 +01:00
James Cole
9dd4b07314 New translations 2016-11-05 14:20:15 +01:00
James Cole
3814f0f3c3 New translations 2016-11-05 14:20:13 +01:00
James Cole
b1e907fae9 New translations 2016-11-05 14:10:15 +01:00
James Cole
5c03a1a9c8 New translations 2016-11-05 14:10:14 +01:00
James Cole
20ac07a386 Translated 2016-11-05 14:10:11 +01:00
James Cole
13e1292bb7 Automated code cleanup [skip ci] 2016-11-05 11:47:21 +01:00
James Cole
8e542531b3 Move collecting journals to the collector. 2016-11-05 11:44:41 +01:00
James Cole
43afdb021a Move collecting journals to the collector. 2016-11-05 11:24:15 +01:00
James Cole
aeca2ef3b2 Move some code around 2016-11-05 10:42:31 +01:00
James Cole
205a593721 Removed unused method. 2016-11-05 10:28:10 +01:00
James Cole
46649fe228 Solved group thing. 2016-11-05 10:26:57 +01:00
James Cole
adb97fcb05 Fix small javascript bug. 2016-11-05 08:47:05 +01:00
James Cole
98160e9b63 Expand use of journal collector. 2016-11-05 08:46:55 +01:00
James Cole
9c5d192d90 Journal collector may not have been a bad idea after all! 2016-11-05 08:27:25 +01:00
James Cole
47bebb614e Only withdrawal can have a budget. 2016-11-05 07:43:23 +01:00
James Cole
5f7fb77db2 Code to fix #378 2016-11-04 16:04:36 +01:00
James Cole
1d15bc0b10 I am changing some string concatenations to sprintf() routines because they are more readable and safer. [skip ci] 2016-11-03 21:54:07 +01:00
James Cole
7bc4c6d115 Update change log [skip ci] 2016-11-03 21:49:08 +01:00
James Cole
45973a53f5 Merge pull request #376 from JC5/l10n_develop
New Crowdin translations
2016-11-03 21:45:55 +01:00
James Cole
8e5e3de8b0 Update change log (prematurely). [skip ci] 2016-11-03 21:45:35 +01:00
James Cole
8738cd4b04 Approved. Step name: Proofread 2016-11-03 21:40:29 +01:00
James Cole
24a7dac235 Approved. Step name: Proofread 2016-11-03 21:40:28 +01:00
James Cole
a3088f6806 Merge branch 'develop' of https://github.com/JC5/firefly-iii into develop
* 'develop' of https://github.com/JC5/firefly-iii:
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  Approved. Step name: Proofread
  Approved. Step name: Proofread
  New translations
  New translations
  New translations
  Translated
2016-11-03 21:07:48 +01:00
James Cole
72f7b5f3ea This fixes #375 2016-11-03 21:07:12 +01:00
James Cole
a636c508a2 Merge pull request #374 from JC5/l10n_develop
New Crowdin translations
2016-11-03 16:42:52 +01:00
James Cole
599db95f73 New translations 2016-11-02 22:31:16 +01:00
James Cole
f5f78ab79b New translations 2016-11-02 22:31:15 +01:00
James Cole
9af9383c29 New translations 2016-11-02 22:31:08 +01:00
James Cole
4b97b86c09 New translations 2016-11-02 22:31:07 +01:00
James Cole
11fb46830c New translations 2016-11-02 22:31:00 +01:00
James Cole
e8dec6d95c New translations 2016-11-02 22:31:00 +01:00
James Cole
bb4ee7470d New translations 2016-11-02 22:30:57 +01:00
James Cole
2e8071db9e New translations 2016-11-02 22:30:54 +01:00
James Cole
4d2901aa02 New translations 2016-11-02 22:30:50 +01:00
James Cole
37bbfab20a Approved. Step name: Proofread 2016-11-02 22:30:46 +01:00
James Cole
fb9161b82d Approved. Step name: Proofread 2016-11-02 22:30:43 +01:00
James Cole
000c9d8974 New translations 2016-11-02 22:30:36 +01:00
James Cole
878b664930 New translations 2016-11-02 22:30:33 +01:00
James Cole
afe28b5581 New translations 2016-11-02 22:30:33 +01:00
James Cole
4106b2e4c0 Remove some help entries in favour of help pages in the top right corner. 2016-11-02 22:23:40 +01:00
James Cole
e1be4909b9 Redirect when 0 accounts.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 21:24:16 +01:00
James Cole
7a0347c0c2 Translated 2016-11-02 21:00:38 +01:00
James Cole
a7e0e3fc15 Small additions and bug fixes.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 20:52:40 +01:00
James Cole
5e480eca36 Clean up some report code.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 20:45:11 +01:00
James Cole
6c8d594df7 Merge branch 'develop' of https://github.com/JC5/firefly-iii into develop
* 'develop' of https://github.com/JC5/firefly-iii:
  New translations
  New translations
  New translations
  New translations
  Approved. Step name: Proofread
  New translations
  New translations

Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 20:08:38 +01:00
James Cole
e24f5ec9f3 Multi year report move to AJAX.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 20:08:11 +01:00
James Cole
1379c0652e Merge pull request #373 from JC5/l10n_develop
New Crowdin translations
2016-11-02 19:50:10 +01:00
James Cole
1f87b0bd2d New translations 2016-11-02 14:41:56 +01:00
James Cole
787a437ca4 New translations 2016-11-02 14:41:44 +01:00
James Cole
c0bdb35cb3 New translations 2016-11-02 14:41:34 +01:00
James Cole
4b9cf67413 New translations 2016-11-02 14:41:26 +01:00
James Cole
86ff3be741 Approved. Step name: Proofread 2016-11-02 14:41:09 +01:00
James Cole
8bc8e8d9fe New translations 2016-11-02 14:40:59 +01:00
James Cole
227a12d75d New translations 2016-11-02 14:40:54 +01:00
James Cole
2ddd4314f1 Extend help pages. 2016-11-02 14:33:57 +01:00
James Cole
b980b5baea Small optimisations.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 07:23:11 +01:00
James Cole
4ba34ab511 Show sum [skip ci]
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 07:16:46 +01:00
James Cole
5be317d73c sprintf ALL THE THINGS
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 07:04:14 +01:00
James Cole
af16205965 Merge branch 'master' into develop
* master:
  New budget table for multi year report.
  Removed everything pointless from multi year report.

Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 07:03:17 +01:00
James Cole
39917b77c1 New GitHub move repository instructions
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 07:02:22 +01:00
James Cole
124ecb1372 New budget table for multi year report.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-02 04:55:44 +01:00
James Cole
33c0c1bea6 Removed everything pointless from multi year report.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-01 19:06:35 +01:00
James Cole
a66990459e Merge branch 'release/4.1.5' 2016-11-01 18:45:25 +01:00
James Cole
fecbdc7fbf New version.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-11-01 18:44:26 +01:00
James Cole
0369ace5f7 Merge branch 'develop' of https://github.com/JC5/firefly-iii into develop
* 'develop' of https://github.com/JC5/firefly-iii: (28 commits)
  Approved. Step name: Proofread
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  New translations
  ...
2016-11-01 18:41:15 +01:00
James Cole
1657048181 Fixed bug #370 2016-11-01 18:40:35 +01:00
James Cole
b9bdaa7a56 Merge pull request #369 from JC5/l10n_develop
New Crowdin translations
2016-11-01 11:43:00 +01:00
James Cole
f28d07e17b Approved. Step name: Proofread 2016-11-01 11:40:36 +01:00
James Cole
8b8bf1debc New translations 2016-10-31 18:41:44 +01:00
James Cole
aff1c1e3ef New translations 2016-10-31 18:41:40 +01:00
James Cole
169bb2c9bb New translations 2016-10-31 18:41:29 +01:00
James Cole
fb1eafef43 New translations 2016-10-31 18:41:26 +01:00
James Cole
bfe26ceb39 New translations 2016-10-31 18:41:25 +01:00
James Cole
050f305e80 New translations 2016-10-31 18:41:24 +01:00
James Cole
63a6a4f823 New translations 2016-10-31 18:41:23 +01:00
James Cole
a3b167cab5 New translations 2016-10-31 18:41:23 +01:00
James Cole
48327948e2 New translations 2016-10-31 18:41:22 +01:00
James Cole
93856d4577 New translations 2016-10-31 18:41:21 +01:00
James Cole
7ff068aa95 New translations 2016-10-31 18:41:20 +01:00
James Cole
b2f00c869e New translations 2016-10-31 18:41:18 +01:00
James Cole
b717cab8f6 New translations 2016-10-31 18:41:17 +01:00
James Cole
adaff52707 New translations 2016-10-31 18:41:16 +01:00
James Cole
54050edcc6 New translations 2016-10-31 18:41:16 +01:00
James Cole
9acbb69a6a New translations 2016-10-31 18:41:15 +01:00
James Cole
a5e6de047a New translations 2016-10-31 18:41:14 +01:00
James Cole
3d8d35207b New translations 2016-10-31 18:41:13 +01:00
James Cole
0a95f59813 New translations 2016-10-31 18:41:10 +01:00
James Cole
43a3d28dbd New translations 2016-10-31 18:41:09 +01:00
James Cole
685cb7a505 Translated 2016-10-31 18:41:06 +01:00
James Cole
dd82466d07 Translated 2016-10-31 18:41:05 +01:00
James Cole
2cbe4a013e Translated 2016-10-31 18:41:04 +01:00
James Cole
fb85341844 Translated 2016-10-31 18:41:03 +01:00
James Cole
116b3ecdad Approved. Step name: Proofread 2016-10-31 18:40:58 +01:00
James Cole
af85fbf0a3 New translations 2016-10-31 18:40:50 +01:00
James Cole
1d250593c0 New translations 2016-10-31 18:40:45 +01:00
James Cole
ed33a054ad This update will make the help method fall back to the English content, if it is available. 2016-10-31 18:31:52 +01:00
James Cole
4e3e015912 Fix multi year account report [skip ci] 2016-10-30 20:13:49 +01:00
James Cole
7821c52842 Ajax some report parts. 2016-10-30 18:29:26 +01:00
James Cole
0a6f299ae6 Merge branch 'release/4.1.4' 2016-10-30 08:56:35 +01:00
James Cole
73f87e30c2 Changelog.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-30 08:52:09 +01:00
James Cole
838ece2c89 Merge pull request #368 from JC5/l10n_develop
New Crowdin translations
2016-10-30 07:32:23 +01:00
James Cole
d8b88ea2c0 New translations 2016-10-30 07:20:17 +01:00
James Cole
5908951b75 New translations 2016-10-30 07:20:17 +01:00
James Cole
0b41f4c4d2 New translations 2016-10-30 07:20:16 +01:00
James Cole
35439d4fbc New translations 2016-10-30 07:20:16 +01:00
James Cole
fdce40310f New translations 2016-10-30 07:20:15 +01:00
James Cole
6b4785ae32 New translations 2016-10-30 07:20:15 +01:00
James Cole
f74e8e9cb7 New translations 2016-10-30 07:20:13 +01:00
James Cole
5a4eb7e09e New translations 2016-10-30 07:20:12 +01:00
James Cole
3bc4df03cc New translations 2016-10-30 07:20:11 +01:00
James Cole
5d585132fb New translations 2016-10-30 07:20:11 +01:00
James Cole
eff4905883 New translations 2016-10-30 07:20:10 +01:00
James Cole
8923ac4fe3 New translations 2016-10-30 07:20:09 +01:00
James Cole
073535e5ed New translations 2016-10-30 07:20:09 +01:00
James Cole
d304b90ca6 New translations 2016-10-30 07:20:08 +01:00
James Cole
816c26e14e New translations 2016-10-30 07:20:07 +01:00
James Cole
b1244ffa01 New translations 2016-10-30 07:20:07 +01:00
James Cole
fc1342bff9 New translations 2016-10-30 07:20:06 +01:00
James Cole
d7b95194b5 New translations 2016-10-30 07:20:04 +01:00
James Cole
b58bdeccd2 New translations 2016-10-30 07:20:04 +01:00
James Cole
f260b9bdee New translations 2016-10-30 07:20:02 +01:00
James Cole
fb1bdc9ec5 New translations 2016-10-30 07:20:01 +01:00
James Cole
697eff48fc New translations 2016-10-30 07:20:00 +01:00
James Cole
c05019339a Approved. Step name: Proofread 2016-10-30 07:20:00 +01:00
James Cole
8438efaf41 Approved. Step name: Proofread 2016-10-30 07:19:59 +01:00
James Cole
81c019cc99 Translated 2016-10-30 07:19:59 +01:00
James Cole
c773fdc435 Approved. Step name: Proofread 2016-10-30 07:19:58 +01:00
James Cole
c1406f51f1 Approved. Step name: Proofread 2016-10-30 07:19:58 +01:00
James Cole
92affd3440 Approved. Step name: Proofread 2016-10-30 07:19:57 +01:00
James Cole
d3da0652ef Approved. Step name: Proofread 2016-10-30 07:19:57 +01:00
James Cole
e3fbbd6cf1 Translated 2016-10-30 07:19:56 +01:00
James Cole
fcff13470c Approved. Step name: Proofread 2016-10-30 07:19:56 +01:00
James Cole
e3061ee7e7 Approved. Step name: Proofread 2016-10-30 07:19:55 +01:00
James Cole
0ee305fc4a Approved. Step name: Proofread 2016-10-30 07:19:55 +01:00
James Cole
58b93fd0c4 Approved. Step name: Proofread 2016-10-30 07:19:53 +01:00
James Cole
b30217fa2d Approved. Step name: Proofread 2016-10-30 07:19:52 +01:00
James Cole
ae48eec3a2 Translated 2016-10-30 07:19:52 +01:00
James Cole
948233ba27 Translated 2016-10-30 07:19:52 +01:00
James Cole
c2db9b183a New translations 2016-10-30 07:19:51 +01:00
James Cole
6d2b88fa0b New translations 2016-10-30 07:19:51 +01:00
James Cole
1d5da825c5 New translations 2016-10-30 07:19:50 +01:00
James Cole
330c9b53d6 New translations 2016-10-30 07:19:50 +01:00
James Cole
751fe7d4fb New translations 2016-10-30 07:19:49 +01:00
James Cole
9df1fc6e5d New translations 2016-10-30 07:19:48 +01:00
James Cole
8d660f1701 New translations 2016-10-30 07:19:46 +01:00
James Cole
4d61d3c4aa New translations 2016-10-30 07:19:46 +01:00
James Cole
0457088c99 New translations 2016-10-30 07:19:45 +01:00
James Cole
8e575da74e New translations 2016-10-30 07:19:45 +01:00
James Cole
48ed28888e Translated 2016-10-30 07:19:43 +01:00
James Cole
4084b1124e Translated 2016-10-30 07:19:42 +01:00
James Cole
60ba607027 Translated 2016-10-30 07:19:42 +01:00
James Cole
3df2c11b4a Translated 2016-10-30 07:19:41 +01:00
James Cole
c93221923a Translated 2016-10-30 07:19:41 +01:00
James Cole
375317e932 New translations 2016-10-30 07:19:40 +01:00
James Cole
7ce527957a New translations 2016-10-30 07:19:40 +01:00
James Cole
6946521199 New translations 2016-10-30 07:19:39 +01:00
James Cole
18ee20e680 Update crowdin file [skip ci]
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-30 06:37:00 +01:00
James Cole
c53da15219 Update composer.lock in anticipation of new release.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-30 06:20:18 +01:00
James Cole
d4995e342f Fixed #330 2016-10-30 06:14:07 +01:00
James Cole
c9f14da294 Translations. 2016-10-29 17:30:55 +02:00
James Cole
e9c2446cba Debug log for #366 2016-10-29 16:16:10 +02:00
James Cole
35f179625c New queries for #366 2016-10-29 16:11:54 +02:00
James Cole
39749aa113 First code set for #330 2016-10-29 15:14:33 +02:00
James Cole
ba65e982fd Forgot to include model [skip ci] 2016-10-29 11:42:04 +02:00
James Cole
b50e5d7e59 Can also change destination in new rule. 2016-10-29 09:22:51 +02:00
James Cole
a3148dc172 Code for #321 2016-10-29 09:03:14 +02:00
James Cole
73f1491d2d Updates for translations. 2016-10-29 07:44:46 +02:00
James Cole
28eb54dc96 Initial split for report options. 2016-10-28 19:01:52 +02:00
James Cole
21fb426524 Make sure date is localised. 2016-10-28 18:16:30 +02:00
James Cole
d5710ca809 Update Crowdin configuration file 2016-10-28 14:06:41 +02:00
James Cole
0ba6cdda17 Merge pull request #364 from schoentoon/develop
Added dockerfile
2016-10-27 19:40:27 +02:00
James Cole
afdcfa8525 Tweak reports. 2016-10-26 19:45:10 +02:00
James Cole
5db4f8512b Tweak reports. 2016-10-26 19:37:19 +02:00
James Cole
dc0c1b73bc Better view for expenses. 2016-10-26 19:32:19 +02:00
James Cole
f999257095 New verify database routine. 2016-10-26 19:32:07 +02:00
James Cole
7182909e28 Keep the box [skip ci] 2016-10-26 16:54:52 +02:00
James Cole
fe3f015171 Add more stuff to ajax controllers, making report controller simpler. 2016-10-26 16:46:43 +02:00
Toon Schoenmakers
5bb668be63 Added dockerfile 2016-10-26 15:07:36 +02:00
James Cole
01de147900 Display message about common error. 2016-10-26 06:41:50 +02:00
James Cole
a7e5fcc806 Move some stuff over to AJAX thing. 2016-10-25 18:53:54 +02:00
James Cole
e2d187d74b Various small bug fixes. 2016-10-24 18:01:15 +02:00
James Cole
48b0620629 New help thing. 2016-10-23 17:33:53 +02:00
James Cole
19e9f382e4 Add some rounding to make forms more neat. 2016-10-23 16:56:18 +02:00
James Cole
446eaf6588 Some code cleanup [skip ci] 2016-10-23 14:58:39 +02:00
James Cole
78deb1420d Some fixes for bills. 2016-10-23 14:56:05 +02:00
James Cole
e092515dff Better export. 2016-10-23 12:55:07 +02:00
James Cole
81f6fef978 Add new line to files [skip ci] 2016-10-23 12:42:44 +02:00
James Cole
6a2f8fa9ee No use models directly. 2016-10-23 12:41:54 +02:00
James Cole
a79a8c8874 Various small upgrades. 2016-10-23 12:37:12 +02:00
James Cole
c39659b064 Remove a lot of references to user id. 2016-10-23 12:19:32 +02:00
James Cole
9a30fbd05a Move stuff to request classes for #339 2016-10-23 12:10:22 +02:00
James Cole
83f48418f6 Small updates [skip ci] 2016-10-23 09:57:04 +02:00
James Cole
bcd7b41c91 Simplified export. 2016-10-23 09:44:14 +02:00
James Cole
cefb7d12bc Merge branch 'release/4.1.3' 2016-10-22 22:45:27 +02:00
James Cole
3c0c15103e This fixes #361 2016-10-22 22:44:57 +02:00
James Cole
a8a8afc2be More for #339 2016-10-22 22:03:00 +02:00
James Cole
49e32abd3f Move some code for #339 2016-10-22 21:40:31 +02:00
James Cole
7977eefaca Merge branch 'release/4.1.2' 2016-10-22 20:52:54 +02:00
James Cole
f1fa6c3108 Fixed a bug in the store transaction routine. 2016-10-22 20:50:20 +02:00
James Cole
2fa0d55f39 Merge branch 'release/4.1.1' 2016-10-22 12:03:34 +02:00
James Cole
5bff509346 New translations.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 12:03:11 +02:00
James Cole
a147e9b74a Fix edit screen.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 11:53:34 +02:00
James Cole
0d87f7c4ca Better implementation of markdown.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 11:50:33 +02:00
James Cole
8c675615df Support markdown in notes.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 11:47:47 +02:00
568 changed files with 27996 additions and 13313 deletions

View File

@@ -35,8 +35,7 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
SEND_REGISTRATION_MAIL=true
MUST_CONFIRM_ACCOUNT=false
SEND_ERROR_MESSAGE=true
SHOW_INCOMPLETE_TRANSLATIONS=false
ANALYTICS_ID=

45
.env.testing Executable file
View File

@@ -0,0 +1,45 @@
APP_ENV=testing
APP_DEBUG=true
APP_FORCE_SSL=false
APP_FORCE_ROOT=
APP_KEY=TestTestTestTestTestTestTestTest
APP_LOG_LEVEL=debug
APP_URL=http://localhost
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=homestead
DB_PASSWORD=secret
BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_FROM=changeme@example.com
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
SHOW_INCOMPLETE_TRANSLATIONS=false
ANALYTICS_ID=
SITE_OWNER=mail@example.com
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ result.html
test-import.sh
test-import-report.txt
public/google*.html
.env.backup

18
.travis.yml Normal file
View File

@@ -0,0 +1,18 @@
language: php
sudo: false
php:
- '7.0'
install:
- phpenv config-rm xdebug.ini
- composer selfupdate
- rm composer.lock
- composer update --no-scripts
- php artisan clear-compiled
- php artisan optimize
- php artisan env
- ./test.sh -r
- php artisan env
script:
- phpunit

View File

@@ -2,7 +2,115 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [4.1] - 2016-10-22
## [4.2.0] - 2016-11-27
### Added
- Lots of (empty) tests
- Expanded transaction lists (#377)
- New charts at account view
- First code for #305
### Changed
- Updated all email messages.
- Made some fonts local
### Deprecated
- Initial release.
### Removed
- Initial release.
### Fixed
- Issue #408
- Various issues with split journals
- Issue #414, thx @zjean
- Issue #419, thx @schwalberich
- Issue #422, thx @xzaz
- Various import bugs, such as #416 (@zjean)
### Security
- Initial release.
## [4.1.7] - 2016-11-19
### Added
- Check for database table presence in console commands.
- Category report
- Reinstated old test routines.
### Changed
- Confirm account setting is no longer in `.env` file.
- Titles are now in reverse (current page > parent > firefly iii)
- Easier update of language files thanks to Github implementation.
- Uniform colours for charts.
### Fixed
- Made all pages more mobile friendly.
- Fixed #395 found by @marcoveeneman.
- Fixed #398 found by @marcoveeneman.
- Fixed #401 found by @marcoveeneman.
- Many optimizations.
- Updated many libraries.
- Various bugs found by myself.
## [4.1.6] - 2016-11-06
### Added
- New budget table for multi year report.
### Changed
- Greatly expanded help pages and their function.
- Built a new transaction collector, which I think was the idea of @roberthorlings originally.
- Rebuilt seach engine.
### Fixed
- #375, thanks to @schoentoon which made it impossible to resurrect currencies.
- #370 thanks to @ksmolder
- #378, thanks to @HomelessAvatar
## [4.1.5] - 2016-11-01
### Changed
- Report parts are loaded using AJAX, making a lot of code more simple.
- Help content will fall back to English.
- Help content is translated through Crowdin.
### Fixed
- Issue #370
## [4.1.4] - 2016-10-30
### Added
- New Dockerfile thanks to @schoentoon
- Added changing the destination account as rule action.
- Added changing the source account as rule action.
- Can convert transactions into different types.
### Changed
- Changed the export routine to be more future-proof.
- Improved help routine.
- Integrated CrowdIn translations.
- Simplified reports
- Change error message to refer to solution.
### Fixed
- #367 thanks to @HungryFeline
- #366 thanks to @3mz3t
- #362 and #341 thanks to @bnw
- #355 thanks to @roberthorlings
## [4.1.3] - 2016-10-22
### Fixed
- Some event handlers called the wrong method.
## [4.1.2] - 2016-10-22
### Fixed
- A bug is fixed in the journal event handler that prevented Firefly III from actually storing journals.
## [4.1.1] - 2016-10-22
### Added
- Option to show deposit accounts on the front page.
- Script to upgrade split transactions

42
Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
FROM php:7-apache
RUN apt-get update -y && \
apt-get install -y --no-install-recommends libcurl4-openssl-dev \
zlib1g-dev \
libjpeg62-turbo-dev \
libpng12-dev \
libicu-dev \
libmcrypt-dev \
libedit-dev \
libtidy-dev \
libxml2-dev \
libsqlite3-dev \
libbz2-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2
# Enable apache mod rewrite..
RUN a2enmod rewrite
# Setup the Composer installer
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer && \
curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig && \
php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" && \
chmod +x /tmp/composer-setup.php && \
php /tmp/composer-setup.php && \
mv composer.phar /usr/local/bin/composer && \
rm -f /tmp/composer-setup.{php,sig}
ADD . /var/www/firefly-iii
RUN chown -R www-data:www-data /var/www/
ADD docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
USER www-data
WORKDIR /var/www/firefly-iii
RUN composer install --no-scripts --no-dev
USER root

View File

@@ -1,5 +1,7 @@
# Firefly III [![Requires PHP7](https://img.shields.io/badge/php-7.0-red.svg)](https://secure.php.net/downloads.php#v7.0.4) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/JC5/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/JC5/firefly-iii/?branch=master)
[![Build Status](https://travis-ci.org/JC5/firefly-iii.svg?branch=master)](https://travis-ci.org/JC5/firefly-iii)
## A personal finances manager
[![Screenshot](https://i.nder.be/hhfv03hp/400)](https://i.nder.be/hhfv03hp) [![Screenshot](https://i.nder.be/hhmwmqw9/400)](https://i.nder.be/hhmwmqw9)
@@ -10,7 +12,7 @@
## Installation
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://jc5.github.io/firefly-iii/installation-guide/).
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/installation-guide/).
## More about Firefly III
@@ -25,6 +27,6 @@ Firefly works on the principle that if you know where you're money is going, you
- Firefly has lots of features without becoming fancy or bloated.
- If you feel you're missing something you can just ask me and I'll add it!
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://jc5.github.io/firefly-iii/).
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).

View File

@@ -50,9 +50,7 @@ class CreateImport extends Command
}
/**
* Execute the console command.
*
* @return mixed
*
*/
public function handle()
{

View File

@@ -51,9 +51,7 @@ class Import extends Command
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
@@ -66,7 +64,7 @@ class Import extends Command
return;
}
$this->line('Going to import job with key "' . $job->key . '" of type ' . $job->file_type);
$this->line(sprintf('Going to import job with key "%s" of type "%s"', $job->key, $job->file_type));
$monolog = Log::getMonolog();
$handler = new CommandHandler($this);

View File

@@ -0,0 +1,86 @@
<?php
/**
* MoveRepository.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
/**
* Class MoveRepository
*
* @package FireflyIII\Console\Commands
*/
class MoveRepository extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'Alerts the user that the Github repository will move, if they are interested to know this.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly:github-move';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*/
public function handle()
{
$moveDate = new Carbon('2017-01-01');
$final = new Carbon('2017-03-01');
$now = new Carbon;
// display message before 2017-01-01
if ($moveDate > $now) {
$this->line('+------------------------------------------------------------------------------+');
$this->line('');
$this->line('The Github repository for Firefly III will MOVE');
$this->line('This move will be on January 1st 2017');
$this->line('');
$this->error('READ THIS WIKI PAGE FOR MORE INFORMATION');
$this->line('');
$this->info('https://github.com/firefly-iii/help/wiki/New-Github-repository');
$this->line('');
$this->line('+------------------------------------------------------------------------------+');
}
// display message after 2017-01-01 but before 2017-03-01
if ($moveDate <= $now && $now <= $final) {
$this->line('+------------------------------------------------------------------------------+');
$this->line('');
$this->line('The Github repository for Firefly III has MOVED');
$this->line('This move was on January 1st 2017!');
$this->line('');
$this->error('READ THIS WIKI PAGE FOR MORE INFORMATION');
$this->line('');
$this->info('https://github.com/firefly-iii/help/wiki/New-Github-repository');
$this->line('');
$this->line('+------------------------------------------------------------------------------+');
}
}
}

View File

@@ -20,6 +20,7 @@ use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Log;
use Schema;
/**
* Class UpgradeDatabase
@@ -65,6 +66,12 @@ class UpgradeDatabase extends Command
*/
private function setTransactionIdentifier()
{
// if table does not exist, return false
if (!Schema::hasTable('transaction_journals')) {
return;
}
$subQuery = TransactionJournal
::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereNull('transaction_journals.deleted_at')
@@ -99,7 +106,7 @@ class UpgradeDatabase extends Command
} catch (QueryException $e) {
Log::error($e->getMessage());
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
$this->error('This field is required for Firefly III version ' . config('firefly.version') . ' to run.');
$this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
$this->error('Please run "php artisan migrate" to add this field to the table.');
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
break 2;
@@ -118,4 +125,4 @@ class UpgradeDatabase extends Command
}
}
}
}
}

View File

@@ -63,21 +63,20 @@ class UpgradeFireflyInstructions extends Command
}
$this->line('+------------------------------------------------------------------------------+');
$this->line('');
if (is_null($text)) {
$this->line('Thank you for installing Firefly III, v' . $version);
$this->line(sprintf('Thank you for installing Firefly III, v%s', $version));
$this->info('There are no extra upgrade instructions.');
$this->line('Firefly III should be ready for use.');
} else {
$this->line('Thank you for installing Firefly III, v' . $version);
$this->line('If you are upgrading from a previous version,');
$this->line('please follow these upgrade instructions carefully:');
$this->line('+------------------------------------------------------------------------------+');
$this->line('');
$this->line(sprintf('Thank you for installing Firefly III, v%s', $version));
$this->info(wordwrap($text));
$this->line('');
$this->line('+------------------------------------------------------------------------------+');
}
$this->line('');
$this->line('+------------------------------------------------------------------------------+');
}
}

View File

@@ -15,6 +15,7 @@ namespace FireflyIII\Console\Commands;
use Crypt;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
@@ -24,7 +25,9 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\Builder;
use Schema;
use stdClass;
/**
@@ -60,16 +63,21 @@ class VerifyDatabase extends Command
*/
public function handle()
{
// if table does not exist, return false
if (!Schema::hasTable('users')) {
return;
}
$this->reportObject('budget');
$this->reportObject('category');
$this->reportObject('tag');
// accounts with no transactions.
$this->reportAccounts();
// budgets with no limits
$this->reportBudgetLimits();
// budgets with no transactions
$this->reportBudgets();
// categories with no transactions
$this->reportCategories();
// tags with no transactions
$this->reportTags();
// sum of transactions is not zero.
$this->reportSum();
// any deleted transaction journals that have transactions that are NOT deleted:
@@ -84,6 +92,9 @@ class VerifyDatabase extends Command
// transfers with budgets.
$this->reportTransfersBudgets();
// report on journals with the wrong types of accounts.
$this->reportIncorrectJournals();
}
/**
@@ -123,50 +134,11 @@ class VerifyDatabase extends Command
/** @var stdClass $entry */
foreach ($set as $entry) {
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has budget #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
. '") which has no budget limits.';
$this->line($line);
}
}
/**
* Reports on budgets without any transactions.
*/
private function reportBudgets()
{
$set = Budget
::leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
->distinct()
->whereNull('budget_transaction_journal.budget_id')
->whereNull('budgets.deleted_at')
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']);
/** @var stdClass $entry */
foreach ($set as $entry) {
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has budget #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
. '") which has no transactions.';
$this->line($line);
}
}
/**
* Reports on categories without any transactions.
*/
private function reportCategories()
{
$set = Category
::leftJoin('category_transaction_journal', 'categories.id', '=', 'category_transaction_journal.category_id')
->leftJoin('users', 'categories.user_id', '=', 'users.id')
->distinct()
->whereNull('category_transaction_journal.category_id')
->whereNull('categories.deleted_at')
->get(['categories.id', 'categories.name', 'categories.user_id', 'users.email']);
/** @var stdClass $entry */
foreach ($set as $entry) {
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has category #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
. '") which has no transactions.';
$line = sprintf(
'Notice: User #%d (%s) has budget #%d ("%s") which has no budget limits.',
$entry->user_id, $entry->email, $entry->id, Crypt::decrypt($entry->name)
);
$this->line($line);
}
}
@@ -202,6 +174,45 @@ class VerifyDatabase extends Command
}
}
private function reportIncorrectJournals()
{
$configuration = [
// a withdrawal can not have revenue account:
TransactionType::WITHDRAWAL => [AccountType::REVENUE],
// deposit cannot have an expense account:
TransactionType::DEPOSIT => [AccountType::EXPENSE],
// transfer cannot have either:
TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE],
];
foreach ($configuration as $transactionType => $accountTypes) {
$set = TransactionJournal
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->leftJoin('users', 'users.id', '=', 'transaction_journals.user_id')
->where('transaction_types.type', $transactionType)
->whereIn('account_types.type', $accountTypes)
->whereNull('transaction_journals.deleted_at')
->get(['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type', 'transaction_types.type']);
foreach ($set as $entry) {
$this->error(
sprintf(
'Transaction journal #%d (user #%d, %s) is of type "%s" but ' .
'is linked to a "%s". The transaction journal should be recreated.',
$entry->id,
$entry->user_id,
$entry->email,
$entry->type,
$entry->a_type
)
);
}
}
}
/**
* Any deleted transaction journals that have transactions that are NOT deleted:
*/
@@ -248,6 +259,39 @@ class VerifyDatabase extends Command
}
/**
* @param string $name
*/
private function reportObject(string $name)
{
$plural = str_plural($name);
$class = sprintf('FireflyIII\Models\%s', ucfirst($name));
$field = $name == 'tag' ? 'tag' : 'name';
$set = $class::leftJoin($name . '_transaction_journal', $plural . '.id', '=', $name . '_transaction_journal.' . $name . '_id')
->leftJoin('users', $plural . '.user_id', '=', 'users.id')
->distinct()
->whereNull($name . '_transaction_journal.' . $name . '_id')
->whereNull($plural . '.deleted_at')
->get([$plural . '.id', $plural . '.' . $field . ' as name', $plural . '.user_id', 'users.email']);
/** @var stdClass $entry */
foreach ($set as $entry) {
$objName = $entry->name;
try {
$objName = Crypt::decrypt($objName);
} catch (DecryptException $e) {
// it probably was not encrypted.
}
$line = sprintf(
'Notice: User #%d (%s) has %s #%d ("%s") which has no transactions.',
$entry->user_id, $entry->email, $name, $entry->id, $objName
);
$this->line($line);
}
}
/**
* Reports for each user when the sum of their transactions is not zero.
*/
@@ -265,34 +309,12 @@ class VerifyDatabase extends Command
}
}
/**
* Reports on tags without any transactions.
*/
private function reportTags()
{
$set = Tag
::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
->leftJoin('users', 'tags.user_id', '=', 'users.id')
->distinct()
->whereNull('tag_transaction_journal.tag_id')
->whereNull('tags.deleted_at')
->get(['tags.id', 'tags.tag', 'tags.user_id', 'users.email']);
/** @var stdClass $entry */
foreach ($set as $entry) {
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has tag #' . $entry->id . ' ("' . $entry->tag
. '") which has no transactions.';
$this->line($line);
}
}
/**
* Reports on deleted transactions that are connected to a not deleted journal.
*/
private function reportTransactions()
{
$set = Transaction
::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
$set = Transaction::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereNotNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->get(

View File

@@ -16,6 +16,7 @@ namespace FireflyIII\Console;
use FireflyIII\Console\Commands\CreateImport;
use FireflyIII\Console\Commands\EncryptFile;
use FireflyIII\Console\Commands\Import;
use FireflyIII\Console\Commands\MoveRepository;
use FireflyIII\Console\Commands\ScanAttachments;
use FireflyIII\Console\Commands\UpgradeDatabase;
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
@@ -63,7 +64,7 @@ class Kernel extends ConsoleKernel
EncryptFile::class,
ScanAttachments::class,
UpgradeDatabase::class,
MoveRepository::class,
];
/**

View File

@@ -29,7 +29,7 @@ class ConfirmedUser extends Event
public $user;
/**
* Create a new event instance.
* Create a new event instance. This event is triggered when a user confirms their new account.
*
* @param User $user
* @param string $ipAddress

View File

@@ -29,7 +29,7 @@ class RegisteredUser extends Event
public $user;
/**
* Create a new event instance.
* Create a new event instance. This event is triggered when a new user registers.
*
* @param User $user
* @param string $ipAddress

View File

@@ -0,0 +1,46 @@
<?php
/**
* RequestedNewPassword.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class RequestedNewPassword
*
* @package FireflyIII\Events
*/
class RequestedNewPassword extends Event
{
use SerializesModels;
public $ipAddress;
public $token;
public $user;
/**
* Create a new event instance. This event is triggered when a users tries to reset his or her password.
*
* @param User $user
* @param string $token
* @param string $ipAddress
*/
public function __construct(User $user, string $token, string $ipAddress)
{
$this->user = $user;
$this->token = $token;
$this->ipAddress = $ipAddress;
}
}

View File

@@ -29,7 +29,7 @@ class ResentConfirmation extends Event
public $user;
/**
* Create a new event instance.
* Create a new event instance. This event is triggered when a users wants a new confirmation.
*
* @param User $user
* @param string $ipAddress

View File

@@ -77,7 +77,8 @@ class Handler extends ExceptionHandler
*/
public function report(Exception $exception)
{
if ($exception instanceof FireflyException || $exception instanceof ErrorException) {
$doMailError = env('SEND_ERROR_MESSAGE', true);
if (($exception instanceof FireflyException || $exception instanceof ErrorException) && $doMailError) {
$userData = [
'id' => 0,
'email' => 'unknown@example.com',

View File

@@ -13,11 +13,10 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Collector;
use Amount;
use Carbon\Carbon;
use Crypt;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Collection;
@@ -31,12 +30,14 @@ use Storage;
*/
class AttachmentCollector extends BasicCollector implements CollectorInterface
{
/** @var string */
private $explanationString = '';
/** @var Carbon */
private $end;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $exportDisk;
/** @var AttachmentRepositoryInterface */
private $repository;
/** @var Carbon */
private $start;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $uploadDisk;
@@ -69,34 +70,17 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
$this->exportAttachment($attachment);
}
// put the explanation string in a file and attach it as well.
$file = $this->job->key . '-Source of all your attachments explained.txt';
$this->exportDisk->put($file, $this->explanationString);
$this->getFiles()->push($file);
return true;
}
/**
* @param Attachment $attachment
* @param Carbon $start
* @param Carbon $end
*/
private function explain(Attachment $attachment)
public function setDates(Carbon $start, Carbon $end)
{
/** @var TransactionJournal $journal */
$journal = $attachment->attachable;
$args = [
'attachment_name' => e($attachment->filename),
'attachment_id' => $attachment->id,
'type' => strtolower($journal->transactionType->type),
'description' => e($journal->description),
'journal_id' => $journal->id,
'date' => $journal->date->formatLocalized(strval(trans('config.month_and_day'))),
'amount' => Amount::formatJournal($journal, false),
];
$string = trans('firefly.attachment_explanation', $args) . "\n";
Log::debug('Appended explanation string', ['string' => $string]);
$this->explanationString .= $string;
$this->start = $start;
$this->end = $end;
}
/**
@@ -112,10 +96,8 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
$decrypted = Crypt::decrypt($this->uploadDisk->get($file));
$exportFile = $this->exportFileName($attachment);
$this->exportDisk->put($exportFile, $decrypted);
$this->getFiles()->push($exportFile);
$this->getEntries()->push($exportFile);
// explain:
$this->explain($attachment);
} catch (DecryptException $e) {
Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage());
}
@@ -143,7 +125,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
*/
private function getAttachments(): Collection
{
$attachments = $this->repository->get();
$attachments = $this->repository->getBetween($this->start, $this->end);
return $attachments;
}

View File

@@ -27,7 +27,7 @@ class BasicCollector
/** @var ExportJob */
protected $job;
/** @var Collection */
private $files;
private $entries;
/**
* BasicCollector constructor.
@@ -36,24 +36,24 @@ class BasicCollector
*/
public function __construct(ExportJob $job)
{
$this->files = new Collection;
$this->job = $job;
$this->entries = new Collection;
$this->job = $job;
}
/**
* @return Collection
*/
public function getFiles(): Collection
public function getEntries(): Collection
{
return $this->files;
return $this->entries;
}
/**
* @param Collection $files
* @param Collection $entries
*/
public function setFiles(Collection $files)
public function setEntries(Collection $entries)
{
$this->files = $files;
$this->entries = $entries;
}

View File

@@ -25,7 +25,7 @@ interface CollectorInterface
/**
* @return Collection
*/
public function getFiles(): Collection;
public function getEntries(): Collection;
/**
* @return bool
@@ -33,9 +33,9 @@ interface CollectorInterface
public function run(): bool;
/**
* @param Collection $files
* @param Collection $entries
*
*/
public function setFiles(Collection $files);
public function setEntries(Collection $entries);
}

View File

@@ -0,0 +1,348 @@
<?php
/**
* JournalExportCollector.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Export\Collector;
use Carbon\Carbon;
use Crypt;
use DB;
use FireflyIII\Models\Transaction;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
/**
* Class JournalExportCollector
*
* @package FireflyIII\Export\Collector
*/
class JournalExportCollector extends BasicCollector implements CollectorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/** @var Collection */
private $workSet;
/**
* @return bool
*/
public function run(): bool
{
/*
* Instead of collecting journals we collect transactions for the given accounts.
* We left join the OPPOSING transaction AND some journal data.
* After that we complement this info with budgets, categories, etc.
*
* This is way more efficient and will also work on split journals.
*/
$this->getWorkSet();
/*
* Extract:
* possible budget ids for journals
* possible category ids journals
* possible budget ids for transactions
* possible category ids for transactions
*
* possible IBAN and account numbers?
*
*/
$journals = $this->extractJournalIds();
$transactions = $this->extractTransactionIds();
// extend work set with category data from journals:
$this->categoryDataForJournals($journals);
// extend work set with category cate from transactions (overrules journals):
$this->categoryDataForTransactions($transactions);
// same for budgets:
$this->budgetDataForJournals($journals);
$this->budgetDataForTransactions($transactions);
$this->setEntries($this->workSet);
return true;
}
/**
* @param Collection $accounts
*/
public function setAccounts(Collection $accounts)
{
$this->accounts = $accounts;
}
/**
* @param Carbon $start
* @param Carbon $end
*/
public function setDates(Carbon $start, Carbon $end)
{
$this->start = $start;
$this->end = $end;
}
/**
* @param array $journals
*
* @return bool
*/
private function budgetDataForJournals(array $journals): bool
{
$set = DB::table('budget_transaction_journal')
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
->whereIn('budget_transaction_journal.transaction_journal_id', $journals)
->get(
[
'budget_transaction_journal.budget_id',
'budget_transaction_journal.transaction_journal_id',
'budgets.name',
'budgets.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_journal_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
if (isset($array[$obj->transaction_journal_id])) {
$obj->budget_id = $array[$obj->transaction_journal_id]['id'];
$obj->budget_name = $array[$obj->transaction_journal_id]['name'];
}
}
);
return true;
}
/**
* @param array $transactions
*
* @return bool
*/
private function budgetDataForTransactions(array $transactions): bool
{
$set = DB::table('budget_transaction')
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction.budget_id')
->whereIn('budget_transaction.transaction_id', $transactions)
->get(
[
'budget_transaction.budget_id',
'budget_transaction.transaction_id',
'budgets.name',
'budgets.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
// first transaction
if (isset($array[$obj->id])) {
$obj->budget_id = $array[$obj->id]['id'];
$obj->budget_name = $array[$obj->id]['name'];
}
}
);
return true;
}
/**
* @param array $journals
*
* @return bool
*/
private function categoryDataForJournals(array $journals): bool
{
$set = DB::table('category_transaction_journal')
->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id')
->whereIn('category_transaction_journal.transaction_journal_id', $journals)
->get(
[
'category_transaction_journal.category_id',
'category_transaction_journal.transaction_journal_id',
'categories.name',
'categories.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_journal_id] = ['id' => $obj->category_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
if (isset($array[$obj->transaction_journal_id])) {
$obj->category_id = $array[$obj->transaction_journal_id]['id'];
$obj->category_name = $array[$obj->transaction_journal_id]['name'];
}
}
);
return true;
}
/**
* @param array $transactions
*
* @return bool
*/
private function categoryDataForTransactions(array $transactions): bool
{
$set = DB::table('category_transaction')
->leftJoin('categories', 'categories.id', '=', 'category_transaction.category_id')
->whereIn('category_transaction.transaction_id', $transactions)
->get(
[
'category_transaction.category_id',
'category_transaction.transaction_id',
'categories.name',
'categories.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_id] = ['id' => $obj->category_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
// first transaction
if (isset($array[$obj->id])) {
$obj->category_id = $array[$obj->id]['id'];
$obj->category_name = $array[$obj->id]['name'];
}
}
);
return true;
}
/**
* @return array
*/
private function extractJournalIds(): array
{
return $this->workSet->pluck('transaction_journal_id')->toArray();
}
/**
* @return array
*/
private function extractTransactionIds()
{
$set = $this->workSet->pluck('id')->toArray();
$opposing = $this->workSet->pluck('opposing_id')->toArray();
$complete = $set + $opposing;
return array_unique($complete);
}
/**
*
*/
private function getWorkSet()
{
$accountIds = $this->accounts->pluck('id')->toArray();
$this->workSet = Transaction
::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin(
'transactions AS opposing', function (JoinClause $join) {
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
->where('transactions.identifier', '=', DB::raw('opposing.identifier'));
}
)
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id')
->whereIn('transactions.account_id', $accountIds)
->where('transaction_journals.user_id', $this->job->user_id)
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $this->end->format('Y-m-d'))
->where('transaction_journals.completed', 1)
->whereNull('transaction_journals.deleted_at')
->whereNull('transactions.deleted_at')
->whereNull('opposing.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transactions.identifier', 'ASC')
->get(
[
'transactions.id',
'transactions.amount',
'transactions.description',
'transactions.account_id',
'accounts.name as account_name',
'accounts.encrypted as account_name_encrypted',
'transactions.identifier',
'opposing.id as opposing_id',
'opposing.amount AS opposing_amount',
'opposing.description as opposing_description',
'opposing.account_id as opposing_account_id',
'opposing_accounts.name as opposing_account_name',
'opposing_accounts.encrypted as opposing_account_encrypted',
'opposing.identifier as opposing_identifier',
'transaction_journals.id as transaction_journal_id',
'transaction_journals.date',
'transaction_journals.description as journal_description',
'transaction_journals.encrypted as journal_encrypted',
'transaction_journals.transaction_type_id',
'transaction_types.type as transaction_type',
'transaction_journals.transaction_currency_id',
'transaction_currencies.code AS transaction_currency_code',
]
);
}
}

View File

@@ -26,16 +26,14 @@ use Storage;
*/
class UploadCollector extends BasicCollector implements CollectorInterface
{
/** @var string */
private $expected;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $exportDisk;
private $importKeys = [];
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $uploadDisk;
/** @var string */
private $vintageFormat;
/**
*
* AttachmentCollector constructor.
*
* @param ExportJob $job
@@ -51,50 +49,74 @@ class UploadCollector extends BasicCollector implements CollectorInterface
$this->exportDisk = Storage::disk('export');
// file names associated with the old import routine.
$this->expected = 'csv-upload-' . auth()->user()->id . '-';
$this->vintageFormat = sprintf('csv-upload-%d-', auth()->user()->id);
// for the new import routine:
$this->getImportKeys();
}
/**
* Is called from the outside to actually start the export.
*
* @return bool
*/
public function run(): bool
{
// grab upload directory.
$files = $this->uploadDisk->files();
// collect old upload files (names beginning with "csv-upload".
$this->collectVintageUploads();
foreach ($files as $entry) {
$this->processUpload($entry);
// then collect current upload files:
$this->collectModernUploads();
return true;
}
/**
* This method collects all the uploads that are uploaded using the new importer. So after the summer of 2016.
*
* @return bool
*/
private function collectModernUploads(): bool
{
$set = $this->job->user->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
$keys = [];
if ($set->count() > 0) {
$keys = $set->pluck('key')->toArray();
}
foreach ($keys as $key) {
$this->processModernUpload($key);
}
return true;
}
/**
* This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016.
*
* @return bool
*/
private function getImportKeys()
private function collectVintageUploads():bool
{
$set = auth()->user()->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
if ($set->count() > 0) {
$keys = $set->pluck('key')->toArray();
$this->importKeys = $keys;
// grab upload directory.
$files = $this->uploadDisk->files();
foreach ($files as $entry) {
$this->processVintageUpload($entry);
}
Log::debug('Valid import keys are ', $this->importKeys);
return true;
}
/**
* This method tells you when the vintage upload file was actually uploaded.
*
* @param string $entry
*
* @return string
*/
private function getOriginalUploadDate(string $entry): string
private function getVintageUploadDate(string $entry): string
{
// this is an original upload.
$parts = explode('-', str_replace(['.csv.encrypted', $this->expected], '', $entry));
$parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry));
$originalUpload = intval($parts[1]);
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
@@ -102,33 +124,17 @@ class UploadCollector extends BasicCollector implements CollectorInterface
}
/**
* Tells you if a file name is a vintage upload.
*
* @param string $entry
*
* @return bool
*/
private function isImportFile(string $entry): bool
private function isVintageImport(string $entry): bool
{
$name = str_replace('.upload', '', $entry);
if (in_array($name, $this->importKeys)) {
Log::debug(sprintf('Import file "%s" is in array', $name), $this->importKeys);
return true;
}
Log::debug(sprintf('Import file "%s" is NOT in array', $name), $this->importKeys);
return false;
}
/**
* @param string $entry
*
* @return bool
*/
private function isOldImport(string $entry): bool
{
$len = strlen($this->expected);
$len = strlen($this->vintageFormat);
// file is part of the old import routine:
if (substr($entry, 0, $len) === $this->expected) {
if (substr($entry, 0, $len) === $this->vintageFormat) {
return true;
}
@@ -137,49 +143,62 @@ class UploadCollector extends BasicCollector implements CollectorInterface
}
/**
* @param $entry
* @param string $key
*
* @return bool
*/
private function processUpload(string $entry)
{
// file is old import:
if ($this->isOldImport($entry)) {
$this->saveOldImportFile($entry);
}
// file is current import.
if ($this->isImportFile($entry)) {
$this->saveImportFile($entry);
}
}
/**
* @param string $entry
*/
private function saveImportFile(string $entry)
private function processModernUpload(string $key): bool
{
// find job associated with import file:
$name = str_replace('.upload', '', $entry);
$job = auth()->user()->importJobs()->where('key', $name)->first();
$content = '';
try {
$content = Crypt::decrypt($this->uploadDisk->get($entry));
} catch (DecryptException $e) {
Log::error('Could not decrypt old import file ' . $entry . '. Skipped because ' . $e->getMessage());
$job = $this->job->user->importJobs()->where('key', $key)->first();
if (is_null($job)) {
return false;
}
if (!is_null($job) && strlen($content) > 0) {
// find the file for this import:
$content = '';
try {
$content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key)));
} catch (DecryptException $e) {
Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage()));
}
if (strlen($content) > 0) {
// add to export disk.
$date = $job->created_at->format('Y-m-d');
$file = sprintf('%s-Old %s import dated %s.%s', $this->job->key, strtoupper($job->file_type), $date, $job->file_type);
$this->exportDisk->put($file, $content);
$this->getFiles()->push($file);
$this->getEntries()->push($file);
}
return true;
}
/**
* If the file is a vintage upload, process it.
*
* @param string $entry
*
* @return bool
*/
private function processVintageUpload(string $entry): bool
{
if ($this->isVintageImport($entry)) {
$this->saveVintageImportFile($entry);
return true;
}
return false;
}
/**
* This will store the content of the old vintage upload somewhere.
*
* @param string $entry
*/
private function saveOldImportFile(string $entry)
private function saveVintageImportFile(string $entry)
{
$content = '';
try {
@@ -190,10 +209,10 @@ class UploadCollector extends BasicCollector implements CollectorInterface
if (strlen($content) > 0) {
// add to export disk.
$date = $this->getOriginalUploadDate($entry);
$date = $this->getVintageUploadDate($entry);
$file = $this->job->key . '-Old import dated ' . $date . '.csv';
$this->exportDisk->put($file, $content);
$this->getFiles()->push($file);
$this->getEntries()->push($file);
}
}

View File

@@ -1,68 +0,0 @@
<?php
/**
* ConfigurationFile.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Export;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Models\ExportJob;
use Storage;
/**
* Class ConfigurationFile
*
* @package FireflyIII\Export
*/
class ConfigurationFile
{
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $exportDisk;
/** @var ExportJob */
private $job;
/**
* ConfigurationFile constructor.
*
* @param ExportJob $job
*/
public function __construct(ExportJob $job)
{
$this->job = $job;
$this->exportDisk = Storage::disk('export');
}
/**
* @return string
*/
public function make(): string
{
$fields = array_keys(Entry::getFieldsAndTypes());
$types = Entry::getFieldsAndTypes();
$configuration = [
'date-format' => 'Y-m-d', // unfortunately, this is hard-coded.
'has-headers' => true,
'map' => [], // we could build a map if necessary for easy re-import.
'roles' => [],
'mapped' => [],
'specifix' => [],
];
foreach ($fields as $field) {
$configuration['roles'][] = $types[$field];
}
$file = $this->job->key . '-configuration.json';
$this->exportDisk->put($file, json_encode($configuration, JSON_PRETTY_PRINT));
return $file;
}
}

View File

@@ -13,8 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Entry;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Collection;
use Crypt;
/**
* To extend the exported object, in case of new features in Firefly III for example,
@@ -35,98 +34,77 @@ use Illuminate\Support\Collection;
*/
final class Entry
{
/** @var string */
public $amount;
/** @var EntryBill */
public $bill;
/** @var EntryBudget */
public $budget;
/** @var EntryCategory */
public $category;
/** @var string */
// @formatter:off
public $journal_id;
public $date;
/** @var string */
public $description;
/** @var EntryAccount */
public $destinationAccount;
/** @var Collection */
public $destinationAccounts;
/** @var EntryAccount */
public $sourceAccount;
/** @var Collection */
public $sourceAccounts;
public $currency_code;
public $amount;
public $transaction_type;
public $source_account_id;
public $source_account_name;
public $destination_account_id;
public $destination_account_name;
public $budget_id;
public $budget_name;
public $category_id;
public $category_name;
// @formatter:on
/**
* Entry constructor.
*/
private function __construct()
{
$this->sourceAccounts = new Collection;
$this->destinationAccounts = new Collection;
}
/**
* @param TransactionJournal $journal
* @param $object
*
* @return Entry
*/
public static function fromJournal(TransactionJournal $journal)
public static function fromObject($object): Entry
{
$entry = new self;
$entry = new self;
$entry->description = $journal->description;
$entry->date = $journal->date->format('Y-m-d');
$entry->amount = TransactionJournal::amount($journal);
// journal information:
$entry->journal_id = $object->transaction_journal_id;
$entry->description = $object->journal_encrypted === 1 ? Crypt::decrypt($object->journal_description) : $object->journal_description;
$entry->amount = round($object->amount, 2); // always positive
$entry->date = $object->date;
$entry->transaction_type = $object->transaction_type;
$entry->currency_code = $object->transaction_currency_code;
$entry->budget = new EntryBudget($journal->budgets->first());
$entry->category = new EntryCategory($journal->categories->first());
$entry->bill = new EntryBill($journal->bill);
// source information:
$entry->source_account_id = $object->account_id;
$entry->source_account_name = $object->account_name_encrypted === 1 ? Crypt::decrypt($object->account_name) : $object->account_name;
$sources = TransactionJournal::sourceAccountList($journal);
$destinations = TransactionJournal::destinationAccountList($journal);
$entry->sourceAccount = new EntryAccount($sources->first());
$entry->destinationAccount = new EntryAccount($destinations->first());
foreach ($sources as $source) {
$entry->sourceAccounts->push(new EntryAccount($source));
}
// destination information
$entry->destination_account_id = $object->opposing_account_id;
$entry->destination_account_name = $object->opposing_account_encrypted === 1 ? Crypt::decrypt($object->opposing_account_name)
: $object->opposing_account_name;
foreach ($destinations as $destination) {
$entry->destinationAccounts->push(new EntryAccount($destination));
// category and budget
$entry->category_id = $object->category_id ?? '';
$entry->category_name = $object->category_name ?? '';
$entry->budget_id = $object->budget_id ?? '';
$entry->budget_name = $object->budget_name ?? '';
// update description when transaction description is different:
if (!is_null($object->description) && $object->description != $entry->description) {
$entry->description = $entry->description . ' (' . $object->description . ')';
}
return $entry;
}
/**
* @return array
*/
public static function getFieldsAndTypes(): array
{
// key = field name (see top of class)
// value = field type (see csv.php under 'roles')
return [
'description' => 'description',
'amount' => 'amount',
'date' => 'date-transaction',
'source_account_id' => 'account-id',
'source_account_name' => 'account-name',
'source_account_iban' => 'account-iban',
'source_account_type' => '_ignore',
'source_account_number' => 'account-number',
'destination_account_id' => 'opposing-id',
'destination_account_name' => 'opposing-name',
'destination_account_iban' => 'opposing-iban',
'destination_account_type' => '_ignore',
'destination_account_number' => 'account-number',
'budget_id' => 'budget-id',
'budget_name' => 'budget-name',
'category_id' => 'category-id',
'category_name' => 'category-name',
'bill_id' => 'bill-id',
'bill_name' => 'bill-name',
];
}
}

View File

@@ -1,49 +0,0 @@
<?php
/**
* EntryAccount.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Export\Entry;
use FireflyIII\Models\Account;
/**
* Class EntryAccount
*
* @package FireflyIII\Export\Entry
*/
class EntryAccount
{
/** @var int */
public $accountId;
/** @var string */
public $iban;
/** @var string */
public $name;
/** @var string */
public $number;
/** @var string */
public $type;
/**
* EntryAccount constructor.
*
* @param Account $account
*/
public function __construct(Account $account)
{
$this->accountId = $account->id;
$this->name = $account->name;
$this->iban = $account->iban;
$this->type = $account->accountType->type;
$this->number = $account->getMeta('accountNumber');
}
}

View File

@@ -1,43 +0,0 @@
<?php
/**
* EntryBill.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Export\Entry;
use FireflyIII\Models\Bill;
/**
* Class EntryBill
*
* @package FireflyIII\Export\Entry
*/
class EntryBill
{
/** @var int */
public $billId = '';
/** @var string */
public $name = '';
/**
* EntryBill constructor.
*
* @param Bill $bill
*/
public function __construct(Bill $bill = null)
{
if (!is_null($bill)) {
$this->billId = $bill->id;
$this->name = $bill->name;
}
}
}

View File

@@ -1,43 +0,0 @@
<?php
/**
* EntryBudget.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Export\Entry;
use FireflyIII\Models\Budget;
/**
* Class EntryBudget
*
* @package FireflyIII\Export\Entry
*/
class EntryBudget
{
/** @var int */
public $budgetId = '';
/** @var string */
public $name = '';
/**
* EntryBudget constructor.
*
* @param Budget $budget
*/
public function __construct(Budget $budget = null)
{
if (!is_null($budget)) {
$this->budgetId = $budget->id;
$this->name = $budget->name;
}
}
}

View File

@@ -1,42 +0,0 @@
<?php
/**
* EntryCategory.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Export\Entry;
use FireflyIII\Models\Category;
/**
* Class EntryCategory
*
* @package FireflyIII\Export\Entry
*/
class EntryCategory
{
/** @var int */
public $categoryId = '';
/** @var string */
public $name = '';
/**
* EntryCategory constructor.
*
* @param Category $category
*/
public function __construct(Category $category = null)
{
if (!is_null($category)) {
$this->categoryId = $category->id;
$this->name = $category->name;
}
}
}

View File

@@ -14,9 +14,7 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Exporter;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Export\Entry\EntryAccount;
use FireflyIII\Models\ExportJob;
use Illuminate\Support\Collection;
use League\Csv\Writer;
use SplFileObject;
@@ -62,110 +60,24 @@ class CsvExporter extends BasicExporter implements ExporterInterface
$writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
$rows = [];
// Count the maximum number of sources and destinations each entry has. May need to expand the number of export fields:
$maxSourceAccounts = 1;
$maxDestAccounts = 1;
/** @var Entry $entry */
foreach ($this->getEntries() as $entry) {
$sources = $entry->sourceAccounts->count();
$destinations = $entry->destinationAccounts->count();
$maxSourceAccounts = max($maxSourceAccounts, $sources);
$maxDestAccounts = max($maxDestAccounts, $destinations);
}
$rows[] = array_keys($this->getFieldsAndTypes($maxSourceAccounts, $maxDestAccounts));
// get field names for header row:
$first = $this->getEntries()->first();
$headers = array_keys(get_object_vars($first));
$rows[] = $headers;
/** @var Entry $entry */
foreach ($this->getEntries() as $entry) {
// order is defined in Entry::getFieldsAndTypes.
$current = [$entry->description, $entry->amount, $entry->date];
$sourceData = $this->getAccountData($maxSourceAccounts, $entry->sourceAccounts);
$current = array_merge($current, $sourceData);
$destData = $this->getAccountData($maxDestAccounts, $entry->destinationAccounts);
$current = array_merge($current, $destData);
$rest = [$entry->budget->budgetId, $entry->budget->name, $entry->category->categoryId, $entry->category->name, $entry->bill->billId,
$entry->bill->name];
$current = array_merge($current, $rest);
$rows[] = $current;
$line = [];
foreach ($headers as $header) {
$line[] = $entry->$header;
}
$rows[] = $line;
}
$writer->insertAll($rows);
return true;
}
/**
* @param int $max
* @param Collection $accounts
*
* @return array
*/
private function getAccountData(int $max, Collection $accounts): array
{
$current = [];
for ($i = 0; $i < $max; $i++) {
/** @var EntryAccount $source */
$source = $accounts->get($i);
$currentId = '';
$currentName = '';
$currentIban = '';
$currentType = '';
$currentNumber = '';
if ($source) {
$currentId = $source->accountId;
$currentName = $source->name;
$currentIban = $source->iban;
$currentType = $source->type;
$currentNumber = $source->number;
}
$current[] = $currentId;
$current[] = $currentName;
$current[] = $currentIban;
$current[] = $currentType;
$current[] = $currentNumber;
}
unset($source);
return $current;
}
/**
* @param int $sources
* @param int $destinations
*
* @return array
*/
private function getFieldsAndTypes(int $sources, int $destinations): array
{
// key = field name (see top of class)
// value = field type (see csv.php under 'roles')
$array = [
'description' => 'description',
'amount' => 'amount',
'date' => 'date-transaction',
];
for ($i = 0; $i < $sources; $i++) {
$array['source_account_' . $i . '_id'] = 'account-id';
$array['source_account_' . $i . '_name'] = 'account-name';
$array['source_account_' . $i . '_iban'] = 'account-iban';
$array['source_account_' . $i . '_type'] = '_ignore';
$array['source_account_' . $i . '_number'] = 'account-number';
}
for ($i = 0; $i < $destinations; $i++) {
$array['destination_account_' . $i . '_id'] = 'account-id';
$array['destination_account_' . $i . '_name'] = 'account-name';
$array['destination_account_' . $i . '_iban'] = 'account-iban';
$array['destination_account_' . $i . '_type'] = '_ignore';
$array['destination_account_' . $i . '_number'] = 'account-number';
}
$array['budget_id'] = 'budget-id';
$array['budget_name'] = 'budget-name';
$array['category_id'] = 'category-id';
$array['category_name'] = 'category-name';
$array['bill_id'] = 'bill-id';
$array['bill_name'] = 'bill-name';
return $array;
}
private function tempFile()
{

View File

@@ -15,13 +15,13 @@ namespace FireflyIII\Export;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Export\Collector\AttachmentCollector;
use FireflyIII\Export\Collector\JournalExportCollector;
use FireflyIII\Export\Collector\UploadCollector;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Collection;
use Log;
use Storage;
use ZipArchive;
@@ -40,15 +40,11 @@ class Processor
/** @var bool */
public $includeAttachments;
/** @var bool */
public $includeConfig;
/** @var bool */
public $includeOldUploads;
/** @var ExportJob */
public $job;
/** @var array */
public $settings;
/** @var \FireflyIII\Export\ConfigurationFile */
private $configurationMaker;
/** @var Collection */
private $exportEntries;
/** @var Collection */
@@ -68,7 +64,6 @@ class Processor
$this->accounts = $settings['accounts'];
$this->exportFormat = $settings['exportFormat'];
$this->includeAttachments = $settings['includeAttachments'];
$this->includeConfig = $settings['includeConfig'];
$this->includeOldUploads = $settings['includeOldUploads'];
$this->job = $settings['job'];
$this->journals = new Collection;
@@ -84,8 +79,9 @@ class Processor
{
/** @var AttachmentCollector $attachmentCollector */
$attachmentCollector = app(AttachmentCollector::class, [$this->job]);
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
$attachmentCollector->run();
$this->files = $this->files->merge($attachmentCollector->getFiles());
$this->files = $this->files->merge($attachmentCollector->getEntries());
return true;
}
@@ -95,9 +91,13 @@ class Processor
*/
public function collectJournals(): bool
{
/** @var JournalTaskerInterface $tasker */
$tasker = app(JournalTaskerInterface::class);
$this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
/** @var JournalExportCollector $collector */
$collector = app(JournalExportCollector::class, [$this->job]);
$collector->setDates($this->settings['startDate'], $this->settings['endDate']);
$collector->setAccounts($this->settings['accounts']);
$collector->run();
$this->journals = $collector->getEntries();
Log::debug(sprintf('Count %d journals in collectJournals() ', $this->journals->count()));
return true;
}
@@ -111,7 +111,7 @@ class Processor
$uploadCollector = app(UploadCollector::class, [$this->job]);
$uploadCollector->run();
$this->files = $this->files->merge($uploadCollector->getFiles());
$this->files = $this->files->merge($uploadCollector->getEntries());
return true;
}
@@ -122,22 +122,11 @@ class Processor
public function convertJournals(): bool
{
$count = 0;
/** @var TransactionJournal $journal */
foreach ($this->journals as $journal) {
$this->exportEntries->push(Entry::fromJournal($journal));
foreach ($this->journals as $object) {
$this->exportEntries->push(Entry::fromObject($object));
$count++;
}
return true;
}
/**
* @return bool
*/
public function createConfigFile(): bool
{
$this->configurationMaker = app(ConfigurationFile::class, [$this->job]);
$this->files->push($this->configurationMaker->make());
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
return true;
}

View File

@@ -24,6 +24,15 @@ use Illuminate\Support\Collection;
*/
interface AccountChartGeneratorInterface
{
/**
* @param array $values
* @param array $names
*
* @return array
*/
public function pieChart(array $values, array $names): array;
/**
* @param Collection $accounts
* @param Carbon $start

View File

@@ -14,6 +14,7 @@ namespace FireflyIII\Generator\Chart\Account;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Support\ChartColour;
use Illuminate\Support\Collection;
/**
@@ -83,6 +84,37 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
return $data;
}
/**
* @param array $values
* @param array $names
*
* @return array
*/
public function pieChart(array $values, array $names): array
{
$data = [
'datasets' => [
0 => [],
],
'labels' => [],
];
$index = 0;
foreach ($values as $categoryId => $value) {
// make larger than 0
if (bccomp($value, '0') === -1) {
$value = bcmul($value, '-1');
}
$data['datasets'][0]['data'][] = round($value, 2);
$data['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index);
$data['labels'][] = $names[$categoryId];
$index++;
}
return $data;
}
/**
* @param Collection $accounts
* @param Carbon $start

View File

@@ -14,7 +14,8 @@ declare(strict_types = 1);
namespace FireflyIII\Generator\Chart\Bill;
use FireflyIII\Models\Bill;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Transaction;
use FireflyIII\Support\ChartColour;
use Illuminate\Support\Collection;
/**
@@ -37,7 +38,7 @@ class ChartJsBillChartGenerator implements BillChartGeneratorInterface
'datasets' => [
[
'data' => [round($unpaid, 2), round(bcmul($paid, '-1'), 2)],
'backgroundColor' => ['rgba(53, 124, 165,0.7)', 'rgba(0, 141, 76, 0.7)',],
'backgroundColor' => [ChartColour::getColour(0), ChartColour::getColour(1)],
],
],
@@ -61,13 +62,13 @@ class ChartJsBillChartGenerator implements BillChartGeneratorInterface
$minAmount = [];
$maxAmount = [];
$actualAmount = [];
/** @var TransactionJournal $entry */
/** @var Transaction $entry */
foreach ($entries as $entry) {
$data['labels'][] = $entry->date->formatLocalized($format);
$minAmount[] = round($bill->amount_min, 2);
$maxAmount[] = round($bill->amount_max, 2);
// journalAmount has been collected in BillRepository::getJournals
$actualAmount[] = round(TransactionJournal::amountPositive($entry), 2);
$actualAmount[] = bcmul($entry->transaction_amount, '-1');
}
$data['datasets'][] = [

View File

@@ -39,26 +39,9 @@ interface BudgetChartGeneratorInterface
public function frontpage(Collection $entries): array;
/**
* @param Collection $entries
* @param array $entries
*
* @return array
*/
public function multiYear(Collection $entries): array;
/**
* @param Collection $entries
* @param string $viewRange
*
* @return array
*/
public function period(Collection $entries, string $viewRange) : array;
/**
* @param Collection $budgets
* @param Collection $entries
*
* @return array
*/
public function year(Collection $budgets, Collection $entries): array;
public function period(array $entries): array;
}

View File

@@ -14,7 +14,6 @@ namespace FireflyIII\Generator\Chart\Budget;
use Illuminate\Support\Collection;
use Navigation;
/**
* Class ChartJsBudgetChartGenerator
@@ -101,49 +100,15 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
}
/**
* @param Collection $entries
* @param array $entries
*
* @return array
*/
public function multiYear(Collection $entries): array
public function period(array $entries) : array
{
// dataset:
$data = [
'count' => 0,
'labels' => [],
'datasets' => [],
];
// get labels from one of the budgets (assuming there's at least one):
$first = $entries->first();
$keys = array_keys($first['budgeted']);
foreach ($keys as $year) {
$data['labels'][] = strval($year);
}
// then, loop all entries and create datasets:
foreach ($entries as $entry) {
$name = $entry['name'];
$spent = $entry['spent'];
$budgeted = $entry['budgeted'];
$data['datasets'][] = ['label' => 'Spent on ' . $name, 'data' => array_values($spent)];
$data['datasets'][] = ['label' => 'Budgeted for ' . $name, 'data' => array_values($budgeted)];
}
$data['count'] = count($data['datasets']);
return $data;
}
/**
* @param Collection $entries
* @param string $viewRange
*
* @return array
*/
public function period(Collection $entries, string $viewRange) : array
{
$data = [
'labels' => [],
'labels' => array_keys($entries),
'datasets' => [
0 => [
'label' => trans('firefly.budgeted'),
@@ -156,9 +121,8 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
],
'count' => 2,
];
foreach ($entries as $entry) {
$label = Navigation::periodShow($entry['date'], $viewRange);
$data['labels'][] = $label;
foreach ($entries as $label => $entry) {
// data set 0 is budgeted
// data set 1 is spent:
$data['datasets'][0]['data'][] = $entry['budgeted'];
@@ -169,42 +133,4 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
return $data;
}
/**
* @param Collection $budgets
* @param Collection $entries
*
* @return array
*/
public function year(Collection $budgets, Collection $entries): array
{
// language:
$format = (string)trans('config.month');
$data = [
'labels' => [],
'datasets' => [],
];
foreach ($budgets as $budget) {
$data['labels'][] = $budget->name;
}
// also add "no budget"
$data['labels'][] = strval(trans('firefly.no_budget'));
/** @var array $entry */
foreach ($entries as $entry) {
$array = [
'label' => $entry[0]->formatLocalized($format),
'data' => [],
];
array_shift($entry);
$array['data'] = $entry;
$data['datasets'][] = $array;
}
$data['count'] = count($data['datasets']);
return $data;
}
}

View File

@@ -30,14 +30,6 @@ interface CategoryChartGeneratorInterface
*/
public function all(Collection $entries): array;
/**
* @param Collection $categories
* @param Collection $entries
*
* @return array
*/
public function earnedInPeriod(Collection $categories, Collection $entries): array;
/**
* @param Collection $entries
*
@@ -46,11 +38,11 @@ interface CategoryChartGeneratorInterface
public function frontpage(Collection $entries): array;
/**
* @param Collection $entries
* @param array $entries
*
* @return array
*/
public function multiYear(Collection $entries): array;
public function mainReportChart(array $entries): array;
/**
* @param Collection $entries
@@ -60,10 +52,10 @@ interface CategoryChartGeneratorInterface
public function period(Collection $entries): array;
/**
* @param Collection $categories
* @param Collection $entries
* @param array $data
*
* @return array
*/
public function spentInPeriod(Collection $categories, Collection $entries): array;
public function pieChart(array $data): array;
}

View File

@@ -12,6 +12,7 @@
declare(strict_types = 1);
namespace FireflyIII\Generator\Chart\Category;
use FireflyIII\Support\ChartColour;
use Illuminate\Support\Collection;
@@ -57,39 +58,6 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
return $data;
}
/**
* @param Collection $categories
* @param Collection $entries
*
* @return array
*/
public function earnedInPeriod(Collection $categories, Collection $entries): array
{
// language:
$format = (string)trans('config.month');
$data = [
'count' => 0,
'labels' => [],
'datasets' => [],
];
foreach ($categories as $category) {
$data['labels'][] = $category->name;
}
foreach ($entries as $entry) {
$date = $entry[0]->formatLocalized($format);
array_shift($entry);
$data['count']++;
$data['datasets'][] = ['label' => $date, 'data' => $entry];
}
return $data;
}
/**
* @param Collection $entries
*
@@ -118,29 +86,46 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
}
/**
* @param Collection $entries
* @param array $entries
*
* @return array
*/
public function multiYear(Collection $entries): array
public function mainReportChart(array $entries): array
{
// get labels from one of the categories (assuming there's at least one):
$first = $entries->first();
$data = ['count' => 0, 'labels' => array_keys($first['spent']), 'datasets' => [],];
// then, loop all entries and create datasets:
foreach ($entries as $entry) {
$name = $entry['name'];
$spent = $entry['spent'];
$earned = $entry['earned'];
if (array_sum(array_values($spent)) != 0) {
$data['datasets'][] = ['label' => 'Spent in category ' . $name, 'data' => array_values($spent)];
}
if (array_sum(array_values($earned)) != 0) {
$data['datasets'][] = ['label' => 'Earned in category ' . $name, 'data' => array_values($earned)];
$data = [
'count' => 0,
'labels' => array_keys($entries),
'datasets' => [],
];
foreach ($entries as $row) {
foreach ($row['in'] as $categoryId => $amount) {
// get in:
$data['datasets'][$categoryId . 'in']['data'][] = round($amount, 2);
// get out:
$opposite = $row['out'][$categoryId];
$data['datasets'][$categoryId . 'out']['data'][] = round($opposite, 2);
// set name:
$data['datasets'][$categoryId . 'out']['label'] = $row['name'][$categoryId] . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')';
$data['datasets'][$categoryId . 'in']['label'] = $row['name'][$categoryId] . ' (' . strtolower(strval(trans('firefly.income'))) . ')';
}
}
$data['count'] = count($data['datasets']);
// remove empty rows:
foreach ($data['datasets'] as $key => $content) {
if (array_sum($content['data']) === 0.0) {
unset($data['datasets'][$key]);
}
}
// re-key the datasets array:
$data['datasets'] = array_values($data['datasets']);
$data['count'] = count($data['datasets']);
return $data;
}
@@ -158,35 +143,32 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
}
/**
* @param Collection $categories
* @param Collection $entries
* @param array $entries
*
* @return array
*/
public function spentInPeriod(Collection $categories, Collection $entries): array
public function pieChart(array $entries): array
{
// language:
$format = (string)trans('config.month');
$data = [
'count' => 0,
$data = [
'datasets' => [
0 => [],
],
'labels' => [],
'datasets' => [],
];
foreach ($categories as $category) {
$data['labels'][] = $category->name;
}
$index = 0;
foreach ($entries as $entry) {
$date = $entry[0]->formatLocalized($format);
array_shift($entry);
$data['count']++;
$data['datasets'][] = ['label' => $date, 'data' => $entry];
if (bccomp($entry['amount'], '0') === -1) {
$entry['amount'] = bcmul($entry['amount'], '-1');
}
$data['datasets'][0]['data'][] = round($entry['amount'], 2);
$data['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index);
$data['labels'][] = $entry['name'];
$index++;
}
return $data;
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* MonthReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Audit;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
use Steam;
/**
* Class MonthReportGenerator
*
* @package FireflyIII\Generator\Report\Standard
*/
class MonthReportGenerator implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/**
* @return string
*/
public function generate(): string
{
$auditData = [];
$dayBefore = clone $this->start;
$dayBefore->subDay();
/** @var Account $account */
foreach ($this->accounts as $account) {
// balance the day before:
$id = $account->id;
$dayBeforeBalance = Steam::balance($account, $dayBefore);
$collector = new JournalCollector(auth()->user());
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end);
$journals = $collector->getJournals();
$journals = $journals->reverse();
$startBalance = $dayBeforeBalance;
/** @var Transaction $journal */
foreach ($journals as $transaction) {
$transaction->before = $startBalance;
$transactionAmount = $transaction->transaction_amount;
$newBalance = bcadd($startBalance, $transactionAmount);
$transaction->after = $newBalance;
$startBalance = $newBalance;
}
/*
* Reverse set again.
*/
$auditData[$id]['journals'] = $journals->reverse();
$auditData[$id]['exists'] = $journals->count() > 0;
$auditData[$id]['end'] = $this->end->formatLocalized(strval(trans('config.month_and_day')));
$auditData[$id]['endBalance'] = Steam::balance($account, $this->end);
$auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day')));
$auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance;
}
$reportType = 'audit';
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date',
'interest_date', 'book_date', 'process_date',
// three new optional fields.
'due_date', 'payment_date', 'invoice_date',
'from', 'to', 'budget', 'category', 'bill',
// more new optional fields
'internal_reference', 'notes',
'create_date', 'update_date',
];
$defaultShow = ['icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', 'to'];
return view('reports.audit.report', compact('reportType', 'accountIds', 'auditData', 'hideable', 'defaultShow'))
->with('start', $this->start)->with('end', $this->end)->with('accounts', $this->accounts)
->render();
}
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface
{
$this->accounts = $accounts;
return $this;
}
/**
* @param Collection $categories
*
* @return ReportGeneratorInterface
*/
public function setCategories(Collection $categories): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface
{
$this->end = $date;
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setStartDate(Carbon $date): ReportGeneratorInterface
{
$this->start = $date;
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* MultiYearReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Audit;
/**
* Class MultiYearReportGenerator
*
* @package FireflyIII\Generator\Report\Audit
*/
class MultiYearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* YearReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Audit;
/**
* Class YearReportGenerator
*
* @package FireflyIII\Generator\Report\Audit
*/
class YearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@@ -0,0 +1,336 @@
<?php
/**
* MonthReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Category;
use Carbon\Carbon;
use Crypt;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection;
use Log;
/**
* Class MonthReportGenerator
*
* @package FireflyIII\Generator\Report\Category
*/
class MonthReportGenerator extends Support implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Collection */
private $categories;
/** @var Carbon */
private $end;
/** @var Collection */
private $expenses;
/** @var Collection */
private $income;
/** @var Carbon */
private $start;
/**
* MonthReportGenerator constructor.
*/
public function __construct()
{
$this->income = new Collection;
$this->expenses = new Collection;
}
/**
* @return string
*/
public function generate(): string
{
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$categoryIds = join(',', $this->categories->pluck('id')->toArray());
$reportType = 'category';
$expenses = $this->getExpenses();
$income = $this->getIncome();
$accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income));
$categorySummary = $this->getObjectSummary($this->summarizeByCategory($expenses), $this->summarizeByCategory($income));
$averageExpenses = $this->getAverages($expenses, SORT_ASC);
$averageIncome = $this->getAverages($income, SORT_DESC);
$topExpenses = $this->getTopExpenses();
$topIncome = $this->getTopIncome();
// render!
return view(
'reports.category.month',
compact(
'accountIds', 'categoryIds', 'topIncome', 'reportType', 'accountSummary', 'categorySummary', 'averageExpenses', 'averageIncome', 'topExpenses'
)
)
->with('start', $this->start)->with('end', $this->end)
->with('categories', $this->categories)
->with('accounts', $this->accounts)
->render();
}
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface
{
$this->accounts = $accounts;
return $this;
}
/**
* @param Collection $categories
*
* @return ReportGeneratorInterface
*/
public function setCategories(Collection $categories): ReportGeneratorInterface
{
$this->categories = $categories;
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface
{
$this->end = $date;
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setStartDate(Carbon $date): ReportGeneratorInterface
{
$this->start = $date;
return $this;
}
/**
* @param Collection $collection
* @param int $sortFlag
*
* @return array
*/
private function getAverages(Collection $collection, int $sortFlag): array
{
$result = [];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
// opposing name and ID:
$opposingId = $transaction->opposing_account_id;
// is not set?
if (!isset($result[$opposingId])) {
$name = $transaction->opposing_account_name;
$encrypted = intval($transaction->opposing_account_encrypted);
$name = $encrypted === 1 ? Crypt::decrypt($name) : $name;
$result[$opposingId] = [
'name' => $name,
'count' => 1,
'id' => $opposingId,
'average' => $transaction->transaction_amount,
'sum' => $transaction->transaction_amount,
];
continue;
}
$result[$opposingId]['count']++;
$result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount);
$result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count']));
}
// sort result by average:
$average = [];
foreach ($result as $key => $row) {
$average[$key] = floatval($row['average']);
}
array_multisort($average, $sortFlag, $result);
return $result;
}
/**
* @return Collection
*/
private function getExpenses(): Collection
{
if ($this->expenses->count() > 0) {
Log::debug('Return previous set of expenses.');
return $this->expenses;
}
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setCategories($this->categories)->withOpposingAccount()->disableFilter();
$accountIds = $this->accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$transactions = self::filterExpenses($transactions, $accountIds);
$this->expenses = $transactions;
return $transactions;
}
/**
* @return Collection
*/
private function getIncome(): Collection
{
if ($this->income->count() > 0) {
return $this->income;
}
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->setCategories($this->categories)->withOpposingAccount();
$accountIds = $this->accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$transactions = self::filterIncome($transactions, $accountIds);
$this->income = $transactions;
return $transactions;
}
/**
* @param array $spent
* @param array $earned
*
* @return array
*/
private function getObjectSummary(array $spent, array $earned): array
{
$return = [];
/**
* @var int $accountId
* @var string $entry
*/
foreach ($spent as $objectId => $entry) {
if (!isset($return[$objectId])) {
$return[$objectId] = ['spent' => 0, 'earned' => 0];
}
$return[$objectId]['spent'] = $entry;
}
unset($entry);
/**
* @var int $accountId
* @var string $entry
*/
foreach ($earned as $objectId => $entry) {
if (!isset($return[$objectId])) {
$return[$objectId] = ['spent' => 0, 'earned' => 0];
}
$return[$objectId]['earned'] = $entry;
}
return $return;
}
/**
* @return Collection
*/
private function getTopExpenses(): Collection
{
$transactions = $this->getExpenses()->sortBy('transaction_amount');
$transactions = $transactions->each(
function (Transaction $transaction) {
if (intval($transaction->opposing_account_encrypted) === 1) {
$transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name);
}
}
);
return $transactions;
}
/**
* @return Collection
*/
private function getTopIncome(): Collection
{
$transactions = $this->getIncome()->sortByDesc('transaction_amount');
$transactions = $transactions->each(
function (Transaction $transaction) {
if (intval($transaction->opposing_account_encrypted) === 1) {
$transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name);
}
}
);
return $transactions;
}
/**
* @param Collection $collection
*
* @return array
*/
private function summarizeByAccount(Collection $collection): array
{
$result = [];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$accountId = $transaction->account_id;
$result[$accountId] = $result[$accountId] ?? '0';
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
}
return $result;
}
/**
* @param Collection $collection
*
* @return array
*/
private function summarizeByCategory(Collection $collection): array
{
$result = [];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$jrnlCatId = intval($transaction->transaction_journal_category_id);
$transCatId = intval($transaction->transaction_category_id);
$categoryId = max($jrnlCatId, $transCatId);
$result[$categoryId] = $result[$categoryId] ?? '0';
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
}
return $result;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* MultiYearReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Category;
/**
* Class MultiYearReportGenerator
*
* @package FireflyIII\Generator\Report\Audit
*/
class MultiYearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Support.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Category;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
use Log;
/**
* Class Support
*
* @package FireflyIII\Generator\Report\Category
*/
class Support
{
/**
* @param Collection $collection
* @param array $accounts
*
* @return Collection
*/
public static function filterExpenses(Collection $collection, array $accounts): Collection
{
$result = $collection->filter(
function (Transaction $transaction) use ($accounts) {
$opposing = $transaction->opposing_account_id;
// remove internal transfer
if (in_array($opposing, $accounts)) {
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id));
return null;
}
// remove positive amount
if (bccomp($transaction->transaction_amount, '0') === 1) {
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
return null;
}
return $transaction;
}
);
return $result;
}
/**
* @param Collection $collection
* @param array $accounts
*
* @return Collection
*/
public static function filterIncome(Collection $collection, array $accounts): Collection
{
$result = $collection->filter(
function (Transaction $transaction) use ($accounts) {
$opposing = $transaction->opposing_account_id;
// remove internal transfer
if (in_array($opposing, $accounts)) {
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id));
return null;
}
// remove positive amount
if (bccomp($transaction->transaction_amount, '0') === -1) {
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
return null;
}
return $transaction;
}
);
return $result;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* YearReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Category;
/**
* Class YearReportGenerator
*
* @package FireflyIII\Generator\Report\Audit
*/
class YearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* ReportGeneratorFactory.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
/**
* Class ReportGeneratorFactory
*
* @package FireflyIII\Generator\Report
*/
class ReportGeneratorFactory
{
/**
* @param string $type
* @param Carbon $start
* @param Carbon $end
*
* @return ReportGeneratorInterface
* @throws FireflyException
*/
public static function reportGenerator(string $type, Carbon $start, Carbon $end): ReportGeneratorInterface
{
$period = 'Month';
// more than two months date difference means year report.
if ($start->diffInMonths($end) > 1) {
$period = 'Year';
}
// more than one year date difference means multi year report.
if ($start->diffInMonths($end) > 12) {
$period = 'MultiYear';
}
$class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period);
if (class_exists($class)) {
/** @var ReportGeneratorInterface $obj */
$obj = new $class;
$obj->setStartDate($start);
$obj->setEndDate($end);
return $obj;
}
throw new FireflyException(sprintf('Cannot generate report. There is no "%s"-report for period "%s".', $type, $period));
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* ReportGeneratorInterface.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report;
use Carbon\Carbon;
use Illuminate\Support\Collection;
/**
* Interface ReportGeneratorInterface
*
* @package FireflyIII\Generator\Report
*/
interface ReportGeneratorInterface
{
/**
* @return string
*/
public function generate(): string;
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface;
/**
* @param Collection $categories
*
* @return ReportGeneratorInterface
*/
public function setCategories(Collection $categories): ReportGeneratorInterface;
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface;
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setStartDate(Carbon $date): ReportGeneratorInterface;
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* MonthReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Standard;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use Illuminate\Support\Collection;
/**
* Class MonthReportGenerator
*
* @package FireflyIII\Generator\Report\Standard
*/
class MonthReportGenerator implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/**
* @return string
*/
public function generate(): string
{
/** @var ReportHelperInterface $helper */
$helper = app(ReportHelperInterface::class);
$bills = $helper->getBillReport($this->start, $this->end, $this->accounts);
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$reportType = 'default';
// continue!
return view(
'reports.default.month',
compact('bills', 'accountIds', 'reportType')
)->with('start', $this->start)->with('end', $this->end)->render();
}
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface
{
$this->accounts = $accounts;
return $this;
}
/**
* @param Collection $categories
*
* @return ReportGeneratorInterface
*/
public function setCategories(Collection $categories): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface
{
$this->end = $date;
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setStartDate(Carbon $date): ReportGeneratorInterface
{
$this->start = $date;
return $this;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* MultiYearReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Standard;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use Illuminate\Support\Collection;
/**
* Class MonthReportGenerator
*
* @package FireflyIII\Generator\Report\Standard
*/
class MultiYearReportGenerator implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/**
* @return string
*/
public function generate(): string
{
// and some id's, joined:
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$reportType = 'default';
// continue!
return view(
'reports.default.multi-year',
compact('accountIds', 'reportType')
)->with('start', $this->start)->with('end', $this->end)->render();
}
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface
{
$this->accounts = $accounts;
return $this;
}
/**
* @param Collection $categories
*
* @return ReportGeneratorInterface
*/
public function setCategories(Collection $categories): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface
{
$this->end = $date;
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setStartDate(Carbon $date): ReportGeneratorInterface
{
$this->start = $date;
return $this;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* YearReportGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Report\Standard;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use Illuminate\Support\Collection;
/**
* Class MonthReportGenerator
*
* @package FireflyIII\Generator\Report\Standard
*/
class YearReportGenerator implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/**
* @return string
*/
public function generate(): string
{
// and some id's, joined:
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$reportType = 'default';
// continue!
return view(
'reports.default.year',
compact('accountIds', 'reportType')
)->with('start', $this->start)->with('end', $this->end)->render();
}
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface
{
$this->accounts = $accounts;
return $this;
}
/**
* @param Collection $categories
*
* @return ReportGeneratorInterface
*/
public function setCategories(Collection $categories): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface
{
$this->end = $date;
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setStartDate(Carbon $date): ReportGeneratorInterface
{
$this->start = $date;
return $this;
}
}

View File

@@ -14,8 +14,10 @@ declare(strict_types = 1);
namespace FireflyIII\Handlers\Events;
use Carbon\Carbon;
use FireflyIII\Events\StoredBudgetLimit;
use FireflyIII\Events\UpdatedBudgetLimit;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition;
use Illuminate\Database\QueryException;
use Log;
@@ -38,37 +40,9 @@ class BudgetEventHandler
*/
public function storeRepetition(StoredBudgetLimit $event):bool
{
$budgetLimit = $event->budgetLimit;
$end = $event->end;
$set = $budgetLimit->limitrepetitions()
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
->where('enddate', $end->format('Y-m-d 00:00:00'))
->get();
if ($set->count() == 0) {
$repetition = new LimitRepetition;
$repetition->startdate = $budgetLimit->startdate;
$repetition->enddate = $end;
$repetition->amount = $budgetLimit->amount;
$repetition->budgetLimit()->associate($budgetLimit);
try {
$repetition->save();
} catch (QueryException $e) {
Log::error('Trying to save new LimitRepetition failed: ' . $e->getMessage());
}
}
if ($set->count() == 1) {
$repetition = $set->first();
$repetition->amount = $budgetLimit->amount;
$repetition->save();
}
return true;
return $this->processRepetitionChange($event->budgetLimit, $event->end);
}
/**
* Updates, if present the budget limit repetition part of a budget limit.
*
@@ -76,18 +50,27 @@ class BudgetEventHandler
*
* @return bool
*/
public function update(UpdatedBudgetLimit $event): bool
public function updateRepetition(UpdatedBudgetLimit $event): bool
{
$budgetLimit = $event->budgetLimit;
$end = $event->end;
$set = $budgetLimit->limitrepetitions()
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
->where('enddate', $end->format('Y-m-d 00:00:00'))
->get();
return $this->processRepetitionChange($event->budgetLimit, $event->end);
}
/**
* @param BudgetLimit $budgetLimit
* @param Carbon $date
*
* @return bool
*/
private function processRepetitionChange(BudgetLimit $budgetLimit, Carbon $date):bool
{
$set = $budgetLimit->limitrepetitions()
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
->where('enddate', $date->format('Y-m-d 00:00:00'))
->get();
if ($set->count() == 0) {
$repetition = new LimitRepetition;
$repetition->startdate = $budgetLimit->startdate;
$repetition->enddate = $end;
$repetition->enddate = $date;
$repetition->amount = $budgetLimit->amount;
$repetition->budgetLimit()->associate($budgetLimit);
@@ -107,4 +90,4 @@ class BudgetEventHandler
return true;
}
}
}

View File

@@ -21,6 +21,7 @@ use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Rules\Processor;
use FireflyIII\Support\Events\BillScanner;
use Log;
/**
* Class StoredJournalEventHandler
@@ -42,30 +43,69 @@ class StoredJournalEventHandler
$journal = $event->journal;
$piggyBankId = $event->piggyBankId;
Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId));
/** @var PiggyBank $piggyBank */
$piggyBank = $journal->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
if (is_null($piggyBank)) {
Log::error('No such piggy bank!');
return true;
}
Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name));
// update piggy bank rep for date of transaction journal.
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
if (is_null($repetition)) {
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
return true;
}
$amount = TransactionJournal::amountPositive($journal);
Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
// if piggy account matches source account, the amount is positive
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
if (in_array($piggyBank->account_id, $sources)) {
$amount = bcmul($amount, '-1');
Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id));
}
// if the amount is positive:
// make sure it fits in piggy bank:
if (bccomp($amount, '0') === 1) {
// amount is positive
$room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount));
Log::debug(sprintf('Room in piggy bank for extra money is %f', $room));
if (bccomp($room, $amount) === -1) {
// $room is smaller than $amount
Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
Log::debug(sprintf('New amount is %f', $room));
$amount = $room;
}
}
if (bccomp($amount, '0') === -1) {
// amount is negative
Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount));
$compare = bcmul($repetition->currentamount, '-1');
if (bccomp($compare, $amount) === 1) {
// $currentamount is smaller than $amount
Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
Log::debug(sprintf('New amount is %f', $compare));
$amount = $compare;
}
}
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
$repetition->save();
PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]);
/** @var PiggyBankEvent $event */
$event = PiggyBankEvent::create(
['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]
);
Log::debug(sprintf('Created piggy bank event #%d', $event->id));
return true;
}
@@ -121,4 +161,4 @@ class StoredJournalEventHandler
return true;
}
}
}

View File

@@ -15,11 +15,8 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\UpdatedTransactionJournal;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Rules\Processor;
use FireflyIII\Support\Events\BillScanner;
@@ -30,50 +27,6 @@ use FireflyIII\Support\Events\BillScanner;
*/
class UpdatedJournalEventHandler
{
/**
* This method will try to reconnect a journal to a piggy bank, updating the piggy bank repetition.
*
* @param UpdatedTransactionJournal $event
*
* @return bool
*/
public function connectToPiggyBank(UpdatedTransactionJournal $event): bool
{
$journal = $event->journal;
if (!$journal->isTransfer()) {
return true;
}
// get the event connected to this journal:
/** @var PiggyBankEvent $event */
$event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first();
if (is_null($event)) {
return false;
}
$piggyBank = $event->piggyBank()->first();
$repetition = null;
if (!is_null($piggyBank)) {
/** @var PiggyBankRepetition $repetition */
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
}
if (is_null($repetition)) {
return false;
}
$amount = TransactionJournal::amount($journal);
$diff = bcsub($amount, $event->amount); // update current repetition
$repetition->currentamount = bcadd($repetition->currentamount, $diff);
$repetition->save();
$event->amount = $amount;
$event->save();
return true;
}
/**
* This method will check all the rules when a journal is updated.
@@ -125,4 +78,4 @@ class UpdatedJournalEventHandler
return true;
}
}
}

View File

@@ -14,10 +14,13 @@ declare(strict_types = 1);
namespace FireflyIII\Handlers\Events;
use Exception;
use FireflyConfig;
use FireflyIII\Events\ConfirmedUser;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\ResentConfirmation;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Mail\Message;
use Log;
use Mail;
@@ -63,7 +66,7 @@ class UserEventHandler
*
* @return bool
*/
public function onUserLogout(): bool
public function logoutUser(): bool
{
// dump stuff from the session:
Session::forget('twofactor-authenticated');
@@ -81,36 +84,7 @@ class UserEventHandler
*/
public function sendConfirmationMessage(RegisteredUser $event): bool
{
$user = $event->user;
$ipAddress = $event->ipAddress;
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
if ($confirmAccount === false) {
Preferences::setForUser($user, 'user_confirmed', true);
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
Preferences::mark();
return true;
}
$email = $user->email;
$code = str_random(16);
$route = route('do_confirm_account', [$code]);
Preferences::setForUser($user, 'user_confirmed', false);
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
Preferences::setForUser($user, 'user_confirmed_code', $code);
try {
Mail::send(
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
function (Message $message) use ($email) {
$message->to($email, $email)->subject('Please confirm your Firefly III account');
}
);
} catch (Swift_TransportException $e) {
Log::error($e->getMessage());
} catch (Exception $e) {
Log::error($e->getMessage());
}
return true;
return $this->sendConfirmation($event->user, $event->ipAddress);
}
/**
@@ -124,37 +98,35 @@ class UserEventHandler
*/
function sendConfirmationMessageAgain(ResentConfirmation $event): bool
{
$user = $event->user;
$ipAddress = $event->ipAddress;
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
if ($confirmAccount === false) {
Preferences::setForUser($user, 'user_confirmed', true);
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
Preferences::mark();
return $this->sendConfirmation($event->user, $event->ipAddress);
return true;
}
$email = $user->email;
$code = str_random(16);
$route = route('do_confirm_account', [$code]);
Preferences::setForUser($user, 'user_confirmed', false);
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
Preferences::setForUser($user, 'user_confirmed_code', $code);
}
/**
* @param RequestedNewPassword $event
*
* @return bool
*/
public function sendNewPassword(RequestedNewPassword $event): bool
{
$email = $event->user->email;
$ipAddress = $event->ipAddress;
$token = $event->token;
$url = route('password.reset', [$token]);
// send email.
try {
Mail::send(
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
function (Message $message) use ($email) {
$message->to($email, $email)->subject('Please confirm your Firefly III account');
}
['emails.password-html', 'emails.password-text'], ['url' => $url, 'ip' => $ipAddress], function (Message $message) use ($email) {
$message->to($email, $email)->subject('Your password reset request');
}
);
} catch (Swift_TransportException $e) {
Log::error($e->getMessage());
} catch (Exception $e) {
Log::error($e->getMessage());
}
return true;
}
/**
@@ -179,7 +151,7 @@ class UserEventHandler
// send email.
try {
Mail::send(
['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
['emails.registered-html', 'emails.registered-text'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
$message->to($email, $email)->subject('Welcome to Firefly III! ');
}
);
@@ -222,4 +194,43 @@ class UserEventHandler
}
}
/**
* @param User $user
* @param string $ipAddress
*
* @return bool
*/
private function sendConfirmation(User $user, string $ipAddress): bool
{
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
if ($mustConfirmAccount === false) {
Preferences::setForUser($user, 'user_confirmed', true);
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
Preferences::mark();
return true;
}
$email = $user->email;
$code = str_random(16);
$route = route('do_confirm_account', [$code]);
Preferences::setForUser($user, 'user_confirmed', false);
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
Preferences::setForUser($user, 'user_confirmed_code', $code);
try {
Mail::send(
['emails.confirm-account-html', 'emails.confirm-account-text'], ['route' => $route, 'ip' => $ipAddress],
function (Message $message) use ($email) {
$message->to($email, $email)->subject('Please confirm your Firefly III account');
}
);
} catch (Swift_TransportException $e) {
Log::error($e->getMessage());
} catch (Exception $e) {
Log::error($e->getMessage());
}
return true;
}
}

View File

@@ -17,10 +17,8 @@ use FireflyIII\Models\Attachment;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\MessageBag;
use Input;
use Log;
use Storage;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use TypeError;
/**
* Class AttachmentHelper
@@ -236,13 +234,8 @@ class AttachmentHelper implements AttachmentHelperInterface
private function getFiles()
{
$files = null;
try {
if (Input::hasFile('attachments')) {
$files = Input::file('attachments');
}
} catch (TypeError $e) {
// Log it, do nothing else.
Log::error($e->getMessage());
if (Input::hasFile('attachments')) {
$files = Input::file('attachments');
}
return $files;

View File

@@ -13,7 +13,10 @@ declare(strict_types = 1);
namespace FireflyIII\Helpers\Collection;
use Carbon\Carbon;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Support\Collection;
use Log;
/**
* Class Bill
@@ -26,7 +29,11 @@ class Bill
/**
* @var Collection
*/
protected $bills;
private $bills;
/** @var Carbon */
private $endDate;
/** @var Carbon */
private $startDate;
/**
*
@@ -44,6 +51,43 @@ class Bill
$this->bills->push($bill);
}
/**
*
*/
public function filterBills()
{
Log::debug('Now in filterBills()');
/** @var BillRepositoryInterface $repository */
$repository = app(BillRepositoryInterface::class);
$start = $this->startDate;
$end = $this->endDate;
$lines = $this->bills->filter(
function (BillLine $line) use ($repository, $start, $end) {
// next expected match?
$date = $start;
Log::debug(sprintf('Now at bill line for bill "%s"', $line->getBill()->name));
Log::debug(sprintf('Default date to use is start date: %s', $date->format('Y-m-d')));
if ($line->isHit()) {
$date = $line->getLastHitDate();
Log::debug(sprintf('Line was hit, see date: %s. Always include it.', $date->format('Y-m-d')));
return $line;
}
$expected = $repository->nextExpectedMatch($line->getBill(), $date);
Log::debug(sprintf('Next expected match is %s', $expected->format('Y-m-d')));
if ($expected <= $end && $expected >= $start) {
Log::debug('This date is inside report limits');
return $line;
}
Log::debug('This date is OUTSIDE report limits');
return false;
}
);
$this->bills = $lines;
}
/**
* @return Collection
*/
@@ -62,4 +106,20 @@ class Bill
return $set;
}
/**
* @param Carbon $endDate
*/
public function setEndDate(Carbon $endDate)
{
$this->endDate = $endDate;
}
/**
* @param Carbon $startDate
*/
public function setStartDate(Carbon $startDate)
{
$this->startDate = $startDate;
}
}

View File

@@ -12,6 +12,7 @@
declare(strict_types = 1);
namespace FireflyIII\Helpers\Collection;
use Carbon\Carbon;
use FireflyIII\Models\Bill as BillModel;
/**
@@ -23,8 +24,6 @@ use FireflyIII\Models\Bill as BillModel;
class BillLine
{
/** @var bool */
protected $active;
/** @var string */
protected $amount;
/** @var BillModel */
@@ -35,10 +34,19 @@ class BillLine
protected $max;
/** @var string */
protected $min;
/** @var Carbon */
private $lastHitDate;
/** @var int */
private $transactionJournalId;
/**
* BillLine constructor.
*/
public function __construct()
{
$this->lastHitDate = new Carbon;
}
/**
* @return string
*/
@@ -124,15 +132,7 @@ class BillLine
*/
public function isActive(): bool
{
return $this->active;
}
/**
* @param bool $active
*/
public function setActive(bool $active)
{
$this->active = $active;
return intval($this->bill->active) === 1;
}
/**
@@ -151,4 +151,21 @@ class BillLine
$this->hit = $hit;
}
/**
* @param Carbon $lastHitDate
*/
public function setLastHitDate(Carbon $lastHitDate)
{
$this->lastHitDate = $lastHitDate;
}
/**
* @return Carbon
*/
public function getLastHitDate(): Carbon
{
return $this->lastHitDate;
}
}

View File

@@ -0,0 +1,641 @@
<?php
declare(strict_types = 1);
namespace FireflyIII\Helpers\Collector;
use Carbon\Carbon;
use Crypt;
use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
/**
* Maybe this is a good idea after all...
*
* Class JournalCollector
*
* @package FireflyIII\Helpers\Collector
*/
class JournalCollector implements JournalCollectorInterface
{
/** @var array */
private $accountIds = [];
/** @var int */
private $count = 0;
/** @var array */
private $fields
= [
'transaction_journals.id as journal_id',
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.encrypted',
//'transaction_journals.transaction_currency_id',
'transaction_currencies.code as transaction_currency_code',
//'transaction_currencies.symbol as transaction_currency_symbol',
'transaction_types.type as transaction_type_type',
'transaction_journals.bill_id',
'bills.name as bill_name',
'transactions.id as id',
'transactions.amount as transaction_amount',
'transactions.description as transaction_description',
'transactions.account_id',
'transactions.identifier',
'transactions.transaction_journal_id',
'accounts.name as account_name',
'accounts.encrypted as account_encrypted',
'account_types.type as account_type',
];
/** @var bool */
private $filterTransfers = false;
/** @var bool */
private $joinedBudget = false;
/** @var bool */
private $joinedCategory = false;
/** @var bool */
private $joinedOpposing = false;
/** @var bool */
private $joinedTag = false;
/** @var int */
private $limit;
/** @var int */
private $offset;
/** @var int */
private $page = 1;
/** @var EloquentBuilder */
private $query;
/** @var bool */
private $run = false;
/** @var User */
private $user;
/**
* JournalCollector constructor.
*
* @param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
$this->query = $this->startQuery();
}
/**
* @return int
* @throws FireflyException
*/
public function count(): int
{
if ($this->run === true) {
throw new FireflyException('Cannot count after run in JournalCollector.');
}
$countQuery = clone $this->query;
// dont need some fields:
$countQuery->getQuery()->limit = null;
$countQuery->getQuery()->offset = null;
$countQuery->getQuery()->unionLimit = null;
$countQuery->getQuery()->groups = null;
$countQuery->getQuery()->orders = null;
$countQuery->groupBy('accounts.user_id');
$this->count = $countQuery->count();
return $this->count;
}
/**
* @return JournalCollectorInterface
*/
public function disableFilter(): JournalCollectorInterface
{
$this->filterTransfers = false;
return $this;
}
/**
* @return Collection
*/
public function getJournals(): Collection
{
$this->run = true;
$set = $this->query->get(array_values($this->fields));
Log::debug(sprintf('Count of set is %d', $set->count()));
$set = $this->filterTransfers($set);
Log::debug(sprintf('Count of set after filterTransfers() is %d', $set->count()));
// loop for decryption.
$set->each(
function (Transaction $transaction) {
$transaction->date = new Carbon($transaction->date);
$transaction->description = intval($transaction->encrypted) === 1 ? Crypt::decrypt($transaction->description) : $transaction->description;
$transaction->bill_name = !is_null($transaction->bill_name) ? Crypt::decrypt($transaction->bill_name) : '';
}
);
return $set;
}
/**
* @return LengthAwarePaginator
* @throws FireflyException
*/
public function getPaginatedJournals(): LengthAwarePaginator
{
if ($this->run === true) {
throw new FireflyException('Cannot getPaginatedJournals after run in JournalCollector.');
}
$this->count();
$set = $this->getJournals();
$journals = new LengthAwarePaginator($set, $this->count, $this->limit, $this->page);
return $journals;
}
/**
* @param Collection $accounts
*
* @return JournalCollectorInterface
*/
public function setAccounts(Collection $accounts): JournalCollectorInterface
{
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$this->query->whereIn('transactions.account_id', $accountIds);
Log::debug(sprintf('setAccounts: %s', join(', ', $accountIds)));
$this->accountIds = $accountIds;
}
if ($accounts->count() > 1) {
$this->filterTransfers = true;
}
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function setAllAssetAccounts(): JournalCollectorInterface
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class, [$this->user]);
$accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$this->query->whereIn('transactions.account_id', $accountIds);
$this->accountIds = $accountIds;
}
if ($accounts->count() > 1) {
$this->filterTransfers = true;
}
return $this;
}
/**
* @param Collection $bills
*
* @return JournalCollectorInterface
*/
public function setBills(Collection $bills): JournalCollectorInterface
{
if ($bills->count() > 0) {
$billIds = $bills->pluck('id')->toArray();
$this->query->whereIn('transaction_journals.bill_id', $billIds);
}
return $this;
}
/**
* @param Budget $budget
*
* @return JournalCollectorInterface
*/
public function setBudget(Budget $budget): JournalCollectorInterface
{
$this->joinBudgetTables();
$this->query->where(
function (EloquentBuilder $q) use ($budget) {
$q->where('budget_transaction.budget_id', $budget->id);
$q->orWhere('budget_transaction_journal.budget_id', $budget->id);
}
);
return $this;
}
/**
* @param Collection $budgets
*
* @return JournalCollectorInterface
*/
public function setBudgets(Collection $budgets): JournalCollectorInterface
{
$budgetIds = $budgets->pluck('id')->toArray();
if (count($budgetIds) === 0) {
return $this;
}
$this->joinBudgetTables();
$this->query->where(
function (EloquentBuilder $q) use ($budgetIds) {
$q->whereIn('budget_transaction.budget_id', $budgetIds);
$q->orWhereIn('budget_transaction_journal.budget_id', $budgetIds);
}
);
return $this;
}
/**
* @param Collection $categories
*
* @return JournalCollectorInterface
*/
public function setCategories(Collection $categories): JournalCollectorInterface
{
$categoryIds = $categories->pluck('id')->toArray();
if (count($categoryIds) === 0) {
return $this;
}
$this->joinCategoryTables();
$this->query->where(
function (EloquentBuilder $q) use ($categoryIds) {
$q->whereIn('category_transaction.category_id', $categoryIds);
$q->orWhereIn('category_transaction_journal.category_id', $categoryIds);
}
);
return $this;
}
/**
* @param Category $category
*
* @return JournalCollectorInterface
*/
public function setCategory(Category $category): JournalCollectorInterface
{
$this->joinCategoryTables();
$this->query->where(
function (EloquentBuilder $q) use ($category) {
$q->where('category_transaction.category_id', $category->id);
$q->orWhere('category_transaction_journal.category_id', $category->id);
}
);
return $this;
}
/**
* @param int $limit
*
* @return JournalCollectorInterface
*/
public function setLimit(int $limit): JournalCollectorInterface
{
$this->limit = $limit;
$this->query->limit($limit);
Log::debug(sprintf('Set limit to %d', $limit));
return $this;
}
/**
* @param int $offset
*
* @return JournalCollectorInterface
*/
public function setOffset(int $offset): JournalCollectorInterface
{
$this->offset = $offset;
return $this;
}
/**
* @param int $page
*
* @return JournalCollectorInterface
*/
public function setPage(int $page): JournalCollectorInterface
{
$this->page = $page;
if ($page > 0) {
$page--;
}
Log::debug(sprintf('Page is %d', $page));
if (!is_null($this->limit)) {
$offset = ($this->limit * $page);
$this->offset = $offset;
$this->query->skip($offset);
Log::debug(sprintf('Changed offset to %d', $offset));
}
if (is_null($this->limit)) {
Log::debug('The limit is zero, cannot set the page.');
}
return $this;
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return JournalCollectorInterface
*/
public function setRange(Carbon $start, Carbon $end): JournalCollectorInterface
{
if ($start <= $end) {
$this->query->where('transaction_journals.date', '>=', $start->format('Y-m-d'));
$this->query->where('transaction_journals.date', '<=', $end->format('Y-m-d'));
}
return $this;
}
/**
* @param Tag $tag
*
* @return JournalCollectorInterface
*/
public function setTag(Tag $tag): JournalCollectorInterface
{
$this->joinTagTables();
$this->query->where('tag_transaction_journal.tag_id', $tag->id);
return $this;
}
/**
* @param array $types
*
* @return JournalCollectorInterface
*/
public function setTypes(array $types): JournalCollectorInterface
{
if (count($types) > 0) {
Log::debug('Set query collector types', $types);
$this->query->whereIn('transaction_types.type', $types);
}
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function withBudgetInformation(): JournalCollectorInterface
{
$this->joinBudgetTables();
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function withCategoryInformation(): JournalCollectorInterface
{
$this->joinCategoryTables();
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function withOpposingAccount(): JournalCollectorInterface
{
$this->joinOpposingTables();
$accountIds = $this->accountIds;
$this->query->where(
function (EloquentBuilder $q1) use ($accountIds) {
// set 1:
// where source is in the set of $accounts
// but destination is not.
$q1->where(
function (EloquentBuilder $q2) use ($accountIds) {
// transactions.account_id in set
$q2->whereIn('transactions.account_id', $accountIds);
// opposing.account_id not in set
$q2->whereNotIn('opposing.account_id', $accountIds);
}
);
// set 1:
// where source is not in the set of $accounts
// but destination is.
$q1->orWhere(
function (EloquentBuilder $q3) use ($accountIds) {
// transactions.account_id not in set
$q3->whereNotIn('transactions.account_id', $accountIds);
// B in set
// opposing.account_id not in set
$q3->whereIn('opposing.account_id', $accountIds);
}
);
}
);
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function withoutBudget(): JournalCollectorInterface
{
$this->joinBudgetTables();
$this->query->where(
function (EloquentBuilder $q) {
$q->whereNull('budget_transaction.budget_id');
$q->whereNull('budget_transaction_journal.budget_id');
}
);
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function withoutCategory(): JournalCollectorInterface
{
$this->joinCategoryTables();
$this->query->where(
function (EloquentBuilder $q) {
$q->whereNull('category_transaction.category_id');
$q->whereNull('category_transaction_journal.category_id');
}
);
return $this;
}
/**
* If the set of accounts used by the collector includes more than one asset
* account, chances are the set include double entries: transfers get selected
* on both the source, and then again on the destination account.
*
* This method filters them out.
*
* @param Collection $set
*
* @return Collection
*/
private function filterTransfers(Collection $set): Collection
{
if ($this->filterTransfers) {
$set = $set->filter(
function (Transaction $transaction) {
if (!($transaction->transaction_type_type === TransactionType::TRANSFER && bccomp($transaction->transaction_amount, '0') === -1)) {
Log::debug(
sprintf(
'Included journal #%d (transaction #%d) because its a %s with amount %f',
$transaction->transaction_journal_id,
$transaction->id,
$transaction->transaction_type_type,
$transaction->transaction_amount
)
);
return $transaction;
}
Log::debug(
sprintf(
'Removed journal #%d (transaction #%d) because its a %s with amount %f',
$transaction->transaction_journal_id,
$transaction->id,
$transaction->transaction_type_type,
$transaction->transaction_amount
)
);
return false;
}
);
}
return $set;
}
/**
*
*/
private function joinBudgetTables()
{
if (!$this->joinedBudget) {
// join some extra tables:
$this->joinedBudget = true;
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
$this->fields[] = 'budget_transaction_journal.budget_id as transaction_journal_budget_id';
$this->fields[] = 'budget_transaction.budget_id as transaction_budget_id';
}
}
/**
*
*/
private function joinCategoryTables()
{
if (!$this->joinedCategory) {
// join some extra tables:
$this->joinedCategory = true;
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
$this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id';
$this->fields[] = 'category_transaction.category_id as transaction_category_id';
}
}
private function joinOpposingTables()
{
if (!$this->joinedOpposing) {
// join opposing transaction (hard):
$this->query->leftJoin(
'transactions as opposing', function (JoinClause $join) {
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
->where('opposing.identifier', '=', DB::raw('transactions.identifier'))
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'));
}
);
$this->query->leftJoin('accounts as opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id');
$this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id');
$this->query->whereNull('opposing.deleted_at');
$this->fields[] = 'opposing.account_id as opposing_account_id';
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
$this->fields[] = 'opposing_account_types.type as opposing_account_type';
}
}
/**
*
*/
private function joinTagTables()
{
if (!$this->joinedTag) {
// join some extra tables:
$this->joinedTag = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
}
}
/**
* @return EloquentBuilder
*/
private function startQuery(): EloquentBuilder
{
$query = Transaction
::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC');
return $query;
}
}

View File

@@ -0,0 +1,166 @@
<?php
/**
* JournalCollectorInterface.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Helpers\Collector;
use Carbon\Carbon;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
/**
* Interface JournalCollectorInterface
*
* @package FireflyIII\Helpers\Collector
*/
interface JournalCollectorInterface
{
/**
* @return int
*/
public function count(): int;
/**
* @return JournalCollectorInterface
*/
public function disableFilter(): JournalCollectorInterface;
/**
* @return Collection
*/
public function getJournals(): Collection;
/**
* @return LengthAwarePaginator
*/
public function getPaginatedJournals():LengthAwarePaginator;
/**
* @param Collection $accounts
*
* @return JournalCollectorInterface
*/
public function setAccounts(Collection $accounts): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function setAllAssetAccounts(): JournalCollectorInterface;
/**
* @param Collection $bills
*
* @return JournalCollectorInterface
*/
public function setBills(Collection $bills): JournalCollectorInterface;
/**
* @param Budget $budget
*
* @return JournalCollectorInterface
*/
public function setBudget(Budget $budget): JournalCollectorInterface;
/**
* @param Collection $budgets
*
* @return JournalCollectorInterface
*/
public function setBudgets(Collection $budgets): JournalCollectorInterface;
/**
* @param Collection $categories
*
* @return JournalCollectorInterface
*/
public function setCategories(Collection $categories): JournalCollectorInterface;
/**
* @param Category $category
*
* @return JournalCollectorInterface
*/
public function setCategory(Category $category): JournalCollectorInterface;
/**
* @param int $limit
*
* @return JournalCollectorInterface
*/
public function setLimit(int $limit): JournalCollectorInterface;
/**
* @param int $offset
*
* @return JournalCollectorInterface
*/
public function setOffset(int $offset): JournalCollectorInterface;
/**
* @param int $page
*
* @return JournalCollectorInterface
*/
public function setPage(int $page): JournalCollectorInterface;
/**
* @param Carbon $start
* @param Carbon $end
*
* @return JournalCollectorInterface
*/
public function setRange(Carbon $start, Carbon $end): JournalCollectorInterface;
/**
* @param Tag $tag
*
* @return JournalCollectorInterface
*/
public function setTag(Tag $tag): JournalCollectorInterface;
/**
* @param array $types
*
* @return JournalCollectorInterface
*/
public function setTypes(array $types): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function withBudgetInformation(): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function withCategoryInformation(): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function withOpposingAccount(): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function withoutBudget(): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function withoutCategory(): JournalCollectorInterface;
}

View File

@@ -14,7 +14,9 @@ namespace FireflyIII\Helpers\Help;
use Cache;
use League\CommonMark\CommonMarkConverter;
use Log;
use Requests;
use Requests_Exception;
use Route;
/**
@@ -24,49 +26,57 @@ use Route;
*/
class Help implements HelpInterface
{
/** @var string */
protected $userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36';
/**
*
* @param string $key
* @param string $route
* @param string $language
*
* @return string
*/
public function getFromCache(string $key): string
public function getFromCache(string $route, string $language): string
{
return Cache::get($key);
$line = sprintf('help.%s.%s', $route, $language);
return Cache::get($line);
}
/**
* @param string $language
* @param string $route
*
* @return array
* @return string
*/
public function getFromGithub(string $language, string $route): array
public function getFromGithub(string $language, string $route): string
{
$uri = sprintf('https://raw.githubusercontent.com/JC5/firefly-iii-help/master/%s/%s.md', $language, $route);
$routeIndex = str_replace('.', '-', $route);
$title = trans('help.' . $routeIndex);
$content = [
'text' => '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>',
'title' => $title,
];
$uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route);
Log::debug(sprintf('Trying to get %s...', $uri));
$opt = ['useragent' => $this->userAgent];
$content = '';
try {
$result = Requests::get($uri, [], $opt);
} catch (Requests_Exception $e) {
Log::error($e);
return '';
}
$result = Requests::get($uri);
Log::debug(sprintf('Status code is %d', $result->status_code));
if ($result->status_code === 200) {
$content['text'] = $result->body;
$content = trim($result->body);
}
if (strlen(trim($content['text'])) == 0) {
$content['text'] = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
if (strlen($content) > 0) {
Log::debug('Content is longer than zero. Expect something.');
$converter = new CommonMarkConverter();
$content = $converter->convertToHtml($content);
}
if (strlen($content) === 0) {
Log::warning('Raw content length is zero.');
}
$converter = new CommonMarkConverter();
$content['text'] = $converter->convertToHtml($content['text']);
return $content;
@@ -84,27 +94,42 @@ class Help implements HelpInterface
}
/**
*
* @param string $route
* @param string $language
*
* @return bool
*/
public function inCache(string $route):bool
public function inCache(string $route, string $language):bool
{
return Cache::has('help.' . $route . '.title') && Cache::has('help.' . $route . '.text');
$line = sprintf('help.%s.%s', $route, $language);
$result = Cache::has($line);
if ($result) {
Log::debug(sprintf('Cache has this entry: %s', 'help.' . $route . '.' . $language));
}
if (!$result) {
Log::debug(sprintf('Cache does not have this entry: %s', 'help.' . $route . '.' . $language));
}
return $result;
}
/**
*
* @param string $route
* @param string $language
* @param array $content
* @param string $content
*
* @internal param $title
*/
public function putInCache(string $route, string $language, array $content)
public function putInCache(string $route, string $language, string $content)
{
Cache::put('help.' . $route . '.text.' . $language, $content['text'], 10080); // a week.
Cache::put('help.' . $route . '.title.' . $language, $content['title'], 10080);
$key = sprintf('help.%s.%s', $route, $language);
if (strlen($content) > 0) {
Log::debug(sprintf('Will store entry in cache: %s', $key));
Cache::put($key, $content, 10080); // a week.
return;
}
Log::info(sprintf('Will not cache %s because content is empty.', $key));
}
}

View File

@@ -21,19 +21,20 @@ interface HelpInterface
{
/**
* @param string $key
* @param string $route
* @param string $language
*
* @return string
*/
public function getFromCache(string $key): string;
public function getFromCache(string $route, string $language): string;
/**
* @param string $language
* @param string $route
*
* @return array
* @return string
*/
public function getFromGithub(string $language, string $route):array;
public function getFromGithub(string $language, string $route):string;
/**
* @param string $route
@@ -44,15 +45,16 @@ interface HelpInterface
/**
* @param string $route
* @param string $language
*
* @return bool
*/
public function inCache(string $route): bool;
public function inCache(string $route, string $language): bool;
/**
* @param string $route
* @param string $language
* @param array $content
* @param string $content
*/
public function putInCache(string $route, string $language, array $content);
public function putInCache(string $route, string $language, string $content);
}

View File

@@ -20,7 +20,6 @@ use FireflyIII\Helpers\Collection\BudgetLine;
use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
@@ -44,57 +43,19 @@ class BudgetReportHelper implements BudgetReportHelperInterface
}
/**
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // at 43, its ok.
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return Collection
* @return array
*/
public function budgetYearOverview(Carbon $start, Carbon $end, Collection $accounts): Collection
public function getBudgetPeriodReport(Carbon $start, Carbon $end, Collection $accounts): array
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('budget-year');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$budgets = $this->repository->getBudgets();
$report = $this->repository->getBudgetPeriodReport($budgets, $accounts, $start, $end);
$data = $this->filterBudgetPeriodReport($report);
$current = clone $start;
$return = new Collection;
$set = $this->repository->getBudgets();
$budgets = [];
$spent = [];
$headers = $this->createYearHeaders($current, $end);
/** @var Budget $budget */
foreach ($set as $budget) {
$id = $budget->id;
$budgets[$id] = $budget->name;
$current = clone $start;
$budgetData = $this->getBudgetSpentData($current, $end, $budget, $accounts);
$sum = $budgetData['sum'];
$spent[$id] = $budgetData['spent'];
if (bccomp('0', $sum) === 0) {
// not spent anything.
unset($spent[$id]);
unset($budgets[$id]);
}
}
$return->put('headers', $headers);
$return->put('budgets', $budgets);
$return->put('spent', $spent);
$cache->store($return);
return $return;
return $data;
}
/**
@@ -182,33 +143,6 @@ class BudgetReportHelper implements BudgetReportHelperInterface
return $set;
}
/**
* Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
* and sum up everything in the array in the given range.
*
* @param Carbon $start
* @param Carbon $end
* @param array $array
*
* @return string
*/
protected function getSumOfRange(Carbon $start, Carbon $end, array $array)
{
$sum = '0';
$currentStart = clone $start; // to not mess with the original one
$currentEnd = clone $end; // to not mess with the original one
while ($currentStart <= $currentEnd) {
$date = $currentStart->format('Y-m-d');
if (isset($array[$date])) {
$sum = bcadd($sum, $array[$date]);
}
$currentStart->addDay();
}
return $sum;
}
/**
* @param Budget $budget
* @param LimitRepetition $repetition
@@ -230,48 +164,29 @@ class BudgetReportHelper implements BudgetReportHelperInterface
}
/**
* @param Carbon $current
* @param Carbon $end
* Filters empty results from getBudgetPeriodReport
*
* @param array $data
*
* @return array
*/
private function createYearHeaders(Carbon $current, Carbon $end): array
private function filterBudgetPeriodReport(array $data): array
{
$headers = [];
while ($current < $end) {
$short = $current->format('m-Y');
$headers[$short] = $current->formatLocalized((string)trans('config.month'));
$current->addMonth();
/**
* @var int $budgetId
* @var array $set
*/
foreach ($data as $budgetId => $set) {
$sum = '0';
foreach ($set['entries'] as $amount) {
$sum = bcadd($amount, $sum);
}
$data[$budgetId]['sum'] = $sum;
if (bccomp('0', $sum) === 0) {
unset($data[$budgetId]);
}
}
return $headers;
}
/**
* @param Carbon $current
* @param Carbon $end
* @param Budget $budget
* @param Collection $accounts
*
* @return array
*/
private function getBudgetSpentData(Carbon $current, Carbon $end, Budget $budget, Collection $accounts): array
{
$sum = '0';
$spent = [];
while ($current < $end) {
$currentEnd = clone $current;
$currentEnd->endOfMonth();
$format = $current->format('m-Y');
$budgetSpent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $current, $currentEnd);
$spent[$format] = $budgetSpent;
$sum = bcadd($sum, $budgetSpent);
$current->addMonth();
}
return [
'spent' => $spent,
'sum' => $sum,
];
return $data;
}
}

View File

@@ -25,14 +25,15 @@ use Illuminate\Support\Collection;
*/
interface BudgetReportHelperInterface
{
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return Collection
* @return array
*/
public function budgetYearOverview(Carbon $start, Carbon $end, Collection $accounts): Collection;
public function getBudgetPeriodReport(Carbon $start, Carbon $end, Collection $accounts): array;
/**
* @param Carbon $start

View File

@@ -19,18 +19,15 @@ use FireflyIII\Helpers\Collection\BillLine;
use FireflyIII\Helpers\Collection\Category as CategoryCollection;
use FireflyIII\Helpers\Collection\Expense;
use FireflyIII\Helpers\Collection\Income;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\FiscalHelperInterface;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
use stdClass;
@@ -44,20 +41,16 @@ class ReportHelper implements ReportHelperInterface
/** @var BudgetRepositoryInterface */
protected $budgetRepository;
/** @var TagRepositoryInterface */
protected $tagRepository;
/**
* ReportHelper constructor.
*
*
* @param BudgetRepositoryInterface $budgetRepository
* @param TagRepositoryInterface $tagRepository
*/
public function __construct(BudgetRepositoryInterface $budgetRepository, TagRepositoryInterface $tagRepository)
public function __construct(BudgetRepositoryInterface $budgetRepository)
{
$this->budgetRepository = $budgetRepository;
$this->tagRepository = $tagRepository;
}
/**
@@ -77,36 +70,44 @@ class ReportHelper implements ReportHelperInterface
/** @var BillRepositoryInterface $repository */
$repository = app(BillRepositoryInterface::class);
$bills = $repository->getBillsForAccounts($accounts);
$journals = $repository->getAllJournalsInRange($bills, $start, $end);
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
$collector->setAccounts($accounts)->setRange($start, $end)->setBills($bills);
$journals = $collector->getJournals();
$collection = new BillCollection;
$collection->setStartDate($start);
$collection->setEndDate($end);
/** @var Bill $bill */
foreach ($bills as $bill) {
$billLine = new BillLine;
$billLine->setBill($bill);
$billLine->setActive(intval($bill->active) === 1);
$billLine->setMin(strval($bill->amount_min));
$billLine->setMax(strval($bill->amount_max));
$billLine->setHit(false);
// is hit in period?
$entry = $journals->filter(
function (TransactionJournal $journal) use ($bill) {
return $journal->bill_id === $bill->id;
function (Transaction $transaction) use ($bill) {
return $transaction->bill_id === $bill->id;
}
);
$first = $entry->first();
if (!is_null($first)) {
$billLine->setTransactionJournalId($first->id);
$billLine->setAmount($first->journalAmount);
$billLine->setAmount($first->transaction_amount);
$billLine->setLastHitDate($first->date);
$billLine->setHit(true);
}
if ($billLine->isActive()) {
// bill is active, or bill is hit:
if ($billLine->isActive() || $billLine->isHit()) {
$collection->addBill($billLine);
}
}
// do some extra filtering.
$collection->filterBills();
return $collection;
}
@@ -230,104 +231,4 @@ class ReportHelper implements ReportHelperInterface
return $months;
}
/**
* Returns an array of tags and their comparitive size with amounts bla bla.
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function tagReport(Carbon $start, Carbon $end, Collection $accounts): array
{
$ids = $accounts->pluck('id')->toArray();
$set = Tag::
leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin(
'transactions AS source', function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0');
}
)
->leftJoin(
'transactions AS destination', function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', '0');
}
)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where(
// source.account_id in accountIds XOR destination.account_id in accountIds
function (Builder $query) use ($ids) {
$query->where(
function (Builder $q1) use ($ids) {
$q1->whereIn('source.account_id', $ids)
->whereNotIn('destination.account_id', $ids);
}
)->orWhere(
function (Builder $q2) use ($ids) {
$q2->whereIn('destination.account_id', $ids)
->whereNotIn('source.account_id', $ids);
}
);
}
)
->get(['tags.id', 'tags.tag', 'transaction_journals.id as journal_id', 'destination.amount']);
$collection = [];
if ($set->count() === 0) {
return $collection;
}
/** @var Tag $entry */
foreach ($set as $entry) {
// less than zero? multiply to be above zero.
$amount = $entry->amount;
$id = intval($entry->id);
$previousAmount = $collection[$id]['amount'] ?? '0';
$collection[$id] = [
'id' => $id,
'tag' => $entry->tag,
'amount' => bcadd($previousAmount, $amount),
];
}
// cleanup collection (match "fonts")
$max = strval(max(array_column($collection, 'amount')));
foreach ($collection as $id => $entry) {
$size = bcdiv($entry['amount'], $max, 4);
if (bccomp($size, '0.25') === -1) {
$size = '0.5';
}
$collection[$id]['fontsize'] = $size;
}
return $collection;
}
/**
* Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
* and sum up everything in the array in the given range.
*
* @param Carbon $start
* @param Carbon $end
* @param array $array
*
* @return string
*/
protected function getSumOfRange(Carbon $start, Carbon $end, array $array)
{
$sum = '0';
$currentStart = clone $start; // to not mess with the original one
$currentEnd = clone $end; // to not mess with the original one
while ($currentStart <= $currentEnd) {
$date = $currentStart->format('Y-m-d');
if (isset($array[$date])) {
$sum = bcadd($sum, $array[$date]);
}
$currentStart->addDay();
}
return $sum;
}
}

View File

@@ -80,15 +80,4 @@ interface ReportHelperInterface
*/
public function listOfMonths(Carbon $date): array;
/**
* Returns an array of tags and their comparitive size with amounts bla bla.
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function tagReport(Carbon $start, Carbon $end, Collection $accounts): array;
}

View File

@@ -13,17 +13,24 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
use Amount;
use Carbon\Carbon;
use ExpandedForm;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\AccountFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Input;
use Log;
use Navigation;
use Preferences;
use Session;
@@ -44,8 +51,16 @@ class AccountController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', trans('firefly.accounts'));
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', trans('firefly.accounts'));
return $next($request);
}
);
}
/**
@@ -55,9 +70,18 @@ class AccountController extends Controller
*/
public function create(string $what = 'asset')
{
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$subTitle = trans('firefly.make_new_' . $what . '_account');
Session::flash('preFilled', []);
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$currencies = ExpandedForm::makeSelectList($repository->get());
$defaultCurrency = Amount::getDefaultCurrency();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$subTitle = trans('firefly.make_new_' . $what . '_account');
Session::flash(
'preFilled',
[
'currency_id' => $defaultCurrency->id,
]
);
// put previous url in session if not redirect from store (not "create another").
if (session('accounts.create.fromStore') !== true) {
@@ -67,7 +91,7 @@ class AccountController extends Controller
Session::flash('gaEventCategory', 'accounts');
Session::flash('gaEventAction', 'create-' . $what);
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle'));
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencies'));
}
@@ -124,6 +148,9 @@ class AccountController extends Controller
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$currencies = ExpandedForm::makeSelectList($repository->get());
// put previous url in session if not redirect from store (not "return_to_edit").
if (session('accounts.edit.fromUpdate') !== true) {
@@ -146,13 +173,14 @@ class AccountController extends Controller
'ccMonthlyPaymentDate' => $account->getMeta('ccMonthlyPaymentDate'),
'openingBalanceDate' => $openingBalanceDate,
'openingBalance' => $openingBalanceAmount,
'virtualBalance' => round($account->virtual_balance, 2),
'virtualBalance' => $account->virtual_balance,
'currency_id' => $account->getMeta('currency_id'),
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'accounts');
Session::flash('gaEventAction', 'edit-' . $what);
return view('accounts.edit', compact('account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what'));
return view('accounts.edit', compact('currencies', 'account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what'));
}
/**
@@ -192,100 +220,84 @@ class AccountController extends Controller
}
/**
* @param AccountTaskerInterface $tasker
* @param ARI $repository
* @param Account $account
* @param JournalCollectorInterface $collector
* @param Account $account
*
* @return View
*/
public function show(AccountTaskerInterface $tasker, ARI $repository, Account $account)
public function show(JournalCollectorInterface $collector, Account $account)
{
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
return $this->redirectToOriginalAccount($account);
}
// show journals from current period only:
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$subTitle = $account->name;
$range = Preferences::get('viewRange', '1M')->data;
/** @var Carbon $start */
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
$page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
// grab those journals:
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
$journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id);
// grouped other months thing:
// oldest transaction in account:
$start = $repository->oldestJournalDate($account);
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfX(new Carbon, $range);
$entries = new Collection;
// generate entries for each period (and cache those)
$entries = $this->periodEntries($account);
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-show');
$cache->addProperty($account->id);
if ($cache->has()) {
$entries = $cache->get();
return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
}
// only include asset accounts when this account is an asset:
$assets = new Collection;
if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) {
$assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
}
while ($end >= $start) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
$spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
$earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$entries->push([$dateStr, $dateName, $spent, $earned]);
$end = Navigation::subtractPeriod($end, $range, 1);
}
$cache->store($entries);
return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end'));
}
/**
* @param AccountTaskerInterface $tasker
* @param Account $account
* @param string $date
* @param ARI $repository
* @param Account $account
*
* @return View
*/
public function showWithDate(AccountTaskerInterface $tasker, Account $account, string $date)
public function showAll(AccountRepositoryInterface $repository, Account $account)
{
$subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
// replace with journal collector:
$collector = new JournalCollector(auth()->user());
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
$journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id . '/all');
// get oldest and newest journal for account:
$start = $repository->oldestJournalDate($account);
$end = $repository->newestJournalDate($account);
return view('accounts.show_with_date', compact('account', 'journals', 'subTitle', 'start', 'end'));
}
/**
* @param Account $account
* @param string $date
*
* @return View
*/
public function showWithDate(Account $account, string $date)
{
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($carbon, $range);
$end = Navigation::endOfPeriod($carbon, $range);
$subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
$page = intval(Input::get('page'));
$page = $page === 0 ? 1 : $page;
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
// replace with journal collector:
$collector = new JournalCollector(auth()->user());
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
$journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id . '/' . $date);
return view('accounts.show_with_date', compact('category', 'date', 'account', 'journals', 'subTitle', 'carbon'));
return view('accounts.show_with_date', compact('category', 'date', 'account', 'journals', 'subTitle', 'carbon', 'start', 'end'));
}
/**
@@ -297,23 +309,8 @@ class AccountController extends Controller
*/
public function store(AccountFormRequest $request, ARI $repository)
{
$accountData = [
'name' => trim($request->input('name')),
'accountType' => $request->input('what'),
'virtualBalance' => round($request->input('virtualBalance'), 2),
'virtualBalanceCurrency' => intval($request->input('amount_currency_id_virtualBalance')),
'active' => true,
'user' => auth()->user()->id,
'iban' => trim($request->input('iban')),
'accountNumber' => trim($request->input('accountNumber')),
'accountRole' => $request->input('accountRole'),
'openingBalance' => round($request->input('openingBalance'), 2),
'openingBalanceDate' => new Carbon((string)$request->input('openingBalanceDate')),
'openingBalanceCurrency' => intval($request->input('amount_currency_id_openingBalance')),
];
$account = $repository->store($accountData);
$data = $request->getAccountData();
$account = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
Preferences::mark();
@@ -345,22 +342,8 @@ class AccountController extends Controller
*/
public function update(AccountFormRequest $request, ARI $repository, Account $account)
{
$accountData = [
'name' => $request->input('name'),
'active' => $request->input('active'),
'user' => auth()->user()->id,
'iban' => $request->input('iban'),
'accountNumber' => $request->input('accountNumber'),
'accountRole' => $request->input('accountRole'),
'virtualBalance' => round($request->input('virtualBalance'), 2),
'openingBalance' => round($request->input('openingBalance'), 2),
'openingBalanceDate' => new Carbon((string)$request->input('openingBalanceDate')),
'openingBalanceCurrency' => intval($request->input('amount_currency_id_openingBalance')),
'ccType' => $request->input('ccType'),
'ccMonthlyPaymentDate' => $request->input('ccMonthlyPaymentDate'),
];
$repository->update($account, $accountData);
$data = $request->getAccountData();
$repository->update($account, $data);
Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
Preferences::mark();
@@ -392,4 +375,86 @@ class AccountController extends Controller
return '';
}
/**
* This method returns "period entries", so nov-2015, dec-2015, etc etc (this depends on the users session range)
* and for each period, the amount of money spent and earned. This is a complex operation which is cached for
* performance reasons.
*
* @param Account $account The account involved.
*
* @return Collection
*/
private function periodEntries(Account $account): Collection
{
/** @var ARI $repository */
$repository = app(ARI::class);
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
$start = $repository->oldestJournalDate($account);
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfX(new Carbon, $range);
$entries = new Collection;
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-show-period-entries');
$cache->addProperty($account->id);
if ($cache->has()) {
Log::debug('Entries are cached, return cache.');
return $cache->get();
}
// only include asset accounts when this account is an asset:
$assets = new Collection;
if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) {
$assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
}
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
$spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
$earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$entries->push([$dateStr, $dateName, $spent, $earned]);
$end = Navigation::subtractPeriod($end, $range, 1);
}
$cache->store($entries);
return $entries;
}
/**
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/
private function redirectToOriginalAccount(Account $account)
{
/** @var Transaction $transaction */
$transaction = $account->transactions()->first();
if (is_null($transaction)) {
throw new FireflyException('Expected a transaction. This account has none. BEEP, error.');
}
$journal = $transaction->transactionJournal;
/** @var Transaction $opposingTransaction */
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
if (is_null($opposingTransaction)) {
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.');
}
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
}
}

View File

@@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Admin;
use Config;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\ConfigurationRequest;
use FireflyIII\Support\Facades\FireflyConfig;
@@ -37,8 +36,15 @@ class ConfigurationController extends Controller
{
parent::__construct();
View::share('title', strval(trans('firefly.administration')));
View::share('mainTitleIcon', 'fa-hand-spock-o');
$this->middleware(
function ($request, $next) {
View::share('title', strval(trans('firefly.administration')));
View::share('mainTitleIcon', 'fa-hand-spock-o');
return $next($request);
}
);
}
@@ -52,9 +58,11 @@ class ConfigurationController extends Controller
// all available configuration and their default value in case
// they don't exist yet.
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
return view('admin.configuration.index', compact('subTitle', 'subTitleIcon', 'singleUserMode'));
return view('admin.configuration.index', compact('subTitle', 'subTitleIcon', 'singleUserMode', 'mustConfirmAccount', 'isDemoSite'));
}
@@ -66,10 +74,12 @@ class ConfigurationController extends Controller
public function store(ConfigurationRequest $request)
{
// get config values:
$singleUserMode = intval($request->get('single_user_mode')) === 1 ? true : false;
$data = $request->getConfigurationData();
// store config values
FireflyConfig::set('single_user_mode', $singleUserMode);
FireflyConfig::set('single_user_mode', $data['single_user_mode']);
FireflyConfig::set('must_confirm_account', $data['must_confirm_account']);
FireflyConfig::set('is_demo_site', $data['is_demo_site']);
// flash message
Session::flash('success', strval(trans('firefly.configuration_updated')));

View File

@@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\User;
use Illuminate\Http\Request;
@@ -83,6 +84,7 @@ class DomainController extends Controller
*/
public function toggleDomain(string $domain)
{
$domain = strtolower($domain);
$blocked = FireflyConfig::get('blocked-domains', [])->data;
if (in_array($domain, $blocked)) {
@@ -111,15 +113,16 @@ class DomainController extends Controller
*/
private function getKnownDomains(): array
{
$users = User::get();
$set = [];
$filtered = [];
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
$users = $repository->all();
$set = [];
$filtered = [];
/** @var User $user */
foreach ($users as $user) {
$email = $user->email;
$parts = explode('@', $email);
$domain = $parts[1];
$set[] = $domain;
$email = $user->email;
$parts = explode('@', $email);
$set[] = strtolower($parts[1]);
}
$set = array_unique($set);
// filter for already banned domains:
@@ -131,7 +134,6 @@ class DomainController extends Controller
$filtered[] = $domain;
}
}
asort($filtered);
return $filtered;
}

View File

@@ -24,7 +24,7 @@ use FireflyIII\Http\Controllers\Controller;
class HomeController extends Controller
{
/**
* @return mixed
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
{

View File

@@ -14,6 +14,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyConfig;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
@@ -46,30 +47,32 @@ class UserController extends Controller
*/
public function index(UserRepositoryInterface $repository)
{
$title = strval(trans('firefly.administration'));
$mainTitleIcon = 'fa-hand-spock-o';
$subTitle = strval(trans('firefly.user_administration'));
$subTitleIcon = 'fa-users';
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
$users = $repository->all();
$title = strval(trans('firefly.administration'));
$mainTitleIcon = 'fa-hand-spock-o';
$subTitle = strval(trans('firefly.user_administration'));
$subTitleIcon = 'fa-users';
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
$users = $repository->all();
// add meta stuff.
$users->each(
function (User $user) use ($confirmAccount) {
// is user activated?
$isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data;
function (User $user) use ($mustConfirmAccount) {
$list = ['user_confirmed', 'twoFactorAuthEnabled', 'twoFactorAuthSecret', 'registration_ip_address', 'confirmation_ip_address'];
$preferences = Preferences::getArrayForUser($user, $list);
$user->activated = true;
if ($isConfirmed === false && $confirmAccount === true) {
if (!($preferences['user_confirmed'] === true) && $mustConfirmAccount === true) {
$user->activated = false;
}
$user->isAdmin = $user->hasRole('owner');
$is2faEnabled = Preferences::getForUser($user, 'twoFactorAuthEnabled', false)->data;
$has2faSecret = !is_null(Preferences::getForUser($user, 'twoFactorAuthSecret'));
$is2faEnabled = $preferences['twoFactorAuthEnabled'] === true;
$has2faSecret = !is_null($preferences['twoFactorAuthSecret']);
$user->has2FA = false;
if ($is2faEnabled && $has2faSecret) {
$user->has2FA = true;
}
$user->prefs = $preferences;
}
);

View File

@@ -42,14 +42,22 @@ class AttachmentController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-paperclip');
View::share('title', trans('firefly.attachments'));
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-paperclip');
View::share('title', trans('firefly.attachments'));
return $next($request);
}
);
}
/**
* @param Attachment $attachment
*
* @return View
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function delete(Attachment $attachment)
{
@@ -164,14 +172,8 @@ class AttachmentController extends Controller
*/
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
{
$attachmentData = [
'title' => $request->input('title'),
'description' => $request->input('description'),
'notes' => $request->input('notes'),
];
$repository->update($attachment, $attachmentData);
$data = $request->getAttachmentData();
$repository->update($attachment, $data);
Session::flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
Preferences::mark();

View File

@@ -75,7 +75,8 @@ class LoginController extends Controller
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
$lockedOut = $this->hasTooManyLoginAttempts($request);
if ($lockedOut) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
@@ -188,7 +189,7 @@ class LoginController extends Controller
];
Mail::send(
['emails.blocked-login-html', 'emails.blocked-login'], $fields, function (Message $message) use ($email, $user) {
['emails.blocked-login-html', 'emails.blocked-login-text'], $fields, function (Message $message) use ($email, $user) {
$message->to($email, $email)->subject('Blocked a login attempt from ' . trim($user->email) . '.');
}
);

View File

@@ -123,7 +123,11 @@ class RegisterController extends Controller
*/
public function showRegistrationForm(Request $request)
{
$showDemoWarning = config('firefly.show-demo-warning', false);
// is demo site?
$isDemoSite = FireflyConfig::get('is_demo_site', Config::get('firefly.configuration.is_demo_site'))->data;
// activate account?
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', Config::get('firefly.configuration.must_confirm_account'))->data;
// is allowed to?
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
@@ -136,7 +140,7 @@ class RegisterController extends Controller
$email = $request->old('email');
return view('auth.register', compact('showDemoWarning', 'email'));
return view('auth.register', compact('isDemoSite', 'email', 'mustConfirmAccount'));
}
/**
@@ -217,7 +221,7 @@ class RegisterController extends Controller
];
Mail::send(
['emails.blocked-registration-html', 'emails.blocked-registration'], $fields, function (Message $message) use ($email, $domain) {
['emails.blocked-registration-html', 'emails.blocked-registration-text'], $fields, function (Message $message) use ($email, $domain) {
$message->to($email, $email)->subject('Blocked a registration attempt with domain ' . $domain . '.');
}
);

View File

@@ -14,10 +14,12 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Requests\BillFormRequest;
use FireflyIII\Models\Bill;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Support\Collection;
use Input;
use Preferences;
use Session;
@@ -38,8 +40,16 @@ class BillController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.bills'));
View::share('mainTitleIcon', 'fa-calendar-o');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.bills'));
View::share('mainTitleIcon', 'fa-calendar-o');
return $next($request);
}
);
}
/**
@@ -137,23 +147,15 @@ class BillController extends Controller
$bills = $repository->getBills();
$bills->each(
function (Bill $bill) use ($repository, $start, $end) {
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
$bill->lastFoundMatch = $repository->lastFoundMatch($bill);
$journals = $repository->getJournalsInRange($bill, $start, $end);
// loop journals, find average:
$average = '0';
$count = $journals->count();
if ($count > 0) {
$sum = '0';
foreach ($journals as $journal) {
$sum = bcadd($sum, TransactionJournal::amountPositive($journal));
}
$average = bcdiv($sum, strval($count));
// paid in this period?
$bill->paidDates = $repository->getPaidDatesInRange($bill, $start, $end);
$bill->payDates = $repository->getPayDatesInRange($bill, $start, $end);
$lastDate = clone $start;
if ($bill->paidDates->count() >= $bill->payDates->count()) {
$lastDate = $end;
}
$bill->lastPaidAmount = $average;
$bill->paidInPeriod = ($start <= $bill->lastFoundMatch) && ($end >= $bill->lastFoundMatch);
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastDate);
}
);
@@ -200,10 +202,15 @@ class BillController extends Controller
$year = $date->year;
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$journals = $repository->getJournals($bill, $page, $pageSize);
$yearAverage = $repository->getYearAverage($bill, $date);
$overallAverage = $repository->getOverallAverage($bill);
// use collector:
$collector = new JournalCollector(auth()->user());
$collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setPage($page)->setLimit($pageSize);
$journals = $collector->getPaginatedJournals();
$journals->setPath('/bills/show/' . $bill->id);
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
$hideBill = true;
$subTitle = e($bill->name);

View File

@@ -17,15 +17,16 @@ use Amount;
use Carbon\Carbon;
use Config;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Requests\BudgetFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Input;
use Log;
use Navigation;
use Preferences;
use Response;
@@ -47,9 +48,17 @@ class BudgetController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.budgets'));
View::share('mainTitleIcon', 'fa-tasks');
View::share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.budgets'));
View::share('mainTitleIcon', 'fa-tasks');
return $next($request);
}
);
}
/**
@@ -191,10 +200,12 @@ class BudgetController extends Controller
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$startAsString = $start->format('Y-m-d');
$endAsString = $end->format('Y-m-d');
Log::debug('Now at /budgets');
// loop the budgets:
/** @var Budget $budget */
foreach ($budgets as $budget) {
Log::debug(sprintf('Now at budget #%d ("%s")', $budget->id, $budget->name));
$budget->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
$allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
$otherRepetitions = new Collection;
@@ -236,31 +247,28 @@ class BudgetController extends Controller
}
/**
* @param BudgetRepositoryInterface $repository
*
* @return View
*/
public function noBudget(BudgetRepositoryInterface $repository)
public function noBudget()
{
/** @var Carbon $start */
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); // budget
$count = $journals->count();
$journals = $journals->slice($offset, $pageSize);
$list = new LengthAwarePaginator($journals, $count, $pageSize);
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$list->setPath('/budgets/list/noBudget');
return view('budgets.noBudget', compact('list', 'subTitle'));
// collector
$collector = new JournalCollector(auth()->user());
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutBudget();
$journals = $collector->getPaginatedJournals();
$journals->setPath('/budgets/list/noBudget');
return view('budgets.no-budget', compact('journals', 'subTitle'));
}
/**
@@ -282,25 +290,25 @@ class BudgetController extends Controller
}
/**
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
* @param BudgetRepositoryInterface $repository
* @param AccountRepositoryInterface $accountRepository
* @param Budget $budget
*
* @return View
* @throws FireflyException
*/
public function show(BudgetRepositoryInterface $repository, Budget $budget)
public function show(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget)
{
/** @var Carbon $start */
$start = session('first', Carbon::create()->startOfYear());
$end = new Carbon;
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); // budget
$count = $journals->count();
$journals = $journals->slice($offset, $pageSize);
$journals = new LengthAwarePaginator($journals, $count, $pageSize);
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
// collector:
$collector = new JournalCollector(auth()->user());
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page);
$journals = $collector->getPaginatedJournals();
$journals->setPath('/budgets/show/' . $budget->id);
@@ -310,7 +318,7 @@ class BudgetController extends Controller
/** @var LimitRepetition $entry */
foreach ($set as $entry) {
$entry->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $entry->startdate, $entry->enddate);
$entry->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $entry->startdate, $entry->enddate);
$limits->push($entry);
}
@@ -318,33 +326,40 @@ class BudgetController extends Controller
}
/**
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
* @param LimitRepetition $repetition
* @param Budget $budget
* @param LimitRepetition $repetition
*
* @return View
* @throws FireflyException
*/
public function showWithRepetition(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition)
public function showWithRepetition(Budget $budget, LimitRepetition $repetition)
{
if ($repetition->budgetLimit->budget->id != $budget->id) {
throw new FireflyException('This budget limit is not part of this budget.');
}
$start = $repetition->startdate;
$end = $repetition->enddate;
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); // budget
$count = $journals->count();
$journals = $journals->slice($offset, $pageSize);
$journals = new LengthAwarePaginator($journals, $count, $pageSize);
$subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]);
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$start = $repetition->startdate;
$end = $repetition->enddate;
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitle = trans(
'firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]
);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
// collector:
$collector = new JournalCollector(auth()->user());
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page);
$journals = $collector->getPaginatedJournals();
$journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id);
$repetition->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate);
$repetition->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate);
$limits = new Collection([$repetition]);
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
@@ -359,11 +374,8 @@ class BudgetController extends Controller
*/
public function store(BudgetFormRequest $request, BudgetRepositoryInterface $repository)
{
$budgetData = [
'name' => $request->input('name'),
'user' => auth()->user()->id,
];
$budget = $repository->store($budgetData);
$data = $request->getBudgetData();
$budget = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_new_budget', ['name' => e($budget->name)])));
Preferences::mark();
@@ -389,12 +401,8 @@ class BudgetController extends Controller
*/
public function update(BudgetFormRequest $request, BudgetRepositoryInterface $repository, Budget $budget)
{
$budgetData = [
'name' => $request->input('name'),
'active' => intval($request->input('active')) == 1,
];
$repository->update($budget, $budgetData);
$data = $request->getBudgetData();
$repository->update($budget, $data);
Session::flash('success', strval(trans('firefly.updated_budget', ['name' => e($budget->name)])));
Preferences::mark();

View File

@@ -14,13 +14,13 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Requests\CategoryFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
use FireflyIII\Support\CacheProperties;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Input;
use Navigation;
@@ -43,8 +43,16 @@ class CategoryController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.categories'));
View::share('mainTitleIcon', 'fa-bar-chart');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.categories'));
View::share('mainTitleIcon', 'fa-bar-chart');
return $next($request);
}
);
}
/**
@@ -139,23 +147,25 @@ class CategoryController extends Controller
}
/**
* @param CRI $repository
*
* @return View
*/
public function noCategory(CRI $repository)
public function noCategory()
{
/** @var Carbon $start */
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->startOfMonth());
$list = $repository->journalsInPeriodWithoutCategory(new Collection(), [], $start, $end); // category
$end = session('end', Carbon::now()->startOfMonth());
// new collector:
$collector = new JournalCollector(auth()->user());
$collector->setAllAssetAccounts()->setRange($start, $end)->withoutCategory();//->groupJournals();
$journals = $collector->getJournals();
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
return view('categories.noCategory', compact('list', 'subTitle'));
return view('categories.no-category', compact('journals', 'subTitle'));
}
/**
@@ -172,16 +182,17 @@ class CategoryController extends Controller
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$hideCategory = true; // used in list.
$page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); // category
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
// use journal collector
$collector = new JournalCollector(auth()->user());
$collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category);
$journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id);
// oldest transaction in category:
@@ -210,7 +221,7 @@ class CategoryController extends Controller
$categoryCollection = new Collection([$category]);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
while ($end >= $start) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
@@ -225,18 +236,16 @@ class CategoryController extends Controller
}
$cache->store($entries);
return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle'));
return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle', 'subTitleIcon'));
}
/**
* @param CRI $repository
* @param Category $category
*
* @param $date
* @param Category $category
* @param $date
*
* @return View
*/
public function showWithDate(CRI $repository, Category $category, string $date)
public function showWithDate(Category $category, string $date)
{
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data;
@@ -244,15 +253,16 @@ class CategoryController extends Controller
$end = Navigation::endOfPeriod($carbon, $range);
$subTitle = $category->name;
$hideCategory = true; // used in list.
$page = intval(Input::get('page'));
$pageSize = Preferences::get('transactionPageSize', 50)->data;
$offset = ($page - 1) * $pageSize;
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); // category
$count = $set->count();
$subSet = $set->splice($offset, $pageSize);
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
// new collector:
$collector = new JournalCollector(auth()->user());
$collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category);
$journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id . '/' . $date);
return view('categories.show_with_date', compact('category', 'journals', 'hideCategory', 'subTitle', 'carbon'));
}
@@ -264,11 +274,8 @@ class CategoryController extends Controller
*/
public function store(CategoryFormRequest $request, CRI $repository)
{
$categoryData = [
'name' => trim($request->input('name')),
'user' => auth()->user()->id,
];
$category = $repository->store($categoryData);
$data = $request->getCategoryData();
$category = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_category', ['name' => e($category->name)])));
Preferences::mark();
@@ -292,11 +299,8 @@ class CategoryController extends Controller
*/
public function update(CategoryFormRequest $request, CRI $repository, Category $category)
{
$categoryData = [
'name' => $request->input('name'),
];
$repository->update($category, $categoryData);
$data = $request->getCategoryData();
$repository->update($category, $data);
Session::flash('success', strval(trans('firefly.updated_category', ['name' => e($category->name)])));
Preferences::mark();

View File

@@ -17,10 +17,15 @@ use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Log;
@@ -99,6 +104,87 @@ class AccountController extends Controller
return Response::json($data);
}
/**
* @param JournalCollectorInterface $collector
* @param Account $account
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseByBudget(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
{
$cache = new CacheProperties;
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expenseByBudget');
if ($cache->has()) {
return Response::json($cache->get());
}
// grab all journals:
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionType::WITHDRAWAL]);
$transactions = $collector->getJournals();
$result = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$jrnlBudgetId = intval($transaction->transaction_journal_budget_id);
$transBudgetId = intval($transaction->transaction_budget_id);
$budgetId = max($jrnlBudgetId, $transBudgetId);
$result[$budgetId] = $result[$budgetId] ?? '0';
$result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
}
$names = $this->getBudgetNames(array_keys($result));
$data = $this->generator->pieChart($result, $names);
$cache->store($data);
return Response::json($data);
}
/**
* @param JournalCollectorInterface $collector
* @param Account $account
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseByCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
{
$cache = new CacheProperties;
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expenseByCategory');
if ($cache->has()) {
return Response::json($cache->get());
}
// grab all journals:
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]);
$transactions = $collector->getJournals();
$result = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$jrnlCatId = intval($transaction->transaction_journal_category_id);
$transCatId = intval($transaction->transaction_category_id);
$categoryId = max($jrnlCatId, $transCatId);
$result[$categoryId] = $result[$categoryId] ?? '0';
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
}
$names = $this->getCategoryNames(array_keys($result));
$data = $this->generator->pieChart($result, $names);
$cache->store($data);
return Response::json($data);
}
/**
* Shows the balances for all the user's frontpage accounts.
*
@@ -108,41 +194,52 @@ class AccountController extends Controller
*/
public function frontpage(AccountRepositoryInterface $repository)
{
$start = clone session('start', Carbon::now()->startOfMonth());
$end = clone session('end', Carbon::now()->endOfMonth());
$start = clone session('start', Carbon::now()->startOfMonth());
$end = clone session('end', Carbon::now()->endOfMonth());
$frontPage = Preferences::get('frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray());
$accounts = $repository->getAccountsById($frontPage->data);
return Response::json($this->accountBalanceChart($start, $end, $accounts));
}
// chart properties for cache:
/**
* @param JournalCollectorInterface $collector
* @param Account $account
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function incomeByCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
{
$cache = new CacheProperties;
$cache->addProperty($account->id);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('frontpage');
$cache->addProperty('accounts');
$cache->addProperty('incomeByCategory');
if ($cache->has()) {
return Response::json($cache->get());
}
$frontPage = Preferences::get('frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray());
$accounts = $repository->getAccountsById($frontPage->data);
// grab all journals:
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]);
$transactions = $collector->getJournals();
$result = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$jrnlCatId = intval($transaction->transaction_journal_category_id);
$transCatId = intval($transaction->transaction_category_id);
$categoryId = max($jrnlCatId, $transCatId);
foreach ($accounts as $account) {
$balances = [];
$current = clone $start;
$range = Steam::balanceInRange($account, $start, clone $end);
$previous = round(array_values($range)[0], 2);
while ($current <= $end) {
$format = $current->format('Y-m-d');
$balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
$previous = $balance;
$balances[] = $balance;
$current->addDay();
}
$account->balances = $balances;
$result[$categoryId] = $result[$categoryId] ?? '0';
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
}
$data = $this->generator->frontpage($accounts, $start, $end);
$names = $this->getCategoryNames(array_keys($result));
$data = $this->generator->pieChart($result, $names);
$cache->store($data);
return Response::json($data);
}
/**
@@ -156,38 +253,7 @@ class AccountController extends Controller
*/
public function report(Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('all');
$cache->addProperty('accounts');
$cache->addProperty('default');
$cache->addProperty($accounts);
if ($cache->has()) {
return Response::json($cache->get());
}
foreach ($accounts as $account) {
$balances = [];
$current = clone $start;
$range = Steam::balanceInRange($account, $start, clone $end);
$previous = round(array_values($range)[0], 2);
while ($current <= $end) {
$format = $current->format('Y-m-d');
$balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
$previous = $balance;
$balances[] = $balance;
$current->addDay();
}
$account->balances = $balances;
}
// make chart:
$data = $this->generator->frontpage($accounts, $start, $end);
$cache->store($data);
return Response::json($data);
return Response::json($this->accountBalanceChart($start, $end, $accounts));
}
/**
@@ -287,7 +353,6 @@ class AccountController extends Controller
return Response::json($data);
}
/**
* @param Account $account
* @param string $date
@@ -340,4 +405,90 @@ class AccountController extends Controller
return Response::json($data);
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
private function accountBalanceChart(Carbon $start, Carbon $end, Collection $accounts): array
{
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-balance-chart');
$cache->addProperty($accounts);
if ($cache->has()) {
return $cache->get();
}
foreach ($accounts as $account) {
$balances = [];
$current = clone $start;
$range = Steam::balanceInRange($account, $start, clone $end);
$previous = round(array_values($range)[0], 2);
while ($current <= $end) {
$format = $current->format('Y-m-d');
$balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
$previous = $balance;
$balances[] = $balance;
$current->addDay();
}
$account->balances = $balances;
}
$data = $this->generator->frontpage($accounts, $start, $end);
$cache->store($data);
return $data;
}
/**
* @param array $budgetIds
*
* @return array
*/
private function getBudgetNames(array $budgetIds): array
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$budgets = $repository->getBudgets();
$grouped = $budgets->groupBy('id')->toArray();
$return = [];
foreach ($budgetIds as $budgetId) {
if (isset($grouped[$budgetId])) {
$return[$budgetId] = $grouped[$budgetId][0]['name'];
}
}
$return[0] = trans('firefly.no_budget');
return $return;
}
/**
* Small helper function for some of the charts.
*
* @param array $categoryIds
*
* @return array
*/
private function getCategoryNames(array $categoryIds): array
{
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$categories = $repository->getCategories();
$grouped = $categories->groupBy('id')->toArray();
$return = [];
foreach ($categoryIds as $categoryId) {
if (isset($grouped[$categoryId])) {
$return[$categoryId] = $grouped[$categoryId][0]['name'];
}
}
$return[0] = trans('firefly.noCategory');
return $return;
}
}

View File

@@ -15,11 +15,13 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Bill;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Response;
/**
@@ -64,12 +66,11 @@ class BillController extends Controller
/**
* Shows the overview for a bill. The min/max amount and matched journals.
*
* @param BillRepositoryInterface $repository
* @param Bill $bill
* @param Bill $bill
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function single(BillRepositoryInterface $repository, Bill $bill)
public function single(Bill $bill)
{
$cache = new CacheProperties;
$cache->addProperty('single');
@@ -80,12 +81,14 @@ class BillController extends Controller
}
// get first transaction or today for start:
$results = $repository->getJournals($bill, 1, 200);
$collector = new JournalCollector(auth()->user());
$collector->setAllAssetAccounts()->setBills(new Collection([$bill]));
$results = $collector->getJournals();
// resort:
$results = $results->sortBy(
function (TransactionJournal $journal) {
return $journal->date->format('U');
function (Transaction $transaction) {
return $transaction->date->format('U');
}
);

View File

@@ -15,14 +15,15 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Log;
use Navigation;
use Preferences;
use Response;
@@ -145,7 +146,6 @@ class BudgetController extends Controller
*/
public function frontpage(BudgetRepositoryInterface $repository)
{
Log::debug('Hello');
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
// chart properties for cache:
@@ -175,7 +175,7 @@ class BudgetController extends Controller
$allEntries = $allEntries->merge($collection);
}
$entry = $this->spentInPeriodWithout($repository, $start, $end);
$entry = $this->spentInPeriodWithout($start, $end);
$allEntries->push($entry);
$data = $this->generator->frontpage($allEntries);
$cache->store($data);
@@ -183,79 +183,6 @@ class BudgetController extends Controller
return Response::json($data);
}
/**
*
* @param BudgetRepositoryInterface $repository
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Collection $budgets
*
*
* @return \Illuminate\Http\JsonResponse
*/
public function multiYear(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets)
{
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty($budgets);
$cache->addProperty('multiYearBudget');
if ($cache->has()) {
return Response::json($cache->get());
}
$budgetIds = $budgets->pluck('id')->toArray();
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
$budgeted = [];
$entries = new Collection;
// filter budgets once:
$repetitions = $repetitions->filter(
function (LimitRepetition $repetition) use ($budgetIds) {
if (in_array(strval($repetition->budget_id), $budgetIds)) {
return true;
}
return false;
}
);
/** @var LimitRepetition $repetition */
foreach ($repetitions as $repetition) {
$year = $repetition->startdate->year;
if (isset($budgeted[$repetition->budget_id][$year])) {
$budgeted[$repetition->budget_id][$year] = bcadd($budgeted[$repetition->budget_id][$year], $repetition->amount);
continue;
}
$budgeted[$repetition->budget_id][$year] = $repetition->amount;
}
foreach ($budgets as $budget) {
$currentStart = clone $start;
$entry = ['name' => $budget->name, 'spent' => [], 'budgeted' => []];
while ($currentStart < $end) {
// fix the date:
$currentEnd = clone $currentStart;
$year = $currentStart->year;
$currentEnd->endOfYear();
$spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd);
// jump to next year.
$currentStart = clone $currentEnd;
$currentStart->addDay();
$entry['spent'][$year] = round($spent * -1, 2);
$entry['budgeted'][$year] = isset($budgeted[$budget->id][$year]) ? round($budgeted[$budget->id][$year], 2) : 0;
}
$entries->push($entry);
}
$data = $this->generator->multiYear($entries);
$cache->store($data);
return Response::json($data);
}
/**
* @param BudgetRepositoryInterface $repository
@@ -277,40 +204,49 @@ class BudgetController extends Controller
$cache->addProperty('budget');
$cache->addProperty('period');
if ($cache->has()) {
return Response::json($cache->get());
return Response::json($cache->get());
}
// loop over period, add by users range:
$current = clone $start;
$viewRange = Preferences::get('viewRange', '1M')->data;
$set = new Collection;
// the expenses:
$periods = Navigation::listOfPeriods($start, $end);
$entries = $repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end);
$budgeted = [];
$key = Navigation::preferredCarbonFormat($start, $end);
$range = Navigation::preferredRangeFormat($start, $end);
// get budgeted:
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
$current = clone $start;
while ($current < $end) {
$currentStart = clone $current;
$currentEnd = Navigation::endOfPeriod($currentStart, $viewRange);
$reps = $repetitions->filter(
function (LimitRepetition $repetition) use ($budget, $currentStart) {
if ($repetition->budget_id === $budget->id && $repetition->startdate == $currentStart) {
$currentStart = Navigation::startOfPeriod($current, $range);
$currentEnd = Navigation::endOfPeriod($current, $range);
$reps = $repetitions->filter(
function (LimitRepetition $repetition) use ($budget, $currentStart, $currentEnd) {
if ($repetition->budget_id === $budget->id && $repetition->startdate >= $currentStart && $repetition->enddate <= $currentEnd) {
return true;
}
return false;
}
);
$budgeted = $reps->sum('amount');
$spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd);
$entry = [
'date' => clone $currentStart,
'budgeted' => $budgeted,
'spent' => $spent,
];
$set->push($entry);
$index = $currentStart->format($key);
$budgeted[$index] = $reps->sum('amount');
$currentEnd->addDay();
$current = clone $currentEnd;
}
$data = $this->generator->period($set, $viewRange);
// join them:
$result = [];
foreach (array_keys($periods) as $period) {
$nice = $periods[$period];
$result[$nice] = [
'spent' => isset($entries[$budget->id]['entries'][$period]) ? $entries[$budget->id]['entries'][$period] : '0',
'budgeted' => isset($entries[$period]) ? $budgeted[$period] : 0,
];
}
$data = $this->generator->period($result);
$cache->store($data);
return Response::json($data);
@@ -393,19 +329,22 @@ class BudgetController extends Controller
}
/**
* @param BudgetRepositoryInterface $repository
* @param Carbon $start
* @param Carbon $end
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
private function spentInPeriodWithout(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end):array
private function spentInPeriodWithout(Carbon $start, Carbon $end): array
{
$list = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); // budget
$sum = '0';
/** @var TransactionJournal $entry */
foreach ($list as $entry) {
$sum = bcadd(TransactionJournal::amount($entry), $sum);
// collector
$collector = new JournalCollector(auth()->user());
$types = [TransactionType::WITHDRAWAL];
$collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget();
$journals = $collector->getJournals();
$sum = '0';
/** @var Transaction $entry */
foreach ($journals as $entry) {
$sum = bcadd($entry->transaction_amount, $sum);
}
return [trans('firefly.no_budget'), '0', '0', $sum, '0', '0'];

View File

@@ -48,7 +48,6 @@ class CategoryController extends Controller
$this->generator = app(CategoryChartGeneratorInterface::class);
}
/**
* Show an overview for a category for all time, per month/week/year.
*
@@ -153,78 +152,6 @@ class CategoryController extends Controller
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Collection $categories
*
* @return \Illuminate\Http\JsonResponse
*/
public function multiYear(Carbon $start, Carbon $end, Collection $accounts, Collection $categories)
{
/** @var CRI $repository */
$repository = app(CRI::class);
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty($categories);
$cache->addProperty('multiYearCategory');
if ($cache->has()) {
return Response::json($cache->get());
}
$entries = new Collection;
/** @var Category $category */
foreach ($categories as $category) {
$entry = ['name' => '', 'spent' => [], 'earned' => []];
$currentStart = clone $start;
while ($currentStart < $end) {
// fix the date:
$year = $currentStart->year;
$currentEnd = clone $currentStart;
$currentEnd->endOfYear();
// get data:
if (is_null($category->id)) {
$entry['name'] = trans('firefly.noCategory');
$entry['spent'][$year] = ($repository->spentInPeriodWithoutCategory($accounts, $currentStart, $currentEnd) * -1);
$entry['earned'][$year] = $repository->earnedInPeriodWithoutCategory($accounts, $currentStart, $currentEnd);
// jump to next year.
$currentStart = clone $currentEnd;
$currentStart->addDay();
continue;
}
// alternative is a normal category:
$entry['name'] = $category->name;
$entry['spent'][$year] = ($repository->spentInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd) * -1);
$entry['earned'][$year] = $repository->earnedInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd);
// jump to next year.
$currentStart = clone $currentEnd;
$currentStart->addDay();
}
$entries->push($entry);
}
// generate chart with data:
$data = $this->generator->multiYear($entries);
$cache->store($data);
return Response::json($data);
}
/**
* @param CRI $repository
* @param Category $category
@@ -244,6 +171,7 @@ class CategoryController extends Controller
return Response::json($data);
}
/**
* @param CRI $repository
* @param Category $category

View File

@@ -0,0 +1,397 @@
<?php
/**
* CategoryReportController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface;
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use Illuminate\Support\Collection;
use Log;
use Response;
/**
* Separate controller because many helper functions are shared.
*
* Class CategoryReportController
*
* @package FireflyIII\Http\Controllers\Chart
*/
class CategoryReportController extends Controller
{
/** @var AccountRepositoryInterface */
private $accountRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var CategoryChartGeneratorInterface */
private $generator;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->generator = app(CategoryChartGeneratorInterface::class);
$this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function accountExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
{
/** @var bool $others */
$others = intval($others) === 1;
$names = [];
// collect journals (just like the category report does):
$set = $this->getExpenses($accounts, $categories, $start, $end);
$grouped = $this->groupByOpposingAccount($set);
// show the grouped results:
$result = [];
$total = '0';
foreach ($grouped as $accountId => $amount) {
if (!isset($names[$accountId])) {
$account = $this->accountRepository->find(intval($accountId));
$names[$accountId] = $account->name;
}
$amount = bcmul($amount, '-1');
$total = bcadd($total, $amount);
$result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount];
}
// also collect all transactions NOT in these categories.
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
$journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount'));
$sum = bcmul($sum, '-1');
Log::debug(sprintf('Sum of others in accountExpense is %f', $sum));
$sum = bcsub($sum, $total);
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
}
$data = $this->generator->pieChart($result);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function accountIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
{
/** @var bool $others */
$others = intval($others) === 1;
$names = [];
// collect journals (just like the category report does):
$set = $this->getIncome($accounts, $categories, $start, $end);
$grouped = $this->groupByOpposingAccount($set);
// loop and show the grouped results:
$result = [];
$total = '0';
foreach ($grouped as $accountId => $amount) {
if (!isset($names[$accountId])) {
$account = $this->accountRepository->find(intval($accountId));
$names[$accountId] = $account->name;
}
$total = bcadd($total, $amount);
$result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount];
}
// also collect others?
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
$journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount'));
Log::debug(sprintf('Sum of others in accountIncome is %f', $sum));
$sum = bcsub($sum, $total);
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
}
$data = $this->generator->pieChart($result);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function categoryExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
{
/** @var bool $others */
$others = intval($others) === 1;
$names = [];
// collect journals (just like the category report does):
$set = $this->getExpenses($accounts, $categories, $start, $end);
$grouped = $this->groupByCategory($set);
// show the grouped results:
$result = [];
$total = '0';
foreach ($grouped as $categoryId => $amount) {
if (!isset($names[$categoryId])) {
$category = $this->categoryRepository->find(intval($categoryId));
$names[$categoryId] = $category->name;
}
$amount = bcmul($amount, '-1');
$total = bcadd($total, $amount);
$result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount];
}
// also collect all transactions NOT in these categories.
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
$journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount'));
$sum = bcmul($sum, '-1');
Log::debug(sprintf('Sum of others in categoryExpense is %f', $sum));
$sum = bcsub($sum, $total);
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
}
$data = $this->generator->pieChart($result);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
{
/** @var bool $others */
$others = intval($others) === 1;
$names = [];
// collect journals (just like the category report does):
$set = $this->getIncome($accounts, $categories, $start, $end);
$grouped = $this->groupByCategory($set);
// loop and show the grouped results:
$result = [];
$total = '0';
foreach ($grouped as $categoryId => $amount) {
if (!isset($names[$categoryId])) {
$category = $this->categoryRepository->find(intval($categoryId));
$names[$categoryId] = $category->name;
}
$total = bcadd($total, $amount);
$result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount];
}
// also collect others?
if ($others) {
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
$journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount'));
Log::debug(sprintf('Sum of others in categoryIncome is %f', $sum));
$sum = bcsub($sum, $total);
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
}
$data = $this->generator->pieChart($result);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function mainChart(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
{
// determin optimal period:
$period = '1D';
$format = 'month_and_day';
$function = 'endOfDay';
if ($start->diffInMonths($end) > 1) {
$period = '1M';
$format = 'month';
$function = 'endOfMonth';
}
if ($start->diffInMonths($end) > 13) {
$period = '1Y';
$format = 'year';
$function = 'endOfYear';
}
Log::debug(sprintf('Period is %s', $period));
$data = [];
$currentStart = clone $start;
while ($currentStart < $end) {
$currentEnd = clone $currentStart;
Log::debug(sprintf('Function is %s', $function));
$currentEnd = $currentEnd->$function();
$expenses = $this->groupByCategory($this->getExpenses($accounts, $categories, $currentStart, $currentEnd));
$income = $this->groupByCategory($this->getIncome($accounts, $categories, $currentStart, $currentEnd));
$label = $currentStart->formatLocalized(strval(trans('config.' . $format)));
Log::debug(sprintf('Now grabbing CMC expenses between %s and %s', $currentStart->format('Y-m-d'), $currentEnd->format('Y-m-d')));
$data[$label] = [
'in' => [],
'out' => [],
];
/** @var Category $category */
foreach ($categories as $category) {
// get sum, and get label:
$categoryId = $category->id;
$data[$label]['name'][$categoryId] = $category->name;
$data[$label]['in'][$categoryId] = $income[$categoryId] ?? '0';
$data[$label]['out'][$categoryId] = $expenses[$categoryId] ?? '0';
}
$currentStart = clone $currentEnd;
$currentStart->addDay();
}
$data = $this->generator->mainReportChart($data);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getExpenses(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection
{
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setCategories($categories)->withOpposingAccount()->disableFilter();
$accountIds = $accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
return $set;
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection
{
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->setCategories($categories)->withOpposingAccount();
$accountIds = $accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$set = MonthReportGenerator::filterIncome($transactions, $accountIds);
return $set;
}
/**
* @param Collection $set
*
* @return array
*/
private function groupByCategory(Collection $set): array
{
// group by category ID:
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$jrnlCatId = intval($transaction->transaction_journal_category_id);
$transCatId = intval($transaction->transaction_category_id);
$categoryId = max($jrnlCatId, $transCatId);
$grouped[$categoryId] = $grouped[$categoryId] ?? '0';
$grouped[$categoryId] = bcadd($transaction->transaction_amount, $grouped[$categoryId]);
}
return $grouped;
}
/**
* @param Collection $set
*
* @return array
*/
private function groupByOpposingAccount(Collection $set): array
{
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$accountId = $transaction->opposing_account_id;
$grouped[$accountId] = $grouped[$accountId] ?? '0';
$grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]);
}
return $grouped;
}
}

View File

@@ -49,20 +49,18 @@ class ReportController extends Controller
* This chart, by default, is shown on the multi-year and year report pages,
* which means that giving it a 2 week "period" should be enough granularity.
*
* @param string $reportType
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
public function netWorth(string $reportType, Carbon $start, Carbon $end, Collection $accounts)
public function netWorth(Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty('netWorth');
$cache->addProperty($start);
$cache->addProperty($reportType);
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
@@ -92,52 +90,36 @@ class ReportController extends Controller
/**
* @param AccountTaskerInterface $accountTasker
* @param string $reportType
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
* @internal param AccountRepositoryInterface $repository
*/
public function yearInOut(AccountTaskerInterface $accountTasker, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
public function yearInOut(Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty('yearInOut');
$cache->addProperty($start);
$cache->addProperty($reportType);
$cache->addProperty($accounts);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get());
}
// always per month.
$currentStart = clone $start;
$spentArray = [];
$earnedArray = [];
while ($currentStart <= $end) {
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
$date = $currentStart->format('Y-m');
$spent = $accountTasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$earned = $accountTasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$spentArray[$date] = bcmul($spent, '-1');
$earnedArray[$date] = $earned;
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
}
$chartSource = $this->getYearData($accounts, $start, $end);
if ($start->diffInMonths($end) > 12) {
// data = method X
$data = $this->multiYearInOut($earnedArray, $spentArray, $start, $end);
$data = $this->multiYearInOut($chartSource['earned'], $chartSource['spent'], $start, $end);
$cache->store($data);
return Response::json($data);
}
// data = method Y
$data = $this->singleYearInOut($earnedArray, $spentArray, $start, $end);
$data = $this->singleYearInOut($chartSource['earned'], $chartSource['spent'], $start, $end);
$cache->store($data);
return Response::json($data);
@@ -146,16 +128,14 @@ class ReportController extends Controller
}
/**
* @param AccountTaskerInterface $accountTasker
* @param string $reportType
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
* @internal param AccountRepositoryInterface $repository
*/
public function yearInOutSummarized(AccountTaskerInterface $accountTasker, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
public function yearInOutSummarized(Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
@@ -163,35 +143,21 @@ class ReportController extends Controller
$cache->addProperty('yearInOutSummarized');
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($reportType);
$cache->addProperty($accounts);
if ($cache->has()) {
return Response::json($cache->get());
}
// always per month.
$currentStart = clone $start;
$spentArray = [];
$earnedArray = [];
while ($currentStart <= $end) {
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
$date = $currentStart->format('Y-m');
$spent = $accountTasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$earned = $accountTasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$spentArray[$date] = bcmul($spent, '-1');
$earnedArray[$date] = $earned;
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
}
$chartSource = $this->getYearData($accounts, $start, $end);
if ($start->diffInMonths($end) > 12) {
// per year
$data = $this->multiYearInOutSummarized($earnedArray, $spentArray, $start, $end);
$data = $this->multiYearInOutSummarized($chartSource['earned'], $chartSource['spent'], $start, $end);
$cache->store($data);
return Response::json($data);
}
// per month!
$data = $this->singleYearInOutSummarized($earnedArray, $spentArray, $start, $end);
$data = $this->singleYearInOutSummarized($chartSource['earned'], $chartSource['spent'], $start, $end);
$cache->store($data);
return Response::json($data);
@@ -342,4 +308,33 @@ class ReportController extends Controller
return $sum;
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
private function getYearData(Collection $accounts, Carbon $start, Carbon $end): array
{
$tasker = app(AccountTaskerInterface::class);
$currentStart = clone $start;
$spentArray = [];
$earnedArray = [];
while ($currentStart <= $end) {
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
$date = $currentStart->format('Y-m');
$spent = $tasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$earned = $tasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$spentArray[$date] = bcmul($spent, '-1');
$earnedArray[$date] = $earned;
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
}
return [
'spent' => $spentArray,
'earned' => $earnedArray,
];
}
}

View File

@@ -13,11 +13,15 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Session;
use View;
/**
@@ -46,38 +50,52 @@ class Controller extends BaseController
View::share('hideBills', false);
View::share('hideTags', false);
// save some formats:
$this->monthFormat = (string)trans('config.month');
$this->monthAndDayFormat = (string)trans('config.month_and_day');
$this->dateTimeFormat = (string)trans('config.date_time');
// translations:
$this->middleware(
function ($request, $next) {
$this->monthFormat = (string)trans('config.month');
$this->monthAndDayFormat = (string)trans('config.month_and_day');
$this->dateTimeFormat = (string)trans('config.date_time');
return $next($request);
}
);
}
/**
* @param TransactionJournal $journal
*
* @return bool
*/
protected function isOpeningBalance(TransactionJournal $journal): bool
{
return TransactionJournal::transactionTypeStr($journal) === TransactionType::OPENING_BALANCE;
}
/**
* Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
* and sum up everything in the array in the given range.
* @param TransactionJournal $journal
*
* @param Carbon $start
* @param Carbon $end
* @param array $array
*
* @return string
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
protected function getSumOfRange(Carbon $start, Carbon $end, array $array)
protected function redirectToAccount(TransactionJournal $journal)
{
$sum = '0';
$currentStart = clone $start; // to not mess with the original one
$currentEnd = clone $end; // to not mess with the original one
while ($currentStart <= $currentEnd) {
$date = $currentStart->format('Y-m-d');
if (isset($array[$date])) {
$sum = bcadd($sum, $array[$date]);
$valid = [AccountType::DEFAULT, AccountType::ASSET];
$transactions = $journal->transactions;
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$account = $transaction->account;
if (in_array($account->accountType->type, $valid)) {
return redirect(route('accounts.show', [$account->id]));
}
$currentStart->addDay();
}
return $sum;
}
Session::flash('error', strval(trans('firefly.cannot_redirect_to_account')));
return redirect(route('index'));
}
}

View File

@@ -39,8 +39,16 @@ class CurrencyController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.currencies'));
View::share('mainTitleIcon', 'fa-usd');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.currencies'));
View::share('mainTitleIcon', 'fa-usd');
return $next($request);
}
);
}
/**
@@ -121,7 +129,7 @@ class CurrencyController extends Controller
Session::flash('success', trans('firefly.deleted_currency', ['name' => $currency->name]));
if (auth()->user()->hasRole('owner')) {
$currency->delete();
$currency->forceDelete();
}
return redirect(session('currency.delete.url'));

View File

@@ -41,14 +41,22 @@ class ExportController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-file-archive-o');
View::share('title', trans('firefly.export_data'));
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-file-archive-o');
View::share('title', trans('firefly.export_data'));
return $next($request);
}
);
}
/**
* @param ExportJob $job
*
* @return mixed
* @return \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Routing\ResponseFactory
* @throws FireflyException
*/
public function download(ExportJob $job)
@@ -133,7 +141,6 @@ class ExportController extends Controller
'endDate' => new Carbon($request->get('export_end_range')),
'exportFormat' => $request->get('exportFormat'),
'includeAttachments' => intval($request->get('include_attachments')) === 1,
'includeConfig' => intval($request->get('include_config')) === 1,
'includeOldUploads' => intval($request->get('include_old_uploads')) === 1,
'job' => $job,
];
@@ -177,15 +184,6 @@ class ExportController extends Controller
$job->change('export_status_collected_old_uploads');
}
/*
* Generate / collect config file.
*/
if ($settings['includeConfig']) {
$job->change('export_status_creating_config_file');
$processor->createConfigFile();
$job->change('export_status_created_config_file');
}
/*
* Create ZIP file:
*/

View File

@@ -41,11 +41,9 @@ class HelpController extends Controller
*/
public function show(HelpInterface $help, string $route)
{
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
$content = [
'text' => '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>',
'title' => 'Help',
];
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
if (!$help->hasRoute($route)) {
Log::error('No such route: ' . $route);
@@ -53,17 +51,32 @@ class HelpController extends Controller
return Response::json($content);
}
if ($help->inCache($route)) {
$content = [
'text' => $help->getFromCache('help.' . $route . '.text.' . $language),
'title' => $help->getFromCache('help.' . $route . '.title.' . $language),
];
if ($help->inCache($route, $language)) {
$content = $help->getFromCache($route, $language);
Log::debug(sprintf('Help text %s was in cache.', $language));
return Response::json($content);
}
$content = $help->getFromGithub($language, $route);
// get backup language content (try English):
if (strlen($content) === 0) {
$language = 'en_US';
if ($help->inCache($route, $language)) {
Log::debug(sprintf('Help text %s was in cache.', $language));
$content = $help->getFromCache($route, $language);
}
if (!$help->inCache($route, $language)) {
$content = $help->getFromGithub($language, $route);
$content = '<p><em>' . strval(trans('firefly.help_may_not_be_your_language')) . '</em></p>' . $content;
}
}
if (strlen($content) === 0) {
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
}
$help->putInCache($route, $language, $content);
return Response::json($content);

View File

@@ -15,10 +15,11 @@ namespace FireflyIII\Http\Controllers;
use Artisan;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@@ -26,7 +27,7 @@ use Log;
use Preferences;
use Route;
use Session;
use View;
/**
* Class HomeController
@@ -41,6 +42,8 @@ class HomeController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', 'Firefly III');
View::share('mainTitleIcon', 'fa-fire');
}
/**
@@ -113,14 +116,12 @@ class HomeController extends Controller
}
/**
* @param ARI $repository
* @param AccountTaskerInterface $tasker
* @param ARI $repository
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
public function index(ARI $repository, AccountTaskerInterface $tasker)
public function index(ARI $repository)
{
$types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->count($types);
@@ -128,11 +129,9 @@ class HomeController extends Controller
return redirect(route('new-user.index'));
}
$title = 'Firefly';
$subTitle = trans('firefly.welcomeBack');
$mainTitleIcon = 'fa-fire';
$transactions = [];
$frontPage = Preferences::get(
$subTitle = trans('firefly.welcomeBack');
$transactions = [];
$frontPage = Preferences::get(
'frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray()
);
/** @var Carbon $start */
@@ -143,17 +142,20 @@ class HomeController extends Controller
$accounts = $repository->getAccountsById($frontPage->data);
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
foreach ($accounts as $account) {
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
$set = $set->splice(0, 10);
// zero bills? Hide some elements from view.
/** @var BillRepositoryInterface $billRepository */
$billRepository = app(BillRepositoryInterface::class);
$billCount = $billRepository->getBills()->count();
if (count($set) > 0) {
$transactions[] = [$set, $account];
}
foreach ($accounts as $account) {
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit(10)->setPage(1);
$set = $collector->getJournals();
$transactions[] = [$set, $account];
}
return view(
'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage')
'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage', 'billCount')
);
}
@@ -164,36 +166,26 @@ class HomeController extends Controller
public function routes()
{
// these routes are not relevant for the help pages:
$ignore = [
$ignore = ['login', 'registe', 'logout', 'two-fac', 'lost-two', 'confirm', 'resend', 'do_confirm', 'testFla', 'json.', 'piggy-banks.add',
'piggy-banks.remove', 'preferences.', 'rules.rule.up', 'rules.rule.down', 'rules.rule-group.up', 'rules.rule-group.down', 'popup.report',
'admin.users.domains.block-', 'import.json', 'help.',
];
$routes = Route::getRoutes();
$return = '<pre>';
/** @var \Illuminate\Routing\Route $route */
foreach ($routes as $route) {
$name = $route->getName();
$methods = $route->getMethods();
$search = [
'{account}', '{what}', '{rule}', '{tj}', '{category}', '{budget}', '{code}', '{date}', '{attachment}', '{bill}', '{limitrepetition}',
'{currency}', '{jobKey}', '{piggyBank}', '{ruleGroup}', '{rule}', '{route}', '{unfinishedJournal}',
'{reportType}', '{start_date}', '{end_date}', '{accountList}', '{tag}', '{journalList}',
];
$replace = [1, 'asset', 1, 1, 1, 1, 'abc', '2016-01-01', 1, 1, 1, 1, 1, 1, 1, 1, 'index', 1,
'default', '20160101', '20160131', '1,2', 1, '1,2',
];
if (count($search) != count($replace)) {
echo 'count';
exit;
}
$url = str_replace($search, $replace, $route->getUri());
if (!is_null($name) && in_array('GET', $methods) && !$this->startsWithAny($ignore, $name)) {
echo '<a href="/' . $url . '" title="' . $name . '">' . $name . '</a><br>' . "\n";
if (!is_null($name) && strlen($name) > 0 && in_array('GET', $methods) && !$this->startsWithAny($ignore, $name)) {
$return .= sprintf('touch %s.md', $name) . "\n";
}
}
$return .= '</pre><hr />';
return '<hr>';
return $return;
}
/**
@@ -215,10 +207,7 @@ class HomeController extends Controller
*
* @return bool
*/
private
function startsWithAny(
array $array, string $needle
): bool
private function startsWithAny(array $array, string $needle): bool
{
foreach ($array as $entry) {
if ((substr($needle, 0, strlen($entry)) === $entry)) {

View File

@@ -41,8 +41,15 @@ class ImportController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-archive');
View::share('title', trans('firefly.import_data_full'));
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-archive');
View::share('title', trans('firefly.import_data_full'));
return $next($request);
}
);
}
/**

View File

@@ -15,12 +15,12 @@ namespace FireflyIII\Http\Controllers;
use Amount;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Input;
@@ -270,17 +270,20 @@ class JsonController extends Controller
}
/**
* @param JournalTaskerInterface $tasker
* @param $what
* @param $what
*
* @return \Symfony\Component\HttpFoundation\Response
* @return \Illuminate\Http\JsonResponse
*/
public function transactionJournals(JournalTaskerInterface $tasker, $what)
public function transactionJournals($what)
{
$descriptions = [];
$type = config('firefly.transactionTypesByWhat.' . $what);
$types = [$type];
$journals = $tasker->getJournals($types, 1, 50);
// use journal collector instead:
$collector = new JournalCollector(auth()->user());
$collector->setTypes($types)->setLimit(100)->setPage(1);
$journals = $collector->getJournals();
foreach ($journals as $j) {
$descriptions[] = $j->description;
}

View File

@@ -32,6 +32,13 @@ class NewUserController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
return $next($request);
}
);
}
@@ -71,14 +78,16 @@ class NewUserController extends Controller
$this->createAssetAccount($request, $repository);
// create savings account
if (strlen($request->get('savings_balance')) > 0) {
$savingBalance = strval($request->get('savings_balance')) === '' ? '0' : strval($request->get('savings_balance'));
if (bccomp($savingBalance, '0') !== 0) {
$this->createSavingsAccount($request, $repository);
$count++;
}
// create credit card.
if (strlen($request->get('credit_card_limit')) > 0) {
$limit = strval($request->get('credit_card_limit')) === '' ? '0' : strval($request->get('credit_card_limit'));
if (bccomp($limit, '0') !== 0) {
$this->storeCreditCard($request, $repository);
$count++;
}
@@ -107,7 +116,6 @@ class NewUserController extends Controller
'accountType' => 'asset',
'virtualBalance' => 0,
'active' => true,
'user' => auth()->user()->id,
'accountRole' => 'defaultAsset',
'openingBalance' => round($request->input('bank_balance'), 2),
'openingBalanceDate' => new Carbon,
@@ -133,7 +141,6 @@ class NewUserController extends Controller
'accountType' => 'asset',
'virtualBalance' => 0,
'active' => true,
'user' => auth()->user()->id,
'accountRole' => 'savingAsset',
'openingBalance' => round($request->input('savings_balance'), 2),
'openingBalanceDate' => new Carbon,
@@ -158,7 +165,6 @@ class NewUserController extends Controller
'accountType' => 'asset',
'virtualBalance' => round($request->get('credit_card_limit'), 2),
'active' => true,
'user' => auth()->user()->id,
'accountRole' => 'ccAsset',
'openingBalance' => null,
'openingBalanceDate' => null,

View File

@@ -45,8 +45,16 @@ class PiggyBankController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.piggyBanks'));
View::share('mainTitleIcon', 'fa-sort-amount-asc');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.piggyBanks'));
View::share('mainTitleIcon', 'fa-sort-amount-asc');
return $next($request);
}
);
}
/**
@@ -99,6 +107,12 @@ class PiggyBankController extends Controller
$subTitle = trans('firefly.new_piggy_bank');
$subTitleIcon = 'fa-plus';
if (count($accounts) === 0) {
Session::flash('error', strval(trans('firefly.need_at_least_one_account')));
return redirect(route('new-user.index'));
}
// put previous url in session if not redirect from store (not "create another").
if (session('piggy-banks.create.fromStore') !== true) {
Session::put('piggy-banks.create.url', URL::previous());
@@ -157,6 +171,7 @@ class PiggyBankController extends Controller
$subTitle = trans('firefly.update_piggy_title', ['name' => $piggyBank->name]);
$subTitleIcon = 'fa-pencil';
$targetDate = null;
$note = $piggyBank->notes()->first();
/*
* Flash some data to fill the form.
*/
@@ -168,7 +183,7 @@ class PiggyBankController extends Controller
'account_id' => $piggyBank->account_id,
'targetamount' => $piggyBank->targetamount,
'targetdate' => $targetDate,
'note' => $piggyBank->notes()->first()->text,
'note' => is_null($note) ? '' : $note->text,
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'piggy-banks');
@@ -363,18 +378,8 @@ class PiggyBankController extends Controller
*/
public function store(PiggyBankFormRequest $request, PiggyBankRepositoryInterface $repository)
{
$piggyBankData = [
'name' => $request->get('name'),
'startdate' => new Carbon,
'account_id' => intval($request->get('account_id')),
'targetamount' => round($request->get('targetamount'), 2),
'order' => $repository->getMaxOrder() + 1,
'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null,
'note' => $request->get('note'),
];
$piggyBank = $repository->store($piggyBankData);
$data = $request->getPiggyBankData();
$piggyBank = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_piggy_bank', ['name' => e($piggyBank->name)])));
Preferences::mark();
@@ -399,16 +404,8 @@ class PiggyBankController extends Controller
*/
public function update(PiggyBankRepositoryInterface $repository, PiggyBankFormRequest $request, PiggyBank $piggyBank)
{
$piggyBankData = [
'name' => $request->get('name'),
'startdate' => is_null($piggyBank->startdate) ? $piggyBank->created_at : $piggyBank->startdate,
'account_id' => intval($request->get('account_id')),
'targetamount' => round($request->get('targetamount'), 2),
'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null,
'note' => $request->get('note'),
];
$piggyBank = $repository->update($piggyBank, $piggyBankData);
$data = $request->getPiggyBankData();
$piggyBank = $repository->update($piggyBank, $data);
Session::flash('success', strval(trans('firefly.updated_piggy_bank', ['name' => e($piggyBank->name)])));
Preferences::mark();

View File

@@ -17,12 +17,12 @@ namespace FireflyIII\Http\Controllers\Popup;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collection\BalanceLine;
use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\Binder\AccountList;
@@ -98,20 +98,39 @@ class ReportController extends Controller
$repository = app(AccountRepositoryInterface::class);
$account = $repository->find(intval($attributes['accountId']));
$types = [TransactionType::WITHDRAWAL];
switch (true) {
case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)):
$journals = $budgetRepository->journalsInPeriod(
new Collection([$budget]), new Collection([$account]), $attributes['startDate'], $attributes['endDate']
);
$collector = new JournalCollector(auth()->user());
$collector
->setAccounts(new Collection([$account]))
->setRange($attributes['startDate'], $attributes['endDate'])
->setBudget($budget);
$journals = $collector->getJournals();
break;
case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)):
$budget->name = strval(trans('firefly.no_budget'));
$journals = $budgetRepository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
// collector
$collector = new JournalCollector(auth()->user());
$collector
->setAccounts(new Collection([$account]))
->setTypes($types)
->setRange($attributes['startDate'], $attributes['endDate'])
->withoutBudget();
$journals = $collector->getJournals();
break;
case ($role === BalanceLine::ROLE_DIFFROLE):
// journals no budget, not corrected by a tag.
$journals = $budgetRepository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
$collector = new JournalCollector(auth()->user());
$collector
->setAccounts(new Collection([$account]))
->setTypes($types)
->setRange($attributes['startDate'], $attributes['endDate'])
->withoutBudget();
$journals = $collector->getJournals();
$budget->name = strval(trans('firefly.leftUnbalanced'));
$journals = $journals->filter(
function (TransactionJournal $journal) {
@@ -148,14 +167,21 @@ class ReportController extends Controller
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$budget = $repository->find(intval($attributes['budgetId']));
if (is_null($budget->id)) {
$journals = $repository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
} else {
// get all expenses in budget in period:
$journals = $repository->journalsInPeriod(new Collection([$budget]), $attributes['accounts'], $attributes['startDate'], $attributes['endDate']);
}
$collector = new JournalCollector(auth()->user());
$view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render();
$collector
->setAccounts($attributes['accounts'])
->setRange($attributes['startDate'], $attributes['endDate']);
if (is_null($budget->id)) {
$collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
}
if (!is_null($budget->id)) {
// get all expenses in budget in period:
$collector->setBudget($budget);
}
$journals = $collector->getJournals();
$view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render();
return $view;
}
@@ -173,8 +199,15 @@ class ReportController extends Controller
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$category = $repository->find(intval($attributes['categoryId']));
$journals = $repository->journalsInPeriod(new Collection([$category]), $attributes['accounts'], [], $attributes['startDate'], $attributes['endDate']);
$view = view('popup.report.category-entry', compact('journals', 'category'))->render();
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
// get journal collector instead:
$collector = new JournalCollector(auth()->user());
$collector->setAccounts($attributes['accounts'])->setTypes($types)
->setRange($attributes['startDate'], $attributes['endDate'])
->setCategory($category);
$journals = $collector->getJournals(); // 7193
$view = view('popup.report.category-entry', compact('journals', 'category'))->render();
return $view;
}
@@ -189,14 +222,14 @@ class ReportController extends Controller
*/
private function expenseEntry(array $attributes): string
{
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$account = $repository->find(intval($attributes['accountId']));
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$journals = $tasker->getJournalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']);
$account = $repository->find(intval($attributes['accountId']));
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$collector = new JournalCollector(auth()->user());
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types);
$journals = $collector->getJournals();
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
// filter for transfers and withdrawals TO the given $account
@@ -225,14 +258,14 @@ class ReportController extends Controller
*/
private function incomeEntry(array $attributes): string
{
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$account = $repository->find(intval($attributes['accountId']));
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$journals = $tasker->getJournalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']);
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
$collector = new JournalCollector(auth()->user());
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types);
$journals = $collector->getJournals();
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
// filter the set so the destinations outside of $attributes['accounts'] are not included.
$journals = $journals->filter(

View File

@@ -35,8 +35,16 @@ class PreferencesController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.preferences'));
View::share('mainTitleIcon', 'fa-gear');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.preferences'));
View::share('mainTitleIcon', 'fa-gear');
return $next($request);
}
);
}
/**

View File

@@ -13,7 +13,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Events\DeletedUser;
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest;
use FireflyIII\User;
@@ -36,8 +35,15 @@ class ProfileController extends Controller
{
parent::__construct();
View::share('title', trans('firefly.profile'));
View::share('mainTitleIcon', 'fa-user');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.profile'));
View::share('mainTitleIcon', 'fa-user');
return $next($request);
}
);
}
/**
@@ -45,9 +51,11 @@ class ProfileController extends Controller
*/
public function changePassword()
{
return view('profile.change-password')->with('title', auth()->user()->email)->with('subTitle', trans('firefly.change_your_password'))->with(
'mainTitleIcon', 'fa-user'
);
$title = auth()->user()->email;
$subTitle = strval(trans('firefly.change_your_password'));
$subTitleIcon = 'fa-key';
return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
@@ -55,9 +63,11 @@ class ProfileController extends Controller
*/
public function deleteAccount()
{
return view('profile.delete-account')->with('title', auth()->user()->email)->with('subTitle', trans('firefly.delete_account'))->with(
'mainTitleIcon', 'fa-user'
);
$title = auth()->user()->email;
$subTitle = strval(trans('firefly.delete_account'));
$subTitleIcon = 'fa-trash';
return view('profile.delete-account', compact('title', 'subTitle', 'subTitleIcon'));
}
/**

View File

@@ -17,6 +17,7 @@ namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
@@ -36,9 +37,23 @@ class AccountController extends Controller
*/
public function accountReport(Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$accountTasker = app(AccountTaskerInterface::class);
$accountReport = $accountTasker->getAccountReport($start, $end, $accounts);
return view('reports.partials.accounts', compact('accountReport'));
$result = view('reports.partials.accounts', compact('accountReport'))->render();
$cache->store($result);
return $result;
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* BalanceController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\BalanceReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
* Class BalanceController
*
* @package FireflyIII\Http\Controllers\Report
*/
class BalanceController extends Controller
{
/**
* @param BalanceReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function balanceReport(BalanceReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('balance-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$balance = $helper->getBalanceReport($start, $end, $accounts);
$result = view('reports.partials.balance', compact('balance'))->render();
$cache->store($result);
return $result;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* BudgetController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\BudgetReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
/**
* Class BudgetController
*
* @package FireflyIII\Http\Controllers\Report
*/
class BudgetController extends Controller
{
/**
*
* @param BudgetReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function budgetPeriodReport(BudgetReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('budget-period-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$periods = Navigation::listOfPeriods($start, $end);
$budgets = $helper->getBudgetPeriodReport($start, $end, $accounts);
$result = view('reports.partials.budget-period', compact('budgets', 'periods'))->render();
$cache->store($result);
return $result;
}
/**
* @param BudgetReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function budgetReport(BudgetReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('budget-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$budgets = $helper->getBudgetReport($start, $end, $accounts);
$result = view('reports.partials.budgets', compact('budgets'))->render();
$cache->store($result);
return $result;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* CategoryController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
* Class CategoryController
*
* @package FireflyIII\Http\Controllers\Report
*/
class CategoryController extends Controller
{
/**
* @param ReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function categoryReport(ReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('category-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$categories = $helper->getCategoryReport($start, $end, $accounts);
$result = view('reports.partials.categories', compact('categories'))->render();
$cache->store($result);
return $result;
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* InOutController.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
* Class InOutController
*
* @package FireflyIII\Http\Controllers\Report
*/
class InOutController extends Controller
{
/**
* @param ReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseReport(ReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$expenses = $helper->getExpenseReport($start, $end, $accounts);
$result = view('reports.partials.expenses', compact('expenses'))->render();
$cache->store($result);
return $result;
}
/**
* @param ReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
public function incExpReport(ReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('inc-exp-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$incomes = $helper->getIncomeReport($start, $end, $accounts);
$expenses = $helper->getExpenseReport($start, $end, $accounts);
$result = view('reports.partials.income-vs-expenses', compact('expenses', 'incomes'))->render();
$cache->store($result);
return $result;
}
/**
* @param ReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
public function incomeReport(ReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('income-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$incomes = $helper->getIncomeReport($start, $end, $accounts);
$result = view('reports.partials.income', compact('incomes'))->render();
$cache->store($result);
return $result;
}
}

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