Compare commits

...

348 Commits
4.3.7 ... 4.4.1

Author SHA1 Message Date
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
601 changed files with 10021 additions and 3493 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

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
@@ -18,9 +17,13 @@ install:
- php artisan env - php artisan env
- cp .env.testing .env - cp .env.testing .env
- mv storage/database/databasecopy.sqlite storage/database/database.sqlite - mv storage/database/databasecopy.sqlite storage/database/database.sqlite
- mkdir -p build/logs
script: script:
- phpunit - phpunit -c phpunit.coverage.xml
after_success:
- travis_retry php vendor/bin/coveralls -x storage/build/clover.xml
# safelist # safelist
branches: branches:

View File

@@ -2,6 +2,37 @@
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.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 ## [4.3.7] - 2017-03-06
### Added ### Added
@@ -188,13 +219,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
@@ -203,11 +227,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.
@@ -330,15 +349,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)
@@ -346,12 +356,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.

1
Procfile Normal file
View File

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

View File

@@ -10,7 +10,7 @@
## 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 ## Installation
@@ -35,4 +35,4 @@ If you like Firefly and if it helps you save lots of money, why not send me [a d
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) [![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

@@ -15,13 +15,22 @@ 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;
/** /**
@@ -60,8 +69,15 @@ class UpgradeDatabase extends Command
{ {
$this->setTransactionIdentifier(); $this->setTransactionIdentifier();
$this->migrateRepetitions(); $this->migrateRepetitions();
$this->repairPiggyBanks();
$this->updateAccountCurrencies();
$this->updateJournalCurrencies();
$this->info('Firefly III database is up to date.');
} }
/**
* Migrate budget repetitions to new format.
*/
private function migrateRepetitions() private function migrateRepetitions()
{ {
if (!Schema::hasTable('budget_limits')) { if (!Schema::hasTable('budget_limits')) {
@@ -69,7 +85,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 +102,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 +161,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 +251,87 @@ 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.';
foreach ($types as $type => $operator) {
$set = 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')
->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'))
->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);
}
}
}
} }

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

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

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

@@ -20,6 +20,7 @@ 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\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Rules\Processor; use FireflyIII\Rules\Processor;
use FireflyIII\Support\Events\BillScanner; use FireflyIII\Support\Events\BillScanner;
use Log; use Log;
@@ -45,6 +46,16 @@ class StoredJournalEventHandler
$piggyBankId = $event->piggyBankId; $piggyBankId = $event->piggyBankId;
Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId)); Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId));
/*
* Will only continue when journal is a transfer.
*/
Log::debug(sprintf('Journal transaction type is %s', $journal->transactionType->type));
if ($journal->transactionType->type !== TransactionType::TRANSFER) {
Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id));
return true;
}
/* /*
* Verify existence of piggy bank: * Verify existence of piggy bank:
*/ */

View File

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

View File

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

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

@@ -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->destinationAccountList($transaction->transactionJournal)->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'
)
);
} }
/** /**
@@ -230,15 +238,19 @@ class AccountController extends Controller
/** /**
* @param Request $request * @param Request $request
* @param JournalRepositoryInterface $repository
* @param Account $account * @param Account $account
* @param string $moment * @param string $moment
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/ */
public function show(Request $request, Account $account, string $moment = '') 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);
} }
$subTitle = $account->name; /** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
@@ -247,35 +259,40 @@ class AccountController extends Controller
$start = null; $start = null;
$end = null; $end = null;
$periods = new Collection; $periods = new Collection;
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
// prep for "all" view. // prep for "all" view.
if ($moment === 'all') { if ($moment === 'all') {
$subTitle = $account->name . ' (' . strtolower(strval(trans('firefly.everything'))) . ')'; $subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
$chartUri = route('chart.account.all', [$account->id]); $chartUri = route('chart.account.all', [$account->id]);
$first = $repository->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
} }
// prep for "specific date" view. // prep for "specific date" view.
if (strlen($moment) > 0 && $moment !== 'all') { if (strlen($moment) > 0 && $moment !== 'all') {
$start = new Carbon($moment); $start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range); $end = Navigation::endOfPeriod($start, $range);
$subTitle = $account->name . ' (' . strval( $subTitle = trans(
trans( 'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'firefly.from_to_breadcrumb', 'end' => $end->formatLocalized($this->monthAndDayFormat)]
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] );
)
) . ')';
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]); $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
$periods = $this->periodEntries($account); $periods = $this->getPeriodOverview($account);
} }
// prep for current period // prep for current period
if (strlen($moment) === 0) { if (strlen($moment) === 0) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$periods = $this->periodEntries($account); $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);
} }
$accountType = $account->accountType->type;
$count = 0; $count = 0;
$loop = 0; $loop = 0;
// grab journals, but be prepared to jump a period back to get the right ones: // grab journals, but be prepared to jump a period back to get the right ones:
@@ -289,7 +306,7 @@ class AccountController extends Controller
$collector->setRange($start, $end); $collector->setRange($start, $end);
} }
$journals = $collector->getPaginatedJournals(); $journals = $collector->getPaginatedJournals();
$journals->setPath('accounts/show/' . $account->id); $journals->setPath('accounts/show/' . $account->id . '/' . $moment);
$count = $journals->getCollection()->count(); $count = $journals->getCollection()->count();
if ($count === 0) { if ($count === 0) {
$start->subDay(); $start->subDay();
@@ -299,8 +316,18 @@ class AccountController extends Controller
} }
} }
if ($moment != 'all' && $loop > 1) {
$subTitle = trans(
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
}
return view('accounts.show', compact('account', 'accountType', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
return view(
'accounts.show',
compact('account', 'currency', 'moment', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')
);
} }
/** /**
@@ -314,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();
@@ -388,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);
@@ -412,20 +435,36 @@ class AccountController extends Controller
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
// 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);
} }
@@ -453,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

@@ -176,10 +176,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

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'));
} }

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

@@ -22,11 +22,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;
@@ -188,30 +192,82 @@ class BudgetController extends Controller
/** /**
* @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'));
} }
/** /**
@@ -255,7 +311,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'));
} }
@@ -315,10 +371,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'));
@@ -339,10 +396,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'));
@@ -405,7 +463,6 @@ class BudgetController extends Controller
return $return; return $return;
} }
/** /**
* @param Budget $budget * @param Budget $budget
* @param Carbon $start * @param Carbon $start
@@ -442,4 +499,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

@@ -18,13 +18,17 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
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 +154,170 @@ 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->disableInternalFilter();
$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()->disableInternalFilter();
$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 +333,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 +360,97 @@ 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()->disableInternalFilter();
$count = $collector->getJournals()->count();
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::TRANSFER])->disableInternalFilter();
$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);
@@ -348,7 +483,25 @@ 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])->disableInternalFilter();
$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

@@ -26,6 +26,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 +62,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()); // @codeCoverageIgnore
} }
Log::debug('Regenerate chart.account.all from scratch.');
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
@@ -338,19 +336,13 @@ class AccountController extends Controller
/** /**
* @param Account $account * @param Account $account
* @param string $date * @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();
@@ -500,16 +492,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(); // @codeCoverageIgnore
} }
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

@@ -18,11 +18,14 @@ 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;
@@ -153,6 +156,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.
@@ -288,6 +429,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 +477,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.

View File

@@ -16,7 +16,7 @@ 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\Generator\Report\Support;
use FireflyIII\Helpers\Chart\MetaPieChartInterface; use FireflyIII\Helpers\Chart\MetaPieChartInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
@@ -24,7 +24,6 @@ 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 +40,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 +55,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);
} }
@@ -133,8 +129,6 @@ class BudgetReportController extends Controller
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore 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 +157,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) {
@@ -243,7 +237,7 @@ class BudgetReportController extends Controller
->setBudgets($budgets)->withOpposingAccount()->disableFilter(); ->setBudgets($budgets)->withOpposingAccount()->disableFilter();
$accountIds = $accounts->pluck('id')->toArray(); $accountIds = $accounts->pluck('id')->toArray();
$transactions = $collector->getJournals(); $transactions = $collector->getJournals();
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds); $set = Support::filterExpenses($transactions, $accountIds);
return $set; return $set;
} }

View File

@@ -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);
} }
@@ -105,21 +113,6 @@ class CategoryController extends Controller
} }
/**
* @param CategoryRepositoryInterface $repository
* @param Category $category
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function currentPeriod(CategoryRepositoryInterface $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 CategoryRepositoryInterface $repository * @param CategoryRepositoryInterface $repository
* @param AccountRepositoryInterface $accountRepository * @param AccountRepositoryInterface $accountRepository
@@ -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);
@@ -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);
@@ -264,12 +275,11 @@ class CategoryController extends Controller
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function specificPeriod(CategoryRepositoryInterface $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);
@@ -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

@@ -23,8 +23,6 @@ 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 +39,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 +51,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 +70,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);

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;
@@ -103,8 +104,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()); // @codeCoverageIgnore //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 = [
@@ -163,6 +165,8 @@ class ReportController extends Controller
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore 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 +250,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(); // @codeCoverageIgnore // 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

@@ -233,7 +233,7 @@ class TagReportController extends Controller
} }
} }
if (count($newSet) === 0) { if (count($newSet) === 0) {
$newSet = $chartData; $newSet = $chartData; // @codeCoverageIgnore
} }
$data = $this->generator->multiSet($newSet); $data = $this->generator->multiSet($newSet);
$cache->store($data); $cache->store($data);

View File

@@ -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);
} }
@@ -52,10 +60,16 @@ class CurrencyController extends Controller
/** /**
* @param Request $request * @param Request $request
* *
* @return View * @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');
@@ -93,14 +107,21 @@ class CurrencyController extends Controller
/** /**
* @param Request $request * @param Request $request
* @param CurrencyRepositoryInterface $repository
* @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'));
@@ -119,20 +140,27 @@ class CurrencyController extends Controller
/** /**
* @param Request $request * @param Request $request
* @param CurrencyRepositoryInterface $repository
* @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'));
@@ -142,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);
@@ -164,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'));
@@ -212,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

@@ -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;

View File

@@ -20,6 +20,7 @@ use FireflyIII\Import\Setup\SetupInterface;
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;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse; use Illuminate\Http\Response as LaravelResponse;
use Log; use Log;
@@ -87,8 +88,6 @@ class ImportController extends Controller
{ {
Log::debug('Now at start of configure()'); Log::debug('Now at start of configure()');
if (!$this->jobInCorrectStep($job, 'configure')) { if (!$this->jobInCorrectStep($job, 'configure')) {
Log::debug('Job is not in correct state for configure()', ['status' => $job->status]);
return $this->redirectToCorrectStep($job); return $this->redirectToCorrectStep($job);
} }
@@ -100,8 +99,6 @@ class ImportController extends Controller
$subTitleIcon = 'fa-wrench'; $subTitleIcon = 'fa-wrench';
return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon')); return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon'));
} }
/** /**
@@ -145,8 +142,6 @@ class ImportController extends Controller
public function finished(ImportJob $job) public function finished(ImportJob $job)
{ {
if (!$this->jobInCorrectStep($job, 'finished')) { if (!$this->jobInCorrectStep($job, 'finished')) {
Log::debug('Job is not in correct state for finished()', ['status' => $job->status]);
return $this->redirectToCorrectStep($job); return $this->redirectToCorrectStep($job);
} }
@@ -225,12 +220,12 @@ class ImportController extends Controller
* Step 4. Save the configuration. * Step 4. Save the configuration.
* *
* @param Request $request * @param Request $request
* @param ImportJobRepositoryInterface $repository
* @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, ImportJobRepositoryInterface $repository, ImportJob $job)
{ {
Log::debug('Now in postConfigure()', ['job' => $job->key]); Log::debug('Now in postConfigure()', ['job' => $job->key]);
if (!$this->jobInCorrectStep($job, 'process')) { if (!$this->jobInCorrectStep($job, 'process')) {
@@ -245,8 +240,7 @@ class ImportController extends Controller
$importer->saveImportConfiguration($data, $files); $importer->saveImportConfiguration($data, $files);
// update job: // update job:
$job->status = 'import_configuration_saved'; $repository->updateStatus($job, 'import_configuration_saved');
$job->save();
// return redirect to settings. // return redirect to settings.
// this could loop until the user is done. // this could loop until the user is done.
@@ -280,17 +274,15 @@ class ImportController extends Controller
* Step 5. Depending on the importer, this will show the user settings to * Step 5. Depending on the importer, this will show the user settings to
* fill in. * fill in.
* *
* @param ImportJobRepositoryInterface $repository
* @param ImportJob $job * @param ImportJob $job
* *
* @return View * @return View
* @throws FireflyException
*/ */
public function settings(ImportJob $job) public function settings(ImportJobRepositoryInterface $repository, ImportJob $job)
{ {
Log::debug('Now in settings()', ['job' => $job->key]); Log::debug('Now in settings()', ['job' => $job->key]);
if (!$this->jobInCorrectStep($job, 'settings')) { if (!$this->jobInCorrectStep($job, 'settings')) {
Log::debug('Job should not be in settings()');
return $this->redirectToCorrectStep($job); return $this->redirectToCorrectStep($job);
} }
Log::debug('Continue in settings()'); Log::debug('Continue in settings()');
@@ -308,8 +300,7 @@ class ImportController extends Controller
} }
Log::debug('Job does NOT require user config.'); Log::debug('Job does NOT require user config.');
$job->status = 'settings_complete'; $repository->updateStatus($job, 'settings_complete');
$job->save();
// if no more settings, save job and continue to process thing. // if no more settings, save job and continue to process thing.
return redirect(route('import.complete', [$job->key])); return redirect(route('import.complete', [$job->key]));
@@ -350,15 +341,17 @@ class ImportController extends Controller
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. * This is step 2. It creates an Import Job. Stores the import.
* *
* @param ImportUploadRequest $request * @param ImportUploadRequest $request
* @param ImportJobRepositoryInterface $repository * @param ImportJobRepositoryInterface $repository
* @param UserRepositoryInterface $userRepository
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/ */
public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository) public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository, UserRepositoryInterface $userRepository)
{ {
Log::debug('Now in upload()'); Log::debug('Now in upload()');
// create import job: // create import job:
@@ -375,7 +368,7 @@ class ImportController extends Controller
$disk = Storage::disk('upload'); $disk = Storage::disk('upload');
// user is demo user, replace upload with prepared file. // user is demo user, replace upload with prepared file.
if (auth()->user()->hasRole('demo')) { if ($userRepository->hasRole(auth()->user(), 'demo')) {
$stubsDisk = Storage::disk('stubs'); $stubsDisk = Storage::disk('stubs');
$content = $stubsDisk->get('demo-import.csv'); $content = $stubsDisk->get('demo-import.csv');
$contentEncrypted = Crypt::encrypt($content); $contentEncrypted = Crypt::encrypt($content);
@@ -384,14 +377,13 @@ class ImportController extends Controller
// also set up prepared configuration. // also set up prepared configuration.
$configuration = json_decode($stubsDisk->get('demo-configuration.json'), true); $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true);
$job->configuration = $configuration; $repository->setConfiguration($job, $configuration);
$job->save();
Log::debug('Set configuration for demo user', $configuration); Log::debug('Set configuration for demo user', $configuration);
// also flash info // also flash info
Session::flash('info', trans('demo.import-configure-security')); Session::flash('info', trans('demo.import-configure-security'));
} }
if (!auth()->user()->hasRole('demo')) { if (!$userRepository->hasRole(auth()->user(), 'demo')) {
// user is not demo, process original upload: // user is not demo, process original upload:
$disk->put($newName, $contentEncrypted); $disk->put($newName, $contentEncrypted);
Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]); Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]);
@@ -411,18 +403,15 @@ class ImportController extends Controller
$configRaw = $configFileObject->fread($configFileObject->getSize()); $configRaw = $configFileObject->fread($configFileObject->getSize());
$configuration = json_decode($configRaw, true); $configuration = json_decode($configRaw, true);
// @codeCoverageIgnoreStart
if (!is_null($configuration) && is_array($configuration)) { if (!is_null($configuration) && is_array($configuration)) {
Log::debug('Found configuration', $configuration); Log::debug('Found configuration', $configuration);
$job->configuration = $configuration; $repository->setConfiguration($job, $configuration);
$job->save();
} }
// @codeCoverageIgnoreEnd
} }
// if user is demo user, replace config with prepared config:
return redirect(route('import.configure', [$job->key])); return redirect(route('import.configure', [$job->key]));
} }
/** /**
@@ -440,6 +429,8 @@ class ImportController extends Controller
return $job->status === 'import_status_never_started'; return $job->status === 'import_status_never_started';
case 'settings': case 'settings':
case 'store-settings': case 'store-settings':
Log::debug(sprintf('Job %d with key %s has status %s', $job->id, $job->key, $job->status));
return $job->status === 'import_configuration_saved'; return $job->status === 'import_configuration_saved';
case 'finished': case 'finished':
return $job->status === 'import_complete'; return $job->status === 'import_complete';
@@ -449,7 +440,7 @@ class ImportController extends Controller
return ($job->status === 'settings_complete') || ($job->status === 'import_running'); return ($job->status === 'settings_complete') || ($job->status === 'import_running');
} }
return false; return false; // @codeCoverageIgnore
} }
@@ -475,7 +466,7 @@ class ImportController extends Controller
return $importer; return $importer;
} }
throw new FireflyException(sprintf('"%s" is not a valid file type', $type)); throw new FireflyException(sprintf('"%s" is not a valid file type', $type)); // @codeCoverageIgnore
} }
@@ -507,7 +498,7 @@ class ImportController extends Controller
return redirect(route('import.finished', [$job->key])); return redirect(route('import.finished', [$job->key]));
} }
throw new FireflyException('Cannot redirect for job state ' . $job->status); throw new FireflyException('Cannot redirect for job state ' . $job->status); // @codeCoverageIgnore
} }
} }

View File

@@ -15,6 +15,7 @@ use Amount;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -29,9 +30,11 @@ use Session;
*/ */
class JavascriptController extends Controller class JavascriptController extends Controller
{ {
/** /**
* @param AccountRepositoryInterface $repository
* @param CurrencyRepositoryInterface $currencyRepository
* *
* @return $this
*/ */
public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository) public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository)
{ {
@@ -47,7 +50,7 @@ class JavascriptController extends Controller
$accountId = $account->id; $accountId = $account->id;
$currency = intval($account->getMeta('currency_id')); $currency = intval($account->getMeta('currency_id'));
$currency = $currency === 0 ? $default->id : $currency; $currency = $currency === 0 ? $default->id : $currency;
$entry = ['preferredCurrency' => $currency]; $entry = ['preferredCurrency' => $currency, 'name' => $account->name];
$data['accounts'][$accountId] = $entry; $data['accounts'][$accountId] = $entry;
} }
@@ -57,6 +60,27 @@ class JavascriptController extends Controller
->header('Content-Type', 'text/javascript'); ->header('Content-Type', 'text/javascript');
} }
/**
* @param CurrencyRepositoryInterface $repository
*
* @return $this
*/
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 * @param Request $request
* *
@@ -128,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,66 @@
<?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);
$amount = null;
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,6 +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\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
@@ -144,7 +146,7 @@ class JsonController extends Controller
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* *
*/ */
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());
@@ -157,9 +159,15 @@ class JsonController extends Controller
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore 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 */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
$cache->store($data); $cache->store($data);
@@ -172,7 +180,7 @@ class JsonController extends Controller
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
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());
@@ -186,9 +194,13 @@ class JsonController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore 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)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
$cache->store($data); $cache->store($data);
@@ -287,7 +299,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 = [];

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,32 +277,16 @@ 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));
/** @var Carbon $date */
$date = session('end', Carbon::now()->endOfMonth());
$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) { if ($repository->canAddAmount($piggyBank, $amount)) {
$repetition = $piggyBank->currentRelevantRep(); $repository->addAmount($piggyBank, $amount);
$currentAmount = $repetition->currentamount ?? '0'; Session::flash('success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name])));
$repetition->currentamount = bcadd($currentAmount, $amount);
$repetition->save();
// create event
$repository->createEvent($piggyBank, $amount);
Session::flash(
'success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($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::format($amount, false), 'name' => e($piggyBank->name)])));
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
@@ -312,26 +301,19 @@ 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');
if ($repository->canRemoveAmount($piggyBank, $amount)) {
$savedSoFar = $piggyBank->currentRelevantRep()->currentamount; $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::format($amount, false), 'name' => $piggyBank->name]))
); );
Preferences::mark(); Preferences::mark();
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
} }
$amount = strval(round($request->get('amount'), 12));
Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))); Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
@@ -391,9 +373,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 +399,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 = $transaction->transactionJournal->sourceAccountList()->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 = $transaction->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;

