mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-22 03:01:21 +00:00
Compare commits
807 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63be574a14 | ||
|
|
2414f998ba | ||
|
|
90e2ecad4f | ||
|
|
87a3f5c715 | ||
|
|
a4ef81ebd8 | ||
|
|
67fc810fc2 | ||
|
|
e13a61645d | ||
|
|
aae4a78fd4 | ||
|
|
3947da5e27 | ||
|
|
4e4ce2f77c | ||
|
|
d9a2e081bd | ||
|
|
e60dde7ce5 | ||
|
|
699b138999 | ||
|
|
8f191b497f | ||
|
|
fd013d617c | ||
|
|
a8bda38035 | ||
|
|
411de9aa23 | ||
|
|
f0abe497ef | ||
|
|
9c8be83f5d | ||
|
|
45335af8cf | ||
|
|
945e433bb7 | ||
|
|
70729fdb0c | ||
|
|
4a435e5701 | ||
|
|
427747c6b8 | ||
|
|
be9407cb57 | ||
|
|
c380d3d2a0 | ||
|
|
489b0eb12d | ||
|
|
7c73629962 | ||
|
|
ff619eca1c | ||
|
|
134a39fd71 | ||
|
|
506509b1b2 | ||
|
|
02a36316be | ||
|
|
8549c7c81b | ||
|
|
5a0be7d2ad | ||
|
|
c6f44c6398 | ||
|
|
04a8a0e6a1 | ||
|
|
8a175d147b | ||
|
|
5fbc319b20 | ||
|
|
8e3ba7caf2 | ||
|
|
182aaa7d27 | ||
|
|
ec17d466f7 | ||
|
|
79c2445117 | ||
|
|
aa59db1609 | ||
|
|
814b106be2 | ||
|
|
3901ea0fbe | ||
|
|
65133107be | ||
|
|
08d15cfc5e | ||
|
|
3c89f9a5f1 | ||
|
|
dbc6bc8206 | ||
|
|
dc38291ef5 | ||
|
|
3bb634ed5b | ||
|
|
4bdcfe4d30 | ||
|
|
3b74784486 | ||
|
|
5226664a3d | ||
|
|
6d23823b63 | ||
|
|
da3a56c144 | ||
|
|
58aa54d8cf | ||
|
|
e525e673a8 | ||
|
|
445dbf8779 | ||
|
|
6a27bea2a3 | ||
|
|
13163f9c5c | ||
|
|
fde08f922b | ||
|
|
266ce00872 | ||
|
|
623a70a0d1 | ||
|
|
1d7b738040 | ||
|
|
edc8dd2601 | ||
|
|
31dc932ca4 | ||
|
|
edb355941c | ||
|
|
cddaccb7f7 | ||
|
|
a905cce2c9 | ||
|
|
b9f110ac2b | ||
|
|
7cc24417b3 | ||
|
|
b304284d70 | ||
|
|
c382fb1577 | ||
|
|
77244f4e2c | ||
|
|
8beab5f5bc | ||
|
|
902ae3f0cf | ||
|
|
519ef4e486 | ||
|
|
699e04f371 | ||
|
|
d73de0e60d | ||
|
|
8e0b7d2a73 | ||
|
|
04283cf2d6 | ||
|
|
d60b4aa56f | ||
|
|
9cfeda1b0c | ||
|
|
5e1bd8e1eb | ||
|
|
9b495d212b | ||
|
|
b6fdd0070b | ||
|
|
eea387ad0c | ||
|
|
9cdb0f173a | ||
|
|
51063230d0 | ||
|
|
1111478b7f | ||
|
|
c99c9b441f | ||
|
|
b5065a0276 | ||
|
|
c79a577060 | ||
|
|
091596e80e | ||
|
|
0b4efe4ae1 | ||
|
|
1f9b7faa60 | ||
|
|
762d7bcc34 | ||
|
|
a2145f6b49 | ||
|
|
b48de98865 | ||
|
|
a23179dd83 | ||
|
|
5c18794122 | ||
|
|
f04011f6a7 | ||
|
|
893498238e | ||
|
|
474fa9dea0 | ||
|
|
d8a8574dda | ||
|
|
935fb015d3 | ||
|
|
8bbd3063ec | ||
|
|
92c5cabd70 | ||
|
|
e5db5a7b5c | ||
|
|
28cf123da3 | ||
|
|
5069367873 | ||
|
|
234e656ff6 | ||
|
|
aeadfbdd6a | ||
|
|
39288cfb0b | ||
|
|
5e430968c1 | ||
|
|
154b74ce6f | ||
|
|
2e26303b66 | ||
|
|
51ddcd9ee1 | ||
|
|
9d5d1c0a41 | ||
|
|
6058ccff0d | ||
|
|
a8ec4fe2fd | ||
|
|
65ccb2d443 | ||
|
|
0e929602a8 | ||
|
|
5329e026dc | ||
|
|
a7412e43b3 | ||
|
|
eeae24e058 | ||
|
|
17b6cc43d5 | ||
|
|
b69a2ef0cd | ||
|
|
6d1296094e | ||
|
|
c4039b53e6 | ||
|
|
64831b4c86 | ||
|
|
1dec270907 | ||
|
|
f72f8b03df | ||
|
|
0b47e5d05d | ||
|
|
a487c7b4b2 | ||
|
|
3838b21459 | ||
|
|
7801274d33 | ||
|
|
a505406ee7 | ||
|
|
9487a95c13 | ||
|
|
3b2fe13902 | ||
|
|
db2898dfe5 | ||
|
|
e08fd399d2 | ||
|
|
e45ffba010 | ||
|
|
f7fde93ed2 | ||
|
|
3cf53604a1 | ||
|
|
e041a5e037 | ||
|
|
6c8e10255b | ||
|
|
3464ab1527 | ||
|
|
82e74a2afd | ||
|
|
771ebde295 | ||
|
|
4ce4c3138c | ||
|
|
d37b46effc | ||
|
|
0868aac750 | ||
|
|
4ff5f33966 | ||
|
|
e1aebbe12b | ||
|
|
8273f467b6 | ||
|
|
74664afa68 | ||
|
|
c05f344371 | ||
|
|
ec1507d644 | ||
|
|
8cdc1f0014 | ||
|
|
2b1ab5c6ef | ||
|
|
01fedc0bf8 | ||
|
|
1bd82d71a2 | ||
|
|
1e9dacb6e4 | ||
|
|
94939ea3d3 | ||
|
|
c3ff69d147 | ||
|
|
0fad9d4ac7 | ||
|
|
70cd8ffb72 | ||
|
|
61535bf4b8 | ||
|
|
4adcbf9e48 | ||
|
|
3eba3167fd | ||
|
|
0608fd7732 | ||
|
|
1f41f27e89 | ||
|
|
ded0df9303 | ||
|
|
2a7ba1893a | ||
|
|
6b3b19632a | ||
|
|
98bb0731df | ||
|
|
a102f7044e | ||
|
|
f1028dbaed | ||
|
|
438ce5c3db | ||
|
|
b995a1d091 | ||
|
|
e072f83507 | ||
|
|
79154bba25 | ||
|
|
613eb7522c | ||
|
|
0eb5653713 | ||
|
|
0d624f021b | ||
|
|
b263047f4e | ||
|
|
4410e1bbd7 | ||
|
|
8c3871e8de | ||
|
|
2eafd3cc15 | ||
|
|
368df66947 | ||
|
|
9f9a3ea8fd | ||
|
|
f66286105f | ||
|
|
0f0f912370 | ||
|
|
9fa0e37a5d | ||
|
|
a0cb51ff70 | ||
|
|
a6305ddea4 | ||
|
|
fb8638fe6a | ||
|
|
0009f1f865 | ||
|
|
d449c35025 | ||
|
|
1893d1a2c2 | ||
|
|
0c4539e4fa | ||
|
|
feacddd1d7 | ||
|
|
0c44fe6ce0 | ||
|
|
17fb6983d8 | ||
|
|
5fb73bdb01 | ||
|
|
7f082ea389 | ||
|
|
9856a1831a | ||
|
|
dd17a20b60 | ||
|
|
665a52b106 | ||
|
|
f68d33870b | ||
|
|
49d13b12a5 | ||
|
|
674ab7e41f | ||
|
|
5b3eb3ba82 | ||
|
|
e28d3f3b5a | ||
|
|
870d8b5008 | ||
|
|
6aa240e9a3 | ||
|
|
b55a4047d0 | ||
|
|
a07d87318c | ||
|
|
dd6555c903 | ||
|
|
e8c40b6044 | ||
|
|
91c17a0b2a | ||
|
|
2ed0ea0243 | ||
|
|
df2f92433a | ||
|
|
68b3fc72bf | ||
|
|
df5bb14758 | ||
|
|
e24199bbbe | ||
|
|
2c6099556b | ||
|
|
f98215a5da | ||
|
|
b6b6888493 | ||
|
|
f8e5b9be43 | ||
|
|
692210214f | ||
|
|
cff2546c0c | ||
|
|
f0cc1200f3 | ||
|
|
e83a9af455 | ||
|
|
fb84f9d9cf | ||
|
|
118a2515e1 | ||
|
|
63e891b0f7 | ||
|
|
9891080b57 | ||
|
|
bc7a7e55af | ||
|
|
9b8a029de1 | ||
|
|
11b5575422 | ||
|
|
06336aa580 | ||
|
|
aa8c1d6e9c | ||
|
|
9a4d5d8abf | ||
|
|
3c5631bca3 | ||
|
|
84f85f87b2 | ||
|
|
c35db5976f | ||
|
|
8d1fcf988c | ||
|
|
bbf8f2dd69 | ||
|
|
2f6436e34f | ||
|
|
9cbb03107d | ||
|
|
ee44d7fb2e | ||
|
|
6a0fcd9cf0 | ||
|
|
43cfe3b858 | ||
|
|
dba83c1c03 | ||
|
|
053e139d00 | ||
|
|
04f791a839 | ||
|
|
a4029c9490 | ||
|
|
d471dfec43 | ||
|
|
494bbd46d0 | ||
|
|
1525b9ad06 | ||
|
|
19847ee80b | ||
|
|
fddf1f146c | ||
|
|
5f19cb1c0c | ||
|
|
aaeae992e1 | ||
|
|
a0e7be9d45 | ||
|
|
78faf7e14c | ||
|
|
98f84c2c37 | ||
|
|
278805043e | ||
|
|
dc5215e41e | ||
|
|
7e11691ea4 | ||
|
|
c83dfc44d6 | ||
|
|
68a01b1735 | ||
|
|
0307b58d17 | ||
|
|
9d1508049e | ||
|
|
42322055f9 | ||
|
|
5de8fce156 | ||
|
|
29ff92f833 | ||
|
|
359007c5bf | ||
|
|
bbe40518e4 | ||
|
|
b26f3c0cc6 | ||
|
|
552b4b67a6 | ||
|
|
7fbf359efd | ||
|
|
052b804855 | ||
|
|
67cc611495 | ||
|
|
7e2c24b82e | ||
|
|
4a6bbd4dca | ||
|
|
166bfba5b9 | ||
|
|
87b8ac5f4e | ||
|
|
46b4100291 | ||
|
|
83090ade94 | ||
|
|
44b7a42d5a | ||
|
|
1a5617d430 | ||
|
|
fa818e0924 | ||
|
|
f87b531fe1 | ||
|
|
82fd0c4d37 | ||
|
|
26ceb9e3be | ||
|
|
743458b853 | ||
|
|
920e5f6fbe | ||
|
|
fc0dd22769 | ||
|
|
c7c61ce280 | ||
|
|
5f5a603f71 | ||
|
|
d611858883 | ||
|
|
dbc9ce76a0 | ||
|
|
62386e2e40 | ||
|
|
5aa7ab5e37 | ||
|
|
be1b57eab7 | ||
|
|
ad5c8b41e2 | ||
|
|
09752b25ad | ||
|
|
3f317f961b | ||
|
|
d6d6a62fda | ||
|
|
528833b831 | ||
|
|
95e2bca4a6 | ||
|
|
dfd3327108 | ||
|
|
653befbcd4 | ||
|
|
33202efcdc | ||
|
|
77a37411ba | ||
|
|
491f221741 | ||
|
|
e8e125a598 | ||
|
|
cb01ae0ac5 | ||
|
|
a09469c01f | ||
|
|
662f398b08 | ||
|
|
d7aef627b8 | ||
|
|
87bcf293aa | ||
|
|
0171831ebb | ||
|
|
50cc455f0f | ||
|
|
47a7729358 | ||
|
|
6d508e61a0 | ||
|
|
8ef9223d84 | ||
|
|
e58ea4d0c7 | ||
|
|
9629ba916e | ||
|
|
aad4df1596 | ||
|
|
30ed25f80a | ||
|
|
8b7000681c | ||
|
|
221adbf3db | ||
|
|
39129d8cd0 | ||
|
|
2c2d444906 | ||
|
|
04a1d785bf | ||
|
|
c0a0aa4652 | ||
|
|
21c24fd7f0 | ||
|
|
beb358f8ee | ||
|
|
e3cd11ec2e | ||
|
|
ee08fc2421 | ||
|
|
ae30f7920b | ||
|
|
f4786c3ec8 | ||
|
|
8b9e4d2539 | ||
|
|
d7ca7ed632 | ||
|
|
1346b25fc7 | ||
|
|
15d9314503 | ||
|
|
93f4006c9e | ||
|
|
16182fec6c | ||
|
|
99606ba936 | ||
|
|
be1ed56d42 | ||
|
|
d7fb6f83b8 | ||
|
|
665f1f470a | ||
|
|
7867f26120 | ||
|
|
8e195bf811 | ||
|
|
e3403dc87f | ||
|
|
5844e95488 | ||
|
|
cb3a1f2ff6 | ||
|
|
f95edb06a9 | ||
|
|
86d13060bc | ||
|
|
0b8c2f6f8d | ||
|
|
589cafc64f | ||
|
|
448dc7b5c3 | ||
|
|
10cfde6cab | ||
|
|
e48eb2ce2f | ||
|
|
20a30a2d1d | ||
|
|
2e5b8418ae | ||
|
|
054fd229d2 | ||
|
|
9bdf2c8877 | ||
|
|
04f858a355 | ||
|
|
a9bf405a65 | ||
|
|
463c866cfa | ||
|
|
03fe0c8fff | ||
|
|
102d9f3510 | ||
|
|
3cf9a40c42 | ||
|
|
ff33267296 | ||
|
|
f0dab5bdb9 | ||
|
|
209a907c61 | ||
|
|
c7984d4363 | ||
|
|
a388313e1c | ||
|
|
a3933cb307 | ||
|
|
89c82d9be9 | ||
|
|
6e6311a1a1 | ||
|
|
69b334ed8a | ||
|
|
a84b6f377f | ||
|
|
ee5db4e4cc | ||
|
|
20f3322e5a | ||
|
|
1e84bc3743 | ||
|
|
7f198cbd50 | ||
|
|
1c7d5ccefc | ||
|
|
41dc4d994e | ||
|
|
490733bdd1 | ||
|
|
ed8cf8c431 | ||
|
|
a7e1f85c4d | ||
|
|
4b8919420d | ||
|
|
9afc5d67a4 | ||
|
|
72afaddd7c | ||
|
|
8ae3182e1c | ||
|
|
9d7e16b390 | ||
|
|
3a2ed202ad | ||
|
|
9e4b9b98ab | ||
|
|
e65fb7d995 | ||
|
|
7197830edc | ||
|
|
687603ae84 | ||
|
|
23fc25f926 | ||
|
|
e0c1f07f92 | ||
|
|
9424aa3378 | ||
|
|
57e6e0945f | ||
|
|
d87a033f29 | ||
|
|
10481895e6 | ||
|
|
813432f386 | ||
|
|
aba78c9776 | ||
|
|
8dc56bcee0 | ||
|
|
32b6e030ef | ||
|
|
a27f5d2474 | ||
|
|
e2fe8cfb75 | ||
|
|
9f1c346365 | ||
|
|
3e56803e95 | ||
|
|
fc5feb054d | ||
|
|
8fc233e436 | ||
|
|
22303eb2ff | ||
|
|
d6477ff9ff | ||
|
|
a0f6f13650 | ||
|
|
1dd213d587 | ||
|
|
569e51afbe | ||
|
|
ff06a4ed0d | ||
|
|
ea3fdb0668 | ||
|
|
bd917f6484 | ||
|
|
ee96311222 | ||
|
|
135153ff9c | ||
|
|
fdb98133c8 | ||
|
|
ad4e2c0a85 | ||
|
|
e38cb263aa | ||
|
|
0922136389 | ||
|
|
2a05517a30 | ||
|
|
a0ee924aeb | ||
|
|
1595afce68 | ||
|
|
4919d730c8 | ||
|
|
1407d6cc66 | ||
|
|
55b092e7a0 | ||
|
|
93afb754f3 | ||
|
|
81c0ea8f5c | ||
|
|
6faacd3781 | ||
|
|
ea1e9e407f | ||
|
|
d4f5aa1578 | ||
|
|
1cbbf3fa5d | ||
|
|
dad3dc71ca | ||
|
|
afb1e9f230 | ||
|
|
c33dd1ecee | ||
|
|
7e31a29b12 | ||
|
|
9a69ce309e | ||
|
|
b99bfcd02e | ||
|
|
adb16e4560 | ||
|
|
953c38563b | ||
|
|
89ee9c058a | ||
|
|
bac7a73555 | ||
|
|
5fb6ff230b | ||
|
|
605a718418 | ||
|
|
994542c75d | ||
|
|
d5fdce02fa | ||
|
|
d3637de0c3 | ||
|
|
63acbb222d | ||
|
|
5e1fe157b6 | ||
|
|
8e811da967 | ||
|
|
fb23108f4e | ||
|
|
a0220eb5f8 | ||
|
|
c16cbd5bc8 | ||
|
|
2d0673e1bc | ||
|
|
a90e00d577 | ||
|
|
8db96025a3 | ||
|
|
595596d73f | ||
|
|
240797e92a | ||
|
|
6cafb91680 | ||
|
|
852ce3e32f | ||
|
|
6b9c9458fa | ||
|
|
970ce917b0 | ||
|
|
db46d450bf | ||
|
|
db806b92dd | ||
|
|
6765f08b07 | ||
|
|
99d75ba14b | ||
|
|
0e8ce5680c | ||
|
|
fd01b54a14 | ||
|
|
ce6253bbd7 | ||
|
|
4fd33f19c6 | ||
|
|
01ae278f09 | ||
|
|
7907c71e47 | ||
|
|
4e44733dcc | ||
|
|
dd1d7bb02a | ||
|
|
6f933b3bd1 | ||
|
|
243530b750 | ||
|
|
ea984281b0 | ||
|
|
92cd3d60b9 | ||
|
|
5920ccee04 | ||
|
|
569fec4610 | ||
|
|
5770edcde2 | ||
|
|
1fb0a64f31 | ||
|
|
fe66d089ad | ||
|
|
cef10b4d4e | ||
|
|
b45aa6446d | ||
|
|
61749312b2 | ||
|
|
842bb2c5a6 | ||
|
|
4358fe99b1 | ||
|
|
3fda8b3714 | ||
|
|
09f3274adc | ||
|
|
029224f708 | ||
|
|
e87cce12f8 | ||
|
|
7660667153 | ||
|
|
222b3008d5 | ||
|
|
398cf0b312 | ||
|
|
5ad8be2483 | ||
|
|
6fe319702d | ||
|
|
32c89f9a98 | ||
|
|
64f983786e | ||
|
|
16438b416d | ||
|
|
52cd292a69 | ||
|
|
53b501df19 | ||
|
|
477acafc4c | ||
|
|
ab9146b7c6 | ||
|
|
4d15913e18 | ||
|
|
63a91811e2 | ||
|
|
9df4da6173 | ||
|
|
643927799b | ||
|
|
2652e23089 | ||
|
|
b70c5ae41e | ||
|
|
c53d9b2855 | ||
|
|
2f6712be87 | ||
|
|
37f78985ae | ||
|
|
34ac9bc71a | ||
|
|
19ff2484fd | ||
|
|
b7ef2211d9 | ||
|
|
c5206a4559 | ||
|
|
894296f63d | ||
|
|
61526df245 | ||
|
|
92549c4485 | ||
|
|
6014892d4d | ||
|
|
9515ce6807 | ||
|
|
1adb0f2f0e | ||
|
|
00b1b54347 | ||
|
|
282ce041e1 | ||
|
|
3215c4ee4b | ||
|
|
fce00c95c9 | ||
|
|
0a69ab1dc2 | ||
|
|
94771d250a | ||
|
|
b8395a1dbb | ||
|
|
0436cd1584 | ||
|
|
2c774bb94c | ||
|
|
b879d90b0d | ||
|
|
ba52b5c328 | ||
|
|
bf61de13f4 | ||
|
|
b26e67ae07 | ||
|
|
edafd16c75 | ||
|
|
c62e3dcb78 | ||
|
|
68be58c9f2 | ||
|
|
a25a499ed4 | ||
|
|
97f67912f4 | ||
|
|
e2f3788ff5 | ||
|
|
fd1f06c2cb | ||
|
|
2db8d25038 | ||
|
|
d618ddc8c5 | ||
|
|
79aa0afc97 | ||
|
|
e53d294c1c | ||
|
|
aedc3fdff9 | ||
|
|
b67dfeced2 | ||
|
|
65dbfcba5c | ||
|
|
8f14c78ba1 | ||
|
|
be1b64bb78 | ||
|
|
a34c285820 | ||
|
|
298f0c194d | ||
|
|
f5ad569aba | ||
|
|
c70df01532 | ||
|
|
03e115e066 | ||
|
|
b88fce35ad | ||
|
|
d214e9ff49 | ||
|
|
bd8382c005 | ||
|
|
18cb815e45 | ||
|
|
800e8aca6e | ||
|
|
63a6e0754b | ||
|
|
4abc271805 | ||
|
|
22cca3858c | ||
|
|
b816fbdf82 | ||
|
|
d400060b8b | ||
|
|
c88cfa2d9d | ||
|
|
b060027304 | ||
|
|
d4787aca9c | ||
|
|
69ae2e93f8 | ||
|
|
82a37c62c1 | ||
|
|
ca69970242 | ||
|
|
fac3853d95 | ||
|
|
9dd2f447cc | ||
|
|
ecbd7ca95b | ||
|
|
0c52d54d7d | ||
|
|
78276ac66b | ||
|
|
ca0a22d755 | ||
|
|
47361fd77a | ||
|
|
1af6fd5d74 | ||
|
|
51df78ad2d | ||
|
|
84a3e68eb5 | ||
|
|
b2904e09d8 | ||
|
|
fc42a621e4 | ||
|
|
a1c4fb73cd | ||
|
|
c18f19db97 | ||
|
|
ebc712f6b5 | ||
|
|
ef0057d88d | ||
|
|
a8fdfdd5e0 | ||
|
|
89f7e1ba2a | ||
|
|
6508b4058f | ||
|
|
bec5d93af6 | ||
|
|
1125fcd6c5 | ||
|
|
ff43a547a6 | ||
|
|
ca8684146c | ||
|
|
44bea62383 | ||
|
|
8dc2faa5e5 | ||
|
|
f4867d1d09 | ||
|
|
8d172801b5 | ||
|
|
db6e6dfe4a | ||
|
|
61007a95a6 | ||
|
|
bf138670e8 | ||
|
|
0e59f7433c | ||
|
|
9a033ac62a | ||
|
|
789412ee6a | ||
|
|
91125d20ef | ||
|
|
292b3672e2 | ||
|
|
14ef6b753c | ||
|
|
55fcb97a10 | ||
|
|
6b8ec544c1 | ||
|
|
c0aad385cd | ||
|
|
ecfb5e4711 | ||
|
|
46c9967a68 | ||
|
|
176c44e2b9 | ||
|
|
5957cf5ff8 | ||
|
|
436a270524 | ||
|
|
7a1c14b766 | ||
|
|
721c4aa888 | ||
|
|
638aab4eea | ||
|
|
9aa44f7458 | ||
|
|
1e887a2a8d | ||
|
|
78571ad121 | ||
|
|
8e5ec79097 | ||
|
|
fc351f36b1 | ||
|
|
80204147f7 | ||
|
|
d599b8e5ea | ||
|
|
208ef9d664 | ||
|
|
c14da542d1 | ||
|
|
451832cb2b | ||
|
|
1409aeb8ed | ||
|
|
342f24cfe4 | ||
|
|
8aedfd5153 | ||
|
|
33b099456f | ||
|
|
42cb40102f | ||
|
|
2fbeaaccd3 | ||
|
|
57fb75bef4 | ||
|
|
5c0e22cd31 | ||
|
|
879f74e521 | ||
|
|
e5a2e1a8c7 | ||
|
|
884d6c59a2 | ||
|
|
c206a95d55 | ||
|
|
8b4ef4e2da | ||
|
|
bac5238589 | ||
|
|
ae05d4d51d | ||
|
|
9d22bbee1c | ||
|
|
1d6007b848 | ||
|
|
3157339e44 | ||
|
|
b31d5eefd1 | ||
|
|
e47398dc71 | ||
|
|
757d472704 | ||
|
|
9d0ae99602 | ||
|
|
0d9ceb6fde | ||
|
|
cb665d4016 | ||
|
|
9673dc96d9 | ||
|
|
2295091427 | ||
|
|
45f4395f26 | ||
|
|
d9aa074330 | ||
|
|
33c20c8dc4 | ||
|
|
8101f910f0 | ||
|
|
8fb6c1a0c8 | ||
|
|
978e3e615c | ||
|
|
b3b8981b4b | ||
|
|
015064e5af | ||
|
|
7ef8ff60a5 | ||
|
|
4c3f54699e | ||
|
|
3bf5040324 | ||
|
|
ed9e5b31fa | ||
|
|
506e97e12d | ||
|
|
9365f9ab60 | ||
|
|
dd1e9ecb32 | ||
|
|
d3a2bf174d | ||
|
|
311020ff2e | ||
|
|
a6df3ac1fb | ||
|
|
d313b50e39 | ||
|
|
b23eb07018 | ||
|
|
2116486fe0 | ||
|
|
eed8fe22c6 | ||
|
|
1cec91e4bf | ||
|
|
8decf8ab9f | ||
|
|
79b0c20adb | ||
|
|
65647ca822 | ||
|
|
18fafcd45f | ||
|
|
b7723f4487 | ||
|
|
6418df87bb | ||
|
|
daa88c5be1 | ||
|
|
707b70b136 | ||
|
|
f9b3b6f7d3 | ||
|
|
b295eef970 | ||
|
|
f13dace7ad | ||
|
|
8dfc40ed3e | ||
|
|
2e637031ac | ||
|
|
de9ef20014 | ||
|
|
4f50689d0e | ||
|
|
082392f9e0 | ||
|
|
aca2ef08f1 | ||
|
|
90fcec4ca8 | ||
|
|
c90db28b9e | ||
|
|
56b617bd57 | ||
|
|
06d58c16d8 | ||
|
|
b1a807139a | ||
|
|
ac07736040 | ||
|
|
c68ba8d510 | ||
|
|
470802c93b | ||
|
|
c58745b6ce | ||
|
|
9eea4749f0 | ||
|
|
251206fb75 | ||
|
|
b53a6c5703 | ||
|
|
8c6972d12d | ||
|
|
fe0d88b7b3 | ||
|
|
658b6de9c7 | ||
|
|
4ea89c3811 | ||
|
|
2d3d71f46a | ||
|
|
80a6f431b6 | ||
|
|
0644729148 | ||
|
|
07da48e5ea | ||
|
|
b3f565819b | ||
|
|
af4abfbed9 | ||
|
|
eb0011cb46 | ||
|
|
444439fdab | ||
|
|
a0e66b913b | ||
|
|
96c780c804 | ||
|
|
4f1f46aa93 | ||
|
|
1d9f76ee5a | ||
|
|
a7ceda7eea | ||
|
|
baec4c4d70 | ||
|
|
4fa850048c | ||
|
|
8163655a24 | ||
|
|
4cabf8d2e3 | ||
|
|
db2f08fa96 | ||
|
|
a68be2fed7 | ||
|
|
c3e9aea3a7 | ||
|
|
40c38af766 | ||
|
|
fc2cee7a54 | ||
|
|
3d4feff7de | ||
|
|
f63c6875cd | ||
|
|
115b72149c | ||
|
|
a7e8118c83 | ||
|
|
8569cc5ac6 | ||
|
|
7a0ffce36f | ||
|
|
c573d95ec5 | ||
|
|
d3e1fba4e0 | ||
|
|
e7dd087b52 | ||
|
|
c7cb79906c | ||
|
|
3c3f80c5a0 | ||
|
|
e20fb3c3ac | ||
|
|
e3986a4dd4 | ||
|
|
a3926e3996 | ||
|
|
e737683efb | ||
|
|
0157c4ed65 | ||
|
|
058ade266d | ||
|
|
e0adb4c397 | ||
|
|
35aa61bb23 | ||
|
|
27236d19cf | ||
|
|
063ca3121a | ||
|
|
b8521c6875 | ||
|
|
e3798d6462 | ||
|
|
387a3e541f | ||
|
|
42a3b411e4 | ||
|
|
25c7ec4175 | ||
|
|
7ec11765da | ||
|
|
d57ae126b7 | ||
|
|
87f133555c | ||
|
|
db9883d851 | ||
|
|
8dad5ff7db | ||
|
|
fc36f9cd8c | ||
|
|
563c668e3f | ||
|
|
b3df1f3d26 | ||
|
|
1ac3f7af3b | ||
|
|
6cbb86aed7 | ||
|
|
e459a8c88b | ||
|
|
ca003b7d4d | ||
|
|
03f873a6d3 | ||
|
|
5dbec53847 | ||
|
|
49dfad9b0c | ||
|
|
4ee0462d1b | ||
|
|
3f78be4471 | ||
|
|
d93fad39ac | ||
|
|
aa3429bb0e | ||
|
|
d88246f2f6 | ||
|
|
201db34936 | ||
|
|
47709dfc7c | ||
|
|
2aaafc54ee | ||
|
|
e211881691 | ||
|
|
0d32f16041 | ||
|
|
2082e8d462 | ||
|
|
bfc95cfc57 | ||
|
|
35aeb7e04a | ||
|
|
adcddb09cd |
@@ -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
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ APP_DEBUG=true
|
|||||||
APP_FORCE_SSL=false
|
APP_FORCE_SSL=false
|
||||||
APP_FORCE_ROOT=
|
APP_FORCE_ROOT=
|
||||||
APP_KEY=TestTestTestTestTestTestTestTest
|
APP_KEY=TestTestTestTestTestTestTestTest
|
||||||
|
APP_LOG=daily
|
||||||
APP_LOG_LEVEL=debug
|
APP_LOG_LEVEL=debug
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ REDIS_HOST=127.0.0.1
|
|||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
MAIL_DRIVER=smtp
|
MAIL_DRIVER=log
|
||||||
MAIL_HOST=mailtrap.io
|
MAIL_HOST=mailtrap.io
|
||||||
MAIL_PORT=2525
|
MAIL_PORT=2525
|
||||||
MAIL_FROM=changeme@example.com
|
MAIL_FROM=changeme@example.com
|
||||||
|
|||||||
56
.github/CONTRIBUTING.md
vendored
56
.github/CONTRIBUTING.md
vendored
@@ -4,16 +4,64 @@
|
|||||||
|
|
||||||
## Feature requests
|
## Feature requests
|
||||||
|
|
||||||
If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/).
|
I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/).
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary.
|
First of all: thank you for reporting a bug instead of ditching the tool altogether. If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary. Bugs have a lot of priority!
|
||||||
|
|
||||||
## Installation problems
|
## Installation problems
|
||||||
|
|
||||||
Take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them!
|
Please take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! If not, open an issue and I will help where I can.
|
||||||
|
|
||||||
## Pull requests
|
## Pull requests
|
||||||
|
|
||||||
I can only accept pull requests against the `develop` branch, never the `master` branch.
|
When contributing to Firefly III, please first discuss the change you wish to make via issue, email, or any other method. I can only accept pull requests against the `develop` branch, never the `master` branch.
|
||||||
|
|
||||||
|
## Translations :us: :fr: :de:
|
||||||
|
|
||||||
|
If you see a spelling error, grammatical error or a weird translation in your language, please join [our CrowdIn](https://crowdin.com/project/firefly-iii) project. There, you can submit your translations and fixes. The GitHub repository will download these automatically and they will be included in the next release.
|
||||||
|
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at thegrumpydictator@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org/), version 1.4, available at [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4).
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,14 +1,7 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
/public/storage
|
/public/storage
|
||||||
/vendor
|
/vendor
|
||||||
/.idea
|
|
||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
.env
|
.env
|
||||||
_development
|
|
||||||
.env.local
|
|
||||||
result.html
|
|
||||||
test-import.sh
|
|
||||||
test-import-report.txt
|
|
||||||
public/google*.html
|
public/google*.html
|
||||||
.env.backup
|
|
||||||
|
|||||||
@@ -232,27 +232,52 @@ opt/app/app/Console/Kernel.php
|
|||||||
opt/app/app/Exceptions/Handler.php
|
opt/app/app/Exceptions/Handler.php
|
||||||
opt/app/app/Generator/Chart/Basic/ChartJsGenerator.php
|
opt/app/app/Generator/Chart/Basic/ChartJsGenerator.php
|
||||||
opt/app/app/Generator/Chart/Basic/GeneratorInterface.php
|
opt/app/app/Generator/Chart/Basic/GeneratorInterface.php
|
||||||
|
opt/app/app/Generator/Report/ReportGeneratorFactory.php
|
||||||
|
opt/app/app/Generator/Report/ReportGeneratorInterface.php
|
||||||
|
opt/app/app/Generator/Report/Standard/MonthReportGenerator.php
|
||||||
opt/app/app/Helpers/Attachments/AttachmentHelper.php
|
opt/app/app/Helpers/Attachments/AttachmentHelper.php
|
||||||
opt/app/app/Helpers/Attachments/AttachmentHelperInterface.php
|
opt/app/app/Helpers/Attachments/AttachmentHelperInterface.php
|
||||||
|
opt/app/app/Helpers/Collection/Balance.php
|
||||||
|
opt/app/app/Helpers/Collection/BalanceEntry.php
|
||||||
|
opt/app/app/Helpers/Collection/BalanceHeader.php
|
||||||
|
opt/app/app/Helpers/Collection/BalanceLine.php
|
||||||
|
opt/app/app/Helpers/Collection/Bill.php
|
||||||
opt/app/app/Helpers/Collector/JournalCollector.php
|
opt/app/app/Helpers/Collector/JournalCollector.php
|
||||||
opt/app/app/Helpers/Collector/JournalCollectorInterface.php
|
opt/app/app/Helpers/Collector/JournalCollectorInterface.php
|
||||||
opt/app/app/Helpers/FiscalHelper.php
|
opt/app/app/Helpers/FiscalHelper.php
|
||||||
opt/app/app/Helpers/FiscalHelperInterface.php
|
opt/app/app/Helpers/FiscalHelperInterface.php
|
||||||
|
opt/app/app/Helpers/Report/BalanceReportHelper.php
|
||||||
|
opt/app/app/Helpers/Report/BalanceReportHelperInterface.php
|
||||||
|
opt/app/app/Helpers/Report/BudgetReportHelper.php
|
||||||
|
opt/app/app/Helpers/Report/BudgetReportHelperInterface.php
|
||||||
opt/app/app/Helpers/Report/ReportHelper.php
|
opt/app/app/Helpers/Report/ReportHelper.php
|
||||||
opt/app/app/Helpers/Report/ReportHelperInterface.php
|
opt/app/app/Helpers/Report/ReportHelperInterface.php
|
||||||
|
opt/app/app/Http/Controllers/AccountController.php
|
||||||
opt/app/app/Http/Controllers/Auth/LoginController.php
|
opt/app/app/Http/Controllers/Auth/LoginController.php
|
||||||
|
opt/app/app/Http/Controllers/BillController.php
|
||||||
opt/app/app/Http/Controllers/BudgetController.php
|
opt/app/app/Http/Controllers/BudgetController.php
|
||||||
|
opt/app/app/Http/Controllers/CategoryController.php
|
||||||
opt/app/app/Http/Controllers/Chart/AccountController.php
|
opt/app/app/Http/Controllers/Chart/AccountController.php
|
||||||
opt/app/app/Http/Controllers/Chart/BudgetController.php
|
opt/app/app/Http/Controllers/Chart/BudgetController.php
|
||||||
opt/app/app/Http/Controllers/Chart/CategoryController.php
|
opt/app/app/Http/Controllers/Chart/CategoryController.php
|
||||||
opt/app/app/Http/Controllers/Controller.php
|
opt/app/app/Http/Controllers/Controller.php
|
||||||
opt/app/app/Http/Controllers/HomeController.php
|
opt/app/app/Http/Controllers/HomeController.php
|
||||||
|
opt/app/app/Http/Controllers/ImportController.php
|
||||||
opt/app/app/Http/Controllers/JavascriptController.php
|
opt/app/app/Http/Controllers/JavascriptController.php
|
||||||
opt/app/app/Http/Controllers/JsonController.php
|
opt/app/app/Http/Controllers/JsonController.php
|
||||||
opt/app/app/Http/Controllers/NewUserController.php
|
opt/app/app/Http/Controllers/NewUserController.php
|
||||||
|
opt/app/app/Http/Controllers/PiggyBankController.php
|
||||||
opt/app/app/Http/Controllers/ProfileController.php
|
opt/app/app/Http/Controllers/ProfileController.php
|
||||||
|
opt/app/app/Http/Controllers/Report/AccountController.php
|
||||||
|
opt/app/app/Http/Controllers/Report/BalanceController.php
|
||||||
|
opt/app/app/Http/Controllers/Report/BudgetController.php
|
||||||
|
opt/app/app/Http/Controllers/Report/CategoryController.php
|
||||||
|
opt/app/app/Http/Controllers/Report/OperationsController.php
|
||||||
opt/app/app/Http/Controllers/ReportController.php
|
opt/app/app/Http/Controllers/ReportController.php
|
||||||
|
opt/app/app/Http/Controllers/RuleController.php
|
||||||
|
opt/app/app/Http/Controllers/TagController.php
|
||||||
opt/app/app/Http/Controllers/Transaction/SingleController.php
|
opt/app/app/Http/Controllers/Transaction/SingleController.php
|
||||||
|
opt/app/app/Http/Controllers/TransactionController.php
|
||||||
opt/app/app/Http/Kernel.php
|
opt/app/app/Http/Kernel.php
|
||||||
opt/app/app/Http/Middleware/Authenticate.php
|
opt/app/app/Http/Middleware/Authenticate.php
|
||||||
opt/app/app/Http/Middleware/AuthenticateTwoFactor.php
|
opt/app/app/Http/Middleware/AuthenticateTwoFactor.php
|
||||||
@@ -263,10 +288,16 @@ opt/app/app/Http/Middleware/RedirectIfAuthenticated.php
|
|||||||
opt/app/app/Http/Middleware/Sandstorm.php
|
opt/app/app/Http/Middleware/Sandstorm.php
|
||||||
opt/app/app/Http/Middleware/StartFireflySession.php
|
opt/app/app/Http/Middleware/StartFireflySession.php
|
||||||
opt/app/app/Http/Middleware/VerifyCsrfToken.php
|
opt/app/app/Http/Middleware/VerifyCsrfToken.php
|
||||||
|
opt/app/app/Http/Requests/BudgetFormRequest.php
|
||||||
|
opt/app/app/Http/Requests/BudgetIncomeRequest.php
|
||||||
|
opt/app/app/Http/Requests/CategoryFormRequest.php
|
||||||
opt/app/app/Http/Requests/JournalFormRequest.php
|
opt/app/app/Http/Requests/JournalFormRequest.php
|
||||||
opt/app/app/Http/Requests/NewUserFormRequest.php
|
opt/app/app/Http/Requests/NewUserFormRequest.php
|
||||||
|
opt/app/app/Http/Requests/PiggyBankFormRequest.php
|
||||||
opt/app/app/Http/Requests/ProfileFormRequest.php
|
opt/app/app/Http/Requests/ProfileFormRequest.php
|
||||||
|
opt/app/app/Http/Requests/ReportFormRequest.php
|
||||||
opt/app/app/Http/Requests/Request.php
|
opt/app/app/Http/Requests/Request.php
|
||||||
|
opt/app/app/Http/Requests/TagFormRequest.php
|
||||||
opt/app/app/Http/breadcrumbs.php
|
opt/app/app/Http/breadcrumbs.php
|
||||||
opt/app/app/Jobs/Job.php
|
opt/app/app/Jobs/Job.php
|
||||||
opt/app/app/Jobs/MailError.php
|
opt/app/app/Jobs/MailError.php
|
||||||
@@ -279,9 +310,15 @@ opt/app/app/Models/Budget.php
|
|||||||
opt/app/app/Models/BudgetLimit.php
|
opt/app/app/Models/BudgetLimit.php
|
||||||
opt/app/app/Models/Category.php
|
opt/app/app/Models/Category.php
|
||||||
opt/app/app/Models/Configuration.php
|
opt/app/app/Models/Configuration.php
|
||||||
|
opt/app/app/Models/Note.php
|
||||||
opt/app/app/Models/PiggyBank.php
|
opt/app/app/Models/PiggyBank.php
|
||||||
|
opt/app/app/Models/PiggyBankRepetition.php
|
||||||
opt/app/app/Models/Preference.php
|
opt/app/app/Models/Preference.php
|
||||||
opt/app/app/Models/Role.php
|
opt/app/app/Models/Role.php
|
||||||
|
opt/app/app/Models/Rule.php
|
||||||
|
opt/app/app/Models/RuleAction.php
|
||||||
|
opt/app/app/Models/RuleGroup.php
|
||||||
|
opt/app/app/Models/RuleTrigger.php
|
||||||
opt/app/app/Models/Tag.php
|
opt/app/app/Models/Tag.php
|
||||||
opt/app/app/Models/Transaction.php
|
opt/app/app/Models/Transaction.php
|
||||||
opt/app/app/Models/TransactionCurrency.php
|
opt/app/app/Models/TransactionCurrency.php
|
||||||
@@ -317,14 +354,24 @@ opt/app/app/Repositories/Budget/BudgetRepository.php
|
|||||||
opt/app/app/Repositories/Budget/BudgetRepositoryInterface.php
|
opt/app/app/Repositories/Budget/BudgetRepositoryInterface.php
|
||||||
opt/app/app/Repositories/Category/CategoryRepository.php
|
opt/app/app/Repositories/Category/CategoryRepository.php
|
||||||
opt/app/app/Repositories/Category/CategoryRepositoryInterface.php
|
opt/app/app/Repositories/Category/CategoryRepositoryInterface.php
|
||||||
|
opt/app/app/Repositories/Currency/CurrencyRepository.php
|
||||||
|
opt/app/app/Repositories/Currency/CurrencyRepositoryInterface.php
|
||||||
opt/app/app/Repositories/Journal/JournalRepository.php
|
opt/app/app/Repositories/Journal/JournalRepository.php
|
||||||
opt/app/app/Repositories/Journal/JournalRepositoryInterface.php
|
opt/app/app/Repositories/Journal/JournalRepositoryInterface.php
|
||||||
opt/app/app/Repositories/PiggyBank/PiggyBankRepository.php
|
opt/app/app/Repositories/PiggyBank/PiggyBankRepository.php
|
||||||
opt/app/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
|
opt/app/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
|
||||||
|
opt/app/app/Repositories/Rule/RuleRepository.php
|
||||||
|
opt/app/app/Repositories/Rule/RuleRepositoryInterface.php
|
||||||
|
opt/app/app/Repositories/RuleGroup/RuleGroupRepository.php
|
||||||
|
opt/app/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php
|
||||||
opt/app/app/Repositories/Tag/TagRepository.php
|
opt/app/app/Repositories/Tag/TagRepository.php
|
||||||
opt/app/app/Repositories/Tag/TagRepositoryInterface.php
|
opt/app/app/Repositories/Tag/TagRepositoryInterface.php
|
||||||
|
opt/app/app/Repositories/User/UserRepository.php
|
||||||
opt/app/app/Repositories/User/UserRepositoryInterface.php
|
opt/app/app/Repositories/User/UserRepositoryInterface.php
|
||||||
opt/app/app/Support/Amount.php
|
opt/app/app/Support/Amount.php
|
||||||
|
opt/app/app/Support/Binder/AccountList.php
|
||||||
|
opt/app/app/Support/Binder/BinderInterface.php
|
||||||
|
opt/app/app/Support/Binder/Date.php
|
||||||
opt/app/app/Support/CacheProperties.php
|
opt/app/app/Support/CacheProperties.php
|
||||||
opt/app/app/Support/Domain.php
|
opt/app/app/Support/Domain.php
|
||||||
opt/app/app/Support/ExpandedForm.php
|
opt/app/app/Support/ExpandedForm.php
|
||||||
@@ -386,25 +433,40 @@ opt/app/database/seeds/PermissionSeeder.php
|
|||||||
opt/app/database/seeds/TransactionCurrencySeeder.php
|
opt/app/database/seeds/TransactionCurrencySeeder.php
|
||||||
opt/app/database/seeds/TransactionTypeSeeder.php
|
opt/app/database/seeds/TransactionTypeSeeder.php
|
||||||
opt/app/public/css/bootstrap-multiselect.css
|
opt/app/public/css/bootstrap-multiselect.css
|
||||||
|
opt/app/public/css/bootstrap-sortable.css
|
||||||
opt/app/public/css/bootstrap-tagsinput.css
|
opt/app/public/css/bootstrap-tagsinput.css
|
||||||
opt/app/public/css/bootstrap-tour.min.css
|
opt/app/public/css/bootstrap-tour.min.css
|
||||||
opt/app/public/css/daterangepicker.css
|
opt/app/public/css/daterangepicker.css
|
||||||
opt/app/public/css/firefly.css
|
opt/app/public/css/firefly.css
|
||||||
opt/app/public/css/jquery-ui/jquery-ui.structure.min.css
|
opt/app/public/css/jquery-ui/jquery-ui.structure.min.css
|
||||||
opt/app/public/css/jquery-ui/jquery-ui.theme.min.css
|
opt/app/public/css/jquery-ui/jquery-ui.theme.min.css
|
||||||
|
opt/app/public/favicon-16x16.png
|
||||||
|
opt/app/public/favicon-32x32.png
|
||||||
opt/app/public/index.php
|
opt/app/public/index.php
|
||||||
|
opt/app/public/js/ff/accounts/edit.js
|
||||||
|
opt/app/public/js/ff/bills/create.js
|
||||||
opt/app/public/js/ff/budgets/index.js
|
opt/app/public/js/ff/budgets/index.js
|
||||||
|
opt/app/public/js/ff/categories/index.js
|
||||||
opt/app/public/js/ff/charts.defaults.js
|
opt/app/public/js/ff/charts.defaults.js
|
||||||
opt/app/public/js/ff/charts.js
|
opt/app/public/js/ff/charts.js
|
||||||
opt/app/public/js/ff/firefly.js
|
opt/app/public/js/ff/firefly.js
|
||||||
opt/app/public/js/ff/guest.js
|
opt/app/public/js/ff/guest.js
|
||||||
opt/app/public/js/ff/help.js
|
opt/app/public/js/ff/help.js
|
||||||
opt/app/public/js/ff/index.js
|
opt/app/public/js/ff/index.js
|
||||||
|
opt/app/public/js/ff/piggy-banks/create.js
|
||||||
|
opt/app/public/js/ff/piggy-banks/index.js
|
||||||
|
opt/app/public/js/ff/reports/default/all.js
|
||||||
|
opt/app/public/js/ff/reports/default/month.js
|
||||||
opt/app/public/js/ff/reports/index.js
|
opt/app/public/js/ff/reports/index.js
|
||||||
|
opt/app/public/js/ff/rules/index.js
|
||||||
|
opt/app/public/js/ff/tags/create-edit.js
|
||||||
|
opt/app/public/js/ff/tags/index.js
|
||||||
|
opt/app/public/js/ff/transactions/list.js
|
||||||
opt/app/public/js/ff/transactions/single/create.js
|
opt/app/public/js/ff/transactions/single/create.js
|
||||||
opt/app/public/js/lib/Chart.bundle.min.js
|
opt/app/public/js/lib/Chart.bundle.min.js
|
||||||
opt/app/public/js/lib/accounting.min.js
|
opt/app/public/js/lib/accounting.min.js
|
||||||
opt/app/public/js/lib/bootstrap-multiselect.js
|
opt/app/public/js/lib/bootstrap-multiselect.js
|
||||||
|
opt/app/public/js/lib/bootstrap-sortable.js
|
||||||
opt/app/public/js/lib/bootstrap-tagsinput.min.js
|
opt/app/public/js/lib/bootstrap-tagsinput.min.js
|
||||||
opt/app/public/js/lib/bootstrap-tour.min.js
|
opt/app/public/js/lib/bootstrap-tour.min.js
|
||||||
opt/app/public/js/lib/bootstrap3-typeahead.min.js
|
opt/app/public/js/lib/bootstrap3-typeahead.min.js
|
||||||
@@ -417,6 +479,7 @@ opt/app/public/lib/adminlte/css/AdminLTE.min.css
|
|||||||
opt/app/public/lib/adminlte/css/skins/skin-blue-light.min.css
|
opt/app/public/lib/adminlte/css/skins/skin-blue-light.min.css
|
||||||
opt/app/public/lib/adminlte/js/app.min.js
|
opt/app/public/lib/adminlte/js/app.min.js
|
||||||
opt/app/public/lib/bootstrap/css/bootstrap.min.css
|
opt/app/public/lib/bootstrap/css/bootstrap.min.css
|
||||||
|
opt/app/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
|
||||||
opt/app/public/lib/bootstrap/js/bootstrap.min.js
|
opt/app/public/lib/bootstrap/js/bootstrap.min.js
|
||||||
opt/app/public/lib/font-awesome/css/font-awesome.min.css
|
opt/app/public/lib/font-awesome/css/font-awesome.min.css
|
||||||
opt/app/public/lib/font-awesome/fonts/fontawesome-webfont.woff2
|
opt/app/public/lib/font-awesome/fonts/fontawesome-webfont.woff2
|
||||||
@@ -425,9 +488,19 @@ opt/app/resources/lang/en_US/config.php
|
|||||||
opt/app/resources/lang/en_US/firefly.php
|
opt/app/resources/lang/en_US/firefly.php
|
||||||
opt/app/resources/lang/en_US/form.php
|
opt/app/resources/lang/en_US/form.php
|
||||||
opt/app/resources/lang/en_US/help.php
|
opt/app/resources/lang/en_US/help.php
|
||||||
|
opt/app/resources/lang/en_US/list.php
|
||||||
opt/app/resources/lang/en_US/validation.php
|
opt/app/resources/lang/en_US/validation.php
|
||||||
|
opt/app/resources/views/accounts/delete.twig
|
||||||
|
opt/app/resources/views/accounts/edit.twig
|
||||||
|
opt/app/resources/views/accounts/index.twig
|
||||||
opt/app/resources/views/auth/login.twig
|
opt/app/resources/views/auth/login.twig
|
||||||
|
opt/app/resources/views/bills/create.twig
|
||||||
|
opt/app/resources/views/bills/index.twig
|
||||||
|
opt/app/resources/views/budgets/create.twig
|
||||||
|
opt/app/resources/views/budgets/income.twig
|
||||||
opt/app/resources/views/budgets/index.twig
|
opt/app/resources/views/budgets/index.twig
|
||||||
|
opt/app/resources/views/categories/create.twig
|
||||||
|
opt/app/resources/views/categories/index.twig
|
||||||
opt/app/resources/views/emails/error-html.twig
|
opt/app/resources/views/emails/error-html.twig
|
||||||
opt/app/resources/views/emails/error-text.twig
|
opt/app/resources/views/emails/error-text.twig
|
||||||
opt/app/resources/views/emails/footer-html.twig
|
opt/app/resources/views/emails/footer-html.twig
|
||||||
@@ -437,18 +510,31 @@ opt/app/resources/views/emails/header-text.twig
|
|||||||
opt/app/resources/views/error.twig
|
opt/app/resources/views/error.twig
|
||||||
opt/app/resources/views/form/amount.twig
|
opt/app/resources/views/form/amount.twig
|
||||||
opt/app/resources/views/form/balance.twig
|
opt/app/resources/views/form/balance.twig
|
||||||
|
opt/app/resources/views/form/checkbox.twig
|
||||||
opt/app/resources/views/form/date.twig
|
opt/app/resources/views/form/date.twig
|
||||||
opt/app/resources/views/form/feedback.twig
|
opt/app/resources/views/form/feedback.twig
|
||||||
|
opt/app/resources/views/form/file.twig
|
||||||
opt/app/resources/views/form/help.twig
|
opt/app/resources/views/form/help.twig
|
||||||
|
opt/app/resources/views/form/integer.twig
|
||||||
|
opt/app/resources/views/form/location.twig
|
||||||
|
opt/app/resources/views/form/multiRadio.twig
|
||||||
opt/app/resources/views/form/options.twig
|
opt/app/resources/views/form/options.twig
|
||||||
opt/app/resources/views/form/select.twig
|
opt/app/resources/views/form/select.twig
|
||||||
|
opt/app/resources/views/form/tags.twig
|
||||||
opt/app/resources/views/form/text.twig
|
opt/app/resources/views/form/text.twig
|
||||||
|
opt/app/resources/views/form/textarea.twig
|
||||||
|
opt/app/resources/views/import/index.twig
|
||||||
opt/app/resources/views/index.twig
|
opt/app/resources/views/index.twig
|
||||||
opt/app/resources/views/javascript/variables.twig
|
opt/app/resources/views/javascript/variables.twig
|
||||||
opt/app/resources/views/json/tour.twig
|
opt/app/resources/views/json/tour.twig
|
||||||
opt/app/resources/views/layout/default.twig
|
opt/app/resources/views/layout/default.twig
|
||||||
opt/app/resources/views/layout/guest.twig
|
opt/app/resources/views/layout/guest.twig
|
||||||
|
opt/app/resources/views/list/accounts.twig
|
||||||
|
opt/app/resources/views/list/bills.twig
|
||||||
|
opt/app/resources/views/list/categories.twig
|
||||||
|
opt/app/resources/views/list/journals-tasker.twig
|
||||||
opt/app/resources/views/list/journals-tiny-tasker.twig
|
opt/app/resources/views/list/journals-tiny-tasker.twig
|
||||||
|
opt/app/resources/views/list/piggy-banks.twig
|
||||||
opt/app/resources/views/new-user/index.twig
|
opt/app/resources/views/new-user/index.twig
|
||||||
opt/app/resources/views/partials/boxes.twig
|
opt/app/resources/views/partials/boxes.twig
|
||||||
opt/app/resources/views/partials/control-bar.twig
|
opt/app/resources/views/partials/control-bar.twig
|
||||||
@@ -456,11 +542,25 @@ opt/app/resources/views/partials/favicons.twig
|
|||||||
opt/app/resources/views/partials/flashes.twig
|
opt/app/resources/views/partials/flashes.twig
|
||||||
opt/app/resources/views/partials/menu-sidebar.twig
|
opt/app/resources/views/partials/menu-sidebar.twig
|
||||||
opt/app/resources/views/partials/page-header.twig
|
opt/app/resources/views/partials/page-header.twig
|
||||||
|
opt/app/resources/views/piggy-banks/create.twig
|
||||||
|
opt/app/resources/views/piggy-banks/index.twig
|
||||||
opt/app/resources/views/profile/change-password.twig
|
opt/app/resources/views/profile/change-password.twig
|
||||||
opt/app/resources/views/profile/delete-account.twig
|
opt/app/resources/views/profile/delete-account.twig
|
||||||
opt/app/resources/views/profile/index.twig
|
opt/app/resources/views/profile/index.twig
|
||||||
|
opt/app/resources/views/reports/default/month.twig
|
||||||
opt/app/resources/views/reports/index.twig
|
opt/app/resources/views/reports/index.twig
|
||||||
opt/app/resources/views/reports/options/no-options.twig
|
opt/app/resources/views/reports/options/no-options.twig
|
||||||
|
opt/app/resources/views/reports/partials/accounts.twig
|
||||||
|
opt/app/resources/views/reports/partials/balance.twig
|
||||||
|
opt/app/resources/views/reports/partials/bills.twig
|
||||||
|
opt/app/resources/views/reports/partials/budgets.twig
|
||||||
|
opt/app/resources/views/reports/partials/categories.twig
|
||||||
|
opt/app/resources/views/reports/partials/income-expenses.twig
|
||||||
|
opt/app/resources/views/reports/partials/operations.twig
|
||||||
|
opt/app/resources/views/rules/index.twig
|
||||||
|
opt/app/resources/views/tags/create.twig
|
||||||
|
opt/app/resources/views/tags/index.twig
|
||||||
|
opt/app/resources/views/transactions/index.twig
|
||||||
opt/app/resources/views/transactions/single/create.twig
|
opt/app/resources/views/transactions/single/create.twig
|
||||||
opt/app/routes/api.php
|
opt/app/routes/api.php
|
||||||
opt/app/routes/console.php
|
opt/app/routes/console.php
|
||||||
@@ -612,6 +712,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Http/Kernel.php
|
|||||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Logging/Log.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Logging/Log.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/MailQueue.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/MailQueue.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/Mailer.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Mail/Mailer.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/LengthAwarePaginator.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/Paginator.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/Paginator.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Factory.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Factory.php
|
||||||
@@ -682,6 +783,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Conc
|
|||||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Scope.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Scope.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php
|
||||||
@@ -808,8 +911,11 @@ opt/app/vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php
|
|||||||
opt/app/vendor/laravel/framework/src/Illuminate/Notifications/NotificationServiceProvider.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Notifications/NotificationServiceProvider.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindow.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/default.blade.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php
|
||||||
@@ -884,12 +990,14 @@ opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/App.php
|
|||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Config.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Config.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Cookie.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Event.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Event.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Gate.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Gate.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Input.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Input.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Log.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Log.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Mail.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Mail.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Request.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Request.php
|
||||||
@@ -914,6 +1022,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Support/ViewErrorBag.php
|
|||||||
opt/app/vendor/laravel/framework/src/Illuminate/Support/helpers.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Support/helpers.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php
|
||||||
|
opt/app/vendor/laravel/framework/src/Illuminate/Translation/MessageSelector.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Translation/Translator.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Translation/Translator.php
|
||||||
opt/app/vendor/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php
|
opt/app/vendor/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php
|
||||||
@@ -1214,14 +1323,17 @@ opt/app/vendor/twig/twig/lib/Twig/Node/Expression.php
|
|||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Array.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Array.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary.php
|
||||||
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php
|
||||||
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php
|
||||||
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Call.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Call.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php
|
||||||
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php
|
opt/app/vendor/twig/twig/lib/Twig/Node/Expression/Constant.php
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const pkgdef :Spk.PackageDefinition = (
|
|||||||
manifest = (
|
manifest = (
|
||||||
appTitle = (defaultText = "Firefly III"),
|
appTitle = (defaultText = "Firefly III"),
|
||||||
appVersion = 1,
|
appVersion = 1,
|
||||||
appMarketingVersion = (defaultText = "3.4.3"),
|
appMarketingVersion = (defaultText = "3.4.6"),
|
||||||
actions = [
|
actions = [
|
||||||
# Define your "new document" handlers here.
|
# Define your "new document" handlers here.
|
||||||
( nounPhrase = (defaultText = "administration"),
|
( nounPhrase = (defaultText = "administration"),
|
||||||
@@ -97,7 +97,7 @@ const pkgdef :Spk.PackageDefinition = (
|
|||||||
# `spk dev` will write a list of all the files your app uses to this file.
|
# `spk dev` will write a list of all the files your app uses to this file.
|
||||||
# You should review it later, before shipping your app.
|
# You should review it later, before shipping your app.
|
||||||
|
|
||||||
alwaysInclude = [],
|
alwaysInclude = ["app","bootstrap","config","database","public","resources","routes"],
|
||||||
# Fill this list with more names of files or directories that should be
|
# Fill this list with more names of files or directories that should be
|
||||||
# included in your package, even if not listed in sandstorm-files.list.
|
# included in your package, even if not listed in sandstorm-files.list.
|
||||||
# Use this to force-include stuff that you know you need but which may
|
# Use this to force-include stuff that you know you need but which may
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ cache:
|
|||||||
- $HOME/.composer/cache
|
- $HOME/.composer/cache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- if [[ "$(php -v | grep 'PHP 7')" ]]; then phpenv config-rm xdebug.ini; fi
|
|
||||||
- rm composer.lock
|
- rm composer.lock
|
||||||
- composer update --no-scripts
|
- composer update --no-scripts
|
||||||
- cp .env.testing .env
|
- cp .env.testing .env
|
||||||
@@ -17,10 +16,14 @@ install:
|
|||||||
- php artisan optimize
|
- php artisan optimize
|
||||||
- php artisan env
|
- php artisan env
|
||||||
- cp .env.testing .env
|
- cp .env.testing .env
|
||||||
- mv storage/database/databasecopy.sqlite storage/database/database.sqlite
|
- wget -q https://github.com/firefly-iii/test-data/raw/master/storage/database.sqlite -O storage/database/database.sqlite
|
||||||
|
- mkdir -p build/logs
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- phpunit
|
- phpunit -c phpunit.xml
|
||||||
|
|
||||||
|
#after_success:
|
||||||
|
# - travis_retry php vendor/bin/coveralls -x storage/build/clover.xml
|
||||||
|
|
||||||
# safelist
|
# safelist
|
||||||
branches:
|
branches:
|
||||||
|
|||||||
136
CHANGELOG.md
136
CHANGELOG.md
@@ -2,6 +2,115 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [4.6.0] - 2017-06-28
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Revamped import routine. Will be buggy.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Issue #667, postgresql reported by @skibbipl.
|
||||||
|
- Issue #680 by @Xeli
|
||||||
|
- Fixed #660
|
||||||
|
- Fixes #672, reported by @dzaikos
|
||||||
|
- Translation error fixed by
|
||||||
|
- Fix a bug where the balance routine forgot to account for accounts without a currency preference.
|
||||||
|
- Various other bugfixes.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Initial release.
|
||||||
|
|
||||||
|
## [4.5.0] - 2017-06-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Better support for multi-currency transactions and display of transactions, accounts and everything. This requires a database overhaul (moving the currency information to specific transactions) so be careful when upgrading.
|
||||||
|
- Translations for Spanish and Slovenian.
|
||||||
|
- New interface for budget page, ~~stolen from~~ inspired by YNAB.
|
||||||
|
- Expanded Docker to work with postgresql as well, thanks to @kressh
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- PostgreSQL support in database upgrade routine (#644, reported by @skibbipl)
|
||||||
|
- Frontpage budget chart was off, fix by @nhaarman
|
||||||
|
- Was not possible to remove opening balance.
|
||||||
|
|
||||||
|
## [4.4.3] - 2017-05-03
|
||||||
|
### Added
|
||||||
|
- Added support for Slovenian
|
||||||
|
- Removed support for Spanish. No translations whatsoever by the guy who requested it.
|
||||||
|
- Removed support for Russian. Same thing.
|
||||||
|
- Removed support for Croatian. Same thing.
|
||||||
|
- Removed support for Chinese Traditional, Hong Kong. Same thing.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- The journal collector, an internal piece of code to collect transactions, now uses a slightly different method of collecting journals. This may cause problems.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Issue #638 as reported by [worldworm](https://github.com/worldworm).
|
||||||
|
- Possible fix for #624
|
||||||
|
|
||||||
|
## [4.4.2] - 2017-04-27
|
||||||
|
### Fixed
|
||||||
|
- Fixed a bug where the opening balance could not be stored.
|
||||||
|
|
||||||
|
## [4.4.1] - 2017-04-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Support for deployment on Heroku
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Bug in new-user routine.
|
||||||
|
|
||||||
|
## [4.4.0] - 2017-04-23
|
||||||
|
### Added
|
||||||
|
- Firefly III can now handle foreign currencies better, including some code to get the exchange rate live from the web.
|
||||||
|
- Can now make rules for attachments, see #608, as suggested by dzaikos.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed #629, reported by forcaeluz
|
||||||
|
- Fixed #630, reported by welbert
|
||||||
|
- And more various bug fixes.
|
||||||
|
|
||||||
|
## [4.3.8] - 2017-04-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Better overview / show pages.
|
||||||
|
- #628, as reported by [xzaz](https://github.com/xzaz).
|
||||||
|
- Greatly expanded test coverage
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- #619, as reported by [dfiel](https://github.com/dfiel).
|
||||||
|
- #620, as reported by [forcaeluz](https://github.com/forcaeluz).
|
||||||
|
- Attempt to fix #624, as reported by [TheSerenin](https://github.com/TheSerenin).
|
||||||
|
- Favicon link is relative now, fixed by [welbert](https://github.com/welbert).
|
||||||
|
- Some search bugs
|
||||||
|
|
||||||
|
## [4.3.7] - 2017-03-06
|
||||||
|
### Added
|
||||||
|
- Nice user friendly views for empty lists.
|
||||||
|
- Extended contribution guidelines.
|
||||||
|
- First version of financial report filtered on tags.
|
||||||
|
- Suggested monthly savings for piggy banks, by [Zsub](https://github.com/Zsub)
|
||||||
|
- Better test coverage.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Slightly changed tag overview.
|
||||||
|
- Consistent icon for bill in list.
|
||||||
|
- Slightly changed account overview.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed IDE specific views from .gitignore, issue #598
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Force key generation during installation.
|
||||||
|
- The `date` function takes the fieldname where a date is stored, not the literal date by [Zsub](https://github.com/Zsub)
|
||||||
|
- Improved budget frontpage chart, as suggested by [skibbipl](https://github.com/skibbipl)
|
||||||
|
- Issue #602 and #607, as reported by [skibbipl](https://github.com/skibbipl) and [dzaikos](https://github.com/dzaikos).
|
||||||
|
- Issue #605, as reported by [Zsub](https://github.com/Zsub).
|
||||||
|
- Issue #599, as reported by [leander091](https://github.com/leander091).
|
||||||
|
- Issue #610, as reported by [skibbipl](https://github.com/skibbipl).
|
||||||
|
- Issue #611, as reported by [ragnarkarlsson](https://github.com/ragnarkarlsson).
|
||||||
|
- Issue #612, as reported by [ragnarkarlsson](https://github.com/ragnarkarlsson).
|
||||||
|
- Issue #614, as reported by [worldworm](https://github.com/worldworm).
|
||||||
|
- Various other bug fixes.
|
||||||
|
|
||||||
## [4.3.6] - 2017-02-20
|
## [4.3.6] - 2017-02-20
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -159,13 +268,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
|||||||
- Updated all email messages.
|
- Updated all email messages.
|
||||||
- Made some fonts local
|
- Made some fonts local
|
||||||
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- Initial release.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
- Initial release.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Issue #408
|
- Issue #408
|
||||||
- Various issues with split journals
|
- Various issues with split journals
|
||||||
@@ -174,11 +276,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
|||||||
- Issue #422, thx [xzaz](https://github.com/xzaz)
|
- Issue #422, thx [xzaz](https://github.com/xzaz)
|
||||||
- Various import bugs, such as #416 ([zjean](https://github.com/zjean))
|
- Various import bugs, such as #416 ([zjean](https://github.com/zjean))
|
||||||
|
|
||||||
|
|
||||||
### Security
|
|
||||||
- Initial release.
|
|
||||||
|
|
||||||
|
|
||||||
## [4.1.7] - 2016-11-19
|
## [4.1.7] - 2016-11-19
|
||||||
### Added
|
### Added
|
||||||
- Check for database table presence in console commands.
|
- Check for database table presence in console commands.
|
||||||
@@ -301,15 +398,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
|||||||
- New Presidents Choice specific to fix #307
|
- New Presidents Choice specific to fix #307
|
||||||
- Added some trimming (#335)
|
- Added some trimming (#335)
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Initial release.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- Initial release.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
- Initial release.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed a bug where incoming transactions would not be properly filtered in several reports.
|
- Fixed a bug where incoming transactions would not be properly filtered in several reports.
|
||||||
- #334 by [cyberkov](https://github.com/cyberkov)
|
- #334 by [cyberkov](https://github.com/cyberkov)
|
||||||
@@ -317,12 +405,6 @@ An intermediate release because something in the Twig and Twigbridge libraries i
|
|||||||
- #336
|
- #336
|
||||||
- #338 found by [roberthorlings](https://github.com/roberthorlings)
|
- #338 found by [roberthorlings](https://github.com/roberthorlings)
|
||||||
|
|
||||||
### Security
|
|
||||||
- Initial release.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.0.0] - 2015-09-26
|
## [4.0.0] - 2015-09-26
|
||||||
### Added
|
### Added
|
||||||
- Upgraded to Laravel 5.3, most other libraries upgraded as well.
|
- Upgraded to Laravel 5.3, most other libraries upgraded as well.
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ RUN apt-get update -y && \
|
|||||||
libtidy-dev \
|
libtidy-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
libsqlite3-dev \
|
libsqlite3-dev \
|
||||||
|
libpq-dev \
|
||||||
libbz2-dev \
|
libbz2-dev \
|
||||||
gettext-base \
|
gettext-base \
|
||||||
locales && \
|
locales && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2
|
RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2 pdo_pgsql
|
||||||
|
|
||||||
# Generate locales supported by firefly
|
# Generate locales supported by firefly
|
||||||
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
||||||
|
|||||||
1
Procfile
Normal file
1
Procfile
Normal file
@@ -0,0 +1 @@
|
|||||||
|
web: vendor/bin/heroku-php-nginx -C nginx_app.conf public/
|
||||||
33
README.md
33
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Firefly III: A personal finances manager
|
# Firefly III: A personal finances manager
|
||||||
|
|
||||||
[](https://secure.php.net/downloads.php#v7.0.4) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [](https://travis-ci.org/firefly-iii/firefly-iii) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
[](https://secure.php.net/downloads.php) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://creativecommons.org/licenses/by-sa/4.0/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||||
|
|
||||||
[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
|
[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
|
||||||
|
|
||||||
@@ -10,11 +10,13 @@
|
|||||||
|
|
||||||
## Try it out!
|
## Try it out!
|
||||||
|
|
||||||
Try out Firefly III on the [demo site](https://firefly-iii.nder.be/).
|
[](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
|
||||||
|
|
||||||
## Installation
|
Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
|
||||||
|
|
||||||
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/installation-guide/).
|
## Getting started
|
||||||
|
|
||||||
|
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html).
|
||||||
|
|
||||||
## More about Firefly III
|
## More about Firefly III
|
||||||
|
|
||||||
@@ -22,7 +24,7 @@ Personal financial management is pretty difficult, and everybody has their own a
|
|||||||
|
|
||||||
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
|
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
|
||||||
|
|
||||||
#### Some advantages of using Firefly
|
### Some advantages of using Firefly
|
||||||
|
|
||||||
- Firefly can import any CSV file, so migrating from other systems is easy.
|
- Firefly can import any CSV file, so migrating from other systems is easy.
|
||||||
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", it’s just somebody else’s computer!
|
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", it’s just somebody else’s computer!
|
||||||
@@ -31,6 +33,27 @@ Firefly works on the principle that if you know where you're money is going, you
|
|||||||
|
|
||||||
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
|
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
Please read [CONTRIBUTING.md](https://github.com/firefly-iii/firefly-iii/blob/master/.github/CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests.
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
|
||||||
|
We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository.
|
||||||
|
|
||||||
|
### Authors
|
||||||
|
|
||||||
|
* James Cole
|
||||||
|
* Over time, [many people have contributed to Firefly III](https://github.com/firefly-iii/firefly-iii/graphs/contributors).
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||||
|
|
||||||
|
### Other stuff
|
||||||
|
|
||||||
If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
|
If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
|
||||||
|
|
||||||
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
|
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
|
||||||
|
|
||||||
|
[](https://travis-ci.org/firefly-iii/firefly-iii) [](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master)
|
||||||
|
|||||||
59
app.json
Normal file
59
app.json
Normal 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="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -84,11 +84,11 @@ class CreateImport extends Command
|
|||||||
$job = $jobRepository->create($type);
|
$job = $jobRepository->create($type);
|
||||||
$this->line(sprintf('Created job "%s"...', $job->key));
|
$this->line(sprintf('Created job "%s"...', $job->key));
|
||||||
|
|
||||||
Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]);
|
Artisan::call('firefly:encrypt-file', ['file' => $file, 'key' => $job->key]);
|
||||||
$this->line('Stored import data...');
|
$this->line('Stored import data...');
|
||||||
|
|
||||||
$job->configuration = $configurationData;
|
$job->configuration = $configurationData;
|
||||||
$job->status = 'settings_complete';
|
$job->status = 'configured';
|
||||||
$job->save();
|
$job->save();
|
||||||
$this->line('Stored configuration...');
|
$this->line('Stored configuration...');
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Console\Commands;
|
namespace FireflyIII\Console\Commands;
|
||||||
|
|
||||||
use FireflyIII\Import\ImportProcedure;
|
|
||||||
use FireflyIII\Import\Logging\CommandHandler;
|
use FireflyIII\Import\Logging\CommandHandler;
|
||||||
|
use FireflyIII\Import\Routine\ImportRoutine;
|
||||||
use FireflyIII\Models\ImportJob;
|
use FireflyIII\Models\ImportJob;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\MessageBag;
|
||||||
use Log;
|
use Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,7 +59,12 @@ class Import extends Command
|
|||||||
{
|
{
|
||||||
Log::debug('Start start-import command');
|
Log::debug('Start start-import command');
|
||||||
$jobKey = $this->argument('key');
|
$jobKey = $this->argument('key');
|
||||||
$job = ImportJob::whereKey($jobKey)->first();
|
$job = ImportJob::where('key', $jobKey)->first();
|
||||||
|
if (is_null($job)) {
|
||||||
|
$this->error(sprintf('No job found with key "%s"', $jobKey));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!$this->isValid($job)) {
|
if (!$this->isValid($job)) {
|
||||||
Log::error('Job is not valid for some reason. Exit.');
|
Log::error('Job is not valid for some reason. Exit.');
|
||||||
|
|
||||||
@@ -70,15 +76,18 @@ class Import extends Command
|
|||||||
$monolog = Log::getMonolog();
|
$monolog = Log::getMonolog();
|
||||||
$handler = new CommandHandler($this);
|
$handler = new CommandHandler($this);
|
||||||
$monolog->pushHandler($handler);
|
$monolog->pushHandler($handler);
|
||||||
$importProcedure = new ImportProcedure;
|
|
||||||
$result = $importProcedure->runImport($job);
|
|
||||||
|
|
||||||
// display result to user:
|
/** @var ImportRoutine $routine */
|
||||||
$this->presentResults($result);
|
$routine = app(ImportRoutine::class);
|
||||||
$this->line('The import has completed.');
|
$routine->setJob($job);
|
||||||
|
$routine->run();
|
||||||
|
|
||||||
// get any errors from the importer:
|
/** @var MessageBag $error */
|
||||||
$this->presentErrors($job);
|
foreach ($routine->errors as $index => $error) {
|
||||||
|
$this->error(sprintf('Error importing line #%d: %s', $index, $error));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line(sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->journals->count(), $routine->lines));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -91,12 +100,14 @@ class Import extends Command
|
|||||||
private function isValid(ImportJob $job): bool
|
private function isValid(ImportJob $job): bool
|
||||||
{
|
{
|
||||||
if (is_null($job)) {
|
if (is_null($job)) {
|
||||||
|
Log::error('This job does not seem to exist.');
|
||||||
$this->error('This job does not seem to exist.');
|
$this->error('This job does not seem to exist.');
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($job->status != 'settings_complete') {
|
if ($job->status !== 'configured') {
|
||||||
|
Log::error(sprintf('This job is not ready to be imported (status is %s).', $job->status));
|
||||||
$this->error('This job is not ready to be imported.');
|
$this->error('This job is not ready to be imported.');
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -104,36 +115,4 @@ class Import extends Command
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ImportJob $job
|
|
||||||
*/
|
|
||||||
private function presentErrors(ImportJob $job)
|
|
||||||
{
|
|
||||||
$extendedStatus = $job->extended_status;
|
|
||||||
if (isset($extendedStatus['errors']) && count($extendedStatus['errors']) > 0) {
|
|
||||||
$this->line(sprintf('The following %d error(s) occured during the import:', count($extendedStatus['errors'])));
|
|
||||||
foreach ($extendedStatus['errors'] as $error) {
|
|
||||||
$this->error($error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $result
|
|
||||||
*/
|
|
||||||
private function presentResults(Collection $result)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var int $index
|
|
||||||
* @var TransactionJournal $journal
|
|
||||||
*/
|
|
||||||
foreach ($result as $index => $journal) {
|
|
||||||
if (!is_null($journal->id)) {
|
|
||||||
$this->line(sprintf('Line #%d has been imported as transaction #%d.', $index, $journal->id));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$this->error(sprintf('Could not store line #%d', $index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,24 @@ namespace FireflyIII\Console\Commands;
|
|||||||
|
|
||||||
|
|
||||||
use DB;
|
use DB;
|
||||||
|
use FireflyIII\Models\Account;
|
||||||
|
use FireflyIII\Models\AccountMeta;
|
||||||
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\BudgetLimit;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\LimitRepetition;
|
||||||
|
use FireflyIII\Models\PiggyBankEvent;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionCurrency;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Database\Query\JoinClause;
|
||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Log;
|
use Log;
|
||||||
|
use Preferences;
|
||||||
use Schema;
|
use Schema;
|
||||||
|
use Steam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class UpgradeDatabase
|
* Class UpgradeDatabase
|
||||||
@@ -60,8 +70,60 @@ class UpgradeDatabase extends Command
|
|||||||
{
|
{
|
||||||
$this->setTransactionIdentifier();
|
$this->setTransactionIdentifier();
|
||||||
$this->migrateRepetitions();
|
$this->migrateRepetitions();
|
||||||
|
$this->repairPiggyBanks();
|
||||||
|
$this->updateAccountCurrencies();
|
||||||
|
$this->updateJournalCurrencies();
|
||||||
|
$this->currencyInfoToTransactions();
|
||||||
|
$this->verifyCurrencyInfo();
|
||||||
|
$this->info('Firefly III database is up to date.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the currency id info to the transaction instead of the journal.
|
||||||
|
*/
|
||||||
|
private function currencyInfoToTransactions()
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
|
$set = TransactionJournal::with('transactions')->get();
|
||||||
|
/** @var TransactionJournal $journal */
|
||||||
|
foreach ($set as $journal) {
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($journal->transactions as $transaction) {
|
||||||
|
if (is_null($transaction->transaction_currency_id)) {
|
||||||
|
$transaction->transaction_currency_id = $journal->transaction_currency_id;
|
||||||
|
$transaction->save();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// read and use the foreign amounts when present.
|
||||||
|
if ($journal->hasMeta('foreign_amount')) {
|
||||||
|
$amount = Steam::positive($journal->getMeta('foreign_amount'));
|
||||||
|
|
||||||
|
// update both transactions:
|
||||||
|
foreach ($journal->transactions as $transaction) {
|
||||||
|
$transaction->foreign_amount = $amount;
|
||||||
|
if (bccomp($transaction->amount, '0') === -1) {
|
||||||
|
// update with negative amount:
|
||||||
|
$transaction->foreign_amount = bcmul($amount, '-1');
|
||||||
|
}
|
||||||
|
// set foreign currency id:
|
||||||
|
$transaction->foreign_currency_id = intval($journal->getMeta('foreign_currency_id'));
|
||||||
|
$transaction->save();
|
||||||
|
}
|
||||||
|
$journal->deleteMeta('foreign_amount');
|
||||||
|
$journal->deleteMeta('foreign_currency_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line(sprintf('Updated currency information for %d transactions', $count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate budget repetitions to new format.
|
||||||
|
*/
|
||||||
private function migrateRepetitions()
|
private function migrateRepetitions()
|
||||||
{
|
{
|
||||||
if (!Schema::hasTable('budget_limits')) {
|
if (!Schema::hasTable('budget_limits')) {
|
||||||
@@ -69,7 +131,9 @@ class UpgradeDatabase extends Command
|
|||||||
}
|
}
|
||||||
// get all budget limits with end_date NULL
|
// get all budget limits with end_date NULL
|
||||||
$set = BudgetLimit::whereNull('end_date')->get();
|
$set = BudgetLimit::whereNull('end_date')->get();
|
||||||
|
if ($set->count() > 0) {
|
||||||
$this->line(sprintf('Found %d budget limit(s) to update', $set->count()));
|
$this->line(sprintf('Found %d budget limit(s) to update', $set->count()));
|
||||||
|
}
|
||||||
/** @var BudgetLimit $budgetLimit */
|
/** @var BudgetLimit $budgetLimit */
|
||||||
foreach ($set as $budgetLimit) {
|
foreach ($set as $budgetLimit) {
|
||||||
// get limit repetition (should be just one):
|
// get limit repetition (should be just one):
|
||||||
@@ -84,6 +148,37 @@ class UpgradeDatabase extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure there are only transfers linked to piggy bank events.
|
||||||
|
*/
|
||||||
|
private function repairPiggyBanks()
|
||||||
|
{
|
||||||
|
// if table does not exist, return false
|
||||||
|
if (!Schema::hasTable('piggy_bank_events')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
|
||||||
|
/** @var PiggyBankEvent $event */
|
||||||
|
foreach ($set as $event) {
|
||||||
|
|
||||||
|
if (is_null($event->transaction_journal_id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/** @var TransactionJournal $journal */
|
||||||
|
$journal = $event->transactionJournal()->first();
|
||||||
|
if (is_null($journal)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = $journal->transactionType->type;
|
||||||
|
if ($type !== TransactionType::TRANSFER) {
|
||||||
|
$event->transaction_journal_id = null;
|
||||||
|
$event->save();
|
||||||
|
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
|
* This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
|
||||||
*/
|
*/
|
||||||
@@ -112,6 +207,57 @@ class UpgradeDatabase extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function updateAccountCurrencies()
|
||||||
|
{
|
||||||
|
$accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||||
|
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']);
|
||||||
|
|
||||||
|
/** @var Account $account */
|
||||||
|
foreach ($accounts as $account) {
|
||||||
|
// get users preference, fall back to system pref.
|
||||||
|
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
|
||||||
|
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||||
|
$accountCurrency = intval($account->getMeta('currency_id'));
|
||||||
|
$openingBalance = $account->getOpeningBalance();
|
||||||
|
$openingBalanceCurrency = intval($openingBalance->transaction_currency_id);
|
||||||
|
|
||||||
|
// both 0? set to default currency:
|
||||||
|
if ($accountCurrency === 0 && $openingBalanceCurrency === 0) {
|
||||||
|
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||||
|
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// opening balance 0, account not zero? just continue:
|
||||||
|
if ($accountCurrency > 0 && $openingBalanceCurrency === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// account is set to 0, opening balance is not?
|
||||||
|
if ($accountCurrency === 0 && $openingBalanceCurrency > 0) {
|
||||||
|
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $openingBalanceCurrency]);
|
||||||
|
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// both are equal, just continue:
|
||||||
|
if ($accountCurrency === $openingBalanceCurrency) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// do not match:
|
||||||
|
if ($accountCurrency !== $openingBalanceCurrency) {
|
||||||
|
// update opening balance:
|
||||||
|
$openingBalance->transaction_currency_id = $accountCurrency;
|
||||||
|
$openingBalance->save();
|
||||||
|
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
|
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
|
||||||
* which has 0 as an identifier and give it the same identifier.
|
* which has 0 as an identifier and give it the same identifier.
|
||||||
@@ -151,9 +297,116 @@ class UpgradeDatabase extends Command
|
|||||||
$opposing->save();
|
$opposing->save();
|
||||||
$processed[] = $transaction->id;
|
$processed[] = $transaction->id;
|
||||||
$processed[] = $opposing->id;
|
$processed[] = $opposing->id;
|
||||||
$this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id));
|
|
||||||
}
|
}
|
||||||
$identifier++;
|
$identifier++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure that withdrawals, deposits and transfers have
|
||||||
|
* a currency setting matching their respective accounts
|
||||||
|
*/
|
||||||
|
private function updateJournalCurrencies()
|
||||||
|
{
|
||||||
|
$types = [
|
||||||
|
TransactionType::WITHDRAWAL => '<',
|
||||||
|
TransactionType::DEPOSIT => '>',
|
||||||
|
];
|
||||||
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
|
$notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.';
|
||||||
|
$transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.';
|
||||||
|
$driver = DB::connection()->getDriverName();
|
||||||
|
$pgsql = ['pgsql', 'postgresql'];
|
||||||
|
|
||||||
|
foreach ($types as $type => $operator) {
|
||||||
|
$query = TransactionJournal
|
||||||
|
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin(
|
||||||
|
'transactions', function (JoinClause $join) use ($operator) {
|
||||||
|
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0');
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||||
|
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
|
||||||
|
->where('transaction_types.type', $type)
|
||||||
|
->where('account_meta.name', 'currency_id');
|
||||||
|
if (in_array($driver, $pgsql)) {
|
||||||
|
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)'));
|
||||||
|
}
|
||||||
|
if (!in_array($driver, $pgsql)) {
|
||||||
|
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$set = $query->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']);
|
||||||
|
/** @var TransactionJournal $journal */
|
||||||
|
foreach ($set as $journal) {
|
||||||
|
$expectedCurrency = $repository->find(intval($journal->expected_currency_id));
|
||||||
|
$line = sprintf($notification, $type, $journal->id, $journal->transactionCurrency->code, $expectedCurrency->code);
|
||||||
|
|
||||||
|
$journal->setMeta('foreign_amount', $journal->transaction_amount);
|
||||||
|
$journal->setMeta('foreign_currency_id', $journal->transaction_currency_id);
|
||||||
|
$journal->transaction_currency_id = $expectedCurrency->id;
|
||||||
|
$journal->save();
|
||||||
|
$this->line($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* For transfers it's slightly different. Both source and destination
|
||||||
|
* must match the respective currency preference. So we must verify ALL
|
||||||
|
* transactions.
|
||||||
|
*/
|
||||||
|
$set = TransactionJournal
|
||||||
|
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||||
|
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||||
|
->get(['transaction_journals.*']);
|
||||||
|
/** @var TransactionJournal $journal */
|
||||||
|
foreach ($set as $journal) {
|
||||||
|
$updated = false;
|
||||||
|
/** @var Transaction $sourceTransaction */
|
||||||
|
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||||
|
$sourceCurrency = $repository->find(intval($sourceTransaction->account->getMeta('currency_id')));
|
||||||
|
|
||||||
|
if ($sourceCurrency->id !== $journal->transaction_currency_id) {
|
||||||
|
$updated = true;
|
||||||
|
$journal->transaction_currency_id = $sourceCurrency->id;
|
||||||
|
$journal->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// destination
|
||||||
|
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
|
||||||
|
$destinationCurrency = $repository->find(intval($destinationTransaction->account->getMeta('currency_id')));
|
||||||
|
|
||||||
|
if ($destinationCurrency->id !== $journal->transaction_currency_id) {
|
||||||
|
$updated = true;
|
||||||
|
$journal->deleteMeta('foreign_amount');
|
||||||
|
$journal->deleteMeta('foreign_currency_id');
|
||||||
|
$journal->setMeta('foreign_amount', $destinationTransaction->amount);
|
||||||
|
$journal->setMeta('foreign_currency_id', $destinationCurrency->id);
|
||||||
|
}
|
||||||
|
if ($updated) {
|
||||||
|
$line = sprintf($transfer, $journal->id);
|
||||||
|
$this->line($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function verifyCurrencyInfo()
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
|
$transactions = Transaction::get();
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$currencyId = intval($transaction->transaction_currency_id);
|
||||||
|
$foreignId = intval($transaction->foreign_currency_id);
|
||||||
|
if ($currencyId === $foreignId) {
|
||||||
|
$transaction->foreign_currency_id = null;
|
||||||
|
$transaction->foreign_amount = null;
|
||||||
|
$transaction->save();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->line(sprintf('Updated currency information for %d transactions', $count));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Events;
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ class StoredTransactionJournal extends Event
|
|||||||
|
|
||||||
use SerializesModels;
|
use SerializesModels;
|
||||||
|
|
||||||
|
/** @var TransactionJournal */
|
||||||
public $journal;
|
public $journal;
|
||||||
|
/** @var int */
|
||||||
public $piggyBankId;
|
public $piggyBankId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class UpdatedTransactionJournal extends Event
|
|||||||
|
|
||||||
use SerializesModels;
|
use SerializesModels;
|
||||||
|
|
||||||
|
/** @var TransactionJournal */
|
||||||
public $journal;
|
public $journal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Exceptions;
|
namespace FireflyIII\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Exceptions;
|
namespace FireflyIII\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Exceptions;
|
namespace FireflyIII\Exceptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Export\Collector;
|
namespace FireflyIII\Export\Collector;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Crypt;
|
|
||||||
use DB;
|
use DB;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use Illuminate\Database\Query\JoinClause;
|
use Illuminate\Database\Query\JoinClause;
|
||||||
@@ -304,7 +303,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac
|
|||||||
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||||
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
|
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
|
||||||
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
|
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
|
||||||
->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id')
|
->leftJoin('transaction_currencies', 'transactions.transaction_currency_id', '=', 'transaction_currencies.id')
|
||||||
->whereIn('transactions.account_id', $accountIds)
|
->whereIn('transactions.account_id', $accountIds)
|
||||||
->where('transaction_journals.user_id', $this->job->user_id)
|
->where('transaction_journals.user_id', $this->job->user_id)
|
||||||
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
|
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
|
||||||
@@ -339,7 +338,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac
|
|||||||
'transaction_journals.encrypted as journal_encrypted',
|
'transaction_journals.encrypted as journal_encrypted',
|
||||||
'transaction_journals.transaction_type_id',
|
'transaction_journals.transaction_type_id',
|
||||||
'transaction_types.type as transaction_type',
|
'transaction_types.type as transaction_type',
|
||||||
'transaction_journals.transaction_currency_id',
|
'transactions.transaction_currency_id',
|
||||||
'transaction_currencies.code AS transaction_currency_code',
|
'transaction_currencies.code AS transaction_currency_code',
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Export\Entry;
|
namespace FireflyIII\Export\Entry;
|
||||||
|
|
||||||
use Crypt;
|
|
||||||
use Steam;
|
use Steam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ interface GeneratorInterface
|
|||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will generate a (ChartJS) compatible array from the given input. Expects this format:
|
* Will generate a Chart JS compatible array from the given input. Expects this format
|
||||||
|
*
|
||||||
|
* Will take labels for all from first set.
|
||||||
*
|
*
|
||||||
* 0: [
|
* 0: [
|
||||||
* 'label' => 'label of set',
|
* 'label' => 'label of set',
|
||||||
|
* 'type' => bar or line, optional
|
||||||
|
* 'yAxisID' => ID of yAxis, optional, will not be included when unused.
|
||||||
|
* 'fill' => if to fill a line? optional, will not be included when unused.
|
||||||
* 'entries' =>
|
* 'entries' =>
|
||||||
* [
|
* [
|
||||||
* 'label-of-entry' => 'value'
|
* 'label-of-entry' => 'value'
|
||||||
@@ -33,12 +38,16 @@ interface GeneratorInterface
|
|||||||
* ]
|
* ]
|
||||||
* 1: [
|
* 1: [
|
||||||
* 'label' => 'label of another set',
|
* 'label' => 'label of another set',
|
||||||
|
* 'type' => bar or line, optional
|
||||||
|
* 'yAxisID' => ID of yAxis, optional, will not be included when unused.
|
||||||
|
* 'fill' => if to fill a line? optional, will not be included when unused.
|
||||||
* 'entries' =>
|
* 'entries' =>
|
||||||
* [
|
* [
|
||||||
* 'label-of-entry' => 'value'
|
* 'label-of-entry' => 'value'
|
||||||
* ]
|
* ]
|
||||||
* ]
|
* ]
|
||||||
*
|
*
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five.
|
||||||
*
|
*
|
||||||
* @param array $data
|
* @param array $data
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
|||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Steam;
|
use Steam;
|
||||||
|
|
||||||
@@ -147,6 +148,8 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
|||||||
*/
|
*/
|
||||||
private function getAuditReport(Account $account, Carbon $date): array
|
private function getAuditReport(Account $account, Carbon $date): array
|
||||||
{
|
{
|
||||||
|
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||||
|
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||||
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
@@ -155,15 +158,21 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
|||||||
$journals = $journals->reverse();
|
$journals = $journals->reverse();
|
||||||
$dayBeforeBalance = Steam::balance($account, $date);
|
$dayBeforeBalance = Steam::balance($account, $date);
|
||||||
$startBalance = $dayBeforeBalance;
|
$startBalance = $dayBeforeBalance;
|
||||||
|
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
|
||||||
|
|
||||||
/** @var Transaction $journal */
|
/** @var Transaction $journal */
|
||||||
foreach ($journals as $transaction) {
|
foreach ($journals as $transaction) {
|
||||||
$transaction->before = $startBalance;
|
$transaction->before = $startBalance;
|
||||||
$transactionAmount = $transaction->transaction_amount;
|
$transactionAmount = $transaction->transaction_amount;
|
||||||
|
|
||||||
|
if ($currency->id === $transaction->foreign_currency_id) {
|
||||||
|
$transactionAmount = $transaction->transaction_foreign_amount;
|
||||||
|
}
|
||||||
|
|
||||||
$newBalance = bcadd($startBalance, $transactionAmount);
|
$newBalance = bcadd($startBalance, $transactionAmount);
|
||||||
$transaction->after = $newBalance;
|
$transaction->after = $newBalance;
|
||||||
$startBalance = $newBalance;
|
$startBalance = $newBalance;
|
||||||
|
$transaction->currency = $currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ use Carbon\Carbon;
|
|||||||
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
use FireflyIII\Generator\Report\Support;
|
use FireflyIII\Generator\Report\Support;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -141,52 +144,10 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $collection
|
|
||||||
* @param int $sortFlag
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getAverages(Collection $collection, int $sortFlag): array
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($collection as $transaction) {
|
|
||||||
// opposing name and ID:
|
|
||||||
$opposingId = $transaction->opposing_account_id;
|
|
||||||
|
|
||||||
// is not set?
|
|
||||||
if (!isset($result[$opposingId])) {
|
|
||||||
$name = $transaction->opposing_account_name;
|
|
||||||
$result[$opposingId] = [
|
|
||||||
'name' => $name,
|
|
||||||
'count' => 1,
|
|
||||||
'id' => $opposingId,
|
|
||||||
'average' => $transaction->transaction_amount,
|
|
||||||
'sum' => $transaction->transaction_amount,
|
|
||||||
];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$result[$opposingId]['count']++;
|
|
||||||
$result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount);
|
|
||||||
$result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count']));
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort result by average:
|
|
||||||
$average = [];
|
|
||||||
foreach ($result as $key => $row) {
|
|
||||||
$average[$key] = floatval($row['average']);
|
|
||||||
}
|
|
||||||
|
|
||||||
array_multisort($average, $sortFlag, $result);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
private function getExpenses(): Collection
|
protected function getExpenses(): Collection
|
||||||
{
|
{
|
||||||
if ($this->expenses->count() > 0) {
|
if ($this->expenses->count() > 0) {
|
||||||
Log::debug('Return previous set of expenses.');
|
Log::debug('Return previous set of expenses.');
|
||||||
@@ -198,44 +159,18 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
|||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
->setTypes([TransactionType::WITHDRAWAL])
|
->setTypes([TransactionType::WITHDRAWAL])
|
||||||
->setBudgets($this->budgets)->withOpposingAccount()->disableFilter();
|
->setBudgets($this->budgets)->withOpposingAccount();
|
||||||
|
$collector->removeFilter(TransferFilter::class);
|
||||||
|
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(PositiveAmountFilter::class);
|
||||||
|
|
||||||
$accountIds = $this->accounts->pluck('id')->toArray();
|
|
||||||
$transactions = $collector->getJournals();
|
$transactions = $collector->getJournals();
|
||||||
$transactions = self::filterExpenses($transactions, $accountIds);
|
|
||||||
$this->expenses = $transactions;
|
$this->expenses = $transactions;
|
||||||
|
|
||||||
return $transactions;
|
return $transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
private function getTopExpenses(): Collection
|
|
||||||
{
|
|
||||||
$transactions = $this->getExpenses()->sortBy('transaction_amount');
|
|
||||||
|
|
||||||
return $transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $collection
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function summarizeByAccount(Collection $collection): array
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($collection as $transaction) {
|
|
||||||
$accountId = $transaction->account_id;
|
|
||||||
$result[$accountId] = $result[$accountId] ?? '0';
|
|
||||||
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $collection
|
* @param Collection $collection
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ use Carbon\Carbon;
|
|||||||
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
use FireflyIII\Generator\Report\Support;
|
use FireflyIII\Generator\Report\Support;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -151,52 +155,10 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $collection
|
|
||||||
* @param int $sortFlag
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getAverages(Collection $collection, int $sortFlag): array
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($collection as $transaction) {
|
|
||||||
// opposing name and ID:
|
|
||||||
$opposingId = $transaction->opposing_account_id;
|
|
||||||
|
|
||||||
// is not set?
|
|
||||||
if (!isset($result[$opposingId])) {
|
|
||||||
$name = $transaction->opposing_account_name;
|
|
||||||
$result[$opposingId] = [
|
|
||||||
'name' => $name,
|
|
||||||
'count' => 1,
|
|
||||||
'id' => $opposingId,
|
|
||||||
'average' => $transaction->transaction_amount,
|
|
||||||
'sum' => $transaction->transaction_amount,
|
|
||||||
];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$result[$opposingId]['count']++;
|
|
||||||
$result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount);
|
|
||||||
$result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count']));
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort result by average:
|
|
||||||
$average = [];
|
|
||||||
foreach ($result as $key => $row) {
|
|
||||||
$average[$key] = floatval($row['average']);
|
|
||||||
}
|
|
||||||
|
|
||||||
array_multisort($average, $sortFlag, $result);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
private function getExpenses(): Collection
|
protected function getExpenses(): Collection
|
||||||
{
|
{
|
||||||
if ($this->expenses->count() > 0) {
|
if ($this->expenses->count() > 0) {
|
||||||
Log::debug('Return previous set of expenses.');
|
Log::debug('Return previous set of expenses.');
|
||||||
@@ -208,11 +170,13 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
|||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
->setCategories($this->categories)->withOpposingAccount()->disableFilter();
|
->setCategories($this->categories)->withOpposingAccount();
|
||||||
|
$collector->removeFilter(TransferFilter::class);
|
||||||
|
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(PositiveAmountFilter::class);
|
||||||
|
|
||||||
$accountIds = $this->accounts->pluck('id')->toArray();
|
|
||||||
$transactions = $collector->getJournals();
|
$transactions = $collector->getJournals();
|
||||||
$transactions = self::filterExpenses($transactions, $accountIds);
|
|
||||||
$this->expenses = $transactions;
|
$this->expenses = $transactions;
|
||||||
|
|
||||||
return $transactions;
|
return $transactions;
|
||||||
@@ -221,7 +185,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
|||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
private function getIncome(): Collection
|
protected function getIncome(): Collection
|
||||||
{
|
{
|
||||||
if ($this->income->count() > 0) {
|
if ($this->income->count() > 0) {
|
||||||
return $this->income;
|
return $this->income;
|
||||||
@@ -232,93 +196,16 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
|||||||
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||||
->setCategories($this->categories)->withOpposingAccount();
|
->setCategories($this->categories)->withOpposingAccount();
|
||||||
$accountIds = $this->accounts->pluck('id')->toArray();
|
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(NegativeAmountFilter::class);
|
||||||
|
|
||||||
$transactions = $collector->getJournals();
|
$transactions = $collector->getJournals();
|
||||||
$transactions = self::filterIncome($transactions, $accountIds);
|
|
||||||
$this->income = $transactions;
|
$this->income = $transactions;
|
||||||
|
|
||||||
return $transactions;
|
return $transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
|
||||||
* @param array $spent
|
|
||||||
* @param array $earned
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getObjectSummary(array $spent, array $earned): array
|
|
||||||
{
|
|
||||||
$return = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int $accountId
|
|
||||||
* @var string $entry
|
|
||||||
*/
|
|
||||||
foreach ($spent as $objectId => $entry) {
|
|
||||||
if (!isset($return[$objectId])) {
|
|
||||||
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
$return[$objectId]['spent'] = $entry;
|
|
||||||
}
|
|
||||||
unset($entry);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int $accountId
|
|
||||||
* @var string $entry
|
|
||||||
*/
|
|
||||||
foreach ($earned as $objectId => $entry) {
|
|
||||||
if (!isset($return[$objectId])) {
|
|
||||||
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
$return[$objectId]['earned'] = $entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
private function getTopExpenses(): Collection
|
|
||||||
{
|
|
||||||
$transactions = $this->getExpenses()->sortBy('transaction_amount');
|
|
||||||
|
|
||||||
return $transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
private function getTopIncome(): Collection
|
|
||||||
{
|
|
||||||
$transactions = $this->getIncome()->sortByDesc('transaction_amount');
|
|
||||||
|
|
||||||
return $transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $collection
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function summarizeByAccount(Collection $collection): array
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($collection as $transaction) {
|
|
||||||
$accountId = $transaction->account_id;
|
|
||||||
$result[$accountId] = $result[$accountId] ?? '0';
|
|
||||||
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $collection
|
* @param Collection $collection
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Category;
|
|||||||
/**
|
/**
|
||||||
* Class MultiYearReportGenerator
|
* Class MultiYearReportGenerator
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Generator\Report\Audit
|
* @package FireflyIII\Generator\Report\Category
|
||||||
*/
|
*/
|
||||||
class MultiYearReportGenerator extends MonthReportGenerator
|
class MultiYearReportGenerator extends MonthReportGenerator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace FireflyIII\Generator\Report\Category;
|
|||||||
/**
|
/**
|
||||||
* Class YearReportGenerator
|
* Class YearReportGenerator
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Generator\Report\Audit
|
* @package FireflyIII\Generator\Report\Category
|
||||||
*/
|
*/
|
||||||
class YearReportGenerator extends MonthReportGenerator
|
class YearReportGenerator extends MonthReportGenerator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ReportGeneratorFactory
|
|||||||
$class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period);
|
$class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period);
|
||||||
if (class_exists($class)) {
|
if (class_exists($class)) {
|
||||||
/** @var ReportGeneratorInterface $obj */
|
/** @var ReportGeneratorInterface $obj */
|
||||||
$obj = new $class;
|
$obj = app($class);
|
||||||
$obj->setStartDate($start);
|
$obj->setStartDate($start);
|
||||||
$obj->setEndDate($end);
|
$obj->setEndDate($end);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ namespace FireflyIII\Generator\Report;
|
|||||||
|
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Log;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,57 +24,122 @@ use Log;
|
|||||||
*/
|
*/
|
||||||
class Support
|
class Support
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $collection
|
|
||||||
* @param array $accounts
|
|
||||||
*
|
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
public static function filterExpenses(Collection $collection, array $accounts): Collection
|
public function getTopExpenses(): Collection
|
||||||
{
|
{
|
||||||
return self::filterTransactions($collection, $accounts, 1);
|
$transactions = $this->getExpenses()->sortBy('transaction_amount');
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getTopIncome(): Collection
|
||||||
|
{
|
||||||
|
$transactions = $this->getIncome()->sortByDesc('transaction_amount');
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $collection
|
* @param Collection $collection
|
||||||
* @param array $accounts
|
* @param int $sortFlag
|
||||||
*
|
*
|
||||||
* @return Collection
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function filterIncome(Collection $collection, array $accounts): Collection
|
protected function getAverages(Collection $collection, int $sortFlag): array
|
||||||
{
|
{
|
||||||
return self::filterTransactions($collection, $accounts, -1);
|
$result = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($collection as $transaction) {
|
||||||
|
// opposing name and ID:
|
||||||
|
$opposingId = $transaction->opposing_account_id;
|
||||||
|
|
||||||
|
// is not set?
|
||||||
|
if (!isset($result[$opposingId])) {
|
||||||
|
$name = $transaction->opposing_account_name;
|
||||||
|
$result[$opposingId] = [
|
||||||
|
'name' => $name,
|
||||||
|
'count' => 1,
|
||||||
|
'id' => $opposingId,
|
||||||
|
'average' => $transaction->transaction_amount,
|
||||||
|
'sum' => $transaction->transaction_amount,
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result[$opposingId]['count']++;
|
||||||
|
$result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount);
|
||||||
|
$result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort result by average:
|
||||||
|
$average = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$average[$key] = floatval($row['average']);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_multisort($average, $sortFlag, $result);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
||||||
|
* @param array $spent
|
||||||
|
* @param array $earned
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getObjectSummary(array $spent, array $earned): array
|
||||||
|
{
|
||||||
|
$return = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int $accountId
|
||||||
|
* @var string $entry
|
||||||
|
*/
|
||||||
|
foreach ($spent as $objectId => $entry) {
|
||||||
|
if (!isset($return[$objectId])) {
|
||||||
|
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$objectId]['spent'] = $entry;
|
||||||
|
}
|
||||||
|
unset($entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int $accountId
|
||||||
|
* @var string $entry
|
||||||
|
*/
|
||||||
|
foreach ($earned as $objectId => $entry) {
|
||||||
|
if (!isset($return[$objectId])) {
|
||||||
|
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$objectId]['earned'] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $collection
|
* @param Collection $collection
|
||||||
* @param array $accounts
|
|
||||||
* @param int $modifier
|
|
||||||
*
|
*
|
||||||
* @return Collection
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function filterTransactions(Collection $collection, array $accounts, int $modifier): Collection
|
protected function summarizeByAccount(Collection $collection): array
|
||||||
{
|
{
|
||||||
$result = $collection->filter(
|
$result = [];
|
||||||
function (Transaction $transaction) use ($accounts, $modifier) {
|
/** @var Transaction $transaction */
|
||||||
$opposing = $transaction->opposing_account_id;
|
foreach ($collection as $transaction) {
|
||||||
// remove internal transfer
|
$accountId = $transaction->account_id;
|
||||||
if (in_array($opposing, $accounts)) {
|
$result[$accountId] = $result[$accountId] ?? '0';
|
||||||
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id));
|
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
// remove positive amount
|
|
||||||
if (bccomp($transaction->transaction_amount, '0') === $modifier) {
|
|
||||||
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $transaction;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|||||||
228
app/Generator/Report/Tag/MonthReportGenerator.php
Normal file
228
app/Generator/Report/Tag/MonthReportGenerator.php
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Tag;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
|
use FireflyIII\Generator\Report\Support;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
|
use FireflyIII\Models\Tag;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MonthReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Tag
|
||||||
|
*/
|
||||||
|
class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Collection */
|
||||||
|
private $expenses;
|
||||||
|
/** @var Collection */
|
||||||
|
private $income;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
/** @var Collection */
|
||||||
|
private $tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->expenses = new Collection;
|
||||||
|
$this->income = new Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
|
||||||
|
$tagTags = join(',', $this->tags->pluck('tag')->toArray());
|
||||||
|
$reportType = 'tag';
|
||||||
|
$expenses = $this->getExpenses();
|
||||||
|
$income = $this->getIncome();
|
||||||
|
$accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income));
|
||||||
|
$tagSummary = $this->getObjectSummary($this->summarizeByTag($expenses), $this->summarizeByTag($income));
|
||||||
|
$averageExpenses = $this->getAverages($expenses, SORT_ASC);
|
||||||
|
$averageIncome = $this->getAverages($income, SORT_DESC);
|
||||||
|
$topExpenses = $this->getTopExpenses();
|
||||||
|
$topIncome = $this->getTopIncome();
|
||||||
|
|
||||||
|
|
||||||
|
// render!
|
||||||
|
return view(
|
||||||
|
'reports.tag.month', compact(
|
||||||
|
'accountIds', 'tagTags', 'reportType', 'accountSummary', 'tagSummary', 'averageExpenses', 'averageIncome', 'topIncome',
|
||||||
|
'topExpenses'
|
||||||
|
)
|
||||||
|
)->with('start', $this->start)->with('end', $this->end)->with('tags', $this->tags)->with('accounts', $this->accounts)->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setBudgets(Collection $budgets): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $categories
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setCategories(Collection $categories): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setEndDate(Carbon $date): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->end = $date;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setStartDate(Carbon $date): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->start = $date;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $tags
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setTags(Collection $tags): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->tags = $tags;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
protected function getExpenses(): Collection
|
||||||
|
{
|
||||||
|
if ($this->expenses->count() > 0) {
|
||||||
|
Log::debug('Return previous set of expenses.');
|
||||||
|
|
||||||
|
return $this->expenses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
|
->setTags($this->tags)->withOpposingAccount();
|
||||||
|
$collector->removeFilter(TransferFilter::class);
|
||||||
|
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(PositiveAmountFilter::class);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
$this->expenses = $transactions;
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
protected function getIncome(): Collection
|
||||||
|
{
|
||||||
|
if ($this->income->count() > 0) {
|
||||||
|
return $this->income;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
|
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||||
|
->setTags($this->tags)->withOpposingAccount();
|
||||||
|
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(NegativeAmountFilter::class);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$this->income = $transactions;
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function summarizeByTag(Collection $collection): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($collection as $transaction) {
|
||||||
|
$journal = $transaction->transactionJournal;
|
||||||
|
$journalTags = $journal->tags;
|
||||||
|
/** @var Tag $journalTag */
|
||||||
|
foreach ($journalTags as $journalTag) {
|
||||||
|
$journalTagId = $journalTag->id;
|
||||||
|
$result[$journalTagId] = $result[$journalTagId] ?? '0';
|
||||||
|
$result[$journalTagId] = bcadd($transaction->transaction_amount, $result[$journalTagId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Generator/Report/Tag/MultiYearReportGenerator.php
Normal file
25
app/Generator/Report/Tag/MultiYearReportGenerator.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MultiYearReportGenerator.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Tag;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MultiYearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Tag
|
||||||
|
*/
|
||||||
|
class MultiYearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
26
app/Generator/Report/Tag/YearReportGenerator.php
Normal file
26
app/Generator/Report/Tag/YearReportGenerator.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* YearReportGenerator.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Tag;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class YearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Tag
|
||||||
|
*/
|
||||||
|
class YearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -14,59 +14,95 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Handlers\Events;
|
namespace FireflyIII\Handlers\Events;
|
||||||
|
|
||||||
use FireflyIII\Events\StoredTransactionJournal;
|
use FireflyIII\Events\StoredTransactionJournal;
|
||||||
use FireflyIII\Models\PiggyBank;
|
|
||||||
use FireflyIII\Models\PiggyBankEvent;
|
|
||||||
use FireflyIII\Models\PiggyBankRepetition;
|
|
||||||
use FireflyIII\Models\Rule;
|
use FireflyIII\Models\Rule;
|
||||||
use FireflyIII\Models\RuleGroup;
|
use FireflyIII\Models\RuleGroup;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Repositories\Journal\JournalRepositoryInterface as JRI;
|
||||||
|
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface as PRI;
|
||||||
|
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface as RGRI;
|
||||||
use FireflyIII\Rules\Processor;
|
use FireflyIII\Rules\Processor;
|
||||||
use FireflyIII\Support\Events\BillScanner;
|
use FireflyIII\Support\Events\BillScanner;
|
||||||
use Log;
|
use Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* Class StoredJournalEventHandler
|
* Class StoredJournalEventHandler
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Handlers\Events
|
* @package FireflyIII\Handlers\Events
|
||||||
*/
|
*/
|
||||||
class StoredJournalEventHandler
|
class StoredJournalEventHandler
|
||||||
{
|
{
|
||||||
|
/** @var JRI */
|
||||||
|
public $journalRepository;
|
||||||
|
/** @var PRI */
|
||||||
|
public $repository;
|
||||||
|
|
||||||
|
/** @var RGRI */
|
||||||
|
public $ruleGroupRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StoredJournalEventHandler constructor.
|
||||||
|
*
|
||||||
|
* @param PRI $repository
|
||||||
|
* @param JRI $journalRepository
|
||||||
|
* @param RGRI $ruleGroupRepository
|
||||||
|
*/
|
||||||
|
public function __construct(PRI $repository, JRI $journalRepository, RGRI $ruleGroupRepository)
|
||||||
|
{
|
||||||
|
$this->repository = $repository;
|
||||||
|
$this->journalRepository = $journalRepository;
|
||||||
|
$this->ruleGroupRepository = $ruleGroupRepository;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method connects a new transfer to a piggy bank.
|
* This method connects a new transfer to a piggy bank.
|
||||||
*
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
* @param StoredTransactionJournal $event
|
* @param StoredTransactionJournal $event
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function connectToPiggyBank(StoredTransactionJournal $event): bool
|
public function connectToPiggyBank(StoredTransactionJournal $event): bool
|
||||||
{
|
{
|
||||||
/** @var TransactionJournal $journal */
|
|
||||||
$journal = $event->journal;
|
$journal = $event->journal;
|
||||||
$piggyBankId = $event->piggyBankId;
|
$piggyBankId = $event->piggyBankId;
|
||||||
Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId));
|
$piggyBank = $this->repository->find($piggyBankId);
|
||||||
|
|
||||||
/*
|
// is a transfer?
|
||||||
* Verify existence of piggy bank:
|
if (!$this->journalRepository->isTransfer($journal)) {
|
||||||
*/
|
Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id));
|
||||||
if (!$this->verifyExistence($event)) {
|
|
||||||
Log::error(sprintf('No such piggy bank or no repetition on %s', $journal->date->format('Y-m-d')));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// piggy exists?
|
||||||
* Get relevant data:
|
if (is_null($piggyBank->id)) {
|
||||||
*/
|
Log::error(sprintf('There is no piggy bank with ID #%d', $piggyBankId));
|
||||||
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
|
||||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
return true;
|
||||||
$amount = $this->getExactAmount($journal, $piggyBank, $repetition);
|
}
|
||||||
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
|
|
||||||
$repetition->save();
|
// repetition exists?
|
||||||
|
$repetition = $this->repository->getRepetition($piggyBank, $journal->date);
|
||||||
|
if (is_null($repetition->id)) {
|
||||||
|
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the amount
|
||||||
|
$amount = $this->repository->getExactAmount($piggyBank, $repetition, $journal);
|
||||||
|
if (bccomp($amount, '0') === 0) {
|
||||||
|
Log::debug('Amount is zero, will not create event.');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update amount
|
||||||
|
$this->repository->addAmountToRepetition($repetition, $amount);
|
||||||
|
$event = $this->repository->createEventWithJournal($piggyBank, $amount, $journal);
|
||||||
|
|
||||||
/** @var PiggyBankEvent $event */
|
|
||||||
$event = PiggyBankEvent::create(
|
|
||||||
['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]
|
|
||||||
);
|
|
||||||
Log::debug(sprintf('Created piggy bank event #%d', $event->id));
|
Log::debug(sprintf('Created piggy bank event #%d', $event->id));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -83,16 +119,11 @@ class StoredJournalEventHandler
|
|||||||
{
|
{
|
||||||
// get all the user's rule groups, with the rules, order by 'order'.
|
// get all the user's rule groups, with the rules, order by 'order'.
|
||||||
$journal = $storedJournalEvent->journal;
|
$journal = $storedJournalEvent->journal;
|
||||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
$groups = $this->ruleGroupRepository->getActiveGroups($journal->user);
|
||||||
//
|
|
||||||
/** @var RuleGroup $group */
|
/** @var RuleGroup $group */
|
||||||
foreach ($groups as $group) {
|
foreach ($groups as $group) {
|
||||||
$rules = $group->rules()
|
$rules = $this->ruleGroupRepository->getActiveStoreRules($group);
|
||||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
|
||||||
->where('rule_triggers.trigger_type', 'user_action')
|
|
||||||
->where('rule_triggers.trigger_value', 'store-journal')
|
|
||||||
->where('rules.active', 1)
|
|
||||||
->get(['rules.*']);
|
|
||||||
/** @var Rule $rule */
|
/** @var Rule $rule */
|
||||||
foreach ($rules as $rule) {
|
foreach ($rules as $rule) {
|
||||||
|
|
||||||
@@ -100,9 +131,8 @@ class StoredJournalEventHandler
|
|||||||
$processor->handleTransactionJournal($journal);
|
$processor->handleTransactionJournal($journal);
|
||||||
|
|
||||||
if ($rule->stop_processing) {
|
if ($rule->stop_processing) {
|
||||||
return true;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,81 +153,4 @@ class StoredJournalEventHandler
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but I can live with it.
|
|
||||||
* @param TransactionJournal $journal
|
|
||||||
* @param PiggyBank $piggyBank
|
|
||||||
* @param PiggyBankRepetition $repetition
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function getExactAmount(TransactionJournal $journal, PiggyBank $piggyBank, PiggyBankRepetition $repetition): string
|
|
||||||
{
|
|
||||||
$amount = TransactionJournal::amountPositive($journal);
|
|
||||||
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
|
|
||||||
$room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount));
|
|
||||||
$compare = bcmul($repetition->currentamount, '-1');
|
|
||||||
|
|
||||||
Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
|
||||||
|
|
||||||
// if piggy account matches source account, the amount is positive
|
|
||||||
if (in_array($piggyBank->account_id, $sources)) {
|
|
||||||
$amount = bcmul($amount, '-1');
|
|
||||||
Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// if the amount is positive, make sure it fits in piggy bank:
|
|
||||||
if (bccomp($amount, '0') === 1 && bccomp($room, $amount) === -1) {
|
|
||||||
// amount is positive and $room is smaller than $amount
|
|
||||||
Log::debug(sprintf('Room in piggy bank for extra money is %f', $room));
|
|
||||||
Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
|
||||||
Log::debug(sprintf('New amount is %f', $room));
|
|
||||||
|
|
||||||
return $room;
|
|
||||||
}
|
|
||||||
|
|
||||||
// amount is negative and $currentamount is smaller than $amount
|
|
||||||
if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) {
|
|
||||||
Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount));
|
|
||||||
Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
|
||||||
Log::debug(sprintf('New amount is %f', $compare));
|
|
||||||
|
|
||||||
return $compare;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param StoredTransactionJournal $event
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function verifyExistence(StoredTransactionJournal $event): bool
|
|
||||||
{
|
|
||||||
/** @var TransactionJournal $journal */
|
|
||||||
$journal = $event->journal;
|
|
||||||
$piggyBankId = $event->piggyBankId;
|
|
||||||
|
|
||||||
/** @var PiggyBank $piggyBank */
|
|
||||||
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
|
||||||
|
|
||||||
if (is_null($piggyBank)) {
|
|
||||||
Log::error('No such piggy bank!');
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name));
|
|
||||||
// update piggy bank rep for date of transaction journal.
|
|
||||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
|
||||||
if (is_null($repetition)) {
|
|
||||||
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,31 @@ namespace FireflyIII\Handlers\Events;
|
|||||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||||
use FireflyIII\Models\Rule;
|
use FireflyIII\Models\Rule;
|
||||||
use FireflyIII\Models\RuleGroup;
|
use FireflyIII\Models\RuleGroup;
|
||||||
|
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||||
use FireflyIII\Rules\Processor;
|
use FireflyIII\Rules\Processor;
|
||||||
use FireflyIII\Support\Events\BillScanner;
|
use FireflyIII\Support\Events\BillScanner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* Class UpdatedJournalEventHandler
|
* Class UpdatedJournalEventHandler
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Handlers\Events
|
* @package FireflyIII\Handlers\Events
|
||||||
*/
|
*/
|
||||||
class UpdatedJournalEventHandler
|
class UpdatedJournalEventHandler
|
||||||
{
|
{
|
||||||
|
/** @var RuleGroupRepositoryInterface */
|
||||||
|
public $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StoredJournalEventHandler constructor.
|
||||||
|
*
|
||||||
|
* @param RuleGroupRepositoryInterface $ruleGroupRepository
|
||||||
|
*/
|
||||||
|
public function __construct(RuleGroupRepositoryInterface $ruleGroupRepository)
|
||||||
|
{
|
||||||
|
$this->repository = $ruleGroupRepository;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will check all the rules when a journal is updated.
|
* This method will check all the rules when a journal is updated.
|
||||||
@@ -39,16 +54,11 @@ class UpdatedJournalEventHandler
|
|||||||
{
|
{
|
||||||
// get all the user's rule groups, with the rules, order by 'order'.
|
// get all the user's rule groups, with the rules, order by 'order'.
|
||||||
$journal = $updatedJournalEvent->journal;
|
$journal = $updatedJournalEvent->journal;
|
||||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
$groups = $this->repository->getActiveGroups($journal->user);
|
||||||
//
|
|
||||||
/** @var RuleGroup $group */
|
/** @var RuleGroup $group */
|
||||||
foreach ($groups as $group) {
|
foreach ($groups as $group) {
|
||||||
$rules = $group->rules()
|
$rules = $this->repository->getActiveUpdateRules($group);
|
||||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
|
||||||
->where('rule_triggers.trigger_type', 'user_action')
|
|
||||||
->where('rule_triggers.trigger_value', 'update-journal')
|
|
||||||
->where('rules.active', 1)
|
|
||||||
->get(['rules.*']);
|
|
||||||
/** @var Rule $rule */
|
/** @var Rule $rule */
|
||||||
foreach ($rules as $rule) {
|
foreach ($rules as $rule) {
|
||||||
$processor = Processor::make($rule);
|
$processor = Processor::make($rule);
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ namespace FireflyIII\Handlers\Events;
|
|||||||
|
|
||||||
use FireflyIII\Events\RegisteredUser;
|
use FireflyIII\Events\RegisteredUser;
|
||||||
use FireflyIII\Events\RequestedNewPassword;
|
use FireflyIII\Events\RequestedNewPassword;
|
||||||
|
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
|
||||||
|
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use Illuminate\Mail\Message;
|
|
||||||
use Log;
|
use Log;
|
||||||
use Mail;
|
use Mail;
|
||||||
use Swift_TransportException;
|
use Swift_TransportException;
|
||||||
@@ -68,15 +69,14 @@ class UserEventHandler
|
|||||||
|
|
||||||
// send email.
|
// send email.
|
||||||
try {
|
try {
|
||||||
Mail::send(
|
Mail::to($email)->send(new RequestedNewPasswordMail($url, $ipAddress));
|
||||||
['emails.password-html', 'emails.password-text'], ['url' => $url, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
// @codeCoverageIgnoreStart
|
||||||
$message->to($email, $email)->subject('Your password reset request');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (Swift_TransportException $e) {
|
} catch (Swift_TransportException $e) {
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,23 +93,23 @@ class UserEventHandler
|
|||||||
|
|
||||||
$sendMail = env('SEND_REGISTRATION_MAIL', true);
|
$sendMail = env('SEND_REGISTRATION_MAIL', true);
|
||||||
if (!$sendMail) {
|
if (!$sendMail) {
|
||||||
return true;
|
return true; // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
// get the email address
|
// get the email address
|
||||||
$email = $event->user->email;
|
$email = $event->user->email;
|
||||||
$address = route('index');
|
$uri = route('index');
|
||||||
$ipAddress = $event->ipAddress;
|
$ipAddress = $event->ipAddress;
|
||||||
|
|
||||||
// send email.
|
// send email.
|
||||||
try {
|
try {
|
||||||
Mail::send(
|
Mail::to($email)->send(new RegisteredUserMail($uri, $ipAddress));
|
||||||
['emails.registered-html', 'emails.registered-text'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
// @codeCoverageIgnoreStart
|
||||||
$message->to($email, $email)->subject('Welcome to Firefly III!');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (Swift_TransportException $e) {
|
} catch (Swift_TransportException $e) {
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Helpers\Attachments;
|
namespace FireflyIII\Helpers\Attachments;
|
||||||
|
|
||||||
use Crypt;
|
use Crypt;
|
||||||
use FireflyIII\Models\Attachment;
|
use FireflyIII\Models\Attachment;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\MessageBag;
|
use Illuminate\Support\MessageBag;
|
||||||
|
use Log;
|
||||||
use Storage;
|
use Storage;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
|
||||||
@@ -27,6 +30,8 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|||||||
class AttachmentHelper implements AttachmentHelperInterface
|
class AttachmentHelper implements AttachmentHelperInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @var Collection */
|
||||||
|
public $attachments;
|
||||||
/** @var MessageBag */
|
/** @var MessageBag */
|
||||||
public $errors;
|
public $errors;
|
||||||
/** @var MessageBag */
|
/** @var MessageBag */
|
||||||
@@ -48,6 +53,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
$this->allowedMimes = (array)config('firefly.allowedMimes');
|
$this->allowedMimes = (array)config('firefly.allowedMimes');
|
||||||
$this->errors = new MessageBag;
|
$this->errors = new MessageBag;
|
||||||
$this->messages = new MessageBag;
|
$this->messages = new MessageBag;
|
||||||
|
$this->attachments = new Collection;
|
||||||
$this->uploadDisk = Storage::disk('upload');
|
$this->uploadDisk = Storage::disk('upload');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +69,14 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getAttachments(): Collection
|
||||||
|
{
|
||||||
|
return $this->attachments;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return MessageBag
|
* @return MessageBag
|
||||||
*/
|
*/
|
||||||
@@ -109,7 +123,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
$md5 = md5_file($file->getRealPath());
|
$md5 = md5_file($file->getRealPath());
|
||||||
$name = $file->getClientOriginalName();
|
$name = $file->getClientOriginalName();
|
||||||
$class = get_class($model);
|
$class = get_class($model);
|
||||||
$count = auth()->user()->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count();
|
$count = $model->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count();
|
||||||
|
|
||||||
if ($count > 0) {
|
if ($count > 0) {
|
||||||
$msg = (string)trans('validation.file_already_attached', ['name' => $name]);
|
$msg = (string)trans('validation.file_already_attached', ['name' => $name]);
|
||||||
@@ -136,7 +150,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$attachment = new Attachment; // create Attachment object.
|
$attachment = new Attachment; // create Attachment object.
|
||||||
$attachment->user()->associate(auth()->user());
|
$attachment->user()->associate($model->user);
|
||||||
$attachment->attachable()->associate($model);
|
$attachment->attachable()->associate($model);
|
||||||
$attachment->md5 = md5_file($file->getRealPath());
|
$attachment->md5 = md5_file($file->getRealPath());
|
||||||
$attachment->filename = $file->getClientOriginalName();
|
$attachment->filename = $file->getClientOriginalName();
|
||||||
@@ -144,17 +158,21 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
$attachment->size = $file->getSize();
|
$attachment->size = $file->getSize();
|
||||||
$attachment->uploaded = 0;
|
$attachment->uploaded = 0;
|
||||||
$attachment->save();
|
$attachment->save();
|
||||||
|
Log::debug('Created attachment:', $attachment->toArray());
|
||||||
|
|
||||||
$fileObject = $file->openFile('r');
|
$fileObject = $file->openFile('r');
|
||||||
$fileObject->rewind();
|
$fileObject->rewind();
|
||||||
$content = $fileObject->fread($file->getSize());
|
$content = $fileObject->fread($file->getSize());
|
||||||
$encrypted = Crypt::encrypt($content);
|
$encrypted = Crypt::encrypt($content);
|
||||||
|
Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize()));
|
||||||
|
Log::debug(sprintf('Encrypted content is %d', strlen($encrypted)));
|
||||||
|
|
||||||
// store it:
|
// store it:
|
||||||
$this->uploadDisk->put($attachment->fileName(), $encrypted);
|
$this->uploadDisk->put($attachment->fileName(), $encrypted);
|
||||||
|
|
||||||
$attachment->uploaded = 1; // update attachment
|
$attachment->uploaded = 1; // update attachment
|
||||||
$attachment->save();
|
$attachment->save();
|
||||||
|
$this->attachments->push($attachment);
|
||||||
|
|
||||||
$name = e($file->getClientOriginalName()); // add message:
|
$name = e($file->getClientOriginalName()); // add message:
|
||||||
$msg = (string)trans('validation.file_attached', ['name' => $name]);
|
$msg = (string)trans('validation.file_attached', ['name' => $name]);
|
||||||
@@ -187,6 +205,8 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param UploadedFile $file
|
* @param UploadedFile $file
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
@@ -217,7 +237,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!$this->validSize($file)) {
|
if (!$this->validSize($file)) {
|
||||||
return false;
|
return false; // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
if ($this->hasFile($file, $model)) {
|
if ($this->hasFile($file, $model)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -10,10 +10,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Helpers\Attachments;
|
namespace FireflyIII\Helpers\Attachments;
|
||||||
|
|
||||||
use FireflyIII\Models\Attachment;
|
use FireflyIII\Models\Attachment;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\MessageBag;
|
use Illuminate\Support\MessageBag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +33,11 @@ interface AttachmentHelperInterface
|
|||||||
*/
|
*/
|
||||||
public function getAttachmentLocation(Attachment $attachment): string;
|
public function getAttachmentLocation(Attachment $attachment): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getAttachments(): Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return MessageBag
|
* @return MessageBag
|
||||||
*/
|
*/
|
||||||
@@ -44,6 +51,8 @@ interface AttachmentHelperInterface
|
|||||||
/**
|
/**
|
||||||
* @param Model $model
|
* @param Model $model
|
||||||
*
|
*
|
||||||
|
* @param null|array $files
|
||||||
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function saveAttachmentsForModel(Model $model, array $files = null): bool;
|
public function saveAttachmentsForModel(Model $model, array $files = null): bool;
|
||||||
|
|||||||
@@ -12,13 +12,18 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Helpers\Chart;
|
namespace FireflyIII\Helpers\Chart;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Generator\Report\Support;
|
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
|
use FireflyIII\Models\Tag;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Steam;
|
use Steam;
|
||||||
@@ -46,19 +51,20 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
'account' => ['opposing_account_id'],
|
'account' => ['opposing_account_id'],
|
||||||
'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'],
|
'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'],
|
||||||
'category' => ['transaction_journal_category_id', 'transaction_category_id'],
|
'category' => ['transaction_journal_category_id', 'transaction_category_id'],
|
||||||
|
'tag' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
protected $repositories
|
protected $repositories
|
||||||
= [
|
= [
|
||||||
'account' => AccountRepositoryInterface::class,
|
'account' => AccountRepositoryInterface::class,
|
||||||
'budget' => BudgetRepositoryInterface::class,
|
'budget' => BudgetRepositoryInterface::class,
|
||||||
'category' => CategoryRepositoryInterface::class,
|
'category' => CategoryRepositoryInterface::class,
|
||||||
|
'tag' => TagRepositoryInterface::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/** @var Carbon */
|
/** @var Carbon */
|
||||||
protected $start;
|
protected $start;
|
||||||
|
/** @var Collection */
|
||||||
|
protected $tags;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $total = '0';
|
protected $total = '0';
|
||||||
/** @var User */
|
/** @var User */
|
||||||
@@ -69,6 +75,7 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
$this->accounts = new Collection;
|
$this->accounts = new Collection;
|
||||||
$this->budgets = new Collection;
|
$this->budgets = new Collection;
|
||||||
$this->categories = new Collection;
|
$this->categories = new Collection;
|
||||||
|
$this->tags = new Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,6 +106,7 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
if ($this->collectOtherObjects && $direction === 'income') {
|
if ($this->collectOtherObjects && $direction === 'income') {
|
||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setUser($this->user);
|
||||||
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]);
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]);
|
||||||
$journals = $collector->getJournals();
|
$journals = $collector->getJournals();
|
||||||
$sum = strval($journals->sum('transaction_amount'));
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
@@ -111,6 +119,8 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
*
|
*
|
||||||
* @return MetaPieChartInterface
|
* @return MetaPieChartInterface
|
||||||
@@ -123,6 +133,8 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param Collection $budgets
|
* @param Collection $budgets
|
||||||
*
|
*
|
||||||
* @return MetaPieChartInterface
|
* @return MetaPieChartInterface
|
||||||
@@ -135,6 +147,8 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param Collection $categories
|
* @param Collection $categories
|
||||||
*
|
*
|
||||||
* @return MetaPieChartInterface
|
* @return MetaPieChartInterface
|
||||||
@@ -147,6 +161,8 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param bool $collectOtherObjects
|
* @param bool $collectOtherObjects
|
||||||
*
|
*
|
||||||
* @return MetaPieChartInterface
|
* @return MetaPieChartInterface
|
||||||
@@ -159,6 +175,8 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return MetaPieChartInterface
|
* @return MetaPieChartInterface
|
||||||
@@ -171,6 +189,8 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
*
|
*
|
||||||
* @return MetaPieChartInterface
|
* @return MetaPieChartInterface
|
||||||
@@ -183,6 +203,22 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @param Collection $tags
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setTags(Collection $tags): MetaPieChartInterface
|
||||||
|
{
|
||||||
|
$this->tags = $tags;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param User $user
|
* @param User $user
|
||||||
*
|
*
|
||||||
* @return MetaPieChartInterface
|
* @return MetaPieChartInterface
|
||||||
@@ -194,23 +230,32 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getTransactions(string $direction)
|
/**
|
||||||
|
* @param string $direction
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
protected function getTransactions(string $direction): Collection
|
||||||
{
|
{
|
||||||
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
|
|
||||||
$modifier = -1;
|
|
||||||
if ($direction === 'expense') {
|
|
||||||
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
|
|
||||||
$modifier = 1;
|
|
||||||
}
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
|
||||||
|
$collector->addFilter(NegativeAmountFilter::class);
|
||||||
|
if ($direction === 'expense') {
|
||||||
|
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
|
||||||
|
$collector->addFilter(PositiveAmountFilter::class);
|
||||||
|
$collector->removeFilter(NegativeAmountFilter::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$collector->setUser($this->user);
|
||||||
$collector->setAccounts($this->accounts);
|
$collector->setAccounts($this->accounts);
|
||||||
$collector->setRange($this->start, $this->end);
|
$collector->setRange($this->start, $this->end);
|
||||||
$collector->setTypes($types);
|
$collector->setTypes($types);
|
||||||
$collector->withOpposingAccount();
|
$collector->withOpposingAccount();
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
|
||||||
if ($direction === 'income') {
|
if ($direction === 'income') {
|
||||||
$collector->disableFilter();
|
$collector->removeFilter(TransferFilter::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->budgets->count() > 0) {
|
if ($this->budgets->count() > 0) {
|
||||||
@@ -219,12 +264,13 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
if ($this->categories->count() > 0) {
|
if ($this->categories->count() > 0) {
|
||||||
$collector->setCategories($this->categories);
|
$collector->setCategories($this->categories);
|
||||||
}
|
}
|
||||||
|
if ($this->tags->count() > 0) {
|
||||||
|
$collector->setTags($this->tags);
|
||||||
|
$collector->withCategoryInformation();
|
||||||
|
$collector->withBudgetInformation();
|
||||||
|
}
|
||||||
|
|
||||||
$accountIds = $this->accounts->pluck('id')->toArray();
|
return $collector->getJournals();
|
||||||
$transactions = $collector->getJournals();
|
|
||||||
$set = Support::filterTransactions($transactions, $accountIds, $modifier);
|
|
||||||
|
|
||||||
return $set;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -233,8 +279,13 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function groupByFields(Collection $set, array $fields)
|
protected function groupByFields(Collection $set, array $fields): array
|
||||||
{
|
{
|
||||||
|
if (count($fields) === 0 && $this->tags->count() > 0) {
|
||||||
|
// do a special group on tags:
|
||||||
|
return $this->groupByTag($set);
|
||||||
|
}
|
||||||
|
|
||||||
$grouped = [];
|
$grouped = [];
|
||||||
/** @var Transaction $transaction */
|
/** @var Transaction $transaction */
|
||||||
foreach ($set as $transaction) {
|
foreach ($set as $transaction) {
|
||||||
@@ -261,10 +312,11 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
$chartData = [];
|
$chartData = [];
|
||||||
$names = [];
|
$names = [];
|
||||||
$repository = app($this->repositories[$type]);
|
$repository = app($this->repositories[$type]);
|
||||||
|
$repository->setUser($this->user);
|
||||||
foreach ($array as $objectId => $amount) {
|
foreach ($array as $objectId => $amount) {
|
||||||
if (!isset($names[$objectId])) {
|
if (!isset($names[$objectId])) {
|
||||||
$object = $repository->find(intval($objectId));
|
$object = $repository->find(intval($objectId));
|
||||||
$names[$objectId] = $object->name;
|
$names[$objectId] = $object->name ?? $object->tag;
|
||||||
}
|
}
|
||||||
$amount = Steam::positive($amount);
|
$amount = Steam::positive($amount);
|
||||||
$this->total = bcadd($this->total, $amount);
|
$this->total = bcadd($this->total, $amount);
|
||||||
@@ -274,4 +326,27 @@ class MetaPieChart implements MetaPieChartInterface
|
|||||||
return $chartData;
|
return $chartData;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function groupByTag(Collection $set): array
|
||||||
|
{
|
||||||
|
$grouped = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($set as $transaction) {
|
||||||
|
$journal = $transaction->transactionJournal;
|
||||||
|
$tags = $journal->tags;
|
||||||
|
/** @var Tag $tag */
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$tagId = $tag->id;
|
||||||
|
$grouped[$tagId] = $grouped[$tagId] ?? '0';
|
||||||
|
$grouped[$tagId] = bcadd($transaction->transaction_amount, $grouped[$tagId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,13 @@ interface MetaPieChartInterface
|
|||||||
*/
|
*/
|
||||||
public function setStart(Carbon $start): MetaPieChartInterface;
|
public function setStart(Carbon $start): MetaPieChartInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $tags
|
||||||
|
*
|
||||||
|
* @return MetaPieChartInterface
|
||||||
|
*/
|
||||||
|
public function setTags(Collection $tags): MetaPieChartInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $user
|
* @param User $user
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Helpers\Collection;
|
namespace FireflyIII\Helpers\Collection;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -18,12 +18,17 @@ use Carbon\Carbon;
|
|||||||
use Crypt;
|
use Crypt;
|
||||||
use DB;
|
use DB;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Helpers\Filter\FilterInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
use FireflyIII\Models\Tag;
|
use FireflyIII\Models\Tag;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionType;
|
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Contracts\Encryption\DecryptException;
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
@@ -55,17 +60,30 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
'transaction_journals.description',
|
'transaction_journals.description',
|
||||||
'transaction_journals.date',
|
'transaction_journals.date',
|
||||||
'transaction_journals.encrypted',
|
'transaction_journals.encrypted',
|
||||||
'transaction_currencies.code as transaction_currency_code',
|
|
||||||
'transaction_types.type as transaction_type_type',
|
'transaction_types.type as transaction_type_type',
|
||||||
'transaction_journals.bill_id',
|
'transaction_journals.bill_id',
|
||||||
'bills.name as bill_name',
|
'bills.name as bill_name',
|
||||||
'bills.name_encrypted as bill_name_encrypted',
|
'bills.name_encrypted as bill_name_encrypted',
|
||||||
'transactions.id as id',
|
'transactions.id as id',
|
||||||
'transactions.amount as transaction_amount',
|
|
||||||
'transactions.description as transaction_description',
|
'transactions.description as transaction_description',
|
||||||
'transactions.account_id',
|
'transactions.account_id',
|
||||||
'transactions.identifier',
|
'transactions.identifier',
|
||||||
'transactions.transaction_journal_id',
|
'transactions.transaction_journal_id',
|
||||||
|
|
||||||
|
'transactions.amount as transaction_amount',
|
||||||
|
|
||||||
|
'transactions.transaction_currency_id as transaction_currency_id',
|
||||||
|
'transaction_currencies.code as transaction_currency_code',
|
||||||
|
'transaction_currencies.symbol as transaction_currency_symbol',
|
||||||
|
'transaction_currencies.decimal_places as transaction_currency_dp',
|
||||||
|
|
||||||
|
'transactions.foreign_amount as transaction_foreign_amount',
|
||||||
|
'transactions.foreign_currency_id as foreign_currency_id',
|
||||||
|
'foreign_currencies.code as foreign_currency_code',
|
||||||
|
'foreign_currencies.symbol as foreign_currency_symbol',
|
||||||
|
'foreign_currencies.decimal_places as foreign_currency_dp',
|
||||||
|
|
||||||
'accounts.name as account_name',
|
'accounts.name as account_name',
|
||||||
'accounts.encrypted as account_encrypted',
|
'accounts.encrypted as account_encrypted',
|
||||||
'account_types.type as account_type',
|
'account_types.type as account_type',
|
||||||
@@ -74,6 +92,9 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
private $filterInternalTransfers;
|
private $filterInternalTransfers;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $filterTransfers = false;
|
private $filterTransfers = false;
|
||||||
|
/** @var array */
|
||||||
|
private $filters = [InternalTransferFilter::class];
|
||||||
|
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $joinedBudget = false;
|
private $joinedBudget = false;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
@@ -95,6 +116,22 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
/** @var User */
|
/** @var User */
|
||||||
private $user;
|
private $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filter
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function addFilter(string $filter): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$interfaces = class_implements($filter);
|
||||||
|
if (in_array(FilterInterface::class, $interfaces)) {
|
||||||
|
Log::debug(sprintf('Enabled filter %s', $filter));
|
||||||
|
$this->filters[] = $filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int
|
* @return int
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
@@ -119,36 +156,6 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
return $this->count;
|
return $this->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return JournalCollectorInterface
|
|
||||||
*/
|
|
||||||
public function disableFilter(): JournalCollectorInterface
|
|
||||||
{
|
|
||||||
$this->filterTransfers = false;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return JournalCollectorInterface
|
|
||||||
*/
|
|
||||||
public function disableInternalFilter(): JournalCollectorInterface
|
|
||||||
{
|
|
||||||
$this->filterInternalTransfers = false;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return JournalCollectorInterface
|
|
||||||
*/
|
|
||||||
public function enableInternalFilter(): JournalCollectorInterface
|
|
||||||
{
|
|
||||||
$this->filterInternalTransfers = true;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
@@ -157,14 +164,9 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
$this->run = true;
|
$this->run = true;
|
||||||
/** @var Collection $set */
|
/** @var Collection $set */
|
||||||
$set = $this->query->get(array_values($this->fields));
|
$set = $this->query->get(array_values($this->fields));
|
||||||
Log::debug(sprintf('Count of set is %d', $set->count()));
|
|
||||||
$set = $this->filterTransfers($set);
|
|
||||||
Log::debug(sprintf('Count of set after filterTransfers() is %d', $set->count()));
|
|
||||||
|
|
||||||
// possibly filter "internal" transfers:
|
|
||||||
$set = $this->filterInternalTransfers($set);
|
|
||||||
Log::debug(sprintf('Count of set after filterInternalTransfers() is %d', $set->count()));
|
|
||||||
|
|
||||||
|
// run all filters:
|
||||||
|
$set = $this->filter($set);
|
||||||
|
|
||||||
// loop for decryption.
|
// loop for decryption.
|
||||||
$set->each(
|
$set->each(
|
||||||
@@ -204,6 +206,22 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
return $journals;
|
return $journals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filter
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function removeFilter(string $filter): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$key = array_search($filter, $this->filters, true);
|
||||||
|
if (!($key === false)) {
|
||||||
|
Log::debug(sprintf('Removed filter %s', $filter));
|
||||||
|
unset($this->filters[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
*
|
*
|
||||||
@@ -219,6 +237,7 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($accounts->count() > 1) {
|
if ($accounts->count() > 1) {
|
||||||
|
$this->addFilter(TransferFilter::class);
|
||||||
$this->filterTransfers = true;
|
$this->filterTransfers = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +261,7 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($accounts->count() > 1) {
|
if ($accounts->count() > 1) {
|
||||||
|
$this->addFilter(TransferFilter::class);
|
||||||
$this->filterTransfers = true;
|
$this->filterTransfers = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,6 +450,20 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $tags
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setTags(Collection $tags): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinTagTables();
|
||||||
|
$tagIds = $tags->pluck('id')->toArray();
|
||||||
|
$this->query->whereIn('tag_transaction_journal.tag_id', $tagIds);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $types
|
* @param array $types
|
||||||
*
|
*
|
||||||
@@ -450,7 +484,9 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
*/
|
*/
|
||||||
public function setUser(User $user)
|
public function setUser(User $user)
|
||||||
{
|
{
|
||||||
|
Log::debug(sprintf('Journal collector now collecting for user #%d', $user->id));
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
|
$this->startQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -458,13 +494,15 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
*/
|
*/
|
||||||
public function startQuery()
|
public function startQuery()
|
||||||
{
|
{
|
||||||
|
Log::debug('journalCollector::startQuery');
|
||||||
/** @var EloquentBuilder $query */
|
/** @var EloquentBuilder $query */
|
||||||
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||||
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
|
|
||||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||||
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
|
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
|
||||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||||
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
|
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
|
||||||
|
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transactions.transaction_currency_id')
|
||||||
|
->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', 'transactions.foreign_currency_id')
|
||||||
->whereNull('transactions.deleted_at')
|
->whereNull('transactions.deleted_at')
|
||||||
->whereNull('transaction_journals.deleted_at')
|
->whereNull('transaction_journals.deleted_at')
|
||||||
->where('transaction_journals.user_id', $this->user->id)
|
->where('transaction_journals.user_id', $this->user->id)
|
||||||
@@ -546,79 +584,23 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
*
|
*
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
private function filterInternalTransfers(Collection $set): Collection
|
private function filter(Collection $set): Collection
|
||||||
{
|
{
|
||||||
if ($this->filterInternalTransfers === false) {
|
// create all possible filters:
|
||||||
Log::debug('Did NO filtering for internal transfers on given set.');
|
$filters = [
|
||||||
|
InternalTransferFilter::class => new InternalTransferFilter($this->accountIds),
|
||||||
return $set;
|
OpposingAccountFilter::class => new OpposingAccountFilter($this->accountIds),
|
||||||
|
TransferFilter::class => new TransferFilter,
|
||||||
|
PositiveAmountFilter::class => new PositiveAmountFilter,
|
||||||
|
NegativeAmountFilter::class => new NegativeAmountFilter,
|
||||||
|
];
|
||||||
|
Log::debug(sprintf('Will run %d filters on the set.', count($this->filters)));
|
||||||
|
foreach ($this->filters as $enabled) {
|
||||||
|
if (isset($filters[$enabled])) {
|
||||||
|
Log::debug(sprintf('Before filter %s: %d', $enabled, $set->count()));
|
||||||
|
$set = $filters[$enabled]->filter($set);
|
||||||
|
Log::debug(sprintf('After filter %s: %d', $enabled, $set->count()));
|
||||||
}
|
}
|
||||||
if ($this->joinedOpposing === false) {
|
|
||||||
Log::info('Cannot filter internal transfers because no opposing information is present.');
|
|
||||||
|
|
||||||
return $set;
|
|
||||||
}
|
|
||||||
|
|
||||||
$accountIds = $this->accountIds;
|
|
||||||
$set = $set->filter(
|
|
||||||
function (Transaction $transaction) use ($accountIds) {
|
|
||||||
// both id's in $accountids?
|
|
||||||
if (in_array($transaction->account_id, $accountIds) && in_array($transaction->opposing_account_id, $accountIds)) {
|
|
||||||
Log::debug(
|
|
||||||
sprintf(
|
|
||||||
'Transaction #%d has #%d and #%d in set, so removed',
|
|
||||||
$transaction->id, $transaction->account_id, $transaction->opposing_account_id
|
|
||||||
), $accountIds
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $transaction;
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the set of accounts used by the collector includes more than one asset
|
|
||||||
* account, chances are the set include double entries: transfers get selected
|
|
||||||
* on both the source, and then again on the destination account.
|
|
||||||
*
|
|
||||||
* This method filters them out by removing transfers that have been selected twice.
|
|
||||||
*
|
|
||||||
* @param Collection $set
|
|
||||||
*
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
private function filterTransfers(Collection $set): Collection
|
|
||||||
{
|
|
||||||
if ($this->filterTransfers) {
|
|
||||||
$count = [];
|
|
||||||
$new = new Collection;
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($set as $transaction) {
|
|
||||||
if ($transaction->transaction_type_type !== TransactionType::TRANSFER) {
|
|
||||||
$new->push($transaction);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// make property string:
|
|
||||||
$journalId = $transaction->transaction_journal_id;
|
|
||||||
$amount = Steam::positive($transaction->transaction_amount);
|
|
||||||
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
|
|
||||||
sort($accountIds);
|
|
||||||
$key = $journalId . '-' . join(',', $accountIds) . '-' . $amount;
|
|
||||||
Log::debug(sprintf('Key is %s', $key));
|
|
||||||
if (!isset($count[$key])) {
|
|
||||||
// not yet counted? add to new set and count it:
|
|
||||||
$new->push($transaction);
|
|
||||||
$count[$key] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $new;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $set;
|
return $set;
|
||||||
|
|||||||
@@ -28,26 +28,18 @@ use Illuminate\Support\Collection;
|
|||||||
*/
|
*/
|
||||||
interface JournalCollectorInterface
|
interface JournalCollectorInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param string $filter
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function addFilter(string $filter): JournalCollectorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function count(): int;
|
public function count(): int;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return JournalCollectorInterface
|
|
||||||
*/
|
|
||||||
public function disableFilter(): JournalCollectorInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return JournalCollectorInterface
|
|
||||||
*/
|
|
||||||
public function disableInternalFilter(): JournalCollectorInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return JournalCollectorInterface
|
|
||||||
*/
|
|
||||||
public function enableInternalFilter(): JournalCollectorInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
@@ -58,6 +50,13 @@ interface JournalCollectorInterface
|
|||||||
*/
|
*/
|
||||||
public function getPaginatedJournals(): LengthAwarePaginator;
|
public function getPaginatedJournals(): LengthAwarePaginator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filter
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function removeFilter(string $filter): JournalCollectorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
*
|
*
|
||||||
@@ -141,6 +140,13 @@ interface JournalCollectorInterface
|
|||||||
*/
|
*/
|
||||||
public function setTag(Tag $tag): JournalCollectorInterface;
|
public function setTag(Tag $tag): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $tags
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setTags(Collection $tags): JournalCollectorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $types
|
* @param array $types
|
||||||
*
|
*
|
||||||
|
|||||||
56
app/Helpers/Filter/AmountFilter.php
Normal file
56
app/Helpers/Filter/AmountFilter.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AmountFilter.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AmountFilter
|
||||||
|
*
|
||||||
|
* This filter removes transactions with either a positive amount ($parameters = 1) or a negative amount
|
||||||
|
* ($parameter = -1). This is helpful when a Collection has you with both transactions in a journal.
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Filter
|
||||||
|
*/
|
||||||
|
class AmountFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
private $modifier = 0;
|
||||||
|
|
||||||
|
public function __construct(int $modifier)
|
||||||
|
{
|
||||||
|
$this->modifier = $modifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection
|
||||||
|
{
|
||||||
|
return $set->filter(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
// remove by amount
|
||||||
|
if (bccomp($transaction->transaction_amount, '0') === $this->modifier) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Helpers/Filter/EmptyFilter.php
Normal file
34
app/Helpers/Filter/EmptyFilter.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* EmptyFilter.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class EmptyFilter
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Filter
|
||||||
|
*/
|
||||||
|
class EmptyFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection
|
||||||
|
{
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Helpers/Filter/FilterInterface.php
Normal file
26
app/Helpers/Filter/FilterInterface.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* FilterInterface.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
interface FilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection;
|
||||||
|
|
||||||
|
}
|
||||||
73
app/Helpers/Filter/InternalTransferFilter.php
Normal file
73
app/Helpers/Filter/InternalTransferFilter.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* InternalTransferFilter.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InternalTransferFilter
|
||||||
|
*
|
||||||
|
* This filter removes any filters that are from A to B or from B to A given a set of
|
||||||
|
* account id's (in $parameters) where A and B are mentioned. So transfers between the mentioned
|
||||||
|
* accounts will be removed.
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Filter
|
||||||
|
*/
|
||||||
|
class InternalTransferFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/** @var array */
|
||||||
|
private $accounts = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InternalTransferFilter constructor.
|
||||||
|
*
|
||||||
|
* @param array $accounts
|
||||||
|
*/
|
||||||
|
public function __construct(array $accounts)
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection
|
||||||
|
{
|
||||||
|
return $set->filter(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
if (is_null($transaction->opposing_account_id)) {
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
// both id's in $parameters?
|
||||||
|
if (in_array($transaction->account_id, $this->accounts) && in_array($transaction->opposing_account_id, $this->accounts)) {
|
||||||
|
Log::debug(
|
||||||
|
sprintf(
|
||||||
|
'Transaction #%d has #%d and #%d in set, so removed',
|
||||||
|
$transaction->id, $transaction->account_id, $transaction->opposing_account_id
|
||||||
|
), $this->accounts
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
47
app/Helpers/Filter/NegativeAmountFilter.php
Normal file
47
app/Helpers/Filter/NegativeAmountFilter.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* NegativeAmountFilter.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class NegativeAmountFilter
|
||||||
|
*
|
||||||
|
* This filter removes entries with a negative amount (the original modifier is -1).
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Filter
|
||||||
|
*/
|
||||||
|
class NegativeAmountFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection
|
||||||
|
{
|
||||||
|
return $set->filter(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
// remove by amount
|
||||||
|
if (bccomp($transaction->transaction_amount, '0') === -1) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
app/Helpers/Filter/OpposingAccountFilter.php
Normal file
63
app/Helpers/Filter/OpposingAccountFilter.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* OpposingAccountFilter.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class OpposingAccountFilter
|
||||||
|
*
|
||||||
|
* This filter is similar to the internal transfer filter but only removes transactions when the opposing account is
|
||||||
|
* amongst $parameters (list of account ID's).
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Filter
|
||||||
|
*/
|
||||||
|
class OpposingAccountFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/** @var array */
|
||||||
|
private $accounts = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InternalTransferFilter constructor.
|
||||||
|
*
|
||||||
|
* @param array $accounts
|
||||||
|
*/
|
||||||
|
public function __construct(array $accounts)
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection
|
||||||
|
{
|
||||||
|
return $set->filter(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
$opposing = $transaction->opposing_account_id;
|
||||||
|
// remove internal transfer
|
||||||
|
if (in_array($opposing, $this->accounts)) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id), $this->accounts);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/Helpers/Filter/PositiveAmountFilter.php
Normal file
50
app/Helpers/Filter/PositiveAmountFilter.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PositiveAmountFilter.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PositiveAmountFilter
|
||||||
|
*
|
||||||
|
* This filter removes entries with a negative amount (the original modifier is -1).
|
||||||
|
*
|
||||||
|
* This filter removes transactions with either a positive amount ($parameters = 1) or a negative amount
|
||||||
|
* ($parameter = -1). This is helpful when a Collection has you with both transactions in a journal.
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Filter
|
||||||
|
*/
|
||||||
|
class PositiveAmountFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection
|
||||||
|
{
|
||||||
|
return $set->filter(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
// remove by amount
|
||||||
|
if (bccomp($transaction->transaction_amount, '0') === 1) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
app/Helpers/Filter/TransferFilter.php
Normal file
58
app/Helpers/Filter/TransferFilter.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* TransferFilter.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Filter;
|
||||||
|
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Steam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class TransferFilter
|
||||||
|
*
|
||||||
|
* This filter removes any transfers that are in the collection twice (from A to B and from B to A).
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Filter
|
||||||
|
*/
|
||||||
|
class TransferFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(Collection $set): Collection
|
||||||
|
{
|
||||||
|
$count = [];
|
||||||
|
$new = new Collection;
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($set as $transaction) {
|
||||||
|
if ($transaction->transaction_type_type !== TransactionType::TRANSFER) {
|
||||||
|
$new->push($transaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// make property string:
|
||||||
|
$journalId = $transaction->transaction_journal_id;
|
||||||
|
$amount = Steam::positive($transaction->transaction_amount);
|
||||||
|
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
|
||||||
|
sort($accountIds);
|
||||||
|
$key = $journalId . '-' . join(',', $accountIds) . '-' . $amount;
|
||||||
|
if (!isset($count[$key])) {
|
||||||
|
// not yet counted? add to new set and count it:
|
||||||
|
$new->push($transaction);
|
||||||
|
$count[$key] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
199
app/Helpers/Report/PopupReport.php
Normal file
199
app/Helpers/Report/PopupReport.php
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PopupReport.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Report;
|
||||||
|
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Models\Account;
|
||||||
|
use FireflyIII\Models\Budget;
|
||||||
|
use FireflyIII\Models\Category;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PopupReport
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Report
|
||||||
|
*/
|
||||||
|
class PopupReport implements PopupReportInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $account
|
||||||
|
* @param $attributes
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function balanceDifference($account, $attributes): Collection
|
||||||
|
{
|
||||||
|
// row that displays difference
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector
|
||||||
|
->setAccounts(new Collection([$account]))
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL])
|
||||||
|
->setRange($attributes['startDate'], $attributes['endDate'])
|
||||||
|
->withoutBudget();
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
|
||||||
|
|
||||||
|
return $journals->filter(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
$tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count();
|
||||||
|
if ($tags === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param Account $account
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function balanceForBudget(Budget $budget, Account $account, array $attributes): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setBudget($budget);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
|
||||||
|
return $journals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function balanceForNoBudget(Account $account, array $attributes): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector
|
||||||
|
->setAccounts(new Collection([$account]))
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL])
|
||||||
|
->setRange($attributes['startDate'], $attributes['endDate'])
|
||||||
|
->withoutBudget();
|
||||||
|
|
||||||
|
return $collector->getJournals();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function byBudget(Budget $budget, array $attributes): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
|
||||||
|
$collector->setAccounts($attributes['accounts'])->setRange($attributes['startDate'], $attributes['endDate']);
|
||||||
|
|
||||||
|
if (is_null($budget->id)) {
|
||||||
|
$collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
|
||||||
|
}
|
||||||
|
if (!is_null($budget->id)) {
|
||||||
|
$collector->setBudget($budget);
|
||||||
|
}
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
|
||||||
|
return $journals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Category $category
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function byCategory(Category $category, array $attributes): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts($attributes['accounts'])->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
|
->setRange($attributes['startDate'], $attributes['endDate'])
|
||||||
|
->setCategory($category);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
|
||||||
|
return $journals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function byExpenses(Account $account, array $attributes): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
|
||||||
|
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
|
||||||
|
|
||||||
|
// filter for transfers and withdrawals TO the given $account
|
||||||
|
$journals = $journals->filter(
|
||||||
|
function (Transaction $transaction) use ($report) {
|
||||||
|
// get the destinations:
|
||||||
|
$sources = $transaction->transactionJournal->sourceAccountList()->pluck('id')->toArray();
|
||||||
|
|
||||||
|
// do these intersect with the current list?
|
||||||
|
return !empty(array_intersect($report, $sources));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $journals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function byIncome(Account $account, array $attributes): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])
|
||||||
|
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
|
||||||
|
|
||||||
|
// filter the set so the destinations outside of $attributes['accounts'] are not included.
|
||||||
|
$journals = $journals->filter(
|
||||||
|
function (Transaction $transaction) use ($report) {
|
||||||
|
// get the destinations:
|
||||||
|
$destinations = $transaction->transactionJournal->destinationAccountList()->pluck('id')->toArray();
|
||||||
|
|
||||||
|
// do these intersect with the current list?
|
||||||
|
return !empty(array_intersect($report, $destinations));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $journals;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
app/Helpers/Report/PopupReportInterface.php
Normal file
83
app/Helpers/Report/PopupReportInterface.php
Normal 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;
|
||||||
|
}
|
||||||
@@ -22,9 +22,10 @@ use FireflyIII\Http\Requests\AccountFormRequest;
|
|||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
|
||||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -69,7 +70,8 @@ class AccountController extends Controller
|
|||||||
{
|
{
|
||||||
/** @var CurrencyRepositoryInterface $repository */
|
/** @var CurrencyRepositoryInterface $repository */
|
||||||
$repository = app(CurrencyRepositoryInterface::class);
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
$currencies = ExpandedForm::makeSelectList($repository->get());
|
$allCurrencies = $repository->get();
|
||||||
|
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
|
||||||
$defaultCurrency = Amount::getDefaultCurrency();
|
$defaultCurrency = Amount::getDefaultCurrency();
|
||||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||||
$subTitle = trans('firefly.make_new_' . $what . '_account');
|
$subTitle = trans('firefly.make_new_' . $what . '_account');
|
||||||
@@ -90,7 +92,7 @@ class AccountController extends Controller
|
|||||||
$request->session()->flash('gaEventCategory', 'accounts');
|
$request->session()->flash('gaEventCategory', 'accounts');
|
||||||
$request->session()->flash('gaEventAction', 'create-' . $what);
|
$request->session()->flash('gaEventAction', 'create-' . $what);
|
||||||
|
|
||||||
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencies', 'roles'));
|
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencySelectList', 'allCurrencies', 'roles'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,13 +148,13 @@ class AccountController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function edit(Request $request, Account $account)
|
public function edit(Request $request, Account $account)
|
||||||
{
|
{
|
||||||
|
/** @var CurrencyRepositoryInterface $repository */
|
||||||
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
|
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
|
||||||
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
|
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
|
||||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||||
/** @var CurrencyRepositoryInterface $repository */
|
$allCurrencies = $repository->get();
|
||||||
$repository = app(CurrencyRepositoryInterface::class);
|
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
|
||||||
$currencies = ExpandedForm::makeSelectList($repository->get());
|
|
||||||
$roles = [];
|
$roles = [];
|
||||||
foreach (config('firefly.accountRoles') as $role) {
|
foreach (config('firefly.accountRoles') as $role) {
|
||||||
$roles[$role] = strval(trans('firefly.account_role_' . $role));
|
$roles[$role] = strval(trans('firefly.account_role_' . $role));
|
||||||
@@ -172,6 +174,7 @@ class AccountController extends Controller
|
|||||||
$openingBalanceAmount = $account->getOpeningBalanceAmount() === '0' ? '' : $openingBalanceAmount;
|
$openingBalanceAmount = $account->getOpeningBalanceAmount() === '0' ? '' : $openingBalanceAmount;
|
||||||
$openingBalanceDate = $account->getOpeningBalanceDate();
|
$openingBalanceDate = $account->getOpeningBalanceDate();
|
||||||
$openingBalanceDate = $openingBalanceDate->year === 1900 ? null : $openingBalanceDate->format('Y-m-d');
|
$openingBalanceDate = $openingBalanceDate->year === 1900 ? null : $openingBalanceDate->format('Y-m-d');
|
||||||
|
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||||
|
|
||||||
$preFilled = [
|
$preFilled = [
|
||||||
'accountNumber' => $account->getMeta('accountNumber'),
|
'accountNumber' => $account->getMeta('accountNumber'),
|
||||||
@@ -182,13 +185,18 @@ class AccountController extends Controller
|
|||||||
'openingBalanceDate' => $openingBalanceDate,
|
'openingBalanceDate' => $openingBalanceDate,
|
||||||
'openingBalance' => $openingBalanceAmount,
|
'openingBalance' => $openingBalanceAmount,
|
||||||
'virtualBalance' => $account->virtual_balance,
|
'virtualBalance' => $account->virtual_balance,
|
||||||
'currency_id' => $account->getMeta('currency_id'),
|
'currency_id' => $currency->id,
|
||||||
|
|
||||||
];
|
];
|
||||||
$request->session()->flash('preFilled', $preFilled);
|
$request->session()->flash('preFilled', $preFilled);
|
||||||
$request->session()->flash('gaEventCategory', 'accounts');
|
$request->session()->flash('gaEventCategory', 'accounts');
|
||||||
$request->session()->flash('gaEventAction', 'edit-' . $what);
|
$request->session()->flash('gaEventAction', 'edit-' . $what);
|
||||||
|
|
||||||
return view('accounts.edit', compact('currencies', 'account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles'));
|
return view(
|
||||||
|
'accounts.edit', compact(
|
||||||
|
'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'openingBalance', 'what', 'roles'
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,8 +219,8 @@ class AccountController extends Controller
|
|||||||
$start->subDay();
|
$start->subDay();
|
||||||
|
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
$ids = $accounts->pluck('id')->toArray();
|
||||||
$startBalances = Steam::balancesById($ids, $start);
|
$startBalances = Steam::balancesByAccounts($accounts, $start);
|
||||||
$endBalances = Steam::balancesById($ids, $end);
|
$endBalances = Steam::balancesByAccounts($accounts, $end);
|
||||||
$activities = Steam::getLastActivities($ids);
|
$activities = Steam::getLastActivities($ids);
|
||||||
|
|
||||||
$accounts->each(
|
$accounts->each(
|
||||||
@@ -227,101 +235,99 @@ class AccountController extends Controller
|
|||||||
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
|
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param JournalCollectorInterface $collector
|
* @param JournalRepositoryInterface $repository
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
|
* @param string $moment
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, JournalCollectorInterface $collector, Account $account)
|
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
|
||||||
{
|
{
|
||||||
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
||||||
return $this->redirectToOriginalAccount($account);
|
return $this->redirectToOriginalAccount($account);
|
||||||
}
|
}
|
||||||
// show journals from current period only:
|
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||||
$subTitle = $account->name;
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
|
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||||
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
|
||||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$chartUri = route('chart.account.single', [$account->id]);
|
$chartUri = route('chart.account.single', [$account->id]);
|
||||||
$accountType = $account->accountType->type;
|
$start = null;
|
||||||
|
$end = null;
|
||||||
|
$periods = new Collection;
|
||||||
|
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
|
||||||
|
|
||||||
// grab those journals:
|
// prep for "all" view.
|
||||||
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
|
if ($moment === 'all') {
|
||||||
$journals = $collector->getPaginatedJournals();
|
$subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
|
||||||
$journals->setPath('accounts/show/' . $account->id);
|
|
||||||
|
|
||||||
// generate entries for each period (and cache those)
|
|
||||||
$entries = $this->periodEntries($account);
|
|
||||||
|
|
||||||
return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Request $request
|
|
||||||
* @param AccountRepositoryInterface $repository
|
|
||||||
* @param Account $account
|
|
||||||
*
|
|
||||||
* @return View
|
|
||||||
*/
|
|
||||||
public function showAll(Request $request, AccountRepositoryInterface $repository, Account $account)
|
|
||||||
{
|
|
||||||
$subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
|
|
||||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
|
||||||
$chartUri = route('chart.account.all', [$account->id]);
|
$chartUri = route('chart.account.all', [$account->id]);
|
||||||
|
$first = $repository->first();
|
||||||
// replace with journal collector:
|
$start = $first->date ?? new Carbon;
|
||||||
/** @var JournalCollectorInterface $collector */
|
$end = new Carbon;
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector->setUser(auth()->user());
|
|
||||||
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
|
||||||
$journals = $collector->getPaginatedJournals();
|
|
||||||
$journals->setPath('accounts/show/' . $account->id . '/all');
|
|
||||||
|
|
||||||
// get oldest and newest journal for account:
|
|
||||||
$start = $repository->oldestJournalDate($account);
|
|
||||||
$end = $repository->newestJournalDate($account);
|
|
||||||
|
|
||||||
// same call, except "entries".
|
|
||||||
return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// prep for "specific date" view.
|
||||||
* @param Request $request
|
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||||
* @param Account $account
|
$start = new Carbon($moment);
|
||||||
* @param string $date
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
*
|
$subTitle = trans(
|
||||||
* @return View
|
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||||
*/
|
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
public function showByDate(Request $request, Account $account, string $date)
|
);
|
||||||
{
|
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
|
||||||
$carbon = new Carbon($date);
|
$periods = $this->getPeriodOverview($account);
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
}
|
||||||
$start = Navigation::startOfPeriod($carbon, $range);
|
|
||||||
$end = Navigation::endOfPeriod($carbon, $range);
|
|
||||||
$subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
|
|
||||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
|
||||||
$chartUri = route('chart.account.period', [$account->id, $carbon->format('Y-m-d')]);
|
|
||||||
$accountType = $account->accountType->type;
|
|
||||||
|
|
||||||
// replace with journal collector:
|
// prep for current period
|
||||||
/** @var JournalCollectorInterface $collector */
|
if (strlen($moment) === 0) {
|
||||||
|
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||||
|
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
|
$subTitle = trans(
|
||||||
|
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||||
|
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
|
);
|
||||||
|
$periods = $this->getPeriodOverview($account);
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$loop = 0;
|
||||||
|
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||||
|
Log::info('Now at loop start.');
|
||||||
|
while ($count === 0 && $loop < 3) {
|
||||||
|
$loop++;
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
|
Log::info('Count is zero, search for journals.');
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||||
|
if (!is_null($start)) {
|
||||||
|
$collector->setRange($start, $end);
|
||||||
|
}
|
||||||
$journals = $collector->getPaginatedJournals();
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('accounts/show/' . $account->id . '/' . $date);
|
$journals->setPath('accounts/show/' . $account->id . '/' . $moment);
|
||||||
|
$count = $journals->getCollection()->count();
|
||||||
|
if ($count === 0) {
|
||||||
|
$start->subDay();
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// generate entries for each period (and cache those)
|
if ($moment != 'all' && $loop > 1) {
|
||||||
$entries = $this->periodEntries($account);
|
$subTitle = trans(
|
||||||
|
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||||
|
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// same call, except "entries".
|
|
||||||
return view('accounts.show', compact('account', 'accountType', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
return view(
|
||||||
|
'accounts.show',
|
||||||
|
compact('account', 'currency', 'moment', 'periods', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -335,7 +341,6 @@ class AccountController extends Controller
|
|||||||
{
|
{
|
||||||
$data = $request->getAccountData();
|
$data = $request->getAccountData();
|
||||||
$account = $repository->store($data);
|
$account = $repository->store($data);
|
||||||
|
|
||||||
$request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
|
$request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
@@ -409,13 +414,10 @@ class AccountController extends Controller
|
|||||||
*
|
*
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
private function periodEntries(Account $account): Collection
|
private function getPeriodOverview(Account $account): Collection
|
||||||
{
|
{
|
||||||
/** @var AccountRepositoryInterface $repository */
|
/** @var AccountRepositoryInterface $repository */
|
||||||
$repository = app(AccountRepositoryInterface::class);
|
$repository = app(AccountRepositoryInterface::class);
|
||||||
/** @var AccountTaskerInterface $tasker */
|
|
||||||
$tasker = app(AccountTaskerInterface::class);
|
|
||||||
|
|
||||||
$start = $repository->oldestJournalDate($account);
|
$start = $repository->oldestJournalDate($account);
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
$start = Navigation::startOfPeriod($start, $range);
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
@@ -430,25 +432,39 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty($account->id);
|
$cache->addProperty($account->id);
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
Log::debug('Entries are cached, return cache.');
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
|
|
||||||
return $cache->get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// only include asset accounts when this account is an asset:
|
|
||||||
$assets = new Collection;
|
|
||||||
if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) {
|
|
||||||
$assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
|
||||||
}
|
|
||||||
Log::debug('Going to get period expenses and incomes.');
|
Log::debug('Going to get period expenses and incomes.');
|
||||||
while ($end >= $start) {
|
while ($end >= $start) {
|
||||||
$end = Navigation::startOfPeriod($end, $range);
|
$end = Navigation::startOfPeriod($end, $range);
|
||||||
$currentEnd = Navigation::endOfPeriod($end, $range);
|
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||||
$spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
|
||||||
$earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
// try a collector for income:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
|
||||||
|
->setTypes([TransactionType::DEPOSIT])
|
||||||
|
->withOpposingAccount();
|
||||||
|
$earned = strval($collector->getJournals()->sum('transaction_amount'));
|
||||||
|
|
||||||
|
// try a collector for expenses:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL])
|
||||||
|
->withOpposingAccount();
|
||||||
|
$spent = strval($collector->getJournals()->sum('transaction_amount'));
|
||||||
$dateStr = $end->format('Y-m-d');
|
$dateStr = $end->format('Y-m-d');
|
||||||
$dateName = Navigation::periodShow($end, $range);
|
$dateName = Navigation::periodShow($end, $range);
|
||||||
$entries->push([$dateStr, $dateName, $spent, $earned, clone $end]);
|
$entries->push(
|
||||||
|
[
|
||||||
|
'string' => $dateStr,
|
||||||
|
'name' => $dateName,
|
||||||
|
'spent' => $spent,
|
||||||
|
'earned' => $earned,
|
||||||
|
'date' => clone $end]
|
||||||
|
);
|
||||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -476,7 +492,7 @@ class AccountController extends Controller
|
|||||||
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
|
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
|
||||||
|
|
||||||
if (is_null($opposingTransaction)) {
|
if (is_null($opposingTransaction)) {
|
||||||
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.');
|
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
|
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -98,10 +98,14 @@ class AttachmentController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
|
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
if ($repository->exists($attachment)) {
|
if ($repository->exists($attachment)) {
|
||||||
$content = $repository->getContent($attachment);
|
$content = $repository->getContent($attachment);
|
||||||
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
|
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** @var LaravelResponse $response */
|
/** @var LaravelResponse $response */
|
||||||
$response = response($content, 200);
|
$response = response($content, 200);
|
||||||
$response
|
$response
|
||||||
@@ -149,6 +153,7 @@ class AttachmentController extends Controller
|
|||||||
{
|
{
|
||||||
$image = 'images/page_green.png';
|
$image = 'images/page_green.png';
|
||||||
|
|
||||||
|
|
||||||
if ($attachment->mime == 'application/pdf') {
|
if ($attachment->mime == 'application/pdf') {
|
||||||
$image = 'images/page_white_acrobat.png';
|
$image = 'images/page_white_acrobat.png';
|
||||||
}
|
}
|
||||||
@@ -176,10 +181,11 @@ class AttachmentController extends Controller
|
|||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval($request->get('return_to_edit')) === 1) {
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
// set value so edit routine will not overwrite URL:
|
// @codeCoverageIgnoreStart
|
||||||
$request->session()->put('attachments.edit.fromUpdate', true);
|
$request->session()->put('attachments.edit.fromUpdate', true);
|
||||||
|
|
||||||
return redirect(route('attachments.edit', [$attachment->id]))->withInput(['return_to_edit' => 1]);
|
return redirect(route('attachments.edit', [$attachment->id]))->withInput(['return_to_edit' => 1]);
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to previous URL.
|
// redirect to previous URL.
|
||||||
|
|||||||
@@ -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)]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ use Illuminate\Support\Facades\Password;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* Class PasswordController
|
* Class PasswordController
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Http\Controllers\Auth
|
* @package FireflyIII\Http\Controllers\Auth
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ use Session;
|
|||||||
use Validator;
|
use Validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* Class RegisterController
|
* Class RegisterController
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Http\Controllers\Auth
|
* @package FireflyIII\Http\Controllers\Auth
|
||||||
@@ -122,12 +124,15 @@ class RegisterController extends Controller
|
|||||||
*/
|
*/
|
||||||
protected function create(array $data)
|
protected function create(array $data)
|
||||||
{
|
{
|
||||||
return User::create(
|
/** @var User $user */
|
||||||
|
$user = User::create(
|
||||||
[
|
[
|
||||||
'email' => $data['email'],
|
'email' => $data['email'],
|
||||||
'password' => bcrypt($data['password']),
|
'password' => bcrypt($data['password']),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ class TwoFactorController extends Controller
|
|||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
// to make sure the validator in the next step gets the secret, we push it in session
|
// to make sure the validator in the next step gets the secret, we push it in session
|
||||||
$secret = Preferences::get('twoFactorAuthSecret', null)->data;
|
$secretPreference = Preferences::get('twoFactorAuthSecret', null);
|
||||||
|
$secret = is_null($secretPreference) ? null : $secretPreference->data;
|
||||||
$title = strval(trans('firefly.two_factor_title'));
|
$title = strval(trans('firefly.two_factor_title'));
|
||||||
|
|
||||||
// make sure the user has two factor configured:
|
// make sure the user has two factor configured:
|
||||||
$has2FA = Preferences::get('twoFactorAuthEnabled', null)->data;
|
$has2FA = Preferences::get('twoFactorAuthEnabled', false)->data;
|
||||||
if (is_null($has2FA) || $has2FA === false) {
|
if (is_null($has2FA) || $has2FA === false) {
|
||||||
return redirect(route('index'));
|
return redirect(route('index'));
|
||||||
}
|
}
|
||||||
@@ -79,9 +80,11 @@ class TwoFactorController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param TokenFormRequest $request
|
* @param TokenFormRequest $request
|
||||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
|
* @param CookieJar $cookieJar
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public function postIndex(TokenFormRequest $request, CookieJar $cookieJar)
|
public function postIndex(TokenFormRequest $request, CookieJar $cookieJar)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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'));
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers;
|
|||||||
|
|
||||||
use Amount;
|
use Amount;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Exception;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Http\Requests\BudgetFormRequest;
|
use FireflyIII\Http\Requests\BudgetFormRequest;
|
||||||
@@ -22,11 +23,15 @@ use FireflyIII\Http\Requests\BudgetIncomeRequest;
|
|||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\BudgetLimit;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
use View;
|
use View;
|
||||||
@@ -105,6 +110,7 @@ class BudgetController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param Request $request
|
||||||
* @param Budget $budget
|
* @param Budget $budget
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
@@ -161,16 +167,38 @@ class BudgetController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param string|null $moment
|
||||||
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index(string $moment = null)
|
||||||
{
|
{
|
||||||
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
$start = session('start', new Carbon);
|
||||||
|
$end = session('end', new Carbon);
|
||||||
|
|
||||||
|
// make date if present:
|
||||||
|
if (!is_null($moment) || strlen(strval($moment)) !== 0) {
|
||||||
|
try {
|
||||||
|
$start = new Carbon($moment);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// start and end are already defined.
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$next = clone $end;
|
||||||
|
$next->addDay();
|
||||||
|
$prev = clone $start;
|
||||||
|
$prev->subDay();
|
||||||
|
$prev = Navigation::startOfPeriod($prev, $range);
|
||||||
|
|
||||||
|
|
||||||
$this->repository->cleanupBudgets();
|
$this->repository->cleanupBudgets();
|
||||||
|
|
||||||
|
|
||||||
$budgets = $this->repository->getActiveBudgets();
|
$budgets = $this->repository->getActiveBudgets();
|
||||||
$inactive = $this->repository->getInactiveBudgets();
|
$inactive = $this->repository->getInactiveBudgets();
|
||||||
$start = session('start', new Carbon);
|
|
||||||
$end = session('end', new Carbon);
|
|
||||||
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
|
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||||
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
|
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||||
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
|
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
|
||||||
@@ -179,41 +207,130 @@ class BudgetController extends Controller
|
|||||||
$spent = array_sum(array_column($budgetInformation, 'spent'));
|
$spent = array_sum(array_column($budgetInformation, 'spent'));
|
||||||
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
|
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
|
||||||
|
|
||||||
|
// select thing for last 12 periods:
|
||||||
|
$previousLoop = [];
|
||||||
|
$previousDate = clone $start;
|
||||||
|
$count = 0;
|
||||||
|
while ($count < 12) {
|
||||||
|
$previousDate->subDay();
|
||||||
|
$previousDate = Navigation::startOfPeriod($previousDate, $range);
|
||||||
|
$format = $previousDate->format('Y-m-d');
|
||||||
|
$previousLoop[$format] = Navigation::periodShow($previousDate, $range);
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// select thing for next 12 periods:
|
||||||
|
$nextLoop = [];
|
||||||
|
$nextDate = clone $end;
|
||||||
|
$nextDate->addDay();
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
while ($count < 12) {
|
||||||
|
$format = $nextDate->format('Y-m-d');
|
||||||
|
$nextLoop[$format] = Navigation::periodShow($nextDate, $range);
|
||||||
|
$nextDate = Navigation::endOfPeriod($nextDate, $range);
|
||||||
|
$count++;
|
||||||
|
$nextDate->addDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// display info
|
||||||
|
$currentMonth = Navigation::periodShow($start, $range);
|
||||||
|
$nextText = Navigation::periodShow($next, $range);
|
||||||
|
$prevText = Navigation::periodShow($prev, $range);
|
||||||
|
|
||||||
return view(
|
return view(
|
||||||
'budgets.index',
|
'budgets.index',
|
||||||
compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', 'spent', 'budgeted')
|
compact(
|
||||||
|
'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText',
|
||||||
|
'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets',
|
||||||
|
'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
|
* @param JournalRepositoryInterface $repository
|
||||||
|
* @param string $moment
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function noBudget(Request $request)
|
public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = '')
|
||||||
{
|
{
|
||||||
/** @var Carbon $start */
|
// default values:
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
/** @var Carbon $end */
|
$start = null;
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = null;
|
||||||
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
$periods = new Collection;
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
|
||||||
|
// prep for "all" view.
|
||||||
|
if ($moment === 'all') {
|
||||||
|
$subTitle = trans('firefly.all_journals_without_budget');
|
||||||
|
$first = $repository->first();
|
||||||
|
$start = $first->date ?? new Carbon;
|
||||||
|
$end = new Carbon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prep for "specific date" view.
|
||||||
|
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||||
|
$start = new Carbon($moment);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
$subTitle = trans(
|
$subTitle = trans(
|
||||||
'firefly.without_budget_between',
|
'firefly.without_budget_between',
|
||||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
);
|
);
|
||||||
|
$periods = $this->getPeriodOverview();
|
||||||
|
}
|
||||||
|
|
||||||
// collector
|
// prep for current period
|
||||||
|
if (strlen($moment) === 0) {
|
||||||
|
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||||
|
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
|
$periods = $this->getPeriodOverview();
|
||||||
|
$subTitle = trans(
|
||||||
|
'firefly.without_budget_between',
|
||||||
|
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
||||||
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$loop = 0;
|
||||||
|
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||||
|
Log::info('Now at no-budget loop start.');
|
||||||
|
while ($count === 0 && $loop < 3) {
|
||||||
|
$loop++;
|
||||||
|
Log::info('Count is zero, search for journals.');
|
||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutBudget();
|
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
|
||||||
|
->withoutBudget()->withOpposingAccount();
|
||||||
$journals = $collector->getPaginatedJournals();
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('/budgets/list/noBudget');
|
$journals->setPath('/budgets/list/no-budget');
|
||||||
|
$count = $journals->getCollection()->count();
|
||||||
|
if ($count === 0) {
|
||||||
|
$start->subDay();
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return view('budgets.no-budget', compact('journals', 'subTitle'));
|
if ($moment != 'all' && $loop > 1) {
|
||||||
|
$subTitle = trans(
|
||||||
|
'firefly.without_budget_between',
|
||||||
|
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('budgets.no-budget', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param BudgetIncomeRequest $request
|
||||||
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function postUpdateIncome(BudgetIncomeRequest $request)
|
public function postUpdateIncome(BudgetIncomeRequest $request)
|
||||||
@@ -252,7 +369,7 @@ class BudgetController extends Controller
|
|||||||
$journals->setPath('/budgets/show/' . $budget->id);
|
$journals->setPath('/budgets/show/' . $budget->id);
|
||||||
|
|
||||||
|
|
||||||
$subTitle = e($budget->name);
|
$subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
|
||||||
|
|
||||||
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
|
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
|
||||||
}
|
}
|
||||||
@@ -312,10 +429,11 @@ class BudgetController extends Controller
|
|||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval($request->get('create_another')) === 1) {
|
if (intval($request->get('create_another')) === 1) {
|
||||||
// set value so create routine will not overwrite URL:
|
// @codeCoverageIgnoreStart
|
||||||
$request->session()->put('budgets.create.fromStore', true);
|
$request->session()->put('budgets.create.fromStore', true);
|
||||||
|
|
||||||
return redirect(route('budgets.create'))->withInput();
|
return redirect(route('budgets.create'))->withInput();
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('budgets.create.uri'));
|
return redirect($this->getPreviousUri('budgets.create.uri'));
|
||||||
@@ -336,10 +454,11 @@ class BudgetController extends Controller
|
|||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval($request->get('return_to_edit')) === 1) {
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
// set value so edit routine will not overwrite URL:
|
// @codeCoverageIgnoreStart
|
||||||
$request->session()->put('budgets.edit.fromUpdate', true);
|
$request->session()->put('budgets.edit.fromUpdate', true);
|
||||||
|
|
||||||
return redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]);
|
return redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]);
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('budgets.edit.uri'));
|
return redirect($this->getPreviousUri('budgets.edit.uri'));
|
||||||
@@ -402,7 +521,6 @@ class BudgetController extends Controller
|
|||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Budget $budget
|
* @param Budget $budget
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
@@ -420,7 +538,7 @@ class BudgetController extends Controller
|
|||||||
$cache->addProperty('get-limits');
|
$cache->addProperty('get-limits');
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return $cache->get();
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var AccountRepositoryInterface $accountRepository */
|
/** @var AccountRepositoryInterface $accountRepository */
|
||||||
@@ -439,4 +557,57 @@ class BudgetController extends Controller
|
|||||||
return $set;
|
return $set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getPeriodOverview(): Collection
|
||||||
|
{
|
||||||
|
$repository = app(JournalRepositoryInterface::class);
|
||||||
|
$first = $repository->first();
|
||||||
|
$start = $first->date ?? new Carbon;
|
||||||
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = Navigation::endOfX(new Carbon, $range);
|
||||||
|
$entries = new Collection;
|
||||||
|
|
||||||
|
// properties for cache
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('no-budget-period-entries');
|
||||||
|
|
||||||
|
if ($cache->has()) {
|
||||||
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug('Going to get period expenses and incomes.');
|
||||||
|
while ($end >= $start) {
|
||||||
|
$end = Navigation::startOfPeriod($end, $range);
|
||||||
|
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||||
|
|
||||||
|
// count journals without budget in this period:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$set = $collector->getJournals();
|
||||||
|
$sum = $set->sum('transaction_amount');
|
||||||
|
$journals = $set->count();
|
||||||
|
$dateStr = $end->format('Y-m-d');
|
||||||
|
$dateName = Navigation::periodShow($end, $range);
|
||||||
|
$entries->push(
|
||||||
|
[
|
||||||
|
'string' => $dateStr,
|
||||||
|
'name' => $dateName,
|
||||||
|
'count' => $journals,
|
||||||
|
'sum' => $sum,
|
||||||
|
'date' => clone $end,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||||
|
}
|
||||||
|
$cache->store($entries);
|
||||||
|
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,21 @@ namespace FireflyIII\Http\Controllers;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||||
use FireflyIII\Http\Requests\CategoryFormRequest;
|
use FireflyIII\Http\Requests\CategoryFormRequest;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
|
use Steam;
|
||||||
use View;
|
use View;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,118 +155,171 @@ class CategoryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
* @param JournalRepositoryInterface $repository
|
||||||
|
* @param string $moment
|
||||||
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function noCategory()
|
public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
|
||||||
{
|
{
|
||||||
/** @var Carbon $start */
|
// default values:
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
/** @var Carbon $end */
|
$start = null;
|
||||||
$end = session('end', Carbon::now()->startOfMonth());
|
$end = null;
|
||||||
|
$periods = new Collection;
|
||||||
|
|
||||||
// new collector:
|
// prep for "all" view.
|
||||||
/** @var JournalCollectorInterface $collector */
|
if ($moment === 'all') {
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$subTitle = trans('firefly.all_journals_without_category');
|
||||||
$collector->setAllAssetAccounts()->setRange($start, $end)->withoutCategory();//->groupJournals();
|
$first = $repository->first();
|
||||||
$journals = $collector->getJournals();
|
$start = $first->date ?? new Carbon;
|
||||||
|
$end = new Carbon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prep for "specific date" view.
|
||||||
|
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||||
|
$start = new Carbon($moment);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
$subTitle = trans(
|
$subTitle = trans(
|
||||||
'firefly.without_category_between',
|
'firefly.without_category_between',
|
||||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
);
|
);
|
||||||
|
$periods = $this->getNoCategoryPeriodOverview();
|
||||||
return view('categories.no-category', compact('journals', 'subTitle'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// prep for current period
|
||||||
* @param Request $request
|
if (strlen($moment) === 0) {
|
||||||
* @param JournalCollectorInterface $collector
|
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||||
* @param Category $category
|
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
*
|
$periods = $this->getNoCategoryPeriodOverview();
|
||||||
* @return View
|
$subTitle = trans(
|
||||||
*/
|
'firefly.without_category_between',
|
||||||
public function show(Request $request, JournalCollectorInterface $collector, Category $category)
|
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
{
|
);
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
}
|
||||||
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
|
|
||||||
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
||||||
$hideCategory = true; // used in list.
|
|
||||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$subTitle = $category->name;
|
|
||||||
$subTitleIcon = 'fa-bar-chart';
|
|
||||||
$entries = $this->getGroupedEntries($category);
|
|
||||||
$method = 'default';
|
|
||||||
|
|
||||||
// get journals
|
$count = 0;
|
||||||
$collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation();
|
$loop = 0;
|
||||||
|
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||||
|
Log::info('Now at no-cat loop start.');
|
||||||
|
while ($count === 0 && $loop < 3) {
|
||||||
|
$loop++;
|
||||||
|
Log::info('Count is zero, search for journals.');
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount();
|
||||||
|
$collector->removeFilter(InternalTransferFilter::class);
|
||||||
$journals = $collector->getPaginatedJournals();
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('categories/show/' . $category->id);
|
$journals->setPath('/categories/list/no-category');
|
||||||
|
$count = $journals->getCollection()->count();
|
||||||
|
if ($count === 0) {
|
||||||
|
$start->subDay();
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($moment != 'all' && $loop > 1) {
|
||||||
|
$subTitle = trans(
|
||||||
|
'firefly.without_category_between',
|
||||||
|
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return view('categories.show', compact('category', 'method', 'journals', 'entries', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end'));
|
return view('categories.no-category', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param CategoryRepositoryInterface $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
|
* @param string $moment
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function showAll(Request $request, CategoryRepositoryInterface $repository, Category $category)
|
public function show(Request $request, CategoryRepositoryInterface $repository, Category $category, string $moment = '')
|
||||||
{
|
{
|
||||||
|
// default values:
|
||||||
|
$subTitle = $category->name;
|
||||||
|
$subTitleIcon = 'fa-bar-chart';
|
||||||
|
$page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page'));
|
||||||
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
|
$count = 0;
|
||||||
|
$loop = 0;
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
$start = null;
|
||||||
|
$end = null;
|
||||||
|
$periods = new Collection;
|
||||||
|
|
||||||
|
|
||||||
|
// prep for "all" view.
|
||||||
|
if ($moment === 'all') {
|
||||||
|
$subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]);
|
||||||
$start = $repository->firstUseDate($category);
|
$start = $repository->firstUseDate($category);
|
||||||
if ($start->year == 1900) {
|
$end = new Carbon;
|
||||||
$start = new Carbon;
|
|
||||||
}
|
}
|
||||||
$end = Navigation::endOfPeriod(new Carbon, $range);
|
|
||||||
$subTitle = $category->name;
|
|
||||||
$subTitleIcon = 'fa-bar-chart';
|
|
||||||
$hideCategory = true; // used in list.
|
|
||||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
|
||||||
$method = 'all';
|
|
||||||
|
|
||||||
|
// prep for "specific date" view.
|
||||||
|
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||||
|
$start = new Carbon($moment);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
$subTitle = trans(
|
||||||
|
'firefly.journals_in_period_for_category',
|
||||||
|
['name' => $category->name,
|
||||||
|
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
|
);
|
||||||
|
$periods = $this->getPeriodOverview($category);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prep for current period
|
||||||
|
if (strlen($moment) === 0) {
|
||||||
|
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||||
|
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
|
$periods = $this->getPeriodOverview($category);
|
||||||
|
$subTitle = trans(
|
||||||
|
'firefly.journals_in_period_for_category',
|
||||||
|
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||||
|
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||||
|
Log::info('Now at category loop start.');
|
||||||
|
while ($count === 0 && $loop < 3) {
|
||||||
|
$loop++;
|
||||||
|
Log::info('Count is zero, search for journals.');
|
||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setCategory($category)->withBudgetInformation();
|
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||||
|
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
|
||||||
|
$collector->removeFilter(InternalTransferFilter::class);
|
||||||
$journals = $collector->getPaginatedJournals();
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('categories/show/' . $category->id . '/all');
|
$journals->setPath('categories/show/' . $category->id);
|
||||||
|
$count = $journals->getCollection()->count();
|
||||||
return view('categories.show', compact('category', 'method', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end'));
|
if ($count === 0) {
|
||||||
|
$start->subDay();
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if ($moment != 'all' && $loop > 1) {
|
||||||
* @param Request $request
|
$subTitle = trans(
|
||||||
* @param Category $category
|
'firefly.journals_in_period_for_category',
|
||||||
* @param string $date
|
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||||
*
|
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||||
* @return View
|
);
|
||||||
*/
|
|
||||||
public function showByDate(Request $request, Category $category, string $date)
|
|
||||||
{
|
|
||||||
$carbon = new Carbon($date);
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
|
||||||
$start = Navigation::startOfPeriod($carbon, $range);
|
|
||||||
$end = Navigation::endOfPeriod($carbon, $range);
|
|
||||||
$subTitle = $category->name;
|
|
||||||
$subTitleIcon = 'fa-bar-chart';
|
|
||||||
$hideCategory = true; // used in list.
|
|
||||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
|
||||||
$entries = $this->getGroupedEntries($category);
|
|
||||||
$method = 'date';
|
|
||||||
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category)->withBudgetInformation();
|
|
||||||
$journals = $collector->getPaginatedJournals();
|
|
||||||
$journals->setPath('categories/show/' . $category->id . '/' . $date);
|
|
||||||
|
|
||||||
return view('categories.show', compact('category', 'method', 'entries', 'journals', 'hideCategory', 'subTitle', 'subTitleIcon', 'start', 'end'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return view('categories.show', compact('category', 'moment', 'journals', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CategoryFormRequest $request
|
* @param CategoryFormRequest $request
|
||||||
* @param CategoryRepositoryInterface $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
@@ -277,9 +335,11 @@ class CategoryController extends Controller
|
|||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval($request->get('create_another')) === 1) {
|
if (intval($request->get('create_another')) === 1) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
$request->session()->put('categories.create.fromStore', true);
|
$request->session()->put('categories.create.fromStore', true);
|
||||||
|
|
||||||
return redirect(route('categories.create'))->withInput();
|
return redirect(route('categories.create'))->withInput();
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect(route('categories.index'));
|
return redirect(route('categories.index'));
|
||||||
@@ -302,20 +362,100 @@ class CategoryController extends Controller
|
|||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval($request->get('return_to_edit')) === 1) {
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
$request->session()->put('categories.edit.fromUpdate', true);
|
$request->session()->put('categories.edit.fromUpdate', true);
|
||||||
|
|
||||||
return redirect(route('categories.edit', [$category->id]));
|
return redirect(route('categories.edit', [$category->id]));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('categories.edit.uri'));
|
return redirect($this->getPreviousUri('categories.edit.uri'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getNoCategoryPeriodOverview(): Collection
|
||||||
|
{
|
||||||
|
$repository = app(JournalRepositoryInterface::class);
|
||||||
|
$first = $repository->first();
|
||||||
|
$start = $first->date ?? new Carbon;
|
||||||
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = Navigation::endOfX(new Carbon, $range);
|
||||||
|
$entries = new Collection;
|
||||||
|
|
||||||
|
// properties for cache
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('no-budget-period-entries');
|
||||||
|
|
||||||
|
if ($cache->has()) {
|
||||||
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||||
|
while ($end >= $start) {
|
||||||
|
Log::debug('Loop!');
|
||||||
|
$end = Navigation::startOfPeriod($end, $range);
|
||||||
|
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||||
|
|
||||||
|
// count journals without budget in this period:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
|
||||||
|
->withOpposingAccount();
|
||||||
|
$collector->removeFilter(InternalTransferFilter::class);
|
||||||
|
$count = $collector->getJournals()->count();
|
||||||
|
|
||||||
|
// amount transferred
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
|
||||||
|
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
|
||||||
|
$collector->removeFilter(InternalTransferFilter::class);
|
||||||
|
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
|
||||||
|
|
||||||
|
// amount spent
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$spent = $collector->getJournals()->sum('transaction_amount');
|
||||||
|
|
||||||
|
// amount earned
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::DEPOSIT]);
|
||||||
|
$earned = $collector->getJournals()->sum('transaction_amount');
|
||||||
|
|
||||||
|
$dateStr = $end->format('Y-m-d');
|
||||||
|
$dateName = Navigation::periodShow($end, $range);
|
||||||
|
$entries->push(
|
||||||
|
[
|
||||||
|
'string' => $dateStr,
|
||||||
|
'name' => $dateName,
|
||||||
|
'count' => $count,
|
||||||
|
'spent' => $spent,
|
||||||
|
'earned' => $earned,
|
||||||
|
'transferred' => $transferred,
|
||||||
|
'date' => clone $end,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||||
|
}
|
||||||
|
Log::debug('End of loops');
|
||||||
|
$cache->store($entries);
|
||||||
|
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
*
|
*
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
private function getGroupedEntries(Category $category): Collection
|
private function getPeriodOverview(Category $category): Collection
|
||||||
{
|
{
|
||||||
/** @var CategoryRepositoryInterface $repository */
|
/** @var CategoryRepositoryInterface $repository */
|
||||||
$repository = app(CategoryRepositoryInterface::class);
|
$repository = app(CategoryRepositoryInterface::class);
|
||||||
@@ -339,7 +479,7 @@ class CategoryController extends Controller
|
|||||||
$cache->addProperty($category->id);
|
$cache->addProperty($category->id);
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return $cache->get();
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
while ($end >= $first) {
|
while ($end >= $first) {
|
||||||
$end = Navigation::startOfPeriod($end, $range);
|
$end = Navigation::startOfPeriod($end, $range);
|
||||||
@@ -348,7 +488,26 @@ class CategoryController extends Controller
|
|||||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
|
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
|
||||||
$dateStr = $end->format('Y-m-d');
|
$dateStr = $end->format('Y-m-d');
|
||||||
$dateName = Navigation::periodShow($end, $range);
|
$dateName = Navigation::periodShow($end, $range);
|
||||||
$entries->push([$dateStr, $dateName, $spent, $earned, clone $end]);
|
|
||||||
|
// amount transferred
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->setCategory($category)
|
||||||
|
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
|
||||||
|
$collector->removeFilter(InternalTransferFilter::class);
|
||||||
|
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
|
||||||
|
|
||||||
|
$entries->push(
|
||||||
|
[
|
||||||
|
'string' => $dateStr,
|
||||||
|
'name' => $dateName,
|
||||||
|
'spent' => $spent,
|
||||||
|
'earned' => $earned,
|
||||||
|
'sum' => bcadd($earned, $spent),
|
||||||
|
'transferred' => $transferred,
|
||||||
|
'date' => clone $end,
|
||||||
|
]
|
||||||
|
);
|
||||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||||
}
|
}
|
||||||
$cache->store($entries);
|
$cache->store($entries);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Http\Controllers\Chart;
|
namespace FireflyIII\Http\Controllers\Chart;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Exception;
|
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
@@ -26,6 +25,7 @@ use FireflyIII\Models\TransactionType;
|
|||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Log;
|
use Log;
|
||||||
@@ -61,15 +61,12 @@ class AccountController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function all(Account $account)
|
public function all(Account $account)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty('chart.account.all');
|
$cache->addProperty('chart.account.all');
|
||||||
$cache->addProperty($account->id);
|
$cache->addProperty($account->id);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
Log::debug('Return chart.account.all from cache.');
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
|
|
||||||
return Response::json($cache->get());
|
|
||||||
}
|
}
|
||||||
Log::debug('Regenerate chart.account.all from scratch.');
|
|
||||||
|
|
||||||
/** @var AccountRepositoryInterface $repository */
|
/** @var AccountRepositoryInterface $repository */
|
||||||
$repository = app(AccountRepositoryInterface::class);
|
$repository = app(AccountRepositoryInterface::class);
|
||||||
@@ -112,14 +109,13 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.account.expense-accounts');
|
$cache->addProperty('chart.account.expense-accounts');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$start->subDay();
|
$start->subDay();
|
||||||
|
|
||||||
$accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
$accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
$startBalances = Steam::balancesByAccounts($accounts, $start);
|
||||||
$startBalances = Steam::balancesById($ids, $start);
|
$endBalances = Steam::balancesByAccounts($accounts, $end);
|
||||||
$endBalances = Steam::balancesById($ids, $end);
|
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
@@ -131,6 +127,7 @@ class AccountController extends Controller
|
|||||||
$chartData[$account->name] = $diff;
|
$chartData[$account->name] = $diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arsort($chartData);
|
arsort($chartData);
|
||||||
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
@@ -139,14 +136,13 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param JournalCollectorInterface $collector
|
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function expenseBudget(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
|
public function expenseBudget(Account $account, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($account->id);
|
$cache->addProperty($account->id);
|
||||||
@@ -154,12 +150,10 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.account.expense-budget');
|
$cache->addProperty('chart.account.expense-budget');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$collector->setAccounts(new Collection([$account]))
|
$collector = app(JournalCollectorInterface::class);
|
||||||
->setRange($start, $end)
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
->withBudgetInformation()
|
|
||||||
->setTypes([TransactionType::WITHDRAWAL]);
|
|
||||||
$transactions = $collector->getJournals();
|
$transactions = $collector->getJournals();
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
$result = [];
|
$result = [];
|
||||||
@@ -185,14 +179,27 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param JournalCollectorInterface $collector
|
* @param AccountRepositoryInterface $repository
|
||||||
|
* @param Account $account
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account)
|
||||||
|
{
|
||||||
|
$start = $repository->oldestJournalDate($account);
|
||||||
|
$end = Carbon::now();
|
||||||
|
|
||||||
|
return $this->expenseBudget($account, $start, $end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function expenseCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
|
public function expenseCategory(Account $account, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($account->id);
|
$cache->addProperty($account->id);
|
||||||
@@ -200,9 +207,10 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.account.expense-category');
|
$cache->addProperty('chart.account.expense-category');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]);
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
$transactions = $collector->getJournals();
|
$transactions = $collector->getJournals();
|
||||||
$result = [];
|
$result = [];
|
||||||
@@ -228,6 +236,20 @@ class AccountController extends Controller
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AccountRepositoryInterface $repository
|
||||||
|
* @param Account $account
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account)
|
||||||
|
{
|
||||||
|
$start = $repository->oldestJournalDate($account);
|
||||||
|
$end = Carbon::now();
|
||||||
|
|
||||||
|
return $this->expenseCategory($account, $start, $end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the balances for all the user's frontpage accounts.
|
* Shows the balances for all the user's frontpage accounts.
|
||||||
*
|
*
|
||||||
@@ -254,14 +276,13 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param JournalCollectorInterface $collector
|
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function incomeCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
|
public function incomeCategory(Account $account, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($account->id);
|
$cache->addProperty($account->id);
|
||||||
@@ -269,10 +290,11 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.account.income-category');
|
$cache->addProperty('chart.account.income-category');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab all journals:
|
// grab all journals:
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]);
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]);
|
||||||
$transactions = $collector->getJournals();
|
$transactions = $collector->getJournals();
|
||||||
$result = [];
|
$result = [];
|
||||||
@@ -298,20 +320,28 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param AccountRepositoryInterface $repository
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
* @param string $date
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account)
|
||||||
|
{
|
||||||
|
$start = $repository->oldestJournalDate($account);
|
||||||
|
$end = Carbon::now();
|
||||||
|
|
||||||
|
return $this->incomeCategory($account, $start, $end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon $start
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function period(Account $account, string $date)
|
public function period(Account $account, Carbon $start)
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
$start = new Carbon($date);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
throw new FireflyException('"' . e($date) . '" does not seem to be a valid date. Should be in the format YYYY-MM-DD');
|
|
||||||
}
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
$end = Navigation::endOfPeriod($start, $range);
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties();
|
||||||
@@ -320,7 +350,7 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty('chart.account.period');
|
$cache->addProperty('chart.account.period');
|
||||||
$cache->addProperty($account->id);
|
$cache->addProperty($account->id);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$format = (string)trans('config.month_and_day');
|
$format = (string)trans('config.month_and_day');
|
||||||
@@ -375,14 +405,13 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.account.revenue-accounts');
|
$cache->addProperty('chart.account.revenue-accounts');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$accounts = $repository->getAccountsByType([AccountType::REVENUE]);
|
$accounts = $repository->getAccountsByType([AccountType::REVENUE]);
|
||||||
|
|
||||||
$start->subDay();
|
$start->subDay();
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
$startBalances = Steam::balancesByAccounts($accounts, $start);
|
||||||
$startBalances = Steam::balancesById($ids, $start);
|
$endBalances = Steam::balancesByAccounts($accounts, $end);
|
||||||
$endBalances = Steam::balancesById($ids, $end);
|
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
$id = $account->id;
|
$id = $account->id;
|
||||||
@@ -396,7 +425,7 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
arsort($chartData);
|
arsort($chartData);
|
||||||
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
$data = $this->generator->singleSet(strval(trans('firefly.earned')), $chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -421,7 +450,7 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty('chart.account.single');
|
$cache->addProperty('chart.account.single');
|
||||||
$cache->addProperty($account->id);
|
$cache->addProperty($account->id);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$format = (string)trans('config.month_and_day');
|
$format = (string)trans('config.month_and_day');
|
||||||
@@ -461,16 +490,19 @@ class AccountController extends Controller
|
|||||||
$cache->addProperty('chart.account.account-balance-chart');
|
$cache->addProperty('chart.account.account-balance-chart');
|
||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
Log::debug('Return chart.account.account-balance-chart from cache.');
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
|
|
||||||
return $cache->get();
|
|
||||||
}
|
}
|
||||||
Log::debug('Regenerate chart.account.account-balance-chart from scratch.');
|
Log::debug('Regenerate chart.account.account-balance-chart from scratch.');
|
||||||
|
|
||||||
|
/** @var CurrencyRepositoryInterface $repository */
|
||||||
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
|
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
|
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||||
$currentSet = [
|
$currentSet = [
|
||||||
'label' => $account->name,
|
'label' => $account->name,
|
||||||
|
'currency_symbol' => $currency->symbol,
|
||||||
'entries' => [],
|
'entries' => [],
|
||||||
];
|
];
|
||||||
$currentStart = clone $start;
|
$currentStart = clone $start;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class BillController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.bill.frontpage');
|
$cache->addProperty('chart.bill.frontpage');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
|
$paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
|
||||||
@@ -88,7 +88,7 @@ class BillController extends Controller
|
|||||||
$cache->addProperty('chart.bill.single');
|
$cache->addProperty('chart.bill.single');
|
||||||
$cache->addProperty($bill->id);
|
$cache->addProperty($bill->id);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals();
|
$results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals();
|
||||||
|
|||||||
@@ -18,16 +18,20 @@ use FireflyIII\Exceptions\FireflyException;
|
|||||||
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\BudgetLimit;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
|
use Steam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BudgetController
|
* Class BudgetController
|
||||||
@@ -80,7 +84,7 @@ class BudgetController extends Controller
|
|||||||
$cache->addProperty('chart.budget.budget');
|
$cache->addProperty('chart.budget.budget');
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$final = clone $last;
|
$final = clone $last;
|
||||||
@@ -133,7 +137,7 @@ class BudgetController extends Controller
|
|||||||
$cache->addProperty($budgetLimit->id);
|
$cache->addProperty($budgetLimit->id);
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$entries = [];
|
$entries = [];
|
||||||
@@ -153,6 +157,144 @@ class BudgetController extends Controller
|
|||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param BudgetLimit|null $budgetLimit
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function expenseAsset(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($budget->id);
|
||||||
|
$cache->addProperty($budgetLimit->id ?? 0);
|
||||||
|
$cache->addProperty('chart.budget.expense-asset');
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget);
|
||||||
|
if (!is_null($budgetLimit->id)) {
|
||||||
|
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$result = [];
|
||||||
|
$chartData = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$assetId = intval($transaction->account_id);
|
||||||
|
$result[$assetId] = $result[$assetId] ?? '0';
|
||||||
|
$result[$assetId] = bcadd($transaction->transaction_amount, $result[$assetId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = $this->getAccountNames(array_keys($result));
|
||||||
|
foreach ($result as $assetId => $amount) {
|
||||||
|
$chartData[$names[$assetId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param BudgetLimit|null $budgetLimit
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function expenseCategory(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($budget->id);
|
||||||
|
$cache->addProperty($budgetLimit->id ?? 0);
|
||||||
|
$cache->addProperty('chart.budget.expense-category');
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withCategoryInformation();
|
||||||
|
if (!is_null($budgetLimit->id)) {
|
||||||
|
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$result = [];
|
||||||
|
$chartData = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$jrnlCatId = intval($transaction->transaction_journal_category_id);
|
||||||
|
$transCatId = intval($transaction->transaction_category_id);
|
||||||
|
$categoryId = max($jrnlCatId, $transCatId);
|
||||||
|
$result[$categoryId] = $result[$categoryId] ?? '0';
|
||||||
|
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = $this->getCategoryNames(array_keys($result));
|
||||||
|
foreach ($result as $categoryId => $amount) {
|
||||||
|
$chartData[$names[$categoryId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param BudgetLimit|null $budgetLimit
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function expenseExpense(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($budget->id);
|
||||||
|
$cache->addProperty($budgetLimit->id ?? 0);
|
||||||
|
$cache->addProperty('chart.budget.expense-expense');
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withOpposingAccount();
|
||||||
|
if (!is_null($budgetLimit->id)) {
|
||||||
|
$collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$result = [];
|
||||||
|
$chartData = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$opposingId = intval($transaction->opposing_account_id);
|
||||||
|
$result[$opposingId] = $result[$opposingId] ?? '0';
|
||||||
|
$result[$opposingId] = bcadd($transaction->transaction_amount, $result[$opposingId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = $this->getAccountNames(array_keys($result));
|
||||||
|
foreach ($result as $opposingId => $amount) {
|
||||||
|
$name = $names[$opposingId] ?? 'no name';
|
||||||
|
$chartData[$name] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a budget list with spent/left/overspent.
|
* Shows a budget list with spent/left/overspent.
|
||||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
||||||
@@ -170,7 +312,7 @@ class BudgetController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.budget.frontpage');
|
$cache->addProperty('chart.budget.frontpage');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$budgets = $this->repository->getActiveBudgets();
|
$budgets = $this->repository->getActiveBudgets();
|
||||||
$chartData = [
|
$chartData = [
|
||||||
@@ -179,12 +321,12 @@ class BudgetController extends Controller
|
|||||||
['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',],
|
['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/** @var Budget $budget */
|
/** @var Budget $budget */
|
||||||
foreach ($budgets as $budget) {
|
foreach ($budgets as $budget) {
|
||||||
// get relevant repetitions:
|
// get relevant repetitions:
|
||||||
$limits = $this->repository->getBudgetLimits($budget, $start, $end);
|
$limits = $this->repository->getBudgetLimits($budget, $start, $end);
|
||||||
$expenses = $this->getExpensesForBudget($limits, $budget, $start, $end);
|
$expenses = $this->getExpensesForBudget($limits, $budget, $start, $end);
|
||||||
|
|
||||||
foreach ($expenses as $name => $row) {
|
foreach ($expenses as $name => $row) {
|
||||||
$chartData[0]['entries'][$name] = $row['spent'];
|
$chartData[0]['entries'][$name] = $row['spent'];
|
||||||
$chartData[1]['entries'][$name] = $row['left'];
|
$chartData[1]['entries'][$name] = $row['left'];
|
||||||
@@ -227,7 +369,7 @@ class BudgetController extends Controller
|
|||||||
$cache->addProperty($budget->id);
|
$cache->addProperty($budget->id);
|
||||||
$cache->addProperty('chart.budget.period');
|
$cache->addProperty('chart.budget.period');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$periods = Navigation::listOfPeriods($start, $end);
|
$periods = Navigation::listOfPeriods($start, $end);
|
||||||
$entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses
|
$entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses
|
||||||
@@ -268,7 +410,7 @@ class BudgetController extends Controller
|
|||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
$cache->addProperty('chart.budget.no-budget');
|
$cache->addProperty('chart.budget.no-budget');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
// the expenses:
|
// the expenses:
|
||||||
@@ -288,6 +430,28 @@ class BudgetController extends Controller
|
|||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $accountIds
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getAccountNames(array $accountIds): array
|
||||||
|
{
|
||||||
|
/** @var AccountRepositoryInterface $repository */
|
||||||
|
$repository = app(AccountRepositoryInterface::class);
|
||||||
|
$accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::EXPENSE]);
|
||||||
|
$grouped = $accounts->groupBy('id')->toArray();
|
||||||
|
$return = [];
|
||||||
|
foreach ($accountIds as $accountId) {
|
||||||
|
if (isset($grouped[$accountId])) {
|
||||||
|
$return[$accountId] = $grouped[$accountId][0]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$return[0] = '(no name)';
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Budget $budget
|
* @param Budget $budget
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
@@ -314,6 +478,30 @@ class BudgetController extends Controller
|
|||||||
return $budgeted;
|
return $budgeted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small helper function for some of the charts.
|
||||||
|
*
|
||||||
|
* @param array $categoryIds
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getCategoryNames(array $categoryIds): array
|
||||||
|
{
|
||||||
|
/** @var CategoryRepositoryInterface $repository */
|
||||||
|
$repository = app(CategoryRepositoryInterface::class);
|
||||||
|
$categories = $repository->getCategories();
|
||||||
|
$grouped = $categories->groupBy('id')->toArray();
|
||||||
|
$return = [];
|
||||||
|
foreach ($categoryIds as $categoryId) {
|
||||||
|
if (isset($grouped[$categoryId])) {
|
||||||
|
$return[$categoryId] = $grouped[$categoryId][0]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$return[0] = trans('firefly.noCategory');
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok.
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok.
|
||||||
@@ -342,9 +530,7 @@ class BudgetController extends Controller
|
|||||||
$rows = $this->spentInPeriodMulti($budget, $limits);
|
$rows = $this->spentInPeriodMulti($budget, $limits);
|
||||||
foreach ($rows as $name => $row) {
|
foreach ($rows as $name => $row) {
|
||||||
if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) {
|
if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) {
|
||||||
$return[$name]['spent'] = bcmul($row['spent'], '-1');
|
$return[$name] = $row;
|
||||||
$return[$name]['left'] = $row['left'];
|
|
||||||
$return[$name]['overspent'] = bcmul($row['overspent'], '-1');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset($rows, $row);
|
unset($rows, $row);
|
||||||
@@ -376,6 +562,7 @@ class BudgetController extends Controller
|
|||||||
/** @var BudgetLimit $budgetLimit */
|
/** @var BudgetLimit $budgetLimit */
|
||||||
foreach ($limits as $budgetLimit) {
|
foreach ($limits as $budgetLimit) {
|
||||||
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
|
$expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
|
||||||
|
$expenses = Steam::positive($expenses);
|
||||||
|
|
||||||
if ($limits->count() > 1) {
|
if ($limits->count() > 1) {
|
||||||
$name = $budget->name . ' ' . trans(
|
$name = $budget->name . ' ' . trans(
|
||||||
@@ -386,10 +573,19 @@ class BudgetController extends Controller
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* amount: amount of budget limit
|
||||||
|
* left: amount of budget limit min spent, or 0 when < 0.
|
||||||
|
* spent: spent, or amount of budget limit when > amount
|
||||||
|
*/
|
||||||
$amount = $budgetLimit->amount;
|
$amount = $budgetLimit->amount;
|
||||||
$left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses);
|
$leftInLimit = bcsub($amount, $expenses);
|
||||||
$spent = $expenses;
|
$hasOverspent = bccomp($leftInLimit, '0') === -1;
|
||||||
$overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';
|
|
||||||
|
$left = $hasOverspent ? '0' : bcsub($amount, $expenses);
|
||||||
|
$spent = $hasOverspent ? $amount : $expenses;
|
||||||
|
$overspent = $hasOverspent ? Steam::positive($leftInLimit) : '0';
|
||||||
|
|
||||||
$return[$name] = [
|
$return[$name] = [
|
||||||
'left' => $left,
|
'left' => $left,
|
||||||
'overspent' => $overspent,
|
'overspent' => $overspent,
|
||||||
|
|||||||
@@ -16,15 +16,16 @@ namespace FireflyIII\Http\Controllers\Chart;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
|
|
||||||
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
|
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\BudgetLimit;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -41,8 +42,6 @@ use Response;
|
|||||||
class BudgetReportController extends Controller
|
class BudgetReportController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var AccountRepositoryInterface */
|
|
||||||
private $accountRepository;
|
|
||||||
/** @var BudgetRepositoryInterface */
|
/** @var BudgetRepositoryInterface */
|
||||||
private $budgetRepository;
|
private $budgetRepository;
|
||||||
/** @var GeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
@@ -58,7 +57,6 @@ class BudgetReportController extends Controller
|
|||||||
function ($request, $next) {
|
function ($request, $next) {
|
||||||
$this->generator = app(GeneratorInterface::class);
|
$this->generator = app(GeneratorInterface::class);
|
||||||
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
||||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
@@ -131,10 +129,8 @@ class BudgetReportController extends Controller
|
|||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
/** @var BudgetRepositoryInterface $repository */
|
|
||||||
$repository = app(BudgetRepositoryInterface::class);
|
|
||||||
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||||
$function = Navigation::preferredEndOfPeriod($start, $end);
|
$function = Navigation::preferredEndOfPeriod($start, $end);
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
@@ -163,7 +159,7 @@ class BudgetReportController extends Controller
|
|||||||
'entries' => [],
|
'entries' => [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$allBudgetLimits = $repository->getAllBudgetLimits($start, $end);
|
$allBudgetLimits = $this->budgetRepository->getAllBudgetLimits($start, $end);
|
||||||
$sumOfExpenses = [];
|
$sumOfExpenses = [];
|
||||||
$leftOfLimits = [];
|
$leftOfLimits = [];
|
||||||
while ($currentStart < $end) {
|
while ($currentStart < $end) {
|
||||||
@@ -203,6 +199,7 @@ class BudgetReportController extends Controller
|
|||||||
* Returns the budget limits belonging to the given budget and valid on the given day.
|
* Returns the budget limits belonging to the given budget and valid on the given day.
|
||||||
*
|
*
|
||||||
* @param Collection $budgetLimits
|
* @param Collection $budgetLimits
|
||||||
|
* @param Budget $budget
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
@@ -239,12 +236,15 @@ class BudgetReportController extends Controller
|
|||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
->setBudgets($budgets)->withOpposingAccount()->disableFilter();
|
->setBudgets($budgets)->withOpposingAccount();
|
||||||
$accountIds = $accounts->pluck('id')->toArray();
|
$collector->removeFilter(TransferFilter::class);
|
||||||
$transactions = $collector->getJournals();
|
|
||||||
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
|
|
||||||
|
|
||||||
return $set;
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(PositiveAmountFilter::class);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,21 +268,4 @@ class BudgetReportController extends Controller
|
|||||||
return $grouped;
|
return $grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $set
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function groupByOpposingAccount(Collection $set): array
|
|
||||||
{
|
|
||||||
$grouped = [];
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($set as $transaction) {
|
|
||||||
$accountId = $transaction->opposing_account_id;
|
|
||||||
$grouped[$accountId] = $grouped[$accountId] ?? '0';
|
|
||||||
$grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $grouped;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use FireflyIII\Http\Controllers\Controller;
|
|||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
@@ -50,19 +50,19 @@ class CategoryController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Show an overview for a category for all time, per month/week/year.
|
* Show an overview for a category for all time, per month/week/year.
|
||||||
*
|
*
|
||||||
* @param CRI $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
* @param AccountRepositoryInterface $accountRepository
|
* @param AccountRepositoryInterface $accountRepository
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
*/
|
*/
|
||||||
public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category)
|
public function all(CategoryRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Category $category)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty('chart.category.all');
|
$cache->addProperty('chart.category.all');
|
||||||
$cache->addProperty($category->id);
|
$cache->addProperty($category->id);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$start = $repository->firstUseDate($category);
|
$start = $repository->firstUseDate($category);
|
||||||
@@ -86,15 +86,23 @@ class CategoryController extends Controller
|
|||||||
'entries' => [],
|
'entries' => [],
|
||||||
'type' => 'bar',
|
'type' => 'bar',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.sum')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
while ($start <= $end) {
|
while ($start <= $end) {
|
||||||
$currentEnd = Navigation::endOfPeriod($start, $range);
|
$currentEnd = Navigation::endOfPeriod($start, $range);
|
||||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
||||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
||||||
|
$sum = bcadd($spent, $earned);
|
||||||
$label = Navigation::periodShow($start, $range);
|
$label = Navigation::periodShow($start, $range);
|
||||||
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||||
$chartData[1]['entries'][$label] = $earned;
|
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||||
|
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||||
$start = Navigation::addPeriod($start, $range, 0);
|
$start = Navigation::addPeriod($start, $range, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,27 +114,12 @@ class CategoryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
* @param Category $category
|
|
||||||
*
|
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
|
||||||
*/
|
|
||||||
public function currentPeriod(CRI $repository, Category $category)
|
|
||||||
{
|
|
||||||
$start = clone session('start', Carbon::now()->startOfMonth());
|
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
|
||||||
$data = $this->makePeriodChart($repository, $category, $start, $end);
|
|
||||||
|
|
||||||
return Response::json($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param CRI $repository
|
|
||||||
* @param AccountRepositoryInterface $accountRepository
|
* @param AccountRepositoryInterface $accountRepository
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function frontpage(CRI $repository, AccountRepositoryInterface $accountRepository)
|
public function frontpage(CategoryRepositoryInterface $repository, AccountRepositoryInterface $accountRepository)
|
||||||
{
|
{
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$start = session('start', Carbon::now()->startOfMonth());
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
@@ -136,7 +129,7 @@ class CategoryController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('chart.category.frontpage');
|
$cache->addProperty('chart.category.frontpage');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
$categories = $repository->getCategories();
|
$categories = $repository->getCategories();
|
||||||
@@ -161,7 +154,7 @@ class CategoryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
@@ -169,7 +162,7 @@ class CategoryController extends Controller
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse|mixed
|
* @return \Illuminate\Http\JsonResponse|mixed
|
||||||
*/
|
*/
|
||||||
public function reportPeriod(CRI $repository, Category $category, Collection $accounts, Carbon $start, Carbon $end)
|
public function reportPeriod(CategoryRepositoryInterface $repository, Category $category, Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
@@ -178,7 +171,7 @@ class CategoryController extends Controller
|
|||||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||||
$cache->addProperty($category);
|
$cache->addProperty($category);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return $cache->get();
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
|
$expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
|
||||||
$income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
|
$income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
|
||||||
@@ -194,13 +187,22 @@ class CategoryController extends Controller
|
|||||||
'entries' => [],
|
'entries' => [],
|
||||||
'type' => 'bar',
|
'type' => 'bar',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.sum')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach (array_keys($periods) as $period) {
|
foreach (array_keys($periods) as $period) {
|
||||||
$label = $periods[$period];
|
$label = $periods[$period];
|
||||||
$spent = $expenses[$category->id]['entries'][$period] ?? '0';
|
$spent = $expenses[$category->id]['entries'][$period] ?? '0';
|
||||||
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
$earned = $income[$category->id]['entries'][$period] ?? '0';
|
||||||
$chartData[1]['entries'][$label] = $income[$category->id]['entries'][$period] ?? '0';
|
$sum = bcadd($spent, $earned);
|
||||||
|
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||||
|
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||||
|
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->generator->multiSet($chartData);
|
$data = $this->generator->multiSet($chartData);
|
||||||
@@ -210,14 +212,14 @@ class CategoryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse|mixed
|
* @return \Illuminate\Http\JsonResponse|mixed
|
||||||
*/
|
*/
|
||||||
public function reportPeriodNoCategory(CRI $repository, Collection $accounts, Carbon $start, Carbon $end)
|
public function reportPeriodNoCategory(CategoryRepositoryInterface $repository, Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
@@ -225,7 +227,7 @@ class CategoryController extends Controller
|
|||||||
$cache->addProperty('chart.category.period.no-cat');
|
$cache->addProperty('chart.category.period.no-cat');
|
||||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return $cache->get();
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
|
$expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
|
||||||
$income = $repository->periodIncomeNoCategory($accounts, $start, $end);
|
$income = $repository->periodIncomeNoCategory($accounts, $start, $end);
|
||||||
@@ -241,13 +243,22 @@ class CategoryController extends Controller
|
|||||||
'entries' => [],
|
'entries' => [],
|
||||||
'type' => 'bar',
|
'type' => 'bar',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.sum')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach (array_keys($periods) as $period) {
|
foreach (array_keys($periods) as $period) {
|
||||||
$label = $periods[$period];
|
$label = $periods[$period];
|
||||||
$spent = $expenses['entries'][$period] ?? '0';
|
$spent = $expenses['entries'][$period] ?? '0';
|
||||||
|
$earned = $income['entries'][$period] ?? '0';
|
||||||
|
$sum = bcadd($spent, $earned);
|
||||||
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||||
$chartData[1]['entries'][$label] = $income['entries'][$period] ?? '0';
|
$chartData[1]['entries'][$label] = $earned;
|
||||||
|
$chartData[2]['entries'][$label] = $sum;
|
||||||
|
|
||||||
}
|
}
|
||||||
$data = $this->generator->multiSet($chartData);
|
$data = $this->generator->multiSet($chartData);
|
||||||
@@ -257,19 +268,18 @@ class CategoryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
*
|
*
|
||||||
* @param $date
|
* @param $date
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
*/
|
*/
|
||||||
public function specificPeriod(CRI $repository, Category $category, $date)
|
public function specificPeriod(CategoryRepositoryInterface $repository, Category $category, Carbon $date)
|
||||||
{
|
{
|
||||||
$carbon = new Carbon($date);
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
$start = Navigation::startOfPeriod($carbon, $range);
|
$start = Navigation::startOfPeriod($date, $range);
|
||||||
$end = Navigation::endOfPeriod($carbon, $range);
|
$end = Navigation::endOfPeriod($date, $range);
|
||||||
$data = $this->makePeriodChart($repository, $category, $start, $end);
|
$data = $this->makePeriodChart($repository, $category, $start, $end);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -277,14 +287,14 @@ class CategoryController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
* @param CategoryRepositoryInterface $repository
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end)
|
private function makePeriodChart(CategoryRepositoryInterface $repository, Category $category, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
@@ -297,7 +307,7 @@ class CategoryController extends Controller
|
|||||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return $cache->get();
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
// chart data
|
// chart data
|
||||||
@@ -312,15 +322,23 @@ class CategoryController extends Controller
|
|||||||
'entries' => [],
|
'entries' => [],
|
||||||
'type' => 'bar',
|
'type' => 'bar',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.sum')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
while ($start <= $end) {
|
while ($start <= $end) {
|
||||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
|
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
|
||||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
|
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
|
||||||
$label = Navigation::periodShow($start, '1D');
|
$sum = bcadd($spent, $earned);
|
||||||
|
$label = trim(Navigation::periodShow($start, '1D'));
|
||||||
|
|
||||||
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||||
$chartData[1]['entries'][$label] = $earned;
|
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||||
|
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||||
|
|
||||||
|
|
||||||
$start->addDay();
|
$start->addDay();
|
||||||
|
|||||||
@@ -16,15 +16,16 @@ namespace FireflyIII\Http\Controllers\Chart;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
|
|
||||||
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
|
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
@@ -41,10 +42,6 @@ use Response;
|
|||||||
class CategoryReportController extends Controller
|
class CategoryReportController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var AccountRepositoryInterface */
|
|
||||||
private $accountRepository;
|
|
||||||
/** @var CategoryRepositoryInterface */
|
|
||||||
private $categoryRepository;
|
|
||||||
/** @var GeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
private $generator;
|
private $generator;
|
||||||
|
|
||||||
@@ -57,8 +54,6 @@ class CategoryReportController extends Controller
|
|||||||
$this->middleware(
|
$this->middleware(
|
||||||
function ($request, $next) {
|
function ($request, $next) {
|
||||||
$this->generator = app(GeneratorInterface::class);
|
$this->generator = app(GeneratorInterface::class);
|
||||||
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
|
||||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
@@ -78,11 +73,8 @@ class CategoryReportController extends Controller
|
|||||||
{
|
{
|
||||||
/** @var MetaPieChartInterface $helper */
|
/** @var MetaPieChartInterface $helper */
|
||||||
$helper = app(MetaPieChartInterface::class);
|
$helper = app(MetaPieChartInterface::class);
|
||||||
$helper->setAccounts($accounts);
|
$helper->setAccounts($accounts)->setCategories($categories)->setStart($start)->setEnd($end)->setCollectOtherObjects(intval($others) === 1);
|
||||||
$helper->setCategories($categories);
|
|
||||||
$helper->setStart($start);
|
|
||||||
$helper->setEnd($end);
|
|
||||||
$helper->setCollectOtherObjects(intval($others) === 1);
|
|
||||||
$chartData = $helper->generate('expense', 'account');
|
$chartData = $helper->generate('expense', 'account');
|
||||||
$data = $this->generator->pieChart($chartData);
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
|
||||||
@@ -179,7 +171,7 @@ class CategoryReportController extends Controller
|
|||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||||
@@ -282,12 +274,15 @@ class CategoryReportController extends Controller
|
|||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
->setCategories($categories)->withOpposingAccount()->disableFilter();
|
->setCategories($categories)->withOpposingAccount();
|
||||||
$accountIds = $accounts->pluck('id')->toArray();
|
$collector->removeFilter(TransferFilter::class);
|
||||||
$transactions = $collector->getJournals();
|
|
||||||
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
|
|
||||||
|
|
||||||
return $set;
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(PositiveAmountFilter::class);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,11 +299,13 @@ class CategoryReportController extends Controller
|
|||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||||
->setCategories($categories)->withOpposingAccount();
|
->setCategories($categories)->withOpposingAccount();
|
||||||
$accountIds = $accounts->pluck('id')->toArray();
|
|
||||||
$transactions = $collector->getJournals();
|
|
||||||
$set = MonthReportGenerator::filterIncome($transactions, $accountIds);
|
|
||||||
|
|
||||||
return $set;
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(NegativeAmountFilter::class);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -331,22 +328,4 @@ class CategoryReportController extends Controller
|
|||||||
|
|
||||||
return $grouped;
|
return $grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $set
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function groupByOpposingAccount(Collection $set): array
|
|
||||||
{
|
|
||||||
$grouped = [];
|
|
||||||
/** @var Transaction $transaction */
|
|
||||||
foreach ($set as $transaction) {
|
|
||||||
$accountId = $transaction->opposing_account_id;
|
|
||||||
$grouped[$accountId] = $grouped[$accountId] ?? '0';
|
|
||||||
$grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $grouped;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class PiggyBankController extends Controller
|
|||||||
$cache->addProperty('chart.piggy-bank.history');
|
$cache->addProperty('chart.piggy-bank.history');
|
||||||
$cache->addProperty($piggyBank->id);
|
$cache->addProperty($piggyBank->id);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$set = $repository->getEvents($piggyBank);
|
$set = $repository->getEvents($piggyBank);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use FireflyIII\Http\Controllers\Controller;
|
|||||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
use Response;
|
use Response;
|
||||||
use Steam;
|
use Steam;
|
||||||
@@ -64,13 +65,12 @@ class ReportController extends Controller
|
|||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
|
||||||
$current = clone $start;
|
$current = clone $start;
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
while ($current < $end) {
|
while ($current < $end) {
|
||||||
$balances = Steam::balancesById($ids, $current);
|
$balances = Steam::balancesByAccounts($accounts, $current);
|
||||||
$sum = $this->arraySum($balances);
|
$sum = $this->arraySum($balances);
|
||||||
$label = $current->formatLocalized(strval(trans('config.month_and_day')));
|
$label = $current->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
$chartData[$label] = $sum;
|
$chartData[$label] = $sum;
|
||||||
@@ -103,8 +103,9 @@ class ReportController extends Controller
|
|||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
|
||||||
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||||
$source = $this->getChartData($accounts, $start, $end);
|
$source = $this->getChartData($accounts, $start, $end);
|
||||||
$chartData = [
|
$chartData = [
|
||||||
@@ -161,8 +162,10 @@ class ReportController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$source = $this->getChartData($accounts, $start, $end);
|
$source = $this->getChartData($accounts, $start, $end);
|
||||||
$numbers = [
|
$numbers = [
|
||||||
'sum_earned' => '0',
|
'sum_earned' => '0',
|
||||||
@@ -246,19 +249,41 @@ class ReportController extends Controller
|
|||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return $cache->get();
|
return $cache->get(); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$tasker = app(AccountTaskerInterface::class);
|
|
||||||
$currentStart = clone $start;
|
$currentStart = clone $start;
|
||||||
$spentArray = [];
|
$spentArray = [];
|
||||||
$earnedArray = [];
|
$earnedArray = [];
|
||||||
|
|
||||||
|
/** @var AccountTaskerInterface $tasker */
|
||||||
|
$tasker = app(AccountTaskerInterface::class);
|
||||||
|
|
||||||
while ($currentStart <= $end) {
|
while ($currentStart <= $end) {
|
||||||
|
|
||||||
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
|
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
|
||||||
|
$earned = strval(
|
||||||
|
array_sum(
|
||||||
|
array_map(
|
||||||
|
function ($item) {
|
||||||
|
return $item['sum'];
|
||||||
|
}, $tasker->getIncomeReport($currentStart, $currentEnd, $accounts)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$spent = strval(
|
||||||
|
array_sum(
|
||||||
|
array_map(
|
||||||
|
function ($item) {
|
||||||
|
return $item['sum'];
|
||||||
|
}, $tasker->getExpenseReport($currentStart, $currentEnd, $accounts)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
$label = $currentStart->format('Y-m') . '-01';
|
$label = $currentStart->format('Y-m') . '-01';
|
||||||
$spent = $tasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
|
||||||
$earned = $tasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
|
||||||
$spentArray[$label] = bcmul($spent, '-1');
|
$spentArray[$label] = bcmul($spent, '-1');
|
||||||
$earnedArray[$label] = $earned;
|
$earnedArray[$label] = $earned;
|
||||||
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
|
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
|
||||||
|
|||||||
367
app/Http/Controllers/Chart/TagReportController.php
Normal file
367
app/Http/Controllers/Chart/TagReportController.php
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* TagReportController.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Controllers\Chart;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
|
use FireflyIII\Helpers\Chart\MetaPieChartInterface;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||||
|
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\Tag;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Support\CacheProperties;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Navigation;
|
||||||
|
use Response;
|
||||||
|
|
||||||
|
class TagReportController extends Controller
|
||||||
|
{
|
||||||
|
/** @var GeneratorInterface */
|
||||||
|
protected $generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
// create chart generator:
|
||||||
|
$this->generator = app(GeneratorInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function accountExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var MetaPieChartInterface $helper */
|
||||||
|
$helper = app(MetaPieChartInterface::class);
|
||||||
|
$helper->setAccounts($accounts);
|
||||||
|
$helper->setTags($tags);
|
||||||
|
$helper->setStart($start);
|
||||||
|
$helper->setEnd($end);
|
||||||
|
$helper->setCollectOtherObjects(intval($others) === 1);
|
||||||
|
$chartData = $helper->generate('expense', 'account');
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function accountIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var MetaPieChartInterface $helper */
|
||||||
|
$helper = app(MetaPieChartInterface::class);
|
||||||
|
$helper->setAccounts($accounts);
|
||||||
|
$helper->setTags($tags);
|
||||||
|
$helper->setStart($start);
|
||||||
|
$helper->setEnd($end);
|
||||||
|
$helper->setCollectOtherObjects(intval($others) === 1);
|
||||||
|
$chartData = $helper->generate('income', 'account');
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function budgetExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
/** @var MetaPieChartInterface $helper */
|
||||||
|
$helper = app(MetaPieChartInterface::class);
|
||||||
|
$helper->setAccounts($accounts);
|
||||||
|
$helper->setTags($tags);
|
||||||
|
$helper->setStart($start);
|
||||||
|
$helper->setEnd($end);
|
||||||
|
$helper->setCollectOtherObjects(false);
|
||||||
|
$chartData = $helper->generate('expense', 'budget');
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function categoryExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
/** @var MetaPieChartInterface $helper */
|
||||||
|
$helper = app(MetaPieChartInterface::class);
|
||||||
|
$helper->setAccounts($accounts);
|
||||||
|
$helper->setTags($tags);
|
||||||
|
$helper->setStart($start);
|
||||||
|
$helper->setEnd($end);
|
||||||
|
$helper->setCollectOtherObjects(false);
|
||||||
|
$chartData = $helper->generate('expense', 'category');
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function mainChart(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.category.report.main');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($tags);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||||
|
$function = Navigation::preferredEndOfPeriod($start, $end);
|
||||||
|
$chartData = [];
|
||||||
|
$currentStart = clone $start;
|
||||||
|
|
||||||
|
// prep chart data:
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$chartData[$tag->id . '-in'] = [
|
||||||
|
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.income'))) . ')',
|
||||||
|
'type' => 'bar',
|
||||||
|
'yAxisID' => 'y-axis-0',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
$chartData[$tag->id . '-out'] = [
|
||||||
|
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')',
|
||||||
|
'type' => 'bar',
|
||||||
|
'yAxisID' => 'y-axis-0',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
// total in, total out:
|
||||||
|
$chartData[$tag->id . '-total-in'] = [
|
||||||
|
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.sum_of_income'))) . ')',
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
'yAxisID' => 'y-axis-1',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
$chartData[$tag->id . '-total-out'] = [
|
||||||
|
'label' => $tag->tag . ' (' . strtolower(strval(trans('firefly.sum_of_expenses'))) . ')',
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
'yAxisID' => 'y-axis-1',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$sumOfIncome = [];
|
||||||
|
$sumOfExpense = [];
|
||||||
|
|
||||||
|
while ($currentStart < $end) {
|
||||||
|
$currentEnd = clone $currentStart;
|
||||||
|
$currentEnd = $currentEnd->$function();
|
||||||
|
$expenses = $this->groupByTag($this->getExpenses($accounts, $tags, $currentStart, $currentEnd));
|
||||||
|
$income = $this->groupByTag($this->getIncome($accounts, $tags, $currentStart, $currentEnd));
|
||||||
|
$label = $currentStart->formatLocalized($format);
|
||||||
|
|
||||||
|
/** @var Tag $tag */
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$labelIn = $tag->id . '-in';
|
||||||
|
$labelOut = $tag->id . '-out';
|
||||||
|
$labelSumIn = $tag->id . '-total-in';
|
||||||
|
$labelSumOut = $tag->id . '-total-out';
|
||||||
|
$currentIncome = $income[$tag->id] ?? '0';
|
||||||
|
$currentExpense = $expenses[$tag->id] ?? '0';
|
||||||
|
|
||||||
|
|
||||||
|
// add to sum:
|
||||||
|
$sumOfIncome[$tag->id] = $sumOfIncome[$tag->id] ?? '0';
|
||||||
|
$sumOfExpense[$tag->id] = $sumOfExpense[$tag->id] ?? '0';
|
||||||
|
$sumOfIncome[$tag->id] = bcadd($sumOfIncome[$tag->id], $currentIncome);
|
||||||
|
$sumOfExpense[$tag->id] = bcadd($sumOfExpense[$tag->id], $currentExpense);
|
||||||
|
|
||||||
|
// add to chart:
|
||||||
|
$chartData[$labelIn]['entries'][$label] = $currentIncome;
|
||||||
|
$chartData[$labelOut]['entries'][$label] = $currentExpense;
|
||||||
|
$chartData[$labelSumIn]['entries'][$label] = $sumOfIncome[$tag->id];
|
||||||
|
$chartData[$labelSumOut]['entries'][$label] = $sumOfExpense[$tag->id];
|
||||||
|
}
|
||||||
|
$currentStart = clone $currentEnd;
|
||||||
|
$currentStart->addDay();
|
||||||
|
}
|
||||||
|
// remove all empty entries to prevent cluttering:
|
||||||
|
$newSet = [];
|
||||||
|
foreach ($chartData as $key => $entry) {
|
||||||
|
if (!array_sum($entry['entries']) == 0) {
|
||||||
|
$newSet[$key] = $chartData[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($newSet) === 0) {
|
||||||
|
$newSet = $chartData; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
$data = $this->generator->multiSet($newSet);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function tagExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var MetaPieChartInterface $helper */
|
||||||
|
$helper = app(MetaPieChartInterface::class);
|
||||||
|
$helper->setAccounts($accounts);
|
||||||
|
$helper->setTags($tags);
|
||||||
|
$helper->setStart($start);
|
||||||
|
$helper->setEnd($end);
|
||||||
|
$helper->setCollectOtherObjects(intval($others) === 1);
|
||||||
|
$chartData = $helper->generate('expense', 'tag');
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function tagIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var MetaPieChartInterface $helper */
|
||||||
|
$helper = app(MetaPieChartInterface::class);
|
||||||
|
$helper->setAccounts($accounts);
|
||||||
|
$helper->setTags($tags);
|
||||||
|
$helper->setStart($start);
|
||||||
|
$helper->setEnd($end);
|
||||||
|
$helper->setCollectOtherObjects(intval($others) === 1);
|
||||||
|
$chartData = $helper->generate('income', 'tag');
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getExpenses(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
|
->setTags($tags)->withOpposingAccount();
|
||||||
|
$collector->removeFilter(TransferFilter::class);
|
||||||
|
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(PositiveAmountFilter::class);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $tags
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getIncome(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection
|
||||||
|
{
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||||
|
->setTags($tags)->withOpposingAccount();
|
||||||
|
|
||||||
|
$collector->addFilter(OpposingAccountFilter::class);
|
||||||
|
$collector->addFilter(NegativeAmountFilter::class);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function groupByTag(Collection $set): array
|
||||||
|
{
|
||||||
|
// group by category ID:
|
||||||
|
$grouped = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($set as $transaction) {
|
||||||
|
$journal = $transaction->transactionJournal;
|
||||||
|
$journalTags = $journal->tags;
|
||||||
|
/** @var Tag $journalTag */
|
||||||
|
foreach ($journalTags as $journalTag) {
|
||||||
|
$journalTagId = $journalTag->id;
|
||||||
|
$grouped[$journalTagId] = $grouped[$journalTagId] ?? '0';
|
||||||
|
$grouped[$journalTagId] = bcadd($transaction->transaction_amount, $grouped[$journalTagId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ class Controller extends BaseController
|
|||||||
*/
|
*/
|
||||||
protected function isOpeningBalance(TransactionJournal $journal): bool
|
protected function isOpeningBalance(TransactionJournal $journal): bool
|
||||||
{
|
{
|
||||||
return TransactionJournal::transactionTypeStr($journal) === TransactionType::OPENING_BALANCE;
|
return $journal->transactionTypeStr() === TransactionType::OPENING_BALANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,9 +120,11 @@ class Controller extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
Session::flash('error', strval(trans('firefly.cannot_redirect_to_account')));
|
Session::flash('error', strval(trans('firefly.cannot_redirect_to_account')));
|
||||||
|
|
||||||
return redirect(route('index'));
|
return redirect(route('index'));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use Cache;
|
|||||||
use FireflyIII\Http\Requests\CurrencyFormRequest;
|
use FireflyIII\Http\Requests\CurrencyFormRequest;
|
||||||
use FireflyIII\Models\TransactionCurrency;
|
use FireflyIII\Models\TransactionCurrency;
|
||||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Log;
|
use Log;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
@@ -30,6 +31,11 @@ use View;
|
|||||||
class CurrencyController extends Controller
|
class CurrencyController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @var CurrencyRepositoryInterface */
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
/** @var UserRepositoryInterface */
|
||||||
|
protected $userRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -43,6 +49,8 @@ class CurrencyController extends Controller
|
|||||||
function ($request, $next) {
|
function ($request, $next) {
|
||||||
View::share('title', trans('firefly.currencies'));
|
View::share('title', trans('firefly.currencies'));
|
||||||
View::share('mainTitleIcon', 'fa-usd');
|
View::share('mainTitleIcon', 'fa-usd');
|
||||||
|
$this->repository = app(CurrencyRepositoryInterface::class);
|
||||||
|
$this->userRepository = app(UserRepositoryInterface::class);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
@@ -50,10 +58,18 @@ class CurrencyController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return View
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function create(Request $request)
|
public function create(Request $request)
|
||||||
{
|
{
|
||||||
|
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
|
||||||
|
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
||||||
|
|
||||||
|
return redirect(route('currencies.index'));
|
||||||
|
}
|
||||||
|
|
||||||
$subTitleIcon = 'fa-plus';
|
$subTitleIcon = 'fa-plus';
|
||||||
$subTitle = trans('firefly.create_currency');
|
$subTitle = trans('firefly.create_currency');
|
||||||
|
|
||||||
@@ -90,14 +106,22 @@ class CurrencyController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CurrencyRepositoryInterface $repository
|
* @param Request $request
|
||||||
* @param TransactionCurrency $currency
|
* @param TransactionCurrency $currency
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function delete(Request $request, CurrencyRepositoryInterface $repository, TransactionCurrency $currency)
|
public function delete(Request $request, TransactionCurrency $currency)
|
||||||
{
|
{
|
||||||
if (!$repository->canDeleteCurrency($currency)) {
|
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
||||||
|
|
||||||
|
return redirect(route('currencies.index'));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->repository->canDeleteCurrency($currency)) {
|
||||||
$request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
$request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
||||||
|
|
||||||
return redirect(route('currencies.index'));
|
return redirect(route('currencies.index'));
|
||||||
@@ -115,20 +139,28 @@ class CurrencyController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CurrencyRepositoryInterface $repository
|
* @param Request $request
|
||||||
* @param TransactionCurrency $currency
|
* @param TransactionCurrency $currency
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
*/
|
*/
|
||||||
public function destroy(Request $request, CurrencyRepositoryInterface $repository, TransactionCurrency $currency)
|
public function destroy(Request $request, TransactionCurrency $currency)
|
||||||
{
|
{
|
||||||
if (!$repository->canDeleteCurrency($currency)) {
|
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
||||||
|
|
||||||
|
return redirect(route('currencies.index'));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->repository->canDeleteCurrency($currency)) {
|
||||||
$request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
$request->session()->flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
||||||
|
|
||||||
return redirect(route('currencies.index'));
|
return redirect(route('currencies.index'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$repository->destroy($currency);
|
$this->repository->destroy($currency);
|
||||||
$request->session()->flash('success', trans('firefly.deleted_currency', ['name' => $currency->name]));
|
$request->session()->flash('success', trans('firefly.deleted_currency', ['name' => $currency->name]));
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('currencies.delete.uri'));
|
return redirect($this->getPreviousUri('currencies.delete.uri'));
|
||||||
@@ -138,10 +170,18 @@ class CurrencyController extends Controller
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param TransactionCurrency $currency
|
* @param TransactionCurrency $currency
|
||||||
*
|
*
|
||||||
* @return View
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function edit(Request $request, TransactionCurrency $currency)
|
public function edit(Request $request, TransactionCurrency $currency)
|
||||||
{
|
{
|
||||||
|
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
||||||
|
|
||||||
|
return redirect(route('currencies.index'));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
$subTitleIcon = 'fa-pencil';
|
$subTitleIcon = 'fa-pencil';
|
||||||
$subTitle = trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
|
$subTitle = trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
|
||||||
$currency->symbol = htmlentities($currency->symbol);
|
$currency->symbol = htmlentities($currency->symbol);
|
||||||
@@ -160,47 +200,45 @@ class CurrencyController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param CurrencyRepositoryInterface $repository
|
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function index(Request $request, CurrencyRepositoryInterface $repository)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$currencies = $repository->get();
|
$currencies = $this->repository->get();
|
||||||
$defaultCurrency = $repository->getCurrencyByPreference(Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR')));
|
$defaultCurrency = $this->repository->getCurrencyByPreference(Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR')));
|
||||||
|
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
|
||||||
|
|
||||||
if (!auth()->user()->hasRole('owner')) {
|
|
||||||
$request->session()->flash('info', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
$request->session()->flash('info', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return view('currencies.index', compact('currencies', 'defaultCurrency'));
|
return view('currencies.index', compact('currencies', 'defaultCurrency'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param CurrencyFormRequest $request
|
* @param CurrencyFormRequest $request
|
||||||
* @param CurrencyRepositoryInterface $repository
|
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
*/
|
*/
|
||||||
public function store(CurrencyFormRequest $request, CurrencyRepositoryInterface $repository)
|
public function store(CurrencyFormRequest $request)
|
||||||
{
|
{
|
||||||
if (!auth()->user()->hasRole('owner')) {
|
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
Log::error('User ' . auth()->user()->id . ' is not admin, but tried to store a currency.');
|
Log::error('User ' . auth()->user()->id . ' is not admin, but tried to store a currency.');
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('currencies.create.uri'));
|
return redirect($this->getPreviousUri('currencies.create.uri'));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $request->getCurrencyData();
|
$data = $request->getCurrencyData();
|
||||||
$currency = $repository->store($data);
|
$currency = $this->repository->store($data);
|
||||||
$request->session()->flash('success', trans('firefly.created_currency', ['name' => $currency->name]));
|
$request->session()->flash('success', trans('firefly.created_currency', ['name' => $currency->name]));
|
||||||
|
|
||||||
if (intval($request->get('create_another')) === 1) {
|
if (intval($request->get('create_another')) === 1) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
$request->session()->put('currencies.create.fromStore', true);
|
$request->session()->put('currencies.create.fromStore', true);
|
||||||
|
|
||||||
return redirect(route('currencies.create'))->withInput();
|
return redirect(route('currencies.create'))->withInput();
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('currencies.create.uri'));
|
return redirect($this->getPreviousUri('currencies.create.uri'));
|
||||||
@@ -208,25 +246,32 @@ class CurrencyController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CurrencyFormRequest $request
|
* @param CurrencyFormRequest $request
|
||||||
* @param CurrencyRepositoryInterface $repository
|
|
||||||
* @param TransactionCurrency $currency
|
* @param TransactionCurrency $currency
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
*/
|
*/
|
||||||
public function update(CurrencyFormRequest $request, CurrencyRepositoryInterface $repository, TransactionCurrency $currency)
|
public function update(CurrencyFormRequest $request, TransactionCurrency $currency)
|
||||||
{
|
{
|
||||||
$data = $request->getCurrencyData();
|
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {
|
||||||
if (auth()->user()->hasRole('owner')) {
|
// @codeCoverageIgnoreStart
|
||||||
$currency = $repository->update($currency, $data);
|
$request->session()->flash('error', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
||||||
|
|
||||||
|
return redirect(route('currencies.index'));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data = $request->getCurrencyData();
|
||||||
|
$currency = $this->repository->update($currency, $data);
|
||||||
$request->session()->flash('success', trans('firefly.updated_currency', ['name' => $currency->name]));
|
$request->session()->flash('success', trans('firefly.updated_currency', ['name' => $currency->name]));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
|
|
||||||
if (intval($request->get('return_to_edit')) === 1) {
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
$request->session()->put('currencies.edit.fromUpdate', true);
|
$request->session()->put('currencies.edit.fromUpdate', true);
|
||||||
|
|
||||||
return redirect(route('currencies.edit', [$currency->id]));
|
return redirect(route('currencies.edit', [$currency->id]));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('currencies.edit.uri'));
|
return redirect($this->getPreviousUri('currencies.edit.uri'));
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ use FireflyIII\Models\AccountType;
|
|||||||
use FireflyIII\Models\ExportJob;
|
use FireflyIII\Models\ExportJob;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface;
|
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface;
|
||||||
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface as EJRI;
|
|
||||||
use Illuminate\Http\Response as LaravelResponse;
|
use Illuminate\Http\Response as LaravelResponse;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
@@ -55,9 +54,10 @@ class ExportController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param ExportJobRepositoryInterface $repository
|
||||||
* @param ExportJob $job
|
* @param ExportJob $job
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Routing\ResponseFactory
|
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function download(ExportJobRepositoryInterface $repository, ExportJob $job)
|
public function download(ExportJobRepositoryInterface $repository, ExportJob $job)
|
||||||
@@ -103,11 +103,11 @@ class ExportController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountRepositoryInterface $repository
|
* @param AccountRepositoryInterface $repository
|
||||||
* @param EJRI $jobs
|
* @param ExportJobRepositoryInterface $jobs
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function index(AccountRepositoryInterface $repository, EJRI $jobs)
|
public function index(AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs)
|
||||||
{
|
{
|
||||||
// create new export job.
|
// create new export job.
|
||||||
$job = $jobs->create();
|
$job = $jobs->create();
|
||||||
@@ -130,11 +130,11 @@ class ExportController extends Controller
|
|||||||
/**
|
/**
|
||||||
* @param ExportFormRequest $request
|
* @param ExportFormRequest $request
|
||||||
* @param AccountRepositoryInterface $repository
|
* @param AccountRepositoryInterface $repository
|
||||||
* @param EJRI $jobs
|
* @param ExportJobRepositoryInterface $jobs
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, EJRI $jobs)
|
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs)
|
||||||
{
|
{
|
||||||
$job = $jobs->findByKey($request->get('job'));
|
$job = $jobs->findByKey($request->get('job'));
|
||||||
$settings = [
|
$settings = [
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Artisan;
|
use Artisan;
|
||||||
@@ -17,7 +18,7 @@ use Carbon\Carbon;
|
|||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -97,11 +98,11 @@ class HomeController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ARI $repository
|
* @param AccountRepositoryInterface $repository
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function index(ARI $repository)
|
public function index(AccountRepositoryInterface $repository)
|
||||||
{
|
{
|
||||||
$types = config('firefly.accountTypesByIdentifier.asset');
|
$types = config('firefly.accountTypesByIdentifier.asset');
|
||||||
$count = $repository->count($types);
|
$count = $repository->count($types);
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Crypt;
|
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Http\Requests\ImportUploadRequest;
|
use FireflyIII\Http\Requests\ImportUploadRequest;
|
||||||
use FireflyIII\Import\ImportProcedureInterface;
|
use FireflyIII\Import\Configurator\ConfiguratorInterface;
|
||||||
use FireflyIII\Import\Setup\SetupInterface;
|
use FireflyIII\Import\Routine\ImportRoutine;
|
||||||
use FireflyIII\Models\ImportJob;
|
use FireflyIII\Models\ImportJob;
|
||||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||||
@@ -24,19 +23,18 @@ use Illuminate\Http\Request;
|
|||||||
use Illuminate\Http\Response as LaravelResponse;
|
use Illuminate\Http\Response as LaravelResponse;
|
||||||
use Log;
|
use Log;
|
||||||
use Response;
|
use Response;
|
||||||
use Session;
|
|
||||||
use SplFileObject;
|
|
||||||
use Storage;
|
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
||||||
use View;
|
use View;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ImportController
|
* Class ImportController.
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Http\Controllers
|
* @package FireflyIII\Http\Controllers
|
||||||
*/
|
*/
|
||||||
class ImportController extends Controller
|
class ImportController extends Controller
|
||||||
{
|
{
|
||||||
|
/** @var ImportJobRepositoryInterface */
|
||||||
|
public $repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -47,7 +45,8 @@ class ImportController extends Controller
|
|||||||
$this->middleware(
|
$this->middleware(
|
||||||
function ($request, $next) {
|
function ($request, $next) {
|
||||||
View::share('mainTitleIcon', 'fa-archive');
|
View::share('mainTitleIcon', 'fa-archive');
|
||||||
View::share('title', trans('firefly.import_data_full'));
|
View::share('title', trans('firefly.import_index_title'));
|
||||||
|
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
@@ -55,28 +54,7 @@ class ImportController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the last step before the import starts.
|
* This is step 3. This repeats until the job is configured.
|
||||||
*
|
|
||||||
* @param ImportJob $job
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
|
||||||
*/
|
|
||||||
public function complete(ImportJob $job)
|
|
||||||
{
|
|
||||||
Log::debug('Now in complete()', ['job' => $job->key]);
|
|
||||||
if (!$this->jobInCorrectStep($job, 'complete')) {
|
|
||||||
return $this->redirectToCorrectStep($job);
|
|
||||||
}
|
|
||||||
$subTitle = trans('firefly.import_complete');
|
|
||||||
$subTitleIcon = 'fa-star';
|
|
||||||
|
|
||||||
return view('import.complete', compact('job', 'subTitle', 'subTitleIcon'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is step 3.
|
|
||||||
* This is the first step in configuring the job. It can only be executed
|
|
||||||
* when the job is set to "import_status_never_started".
|
|
||||||
*
|
*
|
||||||
* @param ImportJob $job
|
* @param ImportJob $job
|
||||||
*
|
*
|
||||||
@@ -85,39 +63,41 @@ class ImportController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function configure(ImportJob $job)
|
public function configure(ImportJob $job)
|
||||||
{
|
{
|
||||||
Log::debug('Now at start of configure()');
|
// create configuration class:
|
||||||
if (!$this->jobInCorrectStep($job, 'configure')) {
|
$configurator = $this->makeConfigurator($job);
|
||||||
Log::debug('Job is not in correct state for configure()', ['status' => $job->status]);
|
|
||||||
|
|
||||||
return $this->redirectToCorrectStep($job);
|
// is the job already configured?
|
||||||
|
if ($configurator->isJobConfigured()) {
|
||||||
|
$this->repository->updateStatus($job, 'configured');
|
||||||
|
|
||||||
|
return redirect(route('import.status', [$job->key]));
|
||||||
}
|
}
|
||||||
|
$view = $configurator->getNextView();
|
||||||
// actual code
|
$data = $configurator->getNextData();
|
||||||
$importer = $this->makeImporter($job);
|
$subTitle = trans('firefly.import_config_bread_crumb');
|
||||||
$importer->configure();
|
|
||||||
$data = $importer->getConfigurationData();
|
|
||||||
$subTitle = trans('firefly.configure_import');
|
|
||||||
$subTitleIcon = 'fa-wrench';
|
$subTitleIcon = 'fa-wrench';
|
||||||
|
|
||||||
return view('import.' . $job->file_type . '.configure', compact('data', 'job', 'subTitle', 'subTitleIcon'));
|
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a JSON file of the job's config and send it to the user.
|
* Generate a JSON file of the job's configuration and send it to the user.
|
||||||
*
|
*
|
||||||
* @param ImportJob $job
|
* @param ImportJob $job
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return LaravelResponse
|
||||||
*/
|
*/
|
||||||
public function download(ImportJob $job)
|
public function download(ImportJob $job)
|
||||||
{
|
{
|
||||||
Log::debug('Now in download()', ['job' => $job->key]);
|
Log::debug('Now in download()', ['job' => $job->key]);
|
||||||
$config = $job->configuration;
|
$config = $job->configuration;
|
||||||
|
|
||||||
|
// TODO this is CSV import specific:
|
||||||
$config['column-roles-complete'] = false;
|
$config['column-roles-complete'] = false;
|
||||||
$config['column-mapping-complete'] = false;
|
$config['column-mapping-complete'] = false;
|
||||||
|
$config['initial-config-complete'] = false;
|
||||||
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
|
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
|
||||||
|
|
||||||
$result = json_encode($config, JSON_PRETTY_PRINT);
|
$result = json_encode($config, JSON_PRETTY_PRINT);
|
||||||
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
|
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
|
||||||
|
|
||||||
@@ -137,28 +117,6 @@ class ImportController extends Controller
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ImportJob $job
|
|
||||||
*
|
|
||||||
* @return View
|
|
||||||
*/
|
|
||||||
public function finished(ImportJob $job)
|
|
||||||
{
|
|
||||||
if (!$this->jobInCorrectStep($job, 'finished')) {
|
|
||||||
Log::debug('Job is not in correct state for finished()', ['status' => $job->status]);
|
|
||||||
|
|
||||||
return $this->redirectToCorrectStep($job);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is a tag (there might not be), we can link to it:
|
|
||||||
$tagId = $job->extended_status['importTag'] ?? 0;
|
|
||||||
|
|
||||||
$subTitle = trans('firefly.import_finished');
|
|
||||||
$subTitleIcon = 'fa-star';
|
|
||||||
|
|
||||||
return view('import.finished', compact('job', 'subTitle', 'subTitleIcon', 'tagId'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is step 1. Upload a file.
|
* This is step 1. Upload a file.
|
||||||
*
|
*
|
||||||
@@ -166,8 +124,7 @@ class ImportController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
Log::debug('Now at index');
|
$subTitle = trans('firefly.import_index_sub_title');
|
||||||
$subTitle = trans('firefly.import_data_index');
|
|
||||||
$subTitleIcon = 'fa-home';
|
$subTitleIcon = 'fa-home';
|
||||||
$importFileTypes = [];
|
$importFileTypes = [];
|
||||||
$defaultImportType = config('firefly.default_import_format');
|
$defaultImportType = config('firefly.default_import_format');
|
||||||
@@ -180,6 +137,38 @@ class ImportController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This is step 2. It creates an Import Job. Stores the import.
|
||||||
|
*
|
||||||
|
* @param ImportUploadRequest $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function initialize(ImportUploadRequest $request)
|
||||||
|
{
|
||||||
|
Log::debug('Now in initialize()');
|
||||||
|
|
||||||
|
// create import job:
|
||||||
|
$type = $request->get('import_file_type');
|
||||||
|
$job = $this->repository->create($type);
|
||||||
|
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
|
||||||
|
|
||||||
|
// process file:
|
||||||
|
$this->repository->processFile($job, $request->files->get('import_file'));
|
||||||
|
|
||||||
|
// process config, if present:
|
||||||
|
if ($request->files->has('configuration_file')) {
|
||||||
|
$this->repository->processConfiguration($job, $request->files->get('configuration_file'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->repository->updateStatus($job, 'initialized');
|
||||||
|
|
||||||
|
return redirect(route('import.configure', [$job->key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Show status of import job in JSON.
|
||||||
|
*
|
||||||
* @param ImportJob $job
|
* @param ImportJob $job
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
@@ -187,35 +176,36 @@ class ImportController extends Controller
|
|||||||
public function json(ImportJob $job)
|
public function json(ImportJob $job)
|
||||||
{
|
{
|
||||||
$result = [
|
$result = [
|
||||||
'showPercentage' => false,
|
|
||||||
'started' => false,
|
'started' => false,
|
||||||
'finished' => false,
|
'finished' => false,
|
||||||
'running' => false,
|
'running' => false,
|
||||||
'errors' => $job->extended_status['errors'],
|
'errors' => array_values($job->extended_status['errors']),
|
||||||
'percentage' => 0,
|
'percentage' => 0,
|
||||||
'steps' => $job->extended_status['total_steps'],
|
'show_percentage' => false,
|
||||||
'stepsDone' => $job->extended_status['steps_done'],
|
'steps' => $job->extended_status['steps'],
|
||||||
'statusText' => trans('firefly.import_status_' . $job->status),
|
'done' => $job->extended_status['done'],
|
||||||
|
'statusText' => trans('firefly.import_status_job_' . $job->status),
|
||||||
|
'status' => $job->status,
|
||||||
'finishedText' => '',
|
'finishedText' => '',
|
||||||
];
|
];
|
||||||
$percentage = 0;
|
|
||||||
if ($job->extended_status['total_steps'] !== 0) {
|
if ($job->extended_status['steps'] !== 0) {
|
||||||
$percentage = round(($job->extended_status['steps_done'] / $job->extended_status['total_steps']) * 100, 0);
|
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
|
||||||
|
$result['show_percentage'] = true;
|
||||||
}
|
}
|
||||||
if ($job->status === 'import_complete') {
|
|
||||||
$tagId = $job->extended_status['importTag'];
|
if ($job->status === 'finished') {
|
||||||
|
$tagId = $job->extended_status['tag'];
|
||||||
/** @var TagRepositoryInterface $repository */
|
/** @var TagRepositoryInterface $repository */
|
||||||
$repository = app(TagRepositoryInterface::class);
|
$repository = app(TagRepositoryInterface::class);
|
||||||
$tag = $repository->find($tagId);
|
$tag = $repository->find($tagId);
|
||||||
$result['finished'] = true;
|
$result['finished'] = true;
|
||||||
$result['finishedText'] = trans('firefly.import_finished_link', ['link' => route('tags.show', [$tag->id]), 'tag' => $tag->tag]);
|
$result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($job->status === 'import_running') {
|
if ($job->status === 'running') {
|
||||||
$result['started'] = true;
|
$result['started'] = true;
|
||||||
$result['running'] = true;
|
$result['running'] = true;
|
||||||
$result['showPercentage'] = true;
|
|
||||||
$result['percentage'] = $percentage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response::json($result);
|
return Response::json($result);
|
||||||
@@ -228,286 +218,78 @@ class ImportController extends Controller
|
|||||||
* @param ImportJob $job
|
* @param ImportJob $job
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
* @throws FireflyException
|
|
||||||
*/
|
*/
|
||||||
public function postConfigure(Request $request, ImportJob $job)
|
public function postConfigure(Request $request, ImportJob $job)
|
||||||
{
|
{
|
||||||
Log::debug('Now in postConfigure()', ['job' => $job->key]);
|
Log::debug('Now in postConfigure()', ['job' => $job->key]);
|
||||||
if (!$this->jobInCorrectStep($job, 'process')) {
|
$configurator = $this->makeConfigurator($job);
|
||||||
return $this->redirectToCorrectStep($job);
|
|
||||||
}
|
|
||||||
Log::debug('Continue postConfigure()', ['job' => $job->key]);
|
|
||||||
|
|
||||||
// actual code
|
// is the job already configured?
|
||||||
$importer = $this->makeImporter($job);
|
if ($configurator->isJobConfigured()) {
|
||||||
|
return redirect(route('import.status', [$job->key]));
|
||||||
|
}
|
||||||
$data = $request->all();
|
$data = $request->all();
|
||||||
$files = $request->files;
|
$configurator->configureJob($data);
|
||||||
$importer->saveImportConfiguration($data, $files);
|
|
||||||
|
|
||||||
// update job:
|
// return to configure
|
||||||
$job->status = 'import_configuration_saved';
|
return redirect(route('import.configure', [$job->key]));
|
||||||
$job->save();
|
|
||||||
|
|
||||||
// return redirect to settings.
|
|
||||||
// this could loop until the user is done.
|
|
||||||
return redirect(route('import.settings', [$job->key]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This step 6. Depending on the importer, this will process the
|
|
||||||
* settings given and store them.
|
|
||||||
*
|
|
||||||
* @param Request $request
|
|
||||||
* @param ImportJob $job
|
* @param ImportJob $job
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
* @return \Illuminate\Http\JsonResponse
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function postSettings(Request $request, ImportJob $job)
|
public function start(ImportJob $job)
|
||||||
{
|
{
|
||||||
Log::debug('Now in postSettings()', ['job' => $job->key]);
|
/** @var ImportRoutine $routine */
|
||||||
if (!$this->jobInCorrectStep($job, 'store-settings')) {
|
$routine = app(ImportRoutine::class);
|
||||||
return $this->redirectToCorrectStep($job);
|
$routine->setJob($job);
|
||||||
|
$result = $routine->run();
|
||||||
|
if ($result) {
|
||||||
|
return Response::json(['run' => 'ok']);
|
||||||
}
|
}
|
||||||
$importer = $this->makeImporter($job);
|
|
||||||
$importer->storeSettings($request);
|
|
||||||
|
|
||||||
// return redirect to settings (for more settings perhaps)
|
throw new FireflyException('Job did not complete succesfully.');
|
||||||
return redirect(route('import.settings', [$job->key]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Step 5. Depending on the importer, this will show the user settings to
|
|
||||||
* fill in.
|
|
||||||
*
|
|
||||||
* @param ImportJob $job
|
|
||||||
*
|
|
||||||
* @return View
|
|
||||||
* @throws FireflyException
|
|
||||||
*/
|
|
||||||
public function settings(ImportJob $job)
|
|
||||||
{
|
|
||||||
Log::debug('Now in settings()', ['job' => $job->key]);
|
|
||||||
if (!$this->jobInCorrectStep($job, 'settings')) {
|
|
||||||
Log::debug('Job should not be in settings()');
|
|
||||||
|
|
||||||
return $this->redirectToCorrectStep($job);
|
|
||||||
}
|
|
||||||
Log::debug('Continue in settings()');
|
|
||||||
$importer = $this->makeImporter($job);
|
|
||||||
$subTitle = trans('firefly.settings_for_import');
|
|
||||||
$subTitleIcon = 'fa-wrench';
|
|
||||||
|
|
||||||
// now show settings screen to user.
|
|
||||||
if ($importer->requireUserSettings()) {
|
|
||||||
Log::debug('Job requires user config.');
|
|
||||||
$data = $importer->getDataForSettings();
|
|
||||||
$view = $importer->getViewForSettings();
|
|
||||||
|
|
||||||
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
|
|
||||||
}
|
|
||||||
Log::debug('Job does NOT require user config.');
|
|
||||||
|
|
||||||
$job->status = 'settings_complete';
|
|
||||||
$job->save();
|
|
||||||
|
|
||||||
// if no more settings, save job and continue to process thing.
|
|
||||||
return redirect(route('import.complete', [$job->key]));
|
|
||||||
|
|
||||||
// ask the importer for the requested action.
|
|
||||||
// for example pick columns or map data.
|
|
||||||
// depends of course on the data in the job.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ImportProcedureInterface $importProcedure
|
|
||||||
* @param ImportJob $job
|
|
||||||
*/
|
|
||||||
public function start(ImportProcedureInterface $importProcedure, ImportJob $job)
|
|
||||||
{
|
|
||||||
set_time_limit(0);
|
|
||||||
if ($job->status == 'settings_complete') {
|
|
||||||
$importProcedure->runImport($job);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the last step before the import starts.
|
|
||||||
*
|
|
||||||
* @param ImportJob $job
|
* @param ImportJob $job
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function status(ImportJob $job)
|
public function status(ImportJob $job)
|
||||||
{ //
|
{
|
||||||
Log::debug('Now in status()', ['job' => $job->key]);
|
$statuses = ['configured', 'running', 'finished'];
|
||||||
if (!$this->jobInCorrectStep($job, 'status')) {
|
if (!in_array($job->status, $statuses)) {
|
||||||
return $this->redirectToCorrectStep($job);
|
return redirect(route('import.configure', [$job->key]));
|
||||||
}
|
}
|
||||||
$subTitle = trans('firefly.import_status');
|
$subTitle = trans('firefly.import_status_sub_title');
|
||||||
$subTitleIcon = 'fa-star';
|
$subTitleIcon = 'fa-star';
|
||||||
|
|
||||||
return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
|
return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is step 2. It creates an Import Job. Stores the import.
|
|
||||||
*
|
|
||||||
* @param ImportUploadRequest $request
|
|
||||||
* @param ImportJobRepositoryInterface $repository
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
|
||||||
*/
|
|
||||||
public function upload(ImportUploadRequest $request, ImportJobRepositoryInterface $repository)
|
|
||||||
{
|
|
||||||
Log::debug('Now in upload()');
|
|
||||||
// create import job:
|
|
||||||
$type = $request->get('import_file_type');
|
|
||||||
$job = $repository->create($type);
|
|
||||||
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
|
|
||||||
|
|
||||||
/** @var UploadedFile $upload */
|
|
||||||
$upload = $request->files->get('import_file');
|
|
||||||
$newName = $job->key . '.upload';
|
|
||||||
$uploaded = new SplFileObject($upload->getRealPath());
|
|
||||||
$content = $uploaded->fread($uploaded->getSize());
|
|
||||||
$contentEncrypted = Crypt::encrypt($content);
|
|
||||||
$disk = Storage::disk('upload');
|
|
||||||
|
|
||||||
// user is demo user, replace upload with prepared file.
|
|
||||||
if (auth()->user()->hasRole('demo')) {
|
|
||||||
$stubsDisk = Storage::disk('stubs');
|
|
||||||
$content = $stubsDisk->get('demo-import.csv');
|
|
||||||
$contentEncrypted = Crypt::encrypt($content);
|
|
||||||
$disk->put($newName, $contentEncrypted);
|
|
||||||
Log::debug('Replaced upload with demo file.');
|
|
||||||
|
|
||||||
// also set up prepared configuration.
|
|
||||||
$configuration = json_decode($stubsDisk->get('demo-configuration.json'), true);
|
|
||||||
$job->configuration = $configuration;
|
|
||||||
$job->save();
|
|
||||||
Log::debug('Set configuration for demo user', $configuration);
|
|
||||||
|
|
||||||
// also flash info
|
|
||||||
Session::flash('info', trans('demo.import-configure-security'));
|
|
||||||
}
|
|
||||||
if (!auth()->user()->hasRole('demo')) {
|
|
||||||
// user is not demo, process original upload:
|
|
||||||
$disk->put($newName, $contentEncrypted);
|
|
||||||
Log::debug('Uploaded file', ['name' => $upload->getClientOriginalName(), 'size' => $upload->getSize(), 'mime' => $upload->getClientMimeType()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// store configuration file's content into the job's configuration thing. Otherwise, leave it empty.
|
|
||||||
// demo user's configuration upload is ignored completely.
|
|
||||||
if ($request->files->has('configuration_file') && !auth()->user()->hasRole('demo')) {
|
|
||||||
/** @var UploadedFile $configFile */
|
|
||||||
$configFile = $request->files->get('configuration_file');
|
|
||||||
Log::debug(
|
|
||||||
'Uploaded configuration file',
|
|
||||||
['name' => $configFile->getClientOriginalName(), 'size' => $configFile->getSize(), 'mime' => $configFile->getClientMimeType()]
|
|
||||||
);
|
|
||||||
|
|
||||||
$configFileObject = new SplFileObject($configFile->getRealPath());
|
|
||||||
$configRaw = $configFileObject->fread($configFileObject->getSize());
|
|
||||||
$configuration = json_decode($configRaw, true);
|
|
||||||
|
|
||||||
if (!is_null($configuration) && is_array($configuration)) {
|
|
||||||
Log::debug('Found configuration', $configuration);
|
|
||||||
$job->configuration = $configuration;
|
|
||||||
$job->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if user is demo user, replace config with prepared config:
|
|
||||||
|
|
||||||
|
|
||||||
return redirect(route('import.configure', [$job->key]));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ImportJob $job
|
|
||||||
* @param string $method
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function jobInCorrectStep(ImportJob $job, string $method): bool
|
|
||||||
{
|
|
||||||
Log::debug('Now in jobInCorrectStep()', ['job' => $job->key, 'method' => $method]);
|
|
||||||
switch ($method) {
|
|
||||||
case 'configure':
|
|
||||||
case 'process':
|
|
||||||
return $job->status === 'import_status_never_started';
|
|
||||||
case 'settings':
|
|
||||||
case 'store-settings':
|
|
||||||
return $job->status === 'import_configuration_saved';
|
|
||||||
case 'finished':
|
|
||||||
return $job->status === 'import_complete';
|
|
||||||
case 'complete':
|
|
||||||
return $job->status === 'settings_complete';
|
|
||||||
case 'status':
|
|
||||||
return ($job->status === 'settings_complete') || ($job->status === 'import_running');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ImportJob $job
|
* @param ImportJob $job
|
||||||
*
|
*
|
||||||
* @return SetupInterface
|
* @return ConfiguratorInterface
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
private function makeImporter(ImportJob $job): SetupInterface
|
private function makeConfigurator(ImportJob $job): ConfiguratorInterface
|
||||||
{
|
{
|
||||||
// create proper importer (depends on job)
|
$type = $job->file_type;
|
||||||
$type = strtolower($job->file_type);
|
$key = sprintf('firefly.import_configurators.%s', $type);
|
||||||
|
$className = config($key);
|
||||||
// validate type:
|
if (is_null($className)) {
|
||||||
$validTypes = array_keys(config('firefly.import_formats'));
|
throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
|
||||||
|
|
||||||
|
|
||||||
if (in_array($type, $validTypes)) {
|
|
||||||
/** @var SetupInterface $importer */
|
|
||||||
$importer = app('FireflyIII\Import\Setup\\' . ucfirst($type) . 'Setup');
|
|
||||||
$importer->setJob($job);
|
|
||||||
|
|
||||||
return $importer;
|
|
||||||
}
|
}
|
||||||
throw new FireflyException(sprintf('"%s" is not a valid file type', $type));
|
/** @var ConfiguratorInterface $configurator */
|
||||||
|
$configurator = app($className);
|
||||||
|
$configurator->setJob($job);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ImportJob $job
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
|
||||||
* @throws FireflyException
|
|
||||||
*/
|
|
||||||
private function redirectToCorrectStep(ImportJob $job)
|
|
||||||
{
|
|
||||||
Log::debug('Now in redirectToCorrectStep()', ['job' => $job->key]);
|
|
||||||
switch ($job->status) {
|
|
||||||
case 'import_status_never_started':
|
|
||||||
Log::debug('Will redirect to configure()');
|
|
||||||
|
|
||||||
return redirect(route('import.configure', [$job->key]));
|
|
||||||
case 'import_configuration_saved':
|
|
||||||
Log::debug('Will redirect to settings()');
|
|
||||||
|
|
||||||
return redirect(route('import.settings', [$job->key]));
|
|
||||||
case 'settings_complete':
|
|
||||||
Log::debug('Will redirect to complete()');
|
|
||||||
|
|
||||||
return redirect(route('import.complete', [$job->key]));
|
|
||||||
case 'import_complete':
|
|
||||||
Log::debug('Will redirect to finished()');
|
|
||||||
|
|
||||||
return redirect(route('import.finished', [$job->key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new FireflyException('Cannot redirect for job state ' . $job->status);
|
|
||||||
|
|
||||||
|
return $configurator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ namespace FireflyIII\Http\Controllers;
|
|||||||
|
|
||||||
use Amount;
|
use Amount;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Models\Account;
|
||||||
|
use FireflyIII\Models\AccountType;
|
||||||
|
use FireflyIII\Models\TransactionCurrency;
|
||||||
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
@@ -25,9 +30,61 @@ use Session;
|
|||||||
*/
|
*/
|
||||||
class JavascriptController extends Controller
|
class JavascriptController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param AccountRepositoryInterface $repository
|
||||||
|
* @param CurrencyRepositoryInterface $currencyRepository
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository)
|
||||||
|
{
|
||||||
|
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||||
|
$preference = Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'));
|
||||||
|
$default = $currencyRepository->findByCode($preference->data);
|
||||||
|
|
||||||
|
$data = ['accounts' => [],];
|
||||||
|
|
||||||
|
|
||||||
|
/** @var Account $account */
|
||||||
|
foreach ($accounts as $account) {
|
||||||
|
$accountId = $account->id;
|
||||||
|
$currency = intval($account->getMeta('currency_id'));
|
||||||
|
$currency = $currency === 0 ? $default->id : $currency;
|
||||||
|
$entry = ['preferredCurrency' => $currency, 'name' => $account->name];
|
||||||
|
$data['accounts'][$accountId] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->view('javascript.accounts', $data, 200)
|
||||||
|
->header('Content-Type', 'text/javascript');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param CurrencyRepositoryInterface $repository
|
||||||
*
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function currencies(CurrencyRepositoryInterface $repository)
|
||||||
|
{
|
||||||
|
$currencies = $repository->get();
|
||||||
|
$data = ['currencies' => [],];
|
||||||
|
/** @var TransactionCurrency $currency */
|
||||||
|
foreach ($currencies as $currency) {
|
||||||
|
$currencyId = $currency->id;
|
||||||
|
$entry = ['name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol];
|
||||||
|
$data['currencies'][$currencyId] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->view('javascript.currencies', $data, 200)
|
||||||
|
->header('Content-Type', 'text/javascript');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function variables(Request $request)
|
public function variables(Request $request)
|
||||||
{
|
{
|
||||||
@@ -95,7 +152,7 @@ class JavascriptController extends Controller
|
|||||||
|
|
||||||
switch ($viewRange) {
|
switch ($viewRange) {
|
||||||
default:
|
default:
|
||||||
throw new FireflyException('The date picker does not yet support "' . $viewRange . '".');
|
throw new FireflyException('The date picker does not yet support "' . $viewRange . '".'); // @codeCoverageIgnore
|
||||||
case '1D':
|
case '1D':
|
||||||
case 'custom':
|
case 'custom':
|
||||||
$format = (string)trans('config.month_and_day');
|
$format = (string)trans('config.month_and_day');
|
||||||
|
|||||||
65
app/Http/Controllers/Json/ExchangeController.php
Normal file
65
app/Http/Controllers/Json/ExchangeController.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ExchangeController.php
|
||||||
|
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||||
|
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Controllers\Json;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\TransactionCurrency;
|
||||||
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
|
use FireflyIII\Services\Currency\ExchangeRateInterface;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Log;
|
||||||
|
use Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ExchangeController
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Http\Controllers\Json
|
||||||
|
*/
|
||||||
|
class ExchangeController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
* @param TransactionCurrency $fromCurrency
|
||||||
|
* @param TransactionCurrency $toCurrency
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function getRate(Request $request, TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date)
|
||||||
|
{
|
||||||
|
/** @var CurrencyRepositoryInterface $repository */
|
||||||
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
|
$rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date);
|
||||||
|
if (is_null($rate->id)) {
|
||||||
|
Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
|
||||||
|
$preferred = env('EXCHANGE_RATE_SERVICE', config('firefly.preferred_exchange_service'));
|
||||||
|
$class = config('firefly.currency_exchange_services.' . $preferred);
|
||||||
|
/** @var ExchangeRateInterface $object */
|
||||||
|
$object = app($class);
|
||||||
|
$object->setUser(auth()->user());
|
||||||
|
$rate = $object->getRate($fromCurrency, $toCurrency, $date);
|
||||||
|
}
|
||||||
|
$return = $rate->toArray();
|
||||||
|
$return['amount'] = null;
|
||||||
|
if (!is_null($request->get('amount'))) {
|
||||||
|
// assume amount is in "from" currency:
|
||||||
|
$return['amount'] = bcmul($request->get('amount'), strval($rate->rate), 12);
|
||||||
|
// round to toCurrency decimal places:
|
||||||
|
$return['amount'] = round($return['amount'], $toCurrency->decimal_places);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::json($return);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Amount;
|
use Amount;
|
||||||
@@ -17,8 +18,8 @@ use Carbon\Carbon;
|
|||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
|
||||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
@@ -116,8 +117,9 @@ class JsonController extends Controller
|
|||||||
*/
|
*/
|
||||||
$amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
|
$amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
|
||||||
$amount = bcmul($amount, '-1');
|
$amount = bcmul($amount, '-1');
|
||||||
|
$currency = Amount::getDefaultCurrency();
|
||||||
|
|
||||||
$data = ['box' => 'bills-paid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
|
$data = ['box' => 'bills-paid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
@@ -132,19 +134,19 @@ class JsonController extends Controller
|
|||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$start = session('start', Carbon::now()->startOfMonth());
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
$amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
|
$amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
|
||||||
$data = ['box' => 'bills-unpaid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
|
$currency = Amount::getDefaultCurrency();
|
||||||
|
$data = ['box' => 'bills-unpaid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $accountTasker
|
|
||||||
* @param AccountRepositoryInterface $repository
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
* @internal param AccountTaskerInterface $accountTasker
|
||||||
|
* @internal param AccountRepositoryInterface $repository
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function boxIn(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository)
|
public function boxIn()
|
||||||
{
|
{
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$start = session('start', Carbon::now()->startOfMonth());
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
@@ -155,24 +157,31 @@ class JsonController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('box-in');
|
$cache->addProperty('box-in');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
|
||||||
$assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
// try a collector for income:
|
||||||
$amount = $accountTasker->amountInInPeriod($accounts, $assets, $start, $end);
|
/** @var JournalCollectorInterface $collector */
|
||||||
$data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||||
|
->setTypes([TransactionType::DEPOSIT])
|
||||||
|
->withOpposingAccount();
|
||||||
|
|
||||||
|
$amount = strval($collector->getJournals()->sum('transaction_amount'));
|
||||||
|
$currency = Amount::getDefaultCurrency();
|
||||||
|
$data = ['box' => 'in', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $accountTasker
|
|
||||||
* @param AccountRepositoryInterface $repository
|
|
||||||
*
|
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
|
* @internal param AccountTaskerInterface $accountTasker
|
||||||
|
* @internal param AccountRepositoryInterface $repository
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public function boxOut(AccountTaskerInterface $accountTasker, AccountRepositoryInterface $repository)
|
public function boxOut()
|
||||||
{
|
{
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$start = session('start', Carbon::now()->startOfMonth());
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
@@ -183,14 +192,18 @@ class JsonController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('box-out');
|
$cache->addProperty('box-out');
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
// try a collector for expenses:
|
||||||
$assets = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
/** @var JournalCollectorInterface $collector */
|
||||||
$amount = $accountTasker->amountOutInPeriod($accounts, $assets, $start, $end);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||||
$data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount];
|
->setTypes([TransactionType::WITHDRAWAL])
|
||||||
|
->withOpposingAccount();
|
||||||
|
$amount = strval($collector->getJournals()->sum('transaction_amount'));
|
||||||
|
$currency = Amount::getDefaultCurrency();
|
||||||
|
$data = ['box' => 'out', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -287,7 +300,7 @@ class JsonController extends Controller
|
|||||||
{
|
{
|
||||||
$pref = Preferences::get('tour', true);
|
$pref = Preferences::get('tour', true);
|
||||||
if (!$pref) {
|
if (!$pref) {
|
||||||
throw new FireflyException('Cannot find preference for tour. Exit.');
|
throw new FireflyException('Cannot find preference for tour. Exit.'); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$headers = ['main-content', 'sidebar-toggle', 'account-menu', 'budget-menu', 'report-menu', 'transaction-menu', 'option-menu', 'main-content-end'];
|
$headers = ['main-content', 'sidebar-toggle', 'account-menu', 'budget-menu', 'report-menu', 'transaction-menu', 'option-menu', 'main-content-end'];
|
||||||
$steps = [];
|
$steps = [];
|
||||||
@@ -329,7 +342,9 @@ class JsonController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param JournalRepositoryInterface $repository
|
||||||
*
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function transactionTypes(JournalRepositoryInterface $repository)
|
public function transactionTypes(JournalRepositoryInterface $repository)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Amount;
|
use Amount;
|
||||||
@@ -209,10 +210,11 @@ class PiggyBankController extends Controller
|
|||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
|
|
||||||
$accounts = [];
|
$accounts = [];
|
||||||
|
Log::debug('Looping piggues');
|
||||||
/** @var PiggyBank $piggyBank */
|
/** @var PiggyBank $piggyBank */
|
||||||
foreach ($piggyBanks as $piggyBank) {
|
foreach ($piggyBanks as $piggyBank) {
|
||||||
$piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount;
|
$piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0';
|
||||||
$piggyBank->percentage = $piggyBank->savedSoFar != 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
|
$piggyBank->percentage = bccomp('0', $piggyBank->savedSoFar) !== 0 ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
|
||||||
$piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar));
|
$piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar));
|
||||||
$piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage;
|
$piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage;
|
||||||
|
|
||||||
@@ -220,7 +222,9 @@ class PiggyBankController extends Controller
|
|||||||
* Fill account information:
|
* Fill account information:
|
||||||
*/
|
*/
|
||||||
$account = $piggyBank->account;
|
$account = $piggyBank->account;
|
||||||
|
$new = false;
|
||||||
if (!isset($accounts[$account->id])) {
|
if (!isset($accounts[$account->id])) {
|
||||||
|
$new = true;
|
||||||
$accounts[$account->id] = [
|
$accounts[$account->id] = [
|
||||||
'name' => $account->name,
|
'name' => $account->name,
|
||||||
'balance' => Steam::balanceIgnoreVirtual($account, $end),
|
'balance' => Steam::balanceIgnoreVirtual($account, $end),
|
||||||
@@ -229,7 +233,8 @@ class PiggyBankController extends Controller
|
|||||||
'sumOfTargets' => $piggyBank->targetamount,
|
'sumOfTargets' => $piggyBank->targetamount,
|
||||||
'leftToSave' => $piggyBank->leftToSave,
|
'leftToSave' => $piggyBank->leftToSave,
|
||||||
];
|
];
|
||||||
} else {
|
}
|
||||||
|
if (isset($accounts[$account->id]) && $new === false) {
|
||||||
$accounts[$account->id]['sumOfSaved'] = bcadd($accounts[$account->id]['sumOfSaved'], strval($piggyBank->savedSoFar));
|
$accounts[$account->id]['sumOfSaved'] = bcadd($accounts[$account->id]['sumOfSaved'], strval($piggyBank->savedSoFar));
|
||||||
$accounts[$account->id]['sumOfTargets'] = bcadd($accounts[$account->id]['sumOfTargets'], $piggyBank->targetamount);
|
$accounts[$account->id]['sumOfTargets'] = bcadd($accounts[$account->id]['sumOfTargets'], $piggyBank->targetamount);
|
||||||
$accounts[$account->id]['leftToSave'] = bcadd($accounts[$account->id]['leftToSave'], $piggyBank->leftToSave);
|
$accounts[$account->id]['leftToSave'] = bcadd($accounts[$account->id]['leftToSave'], $piggyBank->leftToSave);
|
||||||
@@ -272,33 +277,28 @@ class PiggyBankController extends Controller
|
|||||||
public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
|
public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
|
||||||
{
|
{
|
||||||
$amount = $request->get('amount');
|
$amount = $request->get('amount');
|
||||||
Log::debug(sprintf('Found amount is %s', $amount));
|
$currency = Amount::getDefaultCurrency();
|
||||||
/** @var Carbon $date */
|
if ($repository->canAddAmount($piggyBank, $amount)) {
|
||||||
$date = session('end', Carbon::now()->endOfMonth());
|
$repository->addAmount($piggyBank, $amount);
|
||||||
$leftOnAccount = $piggyBank->leftOnAccount($date);
|
|
||||||
$savedSoFar = strval($piggyBank->currentRelevantRep()->currentamount);
|
|
||||||
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
|
|
||||||
$maxAmount = strval(min(round($leftOnAccount, 12), round($leftToSave, 12)));
|
|
||||||
|
|
||||||
if (bccomp($amount, $maxAmount) <= 0) {
|
|
||||||
$repetition = $piggyBank->currentRelevantRep();
|
|
||||||
$currentAmount = $repetition->currentamount ?? '0';
|
|
||||||
$repetition->currentamount = bcadd($currentAmount, $amount);
|
|
||||||
$repetition->save();
|
|
||||||
|
|
||||||
// create event
|
|
||||||
$repository->createEvent($piggyBank, $amount);
|
|
||||||
|
|
||||||
Session::flash(
|
Session::flash(
|
||||||
'success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))
|
'success', strval(
|
||||||
|
trans(
|
||||||
|
'firefly.added_amount_to_piggy',
|
||||||
|
['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name]
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
return redirect(route('piggy-banks.index'));
|
return redirect(route('piggy-banks.index'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::error('Cannot add ' . $amount . ' because max amount is ' . $maxAmount . ' (left on account is ' . $leftOnAccount . ')');
|
Log::error('Cannot add ' . $amount . ' because canAddAmount returned false.');
|
||||||
Session::flash('error', strval(trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
|
Session::flash(
|
||||||
|
'error', strval(
|
||||||
|
trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return redirect(route('piggy-banks.index'));
|
return redirect(route('piggy-banks.index'));
|
||||||
}
|
}
|
||||||
@@ -312,27 +312,26 @@ class PiggyBankController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
|
public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank)
|
||||||
{
|
{
|
||||||
$amount = strval(round($request->get('amount'), 12));
|
$amount = $request->get('amount');
|
||||||
|
$currency = Amount::getDefaultCurrency();
|
||||||
$savedSoFar = $piggyBank->currentRelevantRep()->currentamount;
|
if ($repository->canRemoveAmount($piggyBank, $amount)) {
|
||||||
|
$repository->removeAmount($piggyBank, $amount);
|
||||||
if (bccomp($amount, $savedSoFar) <= 0) {
|
|
||||||
$repetition = $piggyBank->currentRelevantRep();
|
|
||||||
$repetition->currentamount = bcsub($repetition->currentamount, $amount);
|
|
||||||
$repetition->save();
|
|
||||||
|
|
||||||
// create event
|
|
||||||
$repository->createEvent($piggyBank, bcmul($amount, '-1'));
|
|
||||||
|
|
||||||
Session::flash(
|
Session::flash(
|
||||||
'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))
|
'success',
|
||||||
|
strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name]))
|
||||||
);
|
);
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
return redirect(route('piggy-banks.index'));
|
return redirect(route('piggy-banks.index'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)])));
|
$amount = strval(round($request->get('amount'), 12));
|
||||||
|
|
||||||
|
Session::flash(
|
||||||
|
'error', strval(
|
||||||
|
trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return redirect(route('piggy-banks.index'));
|
return redirect(route('piggy-banks.index'));
|
||||||
}
|
}
|
||||||
@@ -391,9 +390,11 @@ class PiggyBankController extends Controller
|
|||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval($request->get('create_another')) === 1) {
|
if (intval($request->get('create_another')) === 1) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
Session::put('piggy-banks.create.fromStore', true);
|
Session::put('piggy-banks.create.fromStore', true);
|
||||||
|
|
||||||
return redirect(route('piggy-banks.create'))->withInput();
|
return redirect(route('piggy-banks.create'))->withInput();
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('piggy-banks.edit.uri'));
|
return redirect($this->getPreviousUri('piggy-banks.edit.uri'));
|
||||||
@@ -415,9 +416,11 @@ class PiggyBankController extends Controller
|
|||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
if (intval($request->get('return_to_edit')) === 1) {
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
Session::put('piggy-banks.edit.fromUpdate', true);
|
Session::put('piggy-banks.edit.fromUpdate', true);
|
||||||
|
|
||||||
return redirect(route('piggy-banks.edit', [$piggyBank->id]));
|
return redirect(route('piggy-banks.edit', [$piggyBank->id]));
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect($this->getPreviousUri('piggy-banks.edit.uri'));
|
return redirect($this->getPreviousUri('piggy-banks.edit.uri'));
|
||||||
|
|||||||
@@ -17,17 +17,13 @@ namespace FireflyIII\Http\Controllers\Popup;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Helpers\Collection\BalanceLine;
|
use FireflyIII\Helpers\Collection\BalanceLine;
|
||||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
use FireflyIII\Helpers\Report\PopupReportInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\Transaction;
|
|
||||||
use FireflyIII\Models\TransactionJournal;
|
|
||||||
use FireflyIII\Models\TransactionType;
|
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
use FireflyIII\Support\Binder\AccountList;
|
use FireflyIII\Support\Binder\AccountList;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Response;
|
use Response;
|
||||||
use View;
|
use View;
|
||||||
@@ -40,6 +36,44 @@ use View;
|
|||||||
class ReportController extends Controller
|
class ReportController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @var AccountRepositoryInterface */
|
||||||
|
private $accountRepository;
|
||||||
|
/** @var BudgetRepositoryInterface */
|
||||||
|
private $budgetRepository;
|
||||||
|
/** @var CategoryRepositoryInterface */
|
||||||
|
private $categoryRepository;
|
||||||
|
/** @var PopupReportInterface */
|
||||||
|
private $popupHelper;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
|
||||||
|
/** @var AccountRepositoryInterface $repository */
|
||||||
|
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||||
|
|
||||||
|
/** @var BudgetRepositoryInterface $repository */
|
||||||
|
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
||||||
|
|
||||||
|
/** @var CategoryRepositoryInterface categoryRepository */
|
||||||
|
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
||||||
|
|
||||||
|
/** @var PopupReportInterface popupHelper */
|
||||||
|
$this->popupHelper = app(PopupReportInterface::class);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
@@ -59,7 +93,6 @@ class ReportController extends Controller
|
|||||||
throw new FireflyException('Firefly cannot handle "' . e($attributes['location']) . '" ');
|
throw new FireflyException('Firefly cannot handle "' . e($attributes['location']) . '" ');
|
||||||
case 'budget-spent-amount':
|
case 'budget-spent-amount':
|
||||||
$html = $this->budgetSpentAmount($attributes);
|
$html = $this->budgetSpentAmount($attributes);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'expense-entry':
|
case 'expense-entry':
|
||||||
$html = $this->expenseEntry($attributes);
|
$html = $this->expenseEntry($attributes);
|
||||||
@@ -89,62 +122,25 @@ class ReportController extends Controller
|
|||||||
private function balanceAmount(array $attributes): string
|
private function balanceAmount(array $attributes): string
|
||||||
{
|
{
|
||||||
$role = intval($attributes['role']);
|
$role = intval($attributes['role']);
|
||||||
|
$budget = $this->budgetRepository->find(intval($attributes['budgetId']));
|
||||||
/** @var BudgetRepositoryInterface $budgetRepository */
|
$account = $this->accountRepository->find(intval($attributes['accountId']));
|
||||||
$budgetRepository = app(BudgetRepositoryInterface::class);
|
|
||||||
$budget = $budgetRepository->find(intval($attributes['budgetId']));
|
|
||||||
|
|
||||||
/** @var AccountRepositoryInterface $repository */
|
|
||||||
$repository = app(AccountRepositoryInterface::class);
|
|
||||||
|
|
||||||
$account = $repository->find(intval($attributes['accountId']));
|
|
||||||
$types = [TransactionType::WITHDRAWAL];
|
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)):
|
case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)):
|
||||||
/** @var JournalCollectorInterface $collector */
|
// normal row with a budget:
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$journals = $this->popupHelper->balanceForBudget($budget, $account, $attributes);
|
||||||
$collector
|
|
||||||
->setAccounts(new Collection([$account]))
|
|
||||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
|
||||||
->setBudget($budget);
|
|
||||||
$journals = $collector->getJournals();
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)):
|
case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)):
|
||||||
|
// normal row without a budget:
|
||||||
|
$journals = $this->popupHelper->balanceForNoBudget($account, $attributes);
|
||||||
$budget->name = strval(trans('firefly.no_budget'));
|
$budget->name = strval(trans('firefly.no_budget'));
|
||||||
/** @var JournalCollectorInterface $collector */
|
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector
|
|
||||||
->setAccounts(new Collection([$account]))
|
|
||||||
->setTypes($types)
|
|
||||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
|
||||||
->withoutBudget();
|
|
||||||
$journals = $collector->getJournals();
|
|
||||||
break;
|
break;
|
||||||
case ($role === BalanceLine::ROLE_DIFFROLE):
|
case ($role === BalanceLine::ROLE_DIFFROLE):
|
||||||
/** @var JournalCollectorInterface $collector */
|
$journals = $this->popupHelper->balanceDifference($account, $attributes);
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector
|
|
||||||
->setAccounts(new Collection([$account]))
|
|
||||||
->setTypes($types)
|
|
||||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
|
||||||
->withoutBudget();
|
|
||||||
$journals = $collector->getJournals();
|
|
||||||
|
|
||||||
$budget->name = strval(trans('firefly.leftUnbalanced'));
|
$budget->name = strval(trans('firefly.leftUnbalanced'));
|
||||||
$journals = $journals->filter(
|
|
||||||
function (Transaction $transaction) {
|
|
||||||
$tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count();
|
|
||||||
if ($tags === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case ($role === BalanceLine::ROLE_TAGROLE):
|
case ($role === BalanceLine::ROLE_TAGROLE):
|
||||||
|
// row with tag info.
|
||||||
throw new FireflyException('Firefly cannot handle this type of info-button (BalanceLine::TagRole)');
|
throw new FireflyException('Firefly cannot handle this type of info-button (BalanceLine::TagRole)');
|
||||||
}
|
}
|
||||||
$view = view('popup.report.balance-amount', compact('journals', 'budget', 'account'))->render();
|
$view = view('popup.report.balance-amount', compact('journals', 'budget', 'account'))->render();
|
||||||
@@ -162,27 +158,8 @@ class ReportController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function budgetSpentAmount(array $attributes): string
|
private function budgetSpentAmount(array $attributes): string
|
||||||
{
|
{
|
||||||
// need to find the budget
|
$budget = $this->budgetRepository->find(intval($attributes['budgetId']));
|
||||||
// then search for expenses in the given period
|
$journals = $this->popupHelper->byBudget($budget, $attributes);
|
||||||
// list them in some table format.
|
|
||||||
/** @var BudgetRepositoryInterface $repository */
|
|
||||||
$repository = app(BudgetRepositoryInterface::class);
|
|
||||||
$budget = $repository->find(intval($attributes['budgetId']));
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
|
|
||||||
$collector
|
|
||||||
->setAccounts($attributes['accounts'])
|
|
||||||
->setRange($attributes['startDate'], $attributes['endDate']);
|
|
||||||
|
|
||||||
if (is_null($budget->id)) {
|
|
||||||
$collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
|
|
||||||
}
|
|
||||||
if (!is_null($budget->id)) {
|
|
||||||
// get all expenses in budget in period:
|
|
||||||
$collector->setBudget($budget);
|
|
||||||
}
|
|
||||||
$journals = $collector->getJournals();
|
|
||||||
$view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render();
|
$view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render();
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
@@ -198,17 +175,8 @@ class ReportController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function categoryEntry(array $attributes): string
|
private function categoryEntry(array $attributes): string
|
||||||
{
|
{
|
||||||
/** @var CategoryRepositoryInterface $repository */
|
$category = $this->categoryRepository->find(intval($attributes['categoryId']));
|
||||||
$repository = app(CategoryRepositoryInterface::class);
|
$journals = $this->popupHelper->byCategory($category, $attributes);
|
||||||
$category = $repository->find(intval($attributes['categoryId']));
|
|
||||||
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector->setAccounts($attributes['accounts'])->setTypes($types)
|
|
||||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
|
||||||
->setCategory($category);
|
|
||||||
$journals = $collector->getJournals(); // 7193
|
|
||||||
|
|
||||||
$view = view('popup.report.category-entry', compact('journals', 'category'))->render();
|
$view = view('popup.report.category-entry', compact('journals', 'category'))->render();
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
@@ -224,28 +192,8 @@ class ReportController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function expenseEntry(array $attributes): string
|
private function expenseEntry(array $attributes): string
|
||||||
{
|
{
|
||||||
/** @var AccountRepositoryInterface $repository */
|
$account = $this->accountRepository->find(intval($attributes['accountId']));
|
||||||
$repository = app(AccountRepositoryInterface::class);
|
$journals = $this->popupHelper->byExpenses($account, $attributes);
|
||||||
|
|
||||||
$account = $repository->find(intval($attributes['accountId']));
|
|
||||||
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types);
|
|
||||||
$journals = $collector->getJournals();
|
|
||||||
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
|
|
||||||
|
|
||||||
// filter for transfers and withdrawals TO the given $account
|
|
||||||
$journals = $journals->filter(
|
|
||||||
function (Transaction $transaction) use ($report) {
|
|
||||||
// get the destinations:
|
|
||||||
$sources = TransactionJournal::sourceAccountList($transaction->transactionJournal)->pluck('id')->toArray();
|
|
||||||
|
|
||||||
// do these intersect with the current list?
|
|
||||||
return !empty(array_intersect($report, $sources));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$view = view('popup.report.expense-entry', compact('journals', 'account'))->render();
|
$view = view('popup.report.expense-entry', compact('journals', 'account'))->render();
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
@@ -261,27 +209,8 @@ class ReportController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function incomeEntry(array $attributes): string
|
private function incomeEntry(array $attributes): string
|
||||||
{
|
{
|
||||||
/** @var AccountRepositoryInterface $repository */
|
$account = $this->accountRepository->find(intval($attributes['accountId']));
|
||||||
$repository = app(AccountRepositoryInterface::class);
|
$journals = $this->popupHelper->byIncome($account, $attributes);
|
||||||
$account = $repository->find(intval($attributes['accountId']));
|
|
||||||
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setTypes($types);
|
|
||||||
$journals = $collector->getJournals();
|
|
||||||
$report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report
|
|
||||||
|
|
||||||
// filter the set so the destinations outside of $attributes['accounts'] are not included.
|
|
||||||
$journals = $journals->filter(
|
|
||||||
function (Transaction $transaction) use ($report) {
|
|
||||||
// get the destinations:
|
|
||||||
$destinations = TransactionJournal::destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray();
|
|
||||||
|
|
||||||
// do these intersect with the current list?
|
|
||||||
return !empty(array_intersect($report, $destinations));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$view = view('popup.report.income-entry', compact('journals', 'account'))->render();
|
$view = view('popup.report.income-entry', compact('journals', 'account'))->render();
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user