Compare commits

...

807 Commits
4.3.6 ... 4.6.0

Author SHA1 Message Date
James Cole
63be574a14 Merge branch 'release/4.6.0' 2017-06-28 18:13:00 +02:00
James Cole
2414f998ba Update git ignore file. 2017-06-28 18:09:32 +02:00
James Cole
90e2ecad4f Merge pull request #683 from firefly-iii/l10n_develop
New Crowdin translations
2017-06-28 18:08:27 +02:00
James Cole
87a3f5c715 New translations firefly.php (Dutch) 2017-06-28 18:07:52 +02:00
James Cole
a4ef81ebd8 Improve test coverage. 2017-06-28 18:05:38 +02:00
James Cole
67fc810fc2 Ignore test database. 2017-06-28 18:05:04 +02:00
James Cole
e13a61645d New change log 2017-06-28 18:04:42 +02:00
James Cole
aae4a78fd4 Possible fix for #649 [skip ci] 2017-06-28 15:52:13 +02:00
James Cole
3947da5e27 Possible fix for #682 2017-06-28 15:45:28 +02:00
James Cole
4e4ce2f77c Remove test database from Firefly III thing. [skip ci] 2017-06-28 15:31:11 +02:00
James Cole
d9a2e081bd Update travis script. 2017-06-28 15:27:31 +02:00
James Cole
e60dde7ce5 Merge pull request #681 from firefly-iii/l10n_develop
New Crowdin translations
2017-06-28 15:18:59 +02:00
James Cole
699b138999 Small link fixes [skip ci] 2017-06-28 15:18:11 +02:00
James Cole
8f191b497f Updated read me and contributing guidelines. 2017-06-28 15:17:19 +02:00
James Cole
fd013d617c New translations firefly.php (Chinese Traditional) 2017-06-28 15:11:10 +02:00
James Cole
a8bda38035 New translations firefly.php (Dutch) 2017-06-28 15:11:07 +02:00
James Cole
411de9aa23 New translations firefly.php (French) 2017-06-28 15:11:05 +02:00
James Cole
f0abe497ef New translations firefly.php (German) 2017-06-28 15:11:02 +02:00
James Cole
9c8be83f5d New translations firefly.php (Polish) 2017-06-28 15:10:58 +02:00
James Cole
45335af8cf New translations firefly.php (Portuguese, Brazilian) 2017-06-28 15:10:56 +02:00
James Cole
945e433bb7 New translations firefly.php (Slovenian) 2017-06-28 15:10:51 +02:00
James Cole
70729fdb0c New translations firefly.php (Spanish) 2017-06-28 15:10:48 +02:00
James Cole
4a435e5701 Updated composer file. 2017-06-28 15:01:06 +02:00
James Cole
427747c6b8 Merge pull request #666 from firefly-iii/l10n_develop
New Crowdin translations
2017-06-28 15:00:48 +02:00
James Cole
be9407cb57 New translations csv.php (Dutch) 2017-06-28 15:00:19 +02:00
James Cole
c380d3d2a0 New translations firefly.php (Dutch) 2017-06-28 15:00:13 +02:00
James Cole
489b0eb12d New translations firefly.php (Dutch) 2017-06-28 14:50:13 +02:00
James Cole
7c73629962 Merge pull request #680 from Xeli/l10n_develop
Fix en_US translation: no_accounts_imperative_revenue.
2017-06-28 12:41:26 +02:00
richard@xeli.eu
ff619eca1c Fix en_US translation: no_accounts_imperative_revenue. Change Expense to Revenue 2017-06-28 12:12:31 +02:00
James Cole
134a39fd71 New translations firefly.php (German) 2017-06-27 13:50:38 +02:00
James Cole
506509b1b2 Fix #677 2017-06-26 18:29:45 +02:00
James Cole
02a36316be Forgot to remove a variable. 2017-06-26 18:24:44 +02:00
James Cole
8549c7c81b This should fix #677 2017-06-26 18:24:29 +02:00
James Cole
5a0be7d2ad Push new version number. 2017-06-24 13:05:12 +02:00
James Cole
c6f44c6398 Composer update. 2017-06-24 13:04:50 +02:00
James Cole
04a8a0e6a1 Code clean up 2017-06-24 13:04:41 +02:00
James Cole
8a175d147b Remove JS console log. 2017-06-24 13:03:09 +02:00
James Cole
5fbc319b20 This fixes the tests (must still upload test database). 2017-06-24 12:38:24 +02:00
James Cole
8e3ba7caf2 Fix various import bugs. 2017-06-24 08:37:09 +02:00
James Cole
182aaa7d27 New translations csv.php (Spanish) 2017-06-24 07:30:48 +02:00
James Cole
ec17d466f7 New translations firefly.php (Chinese Traditional) 2017-06-24 07:30:46 +02:00
James Cole
79c2445117 New translations firefly.php (Dutch) 2017-06-24 07:30:44 +02:00
James Cole
aa59db1609 New translations firefly.php (French) 2017-06-24 07:30:42 +02:00
James Cole
814b106be2 New translations csv.php (Slovenian) 2017-06-24 07:30:40 +02:00
James Cole
3901ea0fbe New translations csv.php (Portuguese, Brazilian) 2017-06-24 07:30:39 +02:00
James Cole
65133107be New translations csv.php (Dutch) 2017-06-24 07:30:38 +02:00
James Cole
08d15cfc5e New translations csv.php (French) 2017-06-24 07:30:37 +02:00
James Cole
3c89f9a5f1 New translations csv.php (German) 2017-06-24 07:30:36 +02:00
James Cole
dbc6bc8206 New translations csv.php (Polish) 2017-06-24 07:30:35 +02:00
James Cole
dc38291ef5 New translations firefly.php (German) 2017-06-24 07:30:34 +02:00
James Cole
3bb634ed5b New translations firefly.php (Polish) 2017-06-24 07:30:32 +02:00
James Cole
4bdcfe4d30 New translations firefly.php (Portuguese, Brazilian) 2017-06-24 07:30:28 +02:00
James Cole
3b74784486 New translations firefly.php (Slovenian) 2017-06-24 07:30:25 +02:00
James Cole
5226664a3d New translations firefly.php (Spanish) 2017-06-24 07:30:23 +02:00
James Cole
6d23823b63 New translations csv.php (Chinese Traditional) 2017-06-24 07:30:20 +02:00
James Cole
da3a56c144 Further improve import storage. 2017-06-24 07:30:05 +02:00
James Cole
58aa54d8cf Improving import storage. 2017-06-24 07:21:10 +02:00
James Cole
e525e673a8 Import routine cleanup. 2017-06-24 06:57:24 +02:00
James Cole
445dbf8779 More and improved code for the import routine. 2017-06-24 05:49:33 +02:00
James Cole
6a27bea2a3 New translations firefly.php (Chinese Traditional) 2017-06-22 22:01:08 +02:00
James Cole
13163f9c5c New translations firefly.php (Dutch) 2017-06-22 22:01:05 +02:00
James Cole
fde08f922b New translations firefly.php (French) 2017-06-22 22:01:02 +02:00
James Cole
266ce00872 New translations firefly.php (German) 2017-06-22 22:00:56 +02:00
James Cole
623a70a0d1 New translations firefly.php (Polish) 2017-06-22 22:00:52 +02:00
James Cole
1d7b738040 New translations firefly.php (Portuguese, Brazilian) 2017-06-22 22:00:46 +02:00
James Cole
edc8dd2601 New translations firefly.php (Slovenian) 2017-06-22 22:00:43 +02:00
James Cole
31dc932ca4 New translations firefly.php (Spanish) 2017-06-22 22:00:40 +02:00
James Cole
edb355941c More code for import routine. 2017-06-22 21:50:10 +02:00
James Cole
cddaccb7f7 Further improve import routine 2017-06-21 20:04:35 +02:00
James Cole
a905cce2c9 Expand import routine. 2017-06-20 21:04:25 +02:00
James Cole
b9f110ac2b Refactor and rename some import things. 2017-06-17 22:49:44 +02:00
James Cole
7cc24417b3 Expanded import routine. 2017-06-14 20:13:19 +02:00
James Cole
b304284d70 Fix report bug. 2017-06-14 19:15:30 +02:00
James Cole
c382fb1577 Add debug info for #671 2017-06-12 19:58:32 +02:00
James Cole
77244f4e2c Lots of new code for new importer routine. 2017-06-12 19:12:07 +02:00
James Cole
8beab5f5bc Fix #672 2017-06-12 17:21:31 +02:00
James Cole
902ae3f0cf Fix for #671 2017-06-12 17:07:29 +02:00
James Cole
519ef4e486 New translations csv.php (French) 2017-06-10 15:21:17 +02:00
James Cole
699e04f371 New translations firefly.php (French) 2017-06-10 15:21:16 +02:00
James Cole
d73de0e60d New translations csv.php (German) 2017-06-10 15:21:12 +02:00
James Cole
8e0b7d2a73 New translations firefly.php (German) 2017-06-10 15:21:10 +02:00
James Cole
04283cf2d6 New translations csv.php (Polish) 2017-06-10 15:21:06 +02:00
James Cole
d60b4aa56f New translations csv.php (Chinese Traditional) 2017-06-10 15:20:57 +02:00
James Cole
9cfeda1b0c New translations firefly.php (Chinese Traditional) 2017-06-10 15:20:55 +02:00
James Cole
5e1bd8e1eb New translations csv.php (Dutch) 2017-06-10 15:20:48 +02:00
James Cole
9b495d212b New translations firefly.php (Polish) 2017-06-10 15:20:47 +02:00
James Cole
b6fdd0070b New translations firefly.php (Slovenian) 2017-06-10 15:20:40 +02:00
James Cole
eea387ad0c New translations csv.php (Spanish) 2017-06-10 15:20:33 +02:00
James Cole
9cdb0f173a New translations csv.php (Slovenian) 2017-06-10 15:20:31 +02:00
James Cole
51063230d0 New translations firefly.php (Dutch) 2017-06-10 15:20:30 +02:00
James Cole
1111478b7f New translations firefly.php (Portuguese, Brazilian) 2017-06-10 15:20:25 +02:00
James Cole
c99c9b441f New translations csv.php (Portuguese, Brazilian) 2017-06-10 15:20:17 +02:00
James Cole
b5065a0276 New translations firefly.php (Spanish) 2017-06-10 15:20:11 +02:00
James Cole
c79a577060 Remove unused files in import. 2017-06-10 15:10:46 +02:00
James Cole
091596e80e Lots of new code for new importer routine. 2017-06-10 15:09:41 +02:00
James Cole
0b4efe4ae1 Small typo in chart. [skip ci] 2017-06-09 12:53:31 +02:00
James Cole
1f9b7faa60 Code for #660 2017-06-09 11:52:20 +02:00
James Cole
762d7bcc34 Fix database for postgresql 2017-06-09 11:51:59 +02:00
James Cole
a2145f6b49 Possible fix for #667 2017-06-08 10:54:15 +02:00
James Cole
b48de98865 Fix a bug where the balance routine forgot to account for accounts without a currency preference. 2017-06-08 10:35:02 +02:00
James Cole
a23179dd83 Merge branch 'release/4.5.0' 2017-06-07 12:30:09 +02:00
James Cole
5c18794122 Update lock file, and update database. 2017-06-07 12:22:59 +02:00
James Cole
f04011f6a7 New translations firefly.php (Dutch) 2017-06-07 12:20:13 +02:00
James Cole
893498238e Merge pull request #647 from firefly-iii/l10n_develop
New Crowdin translations
2017-06-07 12:11:28 +02:00
James Cole
474fa9dea0 New translations firefly.php (Dutch) 2017-06-07 12:10:10 +02:00
James Cole
d8a8574dda Prep for new release. 2017-06-07 12:08:32 +02:00
James Cole
935fb015d3 Live update budget amounts. 2017-06-07 11:58:04 +02:00
James Cole
8bbd3063ec Move code around for simplicity and fix tests. 2017-06-07 11:13:04 +02:00
James Cole
92c5cabd70 Try to untangle complex repositories 2017-06-07 08:18:42 +02:00
James Cole
e5db5a7b5c Various code clean up. 2017-06-07 07:38:58 +02:00
James Cole
28cf123da3 New translations firefly.php (French) 2017-06-06 20:41:24 +02:00
James Cole
5069367873 New translations firefly.php (German) 2017-06-06 20:41:16 +02:00
James Cole
234e656ff6 New translations firefly.php (Chinese Traditional) 2017-06-06 20:40:59 +02:00
James Cole
aeadfbdd6a New translations firefly.php (Polish) 2017-06-06 20:40:50 +02:00
James Cole
39288cfb0b New translations firefly.php (Slovenian) 2017-06-06 20:40:41 +02:00
James Cole
5e430968c1 New translations firefly.php (Dutch) 2017-06-06 20:40:34 +02:00
James Cole
154b74ce6f New translations firefly.php (Portuguese, Brazilian) 2017-06-06 20:40:26 +02:00
James Cole
2e26303b66 New translations firefly.php (Spanish) 2017-06-06 20:40:11 +02:00
James Cole
51ddcd9ee1 Plus not minus [skip ci] 2017-06-06 20:37:24 +02:00
James Cole
9d5d1c0a41 Updated budget view. 2017-06-06 20:35:39 +02:00
James Cole
6058ccff0d Merge pull request #664 from kressh/master
Add pgsql dependencies to Dockerfile
2017-06-06 20:01:43 +02:00
James Cole
a8ec4fe2fd New interface for budget overview. 2017-06-06 19:30:31 +02:00
James Cole
65ccb2d443 Fix error display #662 2017-06-06 19:29:10 +02:00
James Cole
0e929602a8 Verify currency data routine. 2017-06-06 07:23:54 +02:00
James Cole
5329e026dc Fixed various currency displays. 2017-06-06 07:18:09 +02:00
James Cole
a7412e43b3 Beter message for #662 2017-06-06 06:56:32 +02:00
Sergey Besedin
eeae24e058 Add pgsql dependencies 2017-06-06 01:22:09 +03:00
James Cole
17b6cc43d5 Fix display of foreign currencies in charts. 2017-06-05 22:11:54 +02:00
James Cole
b69a2ef0cd Can handle multi-currency balances better. 2017-06-05 15:09:17 +02:00
James Cole
6d1296094e Fixes a lot of issues in scrutinizer. 2017-06-05 11:20:38 +02:00
James Cole
c4039b53e6 Various code cleanup. 2017-06-05 11:12:50 +02:00
James Cole
64831b4c86 Code cleanup and refactoring. 2017-06-05 08:31:22 +02:00
James Cole
1dec270907 These changes fix the tests. 2017-06-05 07:37:53 +02:00
James Cole
f72f8b03df Catch empty currency preference 2017-06-05 07:03:32 +02:00
James Cole
0b47e5d05d Removed unnecessary variable. 2017-06-05 07:03:20 +02:00
James Cole
a487c7b4b2 Make sure amounts are formatted, and fixed some issues. 2017-06-04 23:39:26 +02:00
James Cole
3838b21459 New translations firefly.php (French) 2017-06-04 13:41:19 +02:00
James Cole
7801274d33 New translations firefly.php (German) 2017-06-04 13:41:15 +02:00
James Cole
a505406ee7 New translations firefly.php (Chinese Traditional) 2017-06-04 13:41:03 +02:00
James Cole
9487a95c13 New translations firefly.php (Polish) 2017-06-04 13:40:54 +02:00
James Cole
3b2fe13902 New translations firefly.php (Slovenian) 2017-06-04 13:40:47 +02:00
James Cole
db2898dfe5 New translations auth.php (Spanish) 2017-06-04 13:40:44 +02:00
James Cole
e08fd399d2 New translations csv.php (Spanish) 2017-06-04 13:40:40 +02:00
James Cole
e45ffba010 New translations passwords.php (Spanish) 2017-06-04 13:40:39 +02:00
James Cole
f7fde93ed2 New translations firefly.php (Dutch) 2017-06-04 13:40:37 +02:00
James Cole
3cf53604a1 New translations firefly.php (Portuguese, Brazilian) 2017-06-04 13:40:31 +02:00
James Cole
e041a5e037 New translations help.php (Spanish) 2017-06-04 13:40:19 +02:00
James Cole
6c8e10255b New translations form.php (Spanish) 2017-06-04 13:40:17 +02:00
James Cole
3464ab1527 New translations firefly.php (Spanish) 2017-06-04 13:40:13 +02:00
James Cole
82e74a2afd Big update to properly support multi currencies. 2017-06-04 13:39:16 +02:00
James Cole
771ebde295 Update journal collector so currency information is taken from the transaction. 2017-06-04 08:49:37 +02:00
James Cole
4ce4c3138c Update export routine so currency information is taken from the transaction. 2017-06-04 08:49:22 +02:00
James Cole
d37b46effc Database upgrade routine. 2017-06-04 08:48:54 +02:00
James Cole
0868aac750 Small update for 4.5.0 SQL update. 2017-06-04 08:48:40 +02:00
James Cole
4ff5f33966 Prep for solid multi-currency configuration. 2017-06-02 13:01:43 +02:00
James Cole
e1aebbe12b Remove non-existing charts. 2017-06-02 13:01:24 +02:00
James Cole
8273f467b6 Refactor some JS functions. 2017-06-02 13:00:43 +02:00
James Cole
74664afa68 Was not able to remove opening balance. 2017-06-02 13:00:24 +02:00
James Cole
c05f344371 Code clean up [skip ci] 2017-06-02 13:00:09 +02:00
James Cole
ec1507d644 Add some documentation [skip ci] 2017-06-02 12:59:27 +02:00
James Cole
8cdc1f0014 Rename several twig files. 2017-06-02 12:59:14 +02:00
James Cole
2b1ab5c6ef Fixed edit of multi currency transaction, ##651 2017-06-02 07:05:42 +02:00
James Cole
01fedc0bf8 Fix for #593, as inspired by @nhaarman. 2017-06-02 06:45:38 +02:00
James Cole
1bd82d71a2 New translations list.php (Spanish) 2017-05-25 16:50:09 +02:00
James Cole
1e9dacb6e4 New translations pagination.php (Spanish) 2017-05-25 16:40:16 +02:00
James Cole
94939ea3d3 New translations firefly.php (Portuguese, Brazilian) 2017-05-25 16:40:13 +02:00
James Cole
c3ff69d147 New translations list.php (Spanish) 2017-05-25 16:40:09 +02:00
James Cole
0fad9d4ac7 New translations validation.php (Spanish) 2017-05-25 16:40:07 +02:00
James Cole
70cd8ffb72 New translations validation.php (Spanish) 2017-05-25 16:30:10 +02:00
James Cole
61535bf4b8 New translations firefly.php (Spanish) 2017-05-25 16:30:08 +02:00
James Cole
4adcbf9e48 New translations demo.php (Spanish) 2017-05-25 16:20:11 +02:00
James Cole
3eba3167fd New translations firefly.php (Spanish) 2017-05-25 16:20:07 +02:00
James Cole
0608fd7732 New translations demo.php (Spanish) 2017-05-25 16:10:06 +02:00
James Cole
1f41f27e89 New translations breadcrumbs.php (Spanish) 2017-05-25 16:00:11 +02:00
James Cole
ded0df9303 New translations config.php (Spanish) 2017-05-25 16:00:06 +02:00
James Cole
2a7ba1893a New translations form.php (Slovenian) 2017-05-16 15:10:18 +02:00
James Cole
6b3b19632a New translations help.php (Slovenian) 2017-05-16 15:10:15 +02:00
James Cole
98bb0731df New translations list.php (Slovenian) 2017-05-16 15:10:12 +02:00
James Cole
a102f7044e New translations list.php (Slovenian) 2017-05-15 09:20:09 +02:00
James Cole
f1028dbaed New translations list.php (Slovenian) 2017-05-15 09:10:14 +02:00
James Cole
438ce5c3db New translations list.php (Slovenian) 2017-05-15 09:00:35 +02:00
James Cole
b995a1d091 New translations list.php (Slovenian) 2017-05-15 08:50:08 +02:00
James Cole
e072f83507 New translations list.php (Slovenian) 2017-05-14 10:10:08 +02:00
James Cole
79154bba25 New translations pagination.php (Slovenian) 2017-05-14 10:10:06 +02:00
James Cole
613eb7522c New translations pagination.php (Slovenian) 2017-05-14 10:00:10 +02:00
James Cole
0eb5653713 New translations csv.php (Slovenian) 2017-05-14 10:00:08 +02:00
James Cole
0d624f021b New translations csv.php (Slovenian) 2017-05-14 09:30:23 +02:00
James Cole
b263047f4e New translations csv.php (Slovenian) 2017-05-14 09:20:21 +02:00
James Cole
4410e1bbd7 New translations csv.php (Slovenian) 2017-05-14 09:10:12 +02:00
James Cole
8c3871e8de New translations csv.php (Slovenian) 2017-05-14 09:00:09 +02:00
James Cole
2eafd3cc15 Should fix #644 2017-05-14 08:57:43 +02:00
James Cole
368df66947 New translations validation.php (Slovenian) 2017-05-14 08:40:06 +02:00
James Cole
9f9a3ea8fd New translations breadcrumbs.php (Slovenian) 2017-05-14 01:20:08 +02:00
James Cole
f66286105f New translations validation.php (Slovenian) 2017-05-14 01:20:07 +02:00
James Cole
0f0f912370 Partial JS focus [skip ci] 2017-05-12 06:21:26 +02:00
James Cole
9fa0e37a5d Merge pull request #641 from firefly-iii/l10n_develop
New Crowdin translations
2017-05-12 06:20:05 +02:00
James Cole
a0cb51ff70 Merge branch 'develop' into l10n_develop 2017-05-12 06:19:56 +02:00
James Cole
a6305ddea4 New translations auth.php (Spanish) 2017-05-10 20:40:44 +02:00
James Cole
fb8638fe6a New translations breadcrumbs.php (Spanish) 2017-05-10 20:40:42 +02:00
James Cole
0009f1f865 New translations csv.php (Spanish) 2017-05-10 20:40:41 +02:00
James Cole
d449c35025 New translations passwords.php (Spanish) 2017-05-10 20:40:39 +02:00
James Cole
1893d1a2c2 New translations demo.php (Spanish) 2017-05-10 20:40:37 +02:00
James Cole
0c4539e4fa New translations validation.php (Spanish) 2017-05-10 20:40:31 +02:00
James Cole
feacddd1d7 Merge pull request #645 from elamperti/unfinished-translations-es_ES
Unfinished translations for Spanish language
2017-05-10 20:36:55 +02:00
Enrico Lamperti
0c44fe6ce0 Add translations for Spanish language 2017-05-07 21:24:07 -03:00
James Cole
17fb6983d8 Fix issue #637 with cash accounts. 2017-05-07 19:45:40 +02:00
James Cole
5fb73bdb01 Fix bug #642 2017-05-07 19:24:53 +02:00
James Cole
7f082ea389 Remove focus thing 2017-05-06 10:20:27 +02:00
James Cole
9856a1831a New translations firefly.php (Slovenian) 2017-05-04 17:40:13 +02:00
James Cole
dd17a20b60 New translations firefly.php (Slovenian) 2017-05-04 17:30:12 +02:00
James Cole
665a52b106 Merge branch 'release/4.4.3' 2017-05-03 21:25:44 +02:00
James Cole
f68d33870b Composer lock file and version update. 2017-05-03 21:25:13 +02:00
James Cole
49d13b12a5 No code coverage. 2017-05-03 21:22:01 +02:00
James Cole
674ab7e41f Merge branch 'develop' of https://github.com/firefly-iii/firefly-iii into develop
* 'develop' of https://github.com/firefly-iii/firefly-iii: (45 commits)
  New translations validation.php (Slovenian)
  New translations csv.php (Slovenian)
  New translations csv.php (Slovenian)
  New translations csv.php (Slovenian)
  New translations csv.php (Slovenian)
  New translations passwords.php (Slovenian)
  New translations csv.php (Slovenian)
  New translations csv.php (Slovenian)
  New translations config.php (Slovenian)
  New translations breadcrumbs.php (Slovenian)
  New translations breadcrumbs.php (Slovenian)
  New translations auth.php (Slovenian)
  New translations firefly.php (Slovenian)
  New translations breadcrumbs.php (Slovenian)
  New translations demo.php (Slovenian)
  New translations firefly.php (Slovenian)
  New translations firefly.php (Slovenian)
  New translations firefly.php (Slovenian)
  New translations firefly.php (Slovenian)
  New translations form.php (Slovenian)
  ...
2017-05-03 21:21:03 +02:00
James Cole
5b3eb3ba82 Merge pull request #639 from firefly-iii/l10n_develop
New Crowdin translations
2017-05-03 21:20:36 +02:00
James Cole
e28d3f3b5a Remove unused languages. 2017-05-03 21:14:37 +02:00
James Cole
870d8b5008 No prefix key, issue #624 2017-05-03 21:12:49 +02:00
James Cole
6aa240e9a3 Update version and changelog. 2017-05-03 21:12:19 +02:00
James Cole
b55a4047d0 New translations validation.php (Slovenian) 2017-05-03 17:50:07 +02:00
James Cole
a07d87318c New translations csv.php (Slovenian) 2017-05-03 17:40:08 +02:00
James Cole
dd6555c903 New translations csv.php (Slovenian) 2017-05-03 17:30:12 +02:00
James Cole
e8c40b6044 New translations csv.php (Slovenian) 2017-05-03 17:21:14 +02:00
James Cole
91c17a0b2a New translations csv.php (Slovenian) 2017-05-03 09:40:10 +02:00
James Cole
2ed0ea0243 New translations passwords.php (Slovenian) 2017-05-03 09:00:11 +02:00
James Cole
df2f92433a New translations csv.php (Slovenian) 2017-05-03 01:10:06 +02:00
James Cole
68b3fc72bf New translations csv.php (Slovenian) 2017-05-03 01:00:11 +02:00
James Cole
df5bb14758 New translations config.php (Slovenian) 2017-05-03 01:00:08 +02:00
James Cole
e24199bbbe New translations breadcrumbs.php (Slovenian) 2017-05-03 00:40:06 +02:00
James Cole
2c6099556b New translations breadcrumbs.php (Slovenian) 2017-05-03 00:30:13 +02:00
James Cole
f98215a5da New translations auth.php (Slovenian) 2017-05-03 00:30:11 +02:00
James Cole
b6b6888493 New translations firefly.php (Slovenian) 2017-05-03 00:30:09 +02:00
James Cole
f8e5b9be43 New translations breadcrumbs.php (Slovenian) 2017-05-03 00:20:10 +02:00
James Cole
692210214f New translations demo.php (Slovenian) 2017-05-03 00:20:08 +02:00
James Cole
cff2546c0c New translations firefly.php (Slovenian) 2017-05-03 00:10:14 +02:00
James Cole
f0cc1200f3 New translations firefly.php (Slovenian) 2017-05-03 00:00:14 +02:00
James Cole
e83a9af455 New translations firefly.php (Slovenian) 2017-05-02 23:50:09 +02:00
James Cole
fb84f9d9cf New translations firefly.php (Slovenian) 2017-05-02 23:40:12 +02:00
James Cole
118a2515e1 New translations form.php (Slovenian) 2017-05-02 23:40:09 +02:00
James Cole
63e891b0f7 New translations firefly.php (Slovenian) 2017-05-02 23:30:11 +02:00
James Cole
9891080b57 New translations firefly.php (Slovenian) 2017-05-02 23:20:10 +02:00
James Cole
bc7a7e55af New translations firefly.php (Slovenian) 2017-05-02 23:00:13 +02:00
James Cole
9b8a029de1 New translations demo.php (Slovenian) 2017-05-02 22:40:10 +02:00
James Cole
11b5575422 New translations csv.php (Slovenian) 2017-05-02 22:40:09 +02:00
James Cole
06336aa580 New translations form.php (Slovenian) 2017-05-02 22:40:07 +02:00
James Cole
aa8c1d6e9c New translations demo.php (Slovenian) 2017-05-02 22:30:08 +02:00
James Cole
9a4d5d8abf New translations firefly.php (Slovenian) 2017-05-02 22:20:11 +02:00
James Cole
3c5631bca3 New translations firefly.php (Slovenian) 2017-05-02 22:10:17 +02:00
James Cole
84f85f87b2 New translations firefly.php (Slovenian) 2017-05-02 22:00:13 +02:00
James Cole
c35db5976f New translations form.php (Slovenian) 2017-05-02 22:00:10 +02:00
James Cole
8d1fcf988c New translations firefly.php (Slovenian) 2017-05-02 21:50:11 +02:00
James Cole
bbf8f2dd69 New translations form.php (Slovenian) 2017-05-02 21:50:07 +02:00
James Cole
2f6436e34f New translations list.php (Slovenian) 2017-05-02 21:10:34 +02:00
James Cole
9cbb03107d New translations help.php (Slovenian) 2017-05-02 21:10:33 +02:00
James Cole
ee44d7fb2e New translations pagination.php (Slovenian) 2017-05-02 21:10:31 +02:00
James Cole
6a0fcd9cf0 New translations passwords.php (Slovenian) 2017-05-02 21:10:30 +02:00
James Cole
43cfe3b858 New translations demo.php (Slovenian) 2017-05-02 21:10:29 +02:00
James Cole
dba83c1c03 New translations validation.php (Slovenian) 2017-05-02 21:10:28 +02:00
James Cole
053e139d00 New translations form.php (Slovenian) 2017-05-02 21:10:26 +02:00
James Cole
04f791a839 New translations firefly.php (Slovenian) 2017-05-02 21:10:24 +02:00
James Cole
a4029c9490 New translations auth.php (Slovenian) 2017-05-02 21:10:20 +02:00
James Cole
d471dfec43 New translations csv.php (Slovenian) 2017-05-02 21:10:19 +02:00
James Cole
494bbd46d0 New translations config.php (Slovenian) 2017-05-02 21:10:17 +02:00
James Cole
1525b9ad06 New translations breadcrumbs.php (Slovenian) 2017-05-02 21:10:16 +02:00
James Cole
19847ee80b Change log update and added Slovenian. 2017-05-02 21:08:29 +02:00
James Cole
fddf1f146c Pie chart tests. 2017-05-02 20:54:49 +02:00
James Cole
5f19cb1c0c Fix canvas things. 2017-04-29 08:56:08 +02:00
James Cole
aaeae992e1 Reinstate support class. 2017-04-29 08:55:50 +02:00
James Cole
a0e7be9d45 Fix binding. 2017-04-29 08:55:37 +02:00
James Cole
78faf7e14c Forgot call to sprintf. [skip ci] 2017-04-29 08:33:32 +02:00
James Cole
98f84c2c37 Introducing filters to the journal collector. 2017-04-29 08:22:56 +02:00
James Cole
278805043e Update gitignore file. 2017-04-28 20:18:07 +02:00
James Cole
dc5215e41e Update tests. 2017-04-28 20:17:10 +02:00
James Cole
7e11691ea4 Update code to work with filters. 2017-04-28 20:08:25 +02:00
James Cole
c83dfc44d6 Update internal filters. 2017-04-28 20:08:04 +02:00
James Cole
68a01b1735 More text about Heroku. 2017-04-28 20:07:26 +02:00
James Cole
0307b58d17 Small changes to make code more testable. 2017-04-28 18:04:57 +02:00
James Cole
9d1508049e Add comment [skip ci] 2017-04-28 18:03:27 +02:00
James Cole
42322055f9 New filters to clean up the journal collector. 2017-04-28 18:02:54 +02:00
James Cole
5de8fce156 Make event handlers easier to test (and then ignore them). 2017-04-28 10:34:11 +02:00
James Cole
29ff92f833 Clean up event related code. 2017-04-28 07:51:43 +02:00
James Cole
359007c5bf Clean up event related code. 2017-04-28 07:51:09 +02:00
James Cole
bbe40518e4 Can now also test event code. 2017-04-27 08:26:58 +02:00
James Cole
b26f3c0cc6 User registration is now a Mailable. See #636 2017-04-27 08:03:15 +02:00
James Cole
552b4b67a6 Merge branch 'release/4.4.2' 2017-04-27 07:45:50 +02:00
James Cole
7fbf359efd New version [skip ci] 2017-04-27 07:45:15 +02:00
James Cole
052b804855 Fixed a bug where the opening balance could not be stored. 2017-04-27 07:44:35 +02:00
James Cole
67cc611495 Merge branch 'release/4.4.1' 2017-04-27 03:32:16 +02:00
James Cole
7e2c24b82e New test config 2017-04-27 03:29:11 +02:00
James Cole
4a6bbd4dca Update example config 2017-04-27 03:26:54 +02:00
James Cole
166bfba5b9 New version. [skip ci] 2017-04-27 03:17:34 +02:00
James Cole
87b8ac5f4e Update read me with deploy button [skip ci] 2017-04-27 03:16:27 +02:00
James Cole
46b4100291 Default key for instant deployment [skip ci] 2017-04-27 03:11:50 +02:00
James Cole
83090ade94 Generate app key [skip ci] 2017-04-27 03:06:39 +02:00
James Cole
44b7a42d5a Experimental deploy button script [skip ci] 2017-04-27 03:03:31 +02:00
James Cole
1a5617d430 Fixes a bug in the new user handler [skip ci] 2017-04-27 02:56:57 +02:00
James Cole
fa818e0924 Configure nginx [skip ci] 2017-04-27 02:49:06 +02:00
James Cole
f87b531fe1 Configure nginx [skip ci] 2017-04-27 02:45:26 +02:00
James Cole
82fd0c4d37 Set default db driver to be pgsql. [skip ci] 2017-04-27 02:40:57 +02:00
James Cole
26ceb9e3be Include config for Heroku. [skip ci] 2017-04-27 02:39:48 +02:00
James Cole
743458b853 Include config for Heroku. [skip ci] 2017-04-27 02:38:29 +02:00
James Cole
920e5f6fbe Force seed. 2017-04-26 21:32:43 +02:00
James Cole
fc0dd22769 Deploy heroku database. [skip ci] 2017-04-26 21:31:46 +02:00
James Cole
c7c61ce280 Log to error log. 2017-04-26 21:26:44 +02:00
James Cole
5f5a603f71 Update example file and config file for Laravel thing. [skip ci] 2017-04-26 21:02:21 +02:00
James Cole
d611858883 Remove compile step [skip ci] 2017-04-26 20:59:34 +02:00
James Cole
dbc9ce76a0 Try herocu config [skip ci] 2017-04-26 20:57:50 +02:00
James Cole
62386e2e40 Procfile for Herocu deployment. [skip ci] 2017-04-26 20:51:06 +02:00
James Cole
5aa7ab5e37 Merge branch 'release/4.4.0' 2017-04-23 19:07:19 +02:00
James Cole
be1b57eab7 New test database [skip ci] 2017-04-23 19:06:45 +02:00
James Cole
ad5c8b41e2 Merge pull request #635 from firefly-iii/l10n_develop
New Crowdin translations
2017-04-23 19:05:42 +02:00
James Cole
09752b25ad New translations firefly.php (Dutch) 2017-04-23 19:05:23 +02:00
James Cole
3f317f961b New translations firefly.php (French) 2017-04-23 19:01:25 +02:00
James Cole
d6d6a62fda New translations firefly.php (Chinese Traditional) 2017-04-23 19:01:17 +02:00
James Cole
528833b831 New translations firefly.php (Croatian) 2017-04-23 19:01:11 +02:00
James Cole
95e2bca4a6 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-04-23 19:01:06 +02:00
James Cole
dfd3327108 New translations firefly.php (German) 2017-04-23 19:01:01 +02:00
James Cole
653befbcd4 New translations firefly.php (Russian) 2017-04-23 19:00:55 +02:00
James Cole
33202efcdc New translations firefly.php (Spanish) 2017-04-23 19:00:50 +02:00
James Cole
77a37411ba New translations firefly.php (Dutch) 2017-04-23 19:00:45 +02:00
James Cole
491f221741 New translations firefly.php (Polish) 2017-04-23 19:00:37 +02:00
James Cole
e8e125a598 New translations firefly.php (Portuguese, Brazilian) 2017-04-23 19:00:25 +02:00
James Cole
cb01ae0ac5 New version and new change log. 2017-04-23 19:00:19 +02:00
James Cole
a09469c01f Merge pull request #633 from firefly-iii/l10n_develop
New Crowdin translations
2017-04-23 18:58:26 +02:00
James Cole
662f398b08 Update tests. 2017-04-23 18:53:00 +02:00
James Cole
d7aef627b8 Update composer file 2017-04-23 18:52:47 +02:00
James Cole
87bcf293aa New translations firefly.php (French) 2017-04-23 18:20:17 +02:00
James Cole
0171831ebb New translations form.php (French) 2017-04-23 18:10:19 +02:00
James Cole
50cc455f0f New translations firefly.php (French) 2017-04-23 18:10:18 +02:00
James Cole
47a7729358 New translations firefly.php (Dutch) 2017-04-23 09:40:30 +02:00
James Cole
6d508e61a0 New translations form.php (Dutch) 2017-04-23 09:40:28 +02:00
James Cole
8ef9223d84 Final code for #595 2017-04-23 09:33:33 +02:00
James Cole
e58ea4d0c7 New translations firefly.php (French) 2017-04-22 07:11:03 +02:00
James Cole
9629ba916e New translations firefly.php (Chinese Traditional) 2017-04-22 07:10:55 +02:00
James Cole
aad4df1596 New translations firefly.php (Croatian) 2017-04-22 07:10:51 +02:00
James Cole
30ed25f80a New translations firefly.php (Chinese Traditional, Hong Kong) 2017-04-22 07:10:47 +02:00
James Cole
8b7000681c New translations firefly.php (German) 2017-04-22 07:10:43 +02:00
James Cole
221adbf3db New translations firefly.php (Russian) 2017-04-22 07:10:38 +02:00
James Cole
39129d8cd0 New translations firefly.php (Spanish) 2017-04-22 07:10:34 +02:00
James Cole
2c2d444906 New translations firefly.php (Dutch) 2017-04-22 07:10:32 +02:00
James Cole
04a1d785bf New translations firefly.php (Polish) 2017-04-22 07:10:24 +02:00
James Cole
c0a0aa4652 New translations firefly.php (Portuguese, Brazilian) 2017-04-22 07:10:15 +02:00
James Cole
21c24fd7f0 Improved test coverage script. 2017-04-22 07:05:55 +02:00
James Cole
beb358f8ee Small code optimisation. 2017-04-22 07:05:44 +02:00
James Cole
e3cd11ec2e Various code coverage and test related fixes. 2017-04-22 07:05:31 +02:00
James Cole
ee08fc2421 All “all” views are now consistent #595 2017-04-22 07:04:39 +02:00
James Cole
ae30f7920b Fix bread crumbs for tags. 2017-04-19 17:11:07 +02:00
James Cole
f4786c3ec8 Fix balance view [skip ci] 2017-04-19 16:32:12 +02:00
James Cole
8b9e4d2539 New translations firefly.php (German) 2017-04-19 09:31:52 +02:00
James Cole
d7ca7ed632 New translations firefly.php (German) 2017-04-19 09:20:18 +02:00
James Cole
1346b25fc7 New translations firefly.php (German) 2017-04-19 09:10:18 +02:00
James Cole
15d9314503 New translations firefly.php (German) 2017-04-19 09:00:38 +02:00
James Cole
93f4006c9e New translations firefly.php (German) 2017-04-19 08:50:21 +02:00
James Cole
16182fec6c New translations validation.php (German) 2017-04-19 08:40:18 +02:00
James Cole
99606ba936 New translations form.php (German) 2017-04-19 08:40:15 +02:00
James Cole
be1ed56d42 New translations form.php (German) 2017-04-19 01:50:14 +02:00
James Cole
d7fb6f83b8 New translations firefly.php (German) 2017-04-19 00:30:15 +02:00
James Cole
665f1f470a New translations firefly.php (German) 2017-04-19 00:20:16 +02:00
James Cole
7867f26120 Improve report sums. 2017-04-17 08:31:42 +02:00
James Cole
8e195bf811 New translations firefly.php (French) 2017-04-16 22:21:16 +02:00
James Cole
e3403dc87f New translations firefly.php (Chinese Traditional) 2017-04-16 22:21:07 +02:00
James Cole
5844e95488 New translations firefly.php (Croatian) 2017-04-16 22:21:01 +02:00
James Cole
cb3a1f2ff6 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-04-16 22:20:56 +02:00
James Cole
f95edb06a9 New translations firefly.php (German) 2017-04-16 22:20:52 +02:00
James Cole
86d13060bc New translations firefly.php (Russian) 2017-04-16 22:20:45 +02:00
James Cole
0b8c2f6f8d New translations firefly.php (Spanish) 2017-04-16 22:20:40 +02:00
James Cole
589cafc64f New translations firefly.php (Dutch) 2017-04-16 22:20:37 +02:00
James Cole
448dc7b5c3 New translations firefly.php (Polish) 2017-04-16 22:20:25 +02:00
James Cole
10cfde6cab New translations firefly.php (Portuguese, Brazilian) 2017-04-16 22:20:15 +02:00
James Cole
e48eb2ce2f Clean up account taker amount inconsistencies. 2017-04-16 22:15:05 +02:00
James Cole
20a30a2d1d Various cosmetic fixes for /all lists [skip ci] 2017-04-16 12:51:21 +02:00
James Cole
2e5b8418ae New translations firefly.php (French) 2017-04-15 23:01:27 +02:00
James Cole
054fd229d2 New translations firefly.php (Chinese Traditional) 2017-04-15 23:01:18 +02:00
James Cole
9bdf2c8877 New translations firefly.php (Croatian) 2017-04-15 23:01:12 +02:00
James Cole
04f858a355 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-04-15 23:01:07 +02:00
James Cole
a9bf405a65 New translations firefly.php (German) 2017-04-15 23:01:01 +02:00
James Cole
463c866cfa New translations firefly.php (Russian) 2017-04-15 23:00:54 +02:00
James Cole
03fe0c8fff New translations firefly.php (Spanish) 2017-04-15 23:00:49 +02:00
James Cole
102d9f3510 New translations firefly.php (Dutch) 2017-04-15 23:00:44 +02:00
James Cole
3cf9a40c42 New translations firefly.php (Polish) 2017-04-15 23:00:33 +02:00
James Cole
ff33267296 New translations firefly.php (Portuguese, Brazilian) 2017-04-15 23:00:20 +02:00
James Cole
f0dab5bdb9 Code for #608 2017-04-15 22:52:01 +02:00
James Cole
209a907c61 Account chart can display multiple currencies. 2017-04-15 17:26:03 +02:00
James Cole
c7984d4363 Remove stray text. [skip ci] 2017-04-15 15:57:45 +02:00
James Cole
a388313e1c New translations form.php (French) 2017-04-15 08:21:13 +02:00
James Cole
a3933cb307 New translations form.php (Croatian) 2017-04-15 08:21:12 +02:00
James Cole
89c82d9be9 New translations form.php (Chinese Traditional) 2017-04-15 08:21:04 +02:00
James Cole
6e6311a1a1 New translations form.php (Chinese Traditional, Hong Kong) 2017-04-15 08:20:57 +02:00
James Cole
69b334ed8a New translations form.php (Russian) 2017-04-15 08:20:44 +02:00
James Cole
a84b6f377f New translations form.php (Dutch) 2017-04-15 08:20:37 +02:00
James Cole
ee5db4e4cc New translations form.php (Spanish) 2017-04-15 08:20:34 +02:00
James Cole
20f3322e5a New translations form.php (Polish) 2017-04-15 08:20:28 +02:00
James Cole
1e84bc3743 New translations form.php (German) 2017-04-15 08:20:24 +02:00
James Cole
7f198cbd50 New translations form.php (Portuguese, Brazilian) 2017-04-15 08:20:19 +02:00
James Cole
1c7d5ccefc Forgot some translations [skip ci] 2017-04-15 08:15:05 +02:00
James Cole
41dc4d994e Fix transaction journals that may be broken. 2017-04-15 08:13:05 +02:00
James Cole
490733bdd1 Some javascript clean up 2017-04-15 07:25:09 +02:00
James Cole
ed8cf8c431 New translations firefly.php (French) 2017-04-14 23:01:32 +02:00
James Cole
a7e1f85c4d New translations form.php (French) 2017-04-14 23:01:29 +02:00
James Cole
4b8919420d New translations form.php (Croatian) 2017-04-14 23:01:27 +02:00
James Cole
9afc5d67a4 New translations firefly.php (Chinese Traditional) 2017-04-14 23:01:21 +02:00
James Cole
72afaddd7c New translations form.php (Chinese Traditional) 2017-04-14 23:01:18 +02:00
James Cole
8ae3182e1c New translations firefly.php (Croatian) 2017-04-14 23:01:14 +02:00
James Cole
9d7e16b390 New translations form.php (Chinese Traditional, Hong Kong) 2017-04-14 23:01:10 +02:00
James Cole
3a2ed202ad New translations firefly.php (Chinese Traditional, Hong Kong) 2017-04-14 23:01:09 +02:00
James Cole
9e4b9b98ab New translations firefly.php (German) 2017-04-14 23:01:03 +02:00
James Cole
e65fb7d995 New translations firefly.php (Russian) 2017-04-14 23:00:56 +02:00
James Cole
7197830edc New translations form.php (Russian) 2017-04-14 23:00:53 +02:00
James Cole
687603ae84 New translations firefly.php (Spanish) 2017-04-14 23:00:50 +02:00
James Cole
23fc25f926 New translations firefly.php (Dutch) 2017-04-14 23:00:48 +02:00
James Cole
e0c1f07f92 New translations form.php (Dutch) 2017-04-14 23:00:45 +02:00
James Cole
9424aa3378 New translations form.php (Spanish) 2017-04-14 23:00:42 +02:00
James Cole
57e6e0945f New translations form.php (Polish) 2017-04-14 23:00:35 +02:00
James Cole
d87a033f29 New translations firefly.php (Polish) 2017-04-14 23:00:33 +02:00
James Cole
10481895e6 New translations form.php (German) 2017-04-14 23:00:29 +02:00
James Cole
813432f386 New translations form.php (Portuguese, Brazilian) 2017-04-14 23:00:23 +02:00
James Cole
aba78c9776 New translations firefly.php (Portuguese, Brazilian) 2017-04-14 23:00:19 +02:00
James Cole
8dc56bcee0 Fix line [skip ci] 2017-04-14 22:55:19 +02:00
James Cole
32b6e030ef Can now also edit transfers. 2017-04-14 22:49:12 +02:00
James Cole
a27f5d2474 Can now create transfers with different currencies. 2017-04-14 22:25:48 +02:00
James Cole
e2fe8cfb75 Clean up and consistency in foreign and native amounts. 2017-04-14 15:56:43 +02:00
James Cole
9f1c346365 New translations firefly.php (French) 2017-04-14 15:51:37 +02:00
James Cole
3e56803e95 New translations firefly.php (Chinese Traditional) 2017-04-14 15:51:26 +02:00
James Cole
fc5feb054d New translations firefly.php (Croatian) 2017-04-14 15:51:18 +02:00
James Cole
8fc233e436 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-04-14 15:51:12 +02:00
James Cole
22303eb2ff New translations firefly.php (German) 2017-04-14 15:51:06 +02:00
James Cole
d6477ff9ff New translations firefly.php (Russian) 2017-04-14 15:50:56 +02:00
James Cole
a0f6f13650 New translations firefly.php (Spanish) 2017-04-14 15:50:51 +02:00
James Cole
1dd213d587 New translations firefly.php (Dutch) 2017-04-14 15:50:48 +02:00
James Cole
569e51afbe New translations firefly.php (Polish) 2017-04-14 15:50:35 +02:00
James Cole
ff06a4ed0d New translations firefly.php (Portuguese, Brazilian) 2017-04-14 15:50:21 +02:00
James Cole
ea3fdb0668 Clean up and consistency in foreign and native amounts. 2017-04-14 15:42:54 +02:00
James Cole
bd917f6484 Deposit works as well. 2017-04-14 14:48:44 +02:00
James Cole
ee96311222 New translations form.php (Dutch) 2017-04-14 14:41:44 +02:00
James Cole
135153ff9c New translations form.php (French) 2017-04-14 14:41:30 +02:00
James Cole
fdb98133c8 New translations firefly.php (French) 2017-04-14 14:41:29 +02:00
James Cole
ad4e2c0a85 New translations form.php (Croatian) 2017-04-14 14:41:26 +02:00
James Cole
e38cb263aa New translations firefly.php (Chinese Traditional) 2017-04-14 14:41:19 +02:00
James Cole
0922136389 New translations form.php (Chinese Traditional) 2017-04-14 14:41:17 +02:00
James Cole
2a05517a30 New translations firefly.php (Croatian) 2017-04-14 14:41:13 +02:00
James Cole
a0ee924aeb New translations form.php (Chinese Traditional, Hong Kong) 2017-04-14 14:41:09 +02:00
James Cole
1595afce68 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-04-14 14:41:07 +02:00
James Cole
4919d730c8 New translations firefly.php (Russian) 2017-04-14 14:40:56 +02:00
James Cole
1407d6cc66 New translations form.php (Russian) 2017-04-14 14:40:53 +02:00
James Cole
55b092e7a0 New translations firefly.php (Dutch) 2017-04-14 14:40:49 +02:00
James Cole
93afb754f3 New translations form.php (Spanish) 2017-04-14 14:40:45 +02:00
James Cole
81c0ea8f5c New translations firefly.php (Spanish) 2017-04-14 14:40:44 +02:00
James Cole
6faacd3781 New translations firefly.php (Polish) 2017-04-14 14:40:36 +02:00
James Cole
ea1e9e407f New translations form.php (German) 2017-04-14 14:40:32 +02:00
James Cole
d4f5aa1578 New translations firefly.php (German) 2017-04-14 14:40:30 +02:00
James Cole
1cbbf3fa5d New translations form.php (Polish) 2017-04-14 14:40:25 +02:00
James Cole
dad3dc71ca New translations form.php (Portuguese, Brazilian) 2017-04-14 14:40:23 +02:00
James Cole
afb1e9f230 New translations firefly.php (Portuguese, Brazilian) 2017-04-14 14:40:22 +02:00
James Cole
c33dd1ecee Can now handle withdrawals in foreign currency. 2017-04-14 14:37:04 +02:00
James Cole
7e31a29b12 FF3 will now correctly store exchanged / foreign amounts. 2017-04-14 11:19:09 +02:00
James Cole
9a69ce309e Initial user ability to set foreign currency 2017-04-14 10:16:52 +02:00
James Cole
b99bfcd02e Fix null pointer [skip ci] 2017-04-14 07:46:19 +02:00
James Cole
adb16e4560 Also make sure that the account create screen sets the correct currency id. 2017-04-14 07:32:30 +02:00
James Cole
953c38563b Make sure that accounts and their opening balance values are the same currency. 2017-04-14 07:11:30 +02:00
James Cole
89ee9c058a Account list is capable of showing the selected currency 2017-04-13 21:36:23 +02:00
James Cole
bac7a73555 A transaction may now have a currency. This may proof to be useful when transferring money between accounts with different currencies. 2017-04-13 21:29:21 +02:00
James Cole
5fb6ff230b Two small code fixes. 2017-04-13 21:19:10 +02:00
James Cole
605a718418 Turns out open exchange rates is useless. 2017-04-13 21:19:00 +02:00
James Cole
994542c75d First basic code for currency exchange rate routines. 2017-04-13 20:47:59 +02:00
James Cole
d5fdce02fa New translations csv.php (Dutch) 2017-04-11 17:22:26 +02:00
James Cole
d3637de0c3 New translations csv.php (French) 2017-04-11 17:21:56 +02:00
James Cole
63acbb222d New translations csv.php (Chinese Traditional) 2017-04-11 17:21:44 +02:00
James Cole
5e1fe157b6 New translations csv.php (Chinese Traditional, Hong Kong) 2017-04-11 17:21:37 +02:00
James Cole
8e811da967 New translations csv.php (Croatian) 2017-04-11 17:21:30 +02:00
James Cole
fb23108f4e New translations csv.php (German) 2017-04-11 17:21:20 +02:00
James Cole
a0220eb5f8 New translations csv.php (Russian) 2017-04-11 17:21:08 +02:00
James Cole
c16cbd5bc8 New translations csv.php (Spanish) 2017-04-11 17:21:02 +02:00
James Cole
2d0673e1bc New translations csv.php (Portuguese, Brazilian) 2017-04-11 17:20:59 +02:00
James Cole
a90e00d577 New translations csv.php (Polish) 2017-04-11 17:20:40 +02:00
James Cole
8db96025a3 Code cleanup. 2017-04-09 07:56:46 +02:00
James Cole
595596d73f Apparently this is changed in PHPStorm’s formatting templates so there you go [skip ci]. 2017-04-09 07:44:22 +02:00
James Cole
240797e92a Fixes #630 [skip ci] 2017-04-09 07:36:58 +02:00
James Cole
6cafb91680 This fixes #629 2017-04-08 19:05:37 +02:00
James Cole
852ce3e32f Remove unused classes. 2017-04-08 18:02:16 +02:00
James Cole
6b9c9458fa This fixes possible null errors. 2017-04-08 18:00:45 +02:00
James Cole
970ce917b0 Merge branch 'release/4.3.8' 2017-04-08 17:54:10 +02:00
James Cole
db46d450bf New files for new release. 2017-04-08 17:51:34 +02:00
James Cole
db806b92dd Merge pull request #618 from firefly-iii/l10n_develop
New Crowdin translations
2017-04-08 10:20:55 +02:00
James Cole
6765f08b07 Fixed some tests. [skip ci] 2017-04-08 10:20:34 +02:00
James Cole
99d75ba14b New translations firefly.php (Dutch) 2017-04-08 10:10:23 +02:00
James Cole
0e8ce5680c New translations firefly.php (Dutch) 2017-04-08 10:00:17 +02:00
James Cole
fd01b54a14 Code for charts #628 [skip ci] 2017-04-08 09:18:04 +02:00
James Cole
ce6253bbd7 New translations firefly.php (Dutch) 2017-04-08 09:10:16 +02:00
James Cole
4fd33f19c6 Experimental code for #628 2017-04-08 09:00:37 +02:00
James Cole
01ae278f09 Float > intval. [skip ci] 2017-04-08 08:53:53 +02:00
James Cole
7907c71e47 Code to verify issue #620 2017-04-08 07:00:51 +02:00
James Cole
4e44733dcc Updated code for #624 2017-04-08 06:51:16 +02:00
James Cole
dd1d7bb02a Should fix issue #624 2017-04-01 08:56:39 +02:00
James Cole
6f933b3bd1 Small updates. 2017-04-01 08:13:43 +02:00
James Cole
243530b750 Forgot a return value. 2017-03-30 18:43:12 +02:00
James Cole
ea984281b0 Should fix tests. 2017-03-30 18:42:02 +02:00
James Cole
92cd3d60b9 Forgot two files. 2017-03-29 21:21:10 +02:00
James Cole
5920ccee04 Various code cleanup. 2017-03-29 21:20:54 +02:00
James Cole
569fec4610 Double check on NULL. [skip ci] 2017-03-25 22:07:07 +01:00
James Cole
5770edcde2 Expanded test coverage. 2017-03-25 13:41:17 +01:00
James Cole
1fb0a64f31 Various code cleanup [skip ci] 2017-03-24 15:15:12 +01:00
James Cole
fe66d089ad Expanded test coverage. 2017-03-24 15:01:53 +01:00
James Cole
cef10b4d4e New translations firefly.php (French) 2017-03-24 11:12:14 +01:00
James Cole
b45aa6446d New translations firefly.php (Chinese Traditional) 2017-03-24 11:11:56 +01:00
James Cole
61749312b2 New translations firefly.php (Croatian) 2017-03-24 11:11:48 +01:00
James Cole
842bb2c5a6 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-03-24 11:11:40 +01:00
James Cole
4358fe99b1 New translations firefly.php (Russian) 2017-03-24 11:11:25 +01:00
James Cole
3fda8b3714 New translations firefly.php (Dutch) 2017-03-24 11:11:13 +01:00
James Cole
09f3274adc New translations firefly.php (Spanish) 2017-03-24 11:11:04 +01:00
James Cole
029224f708 New translations firefly.php (Polish) 2017-03-24 11:10:52 +01:00
James Cole
e87cce12f8 New translations firefly.php (German) 2017-03-24 11:10:43 +01:00
James Cole
7660667153 New translations firefly.php (Portuguese, Brazilian) 2017-03-24 11:10:33 +01:00
James Cole
222b3008d5 Expanded test coverage. 2017-03-24 11:07:38 +01:00
James Cole
398cf0b312 Small improvements in the search [skip ci] 2017-03-24 07:17:38 +01:00
James Cole
5ad8be2483 This fixes the tests 2017-03-22 17:37:34 +01:00
James Cole
6fe319702d Expand test coverage. Remove else-statement. 2017-03-22 17:02:15 +01:00
James Cole
32c89f9a98 Expand category view. 2017-03-22 17:00:01 +01:00
James Cole
64f983786e New translations firefly.php (French) 2017-03-22 01:20:18 +01:00
James Cole
16438b416d New translations list.php (French) 2017-03-22 01:10:26 +01:00
James Cole
52cd292a69 New translations form.php (French) 2017-03-22 01:10:24 +01:00
James Cole
53b501df19 New translations firefly.php (French) 2017-03-22 01:10:23 +01:00
James Cole
477acafc4c Improve test coverage. 2017-03-21 20:46:14 +01:00
James Cole
ab9146b7c6 Update README.md
New badge
2017-03-21 14:24:10 +01:00
James Cole
4d15913e18 New translations csv.php (French) 2017-03-21 13:50:24 +01:00
James Cole
63a91811e2 New translations firefly.php (French) 2017-03-21 13:50:23 +01:00
James Cole
9df4da6173 New translations validation.php (French) 2017-03-21 13:50:14 +01:00
James Cole
643927799b New translations demo.php (French) 2017-03-21 11:50:25 +01:00
James Cole
2652e23089 New translations firefly.php (French) 2017-03-21 11:50:23 +01:00
James Cole
b70c5ae41e New translations demo.php (French) 2017-03-21 10:11:17 +01:00
James Cole
c53d9b2855 New translations demo.php (French) 2017-03-21 10:00:35 +01:00
James Cole
2f6712be87 New translations config.php (French) 2017-03-21 09:50:13 +01:00
James Cole
37f78985ae Update AccountController.php 2017-03-21 08:17:27 +01:00
James Cole
34ac9bc71a Update AccountController.php 2017-03-21 08:15:21 +01:00
James Cole
19ff2484fd Merge branch 'develop' of https://github.com/firefly-iii/firefly-iii into develop
* 'develop' of https://github.com/firefly-iii/firefly-iii:
  Changed to get relative path and not absolute path
2017-03-21 06:29:38 +01:00
James Cole
b7ef2211d9 Include Firefly version in error mail [skip ci] 2017-03-21 06:28:06 +01:00
James Cole
c5206a4559 Merge pull request #623 from welbert/develop
Changed to get relative path and not absolute path
2017-03-20 20:29:01 +01:00
Welbert Serra
894296f63d Changed to get relative path and not absolute path
If you access with absolute path, favicon don't appear on site and the [progressive webApp](https://developers.google.com/web/progressive-web-apps/) is not correctly generate. Get 404 error
2017-03-19 17:51:32 -03:00
James Cole
61526df245 Make dir where json log will end up. 2017-03-19 20:39:27 +01:00
James Cole
92549c4485 No more script removal. 2017-03-19 18:01:09 +01:00
James Cole
6014892d4d New database and new test script. 2017-03-19 17:58:07 +01:00
James Cole
9515ce6807 Expand tests. 2017-03-19 17:54:21 +01:00
James Cole
1adb0f2f0e Improve test coverage. 2017-03-18 20:53:44 +01:00
James Cole
00b1b54347 New test database. 2017-03-18 11:02:21 +01:00
James Cole
282ce041e1 Code cleanup for #595 2017-03-18 11:02:02 +01:00
James Cole
3215c4ee4b Expand code and some refactoring for #595 2017-03-18 08:09:14 +01:00
James Cole
fce00c95c9 New translations firefly.php (French) 2017-03-18 07:51:13 +01:00
James Cole
0a69ab1dc2 New translations firefly.php (Chinese Traditional) 2017-03-18 07:51:05 +01:00
James Cole
94771d250a New translations firefly.php (Croatian) 2017-03-18 07:50:59 +01:00
James Cole
b8395a1dbb New translations firefly.php (Chinese Traditional, Hong Kong) 2017-03-18 07:50:54 +01:00
James Cole
0436cd1584 New translations firefly.php (Russian) 2017-03-18 07:50:44 +01:00
James Cole
2c774bb94c New translations firefly.php (Dutch) 2017-03-18 07:50:39 +01:00
James Cole
b879d90b0d New translations firefly.php (Spanish) 2017-03-18 07:50:35 +01:00
James Cole
ba52b5c328 New translations firefly.php (German) 2017-03-18 07:50:27 +01:00
James Cole
bf61de13f4 New translations firefly.php (Polish) 2017-03-18 07:50:22 +01:00
James Cole
b26e67ae07 New translations firefly.php (Portuguese, Brazilian) 2017-03-18 07:50:19 +01:00
James Cole
edafd16c75 Bread crumbs for #595 2017-03-18 07:46:57 +01:00
James Cole
c62e3dcb78 Code for #595 2017-03-18 07:46:42 +01:00
James Cole
68be58c9f2 Line chart 2017-03-18 07:46:14 +01:00
James Cole
a25a499ed4 Code cleanup 2017-03-18 07:45:52 +01:00
James Cole
97f67912f4 Remove logging 2017-03-18 07:45:40 +01:00
James Cole
e2f3788ff5 Expand tests. 2017-03-17 16:34:57 +01:00
James Cole
fd1f06c2cb Small fix. 2017-03-16 21:00:34 +01:00
James Cole
2db8d25038 This should fix tests 2017-03-16 20:46:18 +01:00
James Cole
d618ddc8c5 Lot of debug info for #619 2017-03-16 17:52:13 +01:00
James Cole
79aa0afc97 Possible fix for #619 2017-03-15 20:09:36 +01:00
James Cole
e53d294c1c Expand tests. 2017-03-12 21:24:34 +01:00
James Cole
aedc3fdff9 Fix #620 2017-03-12 20:43:37 +01:00
James Cole
b67dfeced2 Expand tests. 2017-03-12 09:22:33 +01:00
James Cole
65dbfcba5c Expand tests. 2017-03-12 08:38:13 +01:00
James Cole
8f14c78ba1 Expand some tests. 2017-03-11 22:28:51 +01:00
James Cole
be1b64bb78 New translations demo.php (French) 2017-03-11 12:10:17 +01:00
James Cole
a34c285820 New translations demo.php (French) 2017-03-11 12:00:19 +01:00
James Cole
298f0c194d New translations firefly.php (French) 2017-03-11 08:01:29 +01:00
James Cole
f5ad569aba New translations firefly.php (Chinese Traditional) 2017-03-11 08:01:20 +01:00
James Cole
c70df01532 New translations firefly.php (Croatian) 2017-03-11 08:01:13 +01:00
James Cole
03e115e066 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-03-11 08:01:08 +01:00
James Cole
b88fce35ad New translations firefly.php (Russian) 2017-03-11 08:00:56 +01:00
James Cole
d214e9ff49 New translations firefly.php (Dutch) 2017-03-11 08:00:50 +01:00
James Cole
bd8382c005 New translations firefly.php (Spanish) 2017-03-11 08:00:45 +01:00
James Cole
18cb815e45 New translations firefly.php (German) 2017-03-11 08:00:34 +01:00
James Cole
800e8aca6e New translations firefly.php (Polish) 2017-03-11 08:00:27 +01:00
James Cole
63a6e0754b New translations firefly.php (Portuguese, Brazilian) 2017-03-11 08:00:23 +01:00
James Cole
4abc271805 Implemented #595 for transactions. 2017-03-11 07:41:26 +01:00
James Cole
22cca3858c New translations firefly.php (French) 2017-03-10 19:41:27 +01:00
James Cole
b816fbdf82 New translations firefly.php (Chinese Traditional) 2017-03-10 19:41:17 +01:00
James Cole
d400060b8b New translations firefly.php (Croatian) 2017-03-10 19:41:10 +01:00
James Cole
c88cfa2d9d New translations firefly.php (Chinese Traditional, Hong Kong) 2017-03-10 19:41:03 +01:00
James Cole
b060027304 New translations firefly.php (Russian) 2017-03-10 19:40:51 +01:00
James Cole
d4787aca9c New translations firefly.php (Dutch) 2017-03-10 19:40:45 +01:00
James Cole
69ae2e93f8 New translations firefly.php (Spanish) 2017-03-10 19:40:40 +01:00
James Cole
82a37c62c1 New translations firefly.php (German) 2017-03-10 19:40:30 +01:00
James Cole
ca69970242 New translations firefly.php (Polish) 2017-03-10 19:40:25 +01:00
James Cole
fac3853d95 New translations firefly.php (Portuguese, Brazilian) 2017-03-10 19:40:21 +01:00
James Cole
9dd2f447cc Fix in date range [skip ci] 2017-03-10 19:36:22 +01:00
James Cole
ecbd7ca95b Also implement #595 for the no-cat view. 2017-03-10 19:34:46 +01:00
James Cole
0c52d54d7d Optimized some views and fixed tests for #595 2017-03-10 18:54:50 +01:00
James Cole
78276ac66b New translations firefly.php (French) 2017-03-10 16:12:09 +01:00
James Cole
ca0a22d755 New translations firefly.php (Chinese Traditional) 2017-03-10 16:11:56 +01:00
James Cole
47361fd77a New translations firefly.php (Croatian) 2017-03-10 16:11:49 +01:00
James Cole
1af6fd5d74 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-03-10 16:11:42 +01:00
James Cole
51df78ad2d New translations firefly.php (Russian) 2017-03-10 16:11:27 +01:00
James Cole
84a3e68eb5 New translations firefly.php (Dutch) 2017-03-10 16:11:18 +01:00
James Cole
b2904e09d8 New translations firefly.php (Spanish) 2017-03-10 16:11:11 +01:00
James Cole
fc42a621e4 New translations firefly.php (German) 2017-03-10 16:10:55 +01:00
James Cole
a1c4fb73cd New translations firefly.php (Polish) 2017-03-10 16:10:46 +01:00
James Cole
c18f19db97 New translations firefly.php (Portuguese, Brazilian) 2017-03-10 16:10:40 +01:00
James Cole
ebc712f6b5 Consistency for #595 2017-03-10 16:08:58 +01:00
James Cole
ef0057d88d Add opposing account info [skip ci] #595 2017-03-10 15:28:04 +01:00
James Cole
a8fdfdd5e0 Add opposing account info [skip ci] #595 2017-03-10 15:27:19 +01:00
James Cole
89f7e1ba2a New translations firefly.php (French) 2017-03-09 21:11:36 +01:00
James Cole
6508b4058f New translations firefly.php (Chinese Traditional) 2017-03-09 21:11:25 +01:00
James Cole
bec5d93af6 New translations firefly.php (Croatian) 2017-03-09 21:11:18 +01:00
James Cole
1125fcd6c5 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-03-09 21:11:11 +01:00
James Cole
ff43a547a6 New translations firefly.php (Russian) 2017-03-09 21:10:57 +01:00
James Cole
ca8684146c New translations firefly.php (Dutch) 2017-03-09 21:10:51 +01:00
James Cole
44bea62383 New translations firefly.php (Spanish) 2017-03-09 21:10:45 +01:00
James Cole
8dc2faa5e5 New translations firefly.php (German) 2017-03-09 21:10:34 +01:00
James Cole
f4867d1d09 New translations firefly.php (Polish) 2017-03-09 21:10:29 +01:00
James Cole
8d172801b5 New translations firefly.php (Portuguese, Brazilian) 2017-03-09 21:10:24 +01:00
James Cole
db6e6dfe4a Fix tests for #595 2017-03-09 21:05:37 +01:00
James Cole
61007a95a6 Initial code for #595, transactions with no budget 2017-03-09 20:54:18 +01:00
James Cole
bf138670e8 New translations firefly.php (Dutch) 2017-03-09 08:20:49 +01:00
James Cole
0e59f7433c Code and tests for #615 2017-03-09 08:19:05 +01:00
James Cole
9a033ac62a New translations firefly.php (French) 2017-03-08 20:41:39 +01:00
James Cole
789412ee6a New translations firefly.php (Chinese Traditional) 2017-03-08 20:41:28 +01:00
James Cole
91125d20ef New translations firefly.php (Croatian) 2017-03-08 20:41:19 +01:00
James Cole
292b3672e2 New translations firefly.php (Chinese Traditional, Hong Kong) 2017-03-08 20:41:14 +01:00
James Cole
14ef6b753c New translations firefly.php (Russian) 2017-03-08 20:40:56 +01:00
James Cole
55fcb97a10 New translations firefly.php (Dutch) 2017-03-08 20:40:50 +01:00
James Cole
6b8ec544c1 New translations firefly.php (Spanish) 2017-03-08 20:40:44 +01:00
James Cole
c0aad385cd New translations firefly.php (German) 2017-03-08 20:40:33 +01:00
James Cole
ecfb5e4711 New translations firefly.php (Polish) 2017-03-08 20:40:26 +01:00
James Cole
46c9967a68 New translations firefly.php (Portuguese, Brazilian) 2017-03-08 20:40:23 +01:00
James Cole
176c44e2b9 Remove newline [skip ci] 2017-03-08 20:38:08 +01:00
James Cole
5957cf5ff8 Remove space. [skip ci] 2017-03-08 20:37:40 +01:00
James Cole
436a270524 Merge branch 'release/4.3.7' 2017-03-06 21:07:40 +01:00
James Cole
7a1c14b766 Update for new release. 2017-03-06 21:04:14 +01:00
James Cole
721c4aa888 Merge pull request #609 from firefly-iii/l10n_develop
New Crowdin translations
2017-03-06 20:54:52 +01:00
James Cole
638aab4eea Update TagRepository.php 2017-03-06 10:16:52 +01:00
James Cole
9aa44f7458 New translations 2017-03-05 19:00:15 +01:00
James Cole
1e887a2a8d Start replacing html comments with twig comments 2017-03-05 18:46:12 +01:00
James Cole
78571ad121 Update change log 2017-03-05 18:45:55 +01:00
James Cole
8e5ec79097 Wrote new tests. 2017-03-05 18:15:38 +01:00
James Cole
fc351f36b1 New translations 2017-03-05 13:31:21 +01:00
James Cole
80204147f7 New translations 2017-03-05 13:31:12 +01:00
James Cole
d599b8e5ea New translations 2017-03-05 13:31:06 +01:00
James Cole
208ef9d664 New translations 2017-03-05 13:31:00 +01:00
James Cole
c14da542d1 New translations 2017-03-05 13:30:48 +01:00
James Cole
451832cb2b New translations 2017-03-05 13:30:42 +01:00
James Cole
1409aeb8ed New translations 2017-03-05 13:30:38 +01:00
James Cole
342f24cfe4 New translations 2017-03-05 13:30:27 +01:00
James Cole
8aedfd5153 New translations 2017-03-05 13:30:22 +01:00
James Cole
33b099456f New translations 2017-03-05 13:30:18 +01:00
James Cole
42cb40102f Updated test database. 2017-03-05 13:26:31 +01:00
James Cole
2fbeaaccd3 Expand tests. 2017-03-05 13:21:36 +01:00
James Cole
57fb75bef4 Fix for #611 [skip ci] 2017-03-05 12:19:12 +01:00
James Cole
5c0e22cd31 Fix for #612 [skip ci] 2017-03-05 12:17:55 +01:00
James Cole
879f74e521 Code cleanup. 2017-03-05 11:19:06 +01:00
James Cole
e5a2e1a8c7 Updated tests 2017-03-05 11:18:34 +01:00
James Cole
884d6c59a2 Expand some tests. 2017-03-04 19:14:36 +01:00
James Cole
c206a95d55 Improved various tests. 2017-03-04 15:29:20 +01:00
James Cole
8b4ef4e2da Fix for #610 2017-03-04 13:08:16 +01:00
James Cole
bac5238589 New test database. 2017-03-04 11:21:42 +01:00
James Cole
ae05d4d51d Expand tests 2017-03-04 11:20:57 +01:00
James Cole
9d22bbee1c Add code coverage ignore instructions. 2017-03-04 11:19:44 +01:00
James Cole
1d6007b848 New translations 2017-03-04 07:31:20 +01:00
James Cole
3157339e44 New translations 2017-03-04 07:31:10 +01:00
James Cole
b31d5eefd1 New translations 2017-03-04 07:31:04 +01:00
James Cole
e47398dc71 New translations 2017-03-04 07:30:58 +01:00
James Cole
757d472704 New translations 2017-03-04 07:30:47 +01:00
James Cole
9d0ae99602 New translations 2017-03-04 07:30:41 +01:00
James Cole
0d9ceb6fde New translations 2017-03-04 07:30:37 +01:00
James Cole
cb665d4016 New translations 2017-03-04 07:30:27 +01:00
James Cole
9673dc96d9 New translations 2017-03-04 07:30:22 +01:00
James Cole
2295091427 New translations 2017-03-04 07:30:18 +01:00
James Cole
45f4395f26 Large commit to get rid of a lot of static methods. 2017-03-04 07:26:03 +01:00
James Cole
d9aa074330 Large commit to get rid of a lot of static methods. 2017-03-04 07:18:35 +01:00
James Cole
33c20c8dc4 Renamed a parameter 2017-03-04 06:54:05 +01:00
James Cole
8101f910f0 Update tests. 2017-03-04 06:53:46 +01:00
James Cole
8fb6c1a0c8 Various small changes. 2017-03-03 18:19:25 +01:00
James Cole
978e3e615c This prevented FF from displaying cash account properly. 2017-03-03 12:55:28 +01:00
James Cole
b3b8981b4b Catch null pointer exception. 2017-03-02 19:57:46 +01:00
James Cole
015064e5af Update composer.lock 2017-03-02 19:45:04 +01:00
James Cole
7ef8ff60a5 Update config files. 2017-03-02 19:41:17 +01:00
James Cole
4c3f54699e Merge pull request #590 from firefly-iii/l10n_develop
New Crowdin translations
2017-03-02 19:26:13 +01:00
James Cole
3bf5040324 Fixed null pointer in debug message [skip ci] 2017-03-02 16:42:33 +01:00
James Cole
ed9e5b31fa Merge pull request #606 from Zsub/fix-piggy-bank-interaction
Set default piggy for new transaction
2017-03-02 07:28:17 +01:00
Joris de Vries
506e97e12d Set default piggy for new transaction
Fixes #605. By explicitly setting the selected piggybank to the 0-piggy, new transactions will not inadvertently get coupled to a piggybank if the piggy’s name starts with characters that get sorted before `(` (such as `!` or `’`).
2017-03-01 22:45:37 +01:00
James Cole
9365f9ab60 This fixes #604 2017-03-01 21:13:45 +01:00
James Cole
dd1e9ecb32 This fixes #599 2017-03-01 21:02:47 +01:00
James Cole
d3a2bf174d This fixes #605 2017-03-01 20:57:52 +01:00
James Cole
311020ff2e This fixes #602 2017-03-01 20:49:16 +01:00
James Cole
a6df3ac1fb New translations 2017-02-26 14:40:10 +01:00
James Cole
d313b50e39 Updated composer.lock 2017-02-26 11:52:40 +01:00
James Cole
b23eb07018 Fix error when not-existing import job is submitted. 2017-02-26 11:48:38 +01:00
James Cole
2116486fe0 Various code cleanup 2017-02-25 17:39:50 +01:00
James Cole
eed8fe22c6 Make sure the loop is broken. #595 2017-02-25 13:34:44 +01:00
James Cole
1cec91e4bf New translations 2017-02-25 13:31:13 +01:00
James Cole
8decf8ab9f Make PHP modules mandatory in composer file. 2017-02-25 13:26:01 +01:00
James Cole
79b0c20adb Forgot about the date for account lists, #595 [skip ci] 2017-02-25 13:22:06 +01:00
James Cole
65647ca822 New translations 2017-02-25 13:21:20 +01:00
James Cole
18fafcd45f New translations 2017-02-25 13:21:10 +01:00
James Cole
b7723f4487 New translations 2017-02-25 13:21:04 +01:00
James Cole
6418df87bb New translations 2017-02-25 13:20:58 +01:00
James Cole
daa88c5be1 New translations 2017-02-25 13:20:47 +01:00
James Cole
707b70b136 New translations 2017-02-25 13:20:42 +01:00
James Cole
f9b3b6f7d3 New translations 2017-02-25 13:20:37 +01:00
James Cole
b295eef970 New translations 2017-02-25 13:20:28 +01:00
James Cole
f13dace7ad New translations 2017-02-25 13:20:22 +01:00
James Cole
8dfc40ed3e New translations 2017-02-25 13:20:18 +01:00
James Cole
2e637031ac Fix charts for #595, account overview. 2017-02-25 13:19:42 +01:00
James Cole
de9ef20014 First code for #595. Charts are still broken. 2017-02-25 13:13:51 +01:00
James Cole
4f50689d0e - Will now return 0 when nothing to save or when target date is in the past.
- Will calculate correctly when date difference with target date is more than a year.
- Will always return a string
- Will do calculations using bcmath module.
2017-02-25 13:05:33 +01:00
James Cole
082392f9e0 New translations 2017-02-25 13:01:34 +01:00
James Cole
aca2ef08f1 New translations 2017-02-25 13:01:24 +01:00
James Cole
90fcec4ca8 New translations 2017-02-25 13:01:17 +01:00
James Cole
c90db28b9e New translations 2017-02-25 13:01:11 +01:00
James Cole
56b617bd57 New translations 2017-02-25 13:00:56 +01:00
James Cole
06d58c16d8 New translations 2017-02-25 13:00:50 +01:00
James Cole
b1a807139a New translations 2017-02-25 13:00:45 +01:00
James Cole
ac07736040 New translations 2017-02-25 13:00:34 +01:00
James Cole
c68ba8d510 New translations 2017-02-25 13:00:28 +01:00
James Cole
470802c93b New translations 2017-02-25 13:00:24 +01:00
James Cole
c58745b6ce Merge pull request #597 from Zsub/make-text-translatable
Make suggested savings text translatable
2017-02-25 12:56:31 +01:00
James Cole
9eea4749f0 Remove IDE and environment specific files from gitignore, as inspired by #598. 2017-02-25 12:55:56 +01:00
Joris de Vries
251206fb75 Make suggested savings text translatable
PR #594 introduced suggested savings, this commit makes the text translatable.
2017-02-25 12:26:46 +01:00
James Cole
b53a6c5703 Update .travis.yml in this branch so it stops building this branch. 2017-02-25 06:13:08 +01:00
James Cole
8c6972d12d Various code cleanup. 2017-02-25 05:57:01 +01:00
James Cole
fe0d88b7b3 New translations 2017-02-25 05:31:24 +01:00
James Cole
658b6de9c7 New translations 2017-02-25 05:31:14 +01:00
James Cole
4ea89c3811 New translations 2017-02-25 05:31:07 +01:00
James Cole
2d3d71f46a New translations 2017-02-25 05:31:02 +01:00
James Cole
80a6f431b6 New translations 2017-02-25 05:30:49 +01:00
James Cole
0644729148 New translations 2017-02-25 05:30:43 +01:00
James Cole
07da48e5ea New translations 2017-02-25 05:30:38 +01:00
James Cole
b3f565819b New translations 2017-02-25 05:30:28 +01:00
James Cole
af4abfbed9 New translations 2017-02-25 05:30:22 +01:00
James Cole
eb0011cb46 New translations 2017-02-25 05:30:18 +01:00
James Cole
444439fdab Merge pull request #594 from Zsub/show-suggested-monthly-savings
Show suggested monthly savings for a piggybank
2017-02-25 05:24:05 +01:00
Joris de Vries
a0e66b913b Show suggested monthly savings for a piggybank
If a piggybank has both a target date and a target amount, show how much money needs to be added to the piggybank each month to achieve both targets.

Strings are currently hard-coded because I want to gauge the reaction to this :)
2017-02-24 22:00:49 +01:00
James Cole
96c780c804 New text for translation [skip ci] 2017-02-24 21:11:51 +01:00
James Cole
4f1f46aa93 New translations 2017-02-24 21:11:48 +01:00
James Cole
1d9f76ee5a New translations 2017-02-24 21:11:36 +01:00
James Cole
a7ceda7eea New translations 2017-02-24 21:11:28 +01:00
James Cole
baec4c4d70 New translations 2017-02-24 21:11:21 +01:00
James Cole
4fa850048c New translations 2017-02-24 21:11:04 +01:00
James Cole
8163655a24 New translations 2017-02-24 21:10:56 +01:00
James Cole
4cabf8d2e3 New translations 2017-02-24 21:10:49 +01:00
James Cole
db2f08fa96 New translations 2017-02-24 21:10:36 +01:00
James Cole
a68be2fed7 New translations 2017-02-24 21:10:29 +01:00
James Cole
c3e9aea3a7 New translations 2017-02-24 21:10:24 +01:00
James Cole
40c38af766 Final code for tag report. 2017-02-24 21:09:20 +01:00
James Cole
fc2cee7a54 Fixes tests. 2017-02-24 21:01:33 +01:00
James Cole
3d4feff7de More code for the tag report. 2017-02-24 20:27:26 +01:00
James Cole
f63c6875cd Initial code base for tag report. 2017-02-24 20:01:35 +01:00
James Cole
115b72149c New translations 2017-02-24 16:21:01 +01:00
James Cole
a7e8118c83 New translations 2017-02-24 16:11:55 +01:00
James Cole
8569cc5ac6 New translations 2017-02-24 16:11:43 +01:00
James Cole
7a0ffce36f New translations 2017-02-24 16:11:33 +01:00
James Cole
c573d95ec5 New translations 2017-02-24 16:11:27 +01:00
James Cole
d3e1fba4e0 New translations 2017-02-24 16:11:10 +01:00
James Cole
e7dd087b52 New translations 2017-02-24 16:10:59 +01:00
James Cole
c7cb79906c New translations 2017-02-24 16:10:53 +01:00
James Cole
3c3f80c5a0 New translations 2017-02-24 16:10:39 +01:00
James Cole
e20fb3c3ac New translations 2017-02-24 16:10:32 +01:00
James Cole
e3986a4dd4 New translations 2017-02-24 16:10:26 +01:00
James Cole
a3926e3996 New translations 2017-02-24 16:00:31 +01:00
James Cole
e737683efb Fine-tune some translations [skip ci] 2017-02-24 16:00:24 +01:00
James Cole
0157c4ed65 New translations 2017-02-24 15:50:41 +01:00
James Cole
058ade266d New translations 2017-02-24 09:21:23 +01:00
James Cole
e0adb4c397 New translations 2017-02-24 09:10:35 +01:00
James Cole
35aa61bb23 Different icon [skip ci] 2017-02-23 17:47:46 +01:00
James Cole
27236d19cf Clone, not copy [skip ci] 2017-02-23 17:47:36 +01:00
James Cole
063ca3121a Fixes #593 2017-02-23 17:43:29 +01:00
James Cole
b8521c6875 New translations 2017-02-23 07:41:38 +01:00
James Cole
e3798d6462 New translations 2017-02-23 07:41:28 +01:00
James Cole
387a3e541f New translations 2017-02-23 07:41:20 +01:00
James Cole
42a3b411e4 New translations 2017-02-23 07:41:15 +01:00
James Cole
25c7ec4175 New translations 2017-02-23 07:40:59 +01:00
James Cole
7ec11765da New translations 2017-02-23 07:40:52 +01:00
James Cole
d57ae126b7 New translations 2017-02-23 07:40:45 +01:00
James Cole
87f133555c New translations 2017-02-23 07:40:34 +01:00
James Cole
db9883d851 New translations 2017-02-23 07:40:27 +01:00
James Cole
8dad5ff7db New translations 2017-02-23 07:40:23 +01:00
James Cole
fc36f9cd8c Added empty box for bills as well. 2017-02-23 07:30:08 +01:00
James Cole
563c668e3f Code to catch empty lists and nudge user in the right direction. 2017-02-23 07:24:05 +01:00
James Cole
b3df1f3d26 Move template around 2017-02-23 07:02:28 +01:00
James Cole
1ac3f7af3b Included a message about translations after a PR was submitted that I had to close :( [skip ci] 2017-02-23 06:53:58 +01:00
James Cole
6cbb86aed7 New translations 2017-02-22 21:51:55 +01:00
James Cole
e459a8c88b New translations 2017-02-22 21:51:41 +01:00
James Cole
ca003b7d4d New translations 2017-02-22 21:51:33 +01:00
James Cole
03f873a6d3 New translations 2017-02-22 21:51:27 +01:00
James Cole
5dbec53847 New translations 2017-02-22 21:51:11 +01:00
James Cole
49dfad9b0c Approved. Step name: Proofread 2017-02-22 21:51:02 +01:00
James Cole
4ee0462d1b New translations 2017-02-22 21:50:55 +01:00
James Cole
3f78be4471 New translations 2017-02-22 21:50:41 +01:00
James Cole
d93fad39ac New translations 2017-02-22 21:50:33 +01:00
James Cole
aa3429bb0e New translations 2017-02-22 21:50:28 +01:00
James Cole
d88246f2f6 First code for #588 2017-02-22 21:40:27 +01:00
James Cole
201db34936 Update various sandstorm files. [skip ci] 2017-02-22 21:27:39 +01:00
James Cole
47709dfc7c Test catches some exceptions. 2017-02-22 20:35:31 +01:00
James Cole
2aaafc54ee Merge pull request #587 from Zsub/fix-tag-date
Fix saving a tag’s date
2017-02-22 20:21:25 +01:00
Joris de Vries
e211881691 Remove superfluous declaration of $date
The null check is already part of the `$this->date()` function and `$date` is never used.
2017-02-22 20:20:00 +01:00
Joris de Vries
0d32f16041 Fix saving a tag’s date
The `date` function takes the fieldname where a date is stored, not the literal date.
2017-02-22 20:07:30 +01:00
James Cole
2082e8d462 Small updates to read me [skip ci] 2017-02-22 17:58:57 +01:00
James Cole
bfc95cfc57 Update composer file. 2017-02-22 17:17:22 +01:00
James Cole
35aeb7e04a Update tag view 2017-02-22 17:15:54 +01:00
James Cole
adcddb09cd Updated link to installation guide. [skip ci] 2017-02-20 19:41:33 +01:00
729 changed files with 24773 additions and 16166 deletions

View File

@@ -41,6 +41,8 @@ SHOW_INCOMPLETE_TRANSLATIONS=false
CACHE_PREFIX=firefly CACHE_PREFIX=firefly
EXCHANGE_RATE_SERVICE=fixerio
GOOGLE_MAPS_API_KEY= GOOGLE_MAPS_API_KEY=
ANALYTICS_ID= ANALYTICS_ID=
SITE_OWNER=mail@example.com SITE_OWNER=mail@example.com

View File

@@ -3,6 +3,7 @@ APP_DEBUG=true
APP_FORCE_SSL=false APP_FORCE_SSL=false
APP_FORCE_ROOT= APP_FORCE_ROOT=
APP_KEY=TestTestTestTestTestTestTestTest APP_KEY=TestTestTestTestTestTestTestTest
APP_LOG=daily
APP_LOG_LEVEL=debug APP_LOG_LEVEL=debug
APP_URL=http://localhost APP_URL=http://localhost
@@ -25,7 +26,7 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_DRIVER=smtp MAIL_DRIVER=log
MAIL_HOST=mailtrap.io MAIL_HOST=mailtrap.io
MAIL_PORT=2525 MAIL_PORT=2525
MAIL_FROM=changeme@example.com MAIL_FROM=changeme@example.com

View File

@@ -4,16 +4,64 @@
## Feature requests ## Feature requests
If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/). I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/).
## Bugs ## Bugs
If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary. First of all: thank you for reporting a bug instead of ditching the tool altogether. If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary. Bugs have a lot of priority!
## Installation problems ## Installation problems
Take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! Please take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! If not, open an issue and I will help where I can.
## Pull requests ## Pull requests
I can only accept pull requests against the `develop` branch, never the `master` branch. When contributing to Firefly III, please first discuss the change you wish to make via issue, email, or any other method. I can only accept pull requests against the `develop` branch, never the `master` branch.
## Translations :us: :fr: :de:
If you see a spelling error, grammatical error or a weird translation in your language, please join [our CrowdIn](https://crowdin.com/project/firefly-iii) project. There, you can submit your translations and fixes. The GitHub repository will download these automatically and they will be included in the next release.
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at thegrumpydictator@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org/), version 1.4, available at [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4).

7
.gitignore vendored
View File

@@ -1,14 +1,7 @@
/node_modules /node_modules
/public/storage /public/storage
/vendor /vendor
/.idea
Homestead.json Homestead.json
Homestead.yaml Homestead.yaml
.env .env
_development
.env.local
result.html
test-import.sh
test-import-report.txt
public/google*.html public/google*.html
.env.backup

View File

@@ -232,27 +232,52 @@ opt/app/app/Console/Kernel.php
opt/app/app/Exceptions/Handler.php opt/app/app/Exceptions/Handler.php
opt/app/app/Generator/Chart/Basic/ChartJsGenerator.php opt/app/app/Generator/Chart/Basic/ChartJsGenerator.php
opt/app/app/Generator/Chart/Basic/GeneratorInterface.php opt/app/app/Generator/Chart/Basic/GeneratorInterface.php
opt/app/app/Generator/Report/ReportGeneratorFactory.php
opt/app/app/Generator/Report/ReportGeneratorInterface.php
opt/app/app/Generator/Report/Standard/MonthReportGenerator.php
opt/app/app/Helpers/Attachments/AttachmentHelper.php opt/app/app/Helpers/Attachments/AttachmentHelper.php
opt/app/app/Helpers/Attachments/AttachmentHelperInterface.php opt/app/app/Helpers/Attachments/AttachmentHelperInterface.php
opt/app/app/Helpers/Collection/Balance.php
opt/app/app/Helpers/Collection/BalanceEntry.php
opt/app/app/Helpers/Collection/BalanceHeader.php
opt/app/app/Helpers/Collection/BalanceLine.php
opt/app/app/Helpers/Collection/Bill.php
opt/app/app/Helpers/Collector/JournalCollector.php opt/app/app/Helpers/Collector/JournalCollector.php
opt/app/app/Helpers/Collector/JournalCollectorInterface.php opt/app/app/Helpers/Collector/JournalCollectorInterface.php
opt/app/app/Helpers/FiscalHelper.php opt/app/app/Helpers/FiscalHelper.php
opt/app/app/Helpers/FiscalHelperInterface.php opt/app/app/Helpers/FiscalHelperInterface.php
opt/app/app/Helpers/Report/BalanceReportHelper.php
opt/app/app/Helpers/Report/BalanceReportHelperInterface.php
opt/app/app/Helpers/Report/BudgetReportHelper.php
opt/app/app/Helpers/Report/BudgetReportHelperInterface.php
opt/app/app/Helpers/Report/ReportHelper.php opt/app/app/Helpers/Report/ReportHelper.php
opt/app/app/Helpers/Report/ReportHelperInterface.php opt/app/app/Helpers/Report/ReportHelperInterface.php
opt/app/app/Http/Controllers/AccountController.php
opt/app/app/Http/Controllers/Auth/LoginController.php opt/app/app/Http/Controllers/Auth/LoginController.php
opt/app/app/Http/Controllers/BillController.php
opt/app/app/Http/Controllers/BudgetController.php opt/app/app/Http/Controllers/BudgetController.php
opt/app/app/Http/Controllers/CategoryController.php
opt/app/app/Http/Controllers/Chart/AccountController.php opt/app/app/Http/Controllers/Chart/AccountController.php
opt/app/app/Http/Controllers/Chart/BudgetController.php opt/app/app/Http/Controllers/Chart/BudgetController.php
opt/app/app/Http/Controllers/Chart/CategoryController.php opt/app/app/Http/Controllers/Chart/CategoryController.php
opt/app/app/Http/Controllers/Controller.php opt/app/app/Http/Controllers/Controller.php
opt/app/app/Http/Controllers/HomeController.php opt/app/app/Http/Controllers/HomeController.php
opt/app/app/Http/Controllers/ImportController.php
opt/app/app/Http/Controllers/JavascriptController.php opt/app/app/Http/Controllers/JavascriptController.php
opt/app/app/Http/Controllers/JsonController.php opt/app/app/Http/Controllers/JsonController.php
opt/app/app/Http/Controllers/NewUserController.php opt/app/app/Http/Controllers/NewUserController.php
opt/app/app/Http/Controllers/PiggyBankController.php
opt/app/app/Http/Controllers/ProfileController.php opt/app/app/Http/Controllers/ProfileController.php
opt/app/app/Http/Controllers/Report/AccountController.php
opt/app/app/Http/Controllers/Report/BalanceController.php
opt/app/app/Http/Controllers/Report/BudgetController.php
opt/app/app/Http/Controllers/Report/CategoryController.php
opt/app/app/Http/Controllers/Report/OperationsController.php
opt/app/app/Http/Controllers/ReportController.php opt/app/app/Http/Controllers/ReportController.php
opt/app/app/Http/Controllers/RuleController.php
opt/app/app/Http/Controllers/TagController.php
opt/app/app/Http/Controllers/Transaction/SingleController.php opt/app/app/Http/Controllers/Transaction/SingleController.php
opt/app/app/Http/Controllers/TransactionController.php
opt/app/app/Http/Kernel.php opt/app/app/Http/Kernel.php
opt/app/app/Http/Middleware/Authenticate.php opt/app/app/Http/Middleware/Authenticate.php
opt/app/app/Http/Middleware/AuthenticateTwoFactor.php opt/app/app/Http/Middleware/AuthenticateTwoFactor.php
@@ -263,10 +288,16 @@ opt/app/app/Http/Middleware/RedirectIfAuthenticated.php
opt/app/app/Http/Middleware/Sandstorm.php opt/app/app/Http/Middleware/Sandstorm.php
opt/app/app/Http/Middleware/StartFireflySession.php opt/app/app/Http/Middleware/StartFireflySession.php
opt/app/app/Http/Middleware/VerifyCsrfToken.php opt/app/app/Http/Middleware/VerifyCsrfToken.php
opt/app/app/Http/Requests/BudgetFormRequest.php
opt/app/app/Http/Requests/BudgetIncomeRequest.php
opt/app/app/Http/Requests/CategoryFormRequest.php
opt/app/app/Http/Requests/JournalFormRequest.php opt/app/app/Http/Requests/JournalFormRequest.php
opt/app/app/Http/Requests/NewUserFormRequest.php opt/app/app/Http/Requests/NewUserFormRequest.php
opt/app/app/Http/Requests/PiggyBankFormRequest.php
opt/app/app/Http/Requests/ProfileFormRequest.php opt/app/app/Http/Requests/ProfileFormRequest.php
opt/app/app/Http/Requests/ReportFormRequest.php
opt/app/app/Http/Requests/Request.php opt/app/app/Http/Requests/Request.php
opt/app/app/Http/Requests/TagFormRequest.php
opt/app/app/Http/breadcrumbs.php opt/app/app/Http/breadcrumbs.php
opt/app/app/Jobs/Job.php opt/app/app/Jobs/Job.php
opt/app/app/Jobs/MailError.php opt/app/app/Jobs/MailError.php
@@ -279,9 +310,15 @@ opt/app/app/Models/Budget.php
opt/app/app/Models/BudgetLimit.php opt/app/app/Models/BudgetLimit.php
opt/app/app/Models/Category.php opt/app/app/Models/Category.php
opt/app/app/Models/Configuration.php opt/app/app/Models/Configuration.php
opt/app/app/Models/Note.php
opt/app/app/Models/PiggyBank.php opt/app/app/Models/PiggyBank.php
opt/app/app/Models/PiggyBankRepetition.php
opt/app/app/Models/Preference.php opt/app/app/Models/Preference.php
opt/app/app/Models/Role.php opt/app/app/Models/Role.php
opt/app/app/Models/Rule.php
opt/app/app/Models/RuleAction.php
opt/app/app/Models/RuleGroup.php
opt/app/app/Models/RuleTrigger.php
opt/app/app/Models/Tag.php opt/app/app/Models/Tag.php
opt/app/app/Models/Transaction.php opt/app/app/Models/Transaction.php
opt/app/app/Models/TransactionCurrency.php opt/app/app/Models/TransactionCurrency.php
@@ -317,14 +354,24 @@ opt/app/app/Repositories/Budget/BudgetRepository.php
opt/app/app/Repositories/Budget/BudgetRepositoryInterface.php opt/app/app/Repositories/Budget/BudgetRepositoryInterface.php
opt/app/app/Repositories/Category/CategoryRepository.php opt/app/app/Repositories/Category/CategoryRepository.php
opt/app/app/Repositories/Category/CategoryRepositoryInterface.php opt/app/app/Repositories/Category/CategoryRepositoryInterface.php
opt/app/app/Repositories/Currency/CurrencyRepository.php
opt/app/app/Repositories/Currency/CurrencyRepositoryInterface.php
opt/app/app/Repositories/Journal/JournalRepository.php opt/app/app/Repositories/Journal/JournalRepository.php
opt/app/app/Repositories/Journal/JournalRepositoryInterface.php opt/app/app/Repositories/Journal/JournalRepositoryInterface.php
opt/app/app/Repositories/PiggyBank/PiggyBankRepository.php opt/app/app/Repositories/PiggyBank/PiggyBankRepository.php
opt/app/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php opt/app/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
opt/app/app/Repositories/Rule/RuleRepository.php
opt/app/app/Repositories/Rule/RuleRepositoryInterface.php
opt/app/app/Repositories/RuleGroup/RuleGroupRepository.php
opt/app/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php
opt/app/app/Repositories/Tag/TagRepository.php opt/app/app/Repositories/Tag/TagRepository.php
opt/app/app/Repositories/Tag/TagRepositoryInterface.php opt/app/app/Repositories/Tag/TagRepositoryInterface.php
opt/app/app/Repositories/User/UserRepository.php
opt/app/app/Repositories/User/UserRepositoryInterface.php opt/app/app/Repositories/User/UserRepositoryInterface.php
opt/app/app/Support/Amount.php opt/app/app/Support/Amount.php
opt/app/app/Support/Binder/AccountList.php
opt/app/app/Support/Binder/BinderInterface.php
opt/app/app/Support/Binder/Date.php
opt/app/app/Support/CacheProperties.php opt/app/app/Support/CacheProperties.php
opt/app/app/Support/Domain.php opt/app/app/Support/Domain.php
opt/app/app/Support/ExpandedForm.php opt/app/app/Support/ExpandedForm.php
@@ -386,25 +433,40 @@ opt/app/database/seeds/PermissionSeeder.php
opt/app/database/seeds/TransactionCurrencySeeder.php opt/app/database/seeds/TransactionCurrencySeeder.php
opt/app/database/seeds/TransactionTypeSeeder.php opt/app/database/seeds/TransactionTypeSeeder.php
opt/app/public/css/bootstrap-multiselect.css opt/app/public/css/bootstrap-multiselect.css
opt/app/public/css/bootstrap-sortable.css
opt/app/public/css/bootstrap-tagsinput.css opt/app/public/css/bootstrap-tagsinput.css
opt/app/public/css/bootstrap-tour.min.css opt/app/public/css/bootstrap-tour.min.css
opt/app/public/css/daterangepicker.css opt/app/public/css/daterangepicker.css
opt/app/public/css/firefly.css opt/app/public/css/firefly.css
opt/app/public/css/jquery-ui/jquery-ui.structure.min.css opt/app/public/css/jquery-ui/jquery-ui.structure.min.css
opt/app/public/css/jquery-ui/jquery-ui.theme.min.css opt/app/public/css/jquery-ui/jquery-ui.theme.min.css
opt/app/public/favicon-16x16.png
opt/app/public/favicon-32x32.png
opt/app/public/index.php opt/app/public/index.php
opt/app/public/js/ff/accounts/edit.js
opt/app/public/js/ff/bills/create.js
opt/app/public/js/ff/budgets/index.js opt/app/public/js/ff/budgets/index.js
opt/app/public/js/ff/categories/index.js
opt/app/public/js/ff/charts.defaults.js opt/app/public/js/ff/charts.defaults.js
opt/app/public/js/ff/charts.js opt/app/public/js/ff/charts.js
opt/app/public/js/ff/firefly.js opt/app/public/js/ff/firefly.js
opt/app/public/js/ff/guest.js opt/app/public/js/ff/guest.js
opt/app/public/js/ff/help.js opt/app/public/js/ff/help.js
opt/app/public/js/ff/index.js opt/app/public/js/ff/index.js
opt/app/public/js/ff/piggy-banks/create.js
opt/app/public/js/ff/piggy-banks/index.js
opt/app/public/js/ff/reports/default/all.js
opt/app/public/js/ff/reports/default/month.js
opt/app/public/js/ff/reports/index.js opt/app/public/js/ff/reports/index.js
opt/app/public/js/ff/rules/index.js
opt/app/public/js/ff/tags/create-edit.js
opt/app/public/js/ff/tags/index.js
opt/app/public/js/ff/transactions/list.js
opt/app/public/js/ff/transactions/single/create.js opt/app/public/js/ff/transactions/single/create.js
opt/app/public/js/lib/Chart.bundle.min.js opt/app/public/js/lib/Chart.bundle.min.js
opt/app/public/js/lib/accounting.min.js opt/app/public/js/lib/accounting.min.js
opt/app/public/js/lib/bootstrap-multiselect.js opt/app/public/js/lib/bootstrap-multiselect.js
opt/app/public/js/lib/bootstrap-sortable.js
opt/app/public/js/lib/bootstrap-tagsinput.min.js opt/app/public/js/lib/bootstrap-tagsinput.min.js
opt/app/public/js/lib/bootstrap-tour.min.js opt/app/public/js/lib/bootstrap-tour.min.js
opt/app/public/js/lib/bootstrap3-typeahead.min.js opt/app/public/js/lib/bootstrap3-typeahead.min.js
@@ -417,6 +479,7 @@ opt/app/public/lib/adminlte/css/AdminLTE.min.css
opt/app/public/lib/adminlte/css/skins/skin-blue-light.min.css opt/app/public/lib/adminlte/css/skins/skin-blue-light.min.css
opt/app/public/lib/adminlte/js/app.min.js opt/app/public/lib/adminlte/js/app.min.js
opt/app/public/lib/bootstrap/css/bootstrap.min.css opt/app/public/lib/bootstrap/css/bootstrap.min.css
opt/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
opt/app/public/lib/bootstrap/js/bootstrap.min.js opt/app/public/lib/bootstrap/js/bootstrap.min.js
opt/app/public/lib/font-awesome/css/font-awesome.min.css opt/app/public/lib/font-awesome/css/font-awesome.min.css
opt/app/public/lib/font-awesome/fonts/fontawesome-webfont.woff2 opt/app/public/lib/font-awesome/fonts/fontawesome-webfont.woff2
@@ -425,9 +488,19 @@ opt/app/resources/lang/en_US/config.php
opt/app/resources/lang/en_US/firefly.php opt/app/resources/lang/en_US/firefly.php
opt/app/resources/lang/en_US/form.php opt/app/resources/lang/en_US/form.php
opt/app/resources/lang/en_US/help.php opt/app/resources/lang/en_US/help.php
opt/app/resources/lang/en_US/list.php
opt/app/resources/lang/en_US/validation.php opt/app/resources/lang/en_US/validation.php
opt/app/resources/views/accounts/delete.twig
opt/app/resources/views/accounts/edit.twig
opt/app/resources/views/accounts/index.twig
opt/app/resources/views/auth/login.twig opt/app/resources/views/auth/login.twig
opt/app/resources/views/bills/create.twig
opt/app/resources/views/bills/index.twig
opt/app/resources/views/budgets/create.twig
opt/app/resources/views/budgets/income.twig
opt/app/resources/views/budgets/index.twig opt/app/resources/views/budgets/index.twig
opt/app/resources/views/categories/create.twig
opt/app/resources/views/categories/index.twig
opt/app/resources/views/emails/error-html.twig opt/app/resources/views/emails/error-html.twig
opt/app/resources/views/emails/error-text.twig opt/app/resources/views/emails/error-text.twig
opt/app/resources/views/emails/footer-html.twig opt/app/resources/views/emails/footer-html.twig
@@ -437,18 +510,31 @@ opt/app/resources/views/emails/header-text.twig
opt/app/resources/views/error.twig opt/app/resources/views/error.twig
opt/app/resources/views/form/amount.twig opt/app/resources/views/form/amount.twig
opt/app/resources/views/form/balance.twig opt/app/resources/views/form/balance.twig
opt/app/resources/views/form/checkbox.twig
opt/app/resources/views/form/date.twig opt/app/resources/views/form/date.twig
opt/app/resources/views/form/feedback.twig opt/app/resources/views/form/feedback.twig
opt/app/resources/views/form/file.twig
opt/app/resources/views/form/help.twig opt/app/resources/views/form/help.twig
opt/app/resources/views/form/integer.twig
opt/app/resources/views/form/location.twig
opt/app/resources/views/form/multiRadio.twig
opt/app/resources/views/form/options.twig opt/app/resources/views/form/options.twig
opt/app/resources/views/form/select.twig opt/app/resources/views/form/select.twig
opt/app/resources/views/form/tags.twig
opt/app/resources/views/form/text.twig opt/app/resources/views/form/text.twig
opt/app/resources/views/form/textarea.twig
opt/app/resources/views/import/index.twig
opt/app/resources/views/index.twig opt/app/resources/views/index.twig
opt/app/resources/views/javascript/variables.twig opt/app/resources/views/javascript/variables.twig
opt/app/resources/views/json/tour.twig opt/app/resources/views/json/tour.twig
opt/app/resources/views/layout/default.twig opt/app/resources/views/layout/default.twig
opt/app/resources/views/layout/guest.twig opt/app/resources/views/layout/guest.twig
opt/app/resources/views/list/accounts.twig
opt/app/resources/views/list/bills.twig
opt/app/resources/views/list/categories.twig
opt/app/resources/views/list/journals-tasker.twig
opt/app/resources/views/list/journals-tiny-tasker.twig opt/app/resources/views/list/journals-tiny-tasker.twig
opt/app/resources/views/list/piggy-banks.twig
opt/app/resources/views/new-user/index.twig opt/app/resources/views/new-user/index.twig
opt/app/resources/views/partials/boxes.twig opt/app/resources/views/partials/boxes.twig
opt/app/resources/views/partials/control-bar.twig opt/app/resources/views/partials/control-bar.twig
@@ -456,11 +542,25 @@ opt/app/resources/views/partials/favicons.twig
opt/app/resources/views/partials/flashes.twig opt/app/resources/views/partials/flashes.twig
opt/app/resources/views/partials/menu-sidebar.twig opt/app/resources/views/partials/menu-sidebar.twig
opt/app/resources/views/partials/page-header.twig opt/app/resources/views/partials/page-header.twig
opt/app/resources/views/piggy-banks/create.twig
opt/app/resources/views/piggy-banks/index.twig
opt/app/resources/views/profile/change-password.twig opt/app/resources/views/profile/change-password.twig
opt/app/resources/views/profile/delete-account.twig opt/app/resources/views/profile/delete-account.twig
opt/app/resources/views/profile/index.twig opt/app/resources/views/profile/index.twig
opt/app/resources/views/reports/default/month.twig
opt/app/resources/views/reports/index.twig opt/app/resources/views/reports/index.twig
opt/app/resources/views/reports/options/no-options.twig opt/app/resources/views/reports/options/no-options.twig
opt/app/resources/views/reports/partials/accounts.twig
opt/app/resources/views/reports/partials/balance.twig
opt/app/resources/views/reports/partials/bills.twig
opt/app/resources/views/reports/partials/budgets.twig
opt/app/resources/views/reports/partials/categories.twig
opt/app/resources/views/reports/partials/income-expenses.twig
opt/app/resources/views/reports/partials/operations.twig
opt/app/resources/views/rules/index.twig
opt/app/resources/views/tags/create.twig
opt/app/resources/views/tags/index.twig
opt/app/resources/views/transactions/index.twig
opt/app/resources/views/transactions/single/create.twig opt/app/resources/views/transactions/single/create.twig
opt/app/routes/api.php opt/app/routes/api.php
opt/app/routes/console.php opt/app/routes/console.php
@@ -612,6 +712,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Http/Kernel.php
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Logging/Log.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Logging/Log.php
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/MailQueue.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/MailQueue.php
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/Mailer.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/Mailer.php
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/LengthAwarePaginator.php
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/Paginator.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/Paginator.php
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Factory.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Factory.php
@@ -682,6 +783,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Conc
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Scope.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Scope.php
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php
@@ -808,8 +911,11 @@ opt/app/vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php
opt/app/vendor/laravel/framework/src/Illuminate/Notifications/NotificationServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Notifications/NotificationServiceProvider.php
opt/app/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php opt/app/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php opt/app/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php opt/app/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindow.php
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/default.blade.php
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php
opt/app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php opt/app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php
@@ -884,12 +990,14 @@ opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/App.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Config.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Config.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cookie.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Event.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Event.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Gate.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Gate.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Input.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Input.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Log.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Log.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Mail.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Mail.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Request.php opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Request.php
@@ -914,6 +1022,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Support/ViewErrorBag.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/helpers.php opt/app/vendor/laravel/framework/src/Illuminate/Support/helpers.php
opt/app/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php
opt/app/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php
opt/app/vendor/laravel/framework/src/Illuminate/Translation/MessageSelector.php
opt/app/vendor/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php
opt/app/vendor/laravel/framework/src/Illuminate/Translation/Translator.php opt/app/vendor/laravel/framework/src/Illuminate/Translation/Translator.php
opt/app/vendor/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php opt/app/vendor/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php
@@ -1214,14 +1323,17 @@ opt/app/vendor/twig/twig/lib/Twig/Node/Expression.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Array.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Array.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Call.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Call.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php

View File

@@ -16,7 +16,7 @@ const pkgdef :Spk.PackageDefinition = (
manifest = ( manifest = (
appTitle = (defaultText = "Firefly III"), appTitle = (defaultText = "Firefly III"),
appVersion = 1, appVersion = 1,
appMarketingVersion = (defaultText = "3.4.3"), appMarketingVersion = (defaultText = "3.4.6"),
actions = [ actions = [
# Define your "new document" handlers here. # Define your "new document" handlers here.
( nounPhrase = (defaultText = "administration"), ( nounPhrase = (defaultText = "administration"),
@@ -97,7 +97,7 @@ const pkgdef :Spk.PackageDefinition = (
# `spk dev` will write a list of all the files your app uses to this file. # `spk dev` will write a list of all the files your app uses to this file.
# You should review it later, before shipping your app. # You should review it later, before shipping your app.
alwaysInclude = [], alwaysInclude = ["app","bootstrap","config","database","public","resources","routes"],
# Fill this list with more names of files or directories that should be # Fill this list with more names of files or directories that should be
# included in your package, even if not listed in sandstorm-files.list. # included in your package, even if not listed in sandstorm-files.list.
# Use this to force-include stuff that you know you need but which may # Use this to force-include stuff that you know you need but which may

View File

@@ -9,7 +9,6 @@ cache:
- $HOME/.composer/cache - $HOME/.composer/cache
install: install:
- if [[ "$(php -v | grep 'PHP 7')" ]]; then phpenv config-rm xdebug.ini; fi
- rm composer.lock - rm composer.lock
- composer update --no-scripts - composer update --no-scripts
- cp .env.testing .env - cp .env.testing .env
@@ -17,10 +16,14 @@ install:
- php artisan optimize - php artisan optimize
- php artisan env - php artisan env
- cp .env.testing .env - cp .env.testing .env
- mv storage/database/databasecopy.sqlite storage/database/database.sqlite - wget -q https://github.com/firefly-iii/test-data/raw/master/storage/database.sqlite -O storage/database/database.sqlite
- mkdir -p build/logs
script: script:
- phpunit - phpunit -c phpunit.xml
#after_success:
# - travis_retry php vendor/bin/coveralls -x storage/build/clover.xml
# safelist # safelist
branches: branches:

View File

@@ -2,6 +2,115 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## [4.6.0] - 2017-06-28
### Changed
- Revamped import routine. Will be buggy.
### Fixed
- Issue #667, postgresql reported by @skibbipl.
- Issue #680 by @Xeli
- Fixed #660
- Fixes #672, reported by @dzaikos
- Translation error fixed by
- Fix a bug where the balance routine forgot to account for accounts without a currency preference.
- Various other bugfixes.
### Security
- Initial release.
## [4.5.0] - 2017-06-07
### Added
- Better support for multi-currency transactions and display of transactions, accounts and everything. This requires a database overhaul (moving the currency information to specific transactions) so be careful when upgrading.
- Translations for Spanish and Slovenian.
- New interface for budget page, ~~stolen from~~ inspired by YNAB.
- Expanded Docker to work with postgresql as well, thanks to @kressh
### Fixed
- PostgreSQL support in database upgrade routine (#644, reported by @skibbipl)
- Frontpage budget chart was off, fix by @nhaarman
- Was not possible to remove opening balance.
## [4.4.3] - 2017-05-03
### Added
- Added support for Slovenian
- Removed support for Spanish. No translations whatsoever by the guy who requested it.
- Removed support for Russian. Same thing.
- Removed support for Croatian. Same thing.
- Removed support for Chinese Traditional, Hong Kong. Same thing.
### Changed
- The journal collector, an internal piece of code to collect transactions, now uses a slightly different method of collecting journals. This may cause problems.
### Fixed
- Issue #638 as reported by [worldworm](https://github.com/worldworm).
- Possible fix for #624
## [4.4.2] - 2017-04-27
### Fixed
- Fixed a bug where the opening balance could not be stored.
## [4.4.1] - 2017-04-27
### Added
- Support for deployment on Heroku
### Fixed
- Bug in new-user routine.
## [4.4.0] - 2017-04-23
### Added
- Firefly III can now handle foreign currencies better, including some code to get the exchange rate live from the web.
- Can now make rules for attachments, see #608, as suggested by dzaikos.
### Fixed
- Fixed #629, reported by forcaeluz
- Fixed #630, reported by welbert
- And more various bug fixes.
## [4.3.8] - 2017-04-08
### Added
- Better overview / show pages.
- #628, as reported by [xzaz](https://github.com/xzaz).
- Greatly expanded test coverage
### Fixed
- #619, as reported by [dfiel](https://github.com/dfiel).
- #620, as reported by [forcaeluz](https://github.com/forcaeluz).
- Attempt to fix #624, as reported by [TheSerenin](https://github.com/TheSerenin).
- Favicon link is relative now, fixed by [welbert](https://github.com/welbert).
- Some search bugs
## [4.3.7] - 2017-03-06
### Added
- Nice user friendly views for empty lists.
- Extended contribution guidelines.
- First version of financial report filtered on tags.
- Suggested monthly savings for piggy banks, by [Zsub](https://github.com/Zsub)
- Better test coverage.
### Changed
- Slightly changed tag overview.
- Consistent icon for bill in list.
- Slightly changed account overview.
### Removed
- Removed IDE specific views from .gitignore, issue #598
### Fixed
- Force key generation during installation.
- The `date` function takes the fieldname where a date is stored, not the literal date by [Zsub](https://github.com/Zsub)
- Improved budget frontpage chart, as suggested by [skibbipl](https://github.com/skibbipl)
- Issue #602 and #607, as reported by [skibbipl](https://github.com/skibbipl) and [dzaikos](https://github.com/dzaikos).
- Issue #605, as reported by [Zsub](https://github.com/Zsub).
- Issue #599, as reported by [leander091](https://github.com/leander091).
- Issue #610, as reported by [skibbipl](https://github.com/skibbipl).
- Issue #611, as reported by [ragnarkarlsson](https://github.com/ragnarkarlsson).
- Issue #612, as reported by [ragnarkarlsson](https://github.com/ragnarkarlsson).
- Issue #614, as reported by [worldworm](https://github.com/worldworm).
- Various other bug fixes.
## [4.3.6] - 2017-02-20 ## [4.3.6] - 2017-02-20
### Fixed ### Fixed
@@ -159,13 +268,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
- Updated all email messages. - Updated all email messages.
- Made some fonts local - Made some fonts local
### Deprecated
- Initial release.
### Removed
- Initial release.
### Fixed ### Fixed
- Issue #408 - Issue #408
- Various issues with split journals - Various issues with split journals
@@ -174,11 +276,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
- Issue #422, thx [xzaz](https://github.com/xzaz) - Issue #422, thx [xzaz](https://github.com/xzaz)
- Various import bugs, such as #416 ([zjean](https://github.com/zjean)) - Various import bugs, such as #416 ([zjean](https://github.com/zjean))
### Security
- Initial release.
## [4.1.7] - 2016-11-19 ## [4.1.7] - 2016-11-19
### Added ### Added
- Check for database table presence in console commands. - Check for database table presence in console commands.
@@ -301,15 +398,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
- New Presidents Choice specific to fix #307 - New Presidents Choice specific to fix #307
- Added some trimming (#335) - Added some trimming (#335)
### Changed
- Initial release.
### Deprecated
- Initial release.
### Removed
- Initial release.
### Fixed ### Fixed
- Fixed a bug where incoming transactions would not be properly filtered in several reports. - Fixed a bug where incoming transactions would not be properly filtered in several reports.
- #334 by [cyberkov](https://github.com/cyberkov) - #334 by [cyberkov](https://github.com/cyberkov)
@@ -317,12 +405,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
- #336 - #336
- #338 found by [roberthorlings](https://github.com/roberthorlings) - #338 found by [roberthorlings](https://github.com/roberthorlings)
### Security
- Initial release.
## [4.0.0] - 2015-09-26 ## [4.0.0] - 2015-09-26
### Added ### Added
- Upgraded to Laravel 5.3, most other libraries upgraded as well. - Upgraded to Laravel 5.3, most other libraries upgraded as well.

View File

@@ -11,13 +11,14 @@ RUN apt-get update -y && \
libtidy-dev \ libtidy-dev \
libxml2-dev \ libxml2-dev \
libsqlite3-dev \ libsqlite3-dev \
libpq-dev \
libbz2-dev \ libbz2-dev \
gettext-base \ gettext-base \
locales && \ locales && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* 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 RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2 pdo_pgsql
# Generate locales supported by firefly # Generate locales supported by firefly
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen

1
Procfile Normal file
View File

@@ -0,0 +1 @@
web: vendor/bin/heroku-php-nginx -C nginx_app.conf public/

View File

@@ -1,6 +1,6 @@
# Firefly III: A personal finances manager # Firefly III: A personal finances manager
[![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/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) [![Requires PHP7](https://img.shields.io/badge/php-7.0-red.svg)](https://secure.php.net/downloads.php) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![License](https://img.shields.io/badge/license-CC%20BY--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
[![The index of Firefly III](https://i.nder.be/hurdhgyg/400)](https://i.nder.be/h2b37243) [![The account overview of Firefly III](https://i.nder.be/hnkfkdpr/400)](https://i.nder.be/hv70pbwc) [![The index of Firefly III](https://i.nder.be/hurdhgyg/400)](https://i.nder.be/h2b37243) [![The account overview of Firefly III](https://i.nder.be/hnkfkdpr/400)](https://i.nder.be/hv70pbwc)
@@ -10,11 +10,13 @@
## Try it out! ## Try it out!
Try out Firefly III on the [demo site](https://firefly-iii.nder.be/). [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
## Installation Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
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/). ## Getting started
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/using-installing.html).
## More about Firefly III ## More about Firefly III
@@ -22,7 +24,7 @@ Personal financial management is pretty difficult, and everybody has their own a
Firefly works on the principle that if you know where you're money is going, you can stop it from going there. Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
#### Some advantages of using Firefly ### Some advantages of using Firefly
- Firefly can import any CSV file, so migrating from other systems is easy. - Firefly can import any CSV file, so migrating from other systems is easy.
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", its just somebody elses computer! - Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", its just somebody elses computer!
@@ -31,6 +33,27 @@ Firefly works on the principle that if you know where you're money is going, you
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/). Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
### Contributing
Please read [CONTRIBUTING.md](https://github.com/firefly-iii/firefly-iii/blob/master/.github/CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests.
### Versioning
We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository.
### Authors
* James Cole
* Over time, [many people have contributed to Firefly III](https://github.com/firefly-iii/firefly-iii/graphs/contributors).
### License
This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
### Other stuff
If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!) If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
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).
[![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Coverage Status](https://coveralls.io/repos/github/firefly-iii/firefly-iii/badge.svg?branch=master)](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master)

59
app.json Normal file
View File

@@ -0,0 +1,59 @@
{
"name": "Firefly III",
"description": "A free and open source personal finances manager",
"repository": "https://github.com/firefly-iii/firefly-iii",
"logo": "https://raw.githubusercontent.com/firefly-iii/firefly-iii/master/public/mstile-150x150.png",
"keywords": [
"finance",
"finances",
"manager",
"management",
"euro",
"dollar",
"laravel",
"money",
"currency",
"financials",
"financial",
"budgets",
"administration",
"tool",
"tooling",
"help",
"helper",
"assistant",
"planning",
"organizing",
"bills",
"personal finance",
"budgets",
"budgeting",
"budgeting tool",
"budgeting application",
"transactions",
"self hosted",
"self-hosted",
"transfers",
"management"
],
"website": "https://firefly-iii.github.io/",
"addons": [
{
"plan": "heroku-postgresql"
}
],
"scripts": {
"postdeploy": "export APP_KEY=$(php artisan --no-ansi key:generate --show)"
},
"buildpacks": [
{
"url": "heroku/php"
}
],
"env": {
"APP_KEY": {
"description": "This key is used to encrypt your data.",
"value": "base64:If1gJN4pyycXTq+WS5TjneDympKuu+8SKvTl6RZnhJg="
}
}
}

View File

@@ -1,63 +0,0 @@
<?php
/**
* ConfigureLogging.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\Bootstrap;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Bootstrap\ConfigureLogging as IlluminateConfigureLogging;
use Illuminate\Log\Writer;
/**
* Class ConfigureLogging
*
* @package FireflyIII\Bootstrap
*/
class ConfigureLogging extends IlluminateConfigureLogging
{
/**
* Configure the Monolog handlers for the application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Log\Writer $log
*
* @return void
*/
protected function configureDailyHandler(Application $app, Writer $log)
{
$config = $app->make('config');
$maxFiles = $config->get('app.log_max_files');
$log->useDailyFiles(
$app->storagePath() . '/logs/firefly-iii.log', is_null($maxFiles) ? 5 : $maxFiles,
$config->get('app.log_level', 'debug')
);
}
/**
* Configure the Monolog handlers for the application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Log\Writer $log
*
* @return void
*/
protected function configureSingleHandler(Application $app, Writer $log)
{
$log->useFiles(
$app->storagePath() . '/logs/firefly-iii.log',
$app->make('config')->get('app.log_level', 'debug')
);
}
}

View File

@@ -84,11 +84,11 @@ class CreateImport extends Command
$job = $jobRepository->create($type); $job = $jobRepository->create($type);
$this->line(sprintf('Created job "%s"...', $job->key)); $this->line(sprintf('Created job "%s"...', $job->key));
Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]); Artisan::call('firefly:encrypt-file', ['file' => $file, 'key' => $job->key]);
$this->line('Stored import data...'); $this->line('Stored import data...');
$job->configuration = $configurationData; $job->configuration = $configurationData;
$job->status = 'settings_complete'; $job->status = 'configured';
$job->save(); $job->save();
$this->line('Stored configuration...'); $this->line('Stored configuration...');

View File

@@ -13,12 +13,13 @@ declare(strict_types = 1);
namespace FireflyIII\Console\Commands; namespace FireflyIII\Console\Commands;
use FireflyIII\Import\ImportProcedure;
use FireflyIII\Import\Logging\CommandHandler; use FireflyIII\Import\Logging\CommandHandler;
use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
use Log; use Log;
/** /**
@@ -58,7 +59,12 @@ class Import extends Command
{ {
Log::debug('Start start-import command'); Log::debug('Start start-import command');
$jobKey = $this->argument('key'); $jobKey = $this->argument('key');
$job = ImportJob::whereKey($jobKey)->first(); $job = ImportJob::where('key', $jobKey)->first();
if (is_null($job)) {
$this->error(sprintf('No job found with key "%s"', $jobKey));
return;
}
if (!$this->isValid($job)) { if (!$this->isValid($job)) {
Log::error('Job is not valid for some reason. Exit.'); Log::error('Job is not valid for some reason. Exit.');
@@ -70,15 +76,18 @@ class Import extends Command
$monolog = Log::getMonolog(); $monolog = Log::getMonolog();
$handler = new CommandHandler($this); $handler = new CommandHandler($this);
$monolog->pushHandler($handler); $monolog->pushHandler($handler);
$importProcedure = new ImportProcedure;
$result = $importProcedure->runImport($job);
// display result to user: /** @var ImportRoutine $routine */
$this->presentResults($result); $routine = app(ImportRoutine::class);
$this->line('The import has completed.'); $routine->setJob($job);
$routine->run();
// get any errors from the importer: /** @var MessageBag $error */
$this->presentErrors($job); foreach ($routine->errors as $index => $error) {
$this->error(sprintf('Error importing line #%d: %s', $index, $error));
}
$this->line(sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->journals->count(), $routine->lines));
return; return;
} }
@@ -91,12 +100,14 @@ class Import extends Command
private function isValid(ImportJob $job): bool private function isValid(ImportJob $job): bool
{ {
if (is_null($job)) { if (is_null($job)) {
Log::error('This job does not seem to exist.');
$this->error('This job does not seem to exist.'); $this->error('This job does not seem to exist.');
return false; return false;
} }
if ($job->status != 'settings_complete') { if ($job->status !== 'configured') {
Log::error(sprintf('This job is not ready to be imported (status is %s).', $job->status));
$this->error('This job is not ready to be imported.'); $this->error('This job is not ready to be imported.');
return false; return false;
@@ -104,36 +115,4 @@ class Import extends Command
return true; return true;
} }
/**
* @param ImportJob $job
*/
private function presentErrors(ImportJob $job)
{
$extendedStatus = $job->extended_status;
if (isset($extendedStatus['errors']) && count($extendedStatus['errors']) > 0) {
$this->line(sprintf('The following %d error(s) occured during the import:', count($extendedStatus['errors'])));
foreach ($extendedStatus['errors'] as $error) {
$this->error($error);
}
}
}
/**
* @param Collection $result
*/
private function presentResults(Collection $result)
{
/**
* @var int $index
* @var TransactionJournal $journal
*/
foreach ($result as $index => $journal) {
if (!is_null($journal->id)) {
$this->line(sprintf('Line #%d has been imported as transaction #%d.', $index, $journal->id));
continue;
}
$this->error(sprintf('Could not store line #%d', $index));
}
}
} }

View File

@@ -15,14 +15,24 @@ namespace FireflyIII\Console\Commands;
use DB; use DB;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
use Log; use Log;
use Preferences;
use Schema; use Schema;
use Steam;
/** /**
* Class UpgradeDatabase * Class UpgradeDatabase
@@ -60,8 +70,60 @@ class UpgradeDatabase extends Command
{ {
$this->setTransactionIdentifier(); $this->setTransactionIdentifier();
$this->migrateRepetitions(); $this->migrateRepetitions();
$this->repairPiggyBanks();
$this->updateAccountCurrencies();
$this->updateJournalCurrencies();
$this->currencyInfoToTransactions();
$this->verifyCurrencyInfo();
$this->info('Firefly III database is up to date.');
} }
/**
* Moves the currency id info to the transaction instead of the journal.
*/
private function currencyInfoToTransactions()
{
$count = 0;
$set = TransactionJournal::with('transactions')->get();
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
if (is_null($transaction->transaction_currency_id)) {
$transaction->transaction_currency_id = $journal->transaction_currency_id;
$transaction->save();
$count++;
}
}
// read and use the foreign amounts when present.
if ($journal->hasMeta('foreign_amount')) {
$amount = Steam::positive($journal->getMeta('foreign_amount'));
// update both transactions:
foreach ($journal->transactions as $transaction) {
$transaction->foreign_amount = $amount;
if (bccomp($transaction->amount, '0') === -1) {
// update with negative amount:
$transaction->foreign_amount = bcmul($amount, '-1');
}
// set foreign currency id:
$transaction->foreign_currency_id = intval($journal->getMeta('foreign_currency_id'));
$transaction->save();
}
$journal->deleteMeta('foreign_amount');
$journal->deleteMeta('foreign_currency_id');
}
}
$this->line(sprintf('Updated currency information for %d transactions', $count));
}
/**
* Migrate budget repetitions to new format.
*/
private function migrateRepetitions() private function migrateRepetitions()
{ {
if (!Schema::hasTable('budget_limits')) { if (!Schema::hasTable('budget_limits')) {
@@ -69,7 +131,9 @@ class UpgradeDatabase extends Command
} }
// get all budget limits with end_date NULL // get all budget limits with end_date NULL
$set = BudgetLimit::whereNull('end_date')->get(); $set = BudgetLimit::whereNull('end_date')->get();
if ($set->count() > 0) {
$this->line(sprintf('Found %d budget limit(s) to update', $set->count())); $this->line(sprintf('Found %d budget limit(s) to update', $set->count()));
}
/** @var BudgetLimit $budgetLimit */ /** @var BudgetLimit $budgetLimit */
foreach ($set as $budgetLimit) { foreach ($set as $budgetLimit) {
// get limit repetition (should be just one): // get limit repetition (should be just one):
@@ -84,6 +148,37 @@ class UpgradeDatabase extends Command
} }
} }
/**
* Make sure there are only transfers linked to piggy bank events.
*/
private function repairPiggyBanks()
{
// if table does not exist, return false
if (!Schema::hasTable('piggy_bank_events')) {
return;
}
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
/** @var PiggyBankEvent $event */
foreach ($set as $event) {
if (is_null($event->transaction_journal_id)) {
continue;
}
/** @var TransactionJournal $journal */
$journal = $event->transactionJournal()->first();
if (is_null($journal)) {
continue;
}
$type = $journal->transactionType->type;
if ($type !== TransactionType::TRANSFER) {
$event->transaction_journal_id = null;
$event->save();
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
}
}
}
/** /**
* This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird. * This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
*/ */
@@ -112,6 +207,57 @@ class UpgradeDatabase extends Command
} }
} }
/**
*
*/
private function updateAccountCurrencies()
{
$accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']);
/** @var Account $account */
foreach ($accounts as $account) {
// get users preference, fall back to system pref.
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
$accountCurrency = intval($account->getMeta('currency_id'));
$openingBalance = $account->getOpeningBalance();
$openingBalanceCurrency = intval($openingBalance->transaction_currency_id);
// both 0? set to default currency:
if ($accountCurrency === 0 && $openingBalanceCurrency === 0) {
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
continue;
}
// opening balance 0, account not zero? just continue:
if ($accountCurrency > 0 && $openingBalanceCurrency === 0) {
continue;
}
// account is set to 0, opening balance is not?
if ($accountCurrency === 0 && $openingBalanceCurrency > 0) {
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $openingBalanceCurrency]);
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
continue;
}
// both are equal, just continue:
if ($accountCurrency === $openingBalanceCurrency) {
continue;
}
// do not match:
if ($accountCurrency !== $openingBalanceCurrency) {
// update opening balance:
$openingBalance->transaction_currency_id = $accountCurrency;
$openingBalance->save();
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
continue;
}
}
}
/** /**
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one * grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
* which has 0 as an identifier and give it the same identifier. * which has 0 as an identifier and give it the same identifier.
@@ -151,9 +297,116 @@ class UpgradeDatabase extends Command
$opposing->save(); $opposing->save();
$processed[] = $transaction->id; $processed[] = $transaction->id;
$processed[] = $opposing->id; $processed[] = $opposing->id;
$this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id));
} }
$identifier++; $identifier++;
} }
} }
/**
* Makes sure that withdrawals, deposits and transfers have
* a currency setting matching their respective accounts
*/
private function updateJournalCurrencies()
{
$types = [
TransactionType::WITHDRAWAL => '<',
TransactionType::DEPOSIT => '>',
];
$repository = app(CurrencyRepositoryInterface::class);
$notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.';
$transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.';
$driver = DB::connection()->getDriverName();
$pgsql = ['pgsql', 'postgresql'];
foreach ($types as $type => $operator) {
$query = TransactionJournal
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin(
'transactions', function (JoinClause $join) use ($operator) {
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0');
}
)
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
->where('transaction_types.type', $type)
->where('account_meta.name', 'currency_id');
if (in_array($driver, $pgsql)) {
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)'));
}
if (!in_array($driver, $pgsql)) {
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'));
}
$set = $query->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']);
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
$expectedCurrency = $repository->find(intval($journal->expected_currency_id));
$line = sprintf($notification, $type, $journal->id, $journal->transactionCurrency->code, $expectedCurrency->code);
$journal->setMeta('foreign_amount', $journal->transaction_amount);
$journal->setMeta('foreign_currency_id', $journal->transaction_currency_id);
$journal->transaction_currency_id = $expectedCurrency->id;
$journal->save();
$this->line($line);
}
}
/*
* For transfers it's slightly different. Both source and destination
* must match the respective currency preference. So we must verify ALL
* transactions.
*/
$set = TransactionJournal
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->where('transaction_types.type', TransactionType::TRANSFER)
->get(['transaction_journals.*']);
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
$updated = false;
/** @var Transaction $sourceTransaction */
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
$sourceCurrency = $repository->find(intval($sourceTransaction->account->getMeta('currency_id')));
if ($sourceCurrency->id !== $journal->transaction_currency_id) {
$updated = true;
$journal->transaction_currency_id = $sourceCurrency->id;
$journal->save();
}
// destination
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
$destinationCurrency = $repository->find(intval($destinationTransaction->account->getMeta('currency_id')));
if ($destinationCurrency->id !== $journal->transaction_currency_id) {
$updated = true;
$journal->deleteMeta('foreign_amount');
$journal->deleteMeta('foreign_currency_id');
$journal->setMeta('foreign_amount', $destinationTransaction->amount);
$journal->setMeta('foreign_currency_id', $destinationCurrency->id);
}
if ($updated) {
$line = sprintf($transfer, $journal->id);
$this->line($line);
}
}
}
/**
*
*/
private function verifyCurrencyInfo()
{
$count = 0;
$transactions = Transaction::get();
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$currencyId = intval($transaction->transaction_currency_id);
$foreignId = intval($transaction->foreign_currency_id);
if ($currencyId === $foreignId) {
$transaction->foreign_currency_id = null;
$transaction->foreign_amount = null;
$transaction->save();
$count++;
}
}
$this->line(sprintf('Updated currency information for %d transactions', $count));
}
} }

View File

@@ -93,6 +93,7 @@ class VerifyDatabase extends Command
// report on journals with the wrong types of accounts. // report on journals with the wrong types of accounts.
$this->reportIncorrectJournals(); $this->reportIncorrectJournals();
} }
/** /**
@@ -131,7 +132,7 @@ class VerifyDatabase extends Command
/** @var Budget $entry */ /** @var Budget $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
$line = sprintf( $line = sprintf(
'Notice: User #%d (%s) has budget #%d ("%s") which has no budget limits.', 'User #%d (%s) has budget #%d ("%s") which has no budget limits.',
$entry->user_id, $entry->email, $entry->id, $entry->name $entry->user_id, $entry->email, $entry->id, $entry->name
); );
$this->line($line); $this->line($line);
@@ -277,7 +278,7 @@ class VerifyDatabase extends Command
} }
$line = sprintf( $line = sprintf(
'Notice: User #%d (%s) has %s #%d ("%s") which has no transactions.', 'User #%d (%s) has %s #%d ("%s") which has no transactions.',
$entry->user_id, $entry->email, $name, $entry->id, $objName $entry->user_id, $entry->email, $name, $entry->id, $objName
); );
$this->line($line); $this->line($line);

View File

@@ -41,7 +41,6 @@ class Kernel extends ConsoleKernel
= [ = [
'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables', 'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables',
'Illuminate\Foundation\Bootstrap\LoadConfiguration', 'Illuminate\Foundation\Bootstrap\LoadConfiguration',
//'FireflyIII\Bootstrap\ConfigureLogging',
'Illuminate\Foundation\Bootstrap\HandleExceptions', 'Illuminate\Foundation\Bootstrap\HandleExceptions',
'Illuminate\Foundation\Bootstrap\RegisterFacades', 'Illuminate\Foundation\Bootstrap\RegisterFacades',
'Illuminate\Foundation\Bootstrap\SetRequestForConsole', 'Illuminate\Foundation\Bootstrap\SetRequestForConsole',

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Events; namespace FireflyIII\Events;
/** /**

View File

@@ -26,7 +26,9 @@ class StoredTransactionJournal extends Event
use SerializesModels; use SerializesModels;
/** @var TransactionJournal */
public $journal; public $journal;
/** @var int */
public $piggyBankId; public $piggyBankId;
/** /**

View File

@@ -26,6 +26,7 @@ class UpdatedTransactionJournal extends Event
use SerializesModels; use SerializesModels;
/** @var TransactionJournal */
public $journal; public $journal;
/** /**

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Exceptions; namespace FireflyIII\Exceptions;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Exceptions; namespace FireflyIII\Exceptions;
use ErrorException; use ErrorException;
@@ -97,6 +98,7 @@ class Handler extends ExceptionHandler
'file' => $exception->getFile(), 'file' => $exception->getFile(),
'line' => $exception->getLine(), 'line' => $exception->getLine(),
'code' => $exception->getCode(), 'code' => $exception->getCode(),
'version' => config('firefly.version'),
]; ];
// create job that will mail. // create job that will mail.

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Exceptions; namespace FireflyIII\Exceptions;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Exceptions; namespace FireflyIII\Exceptions;
/** /**

View File

@@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Collector; namespace FireflyIII\Export\Collector;
use Carbon\Carbon; use Carbon\Carbon;
use Crypt;
use DB; use DB;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
@@ -304,7 +303,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_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_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id') ->leftJoin('transaction_currencies', 'transactions.transaction_currency_id', '=', 'transaction_currencies.id')
->whereIn('transactions.account_id', $accountIds) ->whereIn('transactions.account_id', $accountIds)
->where('transaction_journals.user_id', $this->job->user_id) ->where('transaction_journals.user_id', $this->job->user_id)
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d')) ->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
@@ -339,7 +338,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac
'transaction_journals.encrypted as journal_encrypted', 'transaction_journals.encrypted as journal_encrypted',
'transaction_journals.transaction_type_id', 'transaction_journals.transaction_type_id',
'transaction_types.type as transaction_type', 'transaction_types.type as transaction_type',
'transaction_journals.transaction_currency_id', 'transactions.transaction_currency_id',
'transaction_currencies.code AS transaction_currency_code', 'transaction_currencies.code AS transaction_currency_code',
] ]

View File

@@ -13,7 +13,6 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Entry; namespace FireflyIII\Export\Entry;
use Crypt;
use Steam; use Steam;
/** /**

View File

@@ -81,6 +81,9 @@ class ChartJsGenerator implements GeneratorInterface
if (isset($set['fill'])) { if (isset($set['fill'])) {
$currentSet['fill'] = $set['fill']; $currentSet['fill'] = $set['fill'];
} }
if (isset($set['currency_symbol'])) {
$currentSet['currency_symbol'] = $set['currency_symbol'];
}
$chartData['datasets'][] = $currentSet; $chartData['datasets'][] = $currentSet;
} }
@@ -105,6 +108,10 @@ class ChartJsGenerator implements GeneratorInterface
], ],
'labels' => [], 'labels' => [],
]; ];
// sort by value, keep keys.
asort($data);
$index = 0; $index = 0;
foreach ($data as $key => $value) { foreach ($data as $key => $value) {

View File

@@ -22,10 +22,15 @@ interface GeneratorInterface
{ {
/** /**
* Will generate a (ChartJS) compatible array from the given input. Expects this format: * Will generate a Chart JS compatible array from the given input. Expects this format
*
* Will take labels for all from first set.
* *
* 0: [ * 0: [
* 'label' => 'label of set', * 'label' => 'label of set',
* 'type' => bar or line, optional
* 'yAxisID' => ID of yAxis, optional, will not be included when unused.
* 'fill' => if to fill a line? optional, will not be included when unused.
* 'entries' => * 'entries' =>
* [ * [
* 'label-of-entry' => 'value' * 'label-of-entry' => 'value'
@@ -33,12 +38,16 @@ interface GeneratorInterface
* ] * ]
* 1: [ * 1: [
* 'label' => 'label of another set', * 'label' => 'label of another set',
* 'type' => bar or line, optional
* 'yAxisID' => ID of yAxis, optional, will not be included when unused.
* 'fill' => if to fill a line? optional, will not be included when unused.
* 'entries' => * 'entries' =>
* [ * [
* 'label-of-entry' => 'value' * 'label-of-entry' => 'value'
* ] * ]
* ] * ]
* *
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five.
* *
* @param array $data * @param array $data
* *

View File

@@ -19,6 +19,7 @@ use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Steam; use Steam;
@@ -147,6 +148,8 @@ class MonthReportGenerator implements ReportGeneratorInterface
*/ */
private function getAuditReport(Account $account, Carbon $date): array private function getAuditReport(Account $account, Carbon $date): array
{ {
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
@@ -155,15 +158,21 @@ class MonthReportGenerator implements ReportGeneratorInterface
$journals = $journals->reverse(); $journals = $journals->reverse();
$dayBeforeBalance = Steam::balance($account, $date); $dayBeforeBalance = Steam::balance($account, $date);
$startBalance = $dayBeforeBalance; $startBalance = $dayBeforeBalance;
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
/** @var Transaction $journal */ /** @var Transaction $journal */
foreach ($journals as $transaction) { foreach ($journals as $transaction) {
$transaction->before = $startBalance; $transaction->before = $startBalance;
$transactionAmount = $transaction->transaction_amount; $transactionAmount = $transaction->transaction_amount;
if ($currency->id === $transaction->foreign_currency_id) {
$transactionAmount = $transaction->transaction_foreign_amount;
}
$newBalance = bcadd($startBalance, $transactionAmount); $newBalance = bcadd($startBalance, $transactionAmount);
$transaction->after = $newBalance; $transaction->after = $newBalance;
$startBalance = $newBalance; $startBalance = $newBalance;
$transaction->currency = $currency;
} }
/* /*

View File

@@ -18,6 +18,9 @@ use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Generator\Report\Support; use FireflyIII\Generator\Report\Support;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -141,52 +144,10 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
return $this; 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;
$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 * @return Collection
*/ */
private function getExpenses(): Collection protected function getExpenses(): Collection
{ {
if ($this->expenses->count() > 0) { if ($this->expenses->count() > 0) {
Log::debug('Return previous set of expenses.'); Log::debug('Return previous set of expenses.');
@@ -198,44 +159,18 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end) $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
->setTypes([TransactionType::WITHDRAWAL]) ->setTypes([TransactionType::WITHDRAWAL])
->setBudgets($this->budgets)->withOpposingAccount()->disableFilter(); ->setBudgets($this->budgets)->withOpposingAccount();
$collector->removeFilter(TransferFilter::class);
$collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(PositiveAmountFilter::class);
$accountIds = $this->accounts->pluck('id')->toArray();
$transactions = $collector->getJournals(); $transactions = $collector->getJournals();
$transactions = self::filterExpenses($transactions, $accountIds);
$this->expenses = $transactions; $this->expenses = $transactions;
return $transactions; return $transactions;
} }
/**
* @return Collection
*/
private function getTopExpenses(): Collection
{
$transactions = $this->getExpenses()->sortBy('transaction_amount');
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 * @param Collection $collection
* *

View File

@@ -18,6 +18,10 @@ use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Generator\Report\Support; use FireflyIII\Generator\Report\Support;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -151,52 +155,10 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
return $this; 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;
$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 * @return Collection
*/ */
private function getExpenses(): Collection protected function getExpenses(): Collection
{ {
if ($this->expenses->count() > 0) { if ($this->expenses->count() > 0) {
Log::debug('Return previous set of expenses.'); Log::debug('Return previous set of expenses.');
@@ -208,11 +170,13 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end) $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setCategories($this->categories)->withOpposingAccount()->disableFilter(); ->setCategories($this->categories)->withOpposingAccount();
$collector->removeFilter(TransferFilter::class);
$collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(PositiveAmountFilter::class);
$accountIds = $this->accounts->pluck('id')->toArray();
$transactions = $collector->getJournals(); $transactions = $collector->getJournals();
$transactions = self::filterExpenses($transactions, $accountIds);
$this->expenses = $transactions; $this->expenses = $transactions;
return $transactions; return $transactions;
@@ -221,7 +185,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
/** /**
* @return Collection * @return Collection
*/ */
private function getIncome(): Collection protected function getIncome(): Collection
{ {
if ($this->income->count() > 0) { if ($this->income->count() > 0) {
return $this->income; return $this->income;
@@ -232,93 +196,16 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end) $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->setCategories($this->categories)->withOpposingAccount(); ->setCategories($this->categories)->withOpposingAccount();
$accountIds = $this->accounts->pluck('id')->toArray();
$collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(NegativeAmountFilter::class);
$transactions = $collector->getJournals(); $transactions = $collector->getJournals();
$transactions = self::filterIncome($transactions, $accountIds);
$this->income = $transactions; $this->income = $transactions;
return $transactions; return $transactions;
} }
/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
* @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');
return $transactions;
}
/**
* @return Collection
*/
private function getTopIncome(): Collection
{
$transactions = $this->getIncome()->sortByDesc('transaction_amount');
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 * @param Collection $collection
* *

View File

@@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Category;
/** /**
* Class MultiYearReportGenerator * Class MultiYearReportGenerator
* *
* @package FireflyIII\Generator\Report\Audit * @package FireflyIII\Generator\Report\Category
*/ */
class MultiYearReportGenerator extends MonthReportGenerator class MultiYearReportGenerator extends MonthReportGenerator
{ {

View File

@@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Category;
/** /**
* Class YearReportGenerator * Class YearReportGenerator
* *
* @package FireflyIII\Generator\Report\Audit * @package FireflyIII\Generator\Report\Category
*/ */
class YearReportGenerator extends MonthReportGenerator class YearReportGenerator extends MonthReportGenerator
{ {

View File

@@ -49,7 +49,7 @@ class ReportGeneratorFactory
$class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period); $class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period);
if (class_exists($class)) { if (class_exists($class)) {
/** @var ReportGeneratorInterface $obj */ /** @var ReportGeneratorInterface $obj */
$obj = new $class; $obj = app($class);
$obj->setStartDate($start); $obj->setStartDate($start);
$obj->setEndDate($end); $obj->setEndDate($end);

View File

@@ -15,7 +15,6 @@ namespace FireflyIII\Generator\Report;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
/** /**
@@ -25,57 +24,122 @@ use Log;
*/ */
class Support class Support
{ {
/** /**
* @param Collection $collection
* @param array $accounts
*
* @return Collection * @return Collection
*/ */
public static function filterExpenses(Collection $collection, array $accounts): Collection public function getTopExpenses(): Collection
{ {
return self::filterTransactions($collection, $accounts, 1); $transactions = $this->getExpenses()->sortBy('transaction_amount');
return $transactions;
}
/**
* @return Collection
*/
public function getTopIncome(): Collection
{
$transactions = $this->getIncome()->sortByDesc('transaction_amount');
return $transactions;
} }
/** /**
* @param Collection $collection * @param Collection $collection
* @param array $accounts * @param int $sortFlag
* *
* @return Collection * @return array
*/ */
public static function filterIncome(Collection $collection, array $accounts): Collection protected function getAverages(Collection $collection, int $sortFlag): array
{ {
return self::filterTransactions($collection, $accounts, -1); $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;
$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;
}
/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
* @param array $spent
* @param array $earned
*
* @return array
*/
protected 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;
} }
/** /**
* @param Collection $collection * @param Collection $collection
* @param array $accounts
* @param int $modifier
* *
* @return Collection * @return array
*/ */
public static function filterTransactions(Collection $collection, array $accounts, int $modifier): Collection protected function summarizeByAccount(Collection $collection): array
{ {
$result = $collection->filter( $result = [];
function (Transaction $transaction) use ($accounts, $modifier) { /** @var Transaction $transaction */
$opposing = $transaction->opposing_account_id; foreach ($collection as $transaction) {
// remove internal transfer $accountId = $transaction->account_id;
if (in_array($opposing, $accounts)) { $result[$accountId] = $result[$accountId] ?? '0';
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id)); $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
return null;
} }
// remove positive amount
if (bccomp($transaction->transaction_amount, '0') === $modifier) {
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
return null;
}
return $transaction;
}
);
return $result; return $result;
} }

View File

@@ -0,0 +1,228 @@
<?php
/**
* MonthReportGenerator.php
* Copyright (c) 2017 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\Tag;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Generator\Report\Support;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection;
use Log;
/**
* Class MonthReportGenerator
*
* @package FireflyIII\Generator\Report\Tag
*/
class MonthReportGenerator extends Support implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Collection */
private $expenses;
/** @var Collection */
private $income;
/** @var Carbon */
private $start;
/** @var Collection */
private $tags;
/**
* MonthReportGenerator constructor.
*/
public function __construct()
{
$this->expenses = new Collection;
$this->income = new Collection;
}
/**
* @return string
*/
public function generate(): string
{
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$tagTags = join(',', $this->tags->pluck('tag')->toArray());
$reportType = 'tag';
$expenses = $this->getExpenses();
$income = $this->getIncome();
$accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income));
$tagSummary = $this->getObjectSummary($this->summarizeByTag($expenses), $this->summarizeByTag($income));
$averageExpenses = $this->getAverages($expenses, SORT_ASC);
$averageIncome = $this->getAverages($income, SORT_DESC);
$topExpenses = $this->getTopExpenses();
$topIncome = $this->getTopIncome();
// render!
return view(
'reports.tag.month', compact(
'accountIds', 'tagTags', 'reportType', 'accountSummary', 'tagSummary', 'averageExpenses', 'averageIncome', 'topIncome',
'topExpenses'
)
)->with('start', $this->start)->with('end', $this->end)->with('tags', $this->tags)->with('accounts', $this->accounts)->render();
}
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface
{
$this->accounts = $accounts;
return $this;
}
/**
* @param Collection $budgets
*
* @return ReportGeneratorInterface
*/
public function setBudgets(Collection $budgets): ReportGeneratorInterface
{
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;
}
/**
* @param Collection $tags
*
* @return ReportGeneratorInterface
*/
public function setTags(Collection $tags): ReportGeneratorInterface
{
$this->tags = $tags;
return $this;
}
/**
* @return Collection
*/
protected function getExpenses(): Collection
{
if ($this->expenses->count() > 0) {
Log::debug('Return previous set of expenses.');
return $this->expenses;
}
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setTags($this->tags)->withOpposingAccount();
$collector->removeFilter(TransferFilter::class);
$collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(PositiveAmountFilter::class);
$transactions = $collector->getJournals();
$this->expenses = $transactions;
return $transactions;
}
/**
* @return Collection
*/
protected function getIncome(): Collection
{
if ($this->income->count() > 0) {
return $this->income;
}
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->setTags($this->tags)->withOpposingAccount();
$collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(NegativeAmountFilter::class);
$transactions = $collector->getJournals();
$this->income = $transactions;
return $transactions;
}
/**
* @param Collection $collection
*
* @return array
*/
protected function summarizeByTag(Collection $collection): array
{
$result = [];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$journal = $transaction->transactionJournal;
$journalTags = $journal->tags;
/** @var Tag $journalTag */
foreach ($journalTags as $journalTag) {
$journalTagId = $journalTag->id;
$result[$journalTagId] = $result[$journalTagId] ?? '0';
$result[$journalTagId] = bcadd($transaction->transaction_amount, $result[$journalTagId]);
}
}
return $result;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* MultiYearReportGenerator.php
* Copyright (c) 2017 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\Tag;
/**
* Class MultiYearReportGenerator
*
* @package FireflyIII\Generator\Report\Tag
*/
class MultiYearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* YearReportGenerator.php
* Copyright (c) 2017 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\Tag;
/**
* Class YearReportGenerator
*
* @package FireflyIII\Generator\Report\Tag
*/
class YearReportGenerator extends MonthReportGenerator
{
/**
* Doesn't do anything different.
*/
}

View File

@@ -14,59 +14,95 @@ declare(strict_types = 1);
namespace FireflyIII\Handlers\Events; namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Events\StoredTransactionJournal;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\Rule; use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface as JRI;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface as PRI;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface as RGRI;
use FireflyIII\Rules\Processor; use FireflyIII\Rules\Processor;
use FireflyIII\Support\Events\BillScanner; use FireflyIII\Support\Events\BillScanner;
use Log; use Log;
/** /**
* @codeCoverageIgnore
*
* Class StoredJournalEventHandler * Class StoredJournalEventHandler
* *
* @package FireflyIII\Handlers\Events * @package FireflyIII\Handlers\Events
*/ */
class StoredJournalEventHandler class StoredJournalEventHandler
{ {
/** @var JRI */
public $journalRepository;
/** @var PRI */
public $repository;
/** @var RGRI */
public $ruleGroupRepository;
/**
* StoredJournalEventHandler constructor.
*
* @param PRI $repository
* @param JRI $journalRepository
* @param RGRI $ruleGroupRepository
*/
public function __construct(PRI $repository, JRI $journalRepository, RGRI $ruleGroupRepository)
{
$this->repository = $repository;
$this->journalRepository = $journalRepository;
$this->ruleGroupRepository = $ruleGroupRepository;
}
/** /**
* This method connects a new transfer to a piggy bank. * This method connects a new transfer to a piggy bank.
* *
*
*
* @param StoredTransactionJournal $event * @param StoredTransactionJournal $event
* *
* @return bool * @return bool
*/ */
public function connectToPiggyBank(StoredTransactionJournal $event): bool public function connectToPiggyBank(StoredTransactionJournal $event): bool
{ {
/** @var TransactionJournal $journal */
$journal = $event->journal; $journal = $event->journal;
$piggyBankId = $event->piggyBankId; $piggyBankId = $event->piggyBankId;
Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId)); $piggyBank = $this->repository->find($piggyBankId);
/* // is a transfer?
* Verify existence of piggy bank: if (!$this->journalRepository->isTransfer($journal)) {
*/ Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id));
if (!$this->verifyExistence($event)) {
Log::error(sprintf('No such piggy bank or no repetition on %s', $journal->date->format('Y-m-d')));
return true; return true;
} }
/* // piggy exists?
* Get relevant data: if (is_null($piggyBank->id)) {
*/ Log::error(sprintf('There is no piggy bank with ID #%d', $piggyBankId));
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); return true;
$amount = $this->getExactAmount($journal, $piggyBank, $repetition); }
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
$repetition->save(); // repetition exists?
$repetition = $this->repository->getRepetition($piggyBank, $journal->date);
if (is_null($repetition->id)) {
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
return true;
}
// get the amount
$amount = $this->repository->getExactAmount($piggyBank, $repetition, $journal);
if (bccomp($amount, '0') === 0) {
Log::debug('Amount is zero, will not create event.');
return true;
}
// update amount
$this->repository->addAmountToRepetition($repetition, $amount);
$event = $this->repository->createEventWithJournal($piggyBank, $amount, $journal);
/** @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)); Log::debug(sprintf('Created piggy bank event #%d', $event->id));
return true; return true;
@@ -83,16 +119,11 @@ class StoredJournalEventHandler
{ {
// get all the user's rule groups, with the rules, order by 'order'. // get all the user's rule groups, with the rules, order by 'order'.
$journal = $storedJournalEvent->journal; $journal = $storedJournalEvent->journal;
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); $groups = $this->ruleGroupRepository->getActiveGroups($journal->user);
//
/** @var RuleGroup $group */ /** @var RuleGroup $group */
foreach ($groups as $group) { foreach ($groups as $group) {
$rules = $group->rules() $rules = $this->ruleGroupRepository->getActiveStoreRules($group);
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
->where('rule_triggers.trigger_value', 'store-journal')
->where('rules.active', 1)
->get(['rules.*']);
/** @var Rule $rule */ /** @var Rule $rule */
foreach ($rules as $rule) { foreach ($rules as $rule) {
@@ -100,9 +131,8 @@ class StoredJournalEventHandler
$processor->handleTransactionJournal($journal); $processor->handleTransactionJournal($journal);
if ($rule->stop_processing) { if ($rule->stop_processing) {
return true; break;
} }
} }
} }
@@ -123,81 +153,4 @@ class StoredJournalEventHandler
return true; return true;
} }
/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but I can live with it.
* @param TransactionJournal $journal
* @param PiggyBank $piggyBank
* @param PiggyBankRepetition $repetition
*
* @return string
*/
private function getExactAmount(TransactionJournal $journal, PiggyBank $piggyBank, PiggyBankRepetition $repetition): string
{
$amount = TransactionJournal::amountPositive($journal);
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
$room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount));
$compare = bcmul($repetition->currentamount, '-1');
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
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 && bccomp($room, $amount) === -1) {
// amount is positive and $room is smaller than $amount
Log::debug(sprintf('Room in piggy bank for extra money is %f', $room));
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));
return $room;
}
// amount is negative and $currentamount is smaller than $amount
if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) {
Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount));
Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
Log::debug(sprintf('New amount is %f', $compare));
return $compare;
}
return $amount;
}
/**
* @param StoredTransactionJournal $event
*
* @return bool
*/
private function verifyExistence(StoredTransactionJournal $event): bool
{
/** @var TransactionJournal $journal */
$journal = $event->journal;
$piggyBankId = $event->piggyBankId;
/** @var PiggyBank $piggyBank */
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
if (is_null($piggyBank)) {
Log::error('No such piggy bank!');
return false;
}
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 false;
}
return true;
}
} }

View File

@@ -17,16 +17,31 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Events\UpdatedTransactionJournal;
use FireflyIII\Models\Rule; use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Rules\Processor; use FireflyIII\Rules\Processor;
use FireflyIII\Support\Events\BillScanner; use FireflyIII\Support\Events\BillScanner;
/** /**
* @codeCoverageIgnore
*
* Class UpdatedJournalEventHandler * Class UpdatedJournalEventHandler
* *
* @package FireflyIII\Handlers\Events * @package FireflyIII\Handlers\Events
*/ */
class UpdatedJournalEventHandler class UpdatedJournalEventHandler
{ {
/** @var RuleGroupRepositoryInterface */
public $repository;
/**
* StoredJournalEventHandler constructor.
*
* @param RuleGroupRepositoryInterface $ruleGroupRepository
*/
public function __construct(RuleGroupRepositoryInterface $ruleGroupRepository)
{
$this->repository = $ruleGroupRepository;
}
/** /**
* This method will check all the rules when a journal is updated. * This method will check all the rules when a journal is updated.
@@ -39,16 +54,11 @@ class UpdatedJournalEventHandler
{ {
// get all the user's rule groups, with the rules, order by 'order'. // get all the user's rule groups, with the rules, order by 'order'.
$journal = $updatedJournalEvent->journal; $journal = $updatedJournalEvent->journal;
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); $groups = $this->repository->getActiveGroups($journal->user);
//
/** @var RuleGroup $group */ /** @var RuleGroup $group */
foreach ($groups as $group) { foreach ($groups as $group) {
$rules = $group->rules() $rules = $this->repository->getActiveUpdateRules($group);
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
->where('rule_triggers.trigger_value', 'update-journal')
->where('rules.active', 1)
->get(['rules.*']);
/** @var Rule $rule */ /** @var Rule $rule */
foreach ($rules as $rule) { foreach ($rules as $rule) {
$processor = Processor::make($rule); $processor = Processor::make($rule);

View File

@@ -15,8 +15,9 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword; use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Mail\Message;
use Log; use Log;
use Mail; use Mail;
use Swift_TransportException; use Swift_TransportException;
@@ -68,15 +69,14 @@ class UserEventHandler
// send email. // send email.
try { try {
Mail::send( Mail::to($email)->send(new RequestedNewPasswordMail($url, $ipAddress));
['emails.password-html', 'emails.password-text'], ['url' => $url, 'ip' => $ipAddress], function (Message $message) use ($email) { // @codeCoverageIgnoreStart
$message->to($email, $email)->subject('Your password reset request');
}
);
} catch (Swift_TransportException $e) { } catch (Swift_TransportException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
} }
// @codeCoverageIgnoreEnd
return true; return true;
} }
@@ -93,23 +93,23 @@ class UserEventHandler
$sendMail = env('SEND_REGISTRATION_MAIL', true); $sendMail = env('SEND_REGISTRATION_MAIL', true);
if (!$sendMail) { if (!$sendMail) {
return true; return true; // @codeCoverageIgnore
} }
// get the email address // get the email address
$email = $event->user->email; $email = $event->user->email;
$address = route('index'); $uri = route('index');
$ipAddress = $event->ipAddress; $ipAddress = $event->ipAddress;
// send email. // send email.
try { try {
Mail::send( Mail::to($email)->send(new RegisteredUserMail($uri, $ipAddress));
['emails.registered-html', 'emails.registered-text'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) { // @codeCoverageIgnoreStart
$message->to($email, $email)->subject('Welcome to Firefly III!');
}
);
} catch (Swift_TransportException $e) { } catch (Swift_TransportException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
} }
// @codeCoverageIgnoreEnd
return true; return true;
} }
} }

View File

@@ -10,12 +10,15 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Attachments; namespace FireflyIII\Helpers\Attachments;
use Crypt; use Crypt;
use FireflyIII\Models\Attachment; use FireflyIII\Models\Attachment;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag; use Illuminate\Support\MessageBag;
use Log;
use Storage; use Storage;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
@@ -27,6 +30,8 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
class AttachmentHelper implements AttachmentHelperInterface class AttachmentHelper implements AttachmentHelperInterface
{ {
/** @var Collection */
public $attachments;
/** @var MessageBag */ /** @var MessageBag */
public $errors; public $errors;
/** @var MessageBag */ /** @var MessageBag */
@@ -48,6 +53,7 @@ class AttachmentHelper implements AttachmentHelperInterface
$this->allowedMimes = (array)config('firefly.allowedMimes'); $this->allowedMimes = (array)config('firefly.allowedMimes');
$this->errors = new MessageBag; $this->errors = new MessageBag;
$this->messages = new MessageBag; $this->messages = new MessageBag;
$this->attachments = new Collection;
$this->uploadDisk = Storage::disk('upload'); $this->uploadDisk = Storage::disk('upload');
} }
@@ -63,6 +69,14 @@ class AttachmentHelper implements AttachmentHelperInterface
return $path; return $path;
} }
/**
* @return Collection
*/
public function getAttachments(): Collection
{
return $this->attachments;
}
/** /**
* @return MessageBag * @return MessageBag
*/ */
@@ -109,7 +123,7 @@ class AttachmentHelper implements AttachmentHelperInterface
$md5 = md5_file($file->getRealPath()); $md5 = md5_file($file->getRealPath());
$name = $file->getClientOriginalName(); $name = $file->getClientOriginalName();
$class = get_class($model); $class = get_class($model);
$count = auth()->user()->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count(); $count = $model->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count();
if ($count > 0) { if ($count > 0) {
$msg = (string)trans('validation.file_already_attached', ['name' => $name]); $msg = (string)trans('validation.file_already_attached', ['name' => $name]);
@@ -136,7 +150,7 @@ class AttachmentHelper implements AttachmentHelperInterface
} }
$attachment = new Attachment; // create Attachment object. $attachment = new Attachment; // create Attachment object.
$attachment->user()->associate(auth()->user()); $attachment->user()->associate($model->user);
$attachment->attachable()->associate($model); $attachment->attachable()->associate($model);
$attachment->md5 = md5_file($file->getRealPath()); $attachment->md5 = md5_file($file->getRealPath());
$attachment->filename = $file->getClientOriginalName(); $attachment->filename = $file->getClientOriginalName();
@@ -144,17 +158,21 @@ class AttachmentHelper implements AttachmentHelperInterface
$attachment->size = $file->getSize(); $attachment->size = $file->getSize();
$attachment->uploaded = 0; $attachment->uploaded = 0;
$attachment->save(); $attachment->save();
Log::debug('Created attachment:', $attachment->toArray());
$fileObject = $file->openFile('r'); $fileObject = $file->openFile('r');
$fileObject->rewind(); $fileObject->rewind();
$content = $fileObject->fread($file->getSize()); $content = $fileObject->fread($file->getSize());
$encrypted = Crypt::encrypt($content); $encrypted = Crypt::encrypt($content);
Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize()));
Log::debug(sprintf('Encrypted content is %d', strlen($encrypted)));
// store it: // store it:
$this->uploadDisk->put($attachment->fileName(), $encrypted); $this->uploadDisk->put($attachment->fileName(), $encrypted);
$attachment->uploaded = 1; // update attachment $attachment->uploaded = 1; // update attachment
$attachment->save(); $attachment->save();
$this->attachments->push($attachment);
$name = e($file->getClientOriginalName()); // add message: $name = e($file->getClientOriginalName()); // add message:
$msg = (string)trans('validation.file_attached', ['name' => $name]); $msg = (string)trans('validation.file_attached', ['name' => $name]);
@@ -187,6 +205,8 @@ class AttachmentHelper implements AttachmentHelperInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param UploadedFile $file * @param UploadedFile $file
* *
* @return bool * @return bool
@@ -217,7 +237,7 @@ class AttachmentHelper implements AttachmentHelperInterface
return false; return false;
} }
if (!$this->validSize($file)) { if (!$this->validSize($file)) {
return false; return false; // @codeCoverageIgnore
} }
if ($this->hasFile($file, $model)) { if ($this->hasFile($file, $model)) {
return false; return false;

View File

@@ -10,10 +10,12 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Attachments; namespace FireflyIII\Helpers\Attachments;
use FireflyIII\Models\Attachment; use FireflyIII\Models\Attachment;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag; use Illuminate\Support\MessageBag;
/** /**
@@ -31,6 +33,11 @@ interface AttachmentHelperInterface
*/ */
public function getAttachmentLocation(Attachment $attachment): string; public function getAttachmentLocation(Attachment $attachment): string;
/**
* @return Collection
*/
public function getAttachments(): Collection;
/** /**
* @return MessageBag * @return MessageBag
*/ */
@@ -44,6 +51,8 @@ interface AttachmentHelperInterface
/** /**
* @param Model $model * @param Model $model
* *
* @param null|array $files
*
* @return bool * @return bool
*/ */
public function saveAttachmentsForModel(Model $model, array $files = null): bool; public function saveAttachmentsForModel(Model $model, array $files = null): bool;

View File

@@ -12,13 +12,18 @@ declare(strict_types = 1);
namespace FireflyIII\Helpers\Chart; namespace FireflyIII\Helpers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Generator\Report\Support;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Steam; use Steam;
@@ -46,19 +51,20 @@ class MetaPieChart implements MetaPieChartInterface
'account' => ['opposing_account_id'], 'account' => ['opposing_account_id'],
'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'], 'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'],
'category' => ['transaction_journal_category_id', 'transaction_category_id'], 'category' => ['transaction_journal_category_id', 'transaction_category_id'],
'tag' => [],
]; ];
/** @var array */ /** @var array */
protected $repositories protected $repositories
= [ = [
'account' => AccountRepositoryInterface::class, 'account' => AccountRepositoryInterface::class,
'budget' => BudgetRepositoryInterface::class, 'budget' => BudgetRepositoryInterface::class,
'category' => CategoryRepositoryInterface::class, 'category' => CategoryRepositoryInterface::class,
'tag' => TagRepositoryInterface::class,
]; ];
/** @var Carbon */ /** @var Carbon */
protected $start; protected $start;
/** @var Collection */
protected $tags;
/** @var string */ /** @var string */
protected $total = '0'; protected $total = '0';
/** @var User */ /** @var User */
@@ -69,6 +75,7 @@ class MetaPieChart implements MetaPieChartInterface
$this->accounts = new Collection; $this->accounts = new Collection;
$this->budgets = new Collection; $this->budgets = new Collection;
$this->categories = new Collection; $this->categories = new Collection;
$this->tags = new Collection;
} }
/** /**
@@ -99,6 +106,7 @@ class MetaPieChart implements MetaPieChartInterface
if ($this->collectOtherObjects && $direction === 'income') { if ($this->collectOtherObjects && $direction === 'income') {
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setUser($this->user);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]);
$journals = $collector->getJournals(); $journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount')); $sum = strval($journals->sum('transaction_amount'));
@@ -111,6 +119,8 @@ class MetaPieChart implements MetaPieChartInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param Collection $accounts * @param Collection $accounts
* *
* @return MetaPieChartInterface * @return MetaPieChartInterface
@@ -123,6 +133,8 @@ class MetaPieChart implements MetaPieChartInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param Collection $budgets * @param Collection $budgets
* *
* @return MetaPieChartInterface * @return MetaPieChartInterface
@@ -135,6 +147,8 @@ class MetaPieChart implements MetaPieChartInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param Collection $categories * @param Collection $categories
* *
* @return MetaPieChartInterface * @return MetaPieChartInterface
@@ -147,6 +161,8 @@ class MetaPieChart implements MetaPieChartInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param bool $collectOtherObjects * @param bool $collectOtherObjects
* *
* @return MetaPieChartInterface * @return MetaPieChartInterface
@@ -159,6 +175,8 @@ class MetaPieChart implements MetaPieChartInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param Carbon $end * @param Carbon $end
* *
* @return MetaPieChartInterface * @return MetaPieChartInterface
@@ -171,6 +189,8 @@ class MetaPieChart implements MetaPieChartInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param Carbon $start * @param Carbon $start
* *
* @return MetaPieChartInterface * @return MetaPieChartInterface
@@ -183,6 +203,22 @@ class MetaPieChart implements MetaPieChartInterface
} }
/** /**
* @codeCoverageIgnore
*
* @param Collection $tags
*
* @return MetaPieChartInterface
*/
public function setTags(Collection $tags): MetaPieChartInterface
{
$this->tags = $tags;
return $this;
}
/**
* @codeCoverageIgnore
*
* @param User $user * @param User $user
* *
* @return MetaPieChartInterface * @return MetaPieChartInterface
@@ -194,23 +230,32 @@ class MetaPieChart implements MetaPieChartInterface
return $this; return $this;
} }
protected function getTransactions(string $direction) /**
* @param string $direction
*
* @return Collection
*/
protected function getTransactions(string $direction): Collection
{ {
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$modifier = -1;
if ($direction === 'expense') {
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$modifier = 1;
}
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$collector->addFilter(NegativeAmountFilter::class);
if ($direction === 'expense') {
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$collector->addFilter(PositiveAmountFilter::class);
$collector->removeFilter(NegativeAmountFilter::class);
}
$collector->setUser($this->user);
$collector->setAccounts($this->accounts); $collector->setAccounts($this->accounts);
$collector->setRange($this->start, $this->end); $collector->setRange($this->start, $this->end);
$collector->setTypes($types); $collector->setTypes($types);
$collector->withOpposingAccount(); $collector->withOpposingAccount();
$collector->addFilter(OpposingAccountFilter::class);
if ($direction === 'income') { if ($direction === 'income') {
$collector->disableFilter(); $collector->removeFilter(TransferFilter::class);
} }
if ($this->budgets->count() > 0) { if ($this->budgets->count() > 0) {
@@ -219,12 +264,13 @@ class MetaPieChart implements MetaPieChartInterface
if ($this->categories->count() > 0) { if ($this->categories->count() > 0) {
$collector->setCategories($this->categories); $collector->setCategories($this->categories);
} }
if ($this->tags->count() > 0) {
$collector->setTags($this->tags);
$collector->withCategoryInformation();
$collector->withBudgetInformation();
}
$accountIds = $this->accounts->pluck('id')->toArray(); return $collector->getJournals();
$transactions = $collector->getJournals();
$set = Support::filterTransactions($transactions, $accountIds, $modifier);
return $set;
} }
/** /**
@@ -233,8 +279,13 @@ class MetaPieChart implements MetaPieChartInterface
* *
* @return array * @return array
*/ */
protected function groupByFields(Collection $set, array $fields) protected function groupByFields(Collection $set, array $fields): array
{ {
if (count($fields) === 0 && $this->tags->count() > 0) {
// do a special group on tags:
return $this->groupByTag($set);
}
$grouped = []; $grouped = [];
/** @var Transaction $transaction */ /** @var Transaction $transaction */
foreach ($set as $transaction) { foreach ($set as $transaction) {
@@ -261,10 +312,11 @@ class MetaPieChart implements MetaPieChartInterface
$chartData = []; $chartData = [];
$names = []; $names = [];
$repository = app($this->repositories[$type]); $repository = app($this->repositories[$type]);
$repository->setUser($this->user);
foreach ($array as $objectId => $amount) { foreach ($array as $objectId => $amount) {
if (!isset($names[$objectId])) { if (!isset($names[$objectId])) {
$object = $repository->find(intval($objectId)); $object = $repository->find(intval($objectId));
$names[$objectId] = $object->name; $names[$objectId] = $object->name ?? $object->tag;
} }
$amount = Steam::positive($amount); $amount = Steam::positive($amount);
$this->total = bcadd($this->total, $amount); $this->total = bcadd($this->total, $amount);
@@ -274,4 +326,27 @@ class MetaPieChart implements MetaPieChartInterface
return $chartData; return $chartData;
} }
/**
* @param Collection $set
*
* @return array
*/
private function groupByTag(Collection $set): array
{
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$journal = $transaction->transactionJournal;
$tags = $journal->tags;
/** @var Tag $tag */
foreach ($tags as $tag) {
$tagId = $tag->id;
$grouped[$tagId] = $grouped[$tagId] ?? '0';
$grouped[$tagId] = bcadd($transaction->transaction_amount, $grouped[$tagId]);
}
}
return $grouped;
}
} }

View File

@@ -72,6 +72,13 @@ interface MetaPieChartInterface
*/ */
public function setStart(Carbon $start): MetaPieChartInterface; public function setStart(Carbon $start): MetaPieChartInterface;
/**
* @param Collection $tags
*
* @return MetaPieChartInterface
*/
public function setTags(Collection $tags): MetaPieChartInterface;
/** /**
* @param User $user * @param User $user
* *

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;
use FireflyIII\Models\Account as AccountModel; use FireflyIII\Models\Account as AccountModel;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;
use FireflyIII\Models\Account as AccountModel; use FireflyIII\Models\Account as AccountModel;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;
use Carbon\Carbon; use Carbon\Carbon;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;
use Carbon\Carbon; use Carbon\Carbon;

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Collection; namespace FireflyIII\Helpers\Collection;
use FireflyIII\Models\Category as CategoryModel; use FireflyIII\Models\Category as CategoryModel;

View File

@@ -18,12 +18,17 @@ use Carbon\Carbon;
use Crypt; use Crypt;
use DB; use DB;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Filter\FilterInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
@@ -55,17 +60,30 @@ class JournalCollector implements JournalCollectorInterface
'transaction_journals.description', 'transaction_journals.description',
'transaction_journals.date', 'transaction_journals.date',
'transaction_journals.encrypted', 'transaction_journals.encrypted',
'transaction_currencies.code as transaction_currency_code',
'transaction_types.type as transaction_type_type', 'transaction_types.type as transaction_type_type',
'transaction_journals.bill_id', 'transaction_journals.bill_id',
'bills.name as bill_name', 'bills.name as bill_name',
'bills.name_encrypted as bill_name_encrypted', 'bills.name_encrypted as bill_name_encrypted',
'transactions.id as id', 'transactions.id as id',
'transactions.amount as transaction_amount',
'transactions.description as transaction_description', 'transactions.description as transaction_description',
'transactions.account_id', 'transactions.account_id',
'transactions.identifier', 'transactions.identifier',
'transactions.transaction_journal_id', 'transactions.transaction_journal_id',
'transactions.amount as transaction_amount',
'transactions.transaction_currency_id as transaction_currency_id',
'transaction_currencies.code as transaction_currency_code',
'transaction_currencies.symbol as transaction_currency_symbol',
'transaction_currencies.decimal_places as transaction_currency_dp',
'transactions.foreign_amount as transaction_foreign_amount',
'transactions.foreign_currency_id as foreign_currency_id',
'foreign_currencies.code as foreign_currency_code',
'foreign_currencies.symbol as foreign_currency_symbol',
'foreign_currencies.decimal_places as foreign_currency_dp',
'accounts.name as account_name', 'accounts.name as account_name',
'accounts.encrypted as account_encrypted', 'accounts.encrypted as account_encrypted',
'account_types.type as account_type', 'account_types.type as account_type',
@@ -74,6 +92,9 @@ class JournalCollector implements JournalCollectorInterface
private $filterInternalTransfers; private $filterInternalTransfers;
/** @var bool */ /** @var bool */
private $filterTransfers = false; private $filterTransfers = false;
/** @var array */
private $filters = [InternalTransferFilter::class];
/** @var bool */ /** @var bool */
private $joinedBudget = false; private $joinedBudget = false;
/** @var bool */ /** @var bool */
@@ -95,6 +116,22 @@ class JournalCollector implements JournalCollectorInterface
/** @var User */ /** @var User */
private $user; private $user;
/**
* @param string $filter
*
* @return JournalCollectorInterface
*/
public function addFilter(string $filter): JournalCollectorInterface
{
$interfaces = class_implements($filter);
if (in_array(FilterInterface::class, $interfaces)) {
Log::debug(sprintf('Enabled filter %s', $filter));
$this->filters[] = $filter;
}
return $this;
}
/** /**
* @return int * @return int
* @throws FireflyException * @throws FireflyException
@@ -119,36 +156,6 @@ class JournalCollector implements JournalCollectorInterface
return $this->count; return $this->count;
} }
/**
* @return JournalCollectorInterface
*/
public function disableFilter(): JournalCollectorInterface
{
$this->filterTransfers = false;
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function disableInternalFilter(): JournalCollectorInterface
{
$this->filterInternalTransfers = false;
return $this;
}
/**
* @return JournalCollectorInterface
*/
public function enableInternalFilter(): JournalCollectorInterface
{
$this->filterInternalTransfers = true;
return $this;
}
/** /**
* @return Collection * @return Collection
*/ */
@@ -157,14 +164,9 @@ class JournalCollector implements JournalCollectorInterface
$this->run = true; $this->run = true;
/** @var Collection $set */ /** @var Collection $set */
$set = $this->query->get(array_values($this->fields)); $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()));
// possibly filter "internal" transfers:
$set = $this->filterInternalTransfers($set);
Log::debug(sprintf('Count of set after filterInternalTransfers() is %d', $set->count()));
// run all filters:
$set = $this->filter($set);
// loop for decryption. // loop for decryption.
$set->each( $set->each(
@@ -204,6 +206,22 @@ class JournalCollector implements JournalCollectorInterface
return $journals; return $journals;
} }
/**
* @param string $filter
*
* @return JournalCollectorInterface
*/
public function removeFilter(string $filter): JournalCollectorInterface
{
$key = array_search($filter, $this->filters, true);
if (!($key === false)) {
Log::debug(sprintf('Removed filter %s', $filter));
unset($this->filters[$key]);
}
return $this;
}
/** /**
* @param Collection $accounts * @param Collection $accounts
* *
@@ -219,6 +237,7 @@ class JournalCollector implements JournalCollectorInterface
} }
if ($accounts->count() > 1) { if ($accounts->count() > 1) {
$this->addFilter(TransferFilter::class);
$this->filterTransfers = true; $this->filterTransfers = true;
} }
@@ -242,6 +261,7 @@ class JournalCollector implements JournalCollectorInterface
} }
if ($accounts->count() > 1) { if ($accounts->count() > 1) {
$this->addFilter(TransferFilter::class);
$this->filterTransfers = true; $this->filterTransfers = true;
} }
@@ -430,6 +450,20 @@ class JournalCollector implements JournalCollectorInterface
return $this; return $this;
} }
/**
* @param Collection $tags
*
* @return JournalCollectorInterface
*/
public function setTags(Collection $tags): JournalCollectorInterface
{
$this->joinTagTables();
$tagIds = $tags->pluck('id')->toArray();
$this->query->whereIn('tag_transaction_journal.tag_id', $tagIds);
return $this;
}
/** /**
* @param array $types * @param array $types
* *
@@ -450,7 +484,9 @@ class JournalCollector implements JournalCollectorInterface
*/ */
public function setUser(User $user) public function setUser(User $user)
{ {
Log::debug(sprintf('Journal collector now collecting for user #%d', $user->id));
$this->user = $user; $this->user = $user;
$this->startQuery();
} }
/** /**
@@ -458,13 +494,15 @@ class JournalCollector implements JournalCollectorInterface
*/ */
public function startQuery() public function startQuery()
{ {
Log::debug('journalCollector::startQuery');
/** @var EloquentBuilder $query */ /** @var EloquentBuilder $query */
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') $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('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id') ->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id') ->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transactions.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', 'transactions.foreign_currency_id')
->whereNull('transactions.deleted_at') ->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at') ->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id) ->where('transaction_journals.user_id', $this->user->id)
@@ -546,79 +584,23 @@ class JournalCollector implements JournalCollectorInterface
* *
* @return Collection * @return Collection
*/ */
private function filterInternalTransfers(Collection $set): Collection private function filter(Collection $set): Collection
{ {
if ($this->filterInternalTransfers === false) { // create all possible filters:
Log::debug('Did NO filtering for internal transfers on given set.'); $filters = [
InternalTransferFilter::class => new InternalTransferFilter($this->accountIds),
return $set; OpposingAccountFilter::class => new OpposingAccountFilter($this->accountIds),
TransferFilter::class => new TransferFilter,
PositiveAmountFilter::class => new PositiveAmountFilter,
NegativeAmountFilter::class => new NegativeAmountFilter,
];
Log::debug(sprintf('Will run %d filters on the set.', count($this->filters)));
foreach ($this->filters as $enabled) {
if (isset($filters[$enabled])) {
Log::debug(sprintf('Before filter %s: %d', $enabled, $set->count()));
$set = $filters[$enabled]->filter($set);
Log::debug(sprintf('After filter %s: %d', $enabled, $set->count()));
} }
if ($this->joinedOpposing === false) {
Log::info('Cannot filter internal transfers because no opposing information is present.');
return $set;
}
$accountIds = $this->accountIds;
$set = $set->filter(
function (Transaction $transaction) use ($accountIds) {
// both id's in $accountids?
if (in_array($transaction->account_id, $accountIds) && in_array($transaction->opposing_account_id, $accountIds)) {
Log::debug(
sprintf(
'Transaction #%d has #%d and #%d in set, so removed',
$transaction->id, $transaction->account_id, $transaction->opposing_account_id
), $accountIds
);
return false;
}
return $transaction;
}
);
return $set;
}
/**
* 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 by removing transfers that have been selected twice.
*
* @param Collection $set
*
* @return Collection
*/
private function filterTransfers(Collection $set): Collection
{
if ($this->filterTransfers) {
$count = [];
$new = new Collection;
/** @var Transaction $transaction */
foreach ($set as $transaction) {
if ($transaction->transaction_type_type !== TransactionType::TRANSFER) {
$new->push($transaction);
continue;
}
// make property string:
$journalId = $transaction->transaction_journal_id;
$amount = Steam::positive($transaction->transaction_amount);
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
sort($accountIds);
$key = $journalId . '-' . join(',', $accountIds) . '-' . $amount;
Log::debug(sprintf('Key is %s', $key));
if (!isset($count[$key])) {
// not yet counted? add to new set and count it:
$new->push($transaction);
$count[$key] = 1;
}
}
return $new;
} }
return $set; return $set;

View File

@@ -28,26 +28,18 @@ use Illuminate\Support\Collection;
*/ */
interface JournalCollectorInterface interface JournalCollectorInterface
{ {
/**
* @param string $filter
*
* @return JournalCollectorInterface
*/
public function addFilter(string $filter): JournalCollectorInterface;
/** /**
* @return int * @return int
*/ */
public function count(): int; public function count(): int;
/**
* @return JournalCollectorInterface
*/
public function disableFilter(): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function disableInternalFilter(): JournalCollectorInterface;
/**
* @return JournalCollectorInterface
*/
public function enableInternalFilter(): JournalCollectorInterface;
/** /**
* @return Collection * @return Collection
*/ */
@@ -58,6 +50,13 @@ interface JournalCollectorInterface
*/ */
public function getPaginatedJournals(): LengthAwarePaginator; public function getPaginatedJournals(): LengthAwarePaginator;
/**
* @param string $filter
*
* @return JournalCollectorInterface
*/
public function removeFilter(string $filter): JournalCollectorInterface;
/** /**
* @param Collection $accounts * @param Collection $accounts
* *
@@ -141,6 +140,13 @@ interface JournalCollectorInterface
*/ */
public function setTag(Tag $tag): JournalCollectorInterface; public function setTag(Tag $tag): JournalCollectorInterface;
/**
* @param Collection $tags
*
* @return JournalCollectorInterface
*/
public function setTags(Collection $tags): JournalCollectorInterface;
/** /**
* @param array $types * @param array $types
* *

View File

@@ -0,0 +1,56 @@
<?php
/**
* AmountFilter.php
* Copyright (c) 2017 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\Filter;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
use Log;
/**
* Class AmountFilter
*
* This filter removes transactions with either a positive amount ($parameters = 1) or a negative amount
* ($parameter = -1). This is helpful when a Collection has you with both transactions in a journal.
*
* @package FireflyIII\Helpers\Filter
*/
class AmountFilter implements FilterInterface
{
/** @var int */
private $modifier = 0;
public function __construct(int $modifier)
{
$this->modifier = $modifier;
}
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
return $set->filter(
function (Transaction $transaction) {
// remove by amount
if (bccomp($transaction->transaction_amount, '0') === $this->modifier) {
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
return null;
}
return $transaction;
}
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* EmptyFilter.php
* Copyright (c) 2017 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\Filter;
use Illuminate\Support\Collection;
/**
* Class EmptyFilter
*
* @package FireflyIII\Helpers\Filter
*/
class EmptyFilter implements FilterInterface
{
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
return $set;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* FilterInterface.php
* Copyright (c) 2017 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\Filter;
use Illuminate\Support\Collection;
interface FilterInterface
{
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection;
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* InternalTransferFilter.php
* Copyright (c) 2017 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\Filter;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
use Log;
/**
* Class InternalTransferFilter
*
* This filter removes any filters that are from A to B or from B to A given a set of
* account id's (in $parameters) where A and B are mentioned. So transfers between the mentioned
* accounts will be removed.
*
* @package FireflyIII\Helpers\Filter
*/
class InternalTransferFilter implements FilterInterface
{
/** @var array */
private $accounts = [];
/**
* InternalTransferFilter constructor.
*
* @param array $accounts
*/
public function __construct(array $accounts)
{
$this->accounts = $accounts;
}
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
return $set->filter(
function (Transaction $transaction) {
if (is_null($transaction->opposing_account_id)) {
return $transaction;
}
// both id's in $parameters?
if (in_array($transaction->account_id, $this->accounts) && in_array($transaction->opposing_account_id, $this->accounts)) {
Log::debug(
sprintf(
'Transaction #%d has #%d and #%d in set, so removed',
$transaction->id, $transaction->account_id, $transaction->opposing_account_id
), $this->accounts
);
return false;
}
return $transaction;
}
);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* NegativeAmountFilter.php
* Copyright (c) 2017 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\Filter;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
use Log;
/**
* Class NegativeAmountFilter
*
* This filter removes entries with a negative amount (the original modifier is -1).
*
* @package FireflyIII\Helpers\Filter
*/
class NegativeAmountFilter implements FilterInterface
{
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
return $set->filter(
function (Transaction $transaction) {
// remove by 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;
}
);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* OpposingAccountFilter.php
* Copyright (c) 2017 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\Filter;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
use Log;
/**
* Class OpposingAccountFilter
*
* This filter is similar to the internal transfer filter but only removes transactions when the opposing account is
* amongst $parameters (list of account ID's).
*
* @package FireflyIII\Helpers\Filter
*/
class OpposingAccountFilter implements FilterInterface
{
/** @var array */
private $accounts = [];
/**
* InternalTransferFilter constructor.
*
* @param array $accounts
*/
public function __construct(array $accounts)
{
$this->accounts = $accounts;
}
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
return $set->filter(
function (Transaction $transaction) {
$opposing = $transaction->opposing_account_id;
// remove internal transfer
if (in_array($opposing, $this->accounts)) {
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id), $this->accounts);
return null;
}
return $transaction;
}
);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* PositiveAmountFilter.php
* Copyright (c) 2017 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\Filter;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
use Log;
/**
* Class PositiveAmountFilter
*
* This filter removes entries with a negative amount (the original modifier is -1).
*
* This filter removes transactions with either a positive amount ($parameters = 1) or a negative amount
* ($parameter = -1). This is helpful when a Collection has you with both transactions in a journal.
*
* @package FireflyIII\Helpers\Filter
*/
class PositiveAmountFilter implements FilterInterface
{
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
return $set->filter(
function (Transaction $transaction) {
// remove by 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;
}
);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* TransferFilter.php
* Copyright (c) 2017 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\Filter;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection;
use Steam;
/**
* Class TransferFilter
*
* This filter removes any transfers that are in the collection twice (from A to B and from B to A).
*
* @package FireflyIII\Helpers\Filter
*/
class TransferFilter implements FilterInterface
{
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
$count = [];
$new = new Collection;
/** @var Transaction $transaction */
foreach ($set as $transaction) {
if ($transaction->transaction_type_type !== TransactionType::TRANSFER) {
$new->push($transaction);
continue;
}
// make property string:
$journalId = $transaction->transaction_journal_id;
$amount = Steam::positive($transaction->transaction_amount);
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
sort($accountIds);
$key = $journalId . '-' . join(',', $accountIds) . '-' . $amount;
if (!isset($count[$key])) {
// not yet counted? add to new set and count it:
$new->push($transaction);
$count[$key] = 1;
}
}
return $new;
}
}

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Help; namespace FireflyIII\Helpers\Help;
use Cache; use Cache;
@@ -43,12 +44,12 @@ class Help implements HelpInterface
} }
/** /**
* @param string $language
* @param string $route * @param string $route
* @param string $language
* *
* @return string * @return string
*/ */
public function getFromGithub(string $language, string $route): string public function getFromGithub(string $route, string $language): string
{ {
$uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route); $uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route);
@@ -123,6 +124,7 @@ class Help implements HelpInterface
if (strlen($content) > 0) { if (strlen($content) > 0) {
Log::debug(sprintf('Will store entry in cache: %s', $key)); Log::debug(sprintf('Will store entry in cache: %s', $key));
Cache::put($key, $content, 10080); // a week. Cache::put($key, $content, 10080); // a week.
return; return;
} }
Log::info(sprintf('Will not cache %s because content is empty.', $key)); Log::info(sprintf('Will not cache %s because content is empty.', $key));

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Helpers\Help; namespace FireflyIII\Helpers\Help;
/** /**
@@ -29,12 +30,12 @@ interface HelpInterface
public function getFromCache(string $route, string $language): string; public function getFromCache(string $route, string $language): string;
/** /**
* @param string $language
* @param string $route * @param string $route
* @param string $language
* *
* @return string * @return string
*/ */
public function getFromGithub(string $language, string $route): string; public function getFromGithub(string $route, string $language): string;
/** /**
* @param string $route * @param string $route

View File

@@ -0,0 +1,199 @@
<?php
/**
* PopupReport.php
* Copyright (c) 2017 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\Report;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection;
/**
* Class PopupReport
*
* @package FireflyIII\Helpers\Report
*/
class PopupReport implements PopupReportInterface
{
/**
* @param $account
* @param $attributes
*
* @return Collection
*/
public function balanceDifference($account, $attributes): Collection
{
// row that displays difference
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector
->setAccounts(new Collection([$account]))
->setTypes([TransactionType::WITHDRAWAL])
->setRange($attributes['startDate'], $attributes['endDate'])
->withoutBudget();
$journals = $collector->getJournals();
return $journals->filter(
function (Transaction $transaction) {
$tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count();
if ($tags === 0) {
return true;
}
return false;
}
);
}
/**
* @param Budget $budget
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function balanceForBudget(Budget $budget, Account $account, array $attributes): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setBudget($budget);
$journals = $collector->getJournals();
return $journals;
}
/**
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function balanceForNoBudget(Account $account, array $attributes): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector
->setAccounts(new Collection([$account]))
->setTypes([TransactionType::WITHDRAWAL])
->setRange($attributes['startDate'], $attributes['endDate'])
->withoutBudget();
return $collector->getJournals();
}
/**
* @param Budget $budget
* @param array $attributes
*
* @return Collection
*/
public function byBudget(Budget $budget, array $attributes): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($attributes['accounts'])->setRange($attributes['startDate'], $attributes['endDate']);
if (is_null($budget->id)) {
$collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
}
if (!is_null($budget->id)) {
$collector->setBudget($budget);
}
$journals = $collector->getJournals();
return $journals;
}
/**
* @param Category $category
* @param array $attributes
*
* @return Collection
*/
public function byCategory(Category $category, array $attributes): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($attributes['accounts'])->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setRange($attributes['startDate'], $attributes['endDate'])
->setCategory($category);
$journals = $collector->getJournals();
return $journals;
}
/**
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function byExpenses(Account $account, array $attributes): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]);
$journals = $collector->getJournals();
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
// filter for transfers and withdrawals TO the given $account
$journals = $journals->filter(
function (Transaction $transaction) use ($report) {
// get the destinations:
$sources = $transaction->transactionJournal->sourceAccountList()->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $sources));
}
);
return $journals;
}
/**
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function byIncome(Account $account, array $attributes): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$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(
function (Transaction $transaction) use ($report) {
// get the destinations:
$destinations = $transaction->transactionJournal->destinationAccountList()->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $destinations));
}
);
return $journals;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* PopupReportInterface.php
* Copyright (c) 2017 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\Report;
use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use Illuminate\Support\Collection;
/**
* Interface PopupReportInterface
*
* @package FireflyIII\Helpers\Report
*/
interface PopupReportInterface
{
/**
* @param $account
* @param $attributes
*
* @return Collection
*/
public function balanceDifference($account, $attributes): Collection;
/**
* @param Budget $budget
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function balanceForBudget(Budget $budget, Account $account, array $attributes): Collection;
/**
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function balanceForNoBudget(Account $account, array $attributes): Collection;
/**
* @param Budget $budget
* @param array $attributes
*
* @return Collection
*/
public function byBudget(Budget $budget, array $attributes): Collection;
/**
* @param Category $category
* @param array $attributes
*
* @return Collection
*/
public function byCategory(Category $category, array $attributes): Collection;
/**
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function byExpenses(Account $account, array $attributes): Collection;
/**
* @param Account $account
* @param array $attributes
*
* @return Collection
*/
public function byIncome(Account $account, array $attributes): Collection;
}

View File

@@ -22,9 +22,10 @@ use FireflyIII\Http\Requests\AccountFormRequest;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -69,7 +70,8 @@ class AccountController extends Controller
{ {
/** @var CurrencyRepositoryInterface $repository */ /** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class); $repository = app(CurrencyRepositoryInterface::class);
$currencies = ExpandedForm::makeSelectList($repository->get()); $allCurrencies = $repository->get();
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
$defaultCurrency = Amount::getDefaultCurrency(); $defaultCurrency = Amount::getDefaultCurrency();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$subTitle = trans('firefly.make_new_' . $what . '_account'); $subTitle = trans('firefly.make_new_' . $what . '_account');
@@ -90,7 +92,7 @@ class AccountController extends Controller
$request->session()->flash('gaEventCategory', 'accounts'); $request->session()->flash('gaEventCategory', 'accounts');
$request->session()->flash('gaEventAction', 'create-' . $what); $request->session()->flash('gaEventAction', 'create-' . $what);
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencies', 'roles')); return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencySelectList', 'allCurrencies', 'roles'));
} }
@@ -146,13 +148,13 @@ class AccountController extends Controller
*/ */
public function edit(Request $request, Account $account) public function edit(Request $request, Account $account)
{ {
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$what = config('firefly.shortNamesByFullName')[$account->accountType->type]; $what = config('firefly.shortNamesByFullName')[$account->accountType->type];
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]); $subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
/** @var CurrencyRepositoryInterface $repository */ $allCurrencies = $repository->get();
$repository = app(CurrencyRepositoryInterface::class); $currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
$currencies = ExpandedForm::makeSelectList($repository->get());
$roles = []; $roles = [];
foreach (config('firefly.accountRoles') as $role) { foreach (config('firefly.accountRoles') as $role) {
$roles[$role] = strval(trans('firefly.account_role_' . $role)); $roles[$role] = strval(trans('firefly.account_role_' . $role));
@@ -172,6 +174,7 @@ class AccountController extends Controller
$openingBalanceAmount = $account->getOpeningBalanceAmount() === '0' ? '' : $openingBalanceAmount; $openingBalanceAmount = $account->getOpeningBalanceAmount() === '0' ? '' : $openingBalanceAmount;
$openingBalanceDate = $account->getOpeningBalanceDate(); $openingBalanceDate = $account->getOpeningBalanceDate();
$openingBalanceDate = $openingBalanceDate->year === 1900 ? null : $openingBalanceDate->format('Y-m-d'); $openingBalanceDate = $openingBalanceDate->year === 1900 ? null : $openingBalanceDate->format('Y-m-d');
$currency = $repository->find(intval($account->getMeta('currency_id')));
$preFilled = [ $preFilled = [
'accountNumber' => $account->getMeta('accountNumber'), 'accountNumber' => $account->getMeta('accountNumber'),
@@ -182,13 +185,18 @@ class AccountController extends Controller
'openingBalanceDate' => $openingBalanceDate, 'openingBalanceDate' => $openingBalanceDate,
'openingBalance' => $openingBalanceAmount, 'openingBalance' => $openingBalanceAmount,
'virtualBalance' => $account->virtual_balance, 'virtualBalance' => $account->virtual_balance,
'currency_id' => $account->getMeta('currency_id'), 'currency_id' => $currency->id,
]; ];
$request->session()->flash('preFilled', $preFilled); $request->session()->flash('preFilled', $preFilled);
$request->session()->flash('gaEventCategory', 'accounts'); $request->session()->flash('gaEventCategory', 'accounts');
$request->session()->flash('gaEventAction', 'edit-' . $what); $request->session()->flash('gaEventAction', 'edit-' . $what);
return view('accounts.edit', compact('currencies', 'account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles')); return view(
'accounts.edit', compact(
'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles'
)
);
} }
/** /**
@@ -211,8 +219,8 @@ class AccountController extends Controller
$start->subDay(); $start->subDay();
$ids = $accounts->pluck('id')->toArray(); $ids = $accounts->pluck('id')->toArray();
$startBalances = Steam::balancesById($ids, $start); $startBalances = Steam::balancesByAccounts($accounts, $start);
$endBalances = Steam::balancesById($ids, $end); $endBalances = Steam::balancesByAccounts($accounts, $end);
$activities = Steam::getLastActivities($ids); $activities = Steam::getLastActivities($ids);
$accounts->each( $accounts->each(
@@ -227,101 +235,99 @@ class AccountController extends Controller
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts')); return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
} }
/** /**
* @param Request $request * @param Request $request
* @param JournalCollectorInterface $collector * @param JournalRepositoryInterface $repository
* @param Account $account * @param Account $account
* @param string $moment
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/ */
public function show(Request $request, JournalCollectorInterface $collector, Account $account) public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
{ {
if ($account->accountType->type === AccountType::INITIAL_BALANCE) { if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
return $this->redirectToOriginalAccount($account); return $this->redirectToOriginalAccount($account);
} }
// show journals from current period only: /** @var CurrencyRepositoryInterface $currencyRepos */
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); $currencyRepos = app(CurrencyRepositoryInterface::class);
$subTitle = $account->name;
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$start = session('start', Navigation::startOfPeriod(new Carbon, $range)); $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$chartUri = route('chart.account.single', [$account->id]); $chartUri = route('chart.account.single', [$account->id]);
$accountType = $account->accountType->type; $start = null;
$end = null;
$periods = new Collection;
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
// grab those journals: // prep for "all" view.
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page); if ($moment === 'all') {
$journals = $collector->getPaginatedJournals(); $subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
$journals->setPath('accounts/show/' . $account->id);
// generate entries for each period (and cache those)
$entries = $this->periodEntries($account);
return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
}
/**
* @param Request $request
* @param AccountRepositoryInterface $repository
* @param Account $account
*
* @return View
*/
public function showAll(Request $request, AccountRepositoryInterface $repository, Account $account)
{
$subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$chartUri = route('chart.account.all', [$account->id]); $chartUri = route('chart.account.all', [$account->id]);
$first = $repository->first();
// replace with journal collector: $start = $first->date ?? new Carbon;
/** @var JournalCollectorInterface $collector */ $end = new Carbon;
$collector = app(JournalCollectorInterface::class);
$collector->setUser(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);
// same call, except "entries".
return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
} }
/** // prep for "specific date" view.
* @param Request $request if (strlen($moment) > 0 && $moment !== 'all') {
* @param Account $account $start = new Carbon($moment);
* @param string $date $end = Navigation::endOfPeriod($start, $range);
* $subTitle = trans(
* @return View 'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
*/ 'end' => $end->formatLocalized($this->monthAndDayFormat)]
public function showByDate(Request $request, Account $account, string $date) );
{ $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
$carbon = new Carbon($date); $periods = $this->getPeriodOverview($account);
$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($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$chartUri = route('chart.account.period', [$account->id, $carbon->format('Y-m-d')]);
$accountType = $account->accountType->type;
// replace with journal collector: // prep for current period
/** @var JournalCollectorInterface $collector */ if (strlen($moment) === 0) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$subTitle = trans(
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview($account);
}
$count = 0;
$loop = 0;
// grab journals, but be prepared to jump a period back to get the right ones:
Log::info('Now at loop start.');
while ($count === 0 && $loop < 3) {
$loop++;
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page); Log::info('Count is zero, search for journals.');
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
if (!is_null($start)) {
$collector->setRange($start, $end);
}
$journals = $collector->getPaginatedJournals(); $journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id . '/' . $date); $journals->setPath('accounts/show/' . $account->id . '/' . $moment);
$count = $journals->getCollection()->count();
if ($count === 0) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
}
}
// generate entries for each period (and cache those) if ($moment != 'all' && $loop > 1) {
$entries = $this->periodEntries($account); $subTitle = trans(
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
// same call, except "entries".
return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')); return view(
'accounts.show',
compact('account', 'currency', 'moment', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')
);
} }
/** /**
@@ -335,7 +341,6 @@ class AccountController extends Controller
{ {
$data = $request->getAccountData(); $data = $request->getAccountData();
$account = $repository->store($data); $account = $repository->store($data);
$request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name]))); $request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
Preferences::mark(); Preferences::mark();
@@ -409,13 +414,10 @@ class AccountController extends Controller
* *
* @return Collection * @return Collection
*/ */
private function periodEntries(Account $account): Collection private function getPeriodOverview(Account $account): Collection
{ {
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
$start = $repository->oldestJournalDate($account); $start = $repository->oldestJournalDate($account);
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range); $start = Navigation::startOfPeriod($start, $range);
@@ -430,25 +432,39 @@ class AccountController extends Controller
$cache->addProperty($account->id); $cache->addProperty($account->id);
if ($cache->has()) { if ($cache->has()) {
Log::debug('Entries are cached, return cache.'); return $cache->get(); // @codeCoverageIgnore
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.'); Log::debug('Going to get period expenses and incomes.');
while ($end >= $start) { while ($end >= $start) {
$end = Navigation::startOfPeriod($end, $range); $end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($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); // try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$earned = strval($collector->getJournals()->sum('transaction_amount'));
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$spent = strval($collector->getJournals()->sum('transaction_amount'));
$dateStr = $end->format('Y-m-d'); $dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range); $dateName = Navigation::periodShow($end, $range);
$entries->push([$dateStr, $dateName, $spent, $earned, clone $end]); $entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'date' => clone $end]
);
$end = Navigation::subtractPeriod($end, $range, 1); $end = Navigation::subtractPeriod($end, $range, 1);
} }
@@ -476,7 +492,7 @@ class AccountController extends Controller
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first(); $opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
if (is_null($opposingTransaction)) { if (is_null($opposingTransaction)) {
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); // @codeCoverageIgnore
} }
return redirect(route('accounts.show', [$opposingTransaction->account_id])); return redirect(route('accounts.show', [$opposingTransaction->account_id]));

View File

@@ -18,6 +18,7 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\UserFormRequest; use FireflyIII\Http\Requests\UserFormRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Log;
use Preferences; use Preferences;
use Session; use Session;
use View; use View;
@@ -127,31 +128,31 @@ class UserController extends Controller
* @param UserFormRequest $request * @param UserFormRequest $request
* @param User $user * @param User $user
* *
* @param UserRepositoryInterface $repository
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/ */
public function update(UserFormRequest $request, User $user) public function update(UserFormRequest $request, User $user, UserRepositoryInterface $repository)
{ {
Log::debug('Actually here');
$data = $request->getUserData(); $data = $request->getUserData();
// update password // update password
if (strlen($data['password']) > 0) { if (strlen($data['password']) > 0) {
$user->password = bcrypt($data['password']); $repository->changePassword($user, $data['password']);
$user->save();
} }
// change blocked status and code: $repository->changeStatus($user, $data['blocked'], $data['blocked_code']);
$user->blocked = $data['blocked'];
$user->blocked_code = $data['blocked_code'];
$user->save();
Session::flash('success', strval(trans('firefly.updated_user', ['email' => $user->email]))); Session::flash('success', strval(trans('firefly.updated_user', ['email' => $user->email])));
Preferences::mark(); Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) { if (intval($request->get('return_to_edit')) === 1) {
// set value so edit routine will not overwrite URL: // @codeCoverageIgnoreStart
Session::put('users.edit.fromUpdate', true); Session::put('users.edit.fromUpdate', true);
return redirect(route('admin.users.edit', [$user->id]))->withInput(['return_to_edit' => 1]); return redirect(route('admin.users.edit', [$user->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
} }
// redirect to previous URL. // redirect to previous URL.

View File

@@ -98,10 +98,14 @@ class AttachmentController extends Controller
*/ */
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment) public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
{ {
if ($repository->exists($attachment)) { if ($repository->exists($attachment)) {
$content = $repository->getContent($attachment); $content = $repository->getContent($attachment);
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\')); $quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
/** @var LaravelResponse $response */ /** @var LaravelResponse $response */
$response = response($content, 200); $response = response($content, 200);
$response $response
@@ -149,6 +153,7 @@ class AttachmentController extends Controller
{ {
$image = 'images/page_green.png'; $image = 'images/page_green.png';
if ($attachment->mime == 'application/pdf') { if ($attachment->mime == 'application/pdf') {
$image = 'images/page_white_acrobat.png'; $image = 'images/page_white_acrobat.png';
} }
@@ -176,10 +181,11 @@ class AttachmentController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) { if (intval($request->get('return_to_edit')) === 1) {
// set value so edit routine will not overwrite URL: // @codeCoverageIgnoreStart
$request->session()->put('attachments.edit.fromUpdate', true); $request->session()->put('attachments.edit.fromUpdate', true);
return redirect(route('attachments.edit', [$attachment->id]))->withInput(['return_to_edit' => 1]); return redirect(route('attachments.edit', [$attachment->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
} }
// redirect to previous URL. // redirect to previous URL.

View File

@@ -13,6 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Auth; namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -42,23 +43,22 @@ class ForgotPasswordController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @param UserRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function sendResetLinkEmail(Request $request) public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
{ {
$this->validate($request, ['email' => 'required|email']); $this->validate($request, ['email' => 'required|email']);
// verify if the user is not a demo user. If so, we give him back an error. // verify if the user is not a demo user. If so, we give him back an error.
$user = User::where('email', $request->get('email'))->first(); $user = User::where('email', $request->get('email'))->first();
if (!is_null($user) && $user->hasRole('demo')) {
return back()->withErrors( if (!is_null($user) && $repository->hasRole($user, 'demo')) {
['email' => trans('firefly.cannot_reset_demo_user')] return back()->withErrors(['email' => trans('firefly.cannot_reset_demo_user')]);
);
} }
$response = $this->broker()->sendResetLink( $response = $this->broker()->sendResetLink($request->only('email'));
$request->only('email')
);
if ($response === Password::RESET_LINK_SENT) { if ($response === Password::RESET_LINK_SENT) {
return back()->with('status', trans($response)); return back()->with('status', trans($response));
@@ -67,8 +67,6 @@ class ForgotPasswordController extends Controller
// If an error was returned by the password broker, we will get this message // If an error was returned by the password broker, we will get this message
// translated so we can notify a user of the problem. We'll redirect back // translated so we can notify a user of the problem. We'll redirect back
// to where the users came from so they can attempt this process again. // to where the users came from so they can attempt this process again.
return back()->withErrors( return back()->withErrors(['email' => trans($response)]); // @codeCoverageIgnore
['email' => trans($response)]
);
} }
} }

View File

@@ -22,6 +22,8 @@ use Illuminate\Http\Request;
use Lang; use Lang;
/** /**
* @codeCoverageIgnore
*
* Class LoginController * Class LoginController
* *
* @package FireflyIII\Http\Controllers\Auth * @package FireflyIII\Http\Controllers\Auth
@@ -110,10 +112,14 @@ class LoginController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @param CookieJar $cookieJar
*
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function showLoginForm(Request $request) public function showLoginForm(Request $request, CookieJar $cookieJar)
{ {
// forget 2fa cookie:
$cookie = $cookieJar->forever('twoFactorAuthenticated', 'false');
// is allowed to? // is allowed to?
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data; $singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
$userCount = User::count(); $userCount = User::count();
@@ -125,7 +131,7 @@ class LoginController extends Controller
$email = $request->old('email'); $email = $request->old('email');
$remember = $request->old('remember'); $remember = $request->old('remember');
return view('auth.login', compact('allowRegistration', 'email', 'remember')); return view('auth.login', compact('allowRegistration', 'email', 'remember'))->withCookie($cookie);
} }
/** /**

View File

@@ -22,6 +22,8 @@ use Illuminate\Support\Facades\Password;
/** /**
* @codeCoverageIgnore
*
* Class PasswordController * Class PasswordController
* *
* @package FireflyIII\Http\Controllers\Auth * @package FireflyIII\Http\Controllers\Auth

View File

@@ -24,6 +24,8 @@ use Session;
use Validator; use Validator;
/** /**
* @codeCoverageIgnore
*
* Class RegisterController * Class RegisterController
* *
* @package FireflyIII\Http\Controllers\Auth * @package FireflyIII\Http\Controllers\Auth
@@ -122,12 +124,15 @@ class RegisterController extends Controller
*/ */
protected function create(array $data) protected function create(array $data)
{ {
return User::create( /** @var User $user */
$user = User::create(
[ [
'email' => $data['email'], 'email' => $data['email'],
'password' => bcrypt($data['password']), 'password' => bcrypt($data['password']),
] ]
); );
return $user;
} }
/** /**

View File

@@ -16,6 +16,8 @@ use FireflyIII\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
/** /**
* @codeCoverageIgnore
*
* Class ResetPasswordController * Class ResetPasswordController
* *
* @package FireflyIII\Http\Controllers\Auth * @package FireflyIII\Http\Controllers\Auth

View File

@@ -41,11 +41,12 @@ class TwoFactorController extends Controller
$user = auth()->user(); $user = auth()->user();
// to make sure the validator in the next step gets the secret, we push it in session // to make sure the validator in the next step gets the secret, we push it in session
$secret = Preferences::get('twoFactorAuthSecret', null)->data; $secretPreference = Preferences::get('twoFactorAuthSecret', null);
$secret = is_null($secretPreference) ? null : $secretPreference->data;
$title = strval(trans('firefly.two_factor_title')); $title = strval(trans('firefly.two_factor_title'));
// make sure the user has two factor configured: // make sure the user has two factor configured:
$has2FA = Preferences::get('twoFactorAuthEnabled', null)->data; $has2FA = Preferences::get('twoFactorAuthEnabled', false)->data;
if (is_null($has2FA) || $has2FA === false) { if (is_null($has2FA) || $has2FA === false) {
return redirect(route('index')); return redirect(route('index'));
} }
@@ -79,9 +80,11 @@ class TwoFactorController extends Controller
/** /**
* @param TokenFormRequest $request * @param TokenFormRequest $request
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation. * @param CookieJar $cookieJar
* *
* @return mixed * @return mixed
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
*
*/ */
public function postIndex(TokenFormRequest $request, CookieJar $cookieJar) public function postIndex(TokenFormRequest $request, CookieJar $cookieJar)
{ {

View File

@@ -240,10 +240,11 @@ class BillController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('create_another')) === 1) { if (intval($request->get('create_another')) === 1) {
// set value so create routine will not overwrite URL: // @codeCoverageIgnoreStart
$request->session()->put('bills.create.fromStore', true); $request->session()->put('bills.create.fromStore', true);
return redirect(route('bills.create'))->withInput(); return redirect(route('bills.create'))->withInput();
// @codeCoverageIgnoreEnd
} }
// redirect to previous URL. // redirect to previous URL.
@@ -267,10 +268,11 @@ class BillController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) { if (intval($request->get('return_to_edit')) === 1) {
// set value so edit routine will not overwrite URL: // @codeCoverageIgnoreStart
$request->session()->put('bills.edit.fromUpdate', true); $request->session()->put('bills.edit.fromUpdate', true);
return redirect(route('bills.edit', [$bill->id]))->withInput(['return_to_edit' => 1]); return redirect(route('bills.edit', [$bill->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('bills.edit.uri')); return redirect($this->getPreviousUri('bills.edit.uri'));

View File

@@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers;
use Amount; use Amount;
use Carbon\Carbon; use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\BudgetFormRequest; use FireflyIII\Http\Requests\BudgetFormRequest;
@@ -22,11 +23,15 @@ use FireflyIII\Http\Requests\BudgetIncomeRequest;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Navigation;
use Preferences; use Preferences;
use Response; use Response;
use View; use View;
@@ -105,6 +110,7 @@ class BudgetController extends Controller
} }
/** /**
* @param Request $request
* @param Budget $budget * @param Budget $budget
* *
* @return View * @return View
@@ -161,16 +167,38 @@ class BudgetController extends Controller
} }
/** /**
* @param string|null $moment
*
* @return View * @return View
*/ */
public function index() public function index(string $moment = null)
{ {
$range = Preferences::get('viewRange', '1M')->data;
$start = session('start', new Carbon);
$end = session('end', new Carbon);
// make date if present:
if (!is_null($moment) || strlen(strval($moment)) !== 0) {
try {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
} catch (Exception $e) {
// start and end are already defined.
}
}
$next = clone $end;
$next->addDay();
$prev = clone $start;
$prev->subDay();
$prev = Navigation::startOfPeriod($prev, $range);
$this->repository->cleanupBudgets(); $this->repository->cleanupBudgets();
$budgets = $this->repository->getActiveBudgets(); $budgets = $this->repository->getActiveBudgets();
$inactive = $this->repository->getInactiveBudgets(); $inactive = $this->repository->getInactiveBudgets();
$start = session('start', new Carbon);
$end = session('end', new Carbon);
$periodStart = $start->formatLocalized($this->monthAndDayFormat); $periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat); $periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end); $budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
@@ -179,41 +207,130 @@ class BudgetController extends Controller
$spent = array_sum(array_column($budgetInformation, 'spent')); $spent = array_sum(array_column($budgetInformation, 'spent'));
$budgeted = array_sum(array_column($budgetInformation, 'budgeted')); $budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
// select thing for last 12 periods:
$previousLoop = [];
$previousDate = clone $start;
$count = 0;
while ($count < 12) {
$previousDate->subDay();
$previousDate = Navigation::startOfPeriod($previousDate, $range);
$format = $previousDate->format('Y-m-d');
$previousLoop[$format] = Navigation::periodShow($previousDate, $range);
$count++;
}
// select thing for next 12 periods:
$nextLoop = [];
$nextDate = clone $end;
$nextDate->addDay();
$count = 0;
while ($count < 12) {
$format = $nextDate->format('Y-m-d');
$nextLoop[$format] = Navigation::periodShow($nextDate, $range);
$nextDate = Navigation::endOfPeriod($nextDate, $range);
$count++;
$nextDate->addDay();
}
// display info
$currentMonth = Navigation::periodShow($start, $range);
$nextText = Navigation::periodShow($next, $range);
$prevText = Navigation::periodShow($prev, $range);
return view( return view(
'budgets.index', 'budgets.index',
compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', 'spent', 'budgeted') compact(
'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText',
'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets',
'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start'
)
); );
} }
/** /**
* @param Request $request * @param Request $request
* @param JournalRepositoryInterface $repository
* @param string $moment
* *
* @return View * @return View
*/ */
public function noBudget(Request $request) public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = '')
{ {
/** @var Carbon $start */ // default values:
$start = session('start', Carbon::now()->startOfMonth()); $range = Preferences::get('viewRange', '1M')->data;
/** @var Carbon $end */ $start = null;
$end = session('end', Carbon::now()->endOfMonth()); $end = null;
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); $periods = new Collection;
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
// prep for "all" view.
if ($moment === 'all') {
$subTitle = trans('firefly.all_journals_without_budget');
$first = $repository->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
}
// prep for "specific date" view.
if (strlen($moment) > 0 && $moment !== 'all') {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$subTitle = trans( $subTitle = trans(
'firefly.without_budget_between', 'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
); );
$periods = $this->getPeriodOverview();
}
// collector // prep for current period
if (strlen($moment) === 0) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview();
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$count = 0;
$loop = 0;
// grab journals, but be prepared to jump a period back to get the right ones:
Log::info('Now at no-budget loop start.');
while ($count === 0 && $loop < 3) {
$loop++;
Log::info('Count is zero, search for journals.');
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutBudget(); $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
->withoutBudget()->withOpposingAccount();
$journals = $collector->getPaginatedJournals(); $journals = $collector->getPaginatedJournals();
$journals->setPath('/budgets/list/noBudget'); $journals->setPath('/budgets/list/no-budget');
$count = $journals->getCollection()->count();
if ($count === 0) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
}
}
return view('budgets.no-budget', compact('journals', 'subTitle')); if ($moment != 'all' && $loop > 1) {
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
return view('budgets.no-budget', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
} }
/** /**
* @param BudgetIncomeRequest $request
*
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function postUpdateIncome(BudgetIncomeRequest $request) public function postUpdateIncome(BudgetIncomeRequest $request)
@@ -252,7 +369,7 @@ class BudgetController extends Controller
$journals->setPath('/budgets/show/' . $budget->id); $journals->setPath('/budgets/show/' . $budget->id);
$subTitle = e($budget->name); $subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle')); return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
} }
@@ -312,10 +429,11 @@ class BudgetController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('create_another')) === 1) { if (intval($request->get('create_another')) === 1) {
// set value so create routine will not overwrite URL: // @codeCoverageIgnoreStart
$request->session()->put('budgets.create.fromStore', true); $request->session()->put('budgets.create.fromStore', true);
return redirect(route('budgets.create'))->withInput(); return redirect(route('budgets.create'))->withInput();
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('budgets.create.uri')); return redirect($this->getPreviousUri('budgets.create.uri'));
@@ -336,10 +454,11 @@ class BudgetController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) { if (intval($request->get('return_to_edit')) === 1) {
// set value so edit routine will not overwrite URL: // @codeCoverageIgnoreStart
$request->session()->put('budgets.edit.fromUpdate', true); $request->session()->put('budgets.edit.fromUpdate', true);
return redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]); return redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('budgets.edit.uri')); return redirect($this->getPreviousUri('budgets.edit.uri'));
@@ -402,7 +521,6 @@ class BudgetController extends Controller
return $return; return $return;
} }
/** /**
* @param Budget $budget * @param Budget $budget
* @param Carbon $start * @param Carbon $start
@@ -420,7 +538,7 @@ class BudgetController extends Controller
$cache->addProperty('get-limits'); $cache->addProperty('get-limits');
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get(); // @codeCoverageIgnore
} }
/** @var AccountRepositoryInterface $accountRepository */ /** @var AccountRepositoryInterface $accountRepository */
@@ -439,4 +557,57 @@ class BudgetController extends Controller
return $set; return $set;
} }
/**
* @return Collection
*/
private function getPeriodOverview(): Collection
{
$repository = app(JournalRepositoryInterface::class);
$first = $repository->first();
$start = $first->date ?? new Carbon;
$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('no-budget-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
// count journals without budget in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
$set = $collector->getJournals();
$sum = $set->sum('transaction_amount');
$journals = $set->count();
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'count' => $journals,
'sum' => $sum,
'date' => clone $end,
]
);
$end = Navigation::subtractPeriod($end, $range, 1);
}
$cache->store($entries);
return $entries;
}
} }

View File

@@ -15,16 +15,21 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Http\Requests\CategoryFormRequest; use FireflyIII\Http\Requests\CategoryFormRequest;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Navigation; use Navigation;
use Preferences; use Preferences;
use Steam;
use View; use View;
/** /**
@@ -150,118 +155,171 @@ class CategoryController extends Controller
} }
/** /**
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param string $moment
*
* @return View * @return View
*/ */
public function noCategory() public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
{ {
/** @var Carbon $start */ // default values:
$start = session('start', Carbon::now()->startOfMonth()); $range = Preferences::get('viewRange', '1M')->data;
/** @var Carbon $end */ $start = null;
$end = session('end', Carbon::now()->startOfMonth()); $end = null;
$periods = new Collection;
// new collector: // prep for "all" view.
/** @var JournalCollectorInterface $collector */ if ($moment === 'all') {
$collector = app(JournalCollectorInterface::class); $subTitle = trans('firefly.all_journals_without_category');
$collector->setAllAssetAccounts()->setRange($start, $end)->withoutCategory();//->groupJournals(); $first = $repository->first();
$journals = $collector->getJournals(); $start = $first->date ?? new Carbon;
$end = new Carbon;
}
// prep for "specific date" view.
if (strlen($moment) > 0 && $moment !== 'all') {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$subTitle = trans( $subTitle = trans(
'firefly.without_category_between', 'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
); );
$periods = $this->getNoCategoryPeriodOverview();
return view('categories.no-category', compact('journals', 'subTitle'));
} }
/** // prep for current period
* @param Request $request if (strlen($moment) === 0) {
* @param JournalCollectorInterface $collector $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
* @param Category $category $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
* $periods = $this->getNoCategoryPeriodOverview();
* @return View $subTitle = trans(
*/ 'firefly.without_category_between',
public function show(Request $request, JournalCollectorInterface $collector, Category $category) ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
{ );
$range = Preferences::get('viewRange', '1M')->data; }
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = session('end', Navigation::endOfPeriod(new Carbon, $range)); $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
$hideCategory = true; // used in list.
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
$entries = $this->getGroupedEntries($category);
$method = 'default';
// get journals $count = 0;
$collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation(); $loop = 0;
// grab journals, but be prepared to jump a period back to get the right ones:
Log::info('Now at no-cat loop start.');
while ($count === 0 && $loop < 3) {
$loop++;
Log::info('Count is zero, search for journals.');
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount();
$collector->removeFilter(InternalTransferFilter::class);
$journals = $collector->getPaginatedJournals(); $journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id); $journals->setPath('/categories/list/no-category');
$count = $journals->getCollection()->count();
if ($count === 0) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
}
}
if ($moment != 'all' && $loop > 1) {
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
return view('categories.show', compact('category', 'method', 'journals', 'entries', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end')); return view('categories.no-category', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
} }
/** /**
* @param Request $request * @param Request $request
* @param CategoryRepositoryInterface $repository * @param CategoryRepositoryInterface $repository
* @param Category $category * @param Category $category
* @param string $moment
* *
* @return View * @return View
*/ */
public function showAll(Request $request, CategoryRepositoryInterface $repository, Category $category) public function show(Request $request, CategoryRepositoryInterface $repository, Category $category, string $moment = '')
{ {
// default values:
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$count = 0;
$loop = 0;
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$start = null;
$end = null;
$periods = new Collection;
// prep for "all" view.
if ($moment === 'all') {
$subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]);
$start = $repository->firstUseDate($category); $start = $repository->firstUseDate($category);
if ($start->year == 1900) { $end = new Carbon;
$start = new Carbon;
} }
$end = Navigation::endOfPeriod(new Carbon, $range);
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
$hideCategory = true; // used in list.
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$method = 'all';
// prep for "specific date" view.
if (strlen($moment) > 0 && $moment !== 'all') {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name,
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview($category);
}
// prep for current period
if (strlen($moment) === 0) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($category);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
// grab journals, but be prepared to jump a period back to get the right ones:
Log::info('Now at category loop start.');
while ($count === 0 && $loop < 3) {
$loop++;
Log::info('Count is zero, search for journals.');
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setCategory($category)->withBudgetInformation(); $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
$collector->removeFilter(InternalTransferFilter::class);
$journals = $collector->getPaginatedJournals(); $journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id . '/all'); $journals->setPath('categories/show/' . $category->id);
$count = $journals->getCollection()->count();
return view('categories.show', compact('category', 'method', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end')); if ($count === 0) {
$start->subDay();
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfPeriod($start, $range);
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
}
} }
/** if ($moment != 'all' && $loop > 1) {
* @param Request $request $subTitle = trans(
* @param Category $category 'firefly.journals_in_period_for_category',
* @param string $date ['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
* 'end' => $end->formatLocalized($this->monthAndDayFormat)]
* @return View );
*/
public function showByDate(Request $request, Category $category, string $date)
{
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($carbon, $range);
$end = Navigation::endOfPeriod($carbon, $range);
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
$hideCategory = true; // used in list.
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$entries = $this->getGroupedEntries($category);
$method = 'date';
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation();
$journals = $collector->getPaginatedJournals();
$journals->setPath('categories/show/' . $category->id . '/' . $date);
return view('categories.show', compact('category', 'method', 'entries', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end'));
} }
return view('categories.show', compact('category', 'moment', 'journals', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
}
/** /**
* @param CategoryFormRequest $request * @param CategoryFormRequest $request
* @param CategoryRepositoryInterface $repository * @param CategoryRepositoryInterface $repository
@@ -277,9 +335,11 @@ class CategoryController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('create_another')) === 1) { if (intval($request->get('create_another')) === 1) {
// @codeCoverageIgnoreStart
$request->session()->put('categories.create.fromStore', true); $request->session()->put('categories.create.fromStore', true);
return redirect(route('categories.create'))->withInput(); return redirect(route('categories.create'))->withInput();
// @codeCoverageIgnoreEnd
} }
return redirect(route('categories.index')); return redirect(route('categories.index'));
@@ -302,20 +362,100 @@ class CategoryController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) { if (intval($request->get('return_to_edit')) === 1) {
// @codeCoverageIgnoreStart
$request->session()->put('categories.edit.fromUpdate', true); $request->session()->put('categories.edit.fromUpdate', true);
return redirect(route('categories.edit', [$category->id])); return redirect(route('categories.edit', [$category->id]));
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('categories.edit.uri')); return redirect($this->getPreviousUri('categories.edit.uri'));
} }
/**
* @return Collection
*/
private function getNoCategoryPeriodOverview(): Collection
{
$repository = app(JournalRepositoryInterface::class);
$first = $repository->first();
$start = $first->date ?? new Carbon;
$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('no-budget-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
while ($end >= $start) {
Log::debug('Loop!');
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
// count journals without budget in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
->withOpposingAccount();
$collector->removeFilter(InternalTransferFilter::class);
$count = $collector->getJournals()->count();
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
// amount spent
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
$spent = $collector->getJournals()->sum('transaction_amount');
// amount earned
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::DEPOSIT]);
$earned = $collector->getJournals()->sum('transaction_amount');
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'count' => $count,
'spent' => $spent,
'earned' => $earned,
'transferred' => $transferred,
'date' => clone $end,
]
);
$end = Navigation::subtractPeriod($end, $range, 1);
}
Log::debug('End of loops');
$cache->store($entries);
return $entries;
}
/** /**
* @param Category $category * @param Category $category
* *
* @return Collection * @return Collection
*/ */
private function getGroupedEntries(Category $category): Collection private function getPeriodOverview(Category $category): Collection
{ {
/** @var CategoryRepositoryInterface $repository */ /** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class); $repository = app(CategoryRepositoryInterface::class);
@@ -339,7 +479,7 @@ class CategoryController extends Controller
$cache->addProperty($category->id); $cache->addProperty($category->id);
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get(); // @codeCoverageIgnore
} }
while ($end >= $first) { while ($end >= $first) {
$end = Navigation::startOfPeriod($end, $range); $end = Navigation::startOfPeriod($end, $range);
@@ -348,7 +488,26 @@ class CategoryController extends Controller
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
$dateStr = $end->format('Y-m-d'); $dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range); $dateName = Navigation::periodShow($end, $range);
$entries->push([$dateStr, $dateName, $spent, $earned, clone $end]);
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->setCategory($category)
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'sum' => bcadd($earned, $spent),
'transferred' => $transferred,
'date' => clone $end,
]
);
$end = Navigation::subtractPeriod($end, $range, 1); $end = Navigation::subtractPeriod($end, $range, 1);
} }
$cache->store($entries); $cache->store($entries);

View File

@@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Chart; namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
@@ -26,6 +25,7 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
@@ -61,15 +61,12 @@ class AccountController extends Controller
*/ */
public function all(Account $account) public function all(Account $account)
{ {
$cache = new CacheProperties(); $cache = new CacheProperties;
$cache->addProperty('chart.account.all'); $cache->addProperty('chart.account.all');
$cache->addProperty($account->id); $cache->addProperty($account->id);
if ($cache->has()) { if ($cache->has()) {
Log::debug('Return chart.account.all from cache.'); return Response::json($cache->get()); // @codeCoverageIgnore
return Response::json($cache->get());
} }
Log::debug('Regenerate chart.account.all from scratch.');
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
@@ -112,14 +109,13 @@ class AccountController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.account.expense-accounts'); $cache->addProperty('chart.account.expense-accounts');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$start->subDay(); $start->subDay();
$accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]); $accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
$ids = $accounts->pluck('id')->toArray(); $startBalances = Steam::balancesByAccounts($accounts, $start);
$startBalances = Steam::balancesById($ids, $start); $endBalances = Steam::balancesByAccounts($accounts, $end);
$endBalances = Steam::balancesById($ids, $end);
$chartData = []; $chartData = [];
foreach ($accounts as $account) { foreach ($accounts as $account) {
@@ -131,6 +127,7 @@ class AccountController extends Controller
$chartData[$account->name] = $diff; $chartData[$account->name] = $diff;
} }
} }
arsort($chartData); arsort($chartData);
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData); $data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data); $cache->store($data);
@@ -139,14 +136,13 @@ class AccountController extends Controller
} }
/** /**
* @param JournalCollectorInterface $collector
* @param Account $account * @param Account $account
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function expenseBudget(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end) public function expenseBudget(Account $account, Carbon $start, Carbon $end)
{ {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($account->id); $cache->addProperty($account->id);
@@ -154,12 +150,10 @@ class AccountController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.account.expense-budget'); $cache->addProperty('chart.account.expense-budget');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$collector->setAccounts(new Collection([$account])) $collector = app(JournalCollectorInterface::class);
->setRange($start, $end) $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionType::WITHDRAWAL]);
->withBudgetInformation()
->setTypes([TransactionType::WITHDRAWAL]);
$transactions = $collector->getJournals(); $transactions = $collector->getJournals();
$chartData = []; $chartData = [];
$result = []; $result = [];
@@ -185,14 +179,27 @@ class AccountController extends Controller
} }
/** /**
* @param JournalCollectorInterface $collector * @param AccountRepositoryInterface $repository
* @param Account $account
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account)
{
$start = $repository->oldestJournalDate($account);
$end = Carbon::now();
return $this->expenseBudget($account, $start, $end);
}
/**
* @param Account $account * @param Account $account
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function expenseCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end) public function expenseCategory(Account $account, Carbon $start, Carbon $end)
{ {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($account->id); $cache->addProperty($account->id);
@@ -200,9 +207,10 @@ class AccountController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.account.expense-category'); $cache->addProperty('chart.account.expense-category');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]);
$transactions = $collector->getJournals(); $transactions = $collector->getJournals();
$result = []; $result = [];
@@ -228,6 +236,20 @@ class AccountController extends Controller
} }
/**
* @param AccountRepositoryInterface $repository
* @param Account $account
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account)
{
$start = $repository->oldestJournalDate($account);
$end = Carbon::now();
return $this->expenseCategory($account, $start, $end);
}
/** /**
* Shows the balances for all the user's frontpage accounts. * Shows the balances for all the user's frontpage accounts.
* *
@@ -254,14 +276,13 @@ class AccountController extends Controller
} }
/** /**
* @param JournalCollectorInterface $collector
* @param Account $account * @param Account $account
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function incomeCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end) public function incomeCategory(Account $account, Carbon $start, Carbon $end)
{ {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($account->id); $cache->addProperty($account->id);
@@ -269,10 +290,11 @@ class AccountController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.account.income-category'); $cache->addProperty('chart.account.income-category');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
// grab all journals: // grab all journals:
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]);
$transactions = $collector->getJournals(); $transactions = $collector->getJournals();
$result = []; $result = [];
@@ -298,20 +320,28 @@ class AccountController extends Controller
} }
/** /**
* @param AccountRepositoryInterface $repository
* @param Account $account * @param Account $account
* @param string $date *
* @return \Illuminate\Http\JsonResponse
*/
public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account)
{
$start = $repository->oldestJournalDate($account);
$end = Carbon::now();
return $this->incomeCategory($account, $start, $end);
}
/**
* @param Account $account
* @param Carbon $start
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws FireflyException * @throws FireflyException
*/ */
public function period(Account $account, string $date) public function period(Account $account, Carbon $start)
{ {
try {
$start = new Carbon($date);
} catch (Exception $e) {
Log::error($e->getMessage());
throw new FireflyException('"' . e($date) . '" does not seem to be a valid date. Should be in the format YYYY-MM-DD');
}
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$end = Navigation::endOfPeriod($start, $range); $end = Navigation::endOfPeriod($start, $range);
$cache = new CacheProperties(); $cache = new CacheProperties();
@@ -320,7 +350,7 @@ class AccountController extends Controller
$cache->addProperty('chart.account.period'); $cache->addProperty('chart.account.period');
$cache->addProperty($account->id); $cache->addProperty($account->id);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$format = (string)trans('config.month_and_day'); $format = (string)trans('config.month_and_day');
@@ -375,14 +405,13 @@ class AccountController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.account.revenue-accounts'); $cache->addProperty('chart.account.revenue-accounts');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$accounts = $repository->getAccountsByType([AccountType::REVENUE]); $accounts = $repository->getAccountsByType([AccountType::REVENUE]);
$start->subDay(); $start->subDay();
$ids = $accounts->pluck('id')->toArray(); $startBalances = Steam::balancesByAccounts($accounts, $start);
$startBalances = Steam::balancesById($ids, $start); $endBalances = Steam::balancesByAccounts($accounts, $end);
$endBalances = Steam::balancesById($ids, $end);
foreach ($accounts as $account) { foreach ($accounts as $account) {
$id = $account->id; $id = $account->id;
@@ -396,7 +425,7 @@ class AccountController extends Controller
} }
arsort($chartData); arsort($chartData);
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData); $data = $this->generator->singleSet(strval(trans('firefly.earned')), $chartData);
$cache->store($data); $cache->store($data);
return Response::json($data); return Response::json($data);
@@ -421,7 +450,7 @@ class AccountController extends Controller
$cache->addProperty('chart.account.single'); $cache->addProperty('chart.account.single');
$cache->addProperty($account->id); $cache->addProperty($account->id);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$format = (string)trans('config.month_and_day'); $format = (string)trans('config.month_and_day');
@@ -461,16 +490,19 @@ class AccountController extends Controller
$cache->addProperty('chart.account.account-balance-chart'); $cache->addProperty('chart.account.account-balance-chart');
$cache->addProperty($accounts); $cache->addProperty($accounts);
if ($cache->has()) { if ($cache->has()) {
Log::debug('Return chart.account.account-balance-chart from cache.'); return $cache->get(); // @codeCoverageIgnore
return $cache->get();
} }
Log::debug('Regenerate chart.account.account-balance-chart from scratch.'); Log::debug('Regenerate chart.account.account-balance-chart from scratch.');
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$chartData = []; $chartData = [];
foreach ($accounts as $account) { foreach ($accounts as $account) {
$currency = $repository->find(intval($account->getMeta('currency_id')));
$currentSet = [ $currentSet = [
'label' => $account->name, 'label' => $account->name,
'currency_symbol' => $currency->symbol,
'entries' => [], 'entries' => [],
]; ];
$currentStart = clone $start; $currentStart = clone $start;

View File

@@ -60,7 +60,7 @@ class BillController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.bill.frontpage'); $cache->addProperty('chart.bill.frontpage');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
@@ -88,7 +88,7 @@ class BillController extends Controller
$cache->addProperty('chart.bill.single'); $cache->addProperty('chart.bill.single');
$cache->addProperty($bill->id); $cache->addProperty($bill->id);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals(); $results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals();

View File

@@ -18,16 +18,20 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Navigation; use Navigation;
use Preferences; use Preferences;
use Response; use Response;
use Steam;
/** /**
* Class BudgetController * Class BudgetController
@@ -80,7 +84,7 @@ class BudgetController extends Controller
$cache->addProperty('chart.budget.budget'); $cache->addProperty('chart.budget.budget');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$final = clone $last; $final = clone $last;
@@ -133,7 +137,7 @@ class BudgetController extends Controller
$cache->addProperty($budgetLimit->id); $cache->addProperty($budgetLimit->id);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$entries = []; $entries = [];
@@ -153,6 +157,144 @@ class BudgetController extends Controller
return Response::json($data); return Response::json($data);
} }
/**
* @param Budget $budget
* @param BudgetLimit|null $budgetLimit
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseAsset(Budget $budget, BudgetLimit $budgetLimit = null)
{
$cache = new CacheProperties;
$cache->addProperty($budget->id);
$cache->addProperty($budgetLimit->id ?? 0);
$cache->addProperty('chart.budget.expense-asset');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget);
if (!is_null($budgetLimit->id)) {
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
}
$transactions = $collector->getJournals();
$result = [];
$chartData = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$assetId = intval($transaction->account_id);
$result[$assetId] = $result[$assetId] ?? '0';
$result[$assetId] = bcadd($transaction->transaction_amount, $result[$assetId]);
}
$names = $this->getAccountNames(array_keys($result));
foreach ($result as $assetId => $amount) {
$chartData[$names[$assetId]] = $amount;
}
$data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data);
}
/**
* @param Budget $budget
* @param BudgetLimit|null $budgetLimit
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseCategory(Budget $budget, BudgetLimit $budgetLimit = null)
{
$cache = new CacheProperties;
$cache->addProperty($budget->id);
$cache->addProperty($budgetLimit->id ?? 0);
$cache->addProperty('chart.budget.expense-category');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withCategoryInformation();
if (!is_null($budgetLimit->id)) {
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
}
$transactions = $collector->getJournals();
$result = [];
$chartData = [];
/** @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));
foreach ($result as $categoryId => $amount) {
$chartData[$names[$categoryId]] = $amount;
}
$data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data);
}
/**
* @param Budget $budget
* @param BudgetLimit|null $budgetLimit
*
* @return \Illuminate\Http\JsonResponse
*/
public function expenseExpense(Budget $budget, BudgetLimit $budgetLimit = null)
{
$cache = new CacheProperties;
$cache->addProperty($budget->id);
$cache->addProperty($budgetLimit->id ?? 0);
$cache->addProperty('chart.budget.expense-expense');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withOpposingAccount();
if (!is_null($budgetLimit->id)) {
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
}
$transactions = $collector->getJournals();
$result = [];
$chartData = [];
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$opposingId = intval($transaction->opposing_account_id);
$result[$opposingId] = $result[$opposingId] ?? '0';
$result[$opposingId] = bcadd($transaction->transaction_amount, $result[$opposingId]);
}
$names = $this->getAccountNames(array_keys($result));
foreach ($result as $opposingId => $amount) {
$name = $names[$opposingId] ?? 'no name';
$chartData[$name] = $amount;
}
$data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data);
}
/** /**
* Shows a budget list with spent/left/overspent. * Shows a budget list with spent/left/overspent.
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
@@ -170,7 +312,7 @@ class BudgetController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.budget.frontpage'); $cache->addProperty('chart.budget.frontpage');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$budgets = $this->repository->getActiveBudgets(); $budgets = $this->repository->getActiveBudgets();
$chartData = [ $chartData = [
@@ -179,12 +321,12 @@ class BudgetController extends Controller
['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',], ['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',],
]; ];
/** @var Budget $budget */ /** @var Budget $budget */
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
// get relevant repetitions: // get relevant repetitions:
$limits = $this->repository->getBudgetLimits($budget, $start, $end); $limits = $this->repository->getBudgetLimits($budget, $start, $end);
$expenses = $this->getExpensesForBudget($limits, $budget, $start, $end); $expenses = $this->getExpensesForBudget($limits, $budget, $start, $end);
foreach ($expenses as $name => $row) { foreach ($expenses as $name => $row) {
$chartData[0]['entries'][$name] = $row['spent']; $chartData[0]['entries'][$name] = $row['spent'];
$chartData[1]['entries'][$name] = $row['left']; $chartData[1]['entries'][$name] = $row['left'];
@@ -227,7 +369,7 @@ class BudgetController extends Controller
$cache->addProperty($budget->id); $cache->addProperty($budget->id);
$cache->addProperty('chart.budget.period'); $cache->addProperty('chart.budget.period');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$periods = Navigation::listOfPeriods($start, $end); $periods = Navigation::listOfPeriods($start, $end);
$entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses $entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses
@@ -268,7 +410,7 @@ class BudgetController extends Controller
$cache->addProperty($accounts); $cache->addProperty($accounts);
$cache->addProperty('chart.budget.no-budget'); $cache->addProperty('chart.budget.no-budget');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
// the expenses: // the expenses:
@@ -288,6 +430,28 @@ class BudgetController extends Controller
return Response::json($data); return Response::json($data);
} }
/**
* @param array $accountIds
*
* @return array
*/
private function getAccountNames(array $accountIds): array
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::EXPENSE]);
$grouped = $accounts->groupBy('id')->toArray();
$return = [];
foreach ($accountIds as $accountId) {
if (isset($grouped[$accountId])) {
$return[$accountId] = $grouped[$accountId][0]['name'];
}
}
$return[0] = '(no name)';
return $return;
}
/** /**
* @param Budget $budget * @param Budget $budget
* @param Carbon $start * @param Carbon $start
@@ -314,6 +478,30 @@ class BudgetController extends Controller
return $budgeted; return $budgeted;
} }
/**
* 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;
}
/** /**
* *
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok. * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok.
@@ -342,9 +530,7 @@ class BudgetController extends Controller
$rows = $this->spentInPeriodMulti($budget, $limits); $rows = $this->spentInPeriodMulti($budget, $limits);
foreach ($rows as $name => $row) { foreach ($rows as $name => $row) {
if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) { if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) {
$return[$name]['spent'] = bcmul($row['spent'], '-1'); $return[$name] = $row;
$return[$name]['left'] = $row['left'];
$return[$name]['overspent'] = bcmul($row['overspent'], '-1');
} }
} }
unset($rows, $row); unset($rows, $row);
@@ -376,6 +562,7 @@ class BudgetController extends Controller
/** @var BudgetLimit $budgetLimit */ /** @var BudgetLimit $budgetLimit */
foreach ($limits as $budgetLimit) { foreach ($limits as $budgetLimit) {
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date); $expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
$expenses = Steam::positive($expenses);
if ($limits->count() > 1) { if ($limits->count() > 1) {
$name = $budget->name . ' ' . trans( $name = $budget->name . ' ' . trans(
@@ -386,10 +573,19 @@ class BudgetController extends Controller
] ]
); );
} }
/*
* amount: amount of budget limit
* left: amount of budget limit min spent, or 0 when < 0.
* spent: spent, or amount of budget limit when > amount
*/
$amount = $budgetLimit->amount; $amount = $budgetLimit->amount;
$left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses); $leftInLimit = bcsub($amount, $expenses);
$spent = $expenses; $hasOverspent = bccomp($leftInLimit, '0') === -1;
$overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';
$left = $hasOverspent ? '0' : bcsub($amount, $expenses);
$spent = $hasOverspent ? $amount : $expenses;
$overspent = $hasOverspent ? Steam::positive($leftInLimit) : '0';
$return[$name] = [ $return[$name] = [
'left' => $left, 'left' => $left,
'overspent' => $overspent, 'overspent' => $overspent,

View File

@@ -16,15 +16,16 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
use FireflyIII\Helpers\Chart\MetaPieChartInterface; use FireflyIII\Helpers\Chart\MetaPieChartInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -41,8 +42,6 @@ use Response;
class BudgetReportController extends Controller class BudgetReportController extends Controller
{ {
/** @var AccountRepositoryInterface */
private $accountRepository;
/** @var BudgetRepositoryInterface */ /** @var BudgetRepositoryInterface */
private $budgetRepository; private $budgetRepository;
/** @var GeneratorInterface */ /** @var GeneratorInterface */
@@ -58,7 +57,6 @@ class BudgetReportController extends Controller
function ($request, $next) { function ($request, $next) {
$this->generator = app(GeneratorInterface::class); $this->generator = app(GeneratorInterface::class);
$this->budgetRepository = app(BudgetRepositoryInterface::class); $this->budgetRepository = app(BudgetRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
return $next($request); return $next($request);
} }
@@ -131,10 +129,8 @@ class BudgetReportController extends Controller
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$format = Navigation::preferredCarbonLocalizedFormat($start, $end); $format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$function = Navigation::preferredEndOfPeriod($start, $end); $function = Navigation::preferredEndOfPeriod($start, $end);
$chartData = []; $chartData = [];
@@ -163,7 +159,7 @@ class BudgetReportController extends Controller
'entries' => [], 'entries' => [],
]; ];
} }
$allBudgetLimits = $repository->getAllBudgetLimits($start, $end); $allBudgetLimits = $this->budgetRepository->getAllBudgetLimits($start, $end);
$sumOfExpenses = []; $sumOfExpenses = [];
$leftOfLimits = []; $leftOfLimits = [];
while ($currentStart < $end) { while ($currentStart < $end) {
@@ -203,6 +199,7 @@ class BudgetReportController extends Controller
* Returns the budget limits belonging to the given budget and valid on the given day. * Returns the budget limits belonging to the given budget and valid on the given day.
* *
* @param Collection $budgetLimits * @param Collection $budgetLimits
* @param Budget $budget
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
@@ -239,12 +236,15 @@ class BudgetReportController extends Controller
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setBudgets($budgets)->withOpposingAccount()->disableFilter(); ->setBudgets($budgets)->withOpposingAccount();
$accountIds = $accounts->pluck('id')->toArray(); $collector->removeFilter(TransferFilter::class);
$transactions = $collector->getJournals();
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
return $set; $collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(PositiveAmountFilter::class);
$transactions = $collector->getJournals();
return $transactions;
} }
/** /**
@@ -268,21 +268,4 @@ class BudgetReportController extends Controller
return $grouped; 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

@@ -20,7 +20,7 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Navigation; use Navigation;
@@ -50,19 +50,19 @@ class CategoryController extends Controller
/** /**
* Show an overview for a category for all time, per month/week/year. * Show an overview for a category for all time, per month/week/year.
* *
* @param CRI $repository * @param CategoryRepositoryInterface $repository
* @param AccountRepositoryInterface $accountRepository * @param AccountRepositoryInterface $accountRepository
* @param Category $category * @param Category $category
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category) public function all(CategoryRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Category $category)
{ {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty('chart.category.all'); $cache->addProperty('chart.category.all');
$cache->addProperty($category->id); $cache->addProperty($category->id);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$start = $repository->firstUseDate($category); $start = $repository->firstUseDate($category);
@@ -86,15 +86,23 @@ class CategoryController extends Controller
'entries' => [], 'entries' => [],
'type' => 'bar', 'type' => 'bar',
], ],
[
'label' => strval(trans('firefly.sum')),
'entries' => [],
'type' => 'line',
'fill' => false,
],
]; ];
while ($start <= $end) { while ($start <= $end) {
$currentEnd = Navigation::endOfPeriod($start, $range); $currentEnd = Navigation::endOfPeriod($start, $range);
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
$sum = bcadd($spent, $earned);
$label = Navigation::periodShow($start, $range); $label = Navigation::periodShow($start, $range);
$chartData[0]['entries'][$label] = bcmul($spent, '-1'); $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
$chartData[1]['entries'][$label] = $earned; $chartData[1]['entries'][$label] = round($earned, 12);
$chartData[2]['entries'][$label] = round($sum, 12);
$start = Navigation::addPeriod($start, $range, 0); $start = Navigation::addPeriod($start, $range, 0);
} }
@@ -106,27 +114,12 @@ class CategoryController extends Controller
} }
/** /**
* @param CRI $repository * @param CategoryRepositoryInterface $repository
* @param Category $category
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function currentPeriod(CRI $repository, Category $category)
{
$start = clone session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$data = $this->makePeriodChart($repository, $category, $start, $end);
return Response::json($data);
}
/**
* @param CRI $repository
* @param AccountRepositoryInterface $accountRepository * @param AccountRepositoryInterface $accountRepository
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function frontpage(CRI $repository, AccountRepositoryInterface $accountRepository) public function frontpage(CategoryRepositoryInterface $repository, AccountRepositoryInterface $accountRepository)
{ {
$start = session('start', Carbon::now()->startOfMonth()); $start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth()); $end = session('end', Carbon::now()->endOfMonth());
@@ -136,7 +129,7 @@ class CategoryController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.category.frontpage'); $cache->addProperty('chart.category.frontpage');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$chartData = []; $chartData = [];
$categories = $repository->getCategories(); $categories = $repository->getCategories();
@@ -161,7 +154,7 @@ class CategoryController extends Controller
} }
/** /**
* @param CRI $repository * @param CategoryRepositoryInterface $repository
* @param Category $category * @param Category $category
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
@@ -169,7 +162,7 @@ class CategoryController extends Controller
* *
* @return \Illuminate\Http\JsonResponse|mixed * @return \Illuminate\Http\JsonResponse|mixed
*/ */
public function reportPeriod(CRI $repository, Category $category, Collection $accounts, Carbon $start, Carbon $end) public function reportPeriod(CategoryRepositoryInterface $repository, Category $category, Collection $accounts, Carbon $start, Carbon $end)
{ {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($start); $cache->addProperty($start);
@@ -178,7 +171,7 @@ class CategoryController extends Controller
$cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($category); $cache->addProperty($category);
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get(); // @codeCoverageIgnore
} }
$expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end); $expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
$income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end); $income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
@@ -194,13 +187,22 @@ class CategoryController extends Controller
'entries' => [], 'entries' => [],
'type' => 'bar', 'type' => 'bar',
], ],
[
'label' => strval(trans('firefly.sum')),
'entries' => [],
'type' => 'line',
'fill' => false,
],
]; ];
foreach (array_keys($periods) as $period) { foreach (array_keys($periods) as $period) {
$label = $periods[$period]; $label = $periods[$period];
$spent = $expenses[$category->id]['entries'][$period] ?? '0'; $spent = $expenses[$category->id]['entries'][$period] ?? '0';
$chartData[0]['entries'][$label] = bcmul($spent, '-1'); $earned = $income[$category->id]['entries'][$period] ?? '0';
$chartData[1]['entries'][$label] = $income[$category->id]['entries'][$period] ?? '0'; $sum = bcadd($spent, $earned);
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
$chartData[1]['entries'][$label] = round($earned, 12);
$chartData[2]['entries'][$label] = round($sum, 12);
} }
$data = $this->generator->multiSet($chartData); $data = $this->generator->multiSet($chartData);
@@ -210,14 +212,14 @@ class CategoryController extends Controller
} }
/** /**
* @param CRI $repository * @param CategoryRepositoryInterface $repository
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return \Illuminate\Http\JsonResponse|mixed * @return \Illuminate\Http\JsonResponse|mixed
*/ */
public function reportPeriodNoCategory(CRI $repository, Collection $accounts, Carbon $start, Carbon $end) public function reportPeriodNoCategory(CategoryRepositoryInterface $repository, Collection $accounts, Carbon $start, Carbon $end)
{ {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($start); $cache->addProperty($start);
@@ -225,7 +227,7 @@ class CategoryController extends Controller
$cache->addProperty('chart.category.period.no-cat'); $cache->addProperty('chart.category.period.no-cat');
$cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get(); // @codeCoverageIgnore
} }
$expenses = $repository->periodExpensesNoCategory($accounts, $start, $end); $expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
$income = $repository->periodIncomeNoCategory($accounts, $start, $end); $income = $repository->periodIncomeNoCategory($accounts, $start, $end);
@@ -241,13 +243,22 @@ class CategoryController extends Controller
'entries' => [], 'entries' => [],
'type' => 'bar', 'type' => 'bar',
], ],
[
'label' => strval(trans('firefly.sum')),
'entries' => [],
'type' => 'line',
'fill' => false,
],
]; ];
foreach (array_keys($periods) as $period) { foreach (array_keys($periods) as $period) {
$label = $periods[$period]; $label = $periods[$period];
$spent = $expenses['entries'][$period] ?? '0'; $spent = $expenses['entries'][$period] ?? '0';
$earned = $income['entries'][$period] ?? '0';
$sum = bcadd($spent, $earned);
$chartData[0]['entries'][$label] = bcmul($spent, '-1'); $chartData[0]['entries'][$label] = bcmul($spent, '-1');
$chartData[1]['entries'][$label] = $income['entries'][$period] ?? '0'; $chartData[1]['entries'][$label] = $earned;
$chartData[2]['entries'][$label] = $sum;
} }
$data = $this->generator->multiSet($chartData); $data = $this->generator->multiSet($chartData);
@@ -257,19 +268,18 @@ class CategoryController extends Controller
} }
/** /**
* @param CRI $repository * @param CategoryRepositoryInterface $repository
* @param Category $category * @param Category $category
* *
* @param $date * @param $date
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function specificPeriod(CRI $repository, Category $category, $date) public function specificPeriod(CategoryRepositoryInterface $repository, Category $category, Carbon $date)
{ {
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($carbon, $range); $start = Navigation::startOfPeriod($date, $range);
$end = Navigation::endOfPeriod($carbon, $range); $end = Navigation::endOfPeriod($date, $range);
$data = $this->makePeriodChart($repository, $category, $start, $end); $data = $this->makePeriodChart($repository, $category, $start, $end);
return Response::json($data); return Response::json($data);
@@ -277,14 +287,14 @@ class CategoryController extends Controller
/** /**
* @param CRI $repository * @param CategoryRepositoryInterface $repository
* @param Category $category * @param Category $category
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return array * @return array
*/ */
private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end) private function makePeriodChart(CategoryRepositoryInterface $repository, Category $category, Carbon $start, Carbon $end)
{ {
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($start); $cache->addProperty($start);
@@ -297,7 +307,7 @@ class CategoryController extends Controller
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get(); // @codeCoverageIgnore
} }
// chart data // chart data
@@ -312,15 +322,23 @@ class CategoryController extends Controller
'entries' => [], 'entries' => [],
'type' => 'bar', 'type' => 'bar',
], ],
[
'label' => strval(trans('firefly.sum')),
'entries' => [],
'type' => 'line',
'fill' => false,
],
]; ];
while ($start <= $end) { while ($start <= $end) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
$label = Navigation::periodShow($start, '1D'); $sum = bcadd($spent, $earned);
$label = trim(Navigation::periodShow($start, '1D'));
$chartData[0]['entries'][$label] = bcmul($spent, '-1'); $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
$chartData[1]['entries'][$label] = $earned; $chartData[1]['entries'][$label] = round($earned, 12);
$chartData[2]['entries'][$label] = round($sum, 12);
$start->addDay(); $start->addDay();

View File

@@ -16,15 +16,16 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
use FireflyIII\Helpers\Chart\MetaPieChartInterface; use FireflyIII\Helpers\Chart\MetaPieChartInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Navigation; use Navigation;
@@ -41,10 +42,6 @@ use Response;
class CategoryReportController extends Controller class CategoryReportController extends Controller
{ {
/** @var AccountRepositoryInterface */
private $accountRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var GeneratorInterface */ /** @var GeneratorInterface */
private $generator; private $generator;
@@ -57,8 +54,6 @@ class CategoryReportController extends Controller
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
$this->generator = app(GeneratorInterface::class); $this->generator = app(GeneratorInterface::class);
$this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
return $next($request); return $next($request);
} }
@@ -78,11 +73,8 @@ class CategoryReportController extends Controller
{ {
/** @var MetaPieChartInterface $helper */ /** @var MetaPieChartInterface $helper */
$helper = app(MetaPieChartInterface::class); $helper = app(MetaPieChartInterface::class);
$helper->setAccounts($accounts); $helper->setAccounts($accounts)->setCategories($categories)->setStart($start)->setEnd($end)->setCollectOtherObjects(intval($others) === 1);
$helper->setCategories($categories);
$helper->setStart($start);
$helper->setEnd($end);
$helper->setCollectOtherObjects(intval($others) === 1);
$chartData = $helper->generate('expense', 'account'); $chartData = $helper->generate('expense', 'account');
$data = $this->generator->pieChart($chartData); $data = $this->generator->pieChart($chartData);
@@ -179,7 +171,7 @@ class CategoryReportController extends Controller
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$format = Navigation::preferredCarbonLocalizedFormat($start, $end); $format = Navigation::preferredCarbonLocalizedFormat($start, $end);
@@ -282,12 +274,15 @@ class CategoryReportController extends Controller
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setCategories($categories)->withOpposingAccount()->disableFilter(); ->setCategories($categories)->withOpposingAccount();
$accountIds = $accounts->pluck('id')->toArray(); $collector->removeFilter(TransferFilter::class);
$transactions = $collector->getJournals();
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
return $set; $collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(PositiveAmountFilter::class);
$transactions = $collector->getJournals();
return $transactions;
} }
/** /**
@@ -304,11 +299,13 @@ class CategoryReportController extends Controller
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->setCategories($categories)->withOpposingAccount(); ->setCategories($categories)->withOpposingAccount();
$accountIds = $accounts->pluck('id')->toArray();
$transactions = $collector->getJournals();
$set = MonthReportGenerator::filterIncome($transactions, $accountIds);
return $set; $collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(NegativeAmountFilter::class);
$transactions = $collector->getJournals();
return $transactions;
} }
/** /**
@@ -331,22 +328,4 @@ class CategoryReportController extends Controller
return $grouped; 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

@@ -58,7 +58,7 @@ class PiggyBankController extends Controller
$cache->addProperty('chart.piggy-bank.history'); $cache->addProperty('chart.piggy-bank.history');
$cache->addProperty($piggyBank->id); $cache->addProperty($piggyBank->id);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$set = $repository->getEvents($piggyBank); $set = $repository->getEvents($piggyBank);

View File

@@ -20,6 +20,7 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Navigation; use Navigation;
use Response; use Response;
use Steam; use Steam;
@@ -64,13 +65,12 @@ class ReportController extends Controller
$cache->addProperty($accounts); $cache->addProperty($accounts);
$cache->addProperty($end); $cache->addProperty($end);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$ids = $accounts->pluck('id')->toArray();
$current = clone $start; $current = clone $start;
$chartData = []; $chartData = [];
while ($current < $end) { while ($current < $end) {
$balances = Steam::balancesById($ids, $current); $balances = Steam::balancesByAccounts($accounts, $current);
$sum = $this->arraySum($balances); $sum = $this->arraySum($balances);
$label = $current->formatLocalized(strval(trans('config.month_and_day'))); $label = $current->formatLocalized(strval(trans('config.month_and_day')));
$chartData[$label] = $sum; $chartData[$label] = $sum;
@@ -103,8 +103,9 @@ class ReportController extends Controller
$cache->addProperty($accounts); $cache->addProperty($accounts);
$cache->addProperty($end); $cache->addProperty($end);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
$format = Navigation::preferredCarbonLocalizedFormat($start, $end); $format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$source = $this->getChartData($accounts, $start, $end); $source = $this->getChartData($accounts, $start, $end);
$chartData = [ $chartData = [
@@ -161,8 +162,10 @@ class ReportController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty($accounts); $cache->addProperty($accounts);
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$source = $this->getChartData($accounts, $start, $end); $source = $this->getChartData($accounts, $start, $end);
$numbers = [ $numbers = [
'sum_earned' => '0', 'sum_earned' => '0',
@@ -246,19 +249,41 @@ class ReportController extends Controller
$cache->addProperty($accounts); $cache->addProperty($accounts);
$cache->addProperty($end); $cache->addProperty($end);
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get(); // @codeCoverageIgnore
} }
$tasker = app(AccountTaskerInterface::class);
$currentStart = clone $start; $currentStart = clone $start;
$spentArray = []; $spentArray = [];
$earnedArray = []; $earnedArray = [];
/** @var AccountTaskerInterface $tasker */
$tasker = app(AccountTaskerInterface::class);
while ($currentStart <= $end) { while ($currentStart <= $end) {
$currentEnd = Navigation::endOfPeriod($currentStart, '1M'); $currentEnd = Navigation::endOfPeriod($currentStart, '1M');
$earned = strval(
array_sum(
array_map(
function ($item) {
return $item['sum'];
}, $tasker->getIncomeReport($currentStart, $currentEnd, $accounts)
)
)
);
$spent = strval(
array_sum(
array_map(
function ($item) {
return $item['sum'];
}, $tasker->getExpenseReport($currentStart, $currentEnd, $accounts)
)
)
);
$label = $currentStart->format('Y-m') . '-01'; $label = $currentStart->format('Y-m') . '-01';
$spent = $tasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$earned = $tasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
$spentArray[$label] = bcmul($spent, '-1'); $spentArray[$label] = bcmul($spent, '-1');
$earnedArray[$label] = $earned; $earnedArray[$label] = $earned;
$currentStart = Navigation::addPeriod($currentStart, '1M', 0); $currentStart = Navigation::addPeriod($currentStart, '1M', 0);

View File

@@ -0,0 +1,367 @@
<?php
/**
* TagReportController.php
* Copyright (c) 2017 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\Basic\GeneratorInterface;
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
use Response;
class TagReportController extends Controller
{
/** @var GeneratorInterface */
protected $generator;
/**
*
*/
public function __construct()
{
parent::__construct();
// create chart generator:
$this->generator = app(GeneratorInterface::class);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function accountExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
{
/** @var MetaPieChartInterface $helper */
$helper = app(MetaPieChartInterface::class);
$helper->setAccounts($accounts);
$helper->setTags($tags);
$helper->setStart($start);
$helper->setEnd($end);
$helper->setCollectOtherObjects(intval($others) === 1);
$chartData = $helper->generate('expense', 'account');
$data = $this->generator->pieChart($chartData);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function accountIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
{
/** @var MetaPieChartInterface $helper */
$helper = app(MetaPieChartInterface::class);
$helper->setAccounts($accounts);
$helper->setTags($tags);
$helper->setStart($start);
$helper->setEnd($end);
$helper->setCollectOtherObjects(intval($others) === 1);
$chartData = $helper->generate('income', 'account');
$data = $this->generator->pieChart($chartData);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function budgetExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
{
/** @var MetaPieChartInterface $helper */
$helper = app(MetaPieChartInterface::class);
$helper->setAccounts($accounts);
$helper->setTags($tags);
$helper->setStart($start);
$helper->setEnd($end);
$helper->setCollectOtherObjects(false);
$chartData = $helper->generate('expense', 'budget');
$data = $this->generator->pieChart($chartData);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function categoryExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
{
/** @var MetaPieChartInterface $helper */
$helper = app(MetaPieChartInterface::class);
$helper->setAccounts($accounts);
$helper->setTags($tags);
$helper->setStart($start);
$helper->setEnd($end);
$helper->setCollectOtherObjects(false);
$chartData = $helper->generate('expense', 'category');
$data = $this->generator->pieChart($chartData);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function mainChart(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
{
$cache = new CacheProperties;
$cache->addProperty('chart.category.report.main');
$cache->addProperty($accounts);
$cache->addProperty($tags);
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$function = Navigation::preferredEndOfPeriod($start, $end);
$chartData = [];
$currentStart = clone $start;
// prep chart data:
foreach ($tags as $tag) {
$chartData[$tag->id . '-in'] = [
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.income'))) . ')',
'type' => 'bar',
'yAxisID' => 'y-axis-0',
'entries' => [],
];
$chartData[$tag->id . '-out'] = [
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')',
'type' => 'bar',
'yAxisID' => 'y-axis-0',
'entries' => [],
];
// total in, total out:
$chartData[$tag->id . '-total-in'] = [
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.sum_of_income'))) . ')',
'type' => 'line',
'fill' => false,
'yAxisID' => 'y-axis-1',
'entries' => [],
];
$chartData[$tag->id . '-total-out'] = [
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.sum_of_expenses'))) . ')',
'type' => 'line',
'fill' => false,
'yAxisID' => 'y-axis-1',
'entries' => [],
];
}
$sumOfIncome = [];
$sumOfExpense = [];
while ($currentStart < $end) {
$currentEnd = clone $currentStart;
$currentEnd = $currentEnd->$function();
$expenses = $this->groupByTag($this->getExpenses($accounts, $tags, $currentStart, $currentEnd));
$income = $this->groupByTag($this->getIncome($accounts, $tags, $currentStart, $currentEnd));
$label = $currentStart->formatLocalized($format);
/** @var Tag $tag */
foreach ($tags as $tag) {
$labelIn = $tag->id . '-in';
$labelOut = $tag->id . '-out';
$labelSumIn = $tag->id . '-total-in';
$labelSumOut = $tag->id . '-total-out';
$currentIncome = $income[$tag->id] ?? '0';
$currentExpense = $expenses[$tag->id] ?? '0';
// add to sum:
$sumOfIncome[$tag->id] = $sumOfIncome[$tag->id] ?? '0';
$sumOfExpense[$tag->id] = $sumOfExpense[$tag->id] ?? '0';
$sumOfIncome[$tag->id] = bcadd($sumOfIncome[$tag->id], $currentIncome);
$sumOfExpense[$tag->id] = bcadd($sumOfExpense[$tag->id], $currentExpense);
// add to chart:
$chartData[$labelIn]['entries'][$label] = $currentIncome;
$chartData[$labelOut]['entries'][$label] = $currentExpense;
$chartData[$labelSumIn]['entries'][$label] = $sumOfIncome[$tag->id];
$chartData[$labelSumOut]['entries'][$label] = $sumOfExpense[$tag->id];
}
$currentStart = clone $currentEnd;
$currentStart->addDay();
}
// remove all empty entries to prevent cluttering:
$newSet = [];
foreach ($chartData as $key => $entry) {
if (!array_sum($entry['entries']) == 0) {
$newSet[$key] = $chartData[$key];
}
}
if (count($newSet) === 0) {
$newSet = $chartData; // @codeCoverageIgnore
}
$data = $this->generator->multiSet($newSet);
$cache->store($data);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function tagExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
{
/** @var MetaPieChartInterface $helper */
$helper = app(MetaPieChartInterface::class);
$helper->setAccounts($accounts);
$helper->setTags($tags);
$helper->setStart($start);
$helper->setEnd($end);
$helper->setCollectOtherObjects(intval($others) === 1);
$chartData = $helper->generate('expense', 'tag');
$data = $this->generator->pieChart($chartData);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
* @param string $others
*
* @return \Illuminate\Http\JsonResponse
*/
public function tagIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
{
/** @var MetaPieChartInterface $helper */
$helper = app(MetaPieChartInterface::class);
$helper->setAccounts($accounts);
$helper->setTags($tags);
$helper->setStart($start);
$helper->setEnd($end);
$helper->setCollectOtherObjects(intval($others) === 1);
$chartData = $helper->generate('income', 'tag');
$data = $this->generator->pieChart($chartData);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getExpenses(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setTags($tags)->withOpposingAccount();
$collector->removeFilter(TransferFilter::class);
$collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(PositiveAmountFilter::class);
$transactions = $collector->getJournals();
return $transactions;
}
/**
* @param Collection $accounts
* @param Collection $tags
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->setTags($tags)->withOpposingAccount();
$collector->addFilter(OpposingAccountFilter::class);
$collector->addFilter(NegativeAmountFilter::class);
$transactions = $collector->getJournals();
return $transactions;
}
/**
* @param Collection $set
*
* @return array
*/
private function groupByTag(Collection $set): array
{
// group by category ID:
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$journal = $transaction->transactionJournal;
$journalTags = $journal->tags;
/** @var Tag $journalTag */
foreach ($journalTags as $journalTag) {
$journalTagId = $journalTag->id;
$grouped[$journalTagId] = $grouped[$journalTagId] ?? '0';
$grouped[$journalTagId] = bcadd($transaction->transaction_amount, $grouped[$journalTagId]);
}
}
return $grouped;
}
}

View File

@@ -100,7 +100,7 @@ class Controller extends BaseController
*/ */
protected function isOpeningBalance(TransactionJournal $journal): bool protected function isOpeningBalance(TransactionJournal $journal): bool
{ {
return TransactionJournal::transactionTypeStr($journal) === TransactionType::OPENING_BALANCE; return $journal->transactionTypeStr() === TransactionType::OPENING_BALANCE;
} }
/** /**
@@ -120,9 +120,11 @@ class Controller extends BaseController
} }
} }
// @codeCoverageIgnoreStart
Session::flash('error', strval(trans('firefly.cannot_redirect_to_account'))); Session::flash('error', strval(trans('firefly.cannot_redirect_to_account')));
return redirect(route('index')); return redirect(route('index'));
// @codeCoverageIgnoreEnd
} }
/** /**

View File

@@ -17,6 +17,7 @@ use Cache;
use FireflyIII\Http\Requests\CurrencyFormRequest; use FireflyIII\Http\Requests\CurrencyFormRequest;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Log; use Log;
use Preferences; use Preferences;
@@ -30,6 +31,11 @@ use View;
class CurrencyController extends Controller class CurrencyController extends Controller
{ {
/** @var CurrencyRepositoryInterface */
protected $repository;
/** @var UserRepositoryInterface */
protected $userRepository;
/** /**
* *
@@ -43,6 +49,8 @@ class CurrencyController extends Controller
function ($request, $next) { function ($request, $next) {
View::share('title', trans('firefly.currencies')); View::share('title', trans('firefly.currencies'));
View::share('mainTitleIcon', 'fa-usd'); View::share('mainTitleIcon', 'fa-usd');
$this->repository = app(CurrencyRepositoryInterface::class);
$this->userRepository = app(UserRepositoryInterface::class);
return $next($request); return $next($request);
} }
@@ -50,10 +58,18 @@ class CurrencyController extends Controller
} }
/** /**
* @return View * @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/ */
public function create(Request $request) public function create(Request $request)
{ {
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
return redirect(route('currencies.index'));
}
$subTitleIcon = 'fa-plus'; $subTitleIcon = 'fa-plus';
$subTitle = trans('firefly.create_currency'); $subTitle = trans('firefly.create_currency');
@@ -90,14 +106,22 @@ class CurrencyController extends Controller
/** /**
* @param CurrencyRepositoryInterface $repository * @param Request $request
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/ */
public function delete(Request $request, CurrencyRepositoryInterface $repository, TransactionCurrency $currency) public function delete(Request $request, TransactionCurrency $currency)
{ {
if (!$repository->canDeleteCurrency($currency)) { if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
// @codeCoverageIgnoreStart
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
return redirect(route('currencies.index'));
// @codeCoverageIgnoreEnd
}
if (!$this->repository->canDeleteCurrency($currency)) {
$request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name])); $request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
@@ -115,20 +139,28 @@ class CurrencyController extends Controller
} }
/** /**
* @param CurrencyRepositoryInterface $repository * @param Request $request
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/ */
public function destroy(Request $request, CurrencyRepositoryInterface $repository, TransactionCurrency $currency) public function destroy(Request $request, TransactionCurrency $currency)
{ {
if (!$repository->canDeleteCurrency($currency)) { if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
// @codeCoverageIgnoreStart
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
return redirect(route('currencies.index'));
// @codeCoverageIgnoreEnd
}
if (!$this->repository->canDeleteCurrency($currency)) {
$request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name])); $request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
return redirect(route('currencies.index')); return redirect(route('currencies.index'));
} }
$repository->destroy($currency); $this->repository->destroy($currency);
$request->session()->flash('success', trans('firefly.deleted_currency', ['name' => $currency->name])); $request->session()->flash('success', trans('firefly.deleted_currency', ['name' => $currency->name]));
return redirect($this->getPreviousUri('currencies.delete.uri')); return redirect($this->getPreviousUri('currencies.delete.uri'));
@@ -138,10 +170,18 @@ class CurrencyController extends Controller
* @param Request $request * @param Request $request
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @return View * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/ */
public function edit(Request $request, TransactionCurrency $currency) public function edit(Request $request, TransactionCurrency $currency)
{ {
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
// @codeCoverageIgnoreStart
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
return redirect(route('currencies.index'));
// @codeCoverageIgnoreEnd
}
$subTitleIcon = 'fa-pencil'; $subTitleIcon = 'fa-pencil';
$subTitle = trans('breadcrumbs.edit_currency', ['name' => $currency->name]); $subTitle = trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$currency->symbol = htmlentities($currency->symbol); $currency->symbol = htmlentities($currency->symbol);
@@ -160,47 +200,45 @@ class CurrencyController extends Controller
/** /**
* @param Request $request * @param Request $request
* @param CurrencyRepositoryInterface $repository
* *
* @return View * @return View
*/ */
public function index(Request $request, CurrencyRepositoryInterface $repository) public function index(Request $request)
{ {
$currencies = $repository->get(); $currencies = $this->repository->get();
$defaultCurrency = $repository->getCurrencyByPreference(Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'))); $defaultCurrency = $this->repository->getCurrencyByPreference(Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR')));
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
if (!auth()->user()->hasRole('owner')) {
$request->session()->flash('info', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')])); $request->session()->flash('info', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
} }
return view('currencies.index', compact('currencies', 'defaultCurrency')); return view('currencies.index', compact('currencies', 'defaultCurrency'));
} }
/** /**
*
* @param CurrencyFormRequest $request * @param CurrencyFormRequest $request
* @param CurrencyRepositoryInterface $repository
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/ */
public function store(CurrencyFormRequest $request, CurrencyRepositoryInterface $repository) public function store(CurrencyFormRequest $request)
{ {
if (!auth()->user()->hasRole('owner')) { if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
// @codeCoverageIgnoreStart
Log::error('User ' . auth()->user()->id . ' is not admin, but tried to store a currency.'); Log::error('User ' . auth()->user()->id . ' is not admin, but tried to store a currency.');
return redirect($this->getPreviousUri('currencies.create.uri')); return redirect($this->getPreviousUri('currencies.create.uri'));
// @codeCoverageIgnoreEnd
} }
$data = $request->getCurrencyData(); $data = $request->getCurrencyData();
$currency = $repository->store($data); $currency = $this->repository->store($data);
$request->session()->flash('success', trans('firefly.created_currency', ['name' => $currency->name])); $request->session()->flash('success', trans('firefly.created_currency', ['name' => $currency->name]));
if (intval($request->get('create_another')) === 1) { if (intval($request->get('create_another')) === 1) {
// @codeCoverageIgnoreStart
$request->session()->put('currencies.create.fromStore', true); $request->session()->put('currencies.create.fromStore', true);
return redirect(route('currencies.create'))->withInput(); return redirect(route('currencies.create'))->withInput();
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('currencies.create.uri')); return redirect($this->getPreviousUri('currencies.create.uri'));
@@ -208,25 +246,32 @@ class CurrencyController extends Controller
/** /**
* @param CurrencyFormRequest $request * @param CurrencyFormRequest $request
* @param CurrencyRepositoryInterface $repository
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/ */
public function update(CurrencyFormRequest $request, CurrencyRepositoryInterface $repository, TransactionCurrency $currency) public function update(CurrencyFormRequest $request, TransactionCurrency $currency)
{ {
$data = $request->getCurrencyData(); if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
if (auth()->user()->hasRole('owner')) { // @codeCoverageIgnoreStart
$currency = $repository->update($currency, $data); $request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
return redirect(route('currencies.index'));
// @codeCoverageIgnoreEnd
} }
$data = $request->getCurrencyData();
$currency = $this->repository->update($currency, $data);
$request->session()->flash('success', trans('firefly.updated_currency', ['name' => $currency->name])); $request->session()->flash('success', trans('firefly.updated_currency', ['name' => $currency->name]));
Preferences::mark(); Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) { if (intval($request->get('return_to_edit')) === 1) {
// @codeCoverageIgnoreStart
$request->session()->put('currencies.edit.fromUpdate', true); $request->session()->put('currencies.edit.fromUpdate', true);
return redirect(route('currencies.edit', [$currency->id])); return redirect(route('currencies.edit', [$currency->id]));
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('currencies.edit.uri')); return redirect($this->getPreviousUri('currencies.edit.uri'));

View File

@@ -23,7 +23,6 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Models\ExportJob; use FireflyIII\Models\ExportJob;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface; use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface;
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface as EJRI;
use Illuminate\Http\Response as LaravelResponse; use Illuminate\Http\Response as LaravelResponse;
use Preferences; use Preferences;
use Response; use Response;
@@ -55,9 +54,10 @@ class ExportController extends Controller
} }
/** /**
* @param ExportJobRepositoryInterface $repository
* @param ExportJob $job * @param ExportJob $job
* *
* @return \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Routing\ResponseFactory * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
* @throws FireflyException * @throws FireflyException
*/ */
public function download(ExportJobRepositoryInterface $repository, ExportJob $job) public function download(ExportJobRepositoryInterface $repository, ExportJob $job)
@@ -103,11 +103,11 @@ class ExportController extends Controller
/** /**
* @param AccountRepositoryInterface $repository * @param AccountRepositoryInterface $repository
* @param EJRI $jobs * @param ExportJobRepositoryInterface $jobs
* *
* @return View * @return View
*/ */
public function index(AccountRepositoryInterface $repository, EJRI $jobs) public function index(AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs)
{ {
// create new export job. // create new export job.
$job = $jobs->create(); $job = $jobs->create();
@@ -130,11 +130,11 @@ class ExportController extends Controller
/** /**
* @param ExportFormRequest $request * @param ExportFormRequest $request
* @param AccountRepositoryInterface $repository * @param AccountRepositoryInterface $repository
* @param EJRI $jobs * @param ExportJobRepositoryInterface $jobs
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, EJRI $jobs) public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs)
{ {
$job = $jobs->findByKey($request->get('job')); $job = $jobs->findByKey($request->get('job'));
$settings = [ $settings = [

View File

@@ -58,7 +58,7 @@ class HelpController extends Controller
return Response::json($content); return Response::json($content);
} }
$content = $help->getFromGithub($language, $route); $content = $help->getFromGithub($route, $language);
$notYourLanguage = '<p><em>' . strval(trans('firefly.help_may_not_be_your_language')) . '</em></p>'; $notYourLanguage = '<p><em>' . strval(trans('firefly.help_may_not_be_your_language')) . '</em></p>';
// get backup language content (try English): // get backup language content (try English):
@@ -66,10 +66,10 @@ class HelpController extends Controller
$language = 'en_US'; $language = 'en_US';
if ($help->inCache($route, $language)) { if ($help->inCache($route, $language)) {
Log::debug(sprintf('Help text %s was in cache.', $language)); Log::debug(sprintf('Help text %s was in cache.', $language));
$content = $help->getFromCache($route, $language); $content = $notYourLanguage . $help->getFromCache($route, $language);
} }
if (!$help->inCache($route, $language)) { if (!$help->inCache($route, $language)) {
$content = trim($notYourLanguage . $help->getFromGithub($language, $route)); $content = trim($notYourLanguage . $help->getFromGithub($route, $language));
} }
} }

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Artisan; use Artisan;
@@ -17,7 +18,7 @@ use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -97,11 +98,11 @@ class HomeController extends Controller
} }
/** /**
* @param ARI $repository * @param AccountRepositoryInterface $repository
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/ */
public function index(ARI $repository) public function index(AccountRepositoryInterface $repository)
{ {
$types = config('firefly.accountTypesByIdentifier.asset'); $types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->count($types); $count = $repository->count($types);

View File

@@ -12,11 +12,10 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Crypt;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\ImportUploadRequest; use FireflyIII\Http\Requests\ImportUploadRequest;
use FireflyIII\Import\ImportProcedureInterface; use FireflyIII\Import\Configurator\ConfiguratorInterface;
use FireflyIII\Import\Setup\SetupInterface; use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Models\ImportJob; use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;
@@ -24,19 +23,18 @@ use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse; use Illuminate\Http\Response as LaravelResponse;
use Log; use Log;
use Response; use Response;
use Session;
use SplFileObject;
use Storage;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use View; use View;
/** /**
* Class ImportController * Class ImportController.
* *
* @package FireflyIII\Http\Controllers * @package FireflyIII\Http\Controllers
*/ */
class ImportController extends Controller class ImportController extends Controller
{ {
/** @var ImportJobRepositoryInterface */
public $repository;
/** /**
* *
*/ */
@@ -47,7 +45,8 @@ class ImportController extends Controller
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
View::share('mainTitleIcon', 'fa-archive'); View::share('mainTitleIcon', 'fa-archive');
View::share('title', trans('firefly.import_data_full')); View::share('title', trans('firefly.import_index_title'));
$this->repository = app(ImportJobRepositoryInterface::class);
return $next($request); return $next($request);
} }
@@ -55,28 +54,7 @@ class ImportController extends Controller
} }
/** /**
* This is the last step before the import starts. * This is step 3. This repeats until the job is configured.
*
* @param ImportJob $job
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
public function complete(ImportJob $job)
{
Log::debug('Now in complete()', ['job' => $job->key]);
if (!$this->jobInCorrectStep($job, 'complete')) {
return $this->redirectToCorrectStep($job);
}
$subTitle = trans('firefly.import_complete');
$subTitleIcon = 'fa-star';
return view('import.complete', compact('job', 'subTitle', 'subTitleIcon'));
}
/**
* This is step 3.
* This is the first step in configuring the job. It can only be executed
* when the job is set to "import_status_never_started".
* *
* @param ImportJob $job * @param ImportJob $job
* *
@@ -85,39 +63,41 @@ class ImportController extends Controller
*/ */
public function configure(ImportJob $job) public function configure(ImportJob $job)
{ {
Log::debug('Now at start of configure()'); // create configuration class:
if (!$this->jobInCorrectStep($job, 'configure')) { $configurator = $this->makeConfigurator($job);
Log::debug('Job is not in correct state for configure()', ['status' => $job->status]);
return $this->redirectToCorrectStep($job); // is the job already configured?
if ($configurator->isJobConfigured()) {
$this->repository->updateStatus($job, 'configured');
return redirect(route('import.status', [$job->key]));
} }
$view = $configurator->getNextView();
// actual code $data = $configurator->getNextData();
$importer = $this->makeImporter($job); $subTitle = trans('firefly.import_config_bread_crumb');
$importer->configure();
$data = $importer->getConfigurationData();
$subTitle = trans('firefly.configure_import');
$subTitleIcon = 'fa-wrench'; $subTitleIcon = 'fa-wrench';
return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon')); return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
} }
/** /**
* Generate a JSON file of the job's config and send it to the user. * Generate a JSON file of the job's configuration and send it to the user.
* *
* @param ImportJob $job * @param ImportJob $job
* *
* @return mixed * @return LaravelResponse
*/ */
public function download(ImportJob $job) public function download(ImportJob $job)
{ {
Log::debug('Now in download()', ['job' => $job->key]); Log::debug('Now in download()', ['job' => $job->key]);
$config = $job->configuration; $config = $job->configuration;
// TODO this is CSV import specific:
$config['column-roles-complete'] = false; $config['column-roles-complete'] = false;
$config['column-mapping-complete'] = false; $config['column-mapping-complete'] = false;
$config['initial-config-complete'] = false;
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter']; $config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
$result = json_encode($config, JSON_PRETTY_PRINT); $result = json_encode($config, JSON_PRETTY_PRINT);
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
@@ -137,28 +117,6 @@ class ImportController extends Controller
} }
/**
* @param ImportJob $job
*
* @return View
*/
public function finished(ImportJob $job)
{
if (!$this->jobInCorrectStep($job, 'finished')) {
Log::debug('Job is not in correct state for finished()', ['status' => $job->status]);
return $this->redirectToCorrectStep($job);
}
// if there is a tag (there might not be), we can link to it:
$tagId = $job->extended_status['importTag'] ?? 0;
$subTitle = trans('firefly.import_finished');
$subTitleIcon = 'fa-star';
return view('import.finished', compact('job', 'subTitle', 'subTitleIcon', 'tagId'));
}
/** /**
* This is step 1. Upload a file. * This is step 1. Upload a file.
* *
@@ -166,8 +124,7 @@ class ImportController extends Controller
*/ */
public function index() public function index()
{ {
Log::debug('Now at index'); $subTitle = trans('firefly.import_index_sub_title');
$subTitle = trans('firefly.import_data_index');
$subTitleIcon = 'fa-home'; $subTitleIcon = 'fa-home';
$importFileTypes = []; $importFileTypes = [];
$defaultImportType = config('firefly.default_import_format'); $defaultImportType = config('firefly.default_import_format');
@@ -180,6 +137,38 @@ class ImportController extends Controller
} }
/** /**
* This is step 2. It creates an Import Job. Stores the import.
*
* @param ImportUploadRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function initialize(ImportUploadRequest $request)
{
Log::debug('Now in initialize()');
// create import job:
$type = $request->get('import_file_type');
$job = $this->repository->create($type);
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
// process file:
$this->repository->processFile($job, $request->files->get('import_file'));
// process config, if present:
if ($request->files->has('configuration_file')) {
$this->repository->processConfiguration($job, $request->files->get('configuration_file'));
}
$this->repository->updateStatus($job, 'initialized');
return redirect(route('import.configure', [$job->key]));
}
/**
*
* Show status of import job in JSON.
*
* @param ImportJob $job * @param ImportJob $job
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
@@ -187,35 +176,36 @@ class ImportController extends Controller
public function json(ImportJob $job) public function json(ImportJob $job)
{ {
$result = [ $result = [
'showPercentage' => false,
'started' => false, 'started' => false,
'finished' => false, 'finished' => false,
'running' => false, 'running' => false,
'errors' => $job->extended_status['errors'], 'errors' => array_values($job->extended_status['errors']),
'percentage' => 0, 'percentage' => 0,
'steps' => $job->extended_status['total_steps'], 'show_percentage' => false,
'stepsDone' => $job->extended_status['steps_done'], 'steps' => $job->extended_status['steps'],
'statusText' => trans('firefly.import_status_' . $job->status), 'done' => $job->extended_status['done'],
'statusText' => trans('firefly.import_status_job_' . $job->status),
'status' => $job->status,
'finishedText' => '', 'finishedText' => '',
]; ];
$percentage = 0;
if ($job->extended_status['total_steps'] !== 0) { if ($job->extended_status['steps'] !== 0) {
$percentage = round(($job->extended_status['steps_done'] / $job->extended_status['total_steps']) * 100, 0); $result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
$result['show_percentage'] = true;
} }
if ($job->status === 'import_complete') {
$tagId = $job->extended_status['importTag']; if ($job->status === 'finished') {
$tagId = $job->extended_status['tag'];
/** @var TagRepositoryInterface $repository */ /** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class); $repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId); $tag = $repository->find($tagId);
$result['finished'] = true; $result['finished'] = true;
$result['finishedText'] = trans('firefly.import_finished_link', ['link' => route('tags.show', [$tag->id]), 'tag' => $tag->tag]); $result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
} }
if ($job->status === 'import_running') { if ($job->status === 'running') {
$result['started'] = true; $result['started'] = true;
$result['running'] = true; $result['running'] = true;
$result['showPercentage'] = true;
$result['percentage'] = $percentage;
} }
return Response::json($result); return Response::json($result);
@@ -228,286 +218,78 @@ class ImportController extends Controller
* @param ImportJob $job * @param ImportJob $job
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/ */
public function postConfigure(Request $request, ImportJob $job) public function postConfigure(Request $request, ImportJob $job)
{ {
Log::debug('Now in postConfigure()', ['job' => $job->key]); Log::debug('Now in postConfigure()', ['job' => $job->key]);
if (!$this->jobInCorrectStep($job, 'process')) { $configurator = $this->makeConfigurator($job);
return $this->redirectToCorrectStep($job);
}
Log::debug('Continue postConfigure()', ['job' => $job->key]);
// actual code // is the job already configured?
$importer = $this->makeImporter($job); if ($configurator->isJobConfigured()) {
return redirect(route('import.status', [$job->key]));
}
$data = $request->all(); $data = $request->all();
$files = $request->files; $configurator->configureJob($data);
$importer->saveImportConfiguration($data, $files);
// update job: // return to configure
$job->status = 'import_configuration_saved'; return redirect(route('import.configure', [$job->key]));
$job->save();
// return redirect to settings.
// this could loop until the user is done.
return redirect(route('import.settings', [$job->key]));
} }
/** /**
* This step 6. Depending on the importer, this will process the
* settings given and store them.
*
* @param Request $request
* @param ImportJob $job * @param ImportJob $job
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @return \Illuminate\Http\JsonResponse
* @throws FireflyException * @throws FireflyException
*/ */
public function postSettings(Request $request, ImportJob $job) public function start(ImportJob $job)
{ {
Log::debug('Now in postSettings()', ['job' => $job->key]); /** @var ImportRoutine $routine */
if (!$this->jobInCorrectStep($job, 'store-settings')) { $routine = app(ImportRoutine::class);
return $this->redirectToCorrectStep($job); $routine->setJob($job);
$result = $routine->run();
if ($result) {
return Response::json(['run' => 'ok']);
} }
$importer = $this->makeImporter($job);
$importer->storeSettings($request);
// return redirect to settings (for more settings perhaps) throw new FireflyException('Job did not complete succesfully.');
return redirect(route('import.settings', [$job->key]));
} }
/** /**
* Step 5. Depending on the importer, this will show the user settings to
* fill in.
*
* @param ImportJob $job
*
* @return View
* @throws FireflyException
*/
public function settings(ImportJob $job)
{
Log::debug('Now in settings()', ['job' => $job->key]);
if (!$this->jobInCorrectStep($job, 'settings')) {
Log::debug('Job should not be in settings()');
return $this->redirectToCorrectStep($job);
}
Log::debug('Continue in settings()');
$importer = $this->makeImporter($job);
$subTitle = trans('firefly.settings_for_import');
$subTitleIcon = 'fa-wrench';
// now show settings screen to user.
if ($importer->requireUserSettings()) {
Log::debug('Job requires user config.');
$data = $importer->getDataForSettings();
$view = $importer->getViewForSettings();
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
}
Log::debug('Job does NOT require user config.');
$job->status = 'settings_complete';
$job->save();
// if no more settings, save job and continue to process thing.
return redirect(route('import.complete', [$job->key]));
// ask the importer for the requested action.
// for example pick columns or map data.
// depends of course on the data in the job.
}
/**
* @param ImportProcedureInterface $importProcedure
* @param ImportJob $job
*/
public function start(ImportProcedureInterface $importProcedure, ImportJob $job)
{
set_time_limit(0);
if ($job->status == 'settings_complete') {
$importProcedure->runImport($job);
}
}
/**
* This is the last step before the import starts.
*
* @param ImportJob $job * @param ImportJob $job
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/ */
public function status(ImportJob $job) public function status(ImportJob $job)
{ // {
Log::debug('Now in status()', ['job' => $job->key]); $statuses = ['configured', 'running', 'finished'];
if (!$this->jobInCorrectStep($job, 'status')) { if (!in_array($job->status, $statuses)) {
return $this->redirectToCorrectStep($job); return redirect(route('import.configure', [$job->key]));
} }
$subTitle = trans('firefly.import_status'); $subTitle = trans('firefly.import_status_sub_title');
$subTitleIcon = 'fa-star'; $subTitleIcon = 'fa-star';
return view('import.status', compact('job', 'subTitle', 'subTitleIcon')); return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
} }
/**
* This is step 2. It creates an Import Job. Stores the import.
*
* @param ImportUploadRequest $request
* @param ImportJobRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository)
{
Log::debug('Now in upload()');
// create import job:
$type = $request->get('import_file_type');
$job = $repository->create($type);
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
/** @var UploadedFile $upload */
$upload = $request->files->get('import_file');
$newName = $job->key . '.upload';
$uploaded = new SplFileObject($upload->getRealPath());
$content = $uploaded->fread($uploaded->getSize());
$contentEncrypted = Crypt::encrypt($content);
$disk = Storage::disk('upload');
// user is demo user, replace upload with prepared file.
if (auth()->user()->hasRole('demo')) {
$stubsDisk = Storage::disk('stubs');
$content = $stubsDisk->get('demo-import.csv');
$contentEncrypted = Crypt::encrypt($content);
$disk->put($newName, $contentEncrypted);
Log::debug('Replaced upload with demo file.');
// also set up prepared configuration.
$configuration = json_decode($stubsDisk->get('demo-configuration.json'), true);
$job->configuration = $configuration;
$job->save();
Log::debug('Set configuration for demo user', $configuration);
// also flash info
Session::flash('info', trans('demo.import-configure-security'));
}
if (!auth()->user()->hasRole('demo')) {
// user is not demo, process original upload:
$disk->put($newName, $contentEncrypted);
Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]);
}
// store configuration file's content into the job's configuration thing. Otherwise, leave it empty.
// demo user's configuration upload is ignored completely.
if ($request->files->has('configuration_file') && !auth()->user()->hasRole('demo')) {
/** @var UploadedFile $configFile */
$configFile = $request->files->get('configuration_file');
Log::debug(
'Uploaded configuration file',
['name' => $configFile->getClientOriginalName(), 'size' => $configFile->getSize(), 'mime' => $configFile->getClientMimeType()]
);
$configFileObject = new SplFileObject($configFile->getRealPath());
$configRaw = $configFileObject->fread($configFileObject->getSize());
$configuration = json_decode($configRaw, true);
if (!is_null($configuration) && is_array($configuration)) {
Log::debug('Found configuration', $configuration);
$job->configuration = $configuration;
$job->save();
}
}
// if user is demo user, replace config with prepared config:
return redirect(route('import.configure', [$job->key]));
}
/**
* @param ImportJob $job
* @param string $method
*
* @return bool
*/
private function jobInCorrectStep(ImportJob $job, string $method): bool
{
Log::debug('Now in jobInCorrectStep()', ['job' => $job->key, 'method' => $method]);
switch ($method) {
case 'configure':
case 'process':
return $job->status === 'import_status_never_started';
case 'settings':
case 'store-settings':
return $job->status === 'import_configuration_saved';
case 'finished':
return $job->status === 'import_complete';
case 'complete':
return $job->status === 'settings_complete';
case 'status':
return ($job->status === 'settings_complete') || ($job->status === 'import_running');
}
return false;
}
/** /**
* @param ImportJob $job * @param ImportJob $job
* *
* @return SetupInterface * @return ConfiguratorInterface
* @throws FireflyException * @throws FireflyException
*/ */
private function makeImporter(ImportJob $job): SetupInterface private function makeConfigurator(ImportJob $job): ConfiguratorInterface
{ {
// create proper importer (depends on job) $type = $job->file_type;
$type = strtolower($job->file_type); $key = sprintf('firefly.import_configurators.%s', $type);
$className = config($key);
// validate type: if (is_null($className)) {
$validTypes = array_keys(config('firefly.import_formats')); throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
if (in_array($type, $validTypes)) {
/** @var SetupInterface $importer */
$importer = app('FireflyIII\Import\Setup\\' . ucfirst($type) . 'Setup');
$importer->setJob($job);
return $importer;
} }
throw new FireflyException(sprintf('"%s" is not a valid file type', $type)); /** @var ConfiguratorInterface $configurator */
$configurator = app($className);
$configurator->setJob($job);
}
/**
* @param ImportJob $job
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/
private function redirectToCorrectStep(ImportJob $job)
{
Log::debug('Now in redirectToCorrectStep()', ['job' => $job->key]);
switch ($job->status) {
case 'import_status_never_started':
Log::debug('Will redirect to configure()');
return redirect(route('import.configure', [$job->key]));
case 'import_configuration_saved':
Log::debug('Will redirect to settings()');
return redirect(route('import.settings', [$job->key]));
case 'settings_complete':
Log::debug('Will redirect to complete()');
return redirect(route('import.complete', [$job->key]));
case 'import_complete':
Log::debug('Will redirect to finished()');
return redirect(route('import.finished', [$job->key]));
}
throw new FireflyException('Cannot redirect for job state ' . $job->status);
return $configurator;
} }
} }

View File

@@ -13,6 +13,11 @@ namespace FireflyIII\Http\Controllers;
use Amount; use Amount;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Navigation; use Navigation;
use Preferences; use Preferences;
@@ -25,9 +30,61 @@ use Session;
*/ */
class JavascriptController extends Controller class JavascriptController extends Controller
{ {
/**
* @param AccountRepositoryInterface $repository
* @param CurrencyRepositoryInterface $currencyRepository
*
* @return \Illuminate\Http\Response
*/
public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository)
{
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$preference = Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'));
$default = $currencyRepository->findByCode($preference->data);
$data = ['accounts' => [],];
/** @var Account $account */
foreach ($accounts as $account) {
$accountId = $account->id;
$currency = intval($account->getMeta('currency_id'));
$currency = $currency === 0 ? $default->id : $currency;
$entry = ['preferredCurrency' => $currency, 'name' => $account->name];
$data['accounts'][$accountId] = $entry;
}
return response()
->view('javascript.accounts', $data, 200)
->header('Content-Type', 'text/javascript');
}
/** /**
* @param CurrencyRepositoryInterface $repository
* *
* @return \Illuminate\Http\Response
*/
public function currencies(CurrencyRepositoryInterface $repository)
{
$currencies = $repository->get();
$data = ['currencies' => [],];
/** @var TransactionCurrency $currency */
foreach ($currencies as $currency) {
$currencyId = $currency->id;
$entry = ['name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol];
$data['currencies'][$currencyId] = $entry;
}
return response()
->view('javascript.currencies', $data, 200)
->header('Content-Type', 'text/javascript');
}
/**
* @param Request $request
*
* @return \Illuminate\Http\Response
*/ */
public function variables(Request $request) public function variables(Request $request)
{ {
@@ -95,7 +152,7 @@ class JavascriptController extends Controller
switch ($viewRange) { switch ($viewRange) {
default: default:
throw new FireflyException('The date picker does not yet support "' . $viewRange . '".'); throw new FireflyException('The date picker does not yet support "' . $viewRange . '".'); // @codeCoverageIgnore
case '1D': case '1D':
case 'custom': case 'custom':
$format = (string)trans('config.month_and_day'); $format = (string)trans('config.month_and_day');

View File

@@ -0,0 +1,65 @@
<?php
/**
* ExchangeController.php
* Copyright (c) 2017 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\Json;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Services\Currency\ExchangeRateInterface;
use Illuminate\Http\Request;
use Log;
use Response;
/**
* Class ExchangeController
*
* @package FireflyIII\Http\Controllers\Json
*/
class ExchangeController extends Controller
{
/**
* @param Request $request
* @param TransactionCurrency $fromCurrency
* @param TransactionCurrency $toCurrency
* @param Carbon $date
*
* @return \Illuminate\Http\JsonResponse
*/
public function getRate(Request $request, TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date)
{
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date);
if (is_null($rate->id)) {
Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
$preferred = env('EXCHANGE_RATE_SERVICE', config('firefly.preferred_exchange_service'));
$class = config('firefly.currency_exchange_services.' . $preferred);
/** @var ExchangeRateInterface $object */
$object = app($class);
$object->setUser(auth()->user());
$rate = $object->getRate($fromCurrency, $toCurrency, $date);
}
$return = $rate->toArray();
$return['amount'] = null;
if (!is_null($request->get('amount'))) {
// assume amount is in "from" currency:
$return['amount'] = bcmul($request->get('amount'), strval($rate->rate), 12);
// round to toCurrency decimal places:
$return['amount'] = round($return['amount'], $toCurrency->decimal_places);
}
return Response::json($return);
}
}

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Amount; use Amount;
@@ -17,8 +18,8 @@ use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
@@ -116,8 +117,9 @@ class JsonController extends Controller
*/ */
$amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
$amount = bcmul($amount, '-1'); $amount = bcmul($amount, '-1');
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'bills-paid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $data = ['box' => 'bills-paid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data); return Response::json($data);
} }
@@ -132,19 +134,19 @@ class JsonController extends Controller
$start = session('start', Carbon::now()->startOfMonth()); $start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth()); $end = session('end', Carbon::now()->endOfMonth());
$amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
$data = ['box' => 'bills-unpaid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $currency = Amount::getDefaultCurrency();
$data = ['box' => 'bills-unpaid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data); return Response::json($data);
} }
/** /**
* @param AccountTaskerInterface $accountTasker
* @param AccountRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @internal param AccountTaskerInterface $accountTasker
* @internal param AccountRepositoryInterface $repository
* *
*/ */
public function boxIn(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository) public function boxIn()
{ {
$start = session('start', Carbon::now()->startOfMonth()); $start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth()); $end = session('end', Carbon::now()->endOfMonth());
@@ -155,24 +157,31 @@ class JsonController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('box-in'); $cache->addProperty('box-in');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); // try a collector for income:
$amount = $accountTasker->amountInInPeriod($accounts, $assets, $start, $end); /** @var JournalCollectorInterface $collector */
$data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'in', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data); $cache->store($data);
return Response::json($data); return Response::json($data);
} }
/** /**
* @param AccountTaskerInterface $accountTasker
* @param AccountRepositoryInterface $repository
*
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
* @internal param AccountTaskerInterface $accountTasker
* @internal param AccountRepositoryInterface $repository
*
*/ */
public function boxOut(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository) public function boxOut()
{ {
$start = session('start', Carbon::now()->startOfMonth()); $start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth()); $end = session('end', Carbon::now()->endOfMonth());
@@ -183,14 +192,18 @@ class JsonController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('box-out'); $cache->addProperty('box-out');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get()); // @codeCoverageIgnore
} }
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]); // try a collector for expenses:
$assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); /** @var JournalCollectorInterface $collector */
$amount = $accountTasker->amountOutInPeriod($accounts, $assets, $start, $end); $collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
$data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; ->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'out', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data); $cache->store($data);
return Response::json($data); return Response::json($data);
@@ -287,7 +300,7 @@ class JsonController extends Controller
{ {
$pref = Preferences::get('tour', true); $pref = Preferences::get('tour', true);
if (!$pref) { if (!$pref) {
throw new FireflyException('Cannot find preference for tour. Exit.'); throw new FireflyException('Cannot find preference for tour. Exit.'); // @codeCoverageIgnore
} }
$headers = ['main-content', 'sidebar-toggle', 'account-menu', 'budget-menu', 'report-menu', 'transaction-menu', 'option-menu', 'main-content-end']; $headers = ['main-content', 'sidebar-toggle', 'account-menu', 'budget-menu', 'report-menu', 'transaction-menu', 'option-menu', 'main-content-end'];
$steps = []; $steps = [];
@@ -329,7 +342,9 @@ class JsonController extends Controller
} }
/** /**
* @param JournalRepositoryInterface $repository
* *
* @return \Illuminate\Http\JsonResponse
*/ */
public function transactionTypes(JournalRepositoryInterface $repository) public function transactionTypes(JournalRepositoryInterface $repository)
{ {

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
@@ -119,7 +120,7 @@ class NewUserController extends Controller
'accountRole' => 'defaultAsset', 'accountRole' => 'defaultAsset',
'openingBalance' => round($request->input('bank_balance'), 12), 'openingBalance' => round($request->input('bank_balance'), 12),
'openingBalanceDate' => new Carbon, 'openingBalanceDate' => new Carbon,
'openingBalanceCurrency' => intval($request->input('amount_currency_id_bank_balance')), 'currency_id' => intval($request->input('amount_currency_id_bank_balance')),
]; ];
$repository->store($assetAccount); $repository->store($assetAccount);
@@ -144,7 +145,7 @@ class NewUserController extends Controller
'accountRole' => 'savingAsset', 'accountRole' => 'savingAsset',
'openingBalance' => round($request->input('savings_balance'), 12), 'openingBalance' => round($request->input('savings_balance'), 12),
'openingBalanceDate' => new Carbon, 'openingBalanceDate' => new Carbon,
'openingBalanceCurrency' => intval($request->input('amount_currency_id_savings_balance')), 'currency_id' => intval($request->input('amount_currency_id_savings_balance')),
]; ];
$repository->store($savingsAccount); $repository->store($savingsAccount);

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Amount; use Amount;
@@ -209,10 +210,11 @@ class PiggyBankController extends Controller
$end = session('end', Carbon::now()->endOfMonth()); $end = session('end', Carbon::now()->endOfMonth());
$accounts = []; $accounts = [];
Log::debug('Looping piggues');
/** @var PiggyBank $piggyBank */ /** @var PiggyBank $piggyBank */
foreach ($piggyBanks as $piggyBank) { foreach ($piggyBanks as $piggyBank) {
$piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount; $piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0';
$piggyBank->percentage = $piggyBank->savedSoFar != 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0; $piggyBank->percentage = bccomp('0', $piggyBank->savedSoFar) !== 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
$piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar)); $piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar));
$piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage; $piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage;
@@ -220,7 +222,9 @@ class PiggyBankController extends Controller
* Fill account information: * Fill account information:
*/ */
$account = $piggyBank->account; $account = $piggyBank->account;
$new = false;
if (!isset($accounts[$account->id])) { if (!isset($accounts[$account->id])) {
$new = true;
$accounts[$account->id] = [ $accounts[$account->id] = [
'name' => $account->name, 'name' => $account->name,
'balance' => Steam::balanceIgnoreVirtual($account, $end), 'balance' => Steam::balanceIgnoreVirtual($account, $end),
@@ -229,7 +233,8 @@ class PiggyBankController extends Controller
'sumOfTargets' => $piggyBank->targetamount, 'sumOfTargets' => $piggyBank->targetamount,
'leftToSave' => $piggyBank->leftToSave, 'leftToSave' => $piggyBank->leftToSave,
]; ];
} else { }
if (isset($accounts[$account->id]) && $new === false) {
$accounts[$account->id]['sumOfSaved'] = bcadd($accounts[$account->id]['sumOfSaved'], strval($piggyBank->savedSoFar)); $accounts[$account->id]['sumOfSaved'] = bcadd($accounts[$account->id]['sumOfSaved'], strval($piggyBank->savedSoFar));
$accounts[$account->id]['sumOfTargets'] = bcadd($accounts[$account->id]['sumOfTargets'], $piggyBank->targetamount); $accounts[$account->id]['sumOfTargets'] = bcadd($accounts[$account->id]['sumOfTargets'], $piggyBank->targetamount);
$accounts[$account->id]['leftToSave'] = bcadd($accounts[$account->id]['leftToSave'], $piggyBank->leftToSave); $accounts[$account->id]['leftToSave'] = bcadd($accounts[$account->id]['leftToSave'], $piggyBank->leftToSave);
@@ -272,33 +277,28 @@ class PiggyBankController extends Controller
public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
{ {
$amount = $request->get('amount'); $amount = $request->get('amount');
Log::debug(sprintf('Found amount is %s', $amount)); $currency = Amount::getDefaultCurrency();
/** @var Carbon $date */ if ($repository->canAddAmount($piggyBank, $amount)) {
$date = session('end', Carbon::now()->endOfMonth()); $repository->addAmount($piggyBank, $amount);
$leftOnAccount = $piggyBank->leftOnAccount($date);
$savedSoFar = strval($piggyBank->currentRelevantRep()->currentamount);
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
$maxAmount = strval(min(round($leftOnAccount, 12), round($leftToSave, 12)));
if (bccomp($amount, $maxAmount) <= 0) {
$repetition = $piggyBank->currentRelevantRep();
$currentAmount = $repetition->currentamount ?? '0';
$repetition->currentamount = bcadd($currentAmount, $amount);
$repetition->save();
// create event
$repository->createEvent($piggyBank, $amount);
Session::flash( Session::flash(
'success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])) 'success', strval(
trans(
'firefly.added_amount_to_piggy',
['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name]
)
)
); );
Preferences::mark(); Preferences::mark();
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
} }
Log::error('Cannot add ' . $amount . ' because max amount is ' . $maxAmount . ' (left on account is ' . $leftOnAccount . ')'); Log::error('Cannot add ' . $amount . ' because canAddAmount returned false.');
Session::flash('error', strval(trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))); Session::flash(
'error', strval(
trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)])
)
);
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
} }
@@ -312,27 +312,26 @@ class PiggyBankController extends Controller
*/ */
public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
{ {
$amount = strval(round($request->get('amount'), 12)); $amount = $request->get('amount');
$currency = Amount::getDefaultCurrency();
$savedSoFar = $piggyBank->currentRelevantRep()->currentamount; if ($repository->canRemoveAmount($piggyBank, $amount)) {
$repository->removeAmount($piggyBank, $amount);
if (bccomp($amount, $savedSoFar) <= 0) {
$repetition = $piggyBank->currentRelevantRep();
$repetition->currentamount = bcsub($repetition->currentamount, $amount);
$repetition->save();
// create event
$repository->createEvent($piggyBank, bcmul($amount, '-1'));
Session::flash( Session::flash(
'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])) 'success',
strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name]))
); );
Preferences::mark(); Preferences::mark();
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
} }
Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))); $amount = strval(round($request->get('amount'), 12));
Session::flash(
'error', strval(
trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)])
)
);
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
} }
@@ -391,9 +390,11 @@ class PiggyBankController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('create_another')) === 1) { if (intval($request->get('create_another')) === 1) {
// @codeCoverageIgnoreStart
Session::put('piggy-banks.create.fromStore', true); Session::put('piggy-banks.create.fromStore', true);
return redirect(route('piggy-banks.create'))->withInput(); return redirect(route('piggy-banks.create'))->withInput();
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('piggy-banks.edit.uri')); return redirect($this->getPreviousUri('piggy-banks.edit.uri'));
@@ -415,9 +416,11 @@ class PiggyBankController extends Controller
Preferences::mark(); Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) { if (intval($request->get('return_to_edit')) === 1) {
// @codeCoverageIgnoreStart
Session::put('piggy-banks.edit.fromUpdate', true); Session::put('piggy-banks.edit.fromUpdate', true);
return redirect(route('piggy-banks.edit', [$piggyBank->id])); return redirect(route('piggy-banks.edit', [$piggyBank->id]));
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('piggy-banks.edit.uri')); return redirect($this->getPreviousUri('piggy-banks.edit.uri'));

View File

@@ -17,17 +17,13 @@ namespace FireflyIII\Http\Controllers\Popup;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collection\BalanceLine; use FireflyIII\Helpers\Collection\BalanceLine;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Report\PopupReportInterface;
use FireflyIII\Http\Controllers\Controller; 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\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\Binder\AccountList; use FireflyIII\Support\Binder\AccountList;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use InvalidArgumentException; use InvalidArgumentException;
use Response; use Response;
use View; use View;
@@ -40,6 +36,44 @@ use View;
class ReportController extends Controller class ReportController extends Controller
{ {
/** @var AccountRepositoryInterface */
private $accountRepository;
/** @var BudgetRepositoryInterface */
private $budgetRepository;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var PopupReportInterface */
private $popupHelper;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var AccountRepositoryInterface $repository */
$this->accountRepository = app(AccountRepositoryInterface::class);
/** @var BudgetRepositoryInterface $repository */
$this->budgetRepository = app(BudgetRepositoryInterface::class);
/** @var CategoryRepositoryInterface categoryRepository */
$this->categoryRepository = app(CategoryRepositoryInterface::class);
/** @var PopupReportInterface popupHelper */
$this->popupHelper = app(PopupReportInterface::class);
return $next($request);
}
);
}
/** /**
* @param Request $request * @param Request $request
* *
@@ -59,7 +93,6 @@ class ReportController extends Controller
throw new FireflyException('Firefly cannot handle "' . e($attributes['location']) . '" '); throw new FireflyException('Firefly cannot handle "' . e($attributes['location']) . '" ');
case 'budget-spent-amount': case 'budget-spent-amount':
$html = $this->budgetSpentAmount($attributes); $html = $this->budgetSpentAmount($attributes);
break; break;
case 'expense-entry': case 'expense-entry':
$html = $this->expenseEntry($attributes); $html = $this->expenseEntry($attributes);
@@ -89,62 +122,25 @@ class ReportController extends Controller
private function balanceAmount(array $attributes): string private function balanceAmount(array $attributes): string
{ {
$role = intval($attributes['role']); $role = intval($attributes['role']);
$budget = $this->budgetRepository->find(intval($attributes['budgetId']));
/** @var BudgetRepositoryInterface $budgetRepository */ $account = $this->accountRepository->find(intval($attributes['accountId']));
$budgetRepository = app(BudgetRepositoryInterface::class);
$budget = $budgetRepository->find(intval($attributes['budgetId']));
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$account = $repository->find(intval($attributes['accountId']));
$types = [TransactionType::WITHDRAWAL];
switch (true) { switch (true) {
case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)): case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)):
/** @var JournalCollectorInterface $collector */ // normal row with a budget:
$collector = app(JournalCollectorInterface::class); $journals = $this->popupHelper->balanceForBudget($budget, $account, $attributes);
$collector
->setAccounts(new Collection([$account]))
->setRange($attributes['startDate'], $attributes['endDate'])
->setBudget($budget);
$journals = $collector->getJournals();
break; break;
case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)): case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)):
// normal row without a budget:
$journals = $this->popupHelper->balanceForNoBudget($account, $attributes);
$budget->name = strval(trans('firefly.no_budget')); $budget->name = strval(trans('firefly.no_budget'));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector
->setAccounts(new Collection([$account]))
->setTypes($types)
->setRange($attributes['startDate'], $attributes['endDate'])
->withoutBudget();
$journals = $collector->getJournals();
break; break;
case ($role === BalanceLine::ROLE_DIFFROLE): case ($role === BalanceLine::ROLE_DIFFROLE):
/** @var JournalCollectorInterface $collector */ $journals = $this->popupHelper->balanceDifference($account, $attributes);
$collector = app(JournalCollectorInterface::class);
$collector
->setAccounts(new Collection([$account]))
->setTypes($types)
->setRange($attributes['startDate'], $attributes['endDate'])
->withoutBudget();
$journals = $collector->getJournals();
$budget->name = strval(trans('firefly.leftUnbalanced')); $budget->name = strval(trans('firefly.leftUnbalanced'));
$journals = $journals->filter(
function (Transaction $transaction) {
$tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count();
if ($tags === 0) {
return true;
}
return false;
}
);
break; break;
case ($role === BalanceLine::ROLE_TAGROLE): case ($role === BalanceLine::ROLE_TAGROLE):
// row with tag info.
throw new FireflyException('Firefly cannot handle this type of info-button (BalanceLine::TagRole)'); throw new FireflyException('Firefly cannot handle this type of info-button (BalanceLine::TagRole)');
} }
$view = view('popup.report.balance-amount', compact('journals', 'budget', 'account'))->render(); $view = view('popup.report.balance-amount', compact('journals', 'budget', 'account'))->render();
@@ -162,27 +158,8 @@ class ReportController extends Controller
*/ */
private function budgetSpentAmount(array $attributes): string private function budgetSpentAmount(array $attributes): string
{ {
// need to find the budget $budget = $this->budgetRepository->find(intval($attributes['budgetId']));
// then search for expenses in the given period $journals = $this->popupHelper->byBudget($budget, $attributes);
// list them in some table format.
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$budget = $repository->find(intval($attributes['budgetId']));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$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(); $view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render();
return $view; return $view;
@@ -198,17 +175,8 @@ class ReportController extends Controller
*/ */
private function categoryEntry(array $attributes): string private function categoryEntry(array $attributes): string
{ {
/** @var CategoryRepositoryInterface $repository */ $category = $this->categoryRepository->find(intval($attributes['categoryId']));
$repository = app(CategoryRepositoryInterface::class); $journals = $this->popupHelper->byCategory($category, $attributes);
$category = $repository->find(intval($attributes['categoryId']));
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$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(); $view = view('popup.report.category-entry', compact('journals', 'category'))->render();
return $view; return $view;
@@ -224,28 +192,8 @@ class ReportController extends Controller
*/ */
private function expenseEntry(array $attributes): string private function expenseEntry(array $attributes): string
{ {
/** @var AccountRepositoryInterface $repository */ $account = $this->accountRepository->find(intval($attributes['accountId']));
$repository = app(AccountRepositoryInterface::class); $journals = $this->popupHelper->byExpenses($account, $attributes);
$account = $repository->find(intval($attributes['accountId']));
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$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
$journals = $journals->filter(
function (Transaction $transaction) use ($report) {
// get the destinations:
$sources = TransactionJournal::sourceAccountList($transaction->transactionJournal)->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $sources));
}
);
$view = view('popup.report.expense-entry', compact('journals', 'account'))->render(); $view = view('popup.report.expense-entry', compact('journals', 'account'))->render();
return $view; return $view;
@@ -261,27 +209,8 @@ class ReportController extends Controller
*/ */
private function incomeEntry(array $attributes): string private function incomeEntry(array $attributes): string
{ {
/** @var AccountRepositoryInterface $repository */ $account = $this->accountRepository->find(intval($attributes['accountId']));
$repository = app(AccountRepositoryInterface::class); $journals = $this->popupHelper->byIncome($account, $attributes);
$account = $repository->find(intval($attributes['accountId']));
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$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(
function (Transaction $transaction) use ($report) {
// get the destinations:
$destinations = TransactionJournal::destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $destinations));
}
);
$view = view('popup.report.income-entry', compact('journals', 'account'))->render(); $view = view('popup.report.income-entry', compact('journals', 'account'))->render();
return $view; return $view;

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