View File

@@ -10,11 +10,13 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use FireflyIII\Http\Requests\TokenFormRequest; use FireflyIII\Http\Requests\TokenFormRequest;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use PragmaRX\Google2FA\Contracts\Google2FA; use PragmaRX\Google2FA\Contracts\Google2FA;
use Preferences; use Preferences;
@@ -55,8 +57,11 @@ class PreferencesController extends Controller
public function code(Google2FA $google2fa) public function code(Google2FA $google2fa)
{ {
$domain = $this->getDomain(); $domain = $this->getDomain();
$secretKey = 'FIREFLYIII';
$secretKey = str_pad($secretKey, intval(pow(2, ceil(log(strlen($secretKey), 2)))), 'X');
/** @noinspection PhpMethodParametersCountMismatchInspection */ /** @noinspection PhpMethodParametersCountMismatchInspection */
$secret = $google2fa->generateSecretKey(32, auth()->user()->id); $secret = $google2fa->generateSecretKey(16, $secretKey);
Session::flash('two-factor-secret', $secret); Session::flash('two-factor-secret', $secret);
$image = $google2fa->getQRCodeInline('Firefly III at ' . $domain, auth()->user()->email, $secret, 150); $image = $google2fa->getQRCodeInline('Firefly III at ' . $domain, auth()->user()->email, $secret, 150);
@@ -129,9 +134,11 @@ class PreferencesController extends Controller
/** /**
* @param Request $request * @param Request $request
* *
* @param UserRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/ */
public function postIndex(Request $request) public function postIndex(Request $request, UserRepositoryInterface $repository)
{ {
// front page accounts // front page accounts
$frontPageAccounts = []; $frontPageAccounts = [];
@@ -160,16 +167,15 @@ class PreferencesController extends Controller
Preferences::set('showDepositsFrontpage', $showDepositsFrontpage); Preferences::set('showDepositsFrontpage', $showDepositsFrontpage);
// save page size: // save page size:
Preferences::set('transactionPageSize', 50);
$transactionPageSize = intval($request->get('transactionPageSize')); $transactionPageSize = intval($request->get('transactionPageSize'));
if ($transactionPageSize > 0 && $transactionPageSize < 1337) { if ($transactionPageSize > 0 && $transactionPageSize < 1337) {
Preferences::set('transactionPageSize', $transactionPageSize); Preferences::set('transactionPageSize', $transactionPageSize);
} else {
Preferences::set('transactionPageSize', 50);
} }
$twoFactorAuthEnabled = false; $twoFactorAuthEnabled = false;
$hasTwoFactorAuthSecret = false; $hasTwoFactorAuthSecret = false;
if (!auth()->user()->hasRole('demo')) { if (!$repository->hasRole(auth()->user(), 'demo')) {
// two factor auth // two factor auth
$twoFactorAuthEnabled = intval($request->get('twoFactorAuthEnabled')); $twoFactorAuthEnabled = intval($request->get('twoFactorAuthEnabled'));
$hasTwoFactorAuthSecret = !is_null(Preferences::get('twoFactorAuthSecret')); $hasTwoFactorAuthSecret = !is_null(Preferences::get('twoFactorAuthSecret'));

View File

@@ -14,9 +14,11 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use FireflyIII\Exceptions\ValidationException; use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Http\Middleware\IsLimitedUser;
use FireflyIII\Http\Requests\DeleteAccountFormRequest; use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest; use FireflyIII\Http\Requests\ProfileFormRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Hash; use Hash;
use Log; use Log;
use Session; use Session;
@@ -45,6 +47,8 @@ class ProfileController extends Controller
return $next($request); return $next($request);
} }
); );
$this->middleware(IsLimitedUser::class);
} }
/** /**
@@ -52,16 +56,6 @@ class ProfileController extends Controller
*/ */
public function changePassword() public function changePassword()
{ {
if (intval(getenv('SANDSTORM')) === 1) {
return view('error')->with('message', strval(trans('firefly.sandstorm_not_available')));
}
if (auth()->user()->hasRole('demo')) {
Session::flash('info', strval(trans('firefly.cannot_change_demo')));
return redirect(route('profile.index'));
}
$title = auth()->user()->email; $title = auth()->user()->email;
$subTitle = strval(trans('firefly.change_your_password')); $subTitle = strval(trans('firefly.change_your_password'));
$subTitleIcon = 'fa-key'; $subTitleIcon = 'fa-key';
@@ -74,16 +68,6 @@ class ProfileController extends Controller
*/ */
public function deleteAccount() public function deleteAccount()
{ {
if (intval(getenv('SANDSTORM')) === 1) {
return view('error')->with('message', strval(trans('firefly.sandstorm_not_available')));
}
if (auth()->user()->hasRole('demo')) {
Session::flash('info', strval(trans('firefly.cannot_delete_demo')));
return redirect(route('profile.index'));
}
$title = auth()->user()->email; $title = auth()->user()->email;
$subTitle = strval(trans('firefly.delete_account')); $subTitle = strval(trans('firefly.delete_account'));
$subTitleIcon = 'fa-trash'; $subTitleIcon = 'fa-trash';
@@ -111,32 +95,18 @@ class ProfileController extends Controller
*/ */
public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository) public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository)
{ {
if (intval(getenv('SANDSTORM')) === 1) { // the request has already validated both new passwords must be equal.
return view('error')->with('message', strval(trans('firefly.sandstorm_not_available'))); $current = $request->get('current_password');
} $new = $request->get('new_password');
if (auth()->user()->hasRole('demo')) {
Session::flash('info', strval(trans('firefly.cannot_change_demo')));
return redirect(route('profile.index'));
}
// old, new1, new2
if (!Hash::check($request->get('current_password'), auth()->user()->password)) {
Session::flash('error', strval(trans('firefly.invalid_current_password')));
return redirect(route('profile.change-password'));
}
try { try {
$this->validatePassword($request->get('current_password'), $request->get('new_password')); $this->validatePassword(auth()->user(), $current, $new);
} catch (ValidationException $e) { } catch (ValidationException $e) {
Session::flash('error', $e->getMessage()); Session::flash('error', $e->getMessage());
return redirect(route('profile.change-password')); return redirect(route('profile.change-password'));
} }
// update the user with the new password.
$repository->changePassword(auth()->user(), $request->get('new_password')); $repository->changePassword(auth()->user(), $request->get('new_password'));
Session::flash('success', strval(trans('firefly.password_changed'))); Session::flash('success', strval(trans('firefly.password_changed')));
@@ -151,17 +121,6 @@ class ProfileController extends Controller
*/ */
public function postDeleteAccount(UserRepositoryInterface $repository, DeleteAccountFormRequest $request) public function postDeleteAccount(UserRepositoryInterface $repository, DeleteAccountFormRequest $request)
{ {
if (intval(getenv('SANDSTORM')) === 1) {
return view('error')->with('message', strval(trans('firefly.sandstorm_not_available')));
}
if (auth()->user()->hasRole('demo')) {
Session::flash('info', strval(trans('firefly.cannot_delete_demo')));
return redirect(route('profile.index'));
}
// old, new1, new2
if (!Hash::check($request->get('password'), auth()->user()->password)) { if (!Hash::check($request->get('password'), auth()->user()->password)) {
Session::flash('error', strval(trans('firefly.invalid_password'))); Session::flash('error', strval(trans('firefly.invalid_password')));
@@ -181,16 +140,22 @@ class ProfileController extends Controller
return redirect(route('index')); return redirect(route('index'));
} }
/** /**
* @param string $old * @param User $user
* @param string $current
* @param string $new * @param string $new
* *
* @return bool * @return bool
* @throws ValidationException * @throws ValidationException
*/ */
protected function validatePassword(string $old, string $new): bool protected function validatePassword(User $user, string $current, string $new): bool
{ {
if ($new === $old) { if (!Hash::check($current, $user->password)) {
throw new ValidationException(strval(trans('firefly.invalid_current_password')));
}
if ($current === $new) {
throw new ValidationException(strval(trans('firefly.should_change'))); throw new ValidationException(strval(trans('firefly.should_change')));
} }

View File

@@ -20,7 +20,6 @@ use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Navigation; use Navigation;
/** /**
@@ -45,8 +44,6 @@ class CategoryController extends Controller
$cache->addProperty('category-period-expenses-report'); $cache->addProperty('category-period-expenses-report');
$cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) { if ($cache->has()) {
Log::debug('Return report from cache');
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
/** @var CategoryRepositoryInterface $repository */ /** @var CategoryRepositoryInterface $repository */
@@ -79,8 +76,6 @@ class CategoryController extends Controller
$cache->addProperty('category-period-income-report'); $cache->addProperty('category-period-income-report');
$cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) { if ($cache->has()) {
Log::debug('Return report from cache');
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
/** @var CategoryRepositoryInterface $repository */ /** @var CategoryRepositoryInterface $repository */

View File

@@ -19,6 +19,7 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -31,13 +32,14 @@ class OperationsController extends Controller
{ {
/** /**
* @param AccountTaskerInterface $tasker
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return mixed|string * @return mixed|string
*/ */
public function expenses(Collection $accounts, Carbon $start, Carbon $end) public function expenses(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
{ {
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties; $cache = new CacheProperties;
@@ -48,7 +50,7 @@ class OperationsController extends Controller
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$entries = $this->getExpenseReport($start, $end, $accounts); $entries = $tasker->getExpenseReport($start, $end, $accounts);
$type = 'expense-entry'; $type = 'expense-entry';
$result = view('reports.partials.income-expenses', compact('entries', 'type'))->render(); $result = view('reports.partials.income-expenses', compact('entries', 'type'))->render();
$cache->store($result); $cache->store($result);
@@ -58,13 +60,14 @@ class OperationsController extends Controller
} }
/** /**
* @param AccountTaskerInterface $tasker
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return string * @return string
*/ */
public function income(Collection $accounts, Carbon $start, Carbon $end) public function income(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
{ {
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties; $cache = new CacheProperties;
@@ -75,7 +78,7 @@ class OperationsController extends Controller
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$entries = $this->getIncomeReport($start, $end, $accounts); $entries = $tasker->getIncomeReport($start, $end, $accounts);
$type = 'income-entry'; $type = 'income-entry';
$result = view('reports.partials.income-expenses', compact('entries', 'type'))->render(); $result = view('reports.partials.income-expenses', compact('entries', 'type'))->render();
@@ -86,13 +89,14 @@ class OperationsController extends Controller
} }
/** /**
* @param AccountTaskerInterface $tasker
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return mixed|string * @return mixed|string
*/ */
public function operations(Collection $accounts, Carbon $start, Carbon $end) public function operations(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
{ {
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties; $cache = new CacheProperties;
@@ -104,8 +108,8 @@ class OperationsController extends Controller
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
$incomes = $this->getIncomeReport($start, $end, $accounts); $incomes = $tasker->getIncomeReport($start, $end, $accounts);
$expenses = $this->getExpenseReport($start, $end, $accounts); $expenses = $tasker->getExpenseReport($start, $end, $accounts);
$incomeSum = array_sum( $incomeSum = array_sum(
array_map( array_map(
function ($item) { function ($item) {
@@ -129,125 +133,4 @@ class OperationsController extends Controller
} }
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
private function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array
{
// get all expenses for the given accounts in the given period!
// also transfers!
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->withOpposingAccount()
->enableInternalFilter();
$transactions = $collector->getJournals();
$transactions = $transactions->filter(
function (Transaction $transaction) {
// return negative amounts only.
if (bccomp($transaction->transaction_amount, '0') === -1) {
return $transaction;
}
return false;
}
);
$expenses = $this->groupByOpposing($transactions);
// sort the result
// Obtain a list of columns
$sum = [];
foreach ($expenses as $accountId => $row) {
$sum[$accountId] = floatval($row['sum']);
}
array_multisort($sum, SORT_ASC, $expenses);
return $expenses;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
private function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array
{
// get all expenses for the given accounts in the given period!
// also transfers!
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->withOpposingAccount()
->enableInternalFilter();
$transactions = $collector->getJournals();
$transactions = $transactions->filter(
function (Transaction $transaction) {
// return positive amounts only.
if (bccomp($transaction->transaction_amount, '0') === 1) {
return $transaction;
}
return false;
}
);
$income = $this->groupByOpposing($transactions);
// sort the result
// Obtain a list of columns
$sum = [];
foreach ($income as $accountId => $row) {
$sum[$accountId] = floatval($row['sum']);
}
array_multisort($sum, SORT_DESC, $income);
return $income;
}
/**
* @param Collection $transactions
*
* @return array
*/
private function groupByOpposing(Collection $transactions): array
{
$expenses = [];
// join the result together:
foreach ($transactions as $transaction) {
$opposingId = $transaction->opposing_account_id;
$name = $transaction->opposing_account_name;
if (!isset($expenses[$opposingId])) {
$expenses[$opposingId] = [
'id' => $opposingId,
'name' => $name,
'sum' => '0',
'average' => '0',
'count' => 0,
];
}
$expenses[$opposingId]['sum'] = bcadd($expenses[$opposingId]['sum'], $transaction->transaction_amount);
$expenses[$opposingId]['count']++;
}
// do averages:
foreach ($expenses as $key => $entry) {
if ($expenses[$key]['count'] > 1) {
$expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count']));
}
}
return $expenses;
}
} }

View File

@@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Report\ReportGeneratorFactory; use FireflyIII\Generator\Report\ReportGeneratorFactory;
use FireflyIII\Helpers\Report\ReportHelperInterface; use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Requests\ReportFormRequest; use FireflyIII\Http\Requests\ReportFormRequest;
@@ -26,6 +25,7 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Preferences; use Preferences;
use Response; use Response;
use Session; use Session;
@@ -48,6 +48,7 @@ class ReportController extends Controller
{ {
parent::__construct(); parent::__construct();
$this->helper = app(ReportHelperInterface::class);
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
@@ -55,8 +56,6 @@ class ReportController extends Controller
View::share('mainTitleIcon', 'fa-line-chart'); View::share('mainTitleIcon', 'fa-line-chart');
View::share('subTitleIcon', 'fa-calendar'); View::share('subTitleIcon', 'fa-calendar');
$this->helper = app(ReportHelperInterface::class);
return $next($request); return $next($request);
} }
); );
@@ -73,7 +72,7 @@ class ReportController extends Controller
public function auditReport(Collection $accounts, Carbon $start, Carbon $end) public function auditReport(Collection $accounts, Carbon $start, Carbon $end)
{ {
if ($end < $start) { if ($end < $start) {
return view('error')->with('message', trans('firefly.end_after_start_date')); return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore
} }
if ($start < session('first')) { if ($start < session('first')) {
$start = session('first'); $start = session('first');
@@ -109,7 +108,7 @@ class ReportController extends Controller
public function budgetReport(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end) public function budgetReport(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end)
{ {
if ($end < $start) { if ($end < $start) {
return view('error')->with('message', trans('firefly.end_after_start_date')); return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore
} }
if ($start < session('first')) { if ($start < session('first')) {
$start = session('first'); $start = session('first');
@@ -145,7 +144,7 @@ class ReportController extends Controller
public function categoryReport(Collection $accounts, Collection $categories, Carbon $start, Carbon $end) public function categoryReport(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
{ {
if ($end < $start) { if ($end < $start) {
return view('error')->with('message', trans('firefly.end_after_start_date')); return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore
} }
if ($start < session('first')) { if ($start < session('first')) {
$start = session('first'); $start = session('first');
@@ -251,10 +250,9 @@ class ReportController extends Controller
/** /**
* @param ReportFormRequest $request * @param ReportFormRequest $request
* *
* @return RedirectResponse * @return RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/ */
public function postIndex(ReportFormRequest $request): RedirectResponse public function postIndex(ReportFormRequest $request)
{ {
// report type: // report type:
$reportType = $request->get('report_type'); $reportType = $request->get('report_type');
@@ -264,8 +262,10 @@ class ReportController extends Controller
$categories = join(',', $request->getCategoryList()->pluck('id')->toArray()); $categories = join(',', $request->getCategoryList()->pluck('id')->toArray());
$budgets = join(',', $request->getBudgetList()->pluck('id')->toArray()); $budgets = join(',', $request->getBudgetList()->pluck('id')->toArray());
$tags = join(',', $request->getTagList()->pluck('tag')->toArray()); $tags = join(',', $request->getTagList()->pluck('tag')->toArray());
$uri = route('reports.index');
if ($request->getAccountList()->count() === 0) { if ($request->getAccountList()->count() === 0) {
Log::debug('Account count is zero');
Session::flash('error', trans('firefly.select_more_than_one_account')); Session::flash('error', trans('firefly.select_more_than_one_account'));
return redirect(route('reports.index')); return redirect(route('reports.index'));
@@ -293,14 +293,7 @@ class ReportController extends Controller
return view('error')->with('message', trans('firefly.end_after_start_date')); return view('error')->with('message', trans('firefly.end_after_start_date'));
} }
// lower threshold
if ($start < session('first')) {
$start = session('first');
}
switch ($reportType) { switch ($reportType) {
default:
throw new FireflyException(sprintf('Firefly does not support the "%s"-report yet.', $reportType));
case 'category': case 'category':
$uri = route('reports.report.category', [$accounts, $categories, $start, $end]); $uri = route('reports.report.category', [$accounts, $categories, $start, $end]);
break; break;
@@ -332,7 +325,7 @@ class ReportController extends Controller
public function tagReport(Collection $accounts, Collection $tags, Carbon $start, Carbon $end) public function tagReport(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
{ {
if ($end < $start) { if ($end < $start) {
return view('error')->with('message', trans('firefly.end_after_start_date')); return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore
} }
if ($start < session('first')) { if ($start < session('first')) {
$start = session('first'); $start = session('first');

View File

@@ -255,10 +255,11 @@ class RuleController 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
Session::put('rules.create.fromStore', true); Session::put('rules.create.fromStore', true);
return redirect(route('rules.create', [$ruleGroup]))->withInput(); return redirect(route('rules.create', [$ruleGroup]))->withInput();
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('rules.create.uri')); return redirect($this->getPreviousUri('rules.create.uri'));
@@ -340,10 +341,11 @@ class RuleController 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
Session::put('rules.edit.fromUpdate', true); Session::put('rules.edit.fromUpdate', true);
return redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]); return redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('rules.edit.uri')); return redirect($this->getPreviousUri('rules.edit.uri'));
@@ -473,7 +475,7 @@ class RuleController extends Controller
$actions[] = view( $actions[] = view(
'rules.partials.action', 'rules.partials.action',
[ [
'oldTrigger' => $entry, 'oldAction' => $entry,
'oldValue' => $request->old('rule-action-value')[$index], 'oldValue' => $request->old('rule-action-value')[$index],
'oldChecked' => $checked, 'oldChecked' => $checked,
'count' => $count, 'count' => $count,

View File

@@ -217,10 +217,11 @@ class RuleGroupController 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
Session::put('rule-groups.create.fromStore', true); Session::put('rule-groups.create.fromStore', true);
return redirect(route('rule-groups.create'))->withInput(); return redirect(route('rule-groups.create'))->withInput();
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('rule-groups.create.uri')); return redirect($this->getPreviousUri('rule-groups.create.uri'));
@@ -261,10 +262,11 @@ class RuleGroupController 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
Session::put('rule-groups.edit.fromUpdate', true); Session::put('rule-groups.edit.fromUpdate', true);
return redirect(route('rule-groups.edit', [$ruleGroup->id]))->withInput(['return_to_edit' => 1]); return redirect(route('rule-groups.edit', [$ruleGroup->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
} }
// redirect to previous URL. // redirect to previous URL.

View File

@@ -14,15 +14,14 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use Exception;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\TagFormRequest; use FireflyIII\Http\Requests\TagFormRequest;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;
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 Session; use Session;
@@ -143,9 +142,11 @@ class TagController extends Controller
/** /**
* @param Tag $tag * @param Tag $tag
* *
* @param TagRepositoryInterface $repository
*
* @return View * @return View
*/ */
public function edit(Tag $tag) public function edit(Tag $tag, TagRepositoryInterface $repository)
{ {
$subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]); $subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]);
$subTitleIcon = 'fa-tag'; $subTitleIcon = 'fa-tag';
@@ -159,8 +160,8 @@ class TagController extends Controller
/* /*
* Can this tag become another type? * Can this tag become another type?
*/ */
$allowAdvance = $tag->tagAllowAdvance(); $allowAdvance = $repository->tagAllowAdvance($tag);
$allowToBalancingAct = $tag->tagAllowBalancing(); $allowToBalancingAct = $repository->tagAllowBalancing($tag);
// edit tag options: // edit tag options:
if ($allowAdvance === false) { if ($allowAdvance === false) {
@@ -223,100 +224,91 @@ class TagController extends Controller
/** /**
* @param Request $request * @param Request $request
* @param JournalCollectorInterface $collector * @param TagRepositoryInterface $repository
* @param Tag $tag * @param Tag $tag
* @param string $moment
* *
* @return View * @return View
*/ */
public function show(Request $request, JournalCollectorInterface $collector, Tag $tag) public function show(Request $request, TagRepositoryInterface $repository, Tag $tag, string $moment = '')
{ {
$start = clone session('start', Carbon::now()->startOfMonth()); // default values:
$end = clone session('end', Carbon::now()->endOfMonth());
$subTitle = $tag->tag; $subTitle = $tag->tag;
$subTitleIcon = 'fa-tag'; $subTitleIcon = 'fa-tag';
$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);
$periods = $this->getPeriodOverview($tag); $count = 0;
$loop = 0;
// use collector:
$collector->setAllAssetAccounts()
->setLimit($pageSize)->setPage($page)->setTag($tag)->withOpposingAccount()->disableInternalFilter()
->withBudgetInformation()->withCategoryInformation()->setRange($start, $end);
$journals = $collector->getPaginatedJournals();
$journals->setPath('tags/show/' . $tag->id);
$sum = $journals->sum(
function (Transaction $transaction) {
return $transaction->transaction_amount;
}
);
return view('tags.show', compact('tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end'));
}
/**
* @param Request $request
* @param JournalCollectorInterface $collector
* @param Tag $tag
*
* @return View
*/
public function showAll(Request $request, JournalCollectorInterface $collector, Tag $tag)
{
$subTitle = $tag->tag;
$subTitleIcon = 'fa-tag';
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->setTag($tag)
->withOpposingAccount()->disableInternalFilter()
->withBudgetInformation()->withCategoryInformation();
$journals = $collector->getPaginatedJournals();
$journals->setPath('tags/show/' . $tag->id . '/all');
$sum = $journals->sum(
function (Transaction $transaction) {
return $transaction->transaction_amount;
}
);
return view('tags.show', compact('tag', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end'));
}
public function showByDate(Request $request, JournalCollectorInterface $collector, Tag $tag, string $date)
{
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$start = null;
$end = null;
$periods = new Collection;
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
$sum = '0';
try {
$start = new Carbon($date); // prep for "all" view.
$end = Navigation::endOfPeriod($start, $range); if ($moment === 'all') {
} catch (Exception $e) { $subTitle = trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]);
$start = Navigation::startOfPeriod($this->repository->firstUseDate($tag), $range); $start = $repository->firstUseDate($tag);
$end = Navigation::startOfPeriod($this->repository->lastUseDate($tag), $range); $end = new Carbon;
$sum = $repository->sumOfTag($tag);
} }
$subTitle = $tag->tag; // prep for "specific date" view.
$subTitleIcon = 'fa-tag'; if (strlen($moment) > 0 && $moment !== 'all') {
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $start = new Carbon($moment);
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $end = Navigation::endOfPeriod($start, $range);
$subTitle = trans(
'firefly.journals_in_period_for_tag',
['tag' => $tag->tag,
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview($tag); $periods = $this->getPeriodOverview($tag);
$sum = $repository->sumOfTag($tag, $start, $end);
}
// use collector: // prep for current period
$collector->setAllAssetAccounts() if (strlen($moment) === 0) {
->setLimit($pageSize)->setPage($page)->setTag($tag)->withOpposingAccount()->disableInternalFilter() $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
->withBudgetInformation()->withCategoryInformation()->setRange($start, $end); $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($tag);
$subTitle = trans(
'firefly.journals_in_period_for_tag',
['tag' => $tag->tag, '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 tag 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)->withOpposingAccount()
->setTag($tag)->withBudgetInformation()->withCategoryInformation();
$journals = $collector->getPaginatedJournals(); $journals = $collector->getPaginatedJournals();
$journals->setPath('tags/show/' . $tag->id); $journals->setPath('tags/show/' . $tag->id);
$count = $journals->getCollection()->count();
$sum = $journals->sum( if ($count === 0) {
function (Transaction $transaction) { $start->subDay();
return $transaction->transaction_amount; $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.journals_in_period_for_tag',
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
); );
return view('tags.show', compact('tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end'));
} }
return view('tags.show', compact('apiKey', 'tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
}
/** /**
* @param TagFormRequest $request * @param TagFormRequest $request
* *
@@ -331,10 +323,11 @@ class TagController 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
Session::put('tags.create.fromStore', true); Session::put('tags.create.fromStore', true);
return redirect(route('tags.create'))->withInput(); return redirect(route('tags.create'))->withInput();
// @codeCoverageIgnoreEnd
} }
return redirect($this->getPreviousUri('tags.create.uri')); return redirect($this->getPreviousUri('tags.create.uri'));
@@ -356,10 +349,11 @@ class TagController 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
Session::put('tags.edit.fromUpdate', true); Session::put('tags.edit.fromUpdate', true);
return redirect(route('tags.edit', [$tag->id]))->withInput(['return_to_edit' => 1]); return redirect(route('tags.edit', [$tag->id]))->withInput(['return_to_edit' => 1]);
// @codeCoverageIgnoreEnd
} }
// redirect to previous URL. // redirect to previous URL.
@@ -396,9 +390,9 @@ class TagController extends Controller
// get expenses and what-not in this period and this tag. // get expenses and what-not in this period and this tag.
$arr = [ $arr = [
'date_string' => $end->format('Y-m-d'), 'string' => $end->format('Y-m-d'),
'date_name' => Navigation::periodShow($end, $range), 'name' => Navigation::periodShow($end, $range),
'date' => $end, 'date' => clone $end,
'spent' => $this->repository->spentInperiod($tag, $end, $currentEnd), 'spent' => $this->repository->spentInperiod($tag, $end, $currentEnd),
'earned' => $this->repository->earnedInperiod($tag, $end, $currentEnd), 'earned' => $this->repository->earnedInperiod($tag, $end, $currentEnd),
]; ];

View File

@@ -173,7 +173,6 @@ class ConvertController extends Controller
$joined = $sourceType->type . '-' . $destinationType->type; $joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) { switch ($joined) {
default: default:
throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one
$destination = $sourceAccount; $destination = $sourceAccount;

View File

@@ -14,13 +14,13 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers\Transaction; namespace FireflyIII\Http\Controllers\Transaction;
use Carbon\Carbon; use Carbon\Carbon;
use ExpandedForm;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassDeleteJournalRequest;
use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Preferences; use Preferences;
@@ -119,7 +119,12 @@ class MassController extends Controller
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$accountList = ExpandedForm::makeSelectList($repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// get budgets
/** @var BudgetRepositoryInterface $budgetRepository */
$budgetRepository = app(BudgetRepositoryInterface::class);
$budgets = $budgetRepository->getBudgets();
// skip transactions that have multiple destinations // skip transactions that have multiple destinations
// or multiple sources: // or multiple sources:
@@ -177,7 +182,7 @@ class MassController extends Controller
$journals = $filtered; $journals = $filtered;
return view('transactions.mass.edit', compact('journals', 'subTitle', 'accountList')); return view('transactions.mass.edit', compact('journals', 'subTitle', 'accounts', 'budgets'));
} }
/** /**
@@ -200,7 +205,7 @@ class MassController extends Controller
$sourceAccountName = $request->get('source_account_name')[$journal->id] ?? ''; $sourceAccountName = $request->get('source_account_name')[$journal->id] ?? '';
$destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0; $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0;
$destAccountName = $request->get('destination_account_name')[$journal->id] ?? ''; $destAccountName = $request->get('destination_account_name')[$journal->id] ?? '';
$budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0; $budgetId = $request->get('budget_id')[$journal->id] ?? 0;
$category = $request->get('category')[$journal->id]; $category = $request->get('category')[$journal->id];
$tags = $journal->tags->pluck('tag')->toArray(); $tags = $journal->tags->pluck('tag')->toArray();
@@ -214,12 +219,12 @@ class MassController extends Controller
'destination_account_id' => intval($destAccountId), 'destination_account_id' => intval($destAccountId),
'destination_account_name' => $destAccountName, 'destination_account_name' => $destAccountName,
'amount' => round($request->get('amount')[$journal->id], 12), 'amount' => round($request->get('amount')[$journal->id], 12),
'currency_id' => intval($request->get('amount_currency_id_amount_' . $journal->id)), 'currency_id' => $journal->transaction_currency_id,
'date' => new Carbon($request->get('date')[$journal->id]), 'date' => new Carbon($request->get('date')[$journal->id]),
'interest_date' => $journal->interest_date, 'interest_date' => $journal->interest_date,
'book_date' => $journal->book_date, 'book_date' => $journal->book_date,
'process_date' => $journal->process_date, 'process_date' => $journal->process_date,
'budget_id' => $budgetId, 'budget_id' => intval($budgetId),
'category' => $category, 'category' => $category,
'tags' => $tags, 'tags' => $tags,

View File

@@ -25,6 +25,7 @@ use FireflyIII\Models\TransactionJournal;
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\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use Log; use Log;
@@ -48,7 +49,8 @@ class SingleController extends Controller
/** @var BudgetRepositoryInterface */ /** @var BudgetRepositoryInterface */
private $budgets; private $budgets;
/** @var CurrencyRepositoryInterface */
private $currency;
/** @var PiggyBankRepositoryInterface */ /** @var PiggyBankRepositoryInterface */
private $piggyBanks; private $piggyBanks;
@@ -71,6 +73,7 @@ class SingleController extends Controller
$this->budgets = app(BudgetRepositoryInterface::class); $this->budgets = app(BudgetRepositoryInterface::class);
$this->piggyBanks = app(PiggyBankRepositoryInterface::class); $this->piggyBanks = app(PiggyBankRepositoryInterface::class);
$this->attachments = app(AttachmentHelperInterface::class); $this->attachments = app(AttachmentHelperInterface::class);
$this->currency = app(CurrencyRepositoryInterface::class);
View::share('title', trans('firefly.transactions')); View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat'); View::share('mainTitleIcon', 'fa-repeat');
@@ -231,7 +234,6 @@ class SingleController extends Controller
// view related code // view related code
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
// journal related code // journal related code
$sourceAccounts = $journal->sourceAccountList(); $sourceAccounts = $journal->sourceAccountList();
$destinationAccounts = $journal->destinationAccountList(); $destinationAccounts = $journal->destinationAccountList();
@@ -249,6 +251,7 @@ class SingleController extends Controller
'destination_account_id' => $destinationAccounts->first()->id, 'destination_account_id' => $destinationAccounts->first()->id,
'destination_account_name' => $destinationAccounts->first()->edit_name, 'destination_account_name' => $destinationAccounts->first()->edit_name,
'amount' => $journal->amountPositive(), 'amount' => $journal->amountPositive(),
'currency' => $journal->transactionCurrency,
// new custom fields: // new custom fields:
'due_date' => $journal->dateAsString('due_date'), 'due_date' => $journal->dateAsString('due_date'),
@@ -256,8 +259,22 @@ class SingleController extends Controller
'invoice_date' => $journal->dateAsString('invoice_date'), 'invoice_date' => $journal->dateAsString('invoice_date'),
'interal_reference' => $journal->getMeta('internal_reference'), 'interal_reference' => $journal->getMeta('internal_reference'),
'notes' => $journal->getMeta('notes'), 'notes' => $journal->getMeta('notes'),
// exchange rate fields
'native_amount' => $journal->amountPositive(),
'native_currency' => $journal->transactionCurrency,
]; ];
// if user has entered a foreign currency, update some fields
$foreignCurrencyId = intval($journal->getMeta('foreign_currency_id'));
if ($foreignCurrencyId > 0) {
// update some fields in pre-filled.
// @codeCoverageIgnoreStart
$preFilled['amount'] = $journal->getMeta('foreign_amount');
$preFilled['currency'] = $this->currency->find(intval($journal->getMeta('foreign_currency_id')));
// @codeCoverageIgnoreEnd
}
if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) {
$preFilled['destination_account_name'] = ''; $preFilled['destination_account_name'] = '';
} }

View File

@@ -14,16 +14,20 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers; namespace FireflyIII\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
use Navigation; use Navigation;
use Preferences; use Preferences;
use Response; use Response;
use Steam;
use View; use View;
/** /**
@@ -57,111 +61,81 @@ class TransactionController extends Controller
* @param JournalRepositoryInterface $repository * @param JournalRepositoryInterface $repository
* @param string $what * @param string $what
* *
* @param string $moment
*
* @return View * @return View
*/ */
public function index(Request $request, JournalRepositoryInterface $repository, string $what) public function index(Request $request, JournalRepositoryInterface $repository, string $what, string $moment = '')
{ {
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data); // default values:
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
$types = config('firefly.transactionTypesByWhat.' . $what); $types = config('firefly.transactionTypesByWhat.' . $what);
$subTitle = trans('firefly.title_' . $what); $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;
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); $start = null;
// to make sure we only grab a subset, based on the current date (in session): $end = null;
$start = session('start', Navigation::startOfPeriod(new Carbon, $range)); $periods = new Collection;
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
/** @var JournalCollectorInterface $collector */ // prep for "all" view.
$collector = app(JournalCollectorInterface::class); if ($moment === 'all') {
$collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->withBudgetInformation() $subTitle = trans('firefly.all_' . $what);
->withCategoryInformation();
$collector->withOpposingAccount();
$collector->disableInternalFilter();
$journals = $collector->getPaginatedJournals();
$journals->setPath('transactions/' . $what);
unset($start, $end);
// then also show a list of periods where the user can click on, based on the
// user's range and the oldest journal the user has:
$first = $repository->first(); $first = $repository->first();
$blockStart = is_null($first->id) ? new Carbon : $first->date; $start = $first->date ?? new Carbon;
$blockStart = Navigation::startOfPeriod($blockStart, $range); $end = new Carbon;
$blockEnd = Navigation::endOfX(new Carbon, $range);
$entries = new Collection;
while ($blockEnd >= $blockStart) {
Log::debug(sprintf('Now at blockEnd: %s', $blockEnd->format('Y-m-d')));
$blockEnd = Navigation::startOfPeriod($blockEnd, $range);
$dateStr = $blockEnd->format('Y-m-d');
$dateName = Navigation::periodShow($blockEnd, $range);
$entries->push([$dateStr, $dateName]);
$blockEnd = Navigation::subtractPeriod($blockEnd, $range, 1);
} }
return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'entries')); // prep for "specific date" view.
if (strlen($moment) > 0 && $moment !== 'all') {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$subTitle = trans(
'firefly.title_' . $what . '_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview($what);
} }
/** // prep for current period
* @param Request $request if (strlen($moment) === 0) {
* @param string $what $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
* $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
* @return View $periods = $this->getPeriodOverview($what);
*/ $subTitle = trans(
public function indexAll(Request $request, string $what) 'firefly.title_' . $what . '_between',
{ ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data); );
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); }
$types = config('firefly.transactionTypesByWhat.' . $what); // grab journals, but be prepared to jump a period back to get the right ones:
$subTitle = sprintf('%s (%s)', trans('firefly.title_' . $what), strtolower(trans('firefly.everything'))); Log::info('Now at transaction loop start.');
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); 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->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->withBudgetInformation()->withCategoryInformation(); $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
$collector->withOpposingAccount(); ->disableInternalFilter();
$collector->disableInternalFilter();
$journals = $collector->getPaginatedJournals(); $journals = $collector->getPaginatedJournals();
$journals->setPath('transactions/' . $what . '/all'); $journals->setPath('/budgets/list/no-budget');
$count = $journals->getCollection()->count();
return view('transactions.index-all', compact('subTitle', 'what', 'subTitleIcon', 'journals')); 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 string $what 'firefly.title_' . $what . '_between',
* ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
* @param string $date );
* }
* @return View
*/
public function indexByDate(Request $request, string $what, string $date)
{
$carbon = new Carbon($date);
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($carbon, $range);
$end = Navigation::endOfPeriod($carbon, $range);
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
$types = config('firefly.transactionTypesByWhat.' . $what);
$subTitle = trans('firefly.title_' . $what) . ' (' . Navigation::periodShow($carbon, $range) . ')';
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
Log::debug(sprintf('Transaction index by date will show between %s and %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'periods', 'start', 'end', 'moment'));
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts();
$collector->setRange($start, $end)->withBudgetInformation()->withCategoryInformation();
$collector->withOpposingAccount();
$collector->disableInternalFilter();
$journals = $collector->getPaginatedJournals();
$journals->setPath('transactions/' . $what . '/' . $date);
return view('transactions.index-date', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'carbon'));
} }
@@ -180,10 +154,9 @@ class TransactionController extends Controller
$ids = array_unique($ids); $ids = array_unique($ids);
foreach ($ids as $id) { foreach ($ids as $id) {
$journal = $repository->find(intval($id)); $journal = $repository->find(intval($id));
if ($journal && $journal->date->format('Y-m-d') == $date->format('Y-m-d')) { if ($journal && $journal->date->isSameDay($date)) {
$journal->order = $order; $repository->setOrder($journal, $order);
$order++; $order++;
$journal->save();
} }
} }
} }
@@ -209,10 +182,94 @@ class TransactionController extends Controller
$transactions = $tasker->getTransactionsOverview($journal); $transactions = $tasker->getTransactionsOverview($journal);
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
$foreignCurrency = null;
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions')); if ($journal->hasMeta('foreign_currency_id')) {
// @codeCoverageIgnoreStart
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$foreignCurrency = $repository->find(intval($journal->getMeta('foreign_currency_id')));
// @codeCoverageIgnoreEnd
}
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'foreignCurrency'));
} }
/**
* @param string $what
*
* @return Collection
* @throws FireflyException
*/
private function getPeriodOverview(string $what): 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;
$types = config('firefly.transactionTypesByWhat.' . $what);
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($what);
$cache->addProperty('transaction-list-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 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)->withOpposingAccount()->setTypes($types)->disableInternalFilter();
$set = $collector->getJournals();
$sum = $set->sum('transaction_amount');
$journals = $set->count();
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$array = [
'string' => $dateStr,
'name' => $dateName,
'count' => $journals,
'spent' => 0,
'earned' => 0,
'transferred' => 0,
'date' => clone $end,
];
Log::debug(sprintf('What is %s', $what));
switch ($what) {
case 'withdrawal':
$array['spent'] = $sum;
break;
case 'deposit':
$array['earned'] = $sum;
break;
case 'transfers':
case 'transfer':
$array['transferred'] = Steam::positive($sum);
break;
}
$entries->push($array);
$end = Navigation::subtractPeriod($end, $range, 1);
}
Log::debug('End of loop');
$cache->store($entries);
return $entries;
}
} }

View File

@@ -0,0 +1,61 @@
<?php
/**
* IsLimitedUser.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\Middleware;
use Closure;
use FireflyIII\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Session;
/**
* Class IsAdmin
*
* @package FireflyIII\Http\Middleware
*/
class IsLimitedUser
{
/**
* Handle an incoming request. May not be a limited user (ie. Sandstorm env. or demo user).
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
*
* @return mixed
*/
public function handle(Request $request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
return redirect()->guest('login');
}
/** @var User $user */
$user = auth()->user();
if ($user->hasRole('demo')) {
Session::flash('warning', strval(trans('firefly.not_available_demo_user')));
return redirect(route('index'));
}
if (intval(getenv('SANDSTORM')) === 1) {
Session::flash('warning', strval(trans('firefly.sandstorm_not_available')));
return redirect(route('index'));
}
return $next($request);
}
}

View File

@@ -43,14 +43,12 @@ class AccountFormRequest extends Request
'accountType' => $this->string('what'), 'accountType' => $this->string('what'),
'currency_id' => $this->integer('currency_id'), 'currency_id' => $this->integer('currency_id'),
'virtualBalance' => $this->float('virtualBalance'), 'virtualBalance' => $this->float('virtualBalance'),
'virtualBalanceCurrency' => $this->integer('amount_currency_id_virtualBalance'),
'iban' => $this->string('iban'), 'iban' => $this->string('iban'),
'BIC' => $this->string('BIC'), 'BIC' => $this->string('BIC'),
'accountNumber' => $this->string('accountNumber'), 'accountNumber' => $this->string('accountNumber'),
'accountRole' => $this->string('accountRole'), 'accountRole' => $this->string('accountRole'),
'openingBalance' => $this->float('openingBalance'), 'openingBalance' => $this->float('openingBalance'),
'openingBalanceDate' => $this->date('openingBalanceDate'), 'openingBalanceDate' => $this->date('openingBalanceDate'),
'openingBalanceCurrency' => $this->integer('amount_currency_id_openingBalance'),
'ccType' => $this->string('ccType'), 'ccType' => $this->string('ccType'),
'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'), 'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'),
]; ];

View File

@@ -67,6 +67,11 @@ class JournalFormRequest extends Request
'destination_account_name' => $this->string('destination_account_name'), 'destination_account_name' => $this->string('destination_account_name'),
'piggy_bank_id' => $this->integer('piggy_bank_id'), 'piggy_bank_id' => $this->integer('piggy_bank_id'),
// native amount and stuff like that:
'native_amount' => $this->float('native_amount'),
'source_amount' => $this->float('source_amount'),
'destination_amount' => $this->float('destination_amount'),
]; ];
return $data; return $data;
@@ -101,6 +106,11 @@ class JournalFormRequest extends Request
'destination_account_id' => 'numeric|belongsToUser:accounts,id', 'destination_account_id' => 'numeric|belongsToUser:accounts,id',
'destination_account_name' => 'between:1,255', 'destination_account_name' => 'between:1,255',
'piggy_bank_id' => 'between:1,255', 'piggy_bank_id' => 'between:1,255',
// foreign currency amounts
'native_amount' => 'numeric|more:0',
'source_amount' => 'numeric|more:0',
'destination_amount' => 'numeric|more:0',
]; ];
// some rules get an upgrade depending on the type of data: // some rules get an upgrade depending on the type of data:

View File

@@ -10,6 +10,7 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Http\Requests; namespace FireflyIII\Http\Requests;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;

View File

@@ -37,7 +37,7 @@ class UserFormRequest extends Request
{ {
return [ return [
'email' => $this->string('email'), 'email' => $this->string('email'),
'blocked' => $this->integer('blocked'), 'blocked' => $this->integer('blocked') === 1,
'blocked_code' => $this->string('blocked_code'), 'blocked_code' => $this->string('blocked_code'),
'password' => $this->string('password'), 'password' => $this->string('password'),
]; ];
@@ -50,7 +50,7 @@ class UserFormRequest extends Request
{ {
return [ return [
'id' => 'required|exists:users,id', 'id' => 'required|exists:users,id',
'email' => 'required', 'email' => 'email|required',
'password' => 'confirmed', 'password' => 'confirmed',
'blocked_code' => 'between:0,30', 'blocked_code' => 'between:0,30',
'blocked' => 'between:0,1|numeric', 'blocked' => 'between:0,1|numeric',

View File

@@ -67,38 +67,31 @@ Breadcrumbs::register(
); );
Breadcrumbs::register( Breadcrumbs::register(
'accounts.show', function (BreadCrumbGenerator $breadcrumbs, Account $account) { 'accounts.show', function (BreadCrumbGenerator $breadcrumbs, Account $account, string $moment, Carbon $start, Carbon $end) {
$what = config('firefly.shortNamesByFullName.' . $account->accountType->type); $what = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$breadcrumbs->parent('accounts.index', $what); $breadcrumbs->parent('accounts.index', $what);
$breadcrumbs->push($account->name, route('accounts.show', [$account->id])); $breadcrumbs->push($account->name, route('accounts.show', [$account->id]));
// push when is all:
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('accounts.show', [$account->id, 'all']));
} }
// when is specific period or when empty:
if ($moment !== 'all') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
); );
$breadcrumbs->push($title, route('accounts.show', [$account->id, $moment, $start, $end]));
Breadcrumbs::register(
'accounts.show.date', function (BreadCrumbGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) {
$title = '';
$route = '';
if (!is_null($start) && !is_null($end)) {
$startString = $start->formatLocalized(strval(trans('config.month_and_day')));
$endString = $end->formatLocalized(strval(trans('config.month_and_day')));
$title = sprintf('%s (%s)', $account->name, trans('firefly.from_to_breadcrumb', ['start' => $startString, 'end' => $endString]));
$route = route('accounts.show.date', [$account->id, $start->format('Y-m-d')]);
}
if (is_null($start) && is_null($end)) {
$title = $title = $account->name . ' (' . strtolower(strval(trans('firefly.everything'))) . ')';
$route = route('accounts.show.date', [$account->id, 'all']);
} }
$breadcrumbs->parent('accounts.show', $account);
$breadcrumbs->push($title, $route);
} }
); );
Breadcrumbs::register( Breadcrumbs::register(
'accounts.delete', function (BreadCrumbGenerator $breadcrumbs, Account $account) { 'accounts.delete', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
$breadcrumbs->parent('accounts.show', $account); $breadcrumbs->parent('accounts.show', $account, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.delete_account', ['name' => e($account->name)]), route('accounts.delete', [$account->id])); $breadcrumbs->push(trans('firefly.delete_account', ['name' => e($account->name)]), route('accounts.delete', [$account->id]));
} }
); );
@@ -106,7 +99,7 @@ Breadcrumbs::register(
Breadcrumbs::register( Breadcrumbs::register(
'accounts.edit', function (BreadCrumbGenerator $breadcrumbs, Account $account) { 'accounts.edit', function (BreadCrumbGenerator $breadcrumbs, Account $account) {
$breadcrumbs->parent('accounts.show', $account); $breadcrumbs->parent('accounts.show', $account, '', new Carbon, new Carbon);
$what = config('firefly.shortNamesByFullName.' . $account->accountType->type); $what = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => e($account->name)]), route('accounts.edit', [$account->id])); $breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => e($account->name)]), route('accounts.edit', [$account->id]));
@@ -256,9 +249,24 @@ Breadcrumbs::register(
); );
Breadcrumbs::register( Breadcrumbs::register(
'budgets.no-budget', function (BreadCrumbGenerator $breadcrumbs, $subTitle) { 'budgets.no-budget', function (BreadCrumbGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) {
$breadcrumbs->parent('budgets.index'); $breadcrumbs->parent('budgets.index');
$breadcrumbs->push($subTitle, route('budgets.no-budget')); $breadcrumbs->push(trans('firefly.journals_without_budget'), route('budgets.no-budget'));
// push when is all:
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget', ['all']));
}
// when is specific period:
if ($moment !== 'all') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
);
$breadcrumbs->push($title, route('budgets.no-budget', [$moment]));
}
} }
); );
@@ -266,6 +274,7 @@ Breadcrumbs::register(
'budgets.show', function (BreadCrumbGenerator $breadcrumbs, Budget $budget) { 'budgets.show', function (BreadCrumbGenerator $breadcrumbs, Budget $budget) {
$breadcrumbs->parent('budgets.index'); $breadcrumbs->parent('budgets.index');
$breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id])); $breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id]));
$breadcrumbs->push(trans('firefly.everything'), route('budgets.show', [$budget->id]));
} }
); );
@@ -275,11 +284,8 @@ Breadcrumbs::register(
$breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id])); $breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id]));
$title = trans( $title = trans(
'firefly.budget_in_period_breadcrumb', [ 'firefly.between_dates_breadcrumb', ['start' => $budgetLimit->start_date->formatLocalized(strval(trans('config.month_and_day'))),
'name' => $budget->name, 'end' => $budgetLimit->end_date->formatLocalized(strval(trans('config.month_and_day'))),]
'start' => $budgetLimit->start_date->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $budgetLimit->end_date->formatLocalized(strval(trans('config.month_and_day'))),
]
); );
$breadcrumbs->push( $breadcrumbs->push(
@@ -306,52 +312,61 @@ Breadcrumbs::register(
Breadcrumbs::register( Breadcrumbs::register(
'categories.edit', function (BreadCrumbGenerator $breadcrumbs, Category $category) { 'categories.edit', function (BreadCrumbGenerator $breadcrumbs, Category $category) {
$breadcrumbs->parent('categories.show', $category); $breadcrumbs->parent('categories.show', $category, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.edit_category', ['name' => e($category->name)]), route('categories.edit', [$category->id])); $breadcrumbs->push(trans('firefly.edit_category', ['name' => e($category->name)]), route('categories.edit', [$category->id]));
} }
); );
Breadcrumbs::register( Breadcrumbs::register(
'categories.delete', function (BreadCrumbGenerator $breadcrumbs, Category $category) { 'categories.delete', function (BreadCrumbGenerator $breadcrumbs, Category $category) {
$breadcrumbs->parent('categories.show', $category); $breadcrumbs->parent('categories.show', $category, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.delete_category', ['name' => e($category->name)]), route('categories.delete', [$category->id])); $breadcrumbs->push(trans('firefly.delete_category', ['name' => e($category->name)]), route('categories.delete', [$category->id]));
} }
); );
Breadcrumbs::register( Breadcrumbs::register(
'categories.show', function (BreadCrumbGenerator $breadcrumbs, Category $category) { 'categories.show', function (BreadCrumbGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) {
$breadcrumbs->parent('categories.index');
$breadcrumbs->push(e($category->name), route('categories.show', [$category->id]));
$breadcrumbs->parent('categories.index');
$breadcrumbs->push($category->name, route('categories.show', [$category->id]));
// push when is all:
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all']));
}
// when is specific period:
if ($moment !== 'all') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
);
$breadcrumbs->push($title, route('categories.show', [$category->id, $moment]));
}
} }
); );
Breadcrumbs::register( Breadcrumbs::register(
'categories.show.all', function (BreadCrumbGenerator $breadcrumbs, Category $category) { 'categories.no-category', function (BreadCrumbGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) {
$breadcrumbs->parent('categories.index'); $breadcrumbs->parent('categories.index');
$breadcrumbs->push(e($category->name) . '(' . strtolower(trans('firefly.all_periods')) . ')', route('categories.show.all', [$category->id])); $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category'));
// push when is all:
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('categories.no-category', ['all']));
}
// when is specific period:
if ($moment !== 'all') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
);
$breadcrumbs->push($title, route('categories.no-category', [$moment]));
}
} }
); );
Breadcrumbs::register(
'categories.show.date', function (BreadCrumbGenerator $breadcrumbs, Category $category, Carbon $date) {
// get current period preference.
$range = Preferences::get('viewRange', '1M')->data;
$breadcrumbs->parent('categories.index');
$breadcrumbs->push(e($category->name), route('categories.show', [$category->id]));
$breadcrumbs->push(Navigation::periodShow($date, $range), route('categories.show.date', [$category->id, $date->format('Y-m-d')]));
}
);
Breadcrumbs::register(
'categories.no-category', function (BreadCrumbGenerator $breadcrumbs, $subTitle) {
$breadcrumbs->parent('categories.index');
$breadcrumbs->push($subTitle, route('categories.no-category'));
}
);
/** /**
* CURRENCIES * CURRENCIES
@@ -695,23 +710,33 @@ Breadcrumbs::register(
Breadcrumbs::register( Breadcrumbs::register(
'tags.edit', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) { 'tags.edit', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) {
$breadcrumbs->parent('tags.show', $tag); $breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => e($tag->tag)]), route('tags.edit', [$tag->id])); $breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => e($tag->tag)]), route('tags.edit', [$tag->id]));
} }
); );
Breadcrumbs::register( Breadcrumbs::register(
'tags.delete', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) { 'tags.delete', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) {
$breadcrumbs->parent('tags.show', $tag); $breadcrumbs->parent('tags.show', $tag, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => e($tag->tag)]), route('tags.delete', [$tag->id])); $breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => e($tag->tag)]), route('tags.delete', [$tag->id]));
} }
); );
Breadcrumbs::register( Breadcrumbs::register(
'tags.show', function (BreadCrumbGenerator $breadcrumbs, Tag $tag) { 'tags.show', function (BreadCrumbGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) {
$breadcrumbs->parent('tags.index'); $breadcrumbs->parent('tags.index');
$breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id])); $breadcrumbs->push(e($tag->tag), route('tags.show', [$tag->id], $moment));
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('tags.show', [$tag->id], $moment));
}
if ($moment !== 'all') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
);
$breadcrumbs->push($title, route('tags.show', [$tag->id], $moment));
}
} }
); );
@@ -719,36 +744,30 @@ Breadcrumbs::register(
* TRANSACTIONS * TRANSACTIONS
*/ */
Breadcrumbs::register( Breadcrumbs::register(
'transactions.index', function (BreadCrumbGenerator $breadcrumbs, string $what) { 'transactions.index', function (BreadCrumbGenerator $breadcrumbs, string $what, string $moment = '', Carbon $start, Carbon $end) {
$breadcrumbs->parent('home'); $breadcrumbs->parent('home');
$breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what]));
if ($moment === 'all') {
$breadcrumbs->push(trans('firefly.everything'), route('transactions.index', [$what, 'all']));
} }
// when is specific period:
if ($moment !== 'all') {
$title = trans(
'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))),
'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]
); );
$breadcrumbs->push($title, route('transactions.index', [$what, $moment]));
Breadcrumbs::register(
'transactions.index.all', function (BreadCrumbGenerator $breadcrumbs, string $what) {
$breadcrumbs->parent('transactions.index', $what);
$title = sprintf('%s (%s)', trans('breadcrumbs.' . $what . '_list'), strtolower(trans('firefly.everything')));
$breadcrumbs->push($title, route('transactions.index.all', [$what]));
} }
);
Breadcrumbs::register(
'transactions.index.date', function (BreadCrumbGenerator $breadcrumbs, string $what, Carbon $date) {
$breadcrumbs->parent('transactions.index', $what);
$range = Preferences::get('viewRange', '1M')->data;
$title = trans('breadcrumbs.' . $what . '_list') . ' (' . Navigation::periodShow($date, $range) . ')';
$breadcrumbs->push($title, route('transactions.index.date', [$what, $date->format('Y-m-d')]));
} }
); );
Breadcrumbs::register( Breadcrumbs::register(
'transactions.create', function (BreadCrumbGenerator $breadcrumbs, string $what) { 'transactions.create', function (BreadCrumbGenerator $breadcrumbs, string $what) {
$breadcrumbs->parent('transactions.index', $what); $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what]));
} }
); );
@@ -770,7 +789,7 @@ Breadcrumbs::register(
'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { 'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
$what = strtolower($journal->transactionType->type); $what = strtolower($journal->transactionType->type);
$breadcrumbs->parent('transactions.index', $what); $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
$breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id]));
} }
); );
@@ -795,8 +814,9 @@ Breadcrumbs::register(
if ($journals->count() > 0) { if ($journals->count() > 0) {
$journalIds = $journals->pluck('id')->toArray(); $journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type); $what = strtolower($journals->first()->transactionType->type);
$breadcrumbs->parent('transactions.index', $what); $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds)); $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds));
return; return;
} }
@@ -809,7 +829,7 @@ Breadcrumbs::register(
$journalIds = $journals->pluck('id')->toArray(); $journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type); $what = strtolower($journals->first()->transactionType->type);
$breadcrumbs->parent('transactions.index', $what); $breadcrumbs->parent('transactions.index', $what, '', new Carbon, new Carbon);
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds)); $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds));
} }
); );

View File

@@ -211,6 +211,26 @@ class Account extends Model
return $value; return $value;
} }
/**
* Returns the opening balance
*
* @return TransactionJournal
* @throws FireflyException
*/
public function getOpeningBalance(): TransactionJournal
{
$journal = TransactionJournal::sortCorrectly()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $this->id)
->transactionTypes([TransactionType::OPENING_BALANCE])
->first(['transaction_journals.*']);
if (is_null($journal)) {
return new TransactionJournal;
}
return $journal;
}
/** /**
* Returns the amount of the opening balance for this account. * Returns the amount of the opening balance for this account.
* *

View File

@@ -48,6 +48,7 @@ class AccountType extends Model
protected $dates = ['created_at', 'updated_at']; protected $dates = ['created_at', 'updated_at'];
// //
/** /**
* @return HasMany * @return HasMany
*/ */

View File

@@ -0,0 +1,53 @@
<?php
/**
* CurrencyExchange.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\Models;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Class CurrencyExchange
*
* @package FireflyIII\Models
*/
class CurrencyExchangeRate extends Model
{
protected $dates = ['created_at', 'updated_at', 'date'];
/**
* @return BelongsTo
*/
public function fromCurrency(): BelongsTo
{
return $this->belongsTo(TransactionCurrency::class, 'from_currency_id');
}
/**
* @return BelongsTo
*/
public function toCurrency(): BelongsTo
{
return $this->belongsTo(TransactionCurrency::class, 'to_currency_id');
}
/**
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@@ -16,6 +16,7 @@ namespace FireflyIII\Models;
use Carbon\Carbon; use Carbon\Carbon;
use Crypt; use Crypt;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Steam; use Steam;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -67,7 +68,7 @@ class PiggyBank extends Model
/** /**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public function account() public function account(): BelongsTo
{ {
return $this->belongsTo('FireflyIII\Models\Account'); return $this->belongsTo('FireflyIII\Models\Account');
} }

View File

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

View File

@@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Crypt; use Crypt;
use FireflyIII\Support\Models\TagTrait;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -27,7 +26,7 @@ use Watson\Validating\ValidatingTrait;
*/ */
class Tag extends Model class Tag extends Model
{ {
use ValidatingTrait, SoftDeletes, TagTrait; use ValidatingTrait, SoftDeletes;
/** /**
* The attributes that should be casted to native types. * The attributes that should be casted to native types.

View File

@@ -29,6 +29,8 @@ use FireflyIII\Helpers\Report\BalanceReportHelper;
use FireflyIII\Helpers\Report\BalanceReportHelperInterface; use FireflyIII\Helpers\Report\BalanceReportHelperInterface;
use FireflyIII\Helpers\Report\BudgetReportHelper; use FireflyIII\Helpers\Report\BudgetReportHelper;
use FireflyIII\Helpers\Report\BudgetReportHelperInterface; use FireflyIII\Helpers\Report\BudgetReportHelperInterface;
use FireflyIII\Helpers\Report\PopupReport;
use FireflyIII\Helpers\Report\PopupReportInterface;
use FireflyIII\Helpers\Report\ReportHelper; use FireflyIII\Helpers\Report\ReportHelper;
use FireflyIII\Helpers\Report\ReportHelperInterface; use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Import\ImportProcedure; use FireflyIII\Import\ImportProcedure;
@@ -41,6 +43,7 @@ use FireflyIII\Support\FireflyConfig;
use FireflyIII\Support\Navigation; use FireflyIII\Support\Navigation;
use FireflyIII\Support\Preferences; use FireflyIII\Support\Preferences;
use FireflyIII\Support\Steam; use FireflyIII\Support\Steam;
use FireflyIII\Support\Twig\Account;
use FireflyIII\Support\Twig\General; use FireflyIII\Support\Twig\General;
use FireflyIII\Support\Twig\Journal; use FireflyIII\Support\Twig\Journal;
use FireflyIII\Support\Twig\PiggyBank; use FireflyIII\Support\Twig\PiggyBank;
@@ -75,6 +78,7 @@ class FireflyServiceProvider extends ServiceProvider
Twig::addExtension(new Translation); Twig::addExtension(new Translation);
Twig::addExtension(new Transaction); Twig::addExtension(new Transaction);
Twig::addExtension(new Rule); Twig::addExtension(new Rule);
Twig::addExtension(new Account);
} }
/** /**
@@ -127,6 +131,8 @@ class FireflyServiceProvider extends ServiceProvider
$this->app->bind(UserRepositoryInterface::class, UserRepository::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
$this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class); $this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class);
// more generators:
$this->app->bind(PopupReportInterface::class, PopupReport::class);
$this->app->bind(HelpInterface::class, Help::class); $this->app->bind(HelpInterface::class, Help::class);
$this->app->bind(ReportHelperInterface::class, ReportHelper::class); $this->app->bind(ReportHelperInterface::class, ReportHelper::class);
$this->app->bind(FiscalHelperInterface::class, FiscalHelper::class); $this->app->bind(FiscalHelperInterface::class, FiscalHelper::class);

View File

@@ -455,6 +455,7 @@ class AccountRepository implements AccountRepositoryInterface
{ {
$amount = $data['openingBalance']; $amount = $data['openingBalance'];
$name = $data['name']; $name = $data['name'];
$currencyId = $data['currency_id'];
$opposing = $this->storeOpposingAccount($name); $opposing = $this->storeOpposingAccount($name);
$transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first(); $transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
@@ -462,7 +463,7 @@ class AccountRepository implements AccountRepositoryInterface
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id, 'transaction_type_id' => $transactionType->id,
'transaction_currency_id' => $data['openingBalanceCurrency'], 'transaction_currency_id' => $currencyId,
'description' => 'Initial balance for "' . $account->name . '"', 'description' => 'Initial balance for "' . $account->name . '"',
'completed' => true, 'completed' => true,
'date' => $data['openingBalanceDate'], 'date' => $data['openingBalanceDate'],
@@ -530,12 +531,8 @@ class AccountRepository implements AccountRepositoryInterface
} }
// opening balance data? update it! // opening balance data? update it!
if (!is_null($openingBalance->id)) { if (!is_null($openingBalance->id)) {
$date = $data['openingBalanceDate'];
$amount = $data['openingBalance'];
Log::debug('Opening balance journal found, update journal.'); Log::debug('Opening balance journal found, update journal.');
$this->updateOpeningBalanceJournal($account, $openingBalance, $data);
$this->updateOpeningBalanceJournal($account, $openingBalance, $date, $amount);
return true; return true;
} }
@@ -589,15 +586,19 @@ class AccountRepository implements AccountRepositoryInterface
/** /**
* @param Account $account * @param Account $account
* @param TransactionJournal $journal * @param TransactionJournal $journal
* @param Carbon $date * @param array $data
* @param float $amount
* *
* @return bool * @return bool
*/ */
protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, Carbon $date, float $amount): bool protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, array $data): bool
{ {
$date = $data['openingBalanceDate'];
$amount = $data['openingBalance'];
$currencyId = intval($data['currency_id']);
// update date: // update date:
$journal->date = $date; $journal->date = $date;
$journal->transaction_currency_id = $currencyId;
$journal->save(); $journal->save();
// update transactions: // update transactions:
/** @var Transaction $transaction */ /** @var Transaction $transaction */

View File

@@ -137,4 +137,12 @@ interface AccountRepositoryInterface
*/ */
public function store(array $data): Account; public function store(array $data): Account;
/**
* @param Account $account
* @param array $data
*
* @return Account
*/
public function update(Account $account, array $data): Account;
} }

View File

@@ -14,9 +14,10 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Account; namespace FireflyIII\Repositories\Account;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
use Steam; use Steam;
@@ -31,64 +32,6 @@ class AccountTasker implements AccountTaskerInterface
/** @var User */ /** @var User */
private $user; private $user;
/**
* @see self::amountInPeriod
*
* @param Collection $accounts
* @param Collection $excluded
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function amountInInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string
{
$idList = [
'accounts' => $accounts->pluck('id')->toArray(),
'exclude' => $excluded->pluck('id')->toArray(),
];
Log::debug(
'Now calling amountInInPeriod.',
['accounts' => $idList['accounts'], 'excluded' => $idList['exclude'],
'start' => $start->format('Y-m-d'),
'end' => $end->format('Y-m-d'),
]
);
return $this->amountInPeriod($idList, $start, $end, true);
}
/**
* @see self::amountInPeriod
*
* @param Collection $accounts
* @param Collection $excluded
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function amountOutInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string
{
$idList = [
'accounts' => $accounts->pluck('id')->toArray(),
'exclude' => $excluded->pluck('id')->toArray(),
];
Log::debug(
'Now calling amountOutInPeriod.',
['accounts' => $idList['accounts'], 'excluded' => $idList['exclude'],
'start' => $start->format('Y-m-d'),
'end' => $end->format('Y-m-d'),
]
);
return $this->amountInPeriod($idList, $start, $end, false);
}
/** /**
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
@@ -147,6 +90,92 @@ class AccountTasker implements AccountTaskerInterface
return $return; return $return;
} }
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array
{
// get all expenses for the given accounts in the given period!
// also transfers!
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->withOpposingAccount()
->enableInternalFilter();
$transactions = $collector->getJournals();
$transactions = $transactions->filter(
function (Transaction $transaction) {
// return negative amounts only.
if (bccomp($transaction->transaction_amount, '0') === -1) {
return $transaction;
}
return false;
}
);
$expenses = $this->groupByOpposing($transactions);
// sort the result
// Obtain a list of columns
$sum = [];
foreach ($expenses as $accountId => $row) {
$sum[$accountId] = floatval($row['sum']);
}
array_multisort($sum, SORT_ASC, $expenses);
return $expenses;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array
{
// get all expenses for the given accounts in the given period!
// also transfers!
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->withOpposingAccount()
->enableInternalFilter();
$transactions = $collector->getJournals();
$transactions = $transactions->filter(
function (Transaction $transaction) {
// return positive amounts only.
if (bccomp($transaction->transaction_amount, '0') === 1) {
return $transaction;
}
return false;
}
);
$income = $this->groupByOpposing($transactions);
// sort the result
// Obtain a list of columns
$sum = [];
foreach ($income as $accountId => $row) {
$sum[$accountId] = floatval($row['sum']);
}
array_multisort($sum, SORT_DESC, $income);
return $income;
}
/** /**
* @param User $user * @param User $user
*/ */
@@ -156,62 +185,37 @@ class AccountTasker implements AccountTaskerInterface
} }
/** /**
* Will return how much money has been going out (ie. spent) by the given account(s). * @param Collection $transactions
* Alternatively, will return how much money has been coming in (ie. earned) by the given accounts.
* *
* Enter $incoming=true for any money coming in (income) * @return array
* Enter $incoming=false for any money going out (expenses)
*
* This means any money going out or in. You can also submit accounts to exclude,
* so transfers between accounts are not included.
*
* As a general rule:
*
* - Asset accounts should return both expenses and earnings. But could return 0.
* - Expense accounts (where money is spent) should only return earnings (the account gets money).
* - Revenue accounts (where money comes from) should only return expenses (they spend money).
*
*
*
* @param array $accounts
* @param Carbon $start
* @param Carbon $end
* @param bool $incoming
*
* @return string
*/ */
protected function amountInPeriod(array $accounts, Carbon $start, Carbon $end, bool $incoming): string private function groupByOpposing(Collection $transactions): array
{ {
$joinModifier = $incoming ? '<' : '>'; $expenses = [];
$selection = $incoming ? '>' : '<'; // join the result together:
foreach ($transactions as $transaction) {
$query = Transaction::distinct() $opposingId = $transaction->opposing_account_id;
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') $name = $transaction->opposing_account_name;
->leftJoin( if (!isset($expenses[$opposingId])) {
'transactions as other_side', function (JoinClause $join) use ($joinModifier) { $expenses[$opposingId] = [
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0); 'id' => $opposingId,
'name' => $name,
'sum' => '0',
'average' => '0',
'count' => 0,
];
}
$expenses[$opposingId]['sum'] = bcadd($expenses[$opposingId]['sum'], $transaction->transaction_amount);
$expenses[$opposingId]['count']++;
}
// do averages:
foreach ($expenses as $key => $entry) {
if ($expenses[$key]['count'] > 1) {
$expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], strval($expenses[$key]['count']));
} }
)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereIn('transactions.account_id', $accounts['accounts'])
->where('transactions.amount', $selection, 0);
if (count($accounts['exclude']) > 0) {
$query->whereNotIn('other_side.account_id', $accounts['exclude']);
} }
$result = $query->get(['transactions.id', 'transactions.amount']);
$sum = strval($result->sum('amount'));
if (strlen($sum) === 0) {
Log::debug('Sum is empty.');
$sum = '0';
}
Log::debug(sprintf('Result is %s', $sum));
return $sum; return $expenses;
} }
} }

View File

@@ -24,30 +24,6 @@ use Illuminate\Support\Collection;
*/ */
interface AccountTaskerInterface interface AccountTaskerInterface
{ {
/**
* @param Collection $accounts
* @param Collection $excluded
* @param Carbon $start
* @param Carbon $end
*
* @see AccountTasker::amountInPeriod()
*
* @return string
*/
public function amountInInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string;
/**
* @param Collection $accounts
* @param Collection $excluded
* @param Carbon $start
* @param Carbon $end
*
* @see AccountTasker::amountInPeriod()
*
* @return string
*/
public function amountOutInPeriod(Collection $accounts, Collection $excluded, Carbon $start, Carbon $end): string;
/** /**
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
@@ -57,6 +33,24 @@ interface AccountTaskerInterface
*/ */
public function getAccountReport(Collection $accounts, Carbon $start, Carbon $end): array; public function getAccountReport(Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array;
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array;
/** /**
* @param User $user * @param User $user
*/ */

View File

@@ -14,10 +14,13 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Currency; namespace FireflyIII\Repositories\Currency;
use Carbon\Carbon;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\Preference; use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
use Preferences; use Preferences;
/** /**
@@ -113,7 +116,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/ */
public function findByCode(string $currencyCode): TransactionCurrency public function findByCode(string $currencyCode): TransactionCurrency
{ {
$currency = TransactionCurrency::whereCode($currencyCode)->first(); $currency = TransactionCurrency::where('code', $currencyCode)->first();
if (is_null($currency)) { if (is_null($currency)) {
$currency = new TransactionCurrency; $currency = new TransactionCurrency;
} }
@@ -170,7 +173,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/ */
public function getCurrencyByPreference(Preference $preference): TransactionCurrency public function getCurrencyByPreference(Preference $preference): TransactionCurrency
{ {
$preferred = TransactionCurrency::whereCode($preference->data)->first(); $preferred = TransactionCurrency::where('code', $preference->data)->first();
if (is_null($preferred)) { if (is_null($preferred)) {
$preferred = TransactionCurrency::first(); $preferred = TransactionCurrency::first();
} }
@@ -178,6 +181,38 @@ class CurrencyRepository implements CurrencyRepositoryInterface
return $preferred; return $preferred;
} }
/**
* @param TransactionCurrency $fromCurrency
* @param TransactionCurrency $toCurrency
* @param Carbon $date
*
* @return CurrencyExchangeRate
*/
public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate
{
if ($fromCurrency->id === $toCurrency->id) {
$rate = new CurrencyExchangeRate;
$rate->rate = 1;
$rate->id = 0;
return $rate;
}
$rate = $this->user->currencyExchangeRates()
->where('from_currency_id', $fromCurrency->id)
->where('to_currency_id', $toCurrency->id)
->where('date', $date->format('Y-m-d'))->first();
if (!is_null($rate)) {
Log::debug(sprintf('Found cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
return $rate;
}
return new CurrencyExchangeRate;
}
/** /**
* @param User $user * @param User $user
*/ */

View File

@@ -14,6 +14,8 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Currency; namespace FireflyIII\Repositories\Currency;
use Carbon\Carbon;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\Preference; use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User; use FireflyIII\User;
@@ -95,6 +97,15 @@ interface CurrencyRepositoryInterface
*/ */
public function getCurrencyByPreference(Preference $preference): TransactionCurrency; public function getCurrencyByPreference(Preference $preference): TransactionCurrency;
/**
* @param TransactionCurrency $fromCurrency
* @param TransactionCurrency $toCurrency
* @param Carbon $date
*
* @return CurrencyExchangeRate
*/
public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate;
/** /**
* @param User $user * @param User $user
*/ */

View File

@@ -86,6 +86,20 @@ class ImportJobRepository implements ImportJobRepositoryInterface
return $result; return $result;
} }
/**
* @param ImportJob $job
* @param array $configuration
*
* @return ImportJob
*/
public function setConfiguration(ImportJob $job, array $configuration): ImportJob
{
$job->configuration = $configuration;
$job->save();
return $job;
}
/** /**
* @param User $user * @param User $user
*/ */
@@ -93,4 +107,18 @@ class ImportJobRepository implements ImportJobRepositoryInterface
{ {
$this->user = $user; $this->user = $user;
} }
/**
* @param ImportJob $job
* @param string $status
*
* @return ImportJob
*/
public function updateStatus(ImportJob $job, string $status): ImportJob
{
$job->status = $status;
$job->save();
return $job;
}
} }

View File

@@ -37,8 +37,24 @@ interface ImportJobRepositoryInterface
*/ */
public function findByKey(string $key): ImportJob; public function findByKey(string $key): ImportJob;
/**
* @param ImportJob $job
* @param array $configuration
*
* @return ImportJob
*/
public function setConfiguration(ImportJob $job, array $configuration): ImportJob;
/** /**
* @param User $user * @param User $user
*/ */
public function setUser(User $user); public function setUser(User $user);
/**
* @param ImportJob $job
* @param string $status
*
* @return ImportJob
*/
public function updateStatus(ImportJob $job, string $status): ImportJob;
} }

View File

@@ -41,7 +41,10 @@ class JournalRepository implements JournalRepositoryInterface
private $user; private $user;
/** @var array */ /** @var array */
private $validMetaFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes']; private $validMetaFields
= ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes', 'foreign_amount',
'foreign_currency_id',
];
/** /**
* @param TransactionJournal $journal * @param TransactionJournal $journal
@@ -135,6 +138,20 @@ class JournalRepository implements JournalRepositoryInterface
return TransactionType::orderBy('type', 'ASC')->get(); return TransactionType::orderBy('type', 'ASC')->get();
} }
/**
* @param TransactionJournal $journal
* @param int $order
*
* @return bool
*/
public function setOrder(TransactionJournal $journal, int $order): bool
{
$journal->order = $order;
$journal->save();
return true;
}
/** /**
* @param User $user * @param User $user
*/ */
@@ -151,12 +168,17 @@ class JournalRepository implements JournalRepositoryInterface
public function store(array $data): TransactionJournal public function store(array $data): TransactionJournal
{ {
// find transaction type. // find transaction type.
/** @var TransactionType $transactionType */
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
$accounts = $this->storeAccounts($transactionType, $data);
$data = $this->verifyNativeAmount($data, $accounts);
$currencyId = $data['currency_id'];
$amount = strval($data['amount']);
$journal = new TransactionJournal( $journal = new TransactionJournal(
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id, 'transaction_type_id' => $transactionType->id,
'transaction_currency_id' => $data['currency_id'], 'transaction_currency_id' => $currencyId,
'description' => $data['description'], 'description' => $data['description'],
'completed' => 0, 'completed' => 0,
'date' => $data['date'], 'date' => $data['date'],
@@ -167,13 +189,13 @@ class JournalRepository implements JournalRepositoryInterface
// store stuff: // store stuff:
$this->storeCategoryWithJournal($journal, $data['category']); $this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']); $this->storeBudgetWithJournal($journal, $data['budget_id']);
$accounts = $this->storeAccounts($transactionType, $data);
// store two transactions: // store two transactions:
$one = [ $one = [
'journal' => $journal, 'journal' => $journal,
'account' => $accounts['source'], 'account' => $accounts['source'],
'amount' => bcmul(strval($data['amount']), '-1'), 'amount' => bcmul($amount, '-1'),
'description' => null, 'description' => null,
'category' => null, 'category' => null,
'budget' => null, 'budget' => null,
@@ -184,7 +206,7 @@ class JournalRepository implements JournalRepositoryInterface
$two = [ $two = [
'journal' => $journal, 'journal' => $journal,
'account' => $accounts['destination'], 'account' => $accounts['destination'],
'amount' => $data['amount'], 'amount' => $amount,
'description' => null, 'description' => null,
'category' => null, 'category' => null,
'budget' => null, 'budget' => null,
@@ -222,10 +244,22 @@ class JournalRepository implements JournalRepositoryInterface
*/ */
public function update(TransactionJournal $journal, array $data): TransactionJournal public function update(TransactionJournal $journal, array $data): TransactionJournal
{ {
// update actual journal: // update actual journal:
$journal->transaction_currency_id = $data['currency_id'];
$journal->description = $data['description']; $journal->description = $data['description'];
$journal->date = $data['date']; $journal->date = $data['date'];
$accounts = $this->storeAccounts($journal->transactionType, $data);
$amount = strval($data['amount']);
if ($data['currency_id'] !== $journal->transaction_currency_id) {
// user has entered amount in foreign currency.
// amount in "our" currency is $data['exchanged_amount']:
$amount = strval($data['exchanged_amount']);
// other values must be stored as well:
$data['original_amount'] = $data['amount'];
$data['original_currency_id'] = $data['currency_id'];
}
// unlink all categories, recreate them: // unlink all categories, recreate them:
$journal->categories()->detach(); $journal->categories()->detach();
@@ -233,12 +267,9 @@ class JournalRepository implements JournalRepositoryInterface
$this->storeCategoryWithJournal($journal, $data['category']); $this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']); $this->storeBudgetWithJournal($journal, $data['budget_id']);
$accounts = $this->storeAccounts($journal->transactionType, $data);
$sourceAmount = bcmul(strval($data['amount']), '-1');
$this->updateSourceTransaction($journal, $accounts['source'], $sourceAmount); // negative because source loses money.
$amount = strval($data['amount']); $this->updateSourceTransaction($journal, $accounts['source'], bcmul($amount, '-1')); // negative because source loses money.
$this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money. $this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money.
$journal->save(); $journal->save();
@@ -731,4 +762,56 @@ class JournalRepository implements JournalRepositoryInterface
return true; return true;
} }
/**
* This method checks the data array and the given accounts to verify that the native amount, currency
* and possible the foreign currency and amount are properly saved.
*
* @param array $data
* @param array $accounts
*
* @return array
* @throws FireflyException
*/
private function verifyNativeAmount(array $data, array $accounts): array
{
/** @var TransactionType $transactionType */
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
$submittedCurrencyId = $data['currency_id'];
// which account to check for what the native currency is?
$check = 'source';
if ($transactionType->type === TransactionType::DEPOSIT) {
$check = 'destination';
}
switch ($transactionType->type) {
case TransactionType::DEPOSIT:
case TransactionType::WITHDRAWAL:
// continue:
$nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id'));
// does not match? Then user has submitted amount in a foreign currency:
if ($nativeCurrencyId !== $submittedCurrencyId) {
// store amount and submitted currency in "foreign currency" fields:
$data['foreign_amount'] = $data['amount'];
$data['foreign_currency_id'] = $submittedCurrencyId;
// overrule the amount and currency ID fields to be the original again:
$data['amount'] = strval($data['native_amount']);
$data['currency_id'] = $nativeCurrencyId;
}
break;
case TransactionType::TRANSFER:
// source gets the original amount.
$data['amount'] = strval($data['source_amount']);
$data['currency_id'] = intval($accounts['source']->getMeta('currency_id'));
$data['foreign_amount'] = strval($data['destination_amount']);
$data['foreign_currency_id'] = intval($accounts['destination']->getMeta('currency_id'));
break;
default:
throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type));
}
return $data;
}
} }

View File

@@ -67,6 +67,14 @@ interface JournalRepositoryInterface
*/ */
public function getTransactionTypes(): Collection; public function getTransactionTypes(): Collection;
/**
* @param TransactionJournal $journal
* @param int $order
*
* @return bool
*/
public function setOrder(TransactionJournal $journal, int $order): bool;
/** /**
* @param User $user * @param User $user
*/ */

View File

@@ -32,6 +32,54 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
/** @var User */ /** @var User */
private $user; private $user;
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function addAmount(PiggyBank $piggyBank, string $amount): bool
{
$repetition = $piggyBank->currentRelevantRep();
$currentAmount = $repetition->currentamount ?? '0';
$repetition->currentamount = bcadd($currentAmount, $amount);
$repetition->save();
// create event
$this->createEvent($piggyBank, $amount);
return true;
}
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function canAddAmount(PiggyBank $piggyBank, string $amount): bool
{
$leftOnAccount = $piggyBank->leftOnAccount(new Carbon);
$savedSoFar = strval($piggyBank->currentRelevantRep()->currentamount);
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
$maxAmount = strval(min(round($leftOnAccount, 12), round($leftToSave, 12)));
return bccomp($amount, $maxAmount) <= 0;
}
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool
{
$savedSoFar = $piggyBank->currentRelevantRep()->currentamount;
return bccomp($amount, $savedSoFar) <= 0;
}
/** /**
* @param PiggyBank $piggyBank * @param PiggyBank $piggyBank
* @param string $amount * @param string $amount
@@ -119,6 +167,24 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
return $set; return $set;
} }
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function removeAmount(PiggyBank $piggyBank, string $amount): bool
{
$repetition = $piggyBank->currentRelevantRep();
$repetition->currentamount = bcsub($repetition->currentamount, $amount);
$repetition->save();
// create event
$this->createEvent($piggyBank, bcmul($amount, '-1'));
return true;
}
/** /**
* Set all piggy banks to order 0. * Set all piggy banks to order 0.
* *

View File

@@ -26,6 +26,30 @@ use Illuminate\Support\Collection;
interface PiggyBankRepositoryInterface interface PiggyBankRepositoryInterface
{ {
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function addAmount(PiggyBank $piggyBank, string $amount): bool;
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function canAddAmount(PiggyBank $piggyBank, string $amount): bool;
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool;
/** /**
* Create a new event. * Create a new event.
* *
@@ -82,6 +106,14 @@ interface PiggyBankRepositoryInterface
*/ */
public function getPiggyBanksWithAmount(): Collection; public function getPiggyBanksWithAmount(): Collection;
/**
* @param PiggyBank $piggyBank
* @param string $amount
*
* @return bool
*/
public function removeAmount(PiggyBank $piggyBank, string $amount): bool;
/** /**
* Set all piggy banks to order 0. * Set all piggy banks to order 0.
* *

View File

@@ -246,6 +246,95 @@ class TagRepository implements TagRepositoryInterface
} }
/**
* @param Tag $tag
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return string
*/
public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
if (!is_null($start) && !is_null($end)) {
$collector->setRange($start, $end);
}
$collector->setAllAssetAccounts()->setTag($tag);
$sum = $collector->getJournals()->sum('transaction_amount');
return strval($sum);
}
/**
* Can a tag become an advance payment?
*
* @param Tag $tag
*
* @return bool
*/
public function tagAllowAdvance(Tag $tag): bool
{
/*
* If this tag is a balancing act, and it contains transfers, it cannot be
* changed to an advancePayment.
*/
if ($tag->tagMode == 'balancingAct' || $tag->tagMode == 'nothing') {
foreach ($tag->transactionjournals as $journal) {
if ($journal->isTransfer()) {
return false;
}
}
}
/*
* If this tag contains more than one expenses, it cannot become an advance payment.
*/
$count = 0;
foreach ($tag->transactionjournals as $journal) {
if ($journal->isWithdrawal()) {
$count++;
}
}
if ($count > 1) {
return false;
}
return true;
}
/**
* Can a tag become a balancing act?
*
* @param Tag $tag
*
* @return bool
*/
public function tagAllowBalancing(Tag $tag): bool
{
/*
* If has more than two transactions already, cannot become a balancing act:
*/
if ($tag->transactionjournals->count() > 2) {
return false;
}
/*
* If any transaction is a deposit, cannot become a balancing act.
*/
foreach ($tag->transactionjournals as $journal) {
if ($journal->isDeposit()) {
return false;
}
}
return true;
}
/** /**
* @param Tag $tag * @param Tag $tag
* @param array $data * @param array $data

View File

@@ -27,6 +27,7 @@ use Illuminate\Support\Collection;
*/ */
interface TagRepositoryInterface interface TagRepositoryInterface
{ {
/** /**
* This method will connect a journal with a tag. * This method will connect a journal with a tag.
* *
@@ -125,6 +126,29 @@ interface TagRepositoryInterface
*/ */
public function store(array $data): Tag; public function store(array $data): Tag;
/**
* @param Tag $tag
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return string
*/
public function sumOfTag(Tag $tag, Carbon $start = null, Carbon $end = null): string;
/**
* @param Tag $tag
*
* @return bool
*/
public function tagAllowAdvance(Tag $tag): bool;
/**
* @param Tag $tag
*
* @return bool
*/
public function tagAllowBalancing(Tag $tag): bool;
/** /**
* Update a tag. * Update a tag.
* *

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