mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-21 10:41:22 +00:00
Compare commits
897 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad1e9c27e9 | ||
|
|
ab761696bf | ||
|
|
0713273a99 | ||
|
|
5668a3271b | ||
|
|
1eca105a91 | ||
|
|
3883b99c24 | ||
|
|
d6adbc697a | ||
|
|
a5789b1085 | ||
|
|
a6ccbcb795 | ||
|
|
1a6067f7ae | ||
|
|
cb735b18a9 | ||
|
|
909bd11147 | ||
|
|
1a76c606ed | ||
|
|
8c9b6796a1 | ||
|
|
844ab608d4 | ||
|
|
dc39094975 | ||
|
|
b32184d525 | ||
|
|
d95ae53ce2 | ||
|
|
5e3147ddeb | ||
|
|
9e594c6075 | ||
|
|
c0058c51ea | ||
|
|
b0b68d4243 | ||
|
|
22eb90212d | ||
|
|
94e264b6ce | ||
|
|
7ea15761a6 | ||
|
|
1ced4a089d | ||
|
|
648e63628c | ||
|
|
2847e2aff5 | ||
|
|
9dfaabb5d0 | ||
|
|
6a21f98ea4 | ||
|
|
4d5f4cc1c0 | ||
|
|
970ce6cb0d | ||
|
|
31cad5de00 | ||
|
|
e06db9e620 | ||
|
|
f57ac64dc2 | ||
|
|
57d7c1623f | ||
|
|
c86aa9cb3f | ||
|
|
48209d0d22 | ||
|
|
8f6a271cc0 | ||
|
|
a9b610f367 | ||
|
|
1046930f29 | ||
|
|
1b16e5e216 | ||
|
|
e16ba9ac70 | ||
|
|
71ac676b83 | ||
|
|
1b6c0d5d86 | ||
|
|
14db016e98 | ||
|
|
7e2e1626ac | ||
|
|
bce4e7e2bf | ||
|
|
ede327f3d3 | ||
|
|
82718a74dc | ||
|
|
eefd6141a1 | ||
|
|
7894f1871e | ||
|
|
0ef9b5b462 | ||
|
|
9ca75d134e | ||
|
|
b78776e1f7 | ||
|
|
f2f9f8fbab | ||
|
|
5b5acba816 | ||
|
|
9f2729d0ff | ||
|
|
afe98cda9f | ||
|
|
9c4d2e8791 | ||
|
|
cea170359f | ||
|
|
70bb8fbc89 | ||
|
|
82cd0adca6 | ||
|
|
e821f5b2b6 | ||
|
|
4cade467c6 | ||
|
|
b6c9639948 | ||
|
|
ca9319db34 | ||
|
|
beaec9a4c1 | ||
|
|
cbc44e8200 | ||
|
|
017b1a481a | ||
|
|
e15932fe4a | ||
|
|
08c044fe52 | ||
|
|
0e11245cb4 | ||
|
|
cde494d3ef | ||
|
|
9a15decdff | ||
|
|
186b986e02 | ||
|
|
cdbf5653ac | ||
|
|
c403dd7490 | ||
|
|
d15d9fdf2a | ||
|
|
0b618de44c | ||
|
|
875f19f728 | ||
|
|
7bb549732c | ||
|
|
b9baa93ae4 | ||
|
|
315479fcd3 | ||
|
|
1f1334a1fc | ||
|
|
bf0744e03a | ||
|
|
8fb9577660 | ||
|
|
90d58c5c39 | ||
|
|
b6aa79bb38 | ||
|
|
14a0de6b6a | ||
|
|
13e56b7249 | ||
|
|
3753901e38 | ||
|
|
e76075e29f | ||
|
|
284db7f90b | ||
|
|
cabdf4e380 | ||
|
|
9859052c4d | ||
|
|
0feeac9160 | ||
|
|
54b33a0b69 | ||
|
|
e08e7b2c9b | ||
|
|
782e2add88 | ||
|
|
f18a5a6f1b | ||
|
|
6fc971c4cb | ||
|
|
3250c4830d | ||
|
|
9e1a69217d | ||
|
|
46c26a64d8 | ||
|
|
2f12a70647 | ||
|
|
be190d1fa0 | ||
|
|
1e4888209b | ||
|
|
8aa2961c19 | ||
|
|
304cdabc96 | ||
|
|
c60e272eb3 | ||
|
|
c074f55cb2 | ||
|
|
e6af29646e | ||
|
|
b4213328fe | ||
|
|
8a7628c9dc | ||
|
|
d52c146e12 | ||
|
|
1910a4bd4b | ||
|
|
bd0c552f54 | ||
|
|
b29ea98de4 | ||
|
|
dd1db87806 | ||
|
|
6f9e446577 | ||
|
|
664230dca8 | ||
|
|
1a24e7e0aa | ||
|
|
9239815ce6 | ||
|
|
116e19ec06 | ||
|
|
fc0ad622eb | ||
|
|
2c5cdb8780 | ||
|
|
9a309f32fa | ||
|
|
e2e54d342a | ||
|
|
42f7529495 | ||
|
|
f172151252 | ||
|
|
e2ad38d3e0 | ||
|
|
40cc32fc5a | ||
|
|
436c034fdd | ||
|
|
286b1848d9 | ||
|
|
7fffebf6df | ||
|
|
b1764478ec | ||
|
|
6b6a799206 | ||
|
|
0a82ed901e | ||
|
|
d733c9ed14 | ||
|
|
a752ea489c | ||
|
|
876a24586f | ||
|
|
ea2779cf9a | ||
|
|
77aa36163d | ||
|
|
b581d8ecb7 | ||
|
|
83b404d01e | ||
|
|
8deb92c3e5 | ||
|
|
20a6e0170c | ||
|
|
944a78807c | ||
|
|
0b02d294f4 | ||
|
|
a5d86536c3 | ||
|
|
71c08cfe0c | ||
|
|
8ab0d5fc48 | ||
|
|
57f81ee4c8 | ||
|
|
5c28adf266 | ||
|
|
5a57398f81 | ||
|
|
8121a384ef | ||
|
|
8666197e05 | ||
|
|
1ea2b8bbcb | ||
|
|
a71cedd8a9 | ||
|
|
04c5f583f6 | ||
|
|
7716ff4e8c | ||
|
|
6b51a116d1 | ||
|
|
b2f14dc177 | ||
|
|
da1d3b82f9 | ||
|
|
6282d8c828 | ||
|
|
73129b0ce5 | ||
|
|
f71e7a2f28 | ||
|
|
341da327e3 | ||
|
|
3d8adfa7e4 | ||
|
|
279d7769f5 | ||
|
|
b7d3b40353 | ||
|
|
7ecd691ee2 | ||
|
|
f3398c7dec | ||
|
|
90644e662d | ||
|
|
f5c5cb7fb9 | ||
|
|
312e79921a | ||
|
|
b83d346a86 | ||
|
|
3eed67f108 | ||
|
|
15f0bc63b2 | ||
|
|
0a4b0ec929 | ||
|
|
560f6cbf24 | ||
|
|
9165e0238f | ||
|
|
97d6be6809 | ||
|
|
4de14eba0c | ||
|
|
6c64023bf7 | ||
|
|
a923c288e6 | ||
|
|
4c1d8e8e85 | ||
|
|
02f2def88b | ||
|
|
4bcacc5d68 | ||
|
|
913dbe6b1a | ||
|
|
ce8164dd87 | ||
|
|
a5b412f546 | ||
|
|
82bb352624 | ||
|
|
ebbf2659b1 | ||
|
|
d0084becea | ||
|
|
6af2b37ac2 | ||
|
|
814fc6eabd | ||
|
|
50278a679a | ||
|
|
d42e9c75ef | ||
|
|
00b3dced2c | ||
|
|
5c0c00188f | ||
|
|
2ec56626f3 | ||
|
|
e87456b2f8 | ||
|
|
3609b515e5 | ||
|
|
a1609542c3 | ||
|
|
4c4625583a | ||
|
|
7b479316ea | ||
|
|
b021c7690f | ||
|
|
2be060796e | ||
|
|
1b4d55cca4 | ||
|
|
a8cea4119d | ||
|
|
e247aace8d | ||
|
|
41553e9b86 | ||
|
|
e875587260 | ||
|
|
5377483345 | ||
|
|
4112acfb8d | ||
|
|
f3bc02e11c | ||
|
|
8e411a898b | ||
|
|
915edbecc9 | ||
|
|
975a6c34bf | ||
|
|
cdd988b4de | ||
|
|
b58bc97422 | ||
|
|
482688ac3c | ||
|
|
aea31b5e28 | ||
|
|
d7cbc53b4b | ||
|
|
f74c6c2d19 | ||
|
|
3080d2ddc4 | ||
|
|
4ad5881760 | ||
|
|
7e55d1a4fd | ||
|
|
7ef5eed6e2 | ||
|
|
10aa41a7ea | ||
|
|
1f9b362b6f | ||
|
|
4bf9bfb521 | ||
|
|
1d7119114d | ||
|
|
e1b6df6fb1 | ||
|
|
7cf38bb01e | ||
|
|
e34ec22845 | ||
|
|
46506abeb8 | ||
|
|
95654cc4d4 | ||
|
|
47aded820d | ||
|
|
24444ebf08 | ||
|
|
bdc0df8350 | ||
|
|
b2c9a2973c | ||
|
|
da2a347511 | ||
|
|
6fbc3ba060 | ||
|
|
02eff06cd3 | ||
|
|
7586d4b494 | ||
|
|
9059f0fee6 | ||
|
|
c2af9e3d20 | ||
|
|
0b51366526 | ||
|
|
e40260bd9c | ||
|
|
cf2842840d | ||
|
|
17fa8fcb2c | ||
|
|
0d2f9864e2 | ||
|
|
89cbd91204 | ||
|
|
f4d9b57887 | ||
|
|
4b2e4afca5 | ||
|
|
dd1ba30c48 | ||
|
|
3ba4570691 | ||
|
|
848cfabcba | ||
|
|
1bbd10b909 | ||
|
|
a16a4f813d | ||
|
|
91cfa963b2 | ||
|
|
a35557eb62 | ||
|
|
aad4e47b6a | ||
|
|
1b177723ae | ||
|
|
99dba92bd3 | ||
|
|
e13ccff056 | ||
|
|
46528dd29d | ||
|
|
4f611ad810 | ||
|
|
af41985a64 | ||
|
|
d0864e06b5 | ||
|
|
6f0366e146 | ||
|
|
e0cdbcb28c | ||
|
|
f19b99194c | ||
|
|
43a55e2e35 | ||
|
|
b2743825ca | ||
|
|
d4f6cce56e | ||
|
|
6092d206b6 | ||
|
|
c8ad83cc91 | ||
|
|
7d31071ff8 | ||
|
|
c975ef15f1 | ||
|
|
f855011d34 | ||
|
|
fbcf0929d8 | ||
|
|
d89e75cbe8 | ||
|
|
ccaa42ad74 | ||
|
|
56d8dce622 | ||
|
|
c79baf98cf | ||
|
|
d1cab9f68c | ||
|
|
69c5c93353 | ||
|
|
28ebd683e4 | ||
|
|
d752edd625 | ||
|
|
1dab45d493 | ||
|
|
b99982d02b | ||
|
|
fff17ac6c1 | ||
|
|
4086257983 | ||
|
|
bd9e0ac281 | ||
|
|
b075d6db5e | ||
|
|
befd79cf14 | ||
|
|
07f68d2b14 | ||
|
|
d14889bd27 | ||
|
|
91e40c14f9 | ||
|
|
b7b2206262 | ||
|
|
f344d0319c | ||
|
|
0c8a1682b6 | ||
|
|
39866be3f1 | ||
|
|
947e82fa0f | ||
|
|
0335a64a21 | ||
|
|
a9e57e1c34 | ||
|
|
8a8279f97a | ||
|
|
b968889552 | ||
|
|
4068df5e50 | ||
|
|
dc42370322 | ||
|
|
8c24f14ee5 | ||
|
|
494d1743a2 | ||
|
|
4a30d9f6bb | ||
|
|
ed6d25067c | ||
|
|
445ae7e10e | ||
|
|
6f45609161 | ||
|
|
f1230e47f7 | ||
|
|
7e0ef6d43e | ||
|
|
14f9da544a | ||
|
|
5a84036e16 | ||
|
|
4dccf7b7b5 | ||
|
|
66060dbed4 | ||
|
|
cfb824588f | ||
|
|
d2b4316d7a | ||
|
|
3af69b433d | ||
|
|
a6733fa255 | ||
|
|
4277c54009 | ||
|
|
66baa7554a | ||
|
|
ffca4b0543 | ||
|
|
3e3c48314f | ||
|
|
06ff450d31 | ||
|
|
07c57cc640 | ||
|
|
a67f10c99e | ||
|
|
2882bcbf7b | ||
|
|
67cc5b0280 | ||
|
|
b42b178b71 | ||
|
|
7de05cd173 | ||
|
|
3db43743d9 | ||
|
|
14638e4ed8 | ||
|
|
e756b93810 | ||
|
|
358d83dcfc | ||
|
|
331c231a94 | ||
|
|
4403b65bae | ||
|
|
a27d80d765 | ||
|
|
04272fff81 | ||
|
|
e963708c54 | ||
|
|
08c4542847 | ||
|
|
553e9270e5 | ||
|
|
8a7297e131 | ||
|
|
0f260da8e6 | ||
|
|
77560ab3a8 | ||
|
|
e3b2f2d9a8 | ||
|
|
74e01a52b9 | ||
|
|
dc28ba42ef | ||
|
|
406150620a | ||
|
|
43f59a1135 | ||
|
|
5c02eaa66c | ||
|
|
b4eac84097 | ||
|
|
ec3b356f86 | ||
|
|
bf99d5c299 | ||
|
|
a297131440 | ||
|
|
bae2161ee3 | ||
|
|
0fe0de1a7f | ||
|
|
e7845115f6 | ||
|
|
bc11c3fab2 | ||
|
|
1b7546f3f9 | ||
|
|
663be30117 | ||
|
|
cf34713518 | ||
|
|
3f56a8ec53 | ||
|
|
35d105588b | ||
|
|
122d988ed2 | ||
|
|
9fcc5e7a67 | ||
|
|
9a492c3731 | ||
|
|
4f752031f3 | ||
|
|
19be8bb891 | ||
|
|
693e1b08c7 | ||
|
|
9aad380518 | ||
|
|
8c518c8d58 | ||
|
|
9af89a19db | ||
|
|
939b18b86c | ||
|
|
108e775a15 | ||
|
|
653692ade0 | ||
|
|
72c6bfee7e | ||
|
|
ac92939429 | ||
|
|
052957bbd0 | ||
|
|
97e6afe3dc | ||
|
|
1fd028dfb8 | ||
|
|
c73866f47c | ||
|
|
b0e120abee | ||
|
|
b2da38d401 | ||
|
|
cabe2579fa | ||
|
|
18a845ac55 | ||
|
|
a4d14f8259 | ||
|
|
9d084e62f7 | ||
|
|
0393fcd704 | ||
|
|
edb5b2ed5e | ||
|
|
529bab1112 | ||
|
|
ab9212a4c9 | ||
|
|
b2cbba0f3b | ||
|
|
ca73ef8531 | ||
|
|
d13490cb6e | ||
|
|
73566e11c0 | ||
|
|
36ebd0f0ee | ||
|
|
efe290d96c | ||
|
|
da3988cc63 | ||
|
|
df6f4aecf8 | ||
|
|
db1a60b6df | ||
|
|
d79866f115 | ||
|
|
cdd18b229e | ||
|
|
cca2de9f1b | ||
|
|
6a58dbb207 | ||
|
|
779f461491 | ||
|
|
085eca6c02 | ||
|
|
25db11a8c7 | ||
|
|
76bcc68ab9 | ||
|
|
9daefaaca4 | ||
|
|
fbbbcc4e74 | ||
|
|
2e1f31a7f8 | ||
|
|
8a0ac81fd0 | ||
|
|
cdd50dfdd2 | ||
|
|
a05c8ca351 | ||
|
|
2ca584f097 | ||
|
|
687da83feb | ||
|
|
c799fc655d | ||
|
|
27848f55ce | ||
|
|
001a6e310e | ||
|
|
ad00bc2806 | ||
|
|
d8e3365345 | ||
|
|
5849fe2c30 | ||
|
|
690b498197 | ||
|
|
d5ddd447bc | ||
|
|
628c7cd055 | ||
|
|
f4887bbbf7 | ||
|
|
d8f291be6e | ||
|
|
02257e3887 | ||
|
|
bebfbf0b90 | ||
|
|
9cb3bfaa57 | ||
|
|
8e2c035536 | ||
|
|
6b56c2bf7c | ||
|
|
d91b9e71d5 | ||
|
|
344916d57e | ||
|
|
b1ef225bd0 | ||
|
|
b713eae009 | ||
|
|
098cc88d5f | ||
|
|
2476dd38b3 | ||
|
|
8fec569dbb | ||
|
|
ba92aa207c | ||
|
|
f7abf132e2 | ||
|
|
38919ae300 | ||
|
|
bba15cef24 | ||
|
|
e8792fa218 | ||
|
|
c5f81d4a94 | ||
|
|
a7b8c9d94d | ||
|
|
f4b9b7ae84 | ||
|
|
905a2432c6 | ||
|
|
89e4c3de25 | ||
|
|
86ea9db37e | ||
|
|
62a9fda1c2 | ||
|
|
49f7c1bbc1 | ||
|
|
9dc6f41c18 | ||
|
|
0a844e4313 | ||
|
|
53daa89fcb | ||
|
|
c5d31bccc5 | ||
|
|
b032825342 | ||
|
|
8377a2a0de | ||
|
|
57e49c225b | ||
|
|
6638f6fb5c | ||
|
|
71e1b58f1d | ||
|
|
a87cb0fc0b | ||
|
|
2e65f63e4a | ||
|
|
5fb2db4e28 | ||
|
|
238ae125b5 | ||
|
|
110d7f691c | ||
|
|
9fb9c7e3ee | ||
|
|
a95b1857fe | ||
|
|
ea97b817fc | ||
|
|
0eea85a884 | ||
|
|
eae4e988be | ||
|
|
bdf752bf7e | ||
|
|
a19fed5959 | ||
|
|
7474553832 | ||
|
|
52567116c2 | ||
|
|
a70b369aaf | ||
|
|
33a9e80d9d | ||
|
|
96ef409f75 | ||
|
|
8f5152e185 | ||
|
|
f5f17d1f40 | ||
|
|
b960f50f38 | ||
|
|
72e357b673 | ||
|
|
b33aa733c7 | ||
|
|
a6a2c0c182 | ||
|
|
3097ab84fa | ||
|
|
dd9ce3e06d | ||
|
|
560fc8b01c | ||
|
|
f72aba6939 | ||
|
|
03bc74cae9 | ||
|
|
7eaf8e3eeb | ||
|
|
b2b4732657 | ||
|
|
70473b7635 | ||
|
|
e4a9e23dfb | ||
|
|
f0fd5324ea | ||
|
|
addebad810 | ||
|
|
253466c533 | ||
|
|
885d0f1464 | ||
|
|
4743cc40a2 | ||
|
|
92bf9c9214 | ||
|
|
cd80d82ad4 | ||
|
|
1b7b6a676d | ||
|
|
1c61afca07 | ||
|
|
d4d812c195 | ||
|
|
ab7803f210 | ||
|
|
11007f0476 | ||
|
|
6b1884a9e0 | ||
|
|
1112a0761f | ||
|
|
807947fcd8 | ||
|
|
7afd8f99cb | ||
|
|
b14a15ce49 | ||
|
|
2e6ad0ce5d | ||
|
|
8cdbc96aa5 | ||
|
|
956019ff4a | ||
|
|
8279cf0e88 | ||
|
|
43c32abfe8 | ||
|
|
0e66939408 | ||
|
|
22d2a523fb | ||
|
|
bc825a8603 | ||
|
|
c9cfda34a1 | ||
|
|
e8dfbff73f | ||
|
|
62e41f1997 | ||
|
|
8c9f90f1b4 | ||
|
|
1453a78e49 | ||
|
|
7efaf51595 | ||
|
|
6bc6674ab1 | ||
|
|
d6c7ff0ccb | ||
|
|
28f655dba1 | ||
|
|
6a3de12894 | ||
|
|
c7940333ec | ||
|
|
8860378757 | ||
|
|
728fda0116 | ||
|
|
0c72e1831f | ||
|
|
7da21976ec | ||
|
|
b739859c64 | ||
|
|
d25665f843 | ||
|
|
1f41f7bd0f | ||
|
|
dd8638ca98 | ||
|
|
4ba9ff05b0 | ||
|
|
618aad5432 | ||
|
|
e46fc7501e | ||
|
|
7b91e98d46 | ||
|
|
85be218f92 | ||
|
|
71206e395e | ||
|
|
9b2d2e16b0 | ||
|
|
5ae01b382e | ||
|
|
d92a0753a6 | ||
|
|
f937a74507 | ||
|
|
c3584ad20c | ||
|
|
c049d5cfa6 | ||
|
|
6c9990e0be | ||
|
|
b34e4cd31b | ||
|
|
7852b8a785 | ||
|
|
6eeb60db5c | ||
|
|
d076cfc08f | ||
|
|
68a93ff97c | ||
|
|
295dcb4f65 | ||
|
|
d9849f60c0 | ||
|
|
7ebb68e36c | ||
|
|
f029f7607b | ||
|
|
2ba5733ebc | ||
|
|
3fe1d1d368 | ||
|
|
438c372583 | ||
|
|
797aa4858e | ||
|
|
8c858cd066 | ||
|
|
85aebd39b9 | ||
|
|
9a5a037424 | ||
|
|
7d557cbf91 | ||
|
|
dbbc85a576 | ||
|
|
eb78cf20c2 | ||
|
|
4a99399952 | ||
|
|
6075d75ee2 | ||
|
|
f4c56fee66 | ||
|
|
04c59304da | ||
|
|
4b3c31a11a | ||
|
|
14576d2753 | ||
|
|
72ca1c20c7 | ||
|
|
93645819b8 | ||
|
|
39468f871b | ||
|
|
faa47781d2 | ||
|
|
2c196bab6d | ||
|
|
9ae71075ef | ||
|
|
0013cdfa78 | ||
|
|
52f3f64f7b | ||
|
|
670fa77dd7 | ||
|
|
8baea2feb9 | ||
|
|
c56f937521 | ||
|
|
0b613c3b8c | ||
|
|
78f297e18f | ||
|
|
bd8a285d6d | ||
|
|
b44602fd55 | ||
|
|
41238903e1 | ||
|
|
a0c88e9b33 | ||
|
|
5d184aa53e | ||
|
|
9f9bf86a9f | ||
|
|
53af9345eb | ||
|
|
da6bcf04df | ||
|
|
ec4ec1a147 | ||
|
|
350e0b08b1 | ||
|
|
9340ca09e6 | ||
|
|
a1cef5c339 | ||
|
|
94875adb6c | ||
|
|
75a524c656 | ||
|
|
e1e94a788c | ||
|
|
8417f45d02 | ||
|
|
685310a368 | ||
|
|
45e7a4576a | ||
|
|
f8c5c15655 | ||
|
|
26190524f4 | ||
|
|
5d901a7ecb | ||
|
|
929d8b3adc | ||
|
|
cd6e37b9cb | ||
|
|
b647386541 | ||
|
|
174fd88435 | ||
|
|
cc9211b7c2 | ||
|
|
a9795fb095 | ||
|
|
8554aae21e | ||
|
|
5a2ef36f2a | ||
|
|
01e3f91ece | ||
|
|
7ec9c090cc | ||
|
|
b057d69f8e | ||
|
|
ff4e1838bc | ||
|
|
e4ecd0b7ff | ||
|
|
1ba35f73e1 | ||
|
|
240f3c126b | ||
|
|
23925a0076 | ||
|
|
50b72cf229 | ||
|
|
ee6b72afa5 | ||
|
|
781621960d | ||
|
|
e15ea04186 | ||
|
|
73f0cc705b | ||
|
|
0c072c7d51 | ||
|
|
884bed85a1 | ||
|
|
a319264428 | ||
|
|
6506e70a91 | ||
|
|
e6fcb19db7 | ||
|
|
a3fba53182 | ||
|
|
f8438dd9d3 | ||
|
|
028a0dcae1 | ||
|
|
b4a06b5bbd | ||
|
|
4fe1a5d527 | ||
|
|
865930c5b2 | ||
|
|
96b4e2c196 | ||
|
|
b7e7c7e9e2 | ||
|
|
7771669db7 | ||
|
|
ef59eb6e1f | ||
|
|
a14b2bc5a7 | ||
|
|
47349589cb | ||
|
|
79afe84f30 | ||
|
|
171187b25c | ||
|
|
7f1b661e61 | ||
|
|
2c2a3a5475 | ||
|
|
1677ca9619 | ||
|
|
204da3e846 | ||
|
|
f36d423b1e | ||
|
|
79c7280046 | ||
|
|
e10fc4a854 | ||
|
|
5088df103f | ||
|
|
13b96f6136 | ||
|
|
757662ca4b | ||
|
|
4ef324cf24 | ||
|
|
cb02e0ee71 | ||
|
|
ec3a90688e | ||
|
|
6dcecdcc64 | ||
|
|
25d917240d | ||
|
|
0906915a87 | ||
|
|
9c92a94177 | ||
|
|
1b125ecd22 | ||
|
|
25a2bcd76e | ||
|
|
b2e09f4240 | ||
|
|
560165850f | ||
|
|
0bb07e1eeb | ||
|
|
0c0f2109f6 | ||
|
|
f546670342 | ||
|
|
eecb6c6679 | ||
|
|
750b9d8038 | ||
|
|
9ce28fdd2e | ||
|
|
07af64ada5 | ||
|
|
a0ab0ec902 | ||
|
|
752f8582aa | ||
|
|
4d0eed8c9b | ||
|
|
0d7a8305f3 | ||
|
|
2e8c0ec537 | ||
|
|
3155ec9e2b | ||
|
|
7bbca7f6a8 | ||
|
|
f7579db4ad | ||
|
|
2f47c58df5 | ||
|
|
7e7ac264d2 | ||
|
|
98d6c90e90 | ||
|
|
da49afa37b | ||
|
|
64364c3e77 | ||
|
|
b6f0fd1949 | ||
|
|
0663a18f3a | ||
|
|
c5928897eb | ||
|
|
570373e875 | ||
|
|
228afc2eea | ||
|
|
6b61621d6a | ||
|
|
424133fa83 | ||
|
|
02e30c1fcc | ||
|
|
e17a9d559b | ||
|
|
36744377f6 | ||
|
|
7c479f73c0 | ||
|
|
85b3c4683b | ||
|
|
c5d2fabfec | ||
|
|
a294f757ff | ||
|
|
04515da0bc | ||
|
|
6d60d64a82 | ||
|
|
32b5a84a0c | ||
|
|
4b42ef0db8 | ||
|
|
abc7b9912d | ||
|
|
727717931a | ||
|
|
1d66b16468 | ||
|
|
b918429c43 | ||
|
|
888273d4a0 | ||
|
|
31b5d5ba72 | ||
|
|
b148d0868e | ||
|
|
c1491383a8 | ||
|
|
5f07918682 | ||
|
|
8de6bd7ceb | ||
|
|
5d4f1bc76d | ||
|
|
8583b574ac | ||
|
|
3600e1b5e7 | ||
|
|
fe57648349 | ||
|
|
f0e0cdb49b | ||
|
|
cf69333c6d | ||
|
|
3f7e16d270 | ||
|
|
a63f1638f4 | ||
|
|
8ec2a3a391 | ||
|
|
d875f0e580 | ||
|
|
729534b4f3 | ||
|
|
bd6a56a55e | ||
|
|
96976db350 | ||
|
|
8735190461 | ||
|
|
709a14e5c9 | ||
|
|
c89d2a52b5 | ||
|
|
6084d16ea8 | ||
|
|
1688fdb786 | ||
|
|
6cfb5ee2e9 | ||
|
|
2db560ed7d | ||
|
|
45567cdf65 | ||
|
|
508ad5157b | ||
|
|
8fc41e0226 | ||
|
|
a08dfe1e3c | ||
|
|
49cc8a97a3 | ||
|
|
5b8583dd2b | ||
|
|
f653bc5f6e | ||
|
|
a6a9794fc7 | ||
|
|
fdb8f61e37 | ||
|
|
69422cc796 | ||
|
|
5f9a9bc89a | ||
|
|
4d0d05e0f8 | ||
|
|
0113fedbd4 | ||
|
|
a7d35cd1c3 | ||
|
|
43600fe6cb | ||
|
|
0b5e25960f | ||
|
|
0c8a1b51e9 | ||
|
|
cb49f5e8d8 | ||
|
|
a0e3088ca3 | ||
|
|
b86be6f52f | ||
|
|
4c573e1300 | ||
|
|
1a3d77f117 | ||
|
|
2656da13b1 | ||
|
|
d272ebd95c | ||
|
|
7612f1f91a | ||
|
|
22a2fe3f61 | ||
|
|
1ebb59b352 | ||
|
|
77e2cf40df | ||
|
|
0edffd8ea1 | ||
|
|
ee6e047596 | ||
|
|
bd55636b3f | ||
|
|
b24e97a449 | ||
|
|
d45355fc3f | ||
|
|
b2206f640a | ||
|
|
962cad33e2 | ||
|
|
d65214b75a | ||
|
|
7b4c151df5 | ||
|
|
28d6f51961 | ||
|
|
d2f9deb82b | ||
|
|
d9b05b5f59 | ||
|
|
a8f4b33c57 | ||
|
|
ee849ea12f | ||
|
|
f9d3cf231f | ||
|
|
0713ca7709 | ||
|
|
1e2124c5ed | ||
|
|
37435da459 | ||
|
|
05dbd30bbd | ||
|
|
4b947638a7 | ||
|
|
3d113b9aae | ||
|
|
d1b3681bf3 | ||
|
|
9dd4b07314 | ||
|
|
3814f0f3c3 | ||
|
|
b1e907fae9 | ||
|
|
5c03a1a9c8 | ||
|
|
20ac07a386 | ||
|
|
13e1292bb7 | ||
|
|
8e542531b3 | ||
|
|
43afdb021a | ||
|
|
aeca2ef3b2 | ||
|
|
205a593721 | ||
|
|
46649fe228 | ||
|
|
adb97fcb05 | ||
|
|
98160e9b63 | ||
|
|
9c5d192d90 | ||
|
|
47bebb614e | ||
|
|
5f7fb77db2 | ||
|
|
1d15bc0b10 | ||
|
|
7bc4c6d115 | ||
|
|
45973a53f5 | ||
|
|
8e5e3de8b0 | ||
|
|
8738cd4b04 | ||
|
|
24a7dac235 | ||
|
|
a3088f6806 | ||
|
|
72f7b5f3ea | ||
|
|
a636c508a2 | ||
|
|
599db95f73 | ||
|
|
f5f78ab79b | ||
|
|
9af9383c29 | ||
|
|
4b97b86c09 | ||
|
|
11fb46830c | ||
|
|
e8dec6d95c | ||
|
|
bb4ee7470d | ||
|
|
2e8071db9e | ||
|
|
4d2901aa02 | ||
|
|
37bbfab20a | ||
|
|
fb9161b82d | ||
|
|
000c9d8974 | ||
|
|
878b664930 | ||
|
|
afe28b5581 | ||
|
|
4106b2e4c0 | ||
|
|
e1be4909b9 | ||
|
|
7a0347c0c2 | ||
|
|
a7e0e3fc15 | ||
|
|
5e480eca36 | ||
|
|
6c8d594df7 | ||
|
|
e24f5ec9f3 | ||
|
|
1379c0652e | ||
|
|
1f87b0bd2d | ||
|
|
787a437ca4 | ||
|
|
c0bdb35cb3 | ||
|
|
4b9cf67413 | ||
|
|
86ff3be741 | ||
|
|
8bc8e8d9fe | ||
|
|
227a12d75d | ||
|
|
2ddd4314f1 | ||
|
|
b980b5baea | ||
|
|
4ba34ab511 | ||
|
|
5be317d73c | ||
|
|
af16205965 | ||
|
|
39917b77c1 | ||
|
|
124ecb1372 | ||
|
|
33c0c1bea6 | ||
|
|
a66990459e | ||
|
|
fecbdc7fbf | ||
|
|
0369ace5f7 | ||
|
|
1657048181 | ||
|
|
b9bdaa7a56 | ||
|
|
f28d07e17b | ||
|
|
8b8bf1debc | ||
|
|
aff1c1e3ef | ||
|
|
169bb2c9bb | ||
|
|
fb1eafef43 | ||
|
|
bfe26ceb39 | ||
|
|
050f305e80 | ||
|
|
63a6a4f823 | ||
|
|
a3b167cab5 | ||
|
|
48327948e2 | ||
|
|
93856d4577 | ||
|
|
7ff068aa95 | ||
|
|
b2f00c869e | ||
|
|
b717cab8f6 | ||
|
|
adaff52707 | ||
|
|
54050edcc6 | ||
|
|
9acbb69a6a | ||
|
|
a5e6de047a | ||
|
|
3d8d35207b | ||
|
|
0a95f59813 | ||
|
|
43a3d28dbd | ||
|
|
685cb7a505 | ||
|
|
dd82466d07 | ||
|
|
2cbe4a013e | ||
|
|
fb85341844 | ||
|
|
116b3ecdad | ||
|
|
af85fbf0a3 | ||
|
|
1d250593c0 | ||
|
|
ed33a054ad | ||
|
|
4e3e015912 | ||
|
|
7821c52842 |
@@ -35,13 +35,17 @@ MAIL_PASSWORD=null
|
|||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
|
|
||||||
SEND_REGISTRATION_MAIL=true
|
SEND_REGISTRATION_MAIL=true
|
||||||
MUST_CONFIRM_ACCOUNT=false
|
SEND_ERROR_MESSAGE=true
|
||||||
|
|
||||||
SHOW_INCOMPLETE_TRANSLATIONS=false
|
SHOW_INCOMPLETE_TRANSLATIONS=false
|
||||||
|
|
||||||
|
CACHE_PREFIX=firefly
|
||||||
|
|
||||||
ANALYTICS_ID=
|
ANALYTICS_ID=
|
||||||
SITE_OWNER=mail@example.com
|
SITE_OWNER=mail@example.com
|
||||||
|
|
||||||
PUSHER_KEY=
|
PUSHER_KEY=
|
||||||
PUSHER_SECRET=
|
PUSHER_SECRET=
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
|
|
||||||
|
DEMO_USERNAME=
|
||||||
|
DEMO_PASSWORD=
|
||||||
45
.env.testing
Executable file
45
.env.testing
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
APP_ENV=testing
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_FORCE_SSL=false
|
||||||
|
APP_FORCE_ROOT=
|
||||||
|
APP_KEY=TestTestTestTestTestTestTestTest
|
||||||
|
APP_LOG_LEVEL=debug
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USERNAME=homestead
|
||||||
|
DB_PASSWORD=secret
|
||||||
|
|
||||||
|
BROADCAST_DRIVER=log
|
||||||
|
CACHE_DRIVER=file
|
||||||
|
SESSION_DRIVER=file
|
||||||
|
QUEUE_DRIVER=sync
|
||||||
|
|
||||||
|
COOKIE_PATH="/"
|
||||||
|
COOKIE_DOMAIN=
|
||||||
|
COOKIE_SECURE=false
|
||||||
|
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_DRIVER=smtp
|
||||||
|
MAIL_HOST=mailtrap.io
|
||||||
|
MAIL_PORT=2525
|
||||||
|
MAIL_FROM=changeme@example.com
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
|
||||||
|
SEND_REGISTRATION_MAIL=true
|
||||||
|
SEND_ERROR_MESSAGE=true
|
||||||
|
SHOW_INCOMPLETE_TRANSLATIONS=false
|
||||||
|
|
||||||
|
ANALYTICS_ID=
|
||||||
|
SITE_OWNER=mail@example.com
|
||||||
|
|
||||||
|
PUSHER_KEY=
|
||||||
|
PUSHER_SECRET=
|
||||||
|
PUSHER_APP_ID=
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ result.html
|
|||||||
test-import.sh
|
test-import.sh
|
||||||
test-import-report.txt
|
test-import-report.txt
|
||||||
public/google*.html
|
public/google*.html
|
||||||
|
.env.backup
|
||||||
|
|||||||
17
.travis.yml
Normal file
17
.travis.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
language: php
|
||||||
|
sudo: false
|
||||||
|
php:
|
||||||
|
- '7.0'
|
||||||
|
|
||||||
|
install:
|
||||||
|
- phpenv config-rm xdebug.ini
|
||||||
|
- rm composer.lock
|
||||||
|
- composer update --no-scripts
|
||||||
|
- php artisan clear-compiled
|
||||||
|
- php artisan optimize
|
||||||
|
- php artisan env
|
||||||
|
- cp .env.testing .env
|
||||||
|
- mv storage/database/databasecopy.sqlite storage/database/database.sqlite
|
||||||
|
|
||||||
|
script:
|
||||||
|
- phpunit
|
||||||
123
CHANGELOG.md
123
CHANGELOG.md
@@ -2,6 +2,129 @@
|
|||||||
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.3.0] - 2015-12-26
|
||||||
|
### Added
|
||||||
|
- New method of keeping track of available budget, see issue #489
|
||||||
|
- Support for Spanish
|
||||||
|
- Firefly III now has an extended demo mode. Will expand further in the future.
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- New favicon
|
||||||
|
- Import routine no longer gives transactions a description #483
|
||||||
|
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- All test data generation code.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Removed import accounts from search results #478
|
||||||
|
- Redirect after delete will no longer go back to deleted item #477
|
||||||
|
- Cannot math #482
|
||||||
|
- Fixed bug in virtual balance field #479
|
||||||
|
|
||||||
|
## [4.2.2] - 2016-12-18
|
||||||
|
### Added
|
||||||
|
- New budget report (still a bit of a beta)
|
||||||
|
- Can now edit user
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- New config for specific events. Still need to build Notifications.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Various bugs
|
||||||
|
- Issue #472 thanks to @zjean
|
||||||
|
|
||||||
|
## [4.2.1] - 2016-12-09
|
||||||
|
### Added
|
||||||
|
- BIC support (see #430)
|
||||||
|
- New category report section and chart (see the general financial report)
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Date range picker now also available on mobile devices (see #435)
|
||||||
|
- Extended range of amounts for issue #439
|
||||||
|
- Rewrote all routes. Old bookmarks may break.
|
||||||
|
|
||||||
|
## [4.2.0] - 2016-11-27
|
||||||
|
### Added
|
||||||
|
- Lots of (empty) tests
|
||||||
|
- Expanded transaction lists (#377)
|
||||||
|
- New charts at account view
|
||||||
|
- First code for #305
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated all email messages.
|
||||||
|
- Made some fonts local
|
||||||
|
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
- Initial release.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Initial release.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Issue #408
|
||||||
|
- Various issues with split journals
|
||||||
|
- Issue #414, thx @zjean
|
||||||
|
- Issue #419, thx @schwalberich
|
||||||
|
- Issue #422, thx @xzaz
|
||||||
|
- Various import bugs, such as #416 (@zjean)
|
||||||
|
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Initial release.
|
||||||
|
|
||||||
|
|
||||||
|
## [4.1.7] - 2016-11-19
|
||||||
|
### Added
|
||||||
|
- Check for database table presence in console commands.
|
||||||
|
- Category report
|
||||||
|
- Reinstated old test routines.
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Confirm account setting is no longer in `.env` file.
|
||||||
|
- Titles are now in reverse (current page > parent > firefly iii)
|
||||||
|
- Easier update of language files thanks to Github implementation.
|
||||||
|
- Uniform colours for charts.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Made all pages more mobile friendly.
|
||||||
|
- Fixed #395 found by @marcoveeneman.
|
||||||
|
- Fixed #398 found by @marcoveeneman.
|
||||||
|
- Fixed #401 found by @marcoveeneman.
|
||||||
|
- Many optimizations.
|
||||||
|
- Updated many libraries.
|
||||||
|
- Various bugs found by myself.
|
||||||
|
|
||||||
|
|
||||||
|
## [4.1.6] - 2016-11-06
|
||||||
|
### Added
|
||||||
|
- New budget table for multi year report.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Greatly expanded help pages and their function.
|
||||||
|
- Built a new transaction collector, which I think was the idea of @roberthorlings originally.
|
||||||
|
- Rebuilt seach engine.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- #375, thanks to @schoentoon which made it impossible to resurrect currencies.
|
||||||
|
- #370 thanks to @ksmolder
|
||||||
|
- #378, thanks to @HomelessAvatar
|
||||||
|
|
||||||
|
## [4.1.5] - 2016-11-01
|
||||||
|
### Changed
|
||||||
|
- Report parts are loaded using AJAX, making a lot of code more simple.
|
||||||
|
- Help content will fall back to English.
|
||||||
|
- Help content is translated through Crowdin.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Issue #370
|
||||||
|
|
||||||
## [4.1.4] - 2016-10-30
|
## [4.1.4] - 2016-10-30
|
||||||
### Added
|
### Added
|
||||||
- New Dockerfile thanks to @schoentoon
|
- New Dockerfile thanks to @schoentoon
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -1,16 +1,24 @@
|
|||||||
# Firefly III [](https://secure.php.net/downloads.php#v7.0.4) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://scrutinizer-ci.com/g/JC5/firefly-iii/?branch=master)
|
# Firefly III: A personal finances manager
|
||||||
|
|
||||||
## 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/JC5/firefly-iii/?branch=master)
|
||||||
|
|
||||||
[](https://i.nder.be/hhfv03hp) [](https://i.nder.be/hhmwmqw9)
|
[](https://travis-ci.org/JC5/firefly-iii) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||||
|
|
||||||
[](https://i.nder.be/g63q05m0) [](https://i.nder.be/c2g30ngg)
|
[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
|
||||||
|
|
||||||
|
[](https://i.nder.be/ccn0u2mp) [](https://i.nder.be/gm8hbh7z)
|
||||||
|
|
||||||
|
_(You can click on the images for a better view)_
|
||||||
|
|
||||||
"Firefly III" is a financial manager. It can help you keep track of expenses, income, budgets and everything in between. It even supports credit cards, shared household accounts and savings accounts! It's pretty fancy. You should use it to save and organise money.
|
"Firefly III" is a financial manager. It can help you keep track of expenses, income, budgets and everything in between. It even supports credit cards, shared household accounts and savings accounts! It's pretty fancy. You should use it to save and organise money.
|
||||||
|
|
||||||
|
## Try it out!
|
||||||
|
|
||||||
|
Try out Firefly III on the [demo site](https://firefly-iii.nder.be/).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://jc5.github.io/firefly-iii/installation-guide/).
|
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/installation-guide/).
|
||||||
|
|
||||||
## More about Firefly III
|
## More about Firefly III
|
||||||
|
|
||||||
@@ -25,6 +33,8 @@ Firefly works on the principle that if you know where you're money is going, you
|
|||||||
- Firefly has lots of features without becoming fancy or bloated.
|
- Firefly has lots of features without becoming fancy or bloated.
|
||||||
- If you feel you're missing something you can just ask me and I'll add it!
|
- If you feel you're missing something you can just ask me and I'll add it!
|
||||||
|
|
||||||
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://jc5.github.io/firefly-iii/).
|
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
|
||||||
|
|
||||||
|
If you 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).
|
||||||
@@ -50,13 +50,10 @@ class CreateImport extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
// find the file
|
|
||||||
/** @var UserRepositoryInterface $userRepository */
|
/** @var UserRepositoryInterface $userRepository */
|
||||||
$userRepository = app(UserRepositoryInterface::class);
|
$userRepository = app(UserRepositoryInterface::class);
|
||||||
$file = $this->argument('file');
|
$file = $this->argument('file');
|
||||||
@@ -69,7 +66,6 @@ class CreateImport extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to parse configuration data:
|
|
||||||
$configurationData = json_decode(file_get_contents($configuration));
|
$configurationData = json_decode(file_get_contents($configuration));
|
||||||
if (is_null($configurationData)) {
|
if (is_null($configurationData)) {
|
||||||
$this->error(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
$this->error(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
||||||
@@ -84,21 +80,17 @@ class CreateImport extends Command
|
|||||||
|
|
||||||
/** @var ImportJobRepositoryInterface $jobRepository */
|
/** @var ImportJobRepositoryInterface $jobRepository */
|
||||||
$jobRepository = app(ImportJobRepositoryInterface::class, [$user]);
|
$jobRepository = app(ImportJobRepositoryInterface::class, [$user]);
|
||||||
|
|
||||||
$job = $jobRepository->create($type);
|
$job = $jobRepository->create($type);
|
||||||
$this->line(sprintf('Created job "%s"...', $job->key));
|
$this->line(sprintf('Created job "%s"...', $job->key));
|
||||||
|
|
||||||
// put the file in the proper place:
|
|
||||||
Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]);
|
Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]);
|
||||||
$this->line('Stored import data...');
|
$this->line('Stored import data...');
|
||||||
|
|
||||||
// store the configuration in the job:
|
|
||||||
$job->configuration = $configurationData;
|
$job->configuration = $configurationData;
|
||||||
$job->status = 'settings_complete';
|
$job->status = 'settings_complete';
|
||||||
$job->save();
|
$job->save();
|
||||||
$this->line('Stored configuration...');
|
$this->line('Stored configuration...');
|
||||||
|
|
||||||
// if user wants to run it, do!
|
|
||||||
if ($this->option('start') === true) {
|
if ($this->option('start') === true) {
|
||||||
$this->line('The import will start in a moment. This process is not visible...');
|
$this->line('The import will start in a moment. This process is not visible...');
|
||||||
Log::debug('Go for import!');
|
Log::debug('Go for import!');
|
||||||
@@ -111,10 +103,10 @@ class CreateImport extends Command
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly.
|
||||||
*/
|
*/
|
||||||
private function validArguments(): bool
|
private function validArguments(): bool
|
||||||
{
|
{
|
||||||
// find the file
|
|
||||||
/** @var UserRepositoryInterface $userRepository */
|
/** @var UserRepositoryInterface $userRepository */
|
||||||
$userRepository = app(UserRepositoryInterface::class);
|
$userRepository = app(UserRepositoryInterface::class);
|
||||||
$file = $this->argument('file');
|
$file = $this->argument('file');
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use FireflyIII\Import\Logging\CommandHandler;
|
|||||||
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 Log;
|
use Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,9 +52,7 @@ class Import extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
|
||||||
*
|
*
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
@@ -66,37 +65,20 @@ class Import extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->line('Going to import job with key "' . $job->key . '" of type ' . $job->file_type);
|
$this->line(sprintf('Going to import job with key "%s" of type "%s"', $job->key, $job->file_type));
|
||||||
|
|
||||||
$monolog = Log::getMonolog();
|
$monolog = Log::getMonolog();
|
||||||
$handler = new CommandHandler($this);
|
$handler = new CommandHandler($this);
|
||||||
$monolog->pushHandler($handler);
|
$monolog->pushHandler($handler);
|
||||||
|
$importProcedure = new ImportProcedure;
|
||||||
|
$result = $importProcedure->runImport($job);
|
||||||
|
|
||||||
$result = ImportProcedure::runImport($job);
|
// display result to user:
|
||||||
|
$this->presentResults($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));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->line('The import has completed.');
|
$this->line('The import has completed.');
|
||||||
|
|
||||||
// get any errors from the importer:
|
// get any errors from the importer:
|
||||||
$extendedStatus = $job->extended_status;
|
$this->presentErrors($job);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -122,4 +104,36 @@ 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
app/Console/Commands/MoveRepository.php
Normal file
86
app/Console/Commands/MoveRepository.php
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MoveRepository.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Console\Commands;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MoveRepository
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Console\Commands
|
||||||
|
*/
|
||||||
|
class MoveRepository extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Alerts the user that the Github repository will move, if they are interested to know this.';
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'firefly:github-move';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$moveDate = new Carbon('2017-01-01');
|
||||||
|
$final = new Carbon('2017-03-01');
|
||||||
|
$now = new Carbon;
|
||||||
|
|
||||||
|
// display message before 2017-01-01
|
||||||
|
if ($moveDate > $now) {
|
||||||
|
$this->line('+------------------------------------------------------------------------------+');
|
||||||
|
$this->line('');
|
||||||
|
$this->line('The Github repository for Firefly III will MOVE');
|
||||||
|
$this->line('This move will be on January 1st 2017');
|
||||||
|
$this->line('');
|
||||||
|
$this->error('READ THIS WIKI PAGE FOR MORE INFORMATION');
|
||||||
|
$this->line('');
|
||||||
|
$this->info('https://github.com/firefly-iii/help/wiki/New-Github-repository');
|
||||||
|
$this->line('');
|
||||||
|
$this->line('+------------------------------------------------------------------------------+');
|
||||||
|
}
|
||||||
|
|
||||||
|
// display message after 2017-01-01 but before 2017-03-01
|
||||||
|
if ($moveDate <= $now && $now <= $final) {
|
||||||
|
$this->line('+------------------------------------------------------------------------------+');
|
||||||
|
$this->line('');
|
||||||
|
$this->line('The Github repository for Firefly III has MOVED');
|
||||||
|
$this->line('This move was on January 1st 2017!');
|
||||||
|
$this->line('');
|
||||||
|
$this->error('READ THIS WIKI PAGE FOR MORE INFORMATION');
|
||||||
|
$this->line('');
|
||||||
|
$this->info('https://github.com/firefly-iii/help/wiki/New-Github-repository');
|
||||||
|
$this->line('');
|
||||||
|
$this->line('+------------------------------------------------------------------------------+');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,42 +60,26 @@ class ScanAttachments extends Command
|
|||||||
/** @var Attachment $attachment */
|
/** @var Attachment $attachment */
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
$fileName = $attachment->fileName();
|
$fileName = $attachment->fileName();
|
||||||
|
|
||||||
// try to grab file content:
|
|
||||||
try {
|
try {
|
||||||
$content = $disk->get($fileName);
|
$content = $disk->get($fileName);
|
||||||
} catch (FileNotFoundException $e) {
|
} catch (FileNotFoundException $e) {
|
||||||
$this->error(sprintf('Could not find data for attachment #%d', $attachment->id));
|
$this->error(sprintf('Could not find data for attachment #%d', $attachment->id));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// try to decrypt content.
|
|
||||||
try {
|
try {
|
||||||
$decrypted = Crypt::decrypt($content);
|
$decrypted = Crypt::decrypt($content);
|
||||||
} catch (DecryptException $e) {
|
} catch (DecryptException $e) {
|
||||||
$this->error(sprintf('Could not decrypt data of attachment #%d', $attachment->id));
|
$this->error(sprintf('Could not decrypt data of attachment #%d', $attachment->id));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make temp file:
|
|
||||||
$tmpfname = tempnam(sys_get_temp_dir(), 'FireflyIII');
|
$tmpfname = tempnam(sys_get_temp_dir(), 'FireflyIII');
|
||||||
|
|
||||||
// store content in temp file:
|
|
||||||
file_put_contents($tmpfname, $decrypted);
|
file_put_contents($tmpfname, $decrypted);
|
||||||
|
|
||||||
// get md5 and mime
|
|
||||||
$md5 = md5_file($tmpfname);
|
$md5 = md5_file($tmpfname);
|
||||||
$mime = mime_content_type($tmpfname);
|
$mime = mime_content_type($tmpfname);
|
||||||
|
|
||||||
// update attachment:
|
|
||||||
$attachment->md5 = $md5;
|
$attachment->md5 = $md5;
|
||||||
$attachment->mime = $mime;
|
$attachment->mime = $mime;
|
||||||
$attachment->save();
|
$attachment->save();
|
||||||
|
|
||||||
|
|
||||||
$this->line(sprintf('Fixed attachment #%d', $attachment->id));
|
$this->line(sprintf('Fixed attachment #%d', $attachment->id));
|
||||||
|
|
||||||
// find file:
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use FireflyIII\Models\TransactionJournal;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Log;
|
use Log;
|
||||||
|
use Schema;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class UpgradeDatabase
|
* Class UpgradeDatabase
|
||||||
@@ -65,8 +66,13 @@ class UpgradeDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
private function setTransactionIdentifier()
|
private function setTransactionIdentifier()
|
||||||
{
|
{
|
||||||
$subQuery = TransactionJournal
|
// if table does not exist, return false
|
||||||
::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
if (!Schema::hasTable('transaction_journals')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
->whereNull('transaction_journals.deleted_at')
|
->whereNull('transaction_journals.deleted_at')
|
||||||
->whereNull('transactions.deleted_at')
|
->whereNull('transactions.deleted_at')
|
||||||
->groupBy(['transaction_journals.id'])
|
->groupBy(['transaction_journals.id'])
|
||||||
@@ -91,15 +97,14 @@ class UpgradeDatabase extends Command
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
/** @var Transaction $opposing */
|
/** @var Transaction $opposing */
|
||||||
$opposing = Transaction
|
$opposing = Transaction::where('transaction_journal_id', $journalId)
|
||||||
::where('transaction_journal_id', $journalId)
|
|
||||||
->where('amount', $amount)->where('identifier', '=', 0)
|
->where('amount', $amount)->where('identifier', '=', 0)
|
||||||
->whereNotIn('id', $processed)
|
->whereNotIn('id', $processed)
|
||||||
->first();
|
->first();
|
||||||
} catch (QueryException $e) {
|
} catch (QueryException $e) {
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
|
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
|
||||||
$this->error('This field is required for Firefly III version ' . config('firefly.version') . ' to run.');
|
$this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
|
||||||
$this->error('Please run "php artisan migrate" to add this field to the table.');
|
$this->error('Please run "php artisan migrate" to add this field to the table.');
|
||||||
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
|
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
|
||||||
break 2;
|
break 2;
|
||||||
|
|||||||
@@ -63,21 +63,20 @@ class UpgradeFireflyInstructions extends Command
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->line('+------------------------------------------------------------------------------+');
|
|
||||||
$this->line('');
|
|
||||||
|
|
||||||
if (is_null($text)) {
|
if (is_null($text)) {
|
||||||
$this->line('Thank you for installing Firefly III, v' . $version);
|
$this->line(sprintf('Thank you for installing Firefly III, v%s', $version));
|
||||||
$this->info('There are no extra upgrade instructions.');
|
$this->info('There are no extra upgrade instructions.');
|
||||||
$this->line('Firefly III should be ready for use.');
|
$this->line('Firefly III should be ready for use.');
|
||||||
} else {
|
} else {
|
||||||
$this->line('Thank you for installing Firefly III, v' . $version);
|
$this->line('+------------------------------------------------------------------------------+');
|
||||||
$this->line('If you are upgrading from a previous version,');
|
$this->line('');
|
||||||
$this->line('please follow these upgrade instructions carefully:');
|
$this->line(sprintf('Thank you for installing Firefly III, v%s', $version));
|
||||||
$this->info(wordwrap($text));
|
$this->info(wordwrap($text));
|
||||||
}
|
|
||||||
|
|
||||||
$this->line('');
|
$this->line('');
|
||||||
$this->line('+------------------------------------------------------------------------------+');
|
$this->line('+------------------------------------------------------------------------------+');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ use Crypt;
|
|||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\Category;
|
|
||||||
use FireflyIII\Models\Tag;
|
|
||||||
use FireflyIII\Models\Transaction;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Schema;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,16 +61,21 @@ class VerifyDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
// if table does not exist, return false
|
||||||
|
if (!Schema::hasTable('users')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reportObject('budget');
|
||||||
|
$this->reportObject('category');
|
||||||
|
$this->reportObject('tag');
|
||||||
|
|
||||||
// accounts with no transactions.
|
// accounts with no transactions.
|
||||||
$this->reportAccounts();
|
$this->reportAccounts();
|
||||||
// budgets with no limits
|
// budgets with no limits
|
||||||
$this->reportBudgetLimits();
|
$this->reportBudgetLimits();
|
||||||
// budgets with no transactions
|
// budgets with no transactions
|
||||||
$this->reportBudgets();
|
|
||||||
// categories with no transactions
|
|
||||||
$this->reportCategories();
|
|
||||||
// tags with no transactions
|
|
||||||
$this->reportTags();
|
|
||||||
// sum of transactions is not zero.
|
// sum of transactions is not zero.
|
||||||
$this->reportSum();
|
$this->reportSum();
|
||||||
// any deleted transaction journals that have transactions that are NOT deleted:
|
// any deleted transaction journals that have transactions that are NOT deleted:
|
||||||
@@ -95,8 +100,7 @@ class VerifyDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
private function reportAccounts()
|
private function reportAccounts()
|
||||||
{
|
{
|
||||||
$set = Account
|
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||||
::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
|
||||||
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
|
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
|
||||||
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||||
->whereNull('transactions.account_id')
|
->whereNull('transactions.account_id')
|
||||||
@@ -118,59 +122,19 @@ class VerifyDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
private function reportBudgetLimits()
|
private function reportBudgetLimits()
|
||||||
{
|
{
|
||||||
$set = Budget
|
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
|
||||||
::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
|
|
||||||
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
||||||
->groupBy(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email'])
|
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
|
||||||
->whereNull('budget_limits.id')
|
->whereNull('budget_limits.id')
|
||||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']);
|
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']);
|
||||||
|
|
||||||
/** @var stdClass $entry */
|
/** @var Budget $entry */
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has budget #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
|
$name = $entry->encrypted ? Crypt::decrypt($entry->name) : $entry->name;
|
||||||
. '") which has no budget limits.';
|
$line = sprintf(
|
||||||
$this->line($line);
|
'Notice: User #%d (%s) has budget #%d ("%s") which has no budget limits.',
|
||||||
}
|
$entry->user_id, $entry->email, $entry->id, $name
|
||||||
}
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports on budgets without any transactions.
|
|
||||||
*/
|
|
||||||
private function reportBudgets()
|
|
||||||
{
|
|
||||||
$set = Budget
|
|
||||||
::leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
|
|
||||||
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
|
||||||
->distinct()
|
|
||||||
->whereNull('budget_transaction_journal.budget_id')
|
|
||||||
->whereNull('budgets.deleted_at')
|
|
||||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']);
|
|
||||||
|
|
||||||
/** @var stdClass $entry */
|
|
||||||
foreach ($set as $entry) {
|
|
||||||
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has budget #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
|
|
||||||
. '") which has no transactions.';
|
|
||||||
$this->line($line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports on categories without any transactions.
|
|
||||||
*/
|
|
||||||
private function reportCategories()
|
|
||||||
{
|
|
||||||
$set = Category
|
|
||||||
::leftJoin('category_transaction_journal', 'categories.id', '=', 'category_transaction_journal.category_id')
|
|
||||||
->leftJoin('users', 'categories.user_id', '=', 'users.id')
|
|
||||||
->distinct()
|
|
||||||
->whereNull('category_transaction_journal.category_id')
|
|
||||||
->whereNull('categories.deleted_at')
|
|
||||||
->get(['categories.id', 'categories.name', 'categories.user_id', 'users.email']);
|
|
||||||
|
|
||||||
/** @var stdClass $entry */
|
|
||||||
foreach ($set as $entry) {
|
|
||||||
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has category #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
|
|
||||||
. '") which has no transactions.';
|
|
||||||
$this->line($line);
|
$this->line($line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,8 +144,7 @@ class VerifyDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
private function reportDeletedAccounts()
|
private function reportDeletedAccounts()
|
||||||
{
|
{
|
||||||
$set = Account
|
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||||
::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
|
||||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||||
->whereNotNull('accounts.deleted_at')
|
->whereNotNull('accounts.deleted_at')
|
||||||
->whereNotNull('transactions.id')
|
->whereNotNull('transactions.id')
|
||||||
@@ -219,8 +182,7 @@ class VerifyDatabase extends Command
|
|||||||
TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE],
|
TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE],
|
||||||
];
|
];
|
||||||
foreach ($configuration as $transactionType => $accountTypes) {
|
foreach ($configuration as $transactionType => $accountTypes) {
|
||||||
$set = TransactionJournal
|
$set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
|
||||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||||
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
|
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
|
||||||
@@ -228,7 +190,10 @@ class VerifyDatabase extends Command
|
|||||||
->where('transaction_types.type', $transactionType)
|
->where('transaction_types.type', $transactionType)
|
||||||
->whereIn('account_types.type', $accountTypes)
|
->whereIn('account_types.type', $accountTypes)
|
||||||
->whereNull('transaction_journals.deleted_at')
|
->whereNull('transaction_journals.deleted_at')
|
||||||
->get(['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type', 'transaction_types.type']);
|
->get(
|
||||||
|
['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type',
|
||||||
|
'transaction_types.type']
|
||||||
|
);
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
$this->error(
|
$this->error(
|
||||||
sprintf(
|
sprintf(
|
||||||
@@ -250,8 +215,7 @@ class VerifyDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
private function reportJournals()
|
private function reportJournals()
|
||||||
{
|
{
|
||||||
$set = TransactionJournal
|
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
|
||||||
->whereNotNull('transaction_journals.deleted_at')// USE THIS
|
->whereNotNull('transaction_journals.deleted_at')// USE THIS
|
||||||
->whereNull('transactions.deleted_at')
|
->whereNull('transactions.deleted_at')
|
||||||
->whereNotNull('transactions.id')
|
->whereNotNull('transactions.id')
|
||||||
@@ -277,8 +241,7 @@ class VerifyDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
private function reportNoTransactions()
|
private function reportNoTransactions()
|
||||||
{
|
{
|
||||||
$set = TransactionJournal
|
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
|
||||||
->groupBy('transaction_journals.id')
|
->groupBy('transaction_journals.id')
|
||||||
->whereNull('transactions.transaction_journal_id')
|
->whereNull('transactions.transaction_journal_id')
|
||||||
->get(['transaction_journals.id']);
|
->get(['transaction_journals.id']);
|
||||||
@@ -291,6 +254,39 @@ class VerifyDatabase extends Command
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
*/
|
||||||
|
private function reportObject(string $name)
|
||||||
|
{
|
||||||
|
$plural = str_plural($name);
|
||||||
|
$class = sprintf('FireflyIII\Models\%s', ucfirst($name));
|
||||||
|
$field = $name == 'tag' ? 'tag' : 'name';
|
||||||
|
$set = $class::leftJoin($name . '_transaction_journal', $plural . '.id', '=', $name . '_transaction_journal.' . $name . '_id')
|
||||||
|
->leftJoin('users', $plural . '.user_id', '=', 'users.id')
|
||||||
|
->distinct()
|
||||||
|
->whereNull($name . '_transaction_journal.' . $name . '_id')
|
||||||
|
->whereNull($plural . '.deleted_at')
|
||||||
|
->get([$plural . '.id', $plural . '.' . $field . ' as name', $plural . '.user_id', 'users.email']);
|
||||||
|
|
||||||
|
/** @var stdClass $entry */
|
||||||
|
foreach ($set as $entry) {
|
||||||
|
|
||||||
|
$objName = $entry->name;
|
||||||
|
try {
|
||||||
|
$objName = Crypt::decrypt($objName);
|
||||||
|
} catch (DecryptException $e) {
|
||||||
|
// it probably was not encrypted.
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = sprintf(
|
||||||
|
'Notice: User #%d (%s) has %s #%d ("%s") which has no transactions.',
|
||||||
|
$entry->user_id, $entry->email, $name, $entry->id, $objName
|
||||||
|
);
|
||||||
|
$this->line($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reports for each user when the sum of their transactions is not zero.
|
* Reports for each user when the sum of their transactions is not zero.
|
||||||
*/
|
*/
|
||||||
@@ -308,34 +304,12 @@ class VerifyDatabase extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports on tags without any transactions.
|
|
||||||
*/
|
|
||||||
private function reportTags()
|
|
||||||
{
|
|
||||||
$set = Tag
|
|
||||||
::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
|
|
||||||
->leftJoin('users', 'tags.user_id', '=', 'users.id')
|
|
||||||
->distinct()
|
|
||||||
->whereNull('tag_transaction_journal.tag_id')
|
|
||||||
->whereNull('tags.deleted_at')
|
|
||||||
->get(['tags.id', 'tags.tag', 'tags.user_id', 'users.email']);
|
|
||||||
|
|
||||||
/** @var stdClass $entry */
|
|
||||||
foreach ($set as $entry) {
|
|
||||||
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has tag #' . $entry->id . ' ("' . $entry->tag
|
|
||||||
. '") which has no transactions.';
|
|
||||||
$this->line($line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reports on deleted transactions that are connected to a not deleted journal.
|
* Reports on deleted transactions that are connected to a not deleted journal.
|
||||||
*/
|
*/
|
||||||
private function reportTransactions()
|
private function reportTransactions()
|
||||||
{
|
{
|
||||||
$set = Transaction
|
$set = Transaction::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
|
||||||
->whereNotNull('transactions.deleted_at')
|
->whereNotNull('transactions.deleted_at')
|
||||||
->whereNull('transaction_journals.deleted_at')
|
->whereNull('transaction_journals.deleted_at')
|
||||||
->get(
|
->get(
|
||||||
@@ -356,8 +330,7 @@ class VerifyDatabase extends Command
|
|||||||
*/
|
*/
|
||||||
private function reportTransfersBudgets()
|
private function reportTransfersBudgets()
|
||||||
{
|
{
|
||||||
$set = TransactionJournal
|
$set = TransactionJournal::distinct()
|
||||||
::distinct()
|
|
||||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||||
->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
|
->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
|
||||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||||
|
|||||||
3
app/Console/Kernel.php
Executable file → Normal file
3
app/Console/Kernel.php
Executable file → Normal file
@@ -16,6 +16,7 @@ namespace FireflyIII\Console;
|
|||||||
use FireflyIII\Console\Commands\CreateImport;
|
use FireflyIII\Console\Commands\CreateImport;
|
||||||
use FireflyIII\Console\Commands\EncryptFile;
|
use FireflyIII\Console\Commands\EncryptFile;
|
||||||
use FireflyIII\Console\Commands\Import;
|
use FireflyIII\Console\Commands\Import;
|
||||||
|
use FireflyIII\Console\Commands\MoveRepository;
|
||||||
use FireflyIII\Console\Commands\ScanAttachments;
|
use FireflyIII\Console\Commands\ScanAttachments;
|
||||||
use FireflyIII\Console\Commands\UpgradeDatabase;
|
use FireflyIII\Console\Commands\UpgradeDatabase;
|
||||||
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
|
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
|
||||||
@@ -63,7 +64,7 @@ class Kernel extends ConsoleKernel
|
|||||||
EncryptFile::class,
|
EncryptFile::class,
|
||||||
ScanAttachments::class,
|
ScanAttachments::class,
|
||||||
UpgradeDatabase::class,
|
UpgradeDatabase::class,
|
||||||
|
MoveRepository::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
41
app/Events/BlockedBadLogin.php
Normal file
41
app/Events/BlockedBadLogin.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* BlockedBadLogin.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LockedOutUser
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Events
|
||||||
|
*/
|
||||||
|
class BlockedBadLogin extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $email;
|
||||||
|
public $ipAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance. This event is triggered when a user gets themselves locked out.
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
* @param string $ipAddress
|
||||||
|
*/
|
||||||
|
public function __construct(string $email, string $ipAddress)
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
$this->ipAddress = $ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Events/BlockedUseOfDomain.php
Normal file
42
app/Events/BlockedUseOfDomain.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* BlockedUseOfDomain.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BlockedUseOfDomain
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Events
|
||||||
|
*/
|
||||||
|
class BlockedUseOfDomain extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $email;
|
||||||
|
public $ipAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance. This event is triggered when a user tries to register with a banned domain (on blocked domain list).
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
* @param string $ipAddress
|
||||||
|
*/
|
||||||
|
public function __construct(string $email, string $ipAddress)
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
$this->ipAddress = $ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Events/BlockedUseOfEmail.php
Normal file
42
app/Events/BlockedUseOfEmail.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* BlockedUseOfEmail.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BlockedUseOfEmail
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Events
|
||||||
|
*/
|
||||||
|
class BlockedUseOfEmail extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $email;
|
||||||
|
public $ipAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance. This event is triggered when a user tries to register with a banned email address (already used before).
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
* @param string $ipAddress
|
||||||
|
*/
|
||||||
|
public function __construct(string $email, string $ipAddress)
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
$this->ipAddress = $ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Events/BlockedUserLogin.php
Normal file
42
app/Events/BlockedUserLogin.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* BlockedUserLogin.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BlockedUserLogin
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Events
|
||||||
|
*/
|
||||||
|
class BlockedUserLogin extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $ipAddress;
|
||||||
|
public $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance. This event is triggered when a blocked user logs in.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @param string $ipAddress
|
||||||
|
*/
|
||||||
|
public function __construct(User $user, string $ipAddress)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->ipAddress = $ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Events/DeletedUser.php
Normal file
38
app/Events/DeletedUser.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* DeletedUser.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class DeletedUser
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Events
|
||||||
|
*/
|
||||||
|
class DeletedUser extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance. This event is triggered when a user deletes themselves.
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
*/
|
||||||
|
public function __construct(string $email)
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Events/LockedOutUser.php
Normal file
41
app/Events/LockedOutUser.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* LockedOutUser.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LockedOutUser
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Events
|
||||||
|
*/
|
||||||
|
class LockedOutUser extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $email;
|
||||||
|
public $ipAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance. This event is triggered when a user gets themselves locked out.
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
* @param string $ipAddress
|
||||||
|
*/
|
||||||
|
public function __construct(string $email, string $ipAddress)
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
$this->ipAddress = $ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
app/Events/RequestedNewPassword.php
Normal file
46
app/Events/RequestedNewPassword.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RequestedNewPassword.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Events;
|
||||||
|
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RequestedNewPassword
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Events
|
||||||
|
*/
|
||||||
|
class RequestedNewPassword extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $ipAddress;
|
||||||
|
public $token;
|
||||||
|
public $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance. This event is triggered when a users tries to reset his or her password.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @param string $token
|
||||||
|
* @param string $ipAddress
|
||||||
|
*/
|
||||||
|
public function __construct(User $user, string $token, string $ipAddress)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->token = $token;
|
||||||
|
$this->ipAddress = $ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
3
app/Exceptions/Handler.php
Executable file → Normal file
3
app/Exceptions/Handler.php
Executable file → Normal file
@@ -77,7 +77,8 @@ class Handler extends ExceptionHandler
|
|||||||
*/
|
*/
|
||||||
public function report(Exception $exception)
|
public function report(Exception $exception)
|
||||||
{
|
{
|
||||||
if ($exception instanceof FireflyException || $exception instanceof ErrorException) {
|
$doMailError = env('SEND_ERROR_MESSAGE', true);
|
||||||
|
if (($exception instanceof FireflyException || $exception instanceof ErrorException) && $doMailError) {
|
||||||
$userData = [
|
$userData = [
|
||||||
'id' => 0,
|
'id' => 0,
|
||||||
'email' => 'unknown@example.com',
|
'email' => 'unknown@example.com',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* JournalCollector.php
|
* JournalExportCollector.php
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
*
|
*
|
||||||
* This software may be modified and distributed under the terms of the
|
* This software may be modified and distributed under the terms of the
|
||||||
@@ -21,11 +21,11 @@ use Illuminate\Database\Query\JoinClause;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class JournalCollector
|
* Class JournalExportCollector
|
||||||
*
|
*
|
||||||
* @package FireflyIII\Export\Collector
|
* @package FireflyIII\Export\Collector
|
||||||
*/
|
*/
|
||||||
class JournalCollector extends BasicCollector implements CollectorInterface
|
class JournalExportCollector extends BasicCollector implements CollectorInterface
|
||||||
{
|
{
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
private $accounts;
|
private $accounts;
|
||||||
@@ -292,13 +292,12 @@ class JournalCollector extends BasicCollector implements CollectorInterface
|
|||||||
private function getWorkSet()
|
private function getWorkSet()
|
||||||
{
|
{
|
||||||
$accountIds = $this->accounts->pluck('id')->toArray();
|
$accountIds = $this->accounts->pluck('id')->toArray();
|
||||||
$this->workSet = Transaction
|
$this->workSet = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||||
::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
|
||||||
->leftJoin(
|
->leftJoin(
|
||||||
'transactions AS opposing', function (JoinClause $join) {
|
'transactions AS opposing', function (JoinClause $join) {
|
||||||
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
|
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
|
||||||
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
|
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
|
||||||
->where('transactions.identifier', '=', 'opposing.identifier');
|
->where('transactions.identifier', '=', DB::raw('opposing.identifier'));
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||||
@@ -94,7 +94,7 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function collectVintageUploads():bool
|
private function collectVintageUploads(): bool
|
||||||
{
|
{
|
||||||
// grab upload directory.
|
// grab upload directory.
|
||||||
$files = $this->uploadDisk->files();
|
$files = $this->uploadDisk->files();
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Export\Exporter;
|
namespace FireflyIII\Export\Exporter;
|
||||||
|
|
||||||
use FireflyIII\Export\Entry\Entry;
|
use FireflyIII\Export\Entry\Entry;
|
||||||
use FireflyIII\Export\Entry\EntryAccount;
|
|
||||||
use FireflyIII\Models\ExportJob;
|
use FireflyIII\Models\ExportJob;
|
||||||
use League\Csv\Writer;
|
use League\Csv\Writer;
|
||||||
use SplFileObject;
|
use SplFileObject;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace FireflyIII\Export;
|
|||||||
|
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Export\Collector\AttachmentCollector;
|
use FireflyIII\Export\Collector\AttachmentCollector;
|
||||||
use FireflyIII\Export\Collector\JournalCollector;
|
use FireflyIII\Export\Collector\JournalExportCollector;
|
||||||
use FireflyIII\Export\Collector\UploadCollector;
|
use FireflyIII\Export\Collector\UploadCollector;
|
||||||
use FireflyIII\Export\Entry\Entry;
|
use FireflyIII\Export\Entry\Entry;
|
||||||
use FireflyIII\Models\ExportJob;
|
use FireflyIII\Models\ExportJob;
|
||||||
@@ -30,7 +30,7 @@ use ZipArchive;
|
|||||||
*
|
*
|
||||||
* @package FireflyIII\Export
|
* @package FireflyIII\Export
|
||||||
*/
|
*/
|
||||||
class Processor
|
class Processor implements ProcessorInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
@@ -45,8 +45,6 @@ class Processor
|
|||||||
public $job;
|
public $job;
|
||||||
/** @var array */
|
/** @var array */
|
||||||
public $settings;
|
public $settings;
|
||||||
/** @var \FireflyIII\Export\ConfigurationFile */
|
|
||||||
private $configurationMaker;
|
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
private $exportEntries;
|
private $exportEntries;
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
@@ -93,8 +91,8 @@ class Processor
|
|||||||
*/
|
*/
|
||||||
public function collectJournals(): bool
|
public function collectJournals(): bool
|
||||||
{
|
{
|
||||||
/** @var JournalCollector $collector */
|
/** @var JournalExportCollector $collector */
|
||||||
$collector = app(JournalCollector::class, [$this->job]);
|
$collector = app(JournalExportCollector::class, [$this->job]);
|
||||||
$collector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
$collector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||||
$collector->setAccounts($this->settings['accounts']);
|
$collector->setAccounts($this->settings['accounts']);
|
||||||
$collector->run();
|
$collector->run();
|
||||||
|
|||||||
67
app/Export/ProcessorInterface.php
Normal file
67
app/Export/ProcessorInterface.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ProcessorInterface.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Export;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface ProcessorInterface
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Export
|
||||||
|
*/
|
||||||
|
interface ProcessorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor constructor.
|
||||||
|
*
|
||||||
|
* @param array $settings
|
||||||
|
*/
|
||||||
|
public function __construct(array $settings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function collectAttachments(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function collectJournals(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function collectOldUploads(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function convertJournals(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function createZipFile(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function exportJournals(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getFiles(): Collection;
|
||||||
|
}
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* AccountChartGeneratorInterface.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Generator\Chart\Account;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use FireflyIII\Models\Account;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface AccountChartGeneratorInterface
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Account
|
|
||||||
*/
|
|
||||||
interface AccountChartGeneratorInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function expenseAccounts(Collection $accounts, Carbon $start, Carbon $end): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(Collection $accounts, Carbon $start, Carbon $end): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Account $account
|
|
||||||
* @param array $labels
|
|
||||||
* @param array $dataSet
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function single(Account $account, array $labels, array $dataSet): array;
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ChartJsAccountChartGenerator.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
namespace FireflyIII\Generator\Chart\Account;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use FireflyIII\Models\Account;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ChartJsAccountChartGenerator
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Account
|
|
||||||
*/
|
|
||||||
class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function expenseAccounts(Collection $accounts, Carbon $start, Carbon $end): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 1,
|
|
||||||
'labels' => [], 'datasets' => [[
|
|
||||||
'label' => trans('firefly.spent'),
|
|
||||||
'data' => []]]];
|
|
||||||
foreach ($accounts as $account) {
|
|
||||||
if ($account->difference > 0) {
|
|
||||||
$data['labels'][] = $account->name;
|
|
||||||
$data['datasets'][0]['data'][] = $account->difference;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(Collection $accounts, Carbon $start, Carbon $end): array
|
|
||||||
{
|
|
||||||
// language:
|
|
||||||
$format = (string)trans('config.month_and_day');
|
|
||||||
$data = ['count' => 0, 'labels' => [], 'datasets' => [],];
|
|
||||||
$current = clone $start;
|
|
||||||
while ($current <= $end) {
|
|
||||||
$data['labels'][] = $current->formatLocalized($format);
|
|
||||||
$current->addDay();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
|
||||||
$data['datasets'][] = [
|
|
||||||
'label' => $account->name,
|
|
||||||
'fillColor' => 'rgba(220,220,220,0.2)',
|
|
||||||
'strokeColor' => 'rgba(220,220,220,1)',
|
|
||||||
'pointColor' => 'rgba(220,220,220,1)',
|
|
||||||
'pointStrokeColor' => '#fff',
|
|
||||||
'pointHighlightFill' => '#fff',
|
|
||||||
'pointHighlightStroke' => 'rgba(220,220,220,1)',
|
|
||||||
'data' => $account->balances,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$data['count'] = count($data['datasets']);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 1,
|
|
||||||
'labels' => [], 'datasets' => [[
|
|
||||||
'label' => trans('firefly.earned'),
|
|
||||||
'data' => []]]];
|
|
||||||
foreach ($accounts as $account) {
|
|
||||||
if ($account->difference > 0) {
|
|
||||||
$data['labels'][] = $account->name;
|
|
||||||
$data['datasets'][0]['data'][] = $account->difference;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Account $account
|
|
||||||
* @param array $labels
|
|
||||||
* @param array $dataSet
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function single(Account $account, array $labels, array $dataSet): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 1,
|
|
||||||
'labels' => $labels,
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => $account->name,
|
|
||||||
'data' => $dataSet,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
149
app/Generator/Chart/Basic/ChartJsGenerator.php
Normal file
149
app/Generator/Chart/Basic/ChartJsGenerator.php
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ChartJsGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Chart\Basic;
|
||||||
|
|
||||||
|
use FireflyIII\Support\ChartColour;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ChartJsGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Chart\Basic
|
||||||
|
*/
|
||||||
|
class ChartJsGenerator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will generate a Chart JS compatible array from the given input. Expects this format
|
||||||
|
*
|
||||||
|
* Will take labels for all from first set.
|
||||||
|
*
|
||||||
|
* 0: [
|
||||||
|
* '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' =>
|
||||||
|
* [
|
||||||
|
* 'label-of-entry' => 'value'
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
* 1: [
|
||||||
|
* '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' =>
|
||||||
|
* [
|
||||||
|
* 'label-of-entry' => 'value'
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function multiSet(array $data): array
|
||||||
|
{
|
||||||
|
reset($data);
|
||||||
|
$first = current($data);
|
||||||
|
$labels = array_keys($first['entries']);
|
||||||
|
|
||||||
|
$chartData = [
|
||||||
|
'count' => count($data),
|
||||||
|
'labels' => $labels, // take ALL labels from the first set.
|
||||||
|
'datasets' => [],
|
||||||
|
];
|
||||||
|
unset($first, $labels);
|
||||||
|
|
||||||
|
foreach ($data as $set) {
|
||||||
|
$currentSet = [
|
||||||
|
'label' => $set['label'],
|
||||||
|
'type' => $set['type'] ?? 'line',
|
||||||
|
'data' => array_values($set['entries']),
|
||||||
|
];
|
||||||
|
if (isset($set['yAxisID'])) {
|
||||||
|
$currentSet['yAxisID'] = $set['yAxisID'];
|
||||||
|
}
|
||||||
|
if (isset($set['fill'])) {
|
||||||
|
$currentSet['fill'] = $set['fill'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$chartData['datasets'][] = $currentSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chartData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expects data as:
|
||||||
|
*
|
||||||
|
* key => value
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function pieChart(array $data): array
|
||||||
|
{
|
||||||
|
$chartData = [
|
||||||
|
'datasets' => [
|
||||||
|
0 => [],
|
||||||
|
],
|
||||||
|
'labels' => [],
|
||||||
|
];
|
||||||
|
$index = 0;
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
|
||||||
|
// make larger than 0
|
||||||
|
if (bccomp($value, '0') === -1) {
|
||||||
|
$value = bcmul($value, '-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
$chartData['datasets'][0]['data'][] = round($value, 2);
|
||||||
|
$chartData['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index);
|
||||||
|
$chartData['labels'][] = $key;
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chartData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will generate a (ChartJS) compatible array from the given input. Expects this format:
|
||||||
|
*
|
||||||
|
* 'label-of-entry' => value
|
||||||
|
* 'label-of-entry' => value
|
||||||
|
*
|
||||||
|
* @param string $setLabel
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function singleSet(string $setLabel, array $data): array
|
||||||
|
{
|
||||||
|
$chartData = [
|
||||||
|
'count' => 1,
|
||||||
|
'labels' => array_keys($data), // take ALL labels from the first set.
|
||||||
|
'datasets' => [
|
||||||
|
[
|
||||||
|
'label' => $setLabel,
|
||||||
|
'data' => array_values($data),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $chartData;
|
||||||
|
}
|
||||||
|
}
|
||||||
73
app/Generator/Chart/Basic/GeneratorInterface.php
Normal file
73
app/Generator/Chart/Basic/GeneratorInterface.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GeneratorInterface.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Chart\Basic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface GeneratorInterface
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Chart\Basic
|
||||||
|
*/
|
||||||
|
interface GeneratorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will generate a (ChartJS) compatible array from the given input. Expects this format:
|
||||||
|
*
|
||||||
|
* 0: [
|
||||||
|
* 'label' => 'label of set',
|
||||||
|
* 'entries' =>
|
||||||
|
* [
|
||||||
|
* 'label-of-entry' => 'value'
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
* 1: [
|
||||||
|
* 'label' => 'label of another set',
|
||||||
|
* 'entries' =>
|
||||||
|
* [
|
||||||
|
* 'label-of-entry' => 'value'
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function multiSet(array $data): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expects data as:
|
||||||
|
*
|
||||||
|
* key => value
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function pieChart(array $data): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will generate a (ChartJS) compatible array from the given input. Expects this format:
|
||||||
|
*
|
||||||
|
* 'label-of-entry' => value
|
||||||
|
* 'label-of-entry' => value
|
||||||
|
*
|
||||||
|
* @param string $setLabel
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function singleSet(string $setLabel, array $data): array;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* BillChartGeneratorInterface.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Generator\Chart\Bill;
|
|
||||||
|
|
||||||
|
|
||||||
use FireflyIII\Models\Bill;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface BillChartGeneratorInterface
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Bill
|
|
||||||
*/
|
|
||||||
interface BillChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $paid
|
|
||||||
* @param string $unpaid
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(string $paid, string $unpaid): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Bill $bill
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function single(Bill $bill, Collection $entries): array;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ChartJsBillChartGenerator.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Generator\Chart\Bill;
|
|
||||||
|
|
||||||
use FireflyIII\Models\Bill;
|
|
||||||
use FireflyIII\Models\TransactionJournal;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ChartJsBillChartGenerator
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Bill
|
|
||||||
*/
|
|
||||||
class ChartJsBillChartGenerator implements BillChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $paid
|
|
||||||
* @param string $unpaid
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(string $paid, string $unpaid): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'data' => [round($unpaid, 2), round(bcmul($paid, '-1'), 2)],
|
|
||||||
'backgroundColor' => ['rgba(53, 124, 165,0.7)', 'rgba(0, 141, 76, 0.7)',],
|
|
||||||
],
|
|
||||||
|
|
||||||
],
|
|
||||||
'labels' => [strval(trans('firefly.unpaid')), strval(trans('firefly.paid'))],
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Bill $bill
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function single(Bill $bill, Collection $entries): array
|
|
||||||
{
|
|
||||||
$format = (string)trans('config.month');
|
|
||||||
$data = ['count' => 3, 'labels' => [], 'datasets' => [],];
|
|
||||||
$minAmount = [];
|
|
||||||
$maxAmount = [];
|
|
||||||
$actualAmount = [];
|
|
||||||
/** @var TransactionJournal $entry */
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$data['labels'][] = $entry->date->formatLocalized($format);
|
|
||||||
$minAmount[] = round($bill->amount_min, 2);
|
|
||||||
$maxAmount[] = round($bill->amount_max, 2);
|
|
||||||
// journalAmount has been collected in BillRepository::getJournals
|
|
||||||
$actualAmount[] = round(TransactionJournal::amountPositive($entry), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['datasets'][] = [
|
|
||||||
'type' => 'bar',
|
|
||||||
'label' => trans('firefly.minAmount'),
|
|
||||||
'data' => $minAmount,
|
|
||||||
];
|
|
||||||
$data['datasets'][] = [
|
|
||||||
'type' => 'line',
|
|
||||||
'label' => trans('firefly.billEntry'),
|
|
||||||
'data' => $actualAmount,
|
|
||||||
];
|
|
||||||
$data['datasets'][] = [
|
|
||||||
'type' => 'bar',
|
|
||||||
'label' => trans('firefly.maxAmount'),
|
|
||||||
'data' => $maxAmount,
|
|
||||||
];
|
|
||||||
|
|
||||||
$data['count'] = count($data['datasets']);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* BudgetChartGeneratorInterface.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Generator\Chart\Budget;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface BudgetChartGeneratorInterface
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Budget
|
|
||||||
*/
|
|
||||||
interface BudgetChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
* @param string $dateFormat
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function budgetLimit(Collection $entries, string $dateFormat): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYear(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
* @param string $viewRange
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function period(Collection $entries, string $viewRange) : array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $budgets
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function year(Collection $budgets, Collection $entries): array;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ChartJsBudgetChartGenerator.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
namespace FireflyIII\Generator\Chart\Budget;
|
|
||||||
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Navigation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ChartJsBudgetChartGenerator
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Budget
|
|
||||||
*/
|
|
||||||
class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param Collection $entries
|
|
||||||
* @param string $dateFormat
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function budgetLimit(Collection $entries, string $dateFormat = 'month_and_day'): array
|
|
||||||
{
|
|
||||||
$format = strval(trans('config.' . $dateFormat));
|
|
||||||
$data = [
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => 'Amount',
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/** @var array $entry */
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$data['labels'][] = $entry[0]->formatLocalized($format);
|
|
||||||
$data['datasets'][0]['data'][] = $entry[1];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['count'] = count($data['datasets']);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(Collection $entries): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 0,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [],
|
|
||||||
];
|
|
||||||
$left = [];
|
|
||||||
$spent = [];
|
|
||||||
$overspent = [];
|
|
||||||
$filtered = $entries->filter(
|
|
||||||
function ($entry) {
|
|
||||||
return ($entry[1] != 0 || $entry[2] != 0 || $entry[3] != 0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
foreach ($filtered as $entry) {
|
|
||||||
$data['labels'][] = $entry[0];
|
|
||||||
$left[] = round($entry[1], 2);
|
|
||||||
$spent[] = round(bcmul($entry[2], '-1'), 2); // spent is coming in negative, must be positive
|
|
||||||
$overspent[] = round(bcmul($entry[3], '-1'), 2); // same
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['datasets'][] = [
|
|
||||||
'label' => trans('firefly.overspent'),
|
|
||||||
'data' => $overspent,
|
|
||||||
];
|
|
||||||
$data['datasets'][] = [
|
|
||||||
'label' => trans('firefly.left'),
|
|
||||||
'data' => $left,
|
|
||||||
];
|
|
||||||
$data['datasets'][] = [
|
|
||||||
'label' => trans('firefly.spent'),
|
|
||||||
'data' => $spent,
|
|
||||||
];
|
|
||||||
|
|
||||||
$data['count'] = 3;
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYear(Collection $entries): array
|
|
||||||
{
|
|
||||||
// dataset:
|
|
||||||
$data = [
|
|
||||||
'count' => 0,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [],
|
|
||||||
];
|
|
||||||
// get labels from one of the budgets (assuming there's at least one):
|
|
||||||
$first = $entries->first();
|
|
||||||
$keys = array_keys($first['budgeted']);
|
|
||||||
foreach ($keys as $year) {
|
|
||||||
$data['labels'][] = strval($year);
|
|
||||||
}
|
|
||||||
|
|
||||||
// then, loop all entries and create datasets:
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$name = $entry['name'];
|
|
||||||
$spent = $entry['spent'];
|
|
||||||
$budgeted = $entry['budgeted'];
|
|
||||||
$data['datasets'][] = ['label' => 'Spent on ' . $name, 'data' => array_values($spent)];
|
|
||||||
$data['datasets'][] = ['label' => 'Budgeted for ' . $name, 'data' => array_values($budgeted)];
|
|
||||||
}
|
|
||||||
$data['count'] = count($data['datasets']);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
* @param string $viewRange
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function period(Collection $entries, string $viewRange) : array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
0 => [
|
|
||||||
'label' => trans('firefly.budgeted'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
1 => [
|
|
||||||
'label' => trans('firefly.spent'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'count' => 2,
|
|
||||||
];
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$label = Navigation::periodShow($entry['date'], $viewRange);
|
|
||||||
$data['labels'][] = $label;
|
|
||||||
// data set 0 is budgeted
|
|
||||||
// data set 1 is spent:
|
|
||||||
$data['datasets'][0]['data'][] = $entry['budgeted'];
|
|
||||||
$data['datasets'][1]['data'][] = round(($entry['spent'] * -1), 2);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $budgets
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function year(Collection $budgets, Collection $entries): array
|
|
||||||
{
|
|
||||||
// language:
|
|
||||||
$format = (string)trans('config.month');
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($budgets as $budget) {
|
|
||||||
$data['labels'][] = $budget->name;
|
|
||||||
}
|
|
||||||
// also add "no budget"
|
|
||||||
$data['labels'][] = strval(trans('firefly.no_budget'));
|
|
||||||
|
|
||||||
/** @var array $entry */
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$array = [
|
|
||||||
'label' => $entry[0]->formatLocalized($format),
|
|
||||||
'data' => [],
|
|
||||||
];
|
|
||||||
array_shift($entry);
|
|
||||||
$array['data'] = $entry;
|
|
||||||
$data['datasets'][] = $array;
|
|
||||||
|
|
||||||
}
|
|
||||||
$data['count'] = count($data['datasets']);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* CategoryChartGeneratorInterface.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Generator\Chart\Category;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface CategoryChartGeneratorInterface
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Category
|
|
||||||
*/
|
|
||||||
interface CategoryChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function all(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $categories
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function earnedInPeriod(Collection $categories, Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYear(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function period(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $categories
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function spentInPeriod(Collection $categories, Collection $entries): array;
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ChartJsCategoryChartGenerator.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
namespace FireflyIII\Generator\Chart\Category;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ChartJsCategoryChartGenerator
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Category
|
|
||||||
*/
|
|
||||||
class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function all(Collection $entries): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 2,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.spent'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.earned'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$data['labels'][] = $entry[1];
|
|
||||||
$spent = $entry[2];
|
|
||||||
$earned = $entry[3];
|
|
||||||
|
|
||||||
$data['datasets'][0]['data'][] = bccomp($spent, '0') === 0 ? null : round(bcmul($spent, '-1'), 4);
|
|
||||||
$data['datasets'][1]['data'][] = bccomp($earned, '0') === 0 ? null : round($earned, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $categories
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function earnedInPeriod(Collection $categories, Collection $entries): array
|
|
||||||
{
|
|
||||||
|
|
||||||
// language:
|
|
||||||
$format = (string)trans('config.month');
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'count' => 0,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($categories as $category) {
|
|
||||||
$data['labels'][] = $category->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$date = $entry[0]->formatLocalized($format);
|
|
||||||
array_shift($entry);
|
|
||||||
$data['count']++;
|
|
||||||
$data['datasets'][] = ['label' => $date, 'data' => $entry];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function frontpage(Collection $entries): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 1,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.spent'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
if ($entry->spent != 0) {
|
|
||||||
$data['labels'][] = $entry->name;
|
|
||||||
$data['datasets'][0]['data'][] = round(bcmul($entry->spent, '-1'), 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYear(Collection $entries): array
|
|
||||||
{
|
|
||||||
// get labels from one of the categories (assuming there's at least one):
|
|
||||||
$first = $entries->first();
|
|
||||||
$data = ['count' => 0, 'labels' => array_keys($first['spent']), 'datasets' => [],];
|
|
||||||
|
|
||||||
// then, loop all entries and create datasets:
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$name = $entry['name'];
|
|
||||||
$spent = $entry['spent'];
|
|
||||||
$earned = $entry['earned'];
|
|
||||||
if (array_sum(array_values($spent)) != 0) {
|
|
||||||
$data['datasets'][] = ['label' => 'Spent in category ' . $name, 'data' => array_values($spent)];
|
|
||||||
}
|
|
||||||
if (array_sum(array_values($earned)) != 0) {
|
|
||||||
$data['datasets'][] = ['label' => 'Earned in category ' . $name, 'data' => array_values($earned)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$data['count'] = count($data['datasets']);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function period(Collection $entries): array
|
|
||||||
{
|
|
||||||
return $this->all($entries);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $categories
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function spentInPeriod(Collection $categories, Collection $entries): array
|
|
||||||
{
|
|
||||||
|
|
||||||
// language:
|
|
||||||
$format = (string)trans('config.month');
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'count' => 0,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($categories as $category) {
|
|
||||||
$data['labels'][] = $category->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$date = $entry[0]->formatLocalized($format);
|
|
||||||
array_shift($entry);
|
|
||||||
$data['count']++;
|
|
||||||
$data['datasets'][] = ['label' => $date, 'data' => $entry];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ChartJsPiggyBankChartGenerator.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
namespace FireflyIII\Generator\Chart\PiggyBank;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ChartJsPiggyBankChartGenerator
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\PiggyBank
|
|
||||||
*/
|
|
||||||
class ChartJsPiggyBankChartGenerator implements PiggyBankChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $set
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function history(Collection $set): array
|
|
||||||
{
|
|
||||||
|
|
||||||
// language:
|
|
||||||
$format = (string)trans('config.month_and_day');
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'count' => 1,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => 'Diff',
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
$sum = '0';
|
|
||||||
foreach ($set as $key => $value) {
|
|
||||||
$date = new Carbon($key);
|
|
||||||
$sum = bcadd($sum, $value);
|
|
||||||
$data['labels'][] = $date->formatLocalized($format);
|
|
||||||
$data['datasets'][0]['data'][] = round($sum, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ChartJsReportChartGenerator.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
namespace FireflyIII\Generator\Chart\Report;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ChartJsReportChartGenerator
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Report
|
|
||||||
*/
|
|
||||||
class ChartJsReportChartGenerator implements ReportChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as above but other translations.
|
|
||||||
*
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYearInOut(Collection $entries): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 2,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.income'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.expenses'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$data['labels'][] = $entry[0]->formatLocalized('%Y');
|
|
||||||
$data['datasets'][0]['data'][] = round($entry[1], 2);
|
|
||||||
$data['datasets'][1]['data'][] = round($entry[2], 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $income
|
|
||||||
* @param string $expense
|
|
||||||
* @param int $count
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYearInOutSummarized(string $income, string $expense, int $count): array
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'count' => 2,
|
|
||||||
'labels' => [trans('firefly.sum_of_years'), trans('firefly.average_of_years')],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.income'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.expenses'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
$data['datasets'][0]['data'][] = round($income, 2);
|
|
||||||
$data['datasets'][1]['data'][] = round($expense, 2);
|
|
||||||
$data['datasets'][0]['data'][] = round(($income / $count), 2);
|
|
||||||
$data['datasets'][1]['data'][] = round(($expense / $count), 2);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function netWorth(Collection $entries) : array
|
|
||||||
{
|
|
||||||
$format = (string)trans('config.month_and_day');
|
|
||||||
$data = [
|
|
||||||
'count' => 1,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.net_worth'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$data['labels'][] = trim($entry['date']->formatLocalized($format));
|
|
||||||
$data['datasets'][0]['data'][] = round($entry['net-worth'], 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function yearInOut(Collection $entries): array
|
|
||||||
{
|
|
||||||
// language:
|
|
||||||
$format = (string)trans('config.month');
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'count' => 2,
|
|
||||||
'labels' => [],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.income'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.expenses'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$data['labels'][] = $entry[0]->formatLocalized($format);
|
|
||||||
$data['datasets'][0]['data'][] = round($entry[1], 2);
|
|
||||||
$data['datasets'][1]['data'][] = round($entry[2], 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $income
|
|
||||||
* @param string $expense
|
|
||||||
* @param int $count
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function yearInOutSummarized(string $income, string $expense, int $count): array
|
|
||||||
{
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'count' => 2,
|
|
||||||
'labels' => [trans('firefly.sum_of_year'), trans('firefly.average_of_year')],
|
|
||||||
'datasets' => [
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.income'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => trans('firefly.expenses'),
|
|
||||||
'data' => [],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
$data['datasets'][0]['data'][] = round($income, 2);
|
|
||||||
$data['datasets'][1]['data'][] = round($expense, 2);
|
|
||||||
$data['datasets'][0]['data'][] = round(($income / $count), 2);
|
|
||||||
$data['datasets'][1]['data'][] = round(($expense / $count), 2);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ReportChartGeneratorInterface.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace FireflyIII\Generator\Chart\Report;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface ReportChartGeneratorInterface
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Generator\Chart\Report
|
|
||||||
*/
|
|
||||||
interface ReportChartGeneratorInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYearInOut(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $income
|
|
||||||
* @param string $expense
|
|
||||||
* @param int $count
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function multiYearInOutSummarized(string $income, string $expense, int $count): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function netWorth(Collection $entries) : array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Collection $entries
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function yearInOut(Collection $entries): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $income
|
|
||||||
* @param string $expense
|
|
||||||
* @param int $count
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function yearInOutSummarized(string $income, string $expense, int $count): array;
|
|
||||||
|
|
||||||
}
|
|
||||||
156
app/Generator/Report/Audit/MonthReportGenerator.php
Normal file
156
app/Generator/Report/Audit/MonthReportGenerator.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Audit;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
|
use FireflyIII\Models\Account;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Steam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MonthReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Audit
|
||||||
|
*/
|
||||||
|
class MonthReportGenerator implements ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
$auditData = [];
|
||||||
|
$dayBefore = clone $this->start;
|
||||||
|
$dayBefore->subDay();
|
||||||
|
/** @var Account $account */
|
||||||
|
foreach ($this->accounts as $account) {
|
||||||
|
// balance the day before:
|
||||||
|
$id = $account->id;
|
||||||
|
$dayBeforeBalance = Steam::balance($account, $dayBefore);
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$journals = $journals->reverse();
|
||||||
|
$startBalance = $dayBeforeBalance;
|
||||||
|
|
||||||
|
|
||||||
|
/** @var Transaction $journal */
|
||||||
|
foreach ($journals as $transaction) {
|
||||||
|
$transaction->before = $startBalance;
|
||||||
|
$transactionAmount = $transaction->transaction_amount;
|
||||||
|
$newBalance = bcadd($startBalance, $transactionAmount);
|
||||||
|
$transaction->after = $newBalance;
|
||||||
|
$startBalance = $newBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverse set again.
|
||||||
|
*/
|
||||||
|
$auditData[$id]['journals'] = $journals->reverse();
|
||||||
|
$auditData[$id]['exists'] = $journals->count() > 0;
|
||||||
|
$auditData[$id]['end'] = $this->end->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
|
$auditData[$id]['endBalance'] = Steam::balance($account, $this->end);
|
||||||
|
$auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
|
$auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultShow = ['icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', 'to'];
|
||||||
|
$reportType = 'audit';
|
||||||
|
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
|
||||||
|
$hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date',
|
||||||
|
'interest_date', 'book_date', 'process_date',
|
||||||
|
// three new optional fields.
|
||||||
|
'due_date', 'payment_date', 'invoice_date',
|
||||||
|
'from', 'to', 'budget', 'category', 'bill',
|
||||||
|
// more new optional fields
|
||||||
|
'internal_reference', 'notes',
|
||||||
|
'create_date', 'update_date',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
return view('reports.audit.report', compact('reportType', 'accountIds', 'auditData', 'hideable', 'defaultShow'))
|
||||||
|
->with('start', $this->start)->with('end', $this->end)->with('accounts', $this->accounts)
|
||||||
|
->render();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Generator/Report/Audit/MultiYearReportGenerator.php
Normal file
27
app/Generator/Report/Audit/MultiYearReportGenerator.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MultiYearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Audit;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MultiYearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Audit
|
||||||
|
*/
|
||||||
|
class MultiYearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
28
app/Generator/Report/Audit/YearReportGenerator.php
Normal file
28
app/Generator/Report/Audit/YearReportGenerator.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* YearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Audit;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class YearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Audit
|
||||||
|
*/
|
||||||
|
class YearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
247
app/Generator/Report/Budget/MonthReportGenerator.php
Normal file
247
app/Generator/Report/Budget/MonthReportGenerator.php
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Budget;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
|
use FireflyIII\Generator\Report\Support;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MonthReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Budget
|
||||||
|
*/
|
||||||
|
class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Collection */
|
||||||
|
private $budgets;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Collection */
|
||||||
|
private $expenses;
|
||||||
|
/** @var Collection */
|
||||||
|
private $income;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->income = new Collection;
|
||||||
|
$this->expenses = new Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
|
||||||
|
$budgetIds = join(',', $this->budgets->pluck('id')->toArray());
|
||||||
|
$expenses = $this->getExpenses();
|
||||||
|
$accountSummary = $this->summarizeByAccount($expenses);
|
||||||
|
$budgetSummary = $this->summarizeByBudget($expenses);
|
||||||
|
$averageExpenses = $this->getAverages($expenses, SORT_ASC);
|
||||||
|
$topExpenses = $this->getTopExpenses();
|
||||||
|
|
||||||
|
// render!
|
||||||
|
return view('reports.budget.month', compact('accountIds', 'budgetIds', 'accountSummary', 'budgetSummary', 'averageExpenses', 'topExpenses'))
|
||||||
|
->with('start', $this->start)->with('end', $this->end)
|
||||||
|
->with('budgets', $this->budgets)
|
||||||
|
->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
|
||||||
|
{
|
||||||
|
$this->budgets = $budgets;
|
||||||
|
|
||||||
|
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 $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
|
||||||
|
*/
|
||||||
|
private function getExpenses(): Collection
|
||||||
|
{
|
||||||
|
if ($this->expenses->count() > 0) {
|
||||||
|
Log::debug('Return previous set of expenses.');
|
||||||
|
|
||||||
|
return $this->expenses;
|
||||||
|
}
|
||||||
|
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL])
|
||||||
|
->setBudgets($this->budgets)->withOpposingAccount()->disableFilter();
|
||||||
|
|
||||||
|
$accountIds = $this->accounts->pluck('id')->toArray();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$transactions = self::filterExpenses($transactions, $accountIds);
|
||||||
|
$this->expenses = $transactions;
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function 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
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function summarizeByBudget(Collection $collection): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($collection as $transaction) {
|
||||||
|
$jrnlBudId = intval($transaction->transaction_journal_budget_id);
|
||||||
|
$transBudId = intval($transaction->transaction_budget_id);
|
||||||
|
$budgetId = max($jrnlBudId, $transBudId);
|
||||||
|
$result[$budgetId] = $result[$budgetId] ?? '0';
|
||||||
|
$result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Generator/Report/Budget/MultiYearReportGenerator.php
Normal file
27
app/Generator/Report/Budget/MultiYearReportGenerator.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MultiYearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Budget;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MultiYearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Budget
|
||||||
|
*/
|
||||||
|
class MultiYearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
28
app/Generator/Report/Budget/YearReportGenerator.php
Normal file
28
app/Generator/Report/Budget/YearReportGenerator.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* YearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Budget;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class YearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Budget
|
||||||
|
*/
|
||||||
|
class YearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
328
app/Generator/Report/Category/MonthReportGenerator.php
Normal file
328
app/Generator/Report/Category/MonthReportGenerator.php
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Category;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
|
use FireflyIII\Generator\Report\Support;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MonthReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Category
|
||||||
|
*/
|
||||||
|
class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Collection */
|
||||||
|
private $categories;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Collection */
|
||||||
|
private $expenses;
|
||||||
|
/** @var Collection */
|
||||||
|
private $income;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->income = new Collection;
|
||||||
|
$this->expenses = new Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
|
||||||
|
$categoryIds = join(',', $this->categories->pluck('id')->toArray());
|
||||||
|
$reportType = 'category';
|
||||||
|
$expenses = $this->getExpenses();
|
||||||
|
$income = $this->getIncome();
|
||||||
|
$accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income));
|
||||||
|
$categorySummary = $this->getObjectSummary($this->summarizeByCategory($expenses), $this->summarizeByCategory($income));
|
||||||
|
$averageExpenses = $this->getAverages($expenses, SORT_ASC);
|
||||||
|
$averageIncome = $this->getAverages($income, SORT_DESC);
|
||||||
|
$topExpenses = $this->getTopExpenses();
|
||||||
|
$topIncome = $this->getTopIncome();
|
||||||
|
|
||||||
|
|
||||||
|
// render!
|
||||||
|
return view(
|
||||||
|
'reports.category.month',
|
||||||
|
compact(
|
||||||
|
'accountIds', 'categoryIds', 'topIncome', 'reportType', 'accountSummary', 'categorySummary', 'averageExpenses', 'averageIncome', 'topExpenses'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->with('start', $this->start)->with('end', $this->end)
|
||||||
|
->with('categories', $this->categories)
|
||||||
|
->with('accounts', $this->accounts)
|
||||||
|
->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setBudgets(Collection $budgets): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $categories
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setCategories(Collection $categories): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->categories = $categories;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setEndDate(Carbon $date): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->end = $date;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setStartDate(Carbon $date): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->start = $date;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
* @param int $sortFlag
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getAverages(Collection $collection, int $sortFlag): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($collection as $transaction) {
|
||||||
|
// opposing name and ID:
|
||||||
|
$opposingId = $transaction->opposing_account_id;
|
||||||
|
|
||||||
|
// is not set?
|
||||||
|
if (!isset($result[$opposingId])) {
|
||||||
|
$name = $transaction->opposing_account_name;
|
||||||
|
$result[$opposingId] = [
|
||||||
|
'name' => $name,
|
||||||
|
'count' => 1,
|
||||||
|
'id' => $opposingId,
|
||||||
|
'average' => $transaction->transaction_amount,
|
||||||
|
'sum' => $transaction->transaction_amount,
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result[$opposingId]['count']++;
|
||||||
|
$result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount);
|
||||||
|
$result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort result by average:
|
||||||
|
$average = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$average[$key] = floatval($row['average']);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_multisort($average, $sortFlag, $result);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getExpenses(): Collection
|
||||||
|
{
|
||||||
|
if ($this->expenses->count() > 0) {
|
||||||
|
Log::debug('Return previous set of expenses.');
|
||||||
|
|
||||||
|
return $this->expenses;
|
||||||
|
}
|
||||||
|
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
|
->setCategories($this->categories)->withOpposingAccount()->disableFilter();
|
||||||
|
|
||||||
|
$accountIds = $this->accounts->pluck('id')->toArray();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$transactions = self::filterExpenses($transactions, $accountIds);
|
||||||
|
$this->expenses = $transactions;
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getIncome(): Collection
|
||||||
|
{
|
||||||
|
if ($this->income->count() > 0) {
|
||||||
|
return $this->income;
|
||||||
|
}
|
||||||
|
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)
|
||||||
|
->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||||
|
->setCategories($this->categories)->withOpposingAccount();
|
||||||
|
$accountIds = $this->accounts->pluck('id')->toArray();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$transactions = self::filterIncome($transactions, $accountIds);
|
||||||
|
$this->income = $transactions;
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $spent
|
||||||
|
* @param array $earned
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getObjectSummary(array $spent, array $earned): array
|
||||||
|
{
|
||||||
|
$return = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int $accountId
|
||||||
|
* @var string $entry
|
||||||
|
*/
|
||||||
|
foreach ($spent as $objectId => $entry) {
|
||||||
|
if (!isset($return[$objectId])) {
|
||||||
|
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$objectId]['spent'] = $entry;
|
||||||
|
}
|
||||||
|
unset($entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int $accountId
|
||||||
|
* @var string $entry
|
||||||
|
*/
|
||||||
|
foreach ($earned as $objectId => $entry) {
|
||||||
|
if (!isset($return[$objectId])) {
|
||||||
|
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$objectId]['earned'] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getTopExpenses(): Collection
|
||||||
|
{
|
||||||
|
$transactions = $this->getExpenses()->sortBy('transaction_amount');
|
||||||
|
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function summarizeByCategory(Collection $collection): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($collection as $transaction) {
|
||||||
|
$jrnlCatId = intval($transaction->transaction_journal_category_id);
|
||||||
|
$transCatId = intval($transaction->transaction_category_id);
|
||||||
|
$categoryId = max($jrnlCatId, $transCatId);
|
||||||
|
$result[$categoryId] = $result[$categoryId] ?? '0';
|
||||||
|
$result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Generator/Report/Category/MultiYearReportGenerator.php
Normal file
27
app/Generator/Report/Category/MultiYearReportGenerator.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MultiYearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Category;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MultiYearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Audit
|
||||||
|
*/
|
||||||
|
class MultiYearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
28
app/Generator/Report/Category/YearReportGenerator.php
Normal file
28
app/Generator/Report/Category/YearReportGenerator.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* YearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Category;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class YearReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Audit
|
||||||
|
*/
|
||||||
|
class YearReportGenerator extends MonthReportGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doesn't do anything different.
|
||||||
|
*/
|
||||||
|
}
|
||||||
60
app/Generator/Report/ReportGeneratorFactory.php
Normal file
60
app/Generator/Report/ReportGeneratorFactory.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ReportGeneratorFactory.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ReportGeneratorFactory
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report
|
||||||
|
*/
|
||||||
|
class ReportGeneratorFactory
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $type
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public static function reportGenerator(string $type, Carbon $start, Carbon $end): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$period = 'Month';
|
||||||
|
// more than two months date difference means year report.
|
||||||
|
if ($start->diffInMonths($end) > 1) {
|
||||||
|
$period = 'Year';
|
||||||
|
}
|
||||||
|
|
||||||
|
// more than one year date difference means multi year report.
|
||||||
|
if ($start->diffInMonths($end) > 12) {
|
||||||
|
$period = 'MultiYear';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period);
|
||||||
|
if (class_exists($class)) {
|
||||||
|
/** @var ReportGeneratorInterface $obj */
|
||||||
|
$obj = new $class;
|
||||||
|
$obj->setStartDate($start);
|
||||||
|
$obj->setEndDate($end);
|
||||||
|
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
throw new FireflyException(sprintf('Cannot generate report. There is no "%s"-report for period "%s".', $type, $period));
|
||||||
|
}
|
||||||
|
}
|
||||||
67
app/Generator/Report/ReportGeneratorInterface.php
Normal file
67
app/Generator/Report/ReportGeneratorInterface.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ReportGeneratorInterface.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface ReportGeneratorInterface
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report
|
||||||
|
*/
|
||||||
|
interface ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): ReportGeneratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setBudgets(Collection $budgets): ReportGeneratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $categories
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setCategories(Collection $categories): ReportGeneratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setEndDate(Carbon $date): ReportGeneratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setStartDate(Carbon $date): ReportGeneratorInterface;
|
||||||
|
|
||||||
|
}
|
||||||
109
app/Generator/Report/Standard/MonthReportGenerator.php
Normal file
109
app/Generator/Report/Standard/MonthReportGenerator.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MonthReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Standard;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
|
use FireflyIII\Helpers\Report\ReportHelperInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MonthReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Standard
|
||||||
|
*/
|
||||||
|
class MonthReportGenerator implements ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
/** @var ReportHelperInterface $helper */
|
||||||
|
$helper = app(ReportHelperInterface::class);
|
||||||
|
$bills = $helper->getBillReport($this->start, $this->end, $this->accounts);
|
||||||
|
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
|
||||||
|
$reportType = 'default';
|
||||||
|
|
||||||
|
// continue!
|
||||||
|
return view(
|
||||||
|
'reports.default.month',
|
||||||
|
compact('bills', 'accountIds', 'reportType')
|
||||||
|
)->with('start', $this->start)->with('end', $this->end)->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
app/Generator/Report/Standard/MultiYearReportGenerator.php
Normal file
106
app/Generator/Report/Standard/MultiYearReportGenerator.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MultiYearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Standard;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MonthReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Standard
|
||||||
|
*/
|
||||||
|
class MultiYearReportGenerator implements ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
// and some id's, joined:
|
||||||
|
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
|
||||||
|
$reportType = 'default';
|
||||||
|
|
||||||
|
// continue!
|
||||||
|
return view(
|
||||||
|
'reports.default.multi-year',
|
||||||
|
compact('accountIds', 'reportType')
|
||||||
|
)->with('start', $this->start)->with('end', $this->end)->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
app/Generator/Report/Standard/YearReportGenerator.php
Normal file
106
app/Generator/Report/Standard/YearReportGenerator.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* YearReportGenerator.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report\Standard;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MonthReportGenerator
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Standard
|
||||||
|
*/
|
||||||
|
class YearReportGenerator implements ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
/** @var Collection */
|
||||||
|
private $accounts;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $end;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
// and some id's, joined:
|
||||||
|
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
|
||||||
|
$reportType = 'default';
|
||||||
|
|
||||||
|
// continue!
|
||||||
|
return view(
|
||||||
|
'reports.default.year',
|
||||||
|
compact('accountIds', 'reportType')
|
||||||
|
)->with('start', $this->start)->with('end', $this->end)->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return ReportGeneratorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||||
|
{
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
91
app/Generator/Report/Support.php
Normal file
91
app/Generator/Report/Support.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Support.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Generator\Report;
|
||||||
|
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Support
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Generator\Report\Category
|
||||||
|
*/
|
||||||
|
class Support
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
* @param array $accounts
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public static function filterExpenses(Collection $collection, array $accounts): Collection
|
||||||
|
{
|
||||||
|
$result = $collection->filter(
|
||||||
|
function (Transaction $transaction) use ($accounts) {
|
||||||
|
$opposing = $transaction->opposing_account_id;
|
||||||
|
// remove internal transfer
|
||||||
|
if (in_array($opposing, $accounts)) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// remove positive amount
|
||||||
|
if (bccomp($transaction->transaction_amount, '0') === 1) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $collection
|
||||||
|
* @param array $accounts
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public static function filterIncome(Collection $collection, array $accounts): Collection
|
||||||
|
{
|
||||||
|
$result = $collection->filter(
|
||||||
|
function (Transaction $transaction) use ($accounts) {
|
||||||
|
$opposing = $transaction->opposing_account_id;
|
||||||
|
// remove internal transfer
|
||||||
|
if (in_array($opposing, $accounts)) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// remove positive amount
|
||||||
|
if (bccomp($transaction->transaction_amount, '0') === -1) {
|
||||||
|
Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,8 +14,10 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Handlers\Events;
|
namespace FireflyIII\Handlers\Events;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Events\StoredBudgetLimit;
|
use FireflyIII\Events\StoredBudgetLimit;
|
||||||
use FireflyIII\Events\UpdatedBudgetLimit;
|
use FireflyIII\Events\UpdatedBudgetLimit;
|
||||||
|
use FireflyIII\Models\BudgetLimit;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\LimitRepetition;
|
||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Log;
|
use Log;
|
||||||
@@ -32,62 +34,43 @@ class BudgetEventHandler
|
|||||||
/**
|
/**
|
||||||
* This method creates a new budget limit repetition when a new budget limit has been created.
|
* This method creates a new budget limit repetition when a new budget limit has been created.
|
||||||
*
|
*
|
||||||
* @param StoredBudgetLimit $event
|
* @param StoredBudgetLimit $budgetLimitEvent
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function storeRepetition(StoredBudgetLimit $event):bool
|
public function storeRepetition(StoredBudgetLimit $budgetLimitEvent): bool
|
||||||
{
|
{
|
||||||
$budgetLimit = $event->budgetLimit;
|
return $this->processRepetitionChange($budgetLimitEvent->budgetLimit, $budgetLimitEvent->end);
|
||||||
$end = $event->end;
|
|
||||||
$set = $budgetLimit->limitrepetitions()
|
|
||||||
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
|
|
||||||
->where('enddate', $end->format('Y-m-d 00:00:00'))
|
|
||||||
->get();
|
|
||||||
if ($set->count() == 0) {
|
|
||||||
$repetition = new LimitRepetition;
|
|
||||||
$repetition->startdate = $budgetLimit->startdate;
|
|
||||||
$repetition->enddate = $end;
|
|
||||||
$repetition->amount = $budgetLimit->amount;
|
|
||||||
$repetition->budgetLimit()->associate($budgetLimit);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$repetition->save();
|
|
||||||
} catch (QueryException $e) {
|
|
||||||
Log::error('Trying to save new LimitRepetition failed: ' . $e->getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($set->count() == 1) {
|
|
||||||
$repetition = $set->first();
|
|
||||||
$repetition->amount = $budgetLimit->amount;
|
|
||||||
$repetition->save();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates, if present the budget limit repetition part of a budget limit.
|
* Updates, if present the budget limit repetition part of a budget limit.
|
||||||
*
|
*
|
||||||
* @param UpdatedBudgetLimit $event
|
* @param UpdatedBudgetLimit $budgetLimitEvent
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function updateRepetition(UpdatedBudgetLimit $event): bool
|
public function updateRepetition(UpdatedBudgetLimit $budgetLimitEvent): bool
|
||||||
|
{
|
||||||
|
return $this->processRepetitionChange($budgetLimitEvent->budgetLimit, $budgetLimitEvent->end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BudgetLimit $budgetLimit
|
||||||
|
* @param Carbon $date
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function processRepetitionChange(BudgetLimit $budgetLimit, Carbon $date): bool
|
||||||
{
|
{
|
||||||
$budgetLimit = $event->budgetLimit;
|
|
||||||
$end = $event->end;
|
|
||||||
$set = $budgetLimit->limitrepetitions()
|
$set = $budgetLimit->limitrepetitions()
|
||||||
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
|
->where('startdate', $budgetLimit->startdate->format('Y-m-d 00:00:00'))
|
||||||
->where('enddate', $end->format('Y-m-d 00:00:00'))
|
->where('enddate', $date->format('Y-m-d 00:00:00'))
|
||||||
->get();
|
->get();
|
||||||
if ($set->count() == 0) {
|
if ($set->count() == 0) {
|
||||||
$repetition = new LimitRepetition;
|
$repetition = new LimitRepetition;
|
||||||
$repetition->startdate = $budgetLimit->startdate;
|
$repetition->startdate = $budgetLimit->startdate;
|
||||||
$repetition->enddate = $end;
|
$repetition->enddate = $date;
|
||||||
$repetition->amount = $budgetLimit->amount;
|
$repetition->amount = $budgetLimit->amount;
|
||||||
$repetition->budgetLimit()->associate($budgetLimit);
|
$repetition->budgetLimit()->associate($budgetLimit);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use FireflyIII\Models\RuleGroup;
|
|||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
use FireflyIII\Rules\Processor;
|
use FireflyIII\Rules\Processor;
|
||||||
use FireflyIII\Support\Events\BillScanner;
|
use FireflyIII\Support\Events\BillScanner;
|
||||||
|
use Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class StoredJournalEventHandler
|
* Class StoredJournalEventHandler
|
||||||
@@ -32,40 +33,79 @@ class StoredJournalEventHandler
|
|||||||
/**
|
/**
|
||||||
* 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 $storedJournalEvent
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function connectToPiggyBank(StoredTransactionJournal $event): bool
|
public function connectToPiggyBank(StoredTransactionJournal $storedJournalEvent): bool
|
||||||
{
|
{
|
||||||
/** @var TransactionJournal $journal */
|
/** @var TransactionJournal $journal */
|
||||||
$journal = $event->journal;
|
$journal = $storedJournalEvent->journal;
|
||||||
$piggyBankId = $event->piggyBankId;
|
$piggyBankId = $storedJournalEvent->piggyBankId;
|
||||||
|
|
||||||
|
Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId));
|
||||||
|
|
||||||
/** @var PiggyBank $piggyBank */
|
/** @var PiggyBank $piggyBank */
|
||||||
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
$piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
|
||||||
|
|
||||||
if (is_null($piggyBank)) {
|
if (is_null($piggyBank)) {
|
||||||
|
Log::error('No such piggy bank!');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name));
|
||||||
// update piggy bank rep for date of transaction journal.
|
// update piggy bank rep for date of transaction journal.
|
||||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
||||||
if (is_null($repetition)) {
|
if (is_null($repetition)) {
|
||||||
|
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$amount = TransactionJournal::amountPositive($journal);
|
$amount = TransactionJournal::amountPositive($journal);
|
||||||
|
Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
||||||
// if piggy account matches source account, the amount is positive
|
// if piggy account matches source account, the amount is positive
|
||||||
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
|
$sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray();
|
||||||
if (in_array($piggyBank->account_id, $sources)) {
|
if (in_array($piggyBank->account_id, $sources)) {
|
||||||
$amount = bcmul($amount, '-1');
|
$amount = bcmul($amount, '-1');
|
||||||
|
Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the amount is positive:
|
||||||
|
// make sure it fits in piggy bank:
|
||||||
|
if (bccomp($amount, '0') === 1) {
|
||||||
|
// amount is positive
|
||||||
|
$room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount));
|
||||||
|
Log::debug(sprintf('Room in piggy bank for extra money is %f', $room));
|
||||||
|
if (bccomp($room, $amount) === -1) {
|
||||||
|
// $room is smaller than $amount
|
||||||
|
Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
||||||
|
Log::debug(sprintf('New amount is %f', $room));
|
||||||
|
$amount = $room;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bccomp($amount, '0') === -1) {
|
||||||
|
// amount is negative
|
||||||
|
Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount));
|
||||||
|
$compare = bcmul($repetition->currentamount, '-1');
|
||||||
|
if (bccomp($compare, $amount) === 1) {
|
||||||
|
// $currentamount is smaller than $amount
|
||||||
|
Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
|
||||||
|
Log::debug(sprintf('New amount is %f', $compare));
|
||||||
|
$amount = $compare;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
|
$repetition->currentamount = bcadd($repetition->currentamount, $amount);
|
||||||
$repetition->save();
|
$repetition->save();
|
||||||
|
|
||||||
PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]);
|
/** @var PiggyBankEvent $storedJournalEvent */
|
||||||
|
$storedJournalEvent = 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', $storedJournalEvent->id));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -73,14 +113,14 @@ class StoredJournalEventHandler
|
|||||||
/**
|
/**
|
||||||
* This method grabs all the users rules and processes them.
|
* This method grabs all the users rules and processes them.
|
||||||
*
|
*
|
||||||
* @param StoredTransactionJournal $event
|
* @param StoredTransactionJournal $storedJournalEvent
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function processRules(StoredTransactionJournal $event): bool
|
public function processRules(StoredTransactionJournal $storedJournalEvent): bool
|
||||||
{
|
{
|
||||||
// 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 = $event->journal;
|
$journal = $storedJournalEvent->journal;
|
||||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||||
//
|
//
|
||||||
/** @var RuleGroup $group */
|
/** @var RuleGroup $group */
|
||||||
@@ -110,13 +150,13 @@ class StoredJournalEventHandler
|
|||||||
/**
|
/**
|
||||||
* This method calls a special bill scanner that will check if the stored journal is part of a bill.
|
* This method calls a special bill scanner that will check if the stored journal is part of a bill.
|
||||||
*
|
*
|
||||||
* @param StoredTransactionJournal $event
|
* @param StoredTransactionJournal $storedJournalEvent
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function scanBills(StoredTransactionJournal $event): bool
|
public function scanBills(StoredTransactionJournal $storedJournalEvent): bool
|
||||||
{
|
{
|
||||||
$journal = $event->journal;
|
$journal = $storedJournalEvent->journal;
|
||||||
BillScanner::scan($journal);
|
BillScanner::scan($journal);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,11 +15,8 @@ namespace FireflyIII\Handlers\Events;
|
|||||||
|
|
||||||
|
|
||||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||||
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\Rules\Processor;
|
use FireflyIII\Rules\Processor;
|
||||||
use FireflyIII\Support\Events\BillScanner;
|
use FireflyIII\Support\Events\BillScanner;
|
||||||
|
|
||||||
@@ -30,62 +27,18 @@ use FireflyIII\Support\Events\BillScanner;
|
|||||||
*/
|
*/
|
||||||
class UpdatedJournalEventHandler
|
class UpdatedJournalEventHandler
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* This method will try to reconnect a journal to a piggy bank, updating the piggy bank repetition.
|
|
||||||
*
|
|
||||||
* @param UpdatedTransactionJournal $event
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function connectToPiggyBank(UpdatedTransactionJournal $event): bool
|
|
||||||
{
|
|
||||||
$journal = $event->journal;
|
|
||||||
|
|
||||||
if (!$journal->isTransfer()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the event connected to this journal:
|
|
||||||
/** @var PiggyBankEvent $event */
|
|
||||||
$event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first();
|
|
||||||
if (is_null($event)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$piggyBank = $event->piggyBank()->first();
|
|
||||||
$repetition = null;
|
|
||||||
if (!is_null($piggyBank)) {
|
|
||||||
/** @var PiggyBankRepetition $repetition */
|
|
||||||
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($repetition)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$amount = TransactionJournal::amount($journal);
|
|
||||||
$diff = bcsub($amount, $event->amount); // update current repetition
|
|
||||||
|
|
||||||
$repetition->currentamount = bcadd($repetition->currentamount, $diff);
|
|
||||||
$repetition->save();
|
|
||||||
|
|
||||||
|
|
||||||
$event->amount = $amount;
|
|
||||||
$event->save();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will check all the rules when a journal is updated.
|
* This method will check all the rules when a journal is updated.
|
||||||
*
|
*
|
||||||
* @param UpdatedTransactionJournal $event
|
* @param UpdatedTransactionJournal $updatedJournalEvent
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function processRules(UpdatedTransactionJournal $event):bool
|
public function processRules(UpdatedTransactionJournal $updatedJournalEvent): bool
|
||||||
{
|
{
|
||||||
// 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 = $event->journal;
|
$journal = $updatedJournalEvent->journal;
|
||||||
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
$groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get();
|
||||||
//
|
//
|
||||||
/** @var RuleGroup $group */
|
/** @var RuleGroup $group */
|
||||||
@@ -114,13 +67,13 @@ class UpdatedJournalEventHandler
|
|||||||
/**
|
/**
|
||||||
* This method calls a special bill scanner that will check if the updated journal is part of a bill.
|
* This method calls a special bill scanner that will check if the updated journal is part of a bill.
|
||||||
*
|
*
|
||||||
* @param UpdatedTransactionJournal $event
|
* @param UpdatedTransactionJournal $updatedJournalEvent
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function scanBills(UpdatedTransactionJournal $event): bool
|
public function scanBills(UpdatedTransactionJournal $updatedJournalEvent): bool
|
||||||
{
|
{
|
||||||
$journal = $event->journal;
|
$journal = $updatedJournalEvent->journal;
|
||||||
BillScanner::scan($journal);
|
BillScanner::scan($journal);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -14,10 +14,20 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Handlers\Events;
|
namespace FireflyIII\Handlers\Events;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use FireflyConfig;
|
||||||
|
use FireflyIII\Events\BlockedBadLogin;
|
||||||
|
use FireflyIII\Events\BlockedUseOfDomain;
|
||||||
|
use FireflyIII\Events\BlockedUseOfEmail;
|
||||||
|
use FireflyIII\Events\BlockedUserLogin;
|
||||||
use FireflyIII\Events\ConfirmedUser;
|
use FireflyIII\Events\ConfirmedUser;
|
||||||
|
use FireflyIII\Events\DeletedUser;
|
||||||
|
use FireflyIII\Events\LockedOutUser;
|
||||||
use FireflyIII\Events\RegisteredUser;
|
use FireflyIII\Events\RegisteredUser;
|
||||||
|
use FireflyIII\Events\RequestedNewPassword;
|
||||||
use FireflyIII\Events\ResentConfirmation;
|
use FireflyIII\Events\ResentConfirmation;
|
||||||
|
use FireflyIII\Models\Configuration;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
|
use FireflyIII\User;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Log;
|
use Log;
|
||||||
use Mail;
|
use Mail;
|
||||||
@@ -72,6 +82,210 @@ class UserEventHandler
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BlockedBadLogin $event
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function reportBadLogin(BlockedBadLogin $event)
|
||||||
|
{
|
||||||
|
$email = $event->email;
|
||||||
|
$owner = env('SITE_OWNER');
|
||||||
|
$ipAddress = $event->ipAddress;
|
||||||
|
/** @var Configuration $sendmail */
|
||||||
|
$sendmail = FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login'));
|
||||||
|
Log::debug(sprintf('Now in reportBadLogin for email address %s', $email));
|
||||||
|
Log::error(sprintf('User %s tried to login with bad credentials.', $email));
|
||||||
|
if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Mail::send(
|
||||||
|
['emails.blocked-bad-creds-html', 'emails.blocked-bad-creds-text'], ['email' => $email, 'ip' => $ipAddress],
|
||||||
|
function (Message $message) use ($owner) {
|
||||||
|
$message->to($owner, $owner)->subject('Blocked login attempt with bad credentials');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Swift_TransportException $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BlockedUserLogin $event
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function reportBlockedUser(BlockedUserLogin $event): bool
|
||||||
|
{
|
||||||
|
$user = $event->user;
|
||||||
|
$owner = env('SITE_OWNER');
|
||||||
|
$email = $user->email;
|
||||||
|
$ipAddress = $event->ipAddress;
|
||||||
|
/** @var Configuration $sendmail */
|
||||||
|
$sendmail = FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login'));
|
||||||
|
Log::debug(sprintf('Now in reportBlockedUser for email address %s', $email));
|
||||||
|
Log::error(sprintf('User #%d (%s) has their accout blocked (blocked_code is "%s") but tried to login.', $user->id, $email, $user->blocked_code));
|
||||||
|
if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email message:
|
||||||
|
try {
|
||||||
|
Mail::send(
|
||||||
|
['emails.blocked-login-html', 'emails.blocked-login-text'],
|
||||||
|
[
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'user_address' => $email,
|
||||||
|
'ip' => $ipAddress,
|
||||||
|
'code' => $user->blocked_code,
|
||||||
|
], function (Message $message) use ($owner, $user) {
|
||||||
|
$message->to($owner, $owner)->subject('Blocked login attempt of blocked user');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Swift_TransportException $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LockedOutUser $event
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function reportLockout(LockedOutUser $event): bool
|
||||||
|
{
|
||||||
|
$email = $event->email;
|
||||||
|
$owner = env('SITE_OWNER');
|
||||||
|
$ipAddress = $event->ipAddress;
|
||||||
|
/** @var Configuration $sendmail */
|
||||||
|
$sendmail = FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout'));
|
||||||
|
Log::debug(sprintf('Now in respondToLockout for email address %s', $email));
|
||||||
|
Log::error(sprintf('User %s was locked out after too many invalid login attempts.', $email));
|
||||||
|
if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email message:
|
||||||
|
try {
|
||||||
|
Mail::send(
|
||||||
|
['emails.locked-out-html', 'emails.locked-out-text'], ['email' => $email, 'ip' => $ipAddress], function (Message $message) use ($owner) {
|
||||||
|
$message->to($owner, $owner)->subject('User was locked out');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Swift_TransportException $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BlockedUseOfDomain $event
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function reportUseBlockedDomain(BlockedUseOfDomain $event): bool
|
||||||
|
{
|
||||||
|
$email = $event->email;
|
||||||
|
$owner = env('SITE_OWNER');
|
||||||
|
$ipAddress = $event->ipAddress;
|
||||||
|
$parts = explode('@', $email);
|
||||||
|
/** @var Configuration $sendmail */
|
||||||
|
$sendmail = FireflyConfig::get('mail_for_blocked_domain', config('firefly.configuration.mail_for_blocked_domain'));
|
||||||
|
Log::debug(sprintf('Now in reportUseBlockedDomain for email address %s', $email));
|
||||||
|
Log::error(sprintf('Somebody tried to register using an email address (%s) connected to a banned domain (%s).', $email, $parts[1]));
|
||||||
|
if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email message:
|
||||||
|
try {
|
||||||
|
Mail::send(
|
||||||
|
['emails.blocked-registration-html', 'emails.blocked-registration-text'],
|
||||||
|
[
|
||||||
|
'email_address' => $email,
|
||||||
|
'blocked_domain' => $parts[1],
|
||||||
|
'ip' => $ipAddress,
|
||||||
|
], function (Message $message) use ($owner) {
|
||||||
|
$message->to($owner, $owner)->subject('Blocked registration attempt with blocked domain');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Swift_TransportException $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BlockedUseOfEmail $event
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function reportUseOfBlockedEmail(BlockedUseOfEmail $event): bool
|
||||||
|
{
|
||||||
|
$email = $event->email;
|
||||||
|
$owner = env('SITE_OWNER');
|
||||||
|
$ipAddress = $event->ipAddress;
|
||||||
|
/** @var Configuration $sendmail */
|
||||||
|
$sendmail = FireflyConfig::get('mail_for_blocked_email', config('firefly.configuration.mail_for_blocked_email'));
|
||||||
|
Log::debug(sprintf('Now in reportUseOfBlockedEmail for email address %s', $email));
|
||||||
|
Log::error(sprintf('Somebody tried to register using email address %s which is blocked (SHA2 hash).', $email));
|
||||||
|
if (is_null($sendmail) || (!is_null($sendmail) && $sendmail->data === false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email message:
|
||||||
|
try {
|
||||||
|
Mail::send(
|
||||||
|
['emails.blocked-email-html', 'emails.blocked-email-text'],
|
||||||
|
[
|
||||||
|
'user_address' => $email,
|
||||||
|
'ip' => $ipAddress,
|
||||||
|
], function (Message $message) use ($owner) {
|
||||||
|
$message->to($owner, $owner)->subject('Blocked registration attempt with blocked email address');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Swift_TransportException $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DeletedUser $event
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function saveEmailAddress(DeletedUser $event): bool
|
||||||
|
{
|
||||||
|
Preferences::mark();
|
||||||
|
$email = hash('sha256', $event->email);
|
||||||
|
Log::debug(sprintf('Hash of email is %s', $email));
|
||||||
|
/** @var Configuration $configuration */
|
||||||
|
$configuration = FireflyConfig::get('deleted_users', []);
|
||||||
|
$content = $configuration->data;
|
||||||
|
if (!is_array($content)) {
|
||||||
|
$content = [];
|
||||||
|
}
|
||||||
|
$content[] = $email;
|
||||||
|
$configuration->data = $content;
|
||||||
|
Log::debug('New content of deleted_users is ', $content);
|
||||||
|
FireflyConfig::set('deleted_users', $content);
|
||||||
|
|
||||||
|
Preferences::mark();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will send a newly registered user a confirmation message, urging him or her to activate their account.
|
* This method will send a newly registered user a confirmation message, urging him or her to activate their account.
|
||||||
*
|
*
|
||||||
@@ -81,36 +295,7 @@ class UserEventHandler
|
|||||||
*/
|
*/
|
||||||
public function sendConfirmationMessage(RegisteredUser $event): bool
|
public function sendConfirmationMessage(RegisteredUser $event): bool
|
||||||
{
|
{
|
||||||
$user = $event->user;
|
return $this->sendConfirmation($event->user, $event->ipAddress);
|
||||||
$ipAddress = $event->ipAddress;
|
|
||||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
|
||||||
if ($confirmAccount === false) {
|
|
||||||
Preferences::setForUser($user, 'user_confirmed', true);
|
|
||||||
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
|
||||||
Preferences::mark();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$email = $user->email;
|
|
||||||
$code = str_random(16);
|
|
||||||
$route = route('do_confirm_account', [$code]);
|
|
||||||
Preferences::setForUser($user, 'user_confirmed', false);
|
|
||||||
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
|
|
||||||
Preferences::setForUser($user, 'user_confirmed_code', $code);
|
|
||||||
try {
|
|
||||||
Mail::send(
|
|
||||||
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
|
|
||||||
function (Message $message) use ($email) {
|
|
||||||
$message->to($email, $email)->subject('Please confirm your Firefly III account');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (Swift_TransportException $e) {
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,37 +309,35 @@ class UserEventHandler
|
|||||||
*/
|
*/
|
||||||
function sendConfirmationMessageAgain(ResentConfirmation $event): bool
|
function sendConfirmationMessageAgain(ResentConfirmation $event): bool
|
||||||
{
|
{
|
||||||
$user = $event->user;
|
return $this->sendConfirmation($event->user, $event->ipAddress);
|
||||||
$ipAddress = $event->ipAddress;
|
|
||||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
|
||||||
if ($confirmAccount === false) {
|
|
||||||
Preferences::setForUser($user, 'user_confirmed', true);
|
|
||||||
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
|
||||||
Preferences::mark();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
$email = $user->email;
|
|
||||||
$code = str_random(16);
|
/**
|
||||||
$route = route('do_confirm_account', [$code]);
|
* @param RequestedNewPassword $event
|
||||||
Preferences::setForUser($user, 'user_confirmed', false);
|
*
|
||||||
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
|
* @return bool
|
||||||
Preferences::setForUser($user, 'user_confirmed_code', $code);
|
*/
|
||||||
|
public function sendNewPassword(RequestedNewPassword $event): bool
|
||||||
|
{
|
||||||
|
$email = $event->user->email;
|
||||||
|
$ipAddress = $event->ipAddress;
|
||||||
|
$token = $event->token;
|
||||||
|
|
||||||
|
$url = route('password.reset', [$token]);
|
||||||
|
|
||||||
|
// send email.
|
||||||
try {
|
try {
|
||||||
Mail::send(
|
Mail::send(
|
||||||
['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress],
|
['emails.password-html', 'emails.password-text'], ['url' => $url, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
||||||
function (Message $message) use ($email) {
|
$message->to($email, $email)->subject('Your password reset request');
|
||||||
$message->to($email, $email)->subject('Please confirm your Firefly III account');
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (Swift_TransportException $e) {
|
} catch (Swift_TransportException $e) {
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,8 +362,8 @@ class UserEventHandler
|
|||||||
// send email.
|
// send email.
|
||||||
try {
|
try {
|
||||||
Mail::send(
|
Mail::send(
|
||||||
['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
['emails.registered-html', 'emails.registered-text'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) {
|
||||||
$message->to($email, $email)->subject('Welcome to Firefly III! ');
|
$message->to($email, $email)->subject('Welcome to Firefly III!');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (Swift_TransportException $e) {
|
} catch (Swift_TransportException $e) {
|
||||||
@@ -222,4 +405,42 @@ class UserEventHandler
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $user
|
||||||
|
* @param string $ipAddress
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function sendConfirmation(User $user, string $ipAddress): bool
|
||||||
|
{
|
||||||
|
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
|
||||||
|
if ($mustConfirmAccount === false) {
|
||||||
|
Preferences::setForUser($user, 'user_confirmed', true);
|
||||||
|
Preferences::setForUser($user, 'user_confirmed_last_mail', 0);
|
||||||
|
Preferences::mark();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$email = $user->email;
|
||||||
|
$code = str_random(16);
|
||||||
|
$route = route('do_confirm_account', [$code]);
|
||||||
|
Preferences::setForUser($user, 'user_confirmed', false);
|
||||||
|
Preferences::setForUser($user, 'user_confirmed_last_mail', time());
|
||||||
|
Preferences::setForUser($user, 'user_confirmed_code', $code);
|
||||||
|
try {
|
||||||
|
Mail::send(
|
||||||
|
['emails.confirm-account-html', 'emails.confirm-account-text'], ['route' => $route, 'ip' => $ipAddress],
|
||||||
|
function (Message $message) use ($email) {
|
||||||
|
$message->to($email, $email)->subject('Please confirm your Firefly III account');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Swift_TransportException $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,8 @@ use FireflyIII\Models\Attachment;
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\MessageBag;
|
use Illuminate\Support\MessageBag;
|
||||||
use Input;
|
use Input;
|
||||||
use Log;
|
|
||||||
use Storage;
|
use Storage;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use TypeError;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AttachmentHelper
|
* Class AttachmentHelper
|
||||||
@@ -236,14 +234,9 @@ class AttachmentHelper implements AttachmentHelperInterface
|
|||||||
private function getFiles()
|
private function getFiles()
|
||||||
{
|
{
|
||||||
$files = null;
|
$files = null;
|
||||||
try {
|
|
||||||
if (Input::hasFile('attachments')) {
|
if (Input::hasFile('attachments')) {
|
||||||
$files = Input::file('attachments');
|
$files = Input::file('attachments');
|
||||||
}
|
}
|
||||||
} catch (TypeError $e) {
|
|
||||||
// Log it, do nothing else.
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Helpers\Collection;
|
namespace FireflyIII\Helpers\Collection;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Bill
|
* Class Bill
|
||||||
@@ -26,7 +29,11 @@ class Bill
|
|||||||
/**
|
/**
|
||||||
* @var Collection
|
* @var Collection
|
||||||
*/
|
*/
|
||||||
protected $bills;
|
private $bills;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $endDate;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $startDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -44,6 +51,43 @@ class Bill
|
|||||||
$this->bills->push($bill);
|
$this->bills->push($bill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function filterBills()
|
||||||
|
{
|
||||||
|
Log::debug('Now in filterBills()');
|
||||||
|
/** @var BillRepositoryInterface $repository */
|
||||||
|
$repository = app(BillRepositoryInterface::class);
|
||||||
|
$start = $this->startDate;
|
||||||
|
$end = $this->endDate;
|
||||||
|
$lines = $this->bills->filter(
|
||||||
|
function (BillLine $line) use ($repository, $start, $end) {
|
||||||
|
// next expected match?
|
||||||
|
$date = $start;
|
||||||
|
Log::debug(sprintf('Now at bill line for bill "%s"', $line->getBill()->name));
|
||||||
|
Log::debug(sprintf('Default date to use is start date: %s', $date->format('Y-m-d')));
|
||||||
|
if ($line->isHit()) {
|
||||||
|
$date = $line->getLastHitDate();
|
||||||
|
Log::debug(sprintf('Line was hit, see date: %s. Always include it.', $date->format('Y-m-d')));
|
||||||
|
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
$expected = $repository->nextExpectedMatch($line->getBill(), $date);
|
||||||
|
Log::debug(sprintf('Next expected match is %s', $expected->format('Y-m-d')));
|
||||||
|
if ($expected <= $end && $expected >= $start) {
|
||||||
|
Log::debug('This date is inside report limits');
|
||||||
|
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
Log::debug('This date is OUTSIDE report limits');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$this->bills = $lines;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
@@ -62,4 +106,20 @@ class Bill
|
|||||||
return $set;
|
return $set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $endDate
|
||||||
|
*/
|
||||||
|
public function setEndDate(Carbon $endDate)
|
||||||
|
{
|
||||||
|
$this->endDate = $endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $startDate
|
||||||
|
*/
|
||||||
|
public function setStartDate(Carbon $startDate)
|
||||||
|
{
|
||||||
|
$this->startDate = $startDate;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
declare(strict_types = 1);
|
declare(strict_types = 1);
|
||||||
namespace FireflyIII\Helpers\Collection;
|
namespace FireflyIII\Helpers\Collection;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Models\Bill as BillModel;
|
use FireflyIII\Models\Bill as BillModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,8 +24,6 @@ use FireflyIII\Models\Bill as BillModel;
|
|||||||
class BillLine
|
class BillLine
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
protected $active;
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $amount;
|
protected $amount;
|
||||||
/** @var BillModel */
|
/** @var BillModel */
|
||||||
@@ -35,10 +34,19 @@ class BillLine
|
|||||||
protected $max;
|
protected $max;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $min;
|
protected $min;
|
||||||
|
/** @var Carbon */
|
||||||
|
private $lastHitDate;
|
||||||
/** @var int */
|
/** @var int */
|
||||||
private $transactionJournalId;
|
private $transactionJournalId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BillLine constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->lastHitDate = new Carbon;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -71,6 +79,22 @@ class BillLine
|
|||||||
$this->bill = $bill;
|
$this->bill = $bill;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Carbon
|
||||||
|
*/
|
||||||
|
public function getLastHitDate(): Carbon
|
||||||
|
{
|
||||||
|
return $this->lastHitDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $lastHitDate
|
||||||
|
*/
|
||||||
|
public function setLastHitDate(Carbon $lastHitDate)
|
||||||
|
{
|
||||||
|
$this->lastHitDate = $lastHitDate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -124,15 +148,7 @@ class BillLine
|
|||||||
*/
|
*/
|
||||||
public function isActive(): bool
|
public function isActive(): bool
|
||||||
{
|
{
|
||||||
return $this->active;
|
return intval($this->bill->active) === 1;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param bool $active
|
|
||||||
*/
|
|
||||||
public function setActive(bool $active)
|
|
||||||
{
|
|
||||||
$this->active = $active;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,4 +167,5 @@ class BillLine
|
|||||||
$this->hit = $hit;
|
$this->hit = $hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Expense.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
namespace FireflyIII\Helpers\Collection;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Class Expense
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Helpers\Collection
|
|
||||||
*/
|
|
||||||
class Expense
|
|
||||||
{
|
|
||||||
/** @var Collection */
|
|
||||||
protected $expenses;
|
|
||||||
/** @var string */
|
|
||||||
protected $total = '0';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->expenses = new Collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param stdClass $entry
|
|
||||||
*/
|
|
||||||
public function addOrCreateExpense(stdClass $entry)
|
|
||||||
{
|
|
||||||
$this->expenses->put($entry->id, $entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $add
|
|
||||||
*/
|
|
||||||
public function addToTotal(string $add)
|
|
||||||
{
|
|
||||||
$add = strval(round($add, 2));
|
|
||||||
if (bccomp('0', $add) === -1) {
|
|
||||||
$add = bcmul($add, '-1');
|
|
||||||
}
|
|
||||||
|
|
||||||
// if amount is positive, the original transaction
|
|
||||||
// was a transfer. But since this is an expense report,
|
|
||||||
// that amount must be negative.
|
|
||||||
|
|
||||||
$this->total = bcadd($this->total, $add);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function getExpenses(): Collection
|
|
||||||
{
|
|
||||||
$set = $this->expenses->sortBy(
|
|
||||||
function (stdClass $object) {
|
|
||||||
return $object->amount;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getTotal(): string
|
|
||||||
{
|
|
||||||
return strval(round($this->total, 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Income.php
|
|
||||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
|
||||||
*
|
|
||||||
* This software may be modified and distributed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
|
||||||
*
|
|
||||||
* See the LICENSE file for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
namespace FireflyIII\Helpers\Collection;
|
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Class Income
|
|
||||||
*
|
|
||||||
* @package FireflyIII\Helpers\Collection
|
|
||||||
*/
|
|
||||||
class Income
|
|
||||||
{
|
|
||||||
|
|
||||||
/** @var Collection */
|
|
||||||
protected $incomes;
|
|
||||||
/** @var string */
|
|
||||||
protected $total = '0';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->incomes = new Collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param stdClass $entry
|
|
||||||
*/
|
|
||||||
public function addOrCreateIncome(stdClass $entry)
|
|
||||||
{
|
|
||||||
$this->incomes->put($entry->id, $entry);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $add
|
|
||||||
*/
|
|
||||||
public function addToTotal(string $add)
|
|
||||||
{
|
|
||||||
$add = strval(round($add, 2));
|
|
||||||
$this->total = bcadd($this->total, $add);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function getIncomes(): Collection
|
|
||||||
{
|
|
||||||
$set = $this->incomes->sortByDesc(
|
|
||||||
function (stdClass $object) {
|
|
||||||
return $object->amount;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getTotal(): string
|
|
||||||
{
|
|
||||||
return strval(round($this->total, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
732
app/Helpers/Collector/JournalCollector.php
Normal file
732
app/Helpers/Collector/JournalCollector.php
Normal file
@@ -0,0 +1,732 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* JournalCollector.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Collector;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Crypt;
|
||||||
|
use DB;
|
||||||
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Models\AccountType;
|
||||||
|
use FireflyIII\Models\Budget;
|
||||||
|
use FireflyIII\Models\Category;
|
||||||
|
use FireflyIII\Models\Tag;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
|
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||||
|
use Illuminate\Database\Query\JoinClause;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maybe this is a good idea after all...
|
||||||
|
*
|
||||||
|
* Class JournalCollector
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Collector
|
||||||
|
*/
|
||||||
|
class JournalCollector implements JournalCollectorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $accountIds = [];
|
||||||
|
/** @var int */
|
||||||
|
private $count = 0;
|
||||||
|
/** @var array */
|
||||||
|
private $fields
|
||||||
|
= [
|
||||||
|
'transaction_journals.id as journal_id',
|
||||||
|
'transaction_journals.description',
|
||||||
|
'transaction_journals.date',
|
||||||
|
'transaction_journals.encrypted',
|
||||||
|
//'transaction_journals.transaction_currency_id',
|
||||||
|
'transaction_currencies.code as transaction_currency_code',
|
||||||
|
//'transaction_currencies.symbol as transaction_currency_symbol',
|
||||||
|
'transaction_types.type as transaction_type_type',
|
||||||
|
'transaction_journals.bill_id',
|
||||||
|
'bills.name as bill_name',
|
||||||
|
'bills.name_encrypted as bill_name_encrypted',
|
||||||
|
'transactions.id as id',
|
||||||
|
'transactions.amount as transaction_amount',
|
||||||
|
'transactions.description as transaction_description',
|
||||||
|
'transactions.account_id',
|
||||||
|
'transactions.identifier',
|
||||||
|
'transactions.transaction_journal_id',
|
||||||
|
'accounts.name as account_name',
|
||||||
|
'accounts.encrypted as account_encrypted',
|
||||||
|
'account_types.type as account_type',
|
||||||
|
];
|
||||||
|
/** @var bool */
|
||||||
|
private $filterInternalTransfers;
|
||||||
|
/** @var bool */
|
||||||
|
private $filterTransfers = false;
|
||||||
|
/** @var bool */
|
||||||
|
private $joinedBudget = false;
|
||||||
|
/** @var bool */
|
||||||
|
private $joinedCategory = false;
|
||||||
|
/** @var bool */
|
||||||
|
private $joinedOpposing = false;
|
||||||
|
/** @var bool */
|
||||||
|
private $joinedTag = false;
|
||||||
|
/** @var int */
|
||||||
|
private $limit;
|
||||||
|
/** @var int */
|
||||||
|
private $offset;
|
||||||
|
/** @var int */
|
||||||
|
private $page = 1;
|
||||||
|
/** @var EloquentBuilder */
|
||||||
|
private $query;
|
||||||
|
/** @var bool */
|
||||||
|
private $run = false;
|
||||||
|
/** @var User */
|
||||||
|
private $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JournalCollector constructor.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
*/
|
||||||
|
public function __construct(User $user)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->query = $this->startQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
if ($this->run === true) {
|
||||||
|
throw new FireflyException('Cannot count after run in JournalCollector.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$countQuery = clone $this->query;
|
||||||
|
|
||||||
|
// dont need some fields:
|
||||||
|
$countQuery->getQuery()->limit = null;
|
||||||
|
$countQuery->getQuery()->offset = null;
|
||||||
|
$countQuery->getQuery()->unionLimit = null;
|
||||||
|
$countQuery->getQuery()->groups = null;
|
||||||
|
$countQuery->getQuery()->orders = null;
|
||||||
|
$countQuery->groupBy('accounts.user_id');
|
||||||
|
$this->count = $countQuery->count();
|
||||||
|
|
||||||
|
return $this->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function disableFilter(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->filterTransfers = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function disableInternalFilter(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->filterInternalTransfers = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function enableInternalFilter(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->filterInternalTransfers = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getJournals(): Collection
|
||||||
|
{
|
||||||
|
$this->run = true;
|
||||||
|
$set = $this->query->get(array_values($this->fields));
|
||||||
|
Log::debug(sprintf('Count of set is %d', $set->count()));
|
||||||
|
$set = $this->filterTransfers($set);
|
||||||
|
Log::debug(sprintf('Count of set after filterTransfers() is %d', $set->count()));
|
||||||
|
|
||||||
|
// possibly filter "internal" transfers:
|
||||||
|
$set = $this->filterInternalTransfers($set);
|
||||||
|
Log::debug(sprintf('Count of set after filterInternalTransfers() is %d', $set->count()));
|
||||||
|
|
||||||
|
|
||||||
|
// loop for decryption.
|
||||||
|
$set->each(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
$transaction->date = new Carbon($transaction->date);
|
||||||
|
$transaction->description = $transaction->encrypted ? Crypt::decrypt($transaction->description) : $transaction->description;
|
||||||
|
|
||||||
|
if (!is_null($transaction->bill_name)) {
|
||||||
|
$transaction->bill_name = $transaction->bill_name_encrypted ? Crypt::decrypt($transaction->bill_name) : $transaction->bill_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name);
|
||||||
|
} catch (DecryptException $e) {
|
||||||
|
// if this fails its already decrypted.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return LengthAwarePaginator
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function getPaginatedJournals(): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
if ($this->run === true) {
|
||||||
|
throw new FireflyException('Cannot getPaginatedJournals after run in JournalCollector.');
|
||||||
|
}
|
||||||
|
$this->count();
|
||||||
|
$set = $this->getJournals();
|
||||||
|
$journals = new LengthAwarePaginator($set, $this->count, $this->limit, $this->page);
|
||||||
|
|
||||||
|
return $journals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
if ($accounts->count() > 0) {
|
||||||
|
$accountIds = $accounts->pluck('id')->toArray();
|
||||||
|
$this->query->whereIn('transactions.account_id', $accountIds);
|
||||||
|
Log::debug(sprintf('setAccounts: %s', join(', ', $accountIds)));
|
||||||
|
$this->accountIds = $accountIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($accounts->count() > 1) {
|
||||||
|
$this->filterTransfers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setAllAssetAccounts(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
/** @var AccountRepositoryInterface $repository */
|
||||||
|
$repository = app(AccountRepositoryInterface::class, [$this->user]);
|
||||||
|
$accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
||||||
|
if ($accounts->count() > 0) {
|
||||||
|
$accountIds = $accounts->pluck('id')->toArray();
|
||||||
|
$this->query->whereIn('transactions.account_id', $accountIds);
|
||||||
|
$this->accountIds = $accountIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($accounts->count() > 1) {
|
||||||
|
$this->filterTransfers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $bills
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setBills(Collection $bills): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
if ($bills->count() > 0) {
|
||||||
|
$billIds = $bills->pluck('id')->toArray();
|
||||||
|
$this->query->whereIn('transaction_journals.bill_id', $billIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setBudget(Budget $budget): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinBudgetTables();
|
||||||
|
|
||||||
|
$this->query->where(
|
||||||
|
function (EloquentBuilder $q) use ($budget) {
|
||||||
|
$q->where('budget_transaction.budget_id', $budget->id);
|
||||||
|
$q->orWhere('budget_transaction_journal.budget_id', $budget->id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setBudgets(Collection $budgets): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$budgetIds = $budgets->pluck('id')->toArray();
|
||||||
|
if (count($budgetIds) === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
$this->joinBudgetTables();
|
||||||
|
|
||||||
|
$this->query->where(
|
||||||
|
function (EloquentBuilder $q) use ($budgetIds) {
|
||||||
|
$q->whereIn('budget_transaction.budget_id', $budgetIds);
|
||||||
|
$q->orWhereIn('budget_transaction_journal.budget_id', $budgetIds);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $categories
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setCategories(Collection $categories): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$categoryIds = $categories->pluck('id')->toArray();
|
||||||
|
if (count($categoryIds) === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
$this->joinCategoryTables();
|
||||||
|
|
||||||
|
$this->query->where(
|
||||||
|
function (EloquentBuilder $q) use ($categoryIds) {
|
||||||
|
$q->whereIn('category_transaction.category_id', $categoryIds);
|
||||||
|
$q->orWhereIn('category_transaction_journal.category_id', $categoryIds);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Category $category
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setCategory(Category $category): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinCategoryTables();
|
||||||
|
|
||||||
|
$this->query->where(
|
||||||
|
function (EloquentBuilder $q) use ($category) {
|
||||||
|
$q->where('category_transaction.category_id', $category->id);
|
||||||
|
$q->orWhere('category_transaction_journal.category_id', $category->id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $limit
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setLimit(int $limit): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->limit = $limit;
|
||||||
|
$this->query->limit($limit);
|
||||||
|
Log::debug(sprintf('Set limit to %d', $limit));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $offset
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setOffset(int $offset): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->offset = $offset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $page
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setPage(int $page): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->page = $page;
|
||||||
|
|
||||||
|
if ($page > 0) {
|
||||||
|
$page--;
|
||||||
|
}
|
||||||
|
Log::debug(sprintf('Page is %d', $page));
|
||||||
|
|
||||||
|
if (!is_null($this->limit)) {
|
||||||
|
$offset = ($this->limit * $page);
|
||||||
|
$this->offset = $offset;
|
||||||
|
$this->query->skip($offset);
|
||||||
|
Log::debug(sprintf('Changed offset to %d', $offset));
|
||||||
|
}
|
||||||
|
if (is_null($this->limit)) {
|
||||||
|
Log::debug('The limit is zero, cannot set the page.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setRange(Carbon $start, Carbon $end): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
if ($start <= $end) {
|
||||||
|
$this->query->where('transaction_journals.date', '>=', $start->format('Y-m-d'));
|
||||||
|
$this->query->where('transaction_journals.date', '<=', $end->format('Y-m-d'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Tag $tag
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setTag(Tag $tag): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinTagTables();
|
||||||
|
$this->query->where('tag_transaction_journal.tag_id', $tag->id);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $types
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setTypes(array $types): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
if (count($types) > 0) {
|
||||||
|
Log::debug('Set query collector types', $types);
|
||||||
|
$this->query->whereIn('transaction_types.type', $types);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withBudgetInformation(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinBudgetTables();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withCategoryInformation(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->joinCategoryTables();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withOpposingAccount(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinOpposingTables();
|
||||||
|
|
||||||
|
$accountIds = $this->accountIds;
|
||||||
|
$this->query->where(
|
||||||
|
function (EloquentBuilder $q1) use ($accountIds) {
|
||||||
|
// set 1:
|
||||||
|
// where source is in the set of $accounts
|
||||||
|
// but destination is not.
|
||||||
|
$q1->where(
|
||||||
|
function (EloquentBuilder $q2) use ($accountIds) {
|
||||||
|
// transactions.account_id in set
|
||||||
|
$q2->whereIn('transactions.account_id', $accountIds);
|
||||||
|
// opposing.account_id not in set
|
||||||
|
$q2->whereNotIn('opposing.account_id', $accountIds);
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// set 1:
|
||||||
|
// where source is not in the set of $accounts
|
||||||
|
// but destination is.
|
||||||
|
$q1->orWhere(
|
||||||
|
function (EloquentBuilder $q3) use ($accountIds) {
|
||||||
|
// transactions.account_id not in set
|
||||||
|
$q3->whereNotIn('transactions.account_id', $accountIds);
|
||||||
|
// B in set
|
||||||
|
// opposing.account_id not in set
|
||||||
|
$q3->whereIn('opposing.account_id', $accountIds);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withoutBudget(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinBudgetTables();
|
||||||
|
|
||||||
|
$this->query->where(
|
||||||
|
function (EloquentBuilder $q) {
|
||||||
|
$q->whereNull('budget_transaction.budget_id');
|
||||||
|
$q->whereNull('budget_transaction_journal.budget_id');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withoutCategory(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->joinCategoryTables();
|
||||||
|
|
||||||
|
$this->query->where(
|
||||||
|
function (EloquentBuilder $q) {
|
||||||
|
$q->whereNull('category_transaction.category_id');
|
||||||
|
$q->whereNull('category_transaction_journal.category_id');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function filterInternalTransfers(Collection $set): Collection
|
||||||
|
{
|
||||||
|
if ($this->filterInternalTransfers === false) {
|
||||||
|
Log::debug('Did NO filtering for internal transfers on given set.');
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
if ($this->joinedOpposing === false) {
|
||||||
|
Log::error('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.
|
||||||
|
*
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function filterTransfers(Collection $set): Collection
|
||||||
|
{
|
||||||
|
if ($this->filterTransfers) {
|
||||||
|
$set = $set->filter(
|
||||||
|
function (Transaction $transaction) {
|
||||||
|
if (!($transaction->transaction_type_type === TransactionType::TRANSFER && bccomp($transaction->transaction_amount, '0') === -1)) {
|
||||||
|
|
||||||
|
Log::debug(
|
||||||
|
sprintf(
|
||||||
|
'Included journal #%d (transaction #%d) because its a %s with amount %f',
|
||||||
|
$transaction->transaction_journal_id,
|
||||||
|
$transaction->id,
|
||||||
|
$transaction->transaction_type_type,
|
||||||
|
$transaction->transaction_amount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug(
|
||||||
|
sprintf(
|
||||||
|
'Removed journal #%d (transaction #%d) because its a %s with amount %f',
|
||||||
|
$transaction->transaction_journal_id,
|
||||||
|
$transaction->id,
|
||||||
|
$transaction->transaction_type_type,
|
||||||
|
$transaction->transaction_amount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function joinBudgetTables()
|
||||||
|
{
|
||||||
|
if (!$this->joinedBudget) {
|
||||||
|
// join some extra tables:
|
||||||
|
$this->joinedBudget = true;
|
||||||
|
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
|
||||||
|
$this->query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
|
||||||
|
$this->fields[] = 'budget_transaction_journal.budget_id as transaction_journal_budget_id';
|
||||||
|
$this->fields[] = 'budget_transaction.budget_id as transaction_budget_id';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function joinCategoryTables()
|
||||||
|
{
|
||||||
|
if (!$this->joinedCategory) {
|
||||||
|
// join some extra tables:
|
||||||
|
$this->joinedCategory = true;
|
||||||
|
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
|
||||||
|
$this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
|
||||||
|
$this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id';
|
||||||
|
$this->fields[] = 'category_transaction.category_id as transaction_category_id';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function joinOpposingTables()
|
||||||
|
{
|
||||||
|
if (!$this->joinedOpposing) {
|
||||||
|
Log::debug('joinedOpposing is false');
|
||||||
|
// join opposing transaction (hard):
|
||||||
|
$this->query->leftJoin(
|
||||||
|
'transactions as opposing', function (JoinClause $join) {
|
||||||
|
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
|
||||||
|
->where('opposing.identifier', '=', DB::raw('transactions.identifier'))
|
||||||
|
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$this->query->leftJoin('accounts as opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id');
|
||||||
|
$this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id');
|
||||||
|
$this->query->whereNull('opposing.deleted_at');
|
||||||
|
|
||||||
|
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
||||||
|
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
||||||
|
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
||||||
|
$this->fields[] = 'opposing_account_types.type as opposing_account_type';
|
||||||
|
$this->joinedOpposing = true;
|
||||||
|
Log::debug('joinedOpposing is now true!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function joinTagTables()
|
||||||
|
{
|
||||||
|
if (!$this->joinedTag) {
|
||||||
|
// join some extra tables:
|
||||||
|
$this->joinedTag = true;
|
||||||
|
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return EloquentBuilder
|
||||||
|
*/
|
||||||
|
private function startQuery(): EloquentBuilder
|
||||||
|
{
|
||||||
|
/** @var EloquentBuilder $query */
|
||||||
|
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||||
|
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
|
||||||
|
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||||
|
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
|
||||||
|
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||||
|
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
|
||||||
|
->whereNull('transactions.deleted_at')
|
||||||
|
->whereNull('transaction_journals.deleted_at')
|
||||||
|
->where('transaction_journals.user_id', $this->user->id)
|
||||||
|
->orderBy('transaction_journals.date', 'DESC')
|
||||||
|
->orderBy('transaction_journals.order', 'ASC')
|
||||||
|
->orderBy('transaction_journals.id', 'DESC');
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
176
app/Helpers/Collector/JournalCollectorInterface.php
Normal file
176
app/Helpers/Collector/JournalCollectorInterface.php
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* JournalCollectorInterface.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Helpers\Collector;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Models\Budget;
|
||||||
|
use FireflyIII\Models\Category;
|
||||||
|
use FireflyIII\Models\Tag;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface JournalCollectorInterface
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Helpers\Collector
|
||||||
|
*/
|
||||||
|
interface JournalCollectorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function disableFilter(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function disableInternalFilter(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function enableInternalFilter(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getJournals(): Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return LengthAwarePaginator
|
||||||
|
*/
|
||||||
|
public function getPaginatedJournals(): LengthAwarePaginator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setAccounts(Collection $accounts): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setAllAssetAccounts(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $bills
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setBills(Collection $bills): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setBudget(Budget $budget): JournalCollectorInterface;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setBudgets(Collection $budgets): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $categories
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setCategories(Collection $categories): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Category $category
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setCategory(Category $category): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $limit
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setLimit(int $limit): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $offset
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setOffset(int $offset): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $page
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setPage(int $page): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setRange(Carbon $start, Carbon $end): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Tag $tag
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setTag(Tag $tag): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $types
|
||||||
|
*
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setTypes(array $types): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withBudgetInformation(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withCategoryInformation(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withOpposingAccount(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withoutBudget(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function withoutCategory(): JournalCollectorInterface;
|
||||||
|
}
|
||||||
@@ -14,7 +14,9 @@ namespace FireflyIII\Helpers\Help;
|
|||||||
|
|
||||||
use Cache;
|
use Cache;
|
||||||
use League\CommonMark\CommonMarkConverter;
|
use League\CommonMark\CommonMarkConverter;
|
||||||
|
use Log;
|
||||||
use Requests;
|
use Requests;
|
||||||
|
use Requests_Exception;
|
||||||
use Route;
|
use Route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,6 +26,8 @@ use Route;
|
|||||||
*/
|
*/
|
||||||
class Help implements HelpInterface
|
class Help implements HelpInterface
|
||||||
{
|
{
|
||||||
|
/** @var string */
|
||||||
|
protected $userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $route
|
* @param string $route
|
||||||
@@ -33,7 +37,9 @@ class Help implements HelpInterface
|
|||||||
*/
|
*/
|
||||||
public function getFromCache(string $route, string $language): string
|
public function getFromCache(string $route, string $language): string
|
||||||
{
|
{
|
||||||
return Cache::get('help.' . $route . '.' . $language);
|
$line = sprintf('help.%s.%s', $route, $language);
|
||||||
|
|
||||||
|
return Cache::get($line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,19 +52,31 @@ class Help implements HelpInterface
|
|||||||
{
|
{
|
||||||
|
|
||||||
$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);
|
||||||
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
|
Log::debug(sprintf('Trying to get %s...', $uri));
|
||||||
$result = Requests::get($uri);
|
$opt = ['useragent' => $this->userAgent];
|
||||||
|
$content = '';
|
||||||
|
try {
|
||||||
|
$result = Requests::get($uri, [], $opt);
|
||||||
|
} catch (Requests_Exception $e) {
|
||||||
|
Log::error($e);
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Log::debug(sprintf('Status code is %d', $result->status_code));
|
||||||
|
|
||||||
if ($result->status_code === 200) {
|
if ($result->status_code === 200) {
|
||||||
$content = $result->body;
|
$content = trim($result->body);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (strlen(trim($content)) == 0) {
|
|
||||||
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
|
|
||||||
}
|
}
|
||||||
|
if (strlen($content) > 0) {
|
||||||
|
Log::debug('Content is longer than zero. Expect something.');
|
||||||
$converter = new CommonMarkConverter();
|
$converter = new CommonMarkConverter();
|
||||||
$content = $converter->convertToHtml($content);
|
$content = $converter->convertToHtml($content);
|
||||||
|
}
|
||||||
|
if (strlen($content) === 0) {
|
||||||
|
Log::warning('Raw content length is zero.');
|
||||||
|
}
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
|
|
||||||
@@ -70,7 +88,7 @@ class Help implements HelpInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function hasRoute(string $route):bool
|
public function hasRoute(string $route): bool
|
||||||
{
|
{
|
||||||
return Route::has($route);
|
return Route::has($route);
|
||||||
}
|
}
|
||||||
@@ -81,9 +99,19 @@ class Help implements HelpInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function inCache(string $route, string $language):bool
|
public function inCache(string $route, string $language): bool
|
||||||
{
|
{
|
||||||
return Cache::has('help.' . $route . '.' . $language);
|
$line = sprintf('help.%s.%s', $route, $language);
|
||||||
|
$result = Cache::has($line);
|
||||||
|
if ($result) {
|
||||||
|
Log::debug(sprintf('Cache has this entry: %s', 'help.' . $route . '.' . $language));
|
||||||
|
}
|
||||||
|
if (!$result) {
|
||||||
|
Log::debug(sprintf('Cache does not have this entry: %s', 'help.' . $route . '.' . $language));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,10 +120,15 @@ class Help implements HelpInterface
|
|||||||
* @param string $language
|
* @param string $language
|
||||||
* @param string $content
|
* @param string $content
|
||||||
*
|
*
|
||||||
* @internal param $title
|
|
||||||
*/
|
*/
|
||||||
public function putInCache(string $route, string $language, string $content)
|
public function putInCache(string $route, string $language, string $content)
|
||||||
{
|
{
|
||||||
Cache::put('help.' . $route . '.' . $language, $content, 10080); // a week.
|
$key = sprintf('help.%s.%s', $route, $language);
|
||||||
|
if (strlen($content) > 0) {
|
||||||
|
Log::debug(sprintf('Will store entry in cache: %s', $key));
|
||||||
|
Cache::put($key, $content, 10080); // a week.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log::info(sprintf('Will not cache %s because content is empty.', $key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ interface HelpInterface
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getFromGithub(string $language, string $route):string;
|
public function getFromGithub(string $language, string $route): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $route
|
* @param string $route
|
||||||
@@ -49,7 +49,7 @@ interface HelpInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function inCache(string $route, string $language ): bool;
|
public function inCache(string $route, string $language): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $route
|
* @param string $route
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use FireflyIII\Models\TransactionType;
|
|||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use Illuminate\Database\Query\JoinClause;
|
use Illuminate\Database\Query\JoinClause;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BalanceReportHelper
|
* Class BalanceReportHelper
|
||||||
@@ -51,27 +52,31 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
*
|
||||||
* @return Balance
|
* @return Balance
|
||||||
*/
|
*/
|
||||||
public function getBalanceReport(Carbon $start, Carbon $end, Collection $accounts): Balance
|
public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): Balance
|
||||||
{
|
{
|
||||||
|
Log::debug('Start of balance report');
|
||||||
$balance = new Balance;
|
$balance = new Balance;
|
||||||
$header = new BalanceHeader;
|
$header = new BalanceHeader;
|
||||||
$limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end);
|
$limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end);
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
|
Log::debug(sprintf('Add account %s to headers.', $account->name));
|
||||||
$header->addAccount($account);
|
$header->addAccount($account);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var LimitRepetition $repetition */
|
/** @var LimitRepetition $repetition */
|
||||||
foreach ($limitRepetitions as $repetition) {
|
foreach ($limitRepetitions as $repetition) {
|
||||||
$budget = $this->budgetRepository->find($repetition->budget_id);
|
$budget = $this->budgetRepository->find($repetition->budget_id);
|
||||||
|
Log::debug(sprintf('Create balance line for budget #%d ("%s") and repetition #%d', $budget->id, $budget->name, $repetition->id));
|
||||||
$line = $this->createBalanceLine($budget, $repetition, $accounts);
|
$line = $this->createBalanceLine($budget, $repetition, $accounts);
|
||||||
$balance->addBalanceLine($line);
|
$balance->addBalanceLine($line);
|
||||||
}
|
}
|
||||||
|
Log::debug('Create rest of the things.');
|
||||||
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
|
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
|
||||||
$coveredByTagLine = $this->createTagsBalanceLine($accounts, $start, $end);
|
$coveredByTagLine = $this->createTagsBalanceLine($accounts, $start, $end);
|
||||||
$leftUnbalancedLine = $this->createLeftUnbalancedLine($noBudgetLine, $coveredByTagLine);
|
$leftUnbalancedLine = $this->createLeftUnbalancedLine($noBudgetLine, $coveredByTagLine);
|
||||||
@@ -81,9 +86,12 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
|||||||
$balance->addBalanceLine($leftUnbalancedLine);
|
$balance->addBalanceLine($leftUnbalancedLine);
|
||||||
$balance->setBalanceHeader($header);
|
$balance->setBalanceHeader($header);
|
||||||
|
|
||||||
|
Log::debug('Clear unused budgets.');
|
||||||
// remove budgets without expenses from balance lines:
|
// remove budgets without expenses from balance lines:
|
||||||
$balance = $this->removeUnusedBudgets($balance);
|
$balance = $this->removeUnusedBudgets($balance);
|
||||||
|
|
||||||
|
Log::debug('Return report.');
|
||||||
|
|
||||||
return $balance;
|
return $balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ use Illuminate\Support\Collection;
|
|||||||
interface BalanceReportHelperInterface
|
interface BalanceReportHelperInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
*
|
||||||
* @return Balance
|
* @return Balance
|
||||||
*/
|
*/
|
||||||
public function getBalanceReport(Carbon $start, Carbon $end, Collection $accounts): Balance;
|
public function getBalanceReport(Collection $accounts, Carbon $start, Carbon $end): Balance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use FireflyIII\Helpers\Collection\BudgetLine;
|
|||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\LimitRepetition;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,60 +42,6 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
|||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // at 43, its ok.
|
|
||||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function budgetYearOverview(Carbon $start, Carbon $end, Collection $accounts): Collection
|
|
||||||
{
|
|
||||||
// chart properties for cache:
|
|
||||||
$cache = new CacheProperties;
|
|
||||||
$cache->addProperty($start);
|
|
||||||
$cache->addProperty($end);
|
|
||||||
$cache->addProperty('budget-year');
|
|
||||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
|
||||||
if ($cache->has()) {
|
|
||||||
return $cache->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
$current = clone $start;
|
|
||||||
$return = new Collection;
|
|
||||||
$set = $this->repository->getBudgets();
|
|
||||||
$budgets = [];
|
|
||||||
$spent = [];
|
|
||||||
$headers = $this->createYearHeaders($current, $end);
|
|
||||||
|
|
||||||
/** @var Budget $budget */
|
|
||||||
foreach ($set as $budget) {
|
|
||||||
$id = $budget->id;
|
|
||||||
$budgets[$id] = $budget->name;
|
|
||||||
$current = clone $start;
|
|
||||||
$budgetData = $this->getBudgetSpentData($current, $end, $budget, $accounts);
|
|
||||||
$sum = $budgetData['sum'];
|
|
||||||
$spent[$id] = $budgetData['spent'];
|
|
||||||
|
|
||||||
if (bccomp('0', $sum) === 0) {
|
|
||||||
// not spent anything.
|
|
||||||
unset($spent[$id]);
|
|
||||||
unset($budgets[$id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$return->put('headers', $headers);
|
|
||||||
$return->put('budgets', $budgets);
|
|
||||||
$return->put('spent', $spent);
|
|
||||||
|
|
||||||
$cache->store($return);
|
|
||||||
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
@@ -182,33 +127,6 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
|||||||
return $set;
|
return $set;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
|
|
||||||
* and sum up everything in the array in the given range.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param array $array
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function getSumOfRange(Carbon $start, Carbon $end, array $array)
|
|
||||||
{
|
|
||||||
$sum = '0';
|
|
||||||
$currentStart = clone $start; // to not mess with the original one
|
|
||||||
$currentEnd = clone $end; // to not mess with the original one
|
|
||||||
|
|
||||||
while ($currentStart <= $currentEnd) {
|
|
||||||
$date = $currentStart->format('Y-m-d');
|
|
||||||
if (isset($array[$date])) {
|
|
||||||
$sum = bcadd($sum, $array[$date]);
|
|
||||||
}
|
|
||||||
$currentStart->addDay();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Budget $budget
|
* @param Budget $budget
|
||||||
* @param LimitRepetition $repetition
|
* @param LimitRepetition $repetition
|
||||||
@@ -228,50 +146,4 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
|||||||
return $array;
|
return $array;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Carbon $current
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function createYearHeaders(Carbon $current, Carbon $end): array
|
|
||||||
{
|
|
||||||
$headers = [];
|
|
||||||
while ($current < $end) {
|
|
||||||
$short = $current->format('m-Y');
|
|
||||||
$headers[$short] = $current->formatLocalized((string)trans('config.month'));
|
|
||||||
$current->addMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Carbon $current
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Budget $budget
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getBudgetSpentData(Carbon $current, Carbon $end, Budget $budget, Collection $accounts): array
|
|
||||||
{
|
|
||||||
$sum = '0';
|
|
||||||
$spent = [];
|
|
||||||
while ($current < $end) {
|
|
||||||
$currentEnd = clone $current;
|
|
||||||
$currentEnd->endOfMonth();
|
|
||||||
$format = $current->format('m-Y');
|
|
||||||
$budgetSpent = $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $current, $currentEnd);
|
|
||||||
$spent[$format] = $budgetSpent;
|
|
||||||
$sum = bcadd($sum, $budgetSpent);
|
|
||||||
$current->addMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'spent' => $spent,
|
|
||||||
'sum' => $sum,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,6 @@ use Illuminate\Support\Collection;
|
|||||||
*/
|
*/
|
||||||
interface BudgetReportHelperInterface
|
interface BudgetReportHelperInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function budgetYearOverview(Carbon $start, Carbon $end, Collection $accounts): Collection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
|
|||||||
@@ -17,22 +17,15 @@ use Carbon\Carbon;
|
|||||||
use FireflyIII\Helpers\Collection\Bill as BillCollection;
|
use FireflyIII\Helpers\Collection\Bill as BillCollection;
|
||||||
use FireflyIII\Helpers\Collection\BillLine;
|
use FireflyIII\Helpers\Collection\BillLine;
|
||||||
use FireflyIII\Helpers\Collection\Category as CategoryCollection;
|
use FireflyIII\Helpers\Collection\Category as CategoryCollection;
|
||||||
use FireflyIII\Helpers\Collection\Expense;
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Helpers\Collection\Income;
|
|
||||||
use FireflyIII\Helpers\FiscalHelperInterface;
|
use FireflyIII\Helpers\FiscalHelperInterface;
|
||||||
use FireflyIII\Models\Bill;
|
use FireflyIII\Models\Bill;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
use FireflyIII\Models\Tag;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
|
||||||
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;
|
||||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Query\JoinClause;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ReportHelper
|
* Class ReportHelper
|
||||||
@@ -44,20 +37,16 @@ class ReportHelper implements ReportHelperInterface
|
|||||||
|
|
||||||
/** @var BudgetRepositoryInterface */
|
/** @var BudgetRepositoryInterface */
|
||||||
protected $budgetRepository;
|
protected $budgetRepository;
|
||||||
/** @var TagRepositoryInterface */
|
|
||||||
protected $tagRepository;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ReportHelper constructor.
|
* ReportHelper constructor.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param BudgetRepositoryInterface $budgetRepository
|
* @param BudgetRepositoryInterface $budgetRepository
|
||||||
* @param TagRepositoryInterface $tagRepository
|
|
||||||
*/
|
*/
|
||||||
public function __construct(BudgetRepositoryInterface $budgetRepository, TagRepositoryInterface $tagRepository)
|
public function __construct(BudgetRepositoryInterface $budgetRepository)
|
||||||
{
|
{
|
||||||
$this->budgetRepository = $budgetRepository;
|
$this->budgetRepository = $budgetRepository;
|
||||||
$this->tagRepository = $tagRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,37 +66,44 @@ class ReportHelper implements ReportHelperInterface
|
|||||||
/** @var BillRepositoryInterface $repository */
|
/** @var BillRepositoryInterface $repository */
|
||||||
$repository = app(BillRepositoryInterface::class);
|
$repository = app(BillRepositoryInterface::class);
|
||||||
$bills = $repository->getBillsForAccounts($accounts);
|
$bills = $repository->getBillsForAccounts($accounts);
|
||||||
$journals = $repository->getAllJournalsInRange($bills, $start, $end);
|
$collector = app(JournalCollectorInterface::class, [auth()->user()]);
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setBills($bills);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
$collection = new BillCollection;
|
$collection = new BillCollection;
|
||||||
|
$collection->setStartDate($start);
|
||||||
|
$collection->setEndDate($end);
|
||||||
|
|
||||||
/** @var Bill $bill */
|
/** @var Bill $bill */
|
||||||
foreach ($bills as $bill) {
|
foreach ($bills as $bill) {
|
||||||
$billLine = new BillLine;
|
$billLine = new BillLine;
|
||||||
$billLine->setBill($bill);
|
$billLine->setBill($bill);
|
||||||
$billLine->setActive(intval($bill->active) === 1);
|
|
||||||
$billLine->setMin(strval($bill->amount_min));
|
$billLine->setMin(strval($bill->amount_min));
|
||||||
$billLine->setMax(strval($bill->amount_max));
|
$billLine->setMax(strval($bill->amount_max));
|
||||||
$billLine->setHit(false);
|
$billLine->setHit(false);
|
||||||
// is hit in period?
|
// is hit in period?
|
||||||
|
|
||||||
$entry = $journals->filter(
|
$entry = $journals->filter(
|
||||||
function (TransactionJournal $journal) use ($bill) {
|
function (Transaction $transaction) use ($bill) {
|
||||||
return $journal->bill_id === $bill->id;
|
return $transaction->bill_id === $bill->id;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$first = $entry->first();
|
$first = $entry->first();
|
||||||
if (!is_null($first)) {
|
if (!is_null($first)) {
|
||||||
$billLine->setTransactionJournalId($first->id);
|
$billLine->setTransactionJournalId($first->id);
|
||||||
$billLine->setAmount($first->journalAmount);
|
$billLine->setAmount($first->transaction_amount);
|
||||||
|
$billLine->setLastHitDate($first->date);
|
||||||
$billLine->setHit(true);
|
$billLine->setHit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// non active AND non hit? do not add:
|
// bill is active, or bill is hit:
|
||||||
if ($billLine->isActive() || $billLine->isHit()) {
|
if ($billLine->isActive() || $billLine->isHit()) {
|
||||||
$collection->addBill($billLine);
|
$collection->addBill($billLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do some extra filtering.
|
||||||
|
$collection->filterBills();
|
||||||
|
|
||||||
return $collection;
|
return $collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +114,7 @@ class ReportHelper implements ReportHelperInterface
|
|||||||
*
|
*
|
||||||
* @return CategoryCollection
|
* @return CategoryCollection
|
||||||
*/
|
*/
|
||||||
public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts): CategoryCollection
|
public function getCategoryReport(Collection $accounts, Carbon $start, Carbon $end): CategoryCollection
|
||||||
{
|
{
|
||||||
$object = new CategoryCollection;
|
$object = new CategoryCollection;
|
||||||
/** @var CategoryRepositoryInterface $repository */
|
/** @var CategoryRepositoryInterface $repository */
|
||||||
@@ -136,57 +132,6 @@ class ReportHelper implements ReportHelperInterface
|
|||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a full report on the users expenses during the period for a list of accounts.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return Expense
|
|
||||||
*/
|
|
||||||
public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): Expense
|
|
||||||
{
|
|
||||||
$object = new Expense;
|
|
||||||
|
|
||||||
/** @var AccountTaskerInterface $tasker */
|
|
||||||
$tasker = app(AccountTaskerInterface::class);
|
|
||||||
$collection = $tasker->expenseReport($accounts, $accounts, $start, $end);
|
|
||||||
|
|
||||||
/** @var stdClass $entry */
|
|
||||||
foreach ($collection as $entry) {
|
|
||||||
$object->addToTotal($entry->amount);
|
|
||||||
$object->addOrCreateExpense($entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $object;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a full report on the users incomes during the period for the given accounts.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return Income
|
|
||||||
*/
|
|
||||||
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): Income
|
|
||||||
{
|
|
||||||
$object = new Income;
|
|
||||||
/** @var AccountTaskerInterface $tasker */
|
|
||||||
$tasker = app(AccountTaskerInterface::class);
|
|
||||||
$collection = $tasker->incomeReport($accounts, $accounts, $start, $end);
|
|
||||||
|
|
||||||
/** @var stdClass $entry */
|
|
||||||
foreach ($collection as $entry) {
|
|
||||||
$object->addToTotal($entry->amount);
|
|
||||||
$object->addOrCreateIncome($entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $object;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Carbon $date
|
* @param Carbon $date
|
||||||
*
|
*
|
||||||
@@ -231,104 +176,4 @@ class ReportHelper implements ReportHelperInterface
|
|||||||
return $months;
|
return $months;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of tags and their comparitive size with amounts bla bla.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function tagReport(Carbon $start, Carbon $end, Collection $accounts): array
|
|
||||||
{
|
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
|
||||||
$set = Tag::
|
|
||||||
leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
|
|
||||||
->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
|
|
||||||
->leftJoin(
|
|
||||||
'transactions AS source', function (JoinClause $join) {
|
|
||||||
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0');
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->leftJoin(
|
|
||||||
'transactions AS destination', function (JoinClause $join) {
|
|
||||||
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', '0');
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
|
||||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
|
||||||
->where(
|
|
||||||
// source.account_id in accountIds XOR destination.account_id in accountIds
|
|
||||||
function (Builder $query) use ($ids) {
|
|
||||||
$query->where(
|
|
||||||
function (Builder $q1) use ($ids) {
|
|
||||||
$q1->whereIn('source.account_id', $ids)
|
|
||||||
->whereNotIn('destination.account_id', $ids);
|
|
||||||
}
|
|
||||||
)->orWhere(
|
|
||||||
function (Builder $q2) use ($ids) {
|
|
||||||
$q2->whereIn('destination.account_id', $ids)
|
|
||||||
->whereNotIn('source.account_id', $ids);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->get(['tags.id', 'tags.tag', 'transaction_journals.id as journal_id', 'destination.amount']);
|
|
||||||
$collection = [];
|
|
||||||
if ($set->count() === 0) {
|
|
||||||
return $collection;
|
|
||||||
}
|
|
||||||
/** @var Tag $entry */
|
|
||||||
foreach ($set as $entry) {
|
|
||||||
// less than zero? multiply to be above zero.
|
|
||||||
$amount = $entry->amount;
|
|
||||||
$id = intval($entry->id);
|
|
||||||
$previousAmount = $collection[$id]['amount'] ?? '0';
|
|
||||||
$collection[$id] = [
|
|
||||||
'id' => $id,
|
|
||||||
'tag' => $entry->tag,
|
|
||||||
'amount' => bcadd($previousAmount, $amount),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup collection (match "fonts")
|
|
||||||
$max = strval(max(array_column($collection, 'amount')));
|
|
||||||
foreach ($collection as $id => $entry) {
|
|
||||||
$size = bcdiv($entry['amount'], $max, 4);
|
|
||||||
if (bccomp($size, '0.25') === -1) {
|
|
||||||
$size = '0.5';
|
|
||||||
}
|
|
||||||
$collection[$id]['fontsize'] = $size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
|
|
||||||
* and sum up everything in the array in the given range.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param array $array
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function getSumOfRange(Carbon $start, Carbon $end, array $array)
|
|
||||||
{
|
|
||||||
$sum = '0';
|
|
||||||
$currentStart = clone $start; // to not mess with the original one
|
|
||||||
$currentEnd = clone $end; // to not mess with the original one
|
|
||||||
|
|
||||||
while ($currentStart <= $currentEnd) {
|
|
||||||
$date = $currentStart->format('Y-m-d');
|
|
||||||
if (isset($array[$date])) {
|
|
||||||
$sum = bcadd($sum, $array[$date]);
|
|
||||||
}
|
|
||||||
$currentStart->addDay();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sum;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,29 +49,7 @@ interface ReportHelperInterface
|
|||||||
*
|
*
|
||||||
* @return CategoryCollection
|
* @return CategoryCollection
|
||||||
*/
|
*/
|
||||||
public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts): CategoryCollection;
|
public function getCategoryReport(Collection $accounts, Carbon $start, Carbon $end): CategoryCollection;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a full report on the users expenses during the period for a list of accounts.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return Expense
|
|
||||||
*/
|
|
||||||
public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): Expense;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a full report on the users incomes during the period for the given accounts.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return Income
|
|
||||||
*/
|
|
||||||
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): Income;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Carbon $date
|
* @param Carbon $date
|
||||||
@@ -80,15 +58,4 @@ interface ReportHelperInterface
|
|||||||
*/
|
*/
|
||||||
public function listOfMonths(Carbon $date): array;
|
public function listOfMonths(Carbon $date): array;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of tags and their comparitive size with amounts bla bla.
|
|
||||||
*
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function tagReport(Carbon $start, Carbon $end, Collection $accounts): array;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,19 +13,24 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
|
use Amount;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use ExpandedForm;
|
use ExpandedForm;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Http\Requests\AccountFormRequest;
|
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\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
|
||||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||||
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Input;
|
use Input;
|
||||||
|
use Log;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Session;
|
use Session;
|
||||||
@@ -65,9 +70,18 @@ class AccountController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create(string $what = 'asset')
|
public function create(string $what = 'asset')
|
||||||
{
|
{
|
||||||
|
/** @var CurrencyRepositoryInterface $repository */
|
||||||
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
|
$currencies = ExpandedForm::makeSelectList($repository->get());
|
||||||
|
$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');
|
||||||
Session::flash('preFilled', []);
|
Session::flash(
|
||||||
|
'preFilled',
|
||||||
|
[
|
||||||
|
'currency_id' => $defaultCurrency->id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
// put previous url in session if not redirect from store (not "create another").
|
// put previous url in session if not redirect from store (not "create another").
|
||||||
if (session('accounts.create.fromStore') !== true) {
|
if (session('accounts.create.fromStore') !== true) {
|
||||||
@@ -77,7 +91,7 @@ class AccountController extends Controller
|
|||||||
Session::flash('gaEventCategory', 'accounts');
|
Session::flash('gaEventCategory', 'accounts');
|
||||||
Session::flash('gaEventAction', 'create-' . $what);
|
Session::flash('gaEventAction', 'create-' . $what);
|
||||||
|
|
||||||
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle'));
|
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencies'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +127,7 @@ class AccountController extends Controller
|
|||||||
$type = $account->accountType->type;
|
$type = $account->accountType->type;
|
||||||
$typeName = config('firefly.shortNamesByFullName.' . $type);
|
$typeName = config('firefly.shortNamesByFullName.' . $type);
|
||||||
$name = $account->name;
|
$name = $account->name;
|
||||||
|
$accountId = $account->id;
|
||||||
$moveTo = $repository->find(intval(Input::get('move_account_before_delete')));
|
$moveTo = $repository->find(intval(Input::get('move_account_before_delete')));
|
||||||
|
|
||||||
$repository->destroy($account, $moveTo);
|
$repository->destroy($account, $moveTo);
|
||||||
@@ -120,7 +135,13 @@ class AccountController extends Controller
|
|||||||
Session::flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name])));
|
Session::flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
return redirect(session('accounts.delete.url'));
|
$uri = session('accounts.delete.url');
|
||||||
|
if (!(strpos($uri, sprintf('accounts/show/%s', $accountId)) === false)) {
|
||||||
|
// uri would point back to account
|
||||||
|
$uri = route('accounts.index', [$typeName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect($uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,6 +155,9 @@ class AccountController extends Controller
|
|||||||
$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 */
|
||||||
|
$repository = app(CurrencyRepositoryInterface::class);
|
||||||
|
$currencies = ExpandedForm::makeSelectList($repository->get());
|
||||||
|
|
||||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||||
if (session('accounts.edit.fromUpdate') !== true) {
|
if (session('accounts.edit.fromUpdate') !== true) {
|
||||||
@@ -154,15 +178,17 @@ class AccountController extends Controller
|
|||||||
'accountRole' => $account->getMeta('accountRole'),
|
'accountRole' => $account->getMeta('accountRole'),
|
||||||
'ccType' => $account->getMeta('ccType'),
|
'ccType' => $account->getMeta('ccType'),
|
||||||
'ccMonthlyPaymentDate' => $account->getMeta('ccMonthlyPaymentDate'),
|
'ccMonthlyPaymentDate' => $account->getMeta('ccMonthlyPaymentDate'),
|
||||||
|
'BIC' => $account->getMeta('BIC'),
|
||||||
'openingBalanceDate' => $openingBalanceDate,
|
'openingBalanceDate' => $openingBalanceDate,
|
||||||
'openingBalance' => $openingBalanceAmount,
|
'openingBalance' => $openingBalanceAmount,
|
||||||
'virtualBalance' => $account->virtual_balance,
|
'virtualBalance' => $account->virtual_balance,
|
||||||
|
'currency_id' => $account->getMeta('currency_id'),
|
||||||
];
|
];
|
||||||
Session::flash('preFilled', $preFilled);
|
Session::flash('preFilled', $preFilled);
|
||||||
Session::flash('gaEventCategory', 'accounts');
|
Session::flash('gaEventCategory', 'accounts');
|
||||||
Session::flash('gaEventAction', 'edit-' . $what);
|
Session::flash('gaEventAction', 'edit-' . $what);
|
||||||
|
|
||||||
return view('accounts.edit', compact('account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what'));
|
return view('accounts.edit', compact('currencies', 'account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -174,7 +200,6 @@ class AccountController extends Controller
|
|||||||
public function index(ARI $repository, string $what)
|
public function index(ARI $repository, string $what)
|
||||||
{
|
{
|
||||||
$what = $what ?? 'asset';
|
$what = $what ?? 'asset';
|
||||||
|
|
||||||
$subTitle = trans('firefly.' . $what . '_accounts');
|
$subTitle = trans('firefly.' . $what . '_accounts');
|
||||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||||
$types = config('firefly.accountTypesByIdentifier.' . $what);
|
$types = config('firefly.accountTypesByIdentifier.' . $what);
|
||||||
@@ -195,6 +220,7 @@ class AccountController extends Controller
|
|||||||
$account->lastActivityDate = $this->isInArray($activities, $account->id);
|
$account->lastActivityDate = $this->isInArray($activities, $account->id);
|
||||||
$account->startBalance = $this->isInArray($startBalances, $account->id);
|
$account->startBalance = $this->isInArray($startBalances, $account->id);
|
||||||
$account->endBalance = $this->isInArray($endBalances, $account->id);
|
$account->endBalance = $this->isInArray($endBalances, $account->id);
|
||||||
|
$account->difference = bcsub($account->endBalance, $account->startBalance);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -202,13 +228,12 @@ class AccountController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $tasker
|
* @param JournalCollectorInterface $collector
|
||||||
* @param ARI $repository
|
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function show(AccountTaskerInterface $tasker, ARI $repository, Account $account)
|
public function show(JournalCollectorInterface $collector, Account $account)
|
||||||
{
|
{
|
||||||
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
||||||
return $this->redirectToOriginalAccount($account);
|
return $this->redirectToOriginalAccount($account);
|
||||||
@@ -217,88 +242,75 @@ class AccountController extends Controller
|
|||||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||||
$subTitle = $account->name;
|
$subTitle = $account->name;
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
/** @var Carbon $start */
|
|
||||||
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
|
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||||
/** @var Carbon $end */
|
|
||||||
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
$page = intval(Input::get('page'));
|
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$offset = ($page - 1) * $pageSize;
|
$chartUri = route('chart.account.single', [$account->id]);
|
||||||
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
|
|
||||||
$count = $set->count();
|
// grab those journals:
|
||||||
$subSet = $set->splice($offset, $pageSize);
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
|
||||||
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('accounts/show/' . $account->id);
|
$journals->setPath('accounts/show/' . $account->id);
|
||||||
|
|
||||||
// grouped other months thing:
|
// generate entries for each period (and cache those)
|
||||||
// oldest transaction in account:
|
$entries = $this->periodEntries($account);
|
||||||
$start = $repository->oldestJournalDate($account);
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
|
||||||
$start = Navigation::startOfPeriod($start, $range);
|
|
||||||
$end = Navigation::endOfX(new Carbon, $range);
|
|
||||||
$entries = new Collection;
|
|
||||||
|
|
||||||
// chart properties for cache:
|
return view('accounts.show', compact('account', 'entries', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||||
$cache = new CacheProperties;
|
}
|
||||||
$cache->addProperty($start);
|
|
||||||
$cache->addProperty($end);
|
/**
|
||||||
$cache->addProperty('account-show');
|
* @param ARI $repository
|
||||||
$cache->addProperty($account->id);
|
* @param Account $account
|
||||||
|
*
|
||||||
|
* @return View
|
||||||
if ($cache->has()) {
|
*/
|
||||||
$entries = $cache->get();
|
public function showAll(AccountRepositoryInterface $repository, Account $account)
|
||||||
|
{
|
||||||
return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
|
$subTitle = sprintf('%s (%s)', $account->name, strtolower(trans('firefly.everything')));
|
||||||
}
|
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
|
||||||
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
// only include asset accounts when this account is an asset:
|
$chartUri = route('chart.account.all', [$account->id]);
|
||||||
$assets = new Collection;
|
|
||||||
if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) {
|
// replace with journal collector:
|
||||||
$assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
$collector = new JournalCollector(auth()->user());
|
||||||
}
|
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||||
|
$journals = $collector->getPaginatedJournals();
|
||||||
while ($end >= $start) {
|
$journals->setPath('accounts/show/' . $account->id . '/all');
|
||||||
$end = Navigation::startOfPeriod($end, $range);
|
|
||||||
$currentEnd = Navigation::endOfPeriod($end, $range);
|
// get oldest and newest journal for account:
|
||||||
$spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
$start = $repository->oldestJournalDate($account);
|
||||||
$earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
$end = $repository->newestJournalDate($account);
|
||||||
$dateStr = $end->format('Y-m-d');
|
|
||||||
$dateName = Navigation::periodShow($end, $range);
|
// same call, except "entries".
|
||||||
$entries->push([$dateStr, $dateName, $spent, $earned]);
|
return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
$cache->store($entries);
|
|
||||||
|
|
||||||
return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $tasker
|
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
* @param string $date
|
* @param string $date
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function showWithDate(AccountTaskerInterface $tasker, Account $account, string $date)
|
public function showByDate(Account $account, string $date)
|
||||||
{
|
{
|
||||||
$carbon = new 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($carbon, $range);
|
||||||
$end = Navigation::endOfPeriod($carbon, $range);
|
$end = Navigation::endOfPeriod($carbon, $range);
|
||||||
$subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
|
$subTitle = $account->name . ' (' . Navigation::periodShow($start, $range) . ')';
|
||||||
$page = intval(Input::get('page'));
|
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
|
||||||
$page = $page === 0 ? 1 : $page;
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$chartUri = route('chart.account.period', [$account->id, $carbon->format('Y-m-d')]);
|
||||||
$offset = ($page - 1) * $pageSize;
|
|
||||||
$set = $tasker->getJournalsInPeriod(new Collection([$account]), [], $start, $end);
|
// replace with journal collector:
|
||||||
$count = $set->count();
|
$collector = new JournalCollector(auth()->user());
|
||||||
$subSet = $set->splice($offset, $pageSize);
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit($pageSize)->setPage($page);
|
||||||
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('accounts/show/' . $account->id . '/' . $date);
|
$journals->setPath('accounts/show/' . $account->id . '/' . $date);
|
||||||
|
|
||||||
return view('accounts.show_with_date', compact('category', 'date', 'account', 'journals', 'subTitle', 'carbon'));
|
// same call, except "entries".
|
||||||
|
return view('accounts.show', compact('account', 'subTitleIcon', 'journals', 'subTitle', 'start', 'end', 'chartUri'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -374,7 +386,64 @@ class AccountController extends Controller
|
|||||||
return $array[$entryId];
|
return $array[$entryId];
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns "period entries", so nov-2015, dec-2015, etc etc (this depends on the users session range)
|
||||||
|
* and for each period, the amount of money spent and earned. This is a complex operation which is cached for
|
||||||
|
* performance reasons.
|
||||||
|
*
|
||||||
|
* @param Account $account The account involved.
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function periodEntries(Account $account): Collection
|
||||||
|
{
|
||||||
|
/** @var ARI $repository */
|
||||||
|
$repository = app(ARI::class);
|
||||||
|
/** @var AccountTaskerInterface $tasker */
|
||||||
|
$tasker = app(AccountTaskerInterface::class);
|
||||||
|
|
||||||
|
$start = $repository->oldestJournalDate($account);
|
||||||
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = Navigation::endOfX(new Carbon, $range);
|
||||||
|
$entries = new Collection;
|
||||||
|
|
||||||
|
// properties for cache
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('account-show-period-entries');
|
||||||
|
$cache->addProperty($account->id);
|
||||||
|
|
||||||
|
if ($cache->has()) {
|
||||||
|
Log::debug('Entries are cached, return cache.');
|
||||||
|
|
||||||
|
return $cache->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// only include asset accounts when this account is an asset:
|
||||||
|
$assets = new Collection;
|
||||||
|
if (in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEFAULT])) {
|
||||||
|
$assets = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
||||||
|
}
|
||||||
|
Log::debug('Going to get period expenses and incomes.');
|
||||||
|
while ($end >= $start) {
|
||||||
|
$end = Navigation::startOfPeriod($end, $range);
|
||||||
|
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||||
|
$spent = $tasker->amountOutInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
||||||
|
$earned = $tasker->amountInInPeriod(new Collection([$account]), $assets, $end, $currentEnd);
|
||||||
|
$dateStr = $end->format('Y-m-d');
|
||||||
|
$dateName = Navigation::periodShow($end, $range);
|
||||||
|
$entries->push([$dateStr, $dateName, $spent, $earned]);
|
||||||
|
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
$cache->store($entries);
|
||||||
|
|
||||||
|
return $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Http\Controllers\Admin;
|
namespace FireflyIII\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
|
||||||
use Config;
|
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Http\Requests\ConfigurationRequest;
|
use FireflyIII\Http\Requests\ConfigurationRequest;
|
||||||
use FireflyIII\Support\Facades\FireflyConfig;
|
use FireflyIII\Support\Facades\FireflyConfig;
|
||||||
@@ -59,9 +58,24 @@ class ConfigurationController extends Controller
|
|||||||
|
|
||||||
// all available configuration and their default value in case
|
// all available configuration and their default value in case
|
||||||
// they don't exist yet.
|
// they don't exist yet.
|
||||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||||
|
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
|
||||||
|
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
|
||||||
|
$siteOwner = env('SITE_OWNER');
|
||||||
|
|
||||||
return view('admin.configuration.index', compact('subTitle', 'subTitleIcon', 'singleUserMode'));
|
// email settings:
|
||||||
|
$sendErrorMessage = [
|
||||||
|
'mail_for_lockout' => FireflyConfig::get('mail_for_lockout', config('firefly.configuration.mail_for_lockout'))->data,
|
||||||
|
'mail_for_blocked_domain' => FireflyConfig::get('mail_for_blocked_domain', config('firefly.configuration.mail_for_blocked_domain'))->data,
|
||||||
|
'mail_for_blocked_email' => FireflyConfig::get('mail_for_blocked_email', config('firefly.configuration.mail_for_blocked_email'))->data,
|
||||||
|
'mail_for_bad_login' => FireflyConfig::get('mail_for_bad_login', config('firefly.configuration.mail_for_bad_login'))->data,
|
||||||
|
'mail_for_blocked_login' => FireflyConfig::get('mail_for_blocked_login', config('firefly.configuration.mail_for_blocked_login'))->data,
|
||||||
|
];
|
||||||
|
|
||||||
|
return view(
|
||||||
|
'admin.configuration.index',
|
||||||
|
compact('subTitle', 'subTitleIcon', 'singleUserMode', 'mustConfirmAccount', 'isDemoSite', 'sendErrorMessage', 'siteOwner')
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +84,22 @@ class ConfigurationController extends Controller
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function store(ConfigurationRequest $request)
|
public function postIndex(ConfigurationRequest $request)
|
||||||
{
|
{
|
||||||
// get config values:
|
// get config values:
|
||||||
$data = $request->getConfigurationData();
|
$data = $request->getConfigurationData();
|
||||||
|
|
||||||
// store config values
|
// store config values
|
||||||
FireflyConfig::set('single_user_mode', $data['single_user_mode']);
|
FireflyConfig::set('single_user_mode', $data['single_user_mode']);
|
||||||
|
FireflyConfig::set('must_confirm_account', $data['must_confirm_account']);
|
||||||
|
FireflyConfig::set('is_demo_site', $data['is_demo_site']);
|
||||||
|
|
||||||
|
// email settings
|
||||||
|
FireflyConfig::set('mail_for_lockout', $data['mail_for_lockout']);
|
||||||
|
FireflyConfig::set('mail_for_blocked_domain', $data['mail_for_blocked_domain']);
|
||||||
|
FireflyConfig::set('mail_for_blocked_email', $data['mail_for_blocked_email']);
|
||||||
|
FireflyConfig::set('mail_for_bad_login', $data['mail_for_bad_login']);
|
||||||
|
FireflyConfig::set('mail_for_blocked_login', $data['mail_for_blocked_login']);
|
||||||
|
|
||||||
// flash message
|
// flash message
|
||||||
Session::flash('success', strval(trans('firefly.configuration_updated')));
|
Session::flash('success', strval(trans('firefly.configuration_updated')));
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use FireflyIII\Http\Controllers\Controller;
|
|||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,10 +14,15 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Http\Controllers\Admin;
|
namespace FireflyIII\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
|
||||||
|
use FireflyConfig;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Http\Requests\UserFormRequest;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
|
use Session;
|
||||||
|
use URL;
|
||||||
|
use View;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class UserController
|
* Class UserController
|
||||||
@@ -26,55 +31,85 @@ use Preferences;
|
|||||||
*/
|
*/
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
View::share('title', strval(trans('firefly.administration')));
|
||||||
|
View::share('mainTitleIcon', 'fa-hand-spock-o');
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $user
|
* @param User $user
|
||||||
*
|
*
|
||||||
* @return int
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function edit(User $user)
|
public function edit(User $user)
|
||||||
{
|
{
|
||||||
return $user->id;
|
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||||
|
if (session('users.edit.fromUpdate') !== true) {
|
||||||
|
Session::put('users.edit.url', URL::previous());
|
||||||
|
}
|
||||||
|
Session::forget('users.edit.fromUpdate');
|
||||||
|
|
||||||
|
$subTitle = strval(trans('firefly.edit_user', ['email' => $user->email]));
|
||||||
|
$subTitleIcon = 'fa-user-o';
|
||||||
|
$codes = [
|
||||||
|
'' => strval(trans('firefly.no_block_code')),
|
||||||
|
'bounced' => strval(trans('firefly.block_code_bounced')),
|
||||||
|
'expired' => strval(trans('firefly.block_code_expired')),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin.users.edit', compact('user', 'subTitle', 'subTitleIcon', 'codes'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param UserRepositoryInterface $repository
|
* @param UserRepositoryInterface $repository
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function index(UserRepositoryInterface $repository)
|
public function index(UserRepositoryInterface $repository)
|
||||||
{
|
{
|
||||||
$title = strval(trans('firefly.administration'));
|
|
||||||
$mainTitleIcon = 'fa-hand-spock-o';
|
|
||||||
$subTitle = strval(trans('firefly.user_administration'));
|
$subTitle = strval(trans('firefly.user_administration'));
|
||||||
$subTitleIcon = 'fa-users';
|
$subTitleIcon = 'fa-users';
|
||||||
$confirmAccount = env('MUST_CONFIRM_ACCOUNT', false);
|
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', config('firefly.configuration.must_confirm_account'))->data;
|
||||||
$users = $repository->all();
|
$users = $repository->all();
|
||||||
|
|
||||||
// add meta stuff.
|
// add meta stuff.
|
||||||
$users->each(
|
$users->each(
|
||||||
function (User $user) use ($confirmAccount) {
|
function (User $user) use ($mustConfirmAccount) {
|
||||||
// is user activated?
|
$list = ['user_confirmed', 'twoFactorAuthEnabled', 'twoFactorAuthSecret', 'registration_ip_address', 'confirmation_ip_address'];
|
||||||
$isConfirmed = Preferences::getForUser($user, 'user_confirmed', false)->data;
|
$preferences = Preferences::getArrayForUser($user, $list);
|
||||||
|
|
||||||
$user->activated = true;
|
$user->activated = true;
|
||||||
if ($isConfirmed === false && $confirmAccount === true) {
|
if (!($preferences['user_confirmed'] === true) && $mustConfirmAccount === true) {
|
||||||
$user->activated = false;
|
$user->activated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->isAdmin = $user->hasRole('owner');
|
$user->isAdmin = $user->hasRole('owner');
|
||||||
$is2faEnabled = Preferences::getForUser($user, 'twoFactorAuthEnabled', false)->data;
|
$is2faEnabled = $preferences['twoFactorAuthEnabled'] === true;
|
||||||
$has2faSecret = !is_null(Preferences::getForUser($user, 'twoFactorAuthSecret'));
|
$has2faSecret = !is_null($preferences['twoFactorAuthSecret']);
|
||||||
$user->has2FA = false;
|
$user->has2FA = false;
|
||||||
if ($is2faEnabled && $has2faSecret) {
|
if ($is2faEnabled && $has2faSecret) {
|
||||||
$user->has2FA = true;
|
$user->has2FA = true;
|
||||||
}
|
}
|
||||||
|
$user->prefs = $preferences;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return view('admin.users.index', compact('title', 'mainTitleIcon', 'subTitle', 'subTitleIcon', 'users'));
|
return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,5 +160,41 @@ class UserController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param UserFormRequest $request
|
||||||
|
* @param User $user
|
||||||
|
*
|
||||||
|
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function update(UserFormRequest $request, User $user)
|
||||||
|
{
|
||||||
|
$data = $request->getUserData();
|
||||||
|
|
||||||
|
// update password
|
||||||
|
if (strlen($data['password']) > 0) {
|
||||||
|
$user->password = bcrypt($data['password']);
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// change blocked status and code:
|
||||||
|
$user->blocked = $data['blocked'];
|
||||||
|
$user->blocked_code = $data['blocked_code'];
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
Session::flash('success', strval(trans('firefly.updated_user', ['email' => $user->email])));
|
||||||
|
Preferences::mark();
|
||||||
|
|
||||||
|
if (intval($request->get('return_to_edit')) === 1) {
|
||||||
|
// set value so edit routine will not overwrite URL:
|
||||||
|
Session::put('users.edit.fromUpdate', true);
|
||||||
|
|
||||||
|
return redirect(route('admin.users.edit', [$user->id]))->withInput(['return_to_edit' => 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect to previous URL.
|
||||||
|
return redirect(session('users.edit.url'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class AttachmentController extends Controller
|
|||||||
/**
|
/**
|
||||||
* @param Attachment $attachment
|
* @param Attachment $attachment
|
||||||
*
|
*
|
||||||
* @return View
|
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
|
||||||
*/
|
*/
|
||||||
public function delete(Attachment $attachment)
|
public function delete(Attachment $attachment)
|
||||||
{
|
{
|
||||||
@@ -95,16 +95,12 @@ class AttachmentController extends Controller
|
|||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function download(Attachment $attachment)
|
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||||
{
|
{
|
||||||
// create a disk.
|
if ($repository->exists($attachment)) {
|
||||||
$disk = Storage::disk('upload');
|
$content = $repository->getContent($attachment);
|
||||||
$file = $attachment->fileName();
|
|
||||||
|
|
||||||
if ($disk->exists($file)) {
|
|
||||||
|
|
||||||
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
|
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
|
||||||
$content = Crypt::decrypt($disk->get($file));
|
|
||||||
|
|
||||||
Log::debug('Send file to user', ['file' => $quoted, 'size' => strlen($content)]);
|
Log::debug('Send file to user', ['file' => $quoted, 'size' => strlen($content)]);
|
||||||
|
|
||||||
@@ -118,8 +114,8 @@ class AttachmentController extends Controller
|
|||||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||||
->header('Pragma', 'public')
|
->header('Pragma', 'public')
|
||||||
->header('Content-Length', strlen($content));
|
->header('Content-Length', strlen($content));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new FireflyException('Could not find the indicated attachment. The file is no longer there.');
|
throw new FireflyException('Could not find the indicated attachment. The file is no longer there.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
app/Http/Controllers/Auth/ForgotPasswordController.php
Executable file → Normal file
37
app/Http/Controllers/Auth/ForgotPasswordController.php
Executable file → Normal file
@@ -13,7 +13,9 @@ 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\User;
|
||||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ForgotPasswordController
|
* Class ForgotPasswordController
|
||||||
@@ -33,4 +35,39 @@ class ForgotPasswordController extends Controller
|
|||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->middleware('guest');
|
$this->middleware('guest');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a reset link to the given user.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function sendResetLinkEmail(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, ['email' => 'required|email']);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
if (!is_null($user) && $user->hasRole('demo')) {
|
||||||
|
return back()->withErrors(
|
||||||
|
['email' => trans('firefly.cannot_reset_demo_user')]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->broker()->sendResetLink(
|
||||||
|
$request->only('email')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($response === Password::RESET_LINK_SENT) {
|
||||||
|
return back()->with('status', trans($response));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// to where the users came from so they can attempt this process again.
|
||||||
|
return back()->withErrors(
|
||||||
|
['email' => trans($response)]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
app/Http/Controllers/Auth/LoginController.php
Executable file → Normal file
73
app/Http/Controllers/Auth/LoginController.php
Executable file → Normal file
@@ -14,15 +14,14 @@ namespace FireflyIII\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use Config;
|
use Config;
|
||||||
use FireflyConfig;
|
use FireflyConfig;
|
||||||
|
use FireflyIII\Events\BlockedBadLogin;
|
||||||
|
use FireflyIII\Events\BlockedUserLogin;
|
||||||
|
use FireflyIII\Events\LockedOutUser;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Mail\Message;
|
|
||||||
use Lang;
|
use Lang;
|
||||||
use Log;
|
|
||||||
use Mail;
|
|
||||||
use Swift_TransportException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class LoginController
|
* Class LoginController
|
||||||
@@ -31,16 +30,6 @@ use Swift_TransportException;
|
|||||||
*/
|
*/
|
||||||
class LoginController extends Controller
|
class LoginController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Login Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller handles authenticating users for the application and
|
|
||||||
| redirecting them to your home screen. The controller uses a trait
|
|
||||||
| to conveniently provide its functionality to your applications.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use AuthenticatesUsers;
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
@@ -49,7 +38,7 @@ class LoginController extends Controller
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $redirectTo = '/home';
|
protected $redirectTo = '/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new controller instance.
|
* Create a new controller instance.
|
||||||
@@ -64,25 +53,24 @@ class LoginController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Handle a login request to the application.
|
* Handle a login request to the application.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function login(Request $request)
|
public function login(Request $request)
|
||||||
{
|
{
|
||||||
$this->validateLogin($request);
|
$this->validateLogin($request);
|
||||||
|
$lockedOut = $this->hasTooManyLoginAttempts($request);
|
||||||
// If the class is using the ThrottlesLogins trait, we can automatically throttle
|
if ($lockedOut) {
|
||||||
// the login attempts for this application. We'll key this by the username and
|
|
||||||
// the IP address of the client making these requests into this application.
|
|
||||||
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
|
|
||||||
$this->fireLockoutEvent($request);
|
$this->fireLockoutEvent($request);
|
||||||
|
|
||||||
|
event(new LockedOutUser($request->get('email'), $request->ip()));
|
||||||
|
|
||||||
return $this->sendLockoutResponse($request);
|
return $this->sendLockoutResponse($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$credentials = $this->credentials($request);
|
$credentials = $this->credentials($request);
|
||||||
$credentials['blocked'] = 0; // most not be blocked.
|
$credentials['blocked'] = 0; // must not be blocked.
|
||||||
|
|
||||||
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
|
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
|
||||||
return $this->sendLoginResponse($request);
|
return $this->sendLoginResponse($request);
|
||||||
@@ -93,10 +81,15 @@ class LoginController extends Controller
|
|||||||
/** @var User $foundUser */
|
/** @var User $foundUser */
|
||||||
$foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first();
|
$foundUser = User::where('email', $credentials['email'])->where('blocked', 1)->first();
|
||||||
if (!is_null($foundUser)) {
|
if (!is_null($foundUser)) {
|
||||||
// if it exists, show message:
|
// user exists, but is blocked:
|
||||||
$code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked';
|
$code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked';
|
||||||
$errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $credentials['email']]));
|
$errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $credentials['email']]));
|
||||||
$this->reportBlockedUserLoginAttempt($foundUser, $code, $request->ip());
|
event(new BlockedUserLogin($foundUser, $request->ip()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// simply a bad login.
|
||||||
|
if (is_null($foundUser)) {
|
||||||
|
event(new BlockedBadLogin($credentials['email'], $request->ip()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the login attempt was unsuccessful we will increment the number of attempts
|
// If the login attempt was unsuccessful we will increment the number of attempts
|
||||||
@@ -166,34 +159,4 @@ class LoginController extends Controller
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message home about the blocked attempt to login.
|
|
||||||
* Perhaps in a later stage, simply log these messages.
|
|
||||||
*
|
|
||||||
* @param User $user
|
|
||||||
* @param string $code
|
|
||||||
* @param string $ipAddress
|
|
||||||
*/
|
|
||||||
private function reportBlockedUserLoginAttempt(User $user, string $code, string $ipAddress)
|
|
||||||
{
|
|
||||||
|
|
||||||
try {
|
|
||||||
$email = env('SITE_OWNER', false);
|
|
||||||
$fields = [
|
|
||||||
'user_id' => $user->id,
|
|
||||||
'user_address' => $user->email,
|
|
||||||
'code' => $code,
|
|
||||||
'ip' => $ipAddress,
|
|
||||||
];
|
|
||||||
|
|
||||||
Mail::send(
|
|
||||||
['emails.blocked-login-html', 'emails.blocked-login'], $fields, function (Message $message) use ($email, $user) {
|
|
||||||
$message->to($email, $email)->subject('Blocked a login attempt from ' . trim($user->email) . '.');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (Swift_TransportException $e) {
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,21 +26,11 @@ use Illuminate\Support\Facades\Password;
|
|||||||
*
|
*
|
||||||
* @package FireflyIII\Http\Controllers\Auth
|
* @package FireflyIII\Http\Controllers\Auth
|
||||||
* @method getEmailSubject()
|
* @method getEmailSubject()
|
||||||
* @method getSendResetLinkEmailSuccessResponse()
|
* @method getSendResetLinkEmailSuccessResponse(string $response)
|
||||||
* @method getSendResetLinkEmailFailureResponse()
|
* @method getSendResetLinkEmailFailureResponse(string $response)
|
||||||
*/
|
*/
|
||||||
class PasswordController extends Controller
|
class PasswordController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Password Reset Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller is responsible for handling password reset requests
|
|
||||||
| and uses a simple trait to include this behavior. You're free to
|
|
||||||
| explore this trait and override any methods you wish to tweak.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use ResetsPasswords;
|
use ResetsPasswords;
|
||||||
|
|
||||||
|
|||||||
65
app/Http/Controllers/Auth/RegisterController.php
Executable file → Normal file
65
app/Http/Controllers/Auth/RegisterController.php
Executable file → Normal file
@@ -14,17 +14,16 @@ namespace FireflyIII\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use Auth;
|
use Auth;
|
||||||
use Config;
|
use Config;
|
||||||
|
use FireflyConfig;
|
||||||
|
use FireflyIII\Events\BlockedUseOfDomain;
|
||||||
|
use FireflyIII\Events\BlockedUseOfEmail;
|
||||||
use FireflyIII\Events\RegisteredUser;
|
use FireflyIII\Events\RegisteredUser;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Support\Facades\FireflyConfig;
|
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Mail\Message;
|
|
||||||
use Log;
|
use Log;
|
||||||
use Mail;
|
|
||||||
use Session;
|
use Session;
|
||||||
use Swift_TransportException;
|
|
||||||
use Validator;
|
use Validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,16 +33,6 @@ use Validator;
|
|||||||
*/
|
*/
|
||||||
class RegisterController extends Controller
|
class RegisterController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Register Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller handles the registration of new users as well as their
|
|
||||||
| validation and creation. By default this controller uses a trait to
|
|
||||||
| provide this functionality without requiring any additional code.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use RegistersUsers;
|
use RegistersUsers;
|
||||||
|
|
||||||
@@ -93,8 +82,19 @@ class RegisterController extends Controller
|
|||||||
if ($this->isBlockedDomain($data['email'])) {
|
if ($this->isBlockedDomain($data['email'])) {
|
||||||
$validator->getMessageBag()->add('email', (string)trans('validation.invalid_domain'));
|
$validator->getMessageBag()->add('email', (string)trans('validation.invalid_domain'));
|
||||||
|
|
||||||
$this->reportBlockedDomainRegistrationAttempt($data['email'], $request->ip());
|
event(new BlockedUseOfDomain($data['email'], $request->ip()));
|
||||||
|
$this->throwValidationException($request, $validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// is user a deleted user?
|
||||||
|
$hash = hash('sha256', $data['email']);
|
||||||
|
$configuration = FireflyConfig::get('deleted_users', []);
|
||||||
|
$set = $configuration->data;
|
||||||
|
Log::debug(sprintf('Hash of email is %s', $hash));
|
||||||
|
Log::debug('Hashes of deleted users: ', $set);
|
||||||
|
if (in_array($hash, $set)) {
|
||||||
|
$validator->getMessageBag()->add('email', (string)trans('validation.deleted_user'));
|
||||||
|
event(new BlockedUseOfEmail($data['email'], $request->ip()));
|
||||||
$this->throwValidationException($request, $validator);
|
$this->throwValidationException($request, $validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,11 @@ class RegisterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function showRegistrationForm(Request $request)
|
public function showRegistrationForm(Request $request)
|
||||||
{
|
{
|
||||||
$showDemoWarning = config('firefly.show-demo-warning', false);
|
// is demo site?
|
||||||
|
$isDemoSite = FireflyConfig::get('is_demo_site', Config::get('firefly.configuration.is_demo_site'))->data;
|
||||||
|
|
||||||
|
// activate account?
|
||||||
|
$mustConfirmAccount = FireflyConfig::get('must_confirm_account', Config::get('firefly.configuration.must_confirm_account'))->data;
|
||||||
|
|
||||||
// is allowed to?
|
// 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;
|
||||||
@@ -136,7 +140,7 @@ class RegisterController extends Controller
|
|||||||
|
|
||||||
$email = $request->old('email');
|
$email = $request->old('email');
|
||||||
|
|
||||||
return view('auth.register', compact('showDemoWarning', 'email'));
|
return view('auth.register', compact('isDemoSite', 'email', 'mustConfirmAccount'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,31 +202,4 @@ class RegisterController extends Controller
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message home about a blocked domain and the address attempted to register.
|
|
||||||
*
|
|
||||||
* @param string $registrationMail
|
|
||||||
* @param string $ipAddress
|
|
||||||
*/
|
|
||||||
private function reportBlockedDomainRegistrationAttempt(string $registrationMail, string $ipAddress)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$email = env('SITE_OWNER', false);
|
|
||||||
$parts = explode('@', $registrationMail);
|
|
||||||
$domain = $parts[1];
|
|
||||||
$fields = [
|
|
||||||
'email_address' => $registrationMail,
|
|
||||||
'blocked_domain' => $domain,
|
|
||||||
'ip' => $ipAddress,
|
|
||||||
];
|
|
||||||
|
|
||||||
Mail::send(
|
|
||||||
['emails.blocked-registration-html', 'emails.blocked-registration'], $fields, function (Message $message) use ($email, $domain) {
|
|
||||||
$message->to($email, $email)->subject('Blocked a registration attempt with domain ' . $domain . '.');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (Swift_TransportException $e) {
|
|
||||||
Log::error($e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/Http/Controllers/Auth/ResetPasswordController.php
Executable file → Normal file
10
app/Http/Controllers/Auth/ResetPasswordController.php
Executable file → Normal file
@@ -22,16 +22,6 @@ use Illuminate\Foundation\Auth\ResetsPasswords;
|
|||||||
*/
|
*/
|
||||||
class ResetPasswordController extends Controller
|
class ResetPasswordController extends Controller
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Password Reset Controller
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This controller is responsible for handling password reset requests
|
|
||||||
| and uses a simple trait to include this behavior. You're free to
|
|
||||||
| explore this trait and override any methods you wish to tweak.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
use ResetsPasswords;
|
use ResetsPasswords;
|
||||||
|
|
||||||
|
|||||||
@@ -38,10 +38,16 @@ 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', '')->data;
|
$secret = Preferences::get('twoFactorAuthSecret', null)->data;
|
||||||
$title = strval(trans('firefly.two_factor_title'));
|
$title = strval(trans('firefly.two_factor_title'));
|
||||||
|
|
||||||
if (strlen($secret) === 0) {
|
// make sure the user has two factor configured:
|
||||||
|
$has2FA = Preferences::get('twoFactorAuthEnabled', null)->data;
|
||||||
|
if (is_null($has2FA) || $has2FA === false) {
|
||||||
|
return redirect(route('index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(strval($secret)) === 0) {
|
||||||
throw new FireflyException('Your two factor authentication secret is empty, which it cannot be at this point. Please check the log files.');
|
throw new FireflyException('Your two factor authentication secret is empty, which it cannot be at this point. Please check the log files.');
|
||||||
}
|
}
|
||||||
Session::flash('two-factor-secret', $secret);
|
Session::flash('two-factor-secret', $secret);
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
use FireflyIII\Http\Requests\BillFormRequest;
|
use FireflyIII\Http\Requests\BillFormRequest;
|
||||||
use FireflyIII\Models\Bill;
|
use FireflyIII\Models\Bill;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Input;
|
use Input;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Session;
|
use Session;
|
||||||
@@ -98,12 +100,19 @@ class BillController extends Controller
|
|||||||
public function destroy(BillRepositoryInterface $repository, Bill $bill)
|
public function destroy(BillRepositoryInterface $repository, Bill $bill)
|
||||||
{
|
{
|
||||||
$name = $bill->name;
|
$name = $bill->name;
|
||||||
|
$billId = $bill->id;
|
||||||
$repository->destroy($bill);
|
$repository->destroy($bill);
|
||||||
|
|
||||||
Session::flash('success', strval(trans('firefly.deleted_bill', ['name' => $name])));
|
Session::flash('success', strval(trans('firefly.deleted_bill', ['name' => $name])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
return redirect(session('bills.delete.url'));
|
$uri = session('bills.delete.url');
|
||||||
|
if (!(strpos($uri, sprintf('bills/show/%s', $billId)) === false)) {
|
||||||
|
// uri would point back to bill
|
||||||
|
$uri = route('bills.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect($uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,10 +209,15 @@ class BillController extends Controller
|
|||||||
$year = $date->year;
|
$year = $date->year;
|
||||||
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
||||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$journals = $repository->getJournals($bill, $page, $pageSize);
|
|
||||||
$yearAverage = $repository->getYearAverage($bill, $date);
|
$yearAverage = $repository->getYearAverage($bill, $date);
|
||||||
$overallAverage = $repository->getOverallAverage($bill);
|
$overallAverage = $repository->getOverallAverage($bill);
|
||||||
|
|
||||||
|
// use collector:
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setPage($page)->setLimit($pageSize);
|
||||||
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('/bills/show/' . $bill->id);
|
$journals->setPath('/bills/show/' . $bill->id);
|
||||||
|
|
||||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
|
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
|
||||||
$hideBill = true;
|
$hideBill = true;
|
||||||
$subTitle = e($bill->name);
|
$subTitle = e($bill->name);
|
||||||
|
|||||||
@@ -15,19 +15,17 @@ namespace FireflyIII\Http\Controllers;
|
|||||||
|
|
||||||
use Amount;
|
use Amount;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Config;
|
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
use FireflyIII\Http\Requests\BudgetFormRequest;
|
use FireflyIII\Http\Requests\BudgetFormRequest;
|
||||||
|
use FireflyIII\Http\Requests\BudgetIncomeRequest;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\LimitRepetition;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Input;
|
use Input;
|
||||||
use Log;
|
|
||||||
use Navigation;
|
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
use Session;
|
use Session;
|
||||||
@@ -42,6 +40,9 @@ use View;
|
|||||||
class BudgetController extends Controller
|
class BudgetController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @var BudgetRepositoryInterface */
|
||||||
|
private $repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -55,6 +56,7 @@ class BudgetController extends Controller
|
|||||||
function ($request, $next) {
|
function ($request, $next) {
|
||||||
View::share('title', trans('firefly.budgets'));
|
View::share('title', trans('firefly.budgets'));
|
||||||
View::share('mainTitleIcon', 'fa-tasks');
|
View::share('mainTitleIcon', 'fa-tasks');
|
||||||
|
$this->repository = app(BudgetRepositoryInterface::class);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
@@ -75,19 +77,13 @@ class BudgetController extends Controller
|
|||||||
/** @var Carbon $end */
|
/** @var Carbon $end */
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
$viewRange = Preferences::get('viewRange', '1M')->data;
|
$viewRange = Preferences::get('viewRange', '1M')->data;
|
||||||
|
|
||||||
// is custom view range?
|
|
||||||
if (session('is_custom_range') === true) {
|
|
||||||
$viewRange = 'custom';
|
|
||||||
}
|
|
||||||
|
|
||||||
$limitRepetition = $repository->updateLimitAmount($budget, $start, $end, $viewRange, $amount);
|
$limitRepetition = $repository->updateLimitAmount($budget, $start, $end, $viewRange, $amount);
|
||||||
if ($amount == 0) {
|
if ($amount == 0) {
|
||||||
$limitRepetition = null;
|
$limitRepetition = null;
|
||||||
}
|
}
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
return Response::json(['name' => $budget->name, 'repetition' => $limitRepetition ? $limitRepetition->id : 0]);
|
return Response::json(['name' => $budget->name, 'repetition' => $limitRepetition ? $limitRepetition->id : 0, 'amount' => $amount]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,14 +131,20 @@ class BudgetController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
$name = $budget->name;
|
$name = $budget->name;
|
||||||
|
$budgetId = $budget->id;
|
||||||
$repository->destroy($budget);
|
$repository->destroy($budget);
|
||||||
|
|
||||||
|
|
||||||
Session::flash('success', strval(trans('firefly.deleted_budget', ['name' => e($name)])));
|
Session::flash('success', strval(trans('firefly.deleted_budget', ['name' => e($name)])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
|
$uri = session('budgets.delete.url');
|
||||||
|
if (!(strpos($uri, sprintf('budgets/show/%s', $budgetId)) === false)) {
|
||||||
|
// uri would point back to budget
|
||||||
|
$uri = route('budgets.index');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect(session('budgets.delete.url'));
|
return redirect($uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,126 +169,66 @@ class BudgetController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param BudgetRepositoryInterface $repository
|
|
||||||
* @param AccountRepositoryInterface $accountRepository
|
|
||||||
*
|
|
||||||
* @return View
|
* @return View
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public function index(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository)
|
public function index()
|
||||||
{
|
{
|
||||||
$repository->cleanupBudgets();
|
$this->repository->cleanupBudgets();
|
||||||
|
|
||||||
$budgets = $repository->getActiveBudgets();
|
$budgets = $this->repository->getActiveBudgets();
|
||||||
$inactive = $repository->getInactiveBudgets();
|
$inactive = $this->repository->getInactiveBudgets();
|
||||||
$spent = '0';
|
|
||||||
$budgeted = '0';
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
|
||||||
$repeatFreq = Config::get('firefly.range_to_repeat_freq.' . $range);
|
|
||||||
|
|
||||||
if (session('is_custom_range') === true) {
|
|
||||||
$repeatFreq = 'custom';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Carbon $start */
|
|
||||||
$start = session('start', new Carbon);
|
$start = session('start', new Carbon);
|
||||||
/** @var Carbon $end */
|
|
||||||
$end = session('end', new Carbon);
|
$end = session('end', new Carbon);
|
||||||
$key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd');
|
|
||||||
$budgetIncomeTotal = Preferences::get($key, 1000)->data;
|
|
||||||
$period = Navigation::periodShow($start, $range);
|
|
||||||
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
|
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||||
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
|
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
|
||||||
$startAsString = $start->format('Y-m-d');
|
|
||||||
$endAsString = $end->format('Y-m-d');
|
|
||||||
Log::debug('Now at /budgets');
|
|
||||||
|
|
||||||
// loop the budgets:
|
|
||||||
/** @var Budget $budget */
|
|
||||||
foreach ($budgets as $budget) {
|
|
||||||
Log::debug(sprintf('Now at budget #%d ("%s")', $budget->id, $budget->name));
|
|
||||||
$budget->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
|
|
||||||
$allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
|
|
||||||
$otherRepetitions = new Collection;
|
|
||||||
|
|
||||||
/** @var LimitRepetition $repetition */
|
|
||||||
foreach ($allRepetitions as $repetition) {
|
|
||||||
if ($repetition->budget_id == $budget->id) {
|
|
||||||
if ($repetition->budgetLimit->repeat_freq == $repeatFreq
|
|
||||||
&& $repetition->startdate->format('Y-m-d') == $startAsString
|
|
||||||
&& $repetition->enddate->format('Y-m-d') == $endAsString
|
|
||||||
) {
|
|
||||||
// do something
|
|
||||||
$budget->currentRep = $repetition;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$otherRepetitions->push($repetition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$budget->otherRepetitions = $otherRepetitions;
|
|
||||||
|
|
||||||
if (!is_null($budget->currentRep) && !is_null($budget->currentRep->id)) {
|
|
||||||
$budgeted = bcadd($budgeted, $budget->currentRep->amount);
|
|
||||||
}
|
|
||||||
$spent = bcadd($spent, $budget->spent);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$defaultCurrency = Amount::getDefaultCurrency();
|
$defaultCurrency = Amount::getDefaultCurrency();
|
||||||
|
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
|
||||||
|
$spent = array_sum(array_column($budgetInformation, 'spent'));
|
||||||
|
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
|
||||||
|
|
||||||
return view(
|
return view(
|
||||||
'budgets.index', compact(
|
'budgets.index',
|
||||||
'periodStart', 'periodEnd',
|
compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'defaultCurrency', 'inactive', 'budgets', 'spent', 'budgeted')
|
||||||
'period', 'range', 'budgetIncomeTotal',
|
|
||||||
'defaultCurrency', 'inactive', 'budgets',
|
|
||||||
'spent', 'budgeted'
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param BudgetRepositoryInterface $repository
|
|
||||||
*
|
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function noBudget(BudgetRepositoryInterface $repository)
|
public function noBudget()
|
||||||
{
|
{
|
||||||
/** @var Carbon $start */
|
/** @var Carbon $start */
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$start = session('start', Carbon::now()->startOfMonth());
|
||||||
/** @var Carbon $end */
|
/** @var Carbon $end */
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
|
|
||||||
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$offset = ($page - 1) * $pageSize;
|
|
||||||
$journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); // budget
|
|
||||||
$count = $journals->count();
|
|
||||||
$journals = $journals->slice($offset, $pageSize);
|
|
||||||
$list = new LengthAwarePaginator($journals, $count, $pageSize);
|
|
||||||
$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)]
|
||||||
);
|
);
|
||||||
$list->setPath('/budgets/list/noBudget');
|
|
||||||
|
|
||||||
return view('budgets.noBudget', compact('list', 'subTitle'));
|
// collector
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutBudget();
|
||||||
|
$journals = $collector->getPaginatedJournals();
|
||||||
|
$journals->setPath('/budgets/list/noBudget');
|
||||||
|
|
||||||
|
return view('budgets.no-budget', compact('journals', 'subTitle'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function postUpdateIncome()
|
public function postUpdateIncome(BudgetIncomeRequest $request)
|
||||||
{
|
{
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$start = session('start', new Carbon);
|
||||||
/** @var Carbon $date */
|
$end = session('end', new Carbon);
|
||||||
$date = session('start', new Carbon);
|
$defaultCurrency = Amount::getDefaultCurrency();
|
||||||
$start = Navigation::startOfPeriod($date, $range);
|
$amount = $request->get('amount');
|
||||||
$end = Navigation::endOfPeriod($start, $range);
|
|
||||||
$key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd');
|
|
||||||
|
|
||||||
Preferences::set($key, intval(Input::get('amount')));
|
$this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
return redirect(route('budgets.index'));
|
return redirect(route('budgets.index'));
|
||||||
@@ -305,14 +247,13 @@ class BudgetController extends Controller
|
|||||||
$start = session('first', Carbon::create()->startOfYear());
|
$start = session('first', Carbon::create()->startOfYear());
|
||||||
$end = new Carbon;
|
$end = new Carbon;
|
||||||
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$offset = ($page - 1) * $pageSize;
|
|
||||||
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); // budget
|
|
||||||
$count = $journals->count();
|
|
||||||
$journals = $journals->slice($offset, $pageSize);
|
|
||||||
$journals = new LengthAwarePaginator($journals, $count, $pageSize);
|
|
||||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||||
|
$repetition = null;
|
||||||
|
// collector:
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page);
|
||||||
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('/budgets/show/' . $budget->id);
|
$journals->setPath('/budgets/show/' . $budget->id);
|
||||||
|
|
||||||
|
|
||||||
@@ -330,33 +271,36 @@ class BudgetController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param BudgetRepositoryInterface $repository
|
|
||||||
* @param AccountRepositoryInterface $accountRepository
|
|
||||||
* @param Budget $budget
|
* @param Budget $budget
|
||||||
* @param LimitRepetition $repetition
|
* @param LimitRepetition $repetition
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function showWithRepetition(
|
public function showByRepetition(Budget $budget, LimitRepetition $repetition)
|
||||||
BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget, LimitRepetition $repetition
|
{
|
||||||
) {
|
|
||||||
if ($repetition->budgetLimit->budget->id != $budget->id) {
|
if ($repetition->budgetLimit->budget->id != $budget->id) {
|
||||||
throw new FireflyException('This budget limit is not part of this budget.');
|
throw new FireflyException('This budget limit is not part of this budget.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var BudgetRepositoryInterface $repository */
|
||||||
|
$repository = app(BudgetRepositoryInterface::class);
|
||||||
|
/** @var AccountRepositoryInterface $accountRepository */
|
||||||
|
$accountRepository = app(AccountRepositoryInterface::class);
|
||||||
$start = $repetition->startdate;
|
$start = $repetition->startdate;
|
||||||
$end = $repetition->enddate;
|
$end = $repetition->enddate;
|
||||||
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
$page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page'));
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$offset = ($page - 1) * $pageSize;
|
$subTitle = trans(
|
||||||
$journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); // budget
|
'firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]
|
||||||
$count = $journals->count();
|
);
|
||||||
$journals = $journals->slice($offset, $pageSize);
|
|
||||||
$journals = new LengthAwarePaginator($journals, $count, $pageSize);
|
|
||||||
$subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]);
|
|
||||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||||
|
|
||||||
|
|
||||||
|
// collector:
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page);
|
||||||
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id);
|
$journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id);
|
||||||
|
|
||||||
|
|
||||||
@@ -425,19 +369,57 @@ class BudgetController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function updateIncome()
|
public function updateIncome()
|
||||||
{
|
{
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$start = session('start', new Carbon);
|
||||||
$format = strval(trans('config.month_and_day'));
|
$end = session('end', new Carbon);
|
||||||
|
$defaultCurrency = Amount::getDefaultCurrency();
|
||||||
|
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
|
||||||
|
|
||||||
/** @var Carbon $date */
|
|
||||||
$date = session('start', new Carbon);
|
|
||||||
$start = Navigation::startOfPeriod($date, $range);
|
|
||||||
$end = Navigation::endOfPeriod($start, $range);
|
|
||||||
$key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd');
|
|
||||||
$amount = Preferences::get($key, 1000);
|
|
||||||
$displayStart = $start->formatLocalized($format);
|
|
||||||
$displayEnd = $end->formatLocalized($format);
|
|
||||||
|
|
||||||
return view('budgets.income', compact('amount', 'displayStart', 'displayEnd'));
|
return view('budgets.income', compact('available', 'start', 'end'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $budgets
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array
|
||||||
|
{
|
||||||
|
// get account information
|
||||||
|
$accountRepository = app(AccountRepositoryInterface::class);
|
||||||
|
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||||
|
$return = [];
|
||||||
|
/** @var Budget $budget */
|
||||||
|
foreach ($budgets as $budget) {
|
||||||
|
$budgetId = $budget->id;
|
||||||
|
$return[$budgetId] = [
|
||||||
|
'spent' => $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end),
|
||||||
|
'budgeted' => '0',
|
||||||
|
'currentRep' => false,
|
||||||
|
];
|
||||||
|
$allRepetitions = $this->repository->getAllBudgetLimitRepetitions($start, $end);
|
||||||
|
$otherRepetitions = new Collection;
|
||||||
|
|
||||||
|
// get all the limit repetitions relevant between start and end and examine them:
|
||||||
|
/** @var LimitRepetition $repetition */
|
||||||
|
foreach ($allRepetitions as $repetition) {
|
||||||
|
if ($repetition->budget_id == $budget->id) {
|
||||||
|
if ($repetition->startdate->isSameDay($start) && $repetition->enddate->isSameDay($end)
|
||||||
|
) {
|
||||||
|
$return[$budgetId]['currentRep'] = $repetition;
|
||||||
|
$return[$budgetId]['budgeted'] = $repetition->amount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// otherwise it's just one of the many relevant repetitions:
|
||||||
|
$otherRepetitions->push($repetition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$return[$budgetId]['otherRepetitions'] = $otherRepetitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Http\Requests\CategoryFormRequest;
|
use FireflyIII\Http\Requests\CategoryFormRequest;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Input;
|
use Input;
|
||||||
use Navigation;
|
use Navigation;
|
||||||
@@ -60,7 +61,6 @@ class CategoryController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
// put previous url in session if not redirect from store (not "create another").
|
|
||||||
if (session('categories.create.fromStore') !== true) {
|
if (session('categories.create.fromStore') !== true) {
|
||||||
Session::put('categories.create.url', URL::previous());
|
Session::put('categories.create.url', URL::previous());
|
||||||
}
|
}
|
||||||
@@ -99,12 +99,19 @@ class CategoryController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
$name = $category->name;
|
$name = $category->name;
|
||||||
|
$categoryId = $category->id;
|
||||||
$repository->destroy($category);
|
$repository->destroy($category);
|
||||||
|
|
||||||
Session::flash('success', strval(trans('firefly.deleted_category', ['name' => e($name)])));
|
Session::flash('success', strval(trans('firefly.deleted_category', ['name' => e($name)])));
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
return redirect(session('categories.delete.url'));
|
$uri = session('categories.delete.url');
|
||||||
|
if (!(strpos($uri, sprintf('categories/show/%s', $categoryId)) === false)) {
|
||||||
|
// uri would point back to category
|
||||||
|
$uri = route('categories.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect($uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,23 +154,25 @@ class CategoryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
|
||||||
*
|
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function noCategory(CRI $repository)
|
public function noCategory()
|
||||||
{
|
{
|
||||||
/** @var Carbon $start */
|
/** @var Carbon $start */
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$start = session('start', Carbon::now()->startOfMonth());
|
||||||
/** @var Carbon $end */
|
/** @var Carbon $end */
|
||||||
$end = session('end', Carbon::now()->startOfMonth());
|
$end = session('end', Carbon::now()->startOfMonth());
|
||||||
$list = $repository->journalsInPeriodWithoutCategory(new Collection(), [], $start, $end); // category
|
|
||||||
|
// new collector:
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end)->withoutCategory();//->groupJournals();
|
||||||
|
$journals = $collector->getJournals();
|
||||||
$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)]
|
||||||
);
|
);
|
||||||
|
|
||||||
return view('categories.noCategory', compact('list', 'subTitle'));
|
return view('categories.no-category', compact('journals', 'subTitle'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,16 +189,17 @@ class CategoryController extends Controller
|
|||||||
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
|
$start = session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||||
/** @var Carbon $end */
|
/** @var Carbon $end */
|
||||||
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
$end = session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
|
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||||
$hideCategory = true; // used in list.
|
$hideCategory = true; // used in list.
|
||||||
$page = intval(Input::get('page'));
|
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$offset = ($page - 1) * $pageSize;
|
|
||||||
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); // category
|
|
||||||
$count = $set->count();
|
|
||||||
$subSet = $set->splice($offset, $pageSize);
|
|
||||||
$subTitle = $category->name;
|
$subTitle = $category->name;
|
||||||
$subTitleIcon = 'fa-bar-chart';
|
$subTitleIcon = 'fa-bar-chart';
|
||||||
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
|
|
||||||
|
// use journal collector
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category);
|
||||||
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('categories/show/' . $category->id);
|
$journals->setPath('categories/show/' . $category->id);
|
||||||
|
|
||||||
// oldest transaction in category:
|
// oldest transaction in category:
|
||||||
@@ -218,7 +228,7 @@ class CategoryController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
$categoryCollection = new Collection([$category]);
|
$categoryCollection = new Collection([$category]);
|
||||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
|
||||||
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);
|
||||||
@@ -233,18 +243,16 @@ class CategoryController extends Controller
|
|||||||
}
|
}
|
||||||
$cache->store($entries);
|
$cache->store($entries);
|
||||||
|
|
||||||
return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle'));
|
return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle', 'subTitleIcon'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
*
|
|
||||||
* @param $date
|
* @param $date
|
||||||
*
|
*
|
||||||
* @return View
|
* @return View
|
||||||
*/
|
*/
|
||||||
public function showWithDate(CRI $repository, Category $category, string $date)
|
public function showByDate(Category $category, string $date)
|
||||||
{
|
{
|
||||||
$carbon = new Carbon($date);
|
$carbon = new Carbon($date);
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
@@ -252,16 +260,16 @@ class CategoryController extends Controller
|
|||||||
$end = Navigation::endOfPeriod($carbon, $range);
|
$end = Navigation::endOfPeriod($carbon, $range);
|
||||||
$subTitle = $category->name;
|
$subTitle = $category->name;
|
||||||
$hideCategory = true; // used in list.
|
$hideCategory = true; // used in list.
|
||||||
$page = intval(Input::get('page'));
|
$page = intval(Input::get('page')) === 0 ? 1 : intval(Input::get('page'));
|
||||||
$pageSize = Preferences::get('transactionPageSize', 50)->data;
|
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||||
$offset = ($page - 1) * $pageSize;
|
|
||||||
$set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); // category
|
// new collector:
|
||||||
$count = $set->count();
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$subSet = $set->splice($offset, $pageSize);
|
$collector->setPage($page)->setLimit($pageSize)->setAllAssetAccounts()->setRange($start, $end)->setCategory($category);
|
||||||
$journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page);
|
$journals = $collector->getPaginatedJournals();
|
||||||
$journals->setPath('categories/show/' . $category->id . '/' . $date);
|
$journals->setPath('categories/show/' . $category->id . '/' . $date);
|
||||||
|
|
||||||
return view('categories.show_with_date', compact('category', 'journals', 'hideCategory', 'subTitle', 'carbon'));
|
return view('categories.show-by-date', compact('category', 'journals', 'hideCategory', 'subTitle', 'carbon'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,11 +16,16 @@ namespace FireflyIII\Http\Controllers\Chart;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Exception;
|
use Exception;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
|
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 Log;
|
use Log;
|
||||||
@@ -37,7 +42,7 @@ use Steam;
|
|||||||
class AccountController extends Controller
|
class AccountController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var \FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
protected $generator;
|
protected $generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,8 +51,49 @@ class AccountController extends Controller
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
// create chart generator:
|
$this->generator = app(GeneratorInterface::class);
|
||||||
$this->generator = app(AccountChartGeneratorInterface::class);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function all(Account $account)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties();
|
||||||
|
$cache->addProperty('chart.account.all');
|
||||||
|
$cache->addProperty($account->id);
|
||||||
|
if ($cache->has()) {
|
||||||
|
Log::debug('Return chart.account.all from cache.');
|
||||||
|
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
Log::debug('Regenerate chart.account.all from scratch.');
|
||||||
|
|
||||||
|
/** @var AccountRepositoryInterface $repository */
|
||||||
|
$repository = app(AccountRepositoryInterface::class);
|
||||||
|
$start = $repository->oldestJournalDate($account);
|
||||||
|
$end = new Carbon;
|
||||||
|
$format = (string)trans('config.month_and_day');
|
||||||
|
$range = Steam::balanceInRange($account, $start, $end);
|
||||||
|
$current = clone $start;
|
||||||
|
$previous = array_values($range)[0];
|
||||||
|
$chartData = [];
|
||||||
|
|
||||||
|
while ($end >= $current) {
|
||||||
|
$theDate = $current->format('Y-m-d');
|
||||||
|
$balance = $range[$theDate] ?? $previous;
|
||||||
|
$label = $current->formatLocalized($format);
|
||||||
|
$chartData[$label] = $balance;
|
||||||
|
$previous = $balance;
|
||||||
|
$current->addDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->singleSet($account->name, $chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,41 +110,124 @@ class AccountController extends Controller
|
|||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('expenseAccounts');
|
$cache->addProperty('chart.account.expense-accounts');
|
||||||
$cache->addProperty('accounts');
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
$accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
|
||||||
|
|
||||||
$start->subDay();
|
$start->subDay();
|
||||||
|
|
||||||
|
$accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
$ids = $accounts->pluck('id')->toArray();
|
||||||
$startBalances = Steam::balancesById($ids, $start);
|
$startBalances = Steam::balancesById($ids, $start);
|
||||||
$endBalances = Steam::balancesById($ids, $end);
|
$endBalances = Steam::balancesById($ids, $end);
|
||||||
|
$chartData = [];
|
||||||
|
|
||||||
$accounts->each(
|
foreach ($accounts as $account) {
|
||||||
function (Account $account) use ($startBalances, $endBalances) {
|
|
||||||
$id = $account->id;
|
$id = $account->id;
|
||||||
$startBalance = $startBalances[$id] ?? '0';
|
$startBalance = $startBalances[$id] ?? '0';
|
||||||
$endBalance = $endBalances[$id] ?? '0';
|
$endBalance = $endBalances[$id] ?? '0';
|
||||||
$diff = bcsub($endBalance, $startBalance);
|
$diff = bcsub($endBalance, $startBalance);
|
||||||
$account->difference = round($diff, 2);
|
if (bccomp($diff, '0') !== 0) {
|
||||||
|
$chartData[$account->name] = round($diff, 2);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
$accounts = $accounts->sortByDesc(
|
|
||||||
function (Account $account) {
|
|
||||||
return $account->difference;
|
|
||||||
}
|
}
|
||||||
);
|
arsort($chartData);
|
||||||
|
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
||||||
$data = $this->generator->expenseAccounts($accounts, $start, $end);
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param JournalCollectorInterface $collector
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function expenseBudget(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($account->id);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('chart.account.expense-budget');
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
$collector->setAccounts(new Collection([$account]))
|
||||||
|
->setRange($start, $end)
|
||||||
|
->withBudgetInformation()
|
||||||
|
->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$chartData = [];
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$jrnlBudgetId = intval($transaction->transaction_journal_budget_id);
|
||||||
|
$transBudgetId = intval($transaction->transaction_budget_id);
|
||||||
|
$budgetId = max($jrnlBudgetId, $transBudgetId);
|
||||||
|
$result[$budgetId] = $result[$budgetId] ?? '0';
|
||||||
|
$result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = $this->getBudgetNames(array_keys($result));
|
||||||
|
foreach ($result as $budgetId => $amount) {
|
||||||
|
$chartData[$names[$budgetId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param JournalCollectorInterface $collector
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function expenseCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($account->id);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('chart.account.expense-category');
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the balances for all the user's frontpage accounts.
|
* Shows the balances for all the user's frontpage accounts.
|
||||||
*
|
*
|
||||||
@@ -110,36 +239,106 @@ class AccountController extends Controller
|
|||||||
{
|
{
|
||||||
$start = clone session('start', Carbon::now()->startOfMonth());
|
$start = clone session('start', Carbon::now()->startOfMonth());
|
||||||
$end = clone session('end', Carbon::now()->endOfMonth());
|
$end = clone session('end', Carbon::now()->endOfMonth());
|
||||||
|
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
|
||||||
|
Log::debug('Default set is ', $defaultSet);
|
||||||
|
$frontPage = Preferences::get('frontPageAccounts', $defaultSet);
|
||||||
|
Log::debug('Frontpage preference set is ', $frontPage->data);
|
||||||
|
if (count($frontPage->data) === 0) {
|
||||||
|
$frontPage->data = $defaultSet;
|
||||||
|
Log::debug('frontpage set is empty!');
|
||||||
|
$frontPage->save();
|
||||||
|
}
|
||||||
|
$accounts = $repository->getAccountsById($frontPage->data);
|
||||||
|
|
||||||
|
return Response::json($this->accountBalanceChart($accounts, $start, $end));
|
||||||
|
}
|
||||||
|
|
||||||
// chart properties for cache:
|
/**
|
||||||
|
* @param JournalCollectorInterface $collector
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function incomeCategory(JournalCollectorInterface $collector, Account $account, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($account->id);
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('frontpage');
|
$cache->addProperty('chart.account.income-category');
|
||||||
$cache->addProperty('accounts');
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
|
|
||||||
$frontPage = Preferences::get('frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray());
|
// grab all journals:
|
||||||
$accounts = $repository->getAccountsById($frontPage->data);
|
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]);
|
||||||
|
$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]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
$names = $this->getCategoryNames(array_keys($result));
|
||||||
$balances = [];
|
foreach ($result as $categoryId => $amount) {
|
||||||
|
$chartData[$names[$categoryId]] = $amount;
|
||||||
|
}
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param string $date
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function period(Account $account, string $date)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
$cache = new CacheProperties();
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('chart.account.period');
|
||||||
|
$cache->addProperty($account->id);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$format = (string)trans('config.month_and_day');
|
||||||
|
$range = Steam::balanceInRange($account, $start, $end);
|
||||||
$current = clone $start;
|
$current = clone $start;
|
||||||
$range = Steam::balanceInRange($account, $start, clone $end);
|
$previous = array_values($range)[0];
|
||||||
$previous = round(array_values($range)[0], 2);
|
$chartData = [];
|
||||||
while ($current <= $end) {
|
|
||||||
$format = $current->format('Y-m-d');
|
while ($end >= $current) {
|
||||||
$balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
|
$theDate = $current->format('Y-m-d');
|
||||||
|
$balance = $range[$theDate] ?? $previous;
|
||||||
|
$label = $current->formatLocalized($format);
|
||||||
|
$chartData[$label] = $balance;
|
||||||
$previous = $balance;
|
$previous = $balance;
|
||||||
$balances[] = $balance;
|
|
||||||
$current->addDay();
|
$current->addDay();
|
||||||
}
|
}
|
||||||
$account->balances = $balances;
|
|
||||||
}
|
$data = $this->generator->singleSet($account->name, $chartData);
|
||||||
$data = $this->generator->frontpage($accounts, $start, $end);
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -154,40 +353,9 @@ class AccountController extends Controller
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function report(Carbon $start, Carbon $end, Collection $accounts)
|
public function report(Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
// chart properties for cache:
|
return Response::json($this->accountBalanceChart($accounts, $start, $end));
|
||||||
$cache = new CacheProperties();
|
|
||||||
$cache->addProperty($start);
|
|
||||||
$cache->addProperty($end);
|
|
||||||
$cache->addProperty('all');
|
|
||||||
$cache->addProperty('accounts');
|
|
||||||
$cache->addProperty('default');
|
|
||||||
$cache->addProperty($accounts);
|
|
||||||
if ($cache->has()) {
|
|
||||||
return Response::json($cache->get());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($accounts as $account) {
|
|
||||||
$balances = [];
|
|
||||||
$current = clone $start;
|
|
||||||
$range = Steam::balanceInRange($account, $start, clone $end);
|
|
||||||
$previous = round(array_values($range)[0], 2);
|
|
||||||
while ($current <= $end) {
|
|
||||||
$format = $current->format('Y-m-d');
|
|
||||||
$balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
|
|
||||||
$previous = $balance;
|
|
||||||
$balances[] = $balance;
|
|
||||||
$current->addDay();
|
|
||||||
}
|
|
||||||
$account->balances = $balances;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make chart:
|
|
||||||
$data = $this->generator->frontpage($accounts, $start, $end);
|
|
||||||
$cache->store($data);
|
|
||||||
|
|
||||||
return Response::json($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,11 +369,11 @@ class AccountController extends Controller
|
|||||||
{
|
{
|
||||||
$start = clone session('start', Carbon::now()->startOfMonth());
|
$start = clone session('start', Carbon::now()->startOfMonth());
|
||||||
$end = clone session('end', Carbon::now()->endOfMonth());
|
$end = clone session('end', Carbon::now()->endOfMonth());
|
||||||
|
$chartData = [];
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('revenueAccounts');
|
$cache->addProperty('chart.account.revenue-accounts');
|
||||||
$cache->addProperty('accounts');
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
@@ -216,25 +384,19 @@ class AccountController extends Controller
|
|||||||
$startBalances = Steam::balancesById($ids, $start);
|
$startBalances = Steam::balancesById($ids, $start);
|
||||||
$endBalances = Steam::balancesById($ids, $end);
|
$endBalances = Steam::balancesById($ids, $end);
|
||||||
|
|
||||||
$accounts->each(
|
foreach ($accounts as $account) {
|
||||||
function (Account $account) use ($startBalances, $endBalances) {
|
|
||||||
$id = $account->id;
|
$id = $account->id;
|
||||||
$startBalance = $startBalances[$id] ?? '0';
|
$startBalance = $startBalances[$id] ?? '0';
|
||||||
$endBalance = $endBalances[$id] ?? '0';
|
$endBalance = $endBalances[$id] ?? '0';
|
||||||
$diff = bcsub($endBalance, $startBalance);
|
$diff = bcsub($endBalance, $startBalance);
|
||||||
$diff = bcmul($diff, '-1');
|
$diff = bcmul($diff, '-1');
|
||||||
$account->difference = round($diff, 2);
|
if (bccomp($diff, '0') !== 0) {
|
||||||
|
$chartData[$account->name] = round($diff, 2);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
$accounts = $accounts->sortByDesc(
|
|
||||||
function (Account $account) {
|
|
||||||
return $account->difference;
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
$data = $this->generator->revenueAccounts($accounts, $start, $end);
|
arsort($chartData);
|
||||||
|
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -256,8 +418,7 @@ class AccountController extends Controller
|
|||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties();
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('frontpage');
|
$cache->addProperty('chart.account.single');
|
||||||
$cache->addProperty('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());
|
||||||
@@ -267,77 +428,115 @@ class AccountController extends Controller
|
|||||||
$range = Steam::balanceInRange($account, $start, $end);
|
$range = Steam::balanceInRange($account, $start, $end);
|
||||||
$current = clone $start;
|
$current = clone $start;
|
||||||
$previous = array_values($range)[0];
|
$previous = array_values($range)[0];
|
||||||
$labels = [];
|
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
|
|
||||||
while ($end >= $current) {
|
while ($end >= $current) {
|
||||||
$theDate = $current->format('Y-m-d');
|
$theDate = $current->format('Y-m-d');
|
||||||
$balance = $range[$theDate] ?? $previous;
|
$balance = $range[$theDate] ?? $previous;
|
||||||
|
$label = $current->formatLocalized($format);
|
||||||
$labels[] = $current->formatLocalized($format);
|
$chartData[$label] = $balance;
|
||||||
$chartData[] = $balance;
|
|
||||||
$previous = $balance;
|
$previous = $balance;
|
||||||
$current->addDay();
|
$current->addDay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->singleSet($account->name, $chartData);
|
||||||
$data = $this->generator->single($account, $labels, $chartData);
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Account $account
|
* @param Collection $accounts
|
||||||
* @param string $date
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return array
|
||||||
* @throws FireflyException
|
|
||||||
*/
|
*/
|
||||||
public function specificPeriod(Account $account, string $date)
|
private function accountBalanceChart(Collection $accounts, Carbon $start, Carbon $end): array
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
$end = Navigation::endOfPeriod($start, $range);
|
|
||||||
// chart properties for cache:
|
// chart properties for cache:
|
||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties();
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('frontpage');
|
$cache->addProperty('chart.account.account-balance-chart');
|
||||||
$cache->addProperty('specificPeriod');
|
$cache->addProperty($accounts);
|
||||||
$cache->addProperty($account->id);
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
Log::debug('Return chart.account.account-balance-chart from cache.');
|
||||||
}
|
|
||||||
|
return $cache->get();
|
||||||
|
}
|
||||||
|
Log::debug('Regenerate chart.account.account-balance-chart from scratch.');
|
||||||
|
|
||||||
$format = (string)trans('config.month_and_day');
|
|
||||||
$range = Steam::balanceInRange($account, $start, $end);
|
|
||||||
$current = clone $start;
|
|
||||||
$previous = array_values($range)[0];
|
|
||||||
$labels = [];
|
|
||||||
$chartData = [];
|
$chartData = [];
|
||||||
|
foreach ($accounts as $account) {
|
||||||
while ($end >= $current) {
|
$currentSet = [
|
||||||
$theDate = $current->format('Y-m-d');
|
'label' => $account->name,
|
||||||
$balance = $range[$theDate] ?? $previous;
|
'entries' => [],
|
||||||
|
];
|
||||||
$labels[] = $current->formatLocalized($format);
|
$currentStart = clone $start;
|
||||||
$chartData[] = $balance;
|
$range = Steam::balanceInRange($account, $start, clone $end);
|
||||||
|
$previous = round(array_values($range)[0], 2);
|
||||||
|
while ($currentStart <= $end) {
|
||||||
|
$format = $currentStart->format('Y-m-d');
|
||||||
|
$label = $currentStart->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
|
$balance = isset($range[$format]) ? round($range[$format], 2) : $previous;
|
||||||
$previous = $balance;
|
$previous = $balance;
|
||||||
$current->addDay();
|
$currentStart->addDay();
|
||||||
|
$currentSet['entries'][$label] = $balance;
|
||||||
}
|
}
|
||||||
|
$chartData[] = $currentSet;
|
||||||
|
}
|
||||||
$data = $this->generator->single($account, $labels, $chartData);
|
$data = $this->generator->multiSet($chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $budgetIds
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getBudgetNames(array $budgetIds): array
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var BudgetRepositoryInterface $repository */
|
||||||
|
$repository = app(BudgetRepositoryInterface::class);
|
||||||
|
$budgets = $repository->getBudgets();
|
||||||
|
$grouped = $budgets->groupBy('id')->toArray();
|
||||||
|
$return = [];
|
||||||
|
foreach ($budgetIds as $budgetId) {
|
||||||
|
if (isset($grouped[$budgetId])) {
|
||||||
|
$return[$budgetId] = $grouped[$budgetId][0]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$return[0] = trans('firefly.no_budget');
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small helper function for some of the charts.
|
||||||
|
*
|
||||||
|
* @param array $categoryIds
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getCategoryNames(array $categoryIds): array
|
||||||
|
{
|
||||||
|
/** @var CategoryRepositoryInterface $repository */
|
||||||
|
$repository = app(CategoryRepositoryInterface::class);
|
||||||
|
$categories = $repository->getCategories();
|
||||||
|
$grouped = $categories->groupBy('id')->toArray();
|
||||||
|
$return = [];
|
||||||
|
foreach ($categoryIds as $categoryId) {
|
||||||
|
if (isset($grouped[$categoryId])) {
|
||||||
|
$return[$categoryId] = $grouped[$categoryId][0]['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$return[0] = trans('firefly.noCategory');
|
||||||
|
|
||||||
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Http\Controllers\Chart;
|
namespace FireflyIII\Http\Controllers\Chart;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\Bill;
|
use FireflyIII\Models\Bill;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Response;
|
use Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +32,7 @@ use Response;
|
|||||||
class BillController extends Controller
|
class BillController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var \FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
protected $generator;
|
protected $generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,8 +41,7 @@ class BillController extends Controller
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
// create chart generator:
|
$this->generator = app(GeneratorInterface::class);
|
||||||
$this->generator = app(BillChartGeneratorInterface::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,42 +55,79 @@ class BillController 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());
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('chart.bill.frontpage');
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
$paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
|
$paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
|
||||||
$unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
|
$unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
|
||||||
$data = $this->generator->frontpage($paid, $unpaid);
|
$chartData = [
|
||||||
|
strval(trans('firefly.unpaid')) => $unpaid,
|
||||||
|
strval(trans('firefly.paid')) => $paid,
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the overview for a bill. The min/max amount and matched journals.
|
* @param JournalCollectorInterface $collector
|
||||||
*
|
|
||||||
* @param BillRepositoryInterface $repository
|
|
||||||
* @param Bill $bill
|
* @param Bill $bill
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function single(BillRepositoryInterface $repository, Bill $bill)
|
public function single(JournalCollectorInterface $collector, Bill $bill)
|
||||||
{
|
{
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty('single');
|
$cache->addProperty('chart.bill.single');
|
||||||
$cache->addProperty('bill');
|
|
||||||
$cache->addProperty($bill->id);
|
$cache->addProperty($bill->id);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// get first transaction or today for start:
|
$results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getJournals();
|
||||||
$results = $repository->getJournals($bill, 1, 200);
|
|
||||||
|
|
||||||
// resort:
|
|
||||||
$results = $results->sortBy(
|
$results = $results->sortBy(
|
||||||
function (TransactionJournal $journal) {
|
function (Transaction $transaction) {
|
||||||
return $journal->date->format('U');
|
return $transaction->date->format('U');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$data = $this->generator->single($bill, $results);
|
$chartData = [
|
||||||
|
[
|
||||||
|
'type' => 'bar',
|
||||||
|
'label' => trans('firefly.min-amount'),
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'bar',
|
||||||
|
'label' => trans('firefly.max-amount'),
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'line',
|
||||||
|
'label' => trans('firefly.journal-amount'),
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var Transaction $entry */
|
||||||
|
foreach ($results as $entry) {
|
||||||
|
$date = $entry->date->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
|
// minimum amount of bill:
|
||||||
|
$chartData[0]['entries'][$date] = $bill->amount_min;
|
||||||
|
// maximum amount of bill:
|
||||||
|
$chartData[1]['entries'][$date] = $bill->amount_max;
|
||||||
|
// amount of journal:
|
||||||
|
$chartData[2]['entries'][$date] = bcmul($entry->transaction_amount, '-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->multiSet($chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
|
|||||||
@@ -14,15 +14,16 @@ declare(strict_types = 1);
|
|||||||
namespace FireflyIII\Http\Controllers\Chart;
|
namespace FireflyIII\Http\Controllers\Chart;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\LimitRepetition;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
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;
|
||||||
use Log;
|
|
||||||
use Navigation;
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
@@ -35,7 +36,7 @@ use Response;
|
|||||||
class BudgetController extends Controller
|
class BudgetController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var BudgetChartGeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
protected $generator;
|
protected $generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,8 +45,7 @@ class BudgetController extends Controller
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
// create chart generator:
|
$this->generator = app(GeneratorInterface::class);
|
||||||
$this->generator = app(BudgetChartGeneratorInterface::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,7 @@ class BudgetController extends Controller
|
|||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties();
|
||||||
$cache->addProperty($first);
|
$cache->addProperty($first);
|
||||||
$cache->addProperty($last);
|
$cache->addProperty($last);
|
||||||
$cache->addProperty('budget');
|
$cache->addProperty('chart.budget.budget');
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
@@ -76,7 +76,7 @@ class BudgetController extends Controller
|
|||||||
|
|
||||||
$budgetCollection = new Collection([$budget]);
|
$budgetCollection = new Collection([$budget]);
|
||||||
$last = Navigation::endOfX($last, $range, $final); // not to overshoot.
|
$last = Navigation::endOfX($last, $range, $final); // not to overshoot.
|
||||||
$entries = new Collection;
|
$entries = [];
|
||||||
while ($first < $last) {
|
while ($first < $last) {
|
||||||
|
|
||||||
// periodspecific dates:
|
// periodspecific dates:
|
||||||
@@ -85,12 +85,13 @@ class BudgetController extends Controller
|
|||||||
// sub another day because reasons.
|
// sub another day because reasons.
|
||||||
$currentEnd->subDay();
|
$currentEnd->subDay();
|
||||||
$spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
|
$spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
|
||||||
$entry = [$first, ($spent * -1)];
|
$format = Navigation::periodShow($first, $range);
|
||||||
$entries->push($entry);
|
$entries[$format] = bcmul($spent, '-1');
|
||||||
$first = Navigation::addPeriod($first, $range, 0);
|
$first = Navigation::addPeriod($first, $range, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->generator->budgetLimit($entries, 'month');
|
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $entries);
|
||||||
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -112,25 +113,25 @@ class BudgetController extends Controller
|
|||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties();
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('budget-limit');
|
$cache->addProperty('chart.budget.budget.limit');
|
||||||
$cache->addProperty($budget->id);
|
|
||||||
$cache->addProperty($repetition->id);
|
$cache->addProperty($repetition->id);
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
|
|
||||||
$entries = new Collection;
|
$entries = [];
|
||||||
$amount = $repetition->amount;
|
$amount = $repetition->amount;
|
||||||
$budgetCollection = new Collection([$budget]);
|
$budgetCollection = new Collection([$budget]);
|
||||||
while ($start <= $end) {
|
while ($start <= $end) {
|
||||||
$spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start);
|
$spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start);
|
||||||
$amount = bcadd($amount, $spent);
|
$amount = bcadd($amount, $spent);
|
||||||
$entries->push([clone $start, round($amount, 2)]);
|
$format = $start->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
|
$entries[$format] = $amount;
|
||||||
|
|
||||||
$start->addDay();
|
$start->addDay();
|
||||||
}
|
}
|
||||||
$data = $this->generator->budgetLimit($entries, 'month_and_day');
|
$data = $this->generator->singleSet(strval(trans('firefly.left')), $entries);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -145,21 +146,36 @@ class BudgetController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function frontpage(BudgetRepositoryInterface $repository)
|
public function frontpage(BudgetRepositoryInterface $repository)
|
||||||
{
|
{
|
||||||
Log::debug('Hello');
|
|
||||||
$start = session('start', Carbon::now()->startOfMonth());
|
$start = session('start', Carbon::now()->startOfMonth());
|
||||||
$end = session('end', Carbon::now()->endOfMonth());
|
$end = session('end', Carbon::now()->endOfMonth());
|
||||||
// chart properties for cache:
|
// chart properties for cache:
|
||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties();
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('budget');
|
$cache->addProperty('chart.budget.frontpage');
|
||||||
$cache->addProperty('all');
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
$budgets = $repository->getActiveBudgets();
|
$budgets = $repository->getActiveBudgets();
|
||||||
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
|
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
|
||||||
$allEntries = new Collection;
|
$chartData = [
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.spent_in_budget')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.left_to_spend')),
|
||||||
|
'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) {
|
||||||
@@ -167,95 +183,39 @@ class BudgetController extends Controller
|
|||||||
$reps = $this->filterRepetitions($repetitions, $budget, $start, $end);
|
$reps = $this->filterRepetitions($repetitions, $budget, $start, $end);
|
||||||
|
|
||||||
if ($reps->count() === 0) {
|
if ($reps->count() === 0) {
|
||||||
$collection = $this->spentInPeriodSingle($repository, $budget, $start, $end);
|
$row = $this->spentInPeriodSingle($repository, $budget, $start, $end);
|
||||||
$allEntries = $allEntries->merge($collection);
|
if (bccomp($row['spent'], '0') !== 0 || bccomp($row['repetition_left'], '0') !== 0) {
|
||||||
|
$chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1');
|
||||||
|
$chartData[1]['entries'][$row['name']] = $row['repetition_left'];
|
||||||
|
$chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1');
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$collection = $this->spentInPeriodMulti($repository, $budget, $reps);
|
$rows = $this->spentInPeriodMulti($repository, $budget, $reps);
|
||||||
$allEntries = $allEntries->merge($collection);
|
foreach ($rows as $row) {
|
||||||
|
if (bccomp($row['spent'], '0') !== 0 || bccomp($row['repetition_left'], '0') !== 0) {
|
||||||
|
$chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1');
|
||||||
|
$chartData[1]['entries'][$row['name']] = $row['repetition_left'];
|
||||||
|
$chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($rows, $row);
|
||||||
|
|
||||||
}
|
}
|
||||||
$entry = $this->spentInPeriodWithout($repository, $start, $end);
|
// for no budget:
|
||||||
$allEntries->push($entry);
|
$row = $this->spentInPeriodWithout($start, $end);
|
||||||
$data = $this->generator->frontpage($allEntries);
|
if (bccomp($row['repetition_overspent'], '0') !== 0) {
|
||||||
|
$chartData[0]['entries'][$row['name']] = bcmul($row['spent'], '-1');
|
||||||
|
$chartData[1]['entries'][$row['name']] = $row['repetition_left'];
|
||||||
|
$chartData[2]['entries'][$row['name']] = bcmul($row['repetition_overspent'], '-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->multiSet($chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param BudgetRepositoryInterface $repository
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Collection $budgets
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
|
||||||
public function multiYear(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets)
|
|
||||||
{
|
|
||||||
|
|
||||||
$cache = new CacheProperties();
|
|
||||||
$cache->addProperty($start);
|
|
||||||
$cache->addProperty($end);
|
|
||||||
$cache->addProperty($accounts);
|
|
||||||
$cache->addProperty($budgets);
|
|
||||||
$cache->addProperty('multiYearBudget');
|
|
||||||
|
|
||||||
if ($cache->has()) {
|
|
||||||
return Response::json($cache->get());
|
|
||||||
}
|
|
||||||
$budgetIds = $budgets->pluck('id')->toArray();
|
|
||||||
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
|
|
||||||
$budgeted = [];
|
|
||||||
$entries = new Collection;
|
|
||||||
// filter budgets once:
|
|
||||||
$repetitions = $repetitions->filter(
|
|
||||||
function (LimitRepetition $repetition) use ($budgetIds) {
|
|
||||||
if (in_array(strval($repetition->budget_id), $budgetIds)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
/** @var LimitRepetition $repetition */
|
|
||||||
foreach ($repetitions as $repetition) {
|
|
||||||
$year = $repetition->startdate->year;
|
|
||||||
if (isset($budgeted[$repetition->budget_id][$year])) {
|
|
||||||
$budgeted[$repetition->budget_id][$year] = bcadd($budgeted[$repetition->budget_id][$year], $repetition->amount);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$budgeted[$repetition->budget_id][$year] = $repetition->amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($budgets as $budget) {
|
|
||||||
$currentStart = clone $start;
|
|
||||||
$entry = ['name' => $budget->name, 'spent' => [], 'budgeted' => []];
|
|
||||||
while ($currentStart < $end) {
|
|
||||||
// fix the date:
|
|
||||||
$currentEnd = clone $currentStart;
|
|
||||||
$year = $currentStart->year;
|
|
||||||
$currentEnd->endOfYear();
|
|
||||||
|
|
||||||
$spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd);
|
|
||||||
|
|
||||||
// jump to next year.
|
|
||||||
$currentStart = clone $currentEnd;
|
|
||||||
$currentStart->addDay();
|
|
||||||
|
|
||||||
$entry['spent'][$year] = round($spent * -1, 2);
|
|
||||||
$entry['budgeted'][$year] = isset($budgeted[$budget->id][$year]) ? round($budgeted[$budget->id][$year], 2) : 0;
|
|
||||||
}
|
|
||||||
$entries->push($entry);
|
|
||||||
}
|
|
||||||
$data = $this->generator->multiYear($entries);
|
|
||||||
$cache->store($data);
|
|
||||||
|
|
||||||
return Response::json($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param BudgetRepositoryInterface $repository
|
* @param BudgetRepositoryInterface $repository
|
||||||
@@ -266,7 +226,7 @@ class BudgetController extends Controller
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function period(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end, Collection $accounts)
|
public function period(BudgetRepositoryInterface $repository, Budget $budget, Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
// chart properties for cache:
|
// chart properties for cache:
|
||||||
$cache = new CacheProperties();
|
$cache = new CacheProperties();
|
||||||
@@ -274,43 +234,99 @@ class BudgetController extends Controller
|
|||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
$cache->addProperty($budget->id);
|
$cache->addProperty($budget->id);
|
||||||
$cache->addProperty('budget');
|
$cache->addProperty('chart.budget.period');
|
||||||
$cache->addProperty('period');
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
// loop over period, add by users range:
|
|
||||||
$current = clone $start;
|
// get the expenses
|
||||||
$viewRange = Preferences::get('viewRange', '1M')->data;
|
$budgeted = [];
|
||||||
$set = new Collection;
|
$periods = Navigation::listOfPeriods($start, $end);
|
||||||
|
$entries = $repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end);
|
||||||
|
$key = Navigation::preferredCarbonFormat($start, $end);
|
||||||
|
$range = Navigation::preferredRangeFormat($start, $end);
|
||||||
|
|
||||||
|
// get the budget limits (if any)
|
||||||
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
|
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
|
||||||
|
$current = clone $start;
|
||||||
|
|
||||||
while ($current < $end) {
|
while ($current < $end) {
|
||||||
$currentStart = clone $current;
|
$currentStart = Navigation::startOfPeriod($current, $range);
|
||||||
$currentEnd = Navigation::endOfPeriod($currentStart, $viewRange);
|
$currentEnd = Navigation::endOfPeriod($current, $range);
|
||||||
$reps = $repetitions->filter(
|
$reps = $repetitions->filter(
|
||||||
function (LimitRepetition $repetition) use ($budget, $currentStart) {
|
function (LimitRepetition $repetition) use ($budget, $currentStart, $currentEnd) {
|
||||||
if ($repetition->budget_id === $budget->id && $repetition->startdate == $currentStart) {
|
if ($repetition->budget_id === $budget->id && $repetition->startdate >= $currentStart && $repetition->enddate <= $currentEnd) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$budgeted = $reps->sum('amount');
|
$index = $currentStart->format($key);
|
||||||
$spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd);
|
$budgeted[$index] = $reps->sum('amount');
|
||||||
$entry = [
|
|
||||||
'date' => clone $currentStart,
|
|
||||||
'budgeted' => $budgeted,
|
|
||||||
'spent' => $spent,
|
|
||||||
];
|
|
||||||
$set->push($entry);
|
|
||||||
$currentEnd->addDay();
|
$currentEnd->addDay();
|
||||||
$current = clone $currentEnd;
|
$current = clone $currentEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// join them into one set of data:
|
||||||
|
$chartData = [
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.spent')),
|
||||||
|
'type' => 'bar',
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.budgeted')),
|
||||||
|
'type' => 'bar',
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (array_keys($periods) as $period) {
|
||||||
|
$label = $periods[$period];
|
||||||
|
$spent = isset($entries[$budget->id]['entries'][$period]) ? $entries[$budget->id]['entries'][$period] : '0';
|
||||||
|
$limit = isset($budgeted[$period]) ? $budgeted[$period] : 0;
|
||||||
|
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 2);
|
||||||
|
$chartData[1]['entries'][$label] = $limit;
|
||||||
|
|
||||||
}
|
}
|
||||||
$data = $this->generator->period($set, $viewRange);
|
$data = $this->generator->multiSet($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BudgetRepositoryInterface $repository
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function periodNoBudget(BudgetRepositoryInterface $repository, Collection $accounts, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
// chart properties for cache:
|
||||||
|
$cache = new CacheProperties();
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty('chart.budget.no-budget');
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the expenses:
|
||||||
|
$periods = Navigation::listOfPeriods($start, $end);
|
||||||
|
$entries = $repository->getNoBudgetPeriodReport($accounts, $start, $end);
|
||||||
|
$chartData = [];
|
||||||
|
|
||||||
|
// join them:
|
||||||
|
foreach (array_keys($periods) as $period) {
|
||||||
|
$label = $periods[$period];
|
||||||
|
$spent = isset($entries['entries'][$period]) ? $entries['entries'][$period] : '0';
|
||||||
|
$chartData[$label] = bcmul($spent, '-1');
|
||||||
|
}
|
||||||
|
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -339,16 +355,24 @@ class BudgetController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns an array with the following values:
|
||||||
|
* 0 =>
|
||||||
|
* 'name' => name of budget + repetition
|
||||||
|
* 'repetition_left' => left in budget repetition (always zero)
|
||||||
|
* 'repetition_overspent' => spent more than budget repetition? (always zero)
|
||||||
|
* 'spent' => actually spent in period for budget
|
||||||
|
* 1 => (etc)
|
||||||
|
*
|
||||||
* @param BudgetRepositoryInterface $repository
|
* @param BudgetRepositoryInterface $repository
|
||||||
* @param Budget $budget
|
* @param Budget $budget
|
||||||
* @param Collection $repetitions
|
* @param Collection $repetitions
|
||||||
*
|
*
|
||||||
* @return Collection
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function spentInPeriodMulti(BudgetRepositoryInterface $repository, Budget $budget, Collection $repetitions): Collection
|
private function spentInPeriodMulti(BudgetRepositoryInterface $repository, Budget $budget, Collection $repetitions): array
|
||||||
{
|
{
|
||||||
|
$return = [];
|
||||||
$format = strval(trans('config.month_and_day'));
|
$format = strval(trans('config.month_and_day'));
|
||||||
$collection = new Collection;
|
|
||||||
$name = $budget->name;
|
$name = $budget->name;
|
||||||
/** @var LimitRepetition $repetition */
|
/** @var LimitRepetition $repetition */
|
||||||
foreach ($repetitions as $repetition) {
|
foreach ($repetitions as $repetition) {
|
||||||
@@ -362,52 +386,78 @@ class BudgetController extends Controller
|
|||||||
}
|
}
|
||||||
$amount = $repetition->amount;
|
$amount = $repetition->amount;
|
||||||
$left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses);
|
$left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses);
|
||||||
$spent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses;
|
$spent = $expenses;
|
||||||
$overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';
|
$overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0';
|
||||||
$array = [$name, $left, $spent, $overspent, $amount, $spent];
|
$return[] = [
|
||||||
$collection->push($array);
|
'name' => $name,
|
||||||
|
'repetition_left' => $left,
|
||||||
|
'repetition_overspent' => $overspent,
|
||||||
|
'spent' => $spent,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $collection;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns an array with the following values:
|
||||||
|
* 'name' => name of budget
|
||||||
|
* 'repetition_left' => left in budget repetition (always zero)
|
||||||
|
* 'repetition_overspent' => spent more than budget repetition? (always zero)
|
||||||
|
* 'spent' => actually spent in period for budget
|
||||||
|
*
|
||||||
|
*
|
||||||
* @param BudgetRepositoryInterface $repository
|
* @param BudgetRepositoryInterface $repository
|
||||||
* @param Budget $budget
|
* @param Budget $budget
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return Collection
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function spentInPeriodSingle(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end): Collection
|
private function spentInPeriodSingle(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end): array
|
||||||
{
|
{
|
||||||
$collection = new Collection;
|
|
||||||
$amount = '0';
|
|
||||||
$left = '0';
|
|
||||||
$spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
|
$spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
|
||||||
$overspent = '0';
|
$array = [
|
||||||
$array = [$budget->name, $left, $spent, $overspent, $amount, $spent];
|
'name' => $budget->name,
|
||||||
$collection->push($array);
|
'repetition_left' => '0',
|
||||||
|
'repetition_overspent' => '0',
|
||||||
|
'spent' => $spent,
|
||||||
|
];
|
||||||
|
|
||||||
return $collection;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param BudgetRepositoryInterface $repository
|
* Returns an array with the following values:
|
||||||
|
* 'name' => "no budget" in local language
|
||||||
|
* 'repetition_left' => left in budget repetition (always zero)
|
||||||
|
* 'repetition_overspent' => spent more than budget repetition? (always zero)
|
||||||
|
* 'spent' => actually spent in period for budget
|
||||||
|
*
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function spentInPeriodWithout(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end):array
|
private function spentInPeriodWithout(Carbon $start, Carbon $end): array
|
||||||
{
|
{
|
||||||
$list = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); // budget
|
// collector
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$types = [TransactionType::WITHDRAWAL];
|
||||||
|
$collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget();
|
||||||
|
$journals = $collector->getJournals();
|
||||||
$sum = '0';
|
$sum = '0';
|
||||||
/** @var TransactionJournal $entry */
|
/** @var Transaction $entry */
|
||||||
foreach ($list as $entry) {
|
foreach ($journals as $entry) {
|
||||||
$sum = bcadd(TransactionJournal::amount($entry), $sum);
|
$sum = bcadd($entry->transaction_amount, $sum);
|
||||||
}
|
}
|
||||||
|
$array = [
|
||||||
|
'name' => strval(trans('firefly.no_budget')),
|
||||||
|
'repetition_left' => '0',
|
||||||
|
'repetition_overspent' => $sum,
|
||||||
|
'spent' => '0',
|
||||||
|
];
|
||||||
|
|
||||||
return [trans('firefly.no_budget'), '0', '0', $sum, '0', '0'];
|
return $array;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
364
app/Http/Controllers/Chart/BudgetReportController.php
Normal file
364
app/Http/Controllers/Chart/BudgetReportController.php
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* BudgetReportController.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Controllers\Chart;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
|
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\Budget;
|
||||||
|
use FireflyIII\Models\LimitRepetition;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Support\CacheProperties;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Log;
|
||||||
|
use Navigation;
|
||||||
|
use Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separate controller because many helper functions are shared.
|
||||||
|
*
|
||||||
|
* Class BudgetReportController
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Http\Controllers\Chart
|
||||||
|
*/
|
||||||
|
class BudgetReportController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var AccountRepositoryInterface */
|
||||||
|
private $accountRepository;
|
||||||
|
/** @var BudgetRepositoryInterface */
|
||||||
|
private $budgetRepository;
|
||||||
|
/** @var GeneratorInterface */
|
||||||
|
private $generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
$this->generator = app(GeneratorInterface::class);
|
||||||
|
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
||||||
|
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $budgets
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function accountExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var bool $others */
|
||||||
|
$others = intval($others) === 1;
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.budget.report.account-expense');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($budgets);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty($others);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
$set = $this->getExpenses($accounts, $budgets, $start, $end);
|
||||||
|
$grouped = $this->groupByOpposingAccount($set);
|
||||||
|
$chartData = [];
|
||||||
|
$total = '0';
|
||||||
|
|
||||||
|
foreach ($grouped as $accountId => $amount) {
|
||||||
|
if (!isset($names[$accountId])) {
|
||||||
|
$account = $this->accountRepository->find(intval($accountId));
|
||||||
|
$names[$accountId] = $account->name;
|
||||||
|
}
|
||||||
|
$amount = bcmul($amount, '-1');
|
||||||
|
$total = bcadd($total, $amount);
|
||||||
|
$chartData[$names[$accountId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// also collect all transactions NOT in these budgets.
|
||||||
|
if ($others) {
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcmul($sum, '-1');
|
||||||
|
$sum = bcsub($sum, $total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $budgets
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function budgetExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var bool $others */
|
||||||
|
$others = intval($others) === 1;
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.budget.report.budget-expense');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($budgets);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty($others);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
$set = $this->getExpenses($accounts, $budgets, $start, $end);
|
||||||
|
$grouped = $this->groupByBudget($set);
|
||||||
|
$total = '0';
|
||||||
|
$chartData = [];
|
||||||
|
|
||||||
|
foreach ($grouped as $budgetId => $amount) {
|
||||||
|
if (!isset($names[$budgetId])) {
|
||||||
|
$budget = $this->budgetRepository->find(intval($budgetId));
|
||||||
|
$names[$budgetId] = $budget->name;
|
||||||
|
}
|
||||||
|
$amount = bcmul($amount, '-1');
|
||||||
|
$total = bcadd($total, $amount);
|
||||||
|
$chartData[$names[$budgetId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// also collect all transactions NOT in these budgets.
|
||||||
|
if ($others) {
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcmul($sum, '-1');
|
||||||
|
$sum = bcsub($sum, $total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $budgets
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function mainChart(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.budget.report.main');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($budgets);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
/** @var BudgetRepositoryInterface $repository */
|
||||||
|
$repository = app(BudgetRepositoryInterface::class);
|
||||||
|
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||||
|
$function = Navigation::preferredEndOfPeriod($start, $end);
|
||||||
|
$chartData = [];
|
||||||
|
$currentStart = clone $start;
|
||||||
|
$limits = $repository->getAllBudgetLimitRepetitions($start, $end); // also for ALL budgets.
|
||||||
|
|
||||||
|
// prep chart data:
|
||||||
|
foreach ($budgets as $budget) {
|
||||||
|
$chartData[$budget->id] = [
|
||||||
|
'label' => strval(trans('firefly.spent_in_specific_budget', ['budget' => $budget->name])),
|
||||||
|
'type' => 'bar',
|
||||||
|
'yAxisID' => 'y-axis-0',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
$chartData[$budget->id . '-sum'] = [
|
||||||
|
'label' => strval(trans('firefly.sum_of_expenses_in_budget', ['budget' => $budget->name])),
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
'yAxisID' => 'y-axis-1',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
$chartData[$budget->id . '-left'] = [
|
||||||
|
'label' => strval(trans('firefly.left_in_budget_limit', ['budget' => $budget->name])),
|
||||||
|
'type' => 'bar',
|
||||||
|
'fill' => false,
|
||||||
|
'yAxisID' => 'y-axis-0',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$sumOfExpenses = [];
|
||||||
|
$leftOfLimits = [];
|
||||||
|
while ($currentStart < $end) {
|
||||||
|
$currentEnd = clone $currentStart;
|
||||||
|
$currentEnd = $currentEnd->$function();
|
||||||
|
$expenses = $this->groupByBudget($this->getExpenses($accounts, $budgets, $currentStart, $currentEnd));
|
||||||
|
$label = $currentStart->formatLocalized($format);
|
||||||
|
|
||||||
|
/** @var Budget $budget */
|
||||||
|
foreach ($budgets as $budget) {
|
||||||
|
$currentExpenses = $expenses[$budget->id] ?? '0';
|
||||||
|
$sumOfExpenses[$budget->id] = $sumOfExpenses[$budget->id] ?? '0';
|
||||||
|
$sumOfExpenses[$budget->id] = bcadd($currentExpenses, $sumOfExpenses[$budget->id]);
|
||||||
|
$chartData[$budget->id]['entries'][$label] = round(bcmul($currentExpenses, '-1'), 2);
|
||||||
|
$chartData[$budget->id . '-sum']['entries'][$label] = round(bcmul($sumOfExpenses[$budget->id], '-1'), 2);
|
||||||
|
|
||||||
|
$limit = $this->filterLimits($limits, $budget, $currentStart);
|
||||||
|
if (!is_null($limit->id)) {
|
||||||
|
$leftOfLimits[$limit->id] = $leftOfLimits[$limit->id] ?? strval($limit->amount);
|
||||||
|
$leftOfLimits[$limit->id] = bcadd($leftOfLimits[$limit->id], $currentExpenses);
|
||||||
|
$chartData[$budget->id . '-left']['entries'][$label] = round($leftOfLimits[$limit->id], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
$currentStart = clone $currentEnd;
|
||||||
|
$currentStart->addDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->multiSet($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $limits
|
||||||
|
* @param $budget
|
||||||
|
* @param $currentStart
|
||||||
|
*
|
||||||
|
* @return LimitRepetition
|
||||||
|
*/
|
||||||
|
private function filterLimits(Collection $limits, Budget $budget, Carbon $date): LimitRepetition
|
||||||
|
{
|
||||||
|
Log::debug(sprintf('Start of filterLimits with %d limits.', $limits->count()));
|
||||||
|
$filtered = $limits->filter(
|
||||||
|
function (LimitRepetition $limit) use ($budget, $date) {
|
||||||
|
if ($limit->budget_id !== $budget->id) {
|
||||||
|
Log::debug(sprintf('LimitRepetition has budget #%d but expecting #%d', $limit->budget_id, $budget->id));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($date < $limit->startdate || $date > $limit->enddate) {
|
||||||
|
Log::debug(
|
||||||
|
sprintf(
|
||||||
|
'Date %s is not between %s and %s',
|
||||||
|
$date->format('Y-m-d'), $limit->startdate->format('Y-m-d'), $limit->enddate->format('Y-m-d')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $limit;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if ($filtered->count() === 1) {
|
||||||
|
return $filtered->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LimitRepetition;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $budgets
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getExpenses(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): Collection
|
||||||
|
{
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
|
->setBudgets($budgets)->withOpposingAccount()->disableFilter();
|
||||||
|
$accountIds = $accounts->pluck('id')->toArray();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function groupByBudget(Collection $set): array
|
||||||
|
{
|
||||||
|
// group by category ID:
|
||||||
|
$grouped = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($set as $transaction) {
|
||||||
|
$jrnlBudId = intval($transaction->transaction_journal_budget_id);
|
||||||
|
$transBudId = intval($transaction->transaction_budget_id);
|
||||||
|
$budgetId = max($jrnlBudId, $transBudId);
|
||||||
|
$grouped[$budgetId] = $grouped[$budgetId] ?? '0';
|
||||||
|
$grouped[$budgetId] = bcadd($transaction->transaction_amount, $grouped[$budgetId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Chart;
|
|||||||
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
@@ -26,7 +26,6 @@ use Illuminate\Support\Collection;
|
|||||||
use Navigation;
|
use Navigation;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CategoryController
|
* Class CategoryController
|
||||||
@@ -35,7 +34,7 @@ use stdClass;
|
|||||||
*/
|
*/
|
||||||
class CategoryController extends Controller
|
class CategoryController extends Controller
|
||||||
{
|
{
|
||||||
/** @var CategoryChartGeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
protected $generator;
|
protected $generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,10 +44,9 @@ class CategoryController extends Controller
|
|||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
// create chart generator:
|
// create chart generator:
|
||||||
$this->generator = app(CategoryChartGeneratorInterface::class);
|
$this->generator = app(GeneratorInterface::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*
|
*
|
||||||
@@ -60,34 +58,42 @@ class CategoryController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category)
|
public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category)
|
||||||
{
|
{
|
||||||
$start = $repository->firstUseDate($category);
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
|
||||||
$start = Navigation::startOfPeriod($start, $range);
|
|
||||||
$categoryCollection = new Collection([$category]);
|
|
||||||
$end = new Carbon;
|
|
||||||
$entries = new Collection;
|
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
$cache->addProperty('chart.category.all');
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($category->id);
|
||||||
$cache->addProperty($end);
|
|
||||||
$cache->addProperty('all');
|
|
||||||
$cache->addProperty('categories');
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$start = $repository->firstUseDate($category);
|
||||||
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
$start = Navigation::startOfPeriod($start, $range);
|
||||||
|
$end = new Carbon;
|
||||||
|
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||||
|
$chartData = [
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.spent')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.earned')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
while ($start <= $end) {
|
while ($start <= $end) {
|
||||||
$currentEnd = Navigation::endOfPeriod($start, $range);
|
$currentEnd = Navigation::endOfPeriod($start, $range);
|
||||||
$spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $currentEnd);
|
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
||||||
$earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $currentEnd);
|
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
||||||
$date = Navigation::periodShow($start, $range);
|
$label = Navigation::periodShow($start, $range);
|
||||||
$entries->push([clone $start, $date, $spent, $earned]);
|
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||||
|
$chartData[1]['entries'][$label] = $earned;
|
||||||
$start = Navigation::addPeriod($start, $range, 0);
|
$start = Navigation::addPeriod($start, $range, 0);
|
||||||
}
|
}
|
||||||
$entries = $entries->reverse();
|
|
||||||
$entries = $entries->slice(0, 48);
|
$data = $this->generator->multiSet($chartData);
|
||||||
$entries = $entries->reverse();
|
|
||||||
$data = $this->generator->all($entries);
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -123,106 +129,125 @@ class CategoryController extends Controller
|
|||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty('category');
|
$cache->addProperty('chart.category.frontpage');
|
||||||
$cache->addProperty('frontpage');
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
|
$chartData = [];
|
||||||
$categories = $repository->getCategories();
|
$categories = $repository->getCategories();
|
||||||
$accounts = $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
$accounts = $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
|
||||||
$set = new Collection;
|
|
||||||
/** @var Category $category */
|
/** @var Category $category */
|
||||||
foreach ($categories as $category) {
|
foreach ($categories as $category) {
|
||||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
|
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
|
||||||
if (bccomp($spent, '0') === -1) {
|
if (bccomp($spent, '0') === -1) {
|
||||||
$category->spent = $spent;
|
$chartData[$category->name] = bcmul($spent, '-1');
|
||||||
$set->push($category);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// this is a "fake" entry for the "no category" entry.
|
$chartData[strval(trans('firefly.no_category'))] = bcmul($repository->spentInPeriodWithoutCategory(new Collection, $start, $end), '-1');
|
||||||
$entry = new stdClass;
|
|
||||||
$entry->name = trans('firefly.no_category');
|
|
||||||
$entry->spent = $repository->spentInPeriodWithoutCategory(new Collection, $start, $end);
|
|
||||||
$set->push($entry);
|
|
||||||
|
|
||||||
$set = $set->sortBy('spent');
|
// sort
|
||||||
$data = $this->generator->frontpage($set);
|
arsort($chartData);
|
||||||
|
|
||||||
|
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param CRI $repository
|
||||||
|
* @param Category $category
|
||||||
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
* @param Collection $accounts
|
|
||||||
* @param Collection $categories
|
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse|mixed
|
||||||
*/
|
*/
|
||||||
public function multiYear(Carbon $start, Carbon $end, Collection $accounts, Collection $categories)
|
public function reportPeriod(CRI $repository, Category $category, Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
/** @var CRI $repository */
|
|
||||||
$repository = app(CRI::class);
|
|
||||||
|
|
||||||
// chart properties for cache:
|
|
||||||
$cache = new CacheProperties();
|
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty($accounts);
|
$cache->addProperty('chart.category.period');
|
||||||
$cache->addProperty($categories);
|
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||||
$cache->addProperty('multiYearCategory');
|
$cache->addProperty($category);
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return $cache->get();
|
||||||
|
}
|
||||||
|
$expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
|
||||||
|
$income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
|
||||||
|
$periods = Navigation::listOfPeriods($start, $end);
|
||||||
|
$chartData = [
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.spent')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.earned')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (array_keys($periods) as $period) {
|
||||||
|
$label = $periods[$period];
|
||||||
|
$spent = $expenses[$category->id]['entries'][$period] ?? '0';
|
||||||
|
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||||
|
$chartData[1]['entries'][$label] = $income[$category->id]['entries'][$period] ?? '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
$entries = new Collection;
|
$data = $this->generator->multiSet($chartData);
|
||||||
|
|
||||||
/** @var Category $category */
|
|
||||||
foreach ($categories as $category) {
|
|
||||||
$entry = ['name' => '', 'spent' => [], 'earned' => []];
|
|
||||||
|
|
||||||
$currentStart = clone $start;
|
|
||||||
while ($currentStart < $end) {
|
|
||||||
// fix the date:
|
|
||||||
$year = $currentStart->year;
|
|
||||||
$currentEnd = clone $currentStart;
|
|
||||||
$currentEnd->endOfYear();
|
|
||||||
|
|
||||||
// get data:
|
|
||||||
if (is_null($category->id)) {
|
|
||||||
$entry['name'] = trans('firefly.noCategory');
|
|
||||||
$entry['spent'][$year] = ($repository->spentInPeriodWithoutCategory($accounts, $currentStart, $currentEnd) * -1);
|
|
||||||
$entry['earned'][$year] = $repository->earnedInPeriodWithoutCategory($accounts, $currentStart, $currentEnd);
|
|
||||||
|
|
||||||
// jump to next year.
|
|
||||||
$currentStart = clone $currentEnd;
|
|
||||||
$currentStart->addDay();
|
|
||||||
continue;
|
|
||||||
|
|
||||||
}
|
|
||||||
// alternative is a normal category:
|
|
||||||
$entry['name'] = $category->name;
|
|
||||||
$entry['spent'][$year] = ($repository->spentInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd) * -1);
|
|
||||||
$entry['earned'][$year] = $repository->earnedInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd);
|
|
||||||
|
|
||||||
// jump to next year.
|
|
||||||
$currentStart = clone $currentEnd;
|
|
||||||
$currentStart->addDay();
|
|
||||||
}
|
|
||||||
$entries->push($entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate chart with data:
|
|
||||||
$data = $this->generator->multiYear($entries);
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CRI $repository
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse|mixed
|
||||||
|
*/
|
||||||
|
public function reportPeriodNoCategory(CRI $repository, Collection $accounts, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty('chart.category.period.no-cat');
|
||||||
|
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||||
|
if ($cache->has()) {
|
||||||
|
return $cache->get();
|
||||||
|
}
|
||||||
|
$expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
|
||||||
|
$income = $repository->periodIncomeNoCategory($accounts, $start, $end);
|
||||||
|
$periods = Navigation::listOfPeriods($start, $end);
|
||||||
|
$chartData = [
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.spent')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.earned')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (array_keys($periods) as $period) {
|
||||||
|
$label = $periods[$period];
|
||||||
|
$spent = $expenses['entries'][$period] ?? '0';
|
||||||
|
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||||
|
$chartData[1]['entries'][$label] = $income['entries'][$period] ?? '0';
|
||||||
|
|
||||||
|
}
|
||||||
|
$data = $this->generator->multiSet($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,6 +269,7 @@ class CategoryController extends Controller
|
|||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CRI $repository
|
* @param CRI $repository
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
@@ -254,33 +280,47 @@ class CategoryController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end)
|
private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
$categoryCollection = new Collection([$category]);
|
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty($category->id);
|
||||||
|
$cache->addProperty('chart.category.period-chart');
|
||||||
|
|
||||||
/** @var AccountRepositoryInterface $accountRepository */
|
/** @var AccountRepositoryInterface $accountRepository */
|
||||||
$accountRepository = app(AccountRepositoryInterface::class);
|
$accountRepository = app(AccountRepositoryInterface::class);
|
||||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||||
|
|
||||||
$cache->addProperty($start);
|
|
||||||
$cache->addProperty($end);
|
|
||||||
$cache->addProperty($accounts);
|
|
||||||
$cache->addProperty($category->id);
|
|
||||||
$cache->addProperty('specific-period');
|
|
||||||
|
|
||||||
|
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return $cache->get();
|
return $cache->get();
|
||||||
}
|
}
|
||||||
$entries = new Collection;
|
|
||||||
|
// chart data
|
||||||
|
$chartData = [
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.spent')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => strval(trans('firefly.earned')),
|
||||||
|
'entries' => [],
|
||||||
|
'type' => 'bar',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
while ($start <= $end) {
|
while ($start <= $end) {
|
||||||
$spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $start);
|
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
|
||||||
$earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $start);
|
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
|
||||||
$date = Navigation::periodShow($start, '1D');
|
$label = Navigation::periodShow($start, '1D');
|
||||||
$entries->push([clone $start, $date, $spent, $earned]);
|
|
||||||
|
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
|
||||||
|
$chartData[1]['entries'][$label] = $earned;
|
||||||
|
|
||||||
|
|
||||||
$start->addDay();
|
$start->addDay();
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->generator->period($entries);
|
$data = $this->generator->multiSet($chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
|||||||
476
app/Http/Controllers/Chart/CategoryReportController.php
Normal file
476
app/Http/Controllers/Chart/CategoryReportController.php
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CategoryReportController.php
|
||||||
|
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||||
|
*
|
||||||
|
* This software may be modified and distributed under the terms of the
|
||||||
|
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
*
|
||||||
|
* See the LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Controllers\Chart;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
|
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\Category;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
|
use FireflyIII\Support\CacheProperties;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Navigation;
|
||||||
|
use Response;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separate controller because many helper functions are shared.
|
||||||
|
*
|
||||||
|
* Class CategoryReportController
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Http\Controllers\Chart
|
||||||
|
*/
|
||||||
|
class CategoryReportController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var AccountRepositoryInterface */
|
||||||
|
private $accountRepository;
|
||||||
|
/** @var CategoryRepositoryInterface */
|
||||||
|
private $categoryRepository;
|
||||||
|
/** @var GeneratorInterface */
|
||||||
|
private $generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
$this->generator = app(GeneratorInterface::class);
|
||||||
|
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
||||||
|
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $categories
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function accountExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var bool $others */
|
||||||
|
$others = intval($others) === 1;
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.category.report.account-expense');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($categories);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty($others);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
$set = $this->getExpenses($accounts, $categories, $start, $end);
|
||||||
|
$grouped = $this->groupByOpposingAccount($set);
|
||||||
|
$chartData = [];
|
||||||
|
$total = '0';
|
||||||
|
|
||||||
|
foreach ($grouped as $accountId => $amount) {
|
||||||
|
if (!isset($names[$accountId])) {
|
||||||
|
$account = $this->accountRepository->find(intval($accountId));
|
||||||
|
$names[$accountId] = $account->name;
|
||||||
|
}
|
||||||
|
$amount = bcmul($amount, '-1');
|
||||||
|
$total = bcadd($total, $amount);
|
||||||
|
$chartData[$names[$accountId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// also collect all transactions NOT in these categories.
|
||||||
|
if ($others) {
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcmul($sum, '-1');
|
||||||
|
$sum = bcsub($sum, $total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $categories
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function accountIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var bool $others */
|
||||||
|
$others = intval($others) === 1;
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.category.report.account-income');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($categories);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($others);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
$set = $this->getIncome($accounts, $categories, $start, $end);
|
||||||
|
$grouped = $this->groupByOpposingAccount($set);
|
||||||
|
$chartData = [];
|
||||||
|
$total = '0';
|
||||||
|
|
||||||
|
foreach ($grouped as $accountId => $amount) {
|
||||||
|
if (!isset($names[$accountId])) {
|
||||||
|
$account = $this->accountRepository->find(intval($accountId));
|
||||||
|
$names[$accountId] = $account->name;
|
||||||
|
}
|
||||||
|
$total = bcadd($total, $amount);
|
||||||
|
$chartData[$names[$accountId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// also collect others?
|
||||||
|
if ($others) {
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcsub($sum, $total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $categories
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function categoryExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var bool $others */
|
||||||
|
$others = intval($others) === 1;
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.category.report.category-expense');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($categories);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty($others);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
$set = $this->getExpenses($accounts, $categories, $start, $end);
|
||||||
|
$grouped = $this->groupByCategory($set);
|
||||||
|
$total = '0';
|
||||||
|
$chartData = [];
|
||||||
|
|
||||||
|
foreach ($grouped as $categoryId => $amount) {
|
||||||
|
if (!isset($names[$categoryId])) {
|
||||||
|
$category = $this->categoryRepository->find(intval($categoryId));
|
||||||
|
$names[$categoryId] = $category->name;
|
||||||
|
}
|
||||||
|
$amount = bcmul($amount, '-1');
|
||||||
|
$total = bcadd($total, $amount);
|
||||||
|
$chartData[$names[$categoryId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// also collect all transactions NOT in these categories.
|
||||||
|
if ($others) {
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcmul($sum, '-1');
|
||||||
|
$sum = bcsub($sum, $total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $categories
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param string $others
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
||||||
|
{
|
||||||
|
/** @var bool $others */
|
||||||
|
$others = intval($others) === 1;
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.category.report.category-income');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($categories);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
$cache->addProperty($others);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
$set = $this->getIncome($accounts, $categories, $start, $end);
|
||||||
|
$grouped = $this->groupByCategory($set);
|
||||||
|
$total = '0';
|
||||||
|
$chartData = [];
|
||||||
|
|
||||||
|
foreach ($grouped as $categoryId => $amount) {
|
||||||
|
if (!isset($names[$categoryId])) {
|
||||||
|
$category = $this->categoryRepository->find(intval($categoryId));
|
||||||
|
$names[$categoryId] = $category->name;
|
||||||
|
}
|
||||||
|
$total = bcadd($total, $amount);
|
||||||
|
$chartData[$names[$categoryId]] = $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($others) {
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
|
||||||
|
$journals = $collector->getJournals();
|
||||||
|
$sum = strval($journals->sum('transaction_amount'));
|
||||||
|
$sum = bcsub($sum, $total);
|
||||||
|
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->generator->pieChart($chartData);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $categories
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function mainChart(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.category.report.main');
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($categories);
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return Response::json($cache->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||||
|
$function = Navigation::preferredEndOfPeriod($start, $end);
|
||||||
|
$chartData = [];
|
||||||
|
$currentStart = clone $start;
|
||||||
|
|
||||||
|
// prep chart data:
|
||||||
|
foreach ($categories as $category) {
|
||||||
|
$chartData[$category->id . '-in'] = [
|
||||||
|
'label' => $category->name . ' (' . strtolower(strval(trans('firefly.income'))) . ')',
|
||||||
|
'type' => 'bar',
|
||||||
|
'yAxisID' => 'y-axis-0',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
$chartData[$category->id . '-out'] = [
|
||||||
|
'label' => $category->name . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')',
|
||||||
|
'type' => 'bar',
|
||||||
|
'yAxisID' => 'y-axis-0',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
// total in, total out:
|
||||||
|
$chartData[$category->id . '-total-in'] = [
|
||||||
|
'label' => $category->name . ' (' . strtolower(strval(trans('firefly.sum_of_income'))) . ')',
|
||||||
|
'type' => 'line',
|
||||||
|
'fill' => false,
|
||||||
|
'yAxisID' => 'y-axis-1',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
$chartData[$category->id . '-total-out'] = [
|
||||||
|
'label' => $category->name . ' (' . 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->groupByCategory($this->getExpenses($accounts, $categories, $currentStart, $currentEnd));
|
||||||
|
$income = $this->groupByCategory($this->getIncome($accounts, $categories, $currentStart, $currentEnd));
|
||||||
|
$label = $currentStart->formatLocalized($format);
|
||||||
|
|
||||||
|
/** @var Category $category */
|
||||||
|
foreach ($categories as $category) {
|
||||||
|
$labelIn = $category->id . '-in';
|
||||||
|
$labelOut = $category->id . '-out';
|
||||||
|
$labelSumIn = $category->id . '-total-in';
|
||||||
|
$labelSumOut = $category->id . '-total-out';
|
||||||
|
$currentIncome = $income[$category->id] ?? '0';
|
||||||
|
$currentExpense = $expenses[$category->id] ?? '0';
|
||||||
|
|
||||||
|
|
||||||
|
// add to sum:
|
||||||
|
$sumOfIncome[$category->id] = $sumOfIncome[$category->id] ?? '0';
|
||||||
|
$sumOfExpense[$category->id] = $sumOfExpense[$category->id] ?? '0';
|
||||||
|
$sumOfIncome[$category->id] = bcadd($sumOfIncome[$category->id], $currentIncome);
|
||||||
|
$sumOfExpense[$category->id] = bcadd($sumOfExpense[$category->id], $currentExpense);
|
||||||
|
|
||||||
|
// add to chart:
|
||||||
|
$chartData[$labelIn]['entries'][$label] = $currentIncome;
|
||||||
|
$chartData[$labelOut]['entries'][$label] = $currentExpense;
|
||||||
|
$chartData[$labelSumIn]['entries'][$label] = $sumOfIncome[$category->id];
|
||||||
|
$chartData[$labelSumOut]['entries'][$label] = $sumOfExpense[$category->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;
|
||||||
|
}
|
||||||
|
$data = $this->generator->multiSet($newSet);
|
||||||
|
$cache->store($data);
|
||||||
|
|
||||||
|
return Response::json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $categories
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getExpenses(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection
|
||||||
|
{
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||||
|
->setCategories($categories)->withOpposingAccount()->disableFilter();
|
||||||
|
$accountIds = $accounts->pluck('id')->toArray();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Collection $categories
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function getIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection
|
||||||
|
{
|
||||||
|
$collector = new JournalCollector(auth()->user());
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
||||||
|
->setCategories($categories)->withOpposingAccount();
|
||||||
|
$accountIds = $accounts->pluck('id')->toArray();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$set = MonthReportGenerator::filterIncome($transactions, $accountIds);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function groupByCategory(Collection $set): array
|
||||||
|
{
|
||||||
|
// group by category ID:
|
||||||
|
$grouped = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($set as $transaction) {
|
||||||
|
$jrnlCatId = intval($transaction->transaction_journal_category_id);
|
||||||
|
$transCatId = intval($transaction->transaction_category_id);
|
||||||
|
$categoryId = max($jrnlCatId, $transCatId);
|
||||||
|
$grouped[$categoryId] = $grouped[$categoryId] ?? '0';
|
||||||
|
$grouped[$categoryId] = bcadd($transaction->transaction_amount, $grouped[$categoryId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function groupByOpposingAccount(Collection $set): array
|
||||||
|
{
|
||||||
|
$grouped = [];
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($set as $transaction) {
|
||||||
|
$accountId = $transaction->opposing_account_id;
|
||||||
|
$grouped[$accountId] = $grouped[$accountId] ?? '0';
|
||||||
|
$grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,12 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Http\Controllers\Chart;
|
namespace FireflyIII\Http\Controllers\Chart;
|
||||||
|
|
||||||
use FireflyIII\Generator\Chart\PiggyBank\PiggyBankChartGeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Models\PiggyBank;
|
use FireflyIII\Models\PiggyBank;
|
||||||
use FireflyIII\Models\PiggyBankEvent;
|
use FireflyIII\Models\PiggyBankEvent;
|
||||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Response;
|
use Response;
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ use Response;
|
|||||||
class PiggyBankController extends Controller
|
class PiggyBankController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var PiggyBankChartGeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
protected $generator;
|
protected $generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +40,7 @@ class PiggyBankController extends Controller
|
|||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
// create chart generator:
|
// create chart generator:
|
||||||
$this->generator = app(PiggyBankChartGeneratorInterface::class);
|
$this->generator = app(GeneratorInterface::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +55,7 @@ class PiggyBankController extends Controller
|
|||||||
{
|
{
|
||||||
// chart properties for cache:
|
// chart properties for cache:
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty('piggy-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());
|
||||||
@@ -64,18 +63,16 @@ class PiggyBankController extends Controller
|
|||||||
|
|
||||||
$set = $repository->getEvents($piggyBank);
|
$set = $repository->getEvents($piggyBank);
|
||||||
$set = $set->reverse();
|
$set = $set->reverse();
|
||||||
$collection = [];
|
$chartData = [];
|
||||||
|
$sum = '0';
|
||||||
/** @var PiggyBankEvent $entry */
|
/** @var PiggyBankEvent $entry */
|
||||||
foreach ($set as $entry) {
|
foreach ($set as $entry) {
|
||||||
$date = $entry->date->format('Y-m-d');
|
$label = $entry->date->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
$amount = $entry->amount;
|
$sum = bcadd($sum, $entry->amount);
|
||||||
if (isset($collection[$date])) {
|
$chartData[$label] = $sum;
|
||||||
$amount = bcadd($amount, $collection[$date]);
|
|
||||||
}
|
|
||||||
$collection[$date] = $amount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->generator->history(new Collection($collection));
|
$data = $this->generator->singleSet($piggyBank->name, $chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Chart;
|
|||||||
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface;
|
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
use FireflyIII\Repositories\Account\AccountTaskerInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
@@ -32,7 +32,7 @@ use Steam;
|
|||||||
class ReportController extends Controller
|
class ReportController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/** @var ReportChartGeneratorInterface */
|
/** @var GeneratorInterface */
|
||||||
protected $generator;
|
protected $generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,27 +42,25 @@ class ReportController extends Controller
|
|||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
// create chart generator:
|
// create chart generator:
|
||||||
$this->generator = app(ReportChartGeneratorInterface::class);
|
$this->generator = app(GeneratorInterface::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This chart, by default, is shown on the multi-year and year report pages,
|
* This chart, by default, is shown on the multi-year and year report pages,
|
||||||
* which means that giving it a 2 week "period" should be enough granularity.
|
* which means that giving it a 2 week "period" should be enough granularity.
|
||||||
*
|
*
|
||||||
* @param string $reportType
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
* @param Collection $accounts
|
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function netWorth(string $reportType, Carbon $start, Carbon $end, Collection $accounts)
|
public function netWorth(Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
// chart properties for cache:
|
// chart properties for cache:
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty('netWorth');
|
$cache->addProperty('chart.report.net-worth');
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($reportType);
|
|
||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
@@ -70,21 +68,16 @@ class ReportController extends Controller
|
|||||||
}
|
}
|
||||||
$ids = $accounts->pluck('id')->toArray();
|
$ids = $accounts->pluck('id')->toArray();
|
||||||
$current = clone $start;
|
$current = clone $start;
|
||||||
$entries = new Collection;
|
$chartData = [];
|
||||||
while ($current < $end) {
|
while ($current < $end) {
|
||||||
$balances = Steam::balancesById($ids, $current);
|
$balances = Steam::balancesById($ids, $current);
|
||||||
$sum = $this->arraySum($balances);
|
$sum = $this->arraySum($balances);
|
||||||
$entries->push(
|
$label = $current->formatLocalized(strval(trans('config.month_and_day')));
|
||||||
[
|
$chartData[$label] = $sum;
|
||||||
'date' => clone $current,
|
|
||||||
'net-worth' => $sum,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$current->addDays(7);
|
$current->addDays(7);
|
||||||
}
|
}
|
||||||
$data = $this->generator->netWorth($entries);
|
|
||||||
|
|
||||||
|
$data = $this->generator->singleSet(strval(trans('firefly.net_worth')), $chartData);
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
@@ -92,248 +85,141 @@ class ReportController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $accountTasker
|
* Shows income and expense, debet/credit: operations
|
||||||
* @param string $reportType
|
*
|
||||||
|
* @param Collection $accounts
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
* @param Collection $accounts
|
*
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
* @internal param AccountRepositoryInterface $repository
|
|
||||||
*/
|
*/
|
||||||
public function yearInOut(AccountTaskerInterface $accountTasker, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
|
public function operations(Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
// chart properties for cache:
|
// chart properties for cache:
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty('yearInOut');
|
$cache->addProperty('chart.report.operations');
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($reportType);
|
|
||||||
$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());
|
||||||
}
|
}
|
||||||
|
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
|
||||||
|
$source = $this->getChartData($accounts, $start, $end);
|
||||||
|
$chartData = [
|
||||||
|
[
|
||||||
|
'label' => trans('firefly.income'),
|
||||||
|
'type' => 'bar',
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => trans('firefly.expenses'),
|
||||||
|
'type' => 'bar',
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
// always per month.
|
foreach ($source['earned'] as $date => $amount) {
|
||||||
$currentStart = clone $start;
|
$carbon = new Carbon($date);
|
||||||
$spentArray = [];
|
$label = $carbon->formatLocalized($format);
|
||||||
$earnedArray = [];
|
$earned = $chartData[0]['entries'][$label] ?? '0';
|
||||||
while ($currentStart <= $end) {
|
$chartData[0]['entries'][$label] = bcadd($earned, $amount);
|
||||||
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
|
}
|
||||||
$date = $currentStart->format('Y-m');
|
foreach ($source['spent'] as $date => $amount) {
|
||||||
$spent = $accountTasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
$carbon = new Carbon($date);
|
||||||
$earned = $accountTasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
$label = $carbon->formatLocalized($format);
|
||||||
$spentArray[$date] = bcmul($spent, '-1');
|
$spent = $chartData[1]['entries'][$label] ?? '0';
|
||||||
$earnedArray[$date] = $earned;
|
$chartData[1]['entries'][$label] = bcadd($spent, $amount);
|
||||||
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($start->diffInMonths($end) > 12) {
|
|
||||||
// data = method X
|
$data = $this->generator->multiSet($chartData);
|
||||||
$data = $this->multiYearInOut($earnedArray, $spentArray, $start, $end);
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
}
|
|
||||||
|
|
||||||
// data = method Y
|
|
||||||
$data = $this->singleYearInOut($earnedArray, $spentArray, $start, $end);
|
|
||||||
$cache->store($data);
|
|
||||||
|
|
||||||
return Response::json($data);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AccountTaskerInterface $accountTasker
|
* Shows sum income and expense, debet/credit: operations
|
||||||
* @param string $reportType
|
*
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
* @param Carbon $end
|
* @param Carbon $end
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
* @internal param AccountRepositoryInterface $repository
|
|
||||||
*/
|
*/
|
||||||
public function yearInOutSummarized(AccountTaskerInterface $accountTasker, string $reportType, Carbon $start, Carbon $end, Collection $accounts)
|
public function sum(Collection $accounts, Carbon $start, Carbon $end)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// chart properties for cache:
|
// chart properties for cache:
|
||||||
$cache = new CacheProperties;
|
$cache = new CacheProperties;
|
||||||
$cache->addProperty('yearInOutSummarized');
|
$cache->addProperty('chart.report.sum');
|
||||||
$cache->addProperty($start);
|
$cache->addProperty($start);
|
||||||
$cache->addProperty($end);
|
$cache->addProperty($end);
|
||||||
$cache->addProperty($reportType);
|
|
||||||
$cache->addProperty($accounts);
|
$cache->addProperty($accounts);
|
||||||
if ($cache->has()) {
|
if ($cache->has()) {
|
||||||
return Response::json($cache->get());
|
return Response::json($cache->get());
|
||||||
}
|
}
|
||||||
|
$source = $this->getChartData($accounts, $start, $end);
|
||||||
// always per month.
|
$numbers = [
|
||||||
$currentStart = clone $start;
|
'sum_earned' => '0',
|
||||||
$spentArray = [];
|
'avg_earned' => '0',
|
||||||
$earnedArray = [];
|
'count_earned' => 0,
|
||||||
while ($currentStart <= $end) {
|
'sum_spent' => '0',
|
||||||
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
|
'avg_spent' => '0',
|
||||||
$date = $currentStart->format('Y-m');
|
'count_spent' => 0,
|
||||||
$spent = $accountTasker->amountOutInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
];
|
||||||
$earned = $accountTasker->amountInInPeriod($accounts, $accounts, $currentStart, $currentEnd);
|
foreach ($source['earned'] as $amount) {
|
||||||
$spentArray[$date] = bcmul($spent, '-1');
|
$numbers['sum_earned'] = bcadd($amount, $numbers['sum_earned']);
|
||||||
$earnedArray[$date] = $earned;
|
$numbers['count_earned']++;
|
||||||
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
|
}
|
||||||
|
if ($numbers['count_earned'] > 0) {
|
||||||
|
$numbers['avg_earned'] = $numbers['sum_earned'] / $numbers['count_earned'];
|
||||||
|
}
|
||||||
|
foreach ($source['spent'] as $amount) {
|
||||||
|
$numbers['sum_spent'] = bcadd($amount, $numbers['sum_spent']);
|
||||||
|
$numbers['count_spent']++;
|
||||||
|
}
|
||||||
|
if ($numbers['count_spent'] > 0) {
|
||||||
|
$numbers['avg_spent'] = $numbers['sum_spent'] / $numbers['count_spent'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($start->diffInMonths($end) > 12) {
|
$chartData = [
|
||||||
// per year
|
[
|
||||||
$data = $this->multiYearInOutSummarized($earnedArray, $spentArray, $start, $end);
|
'label' => strval(trans('firefly.income')),
|
||||||
$cache->store($data);
|
'type' => 'bar',
|
||||||
|
'entries' => [
|
||||||
|
strval(trans('firefly.sum_of_period')) => $numbers['sum_earned'],
|
||||||
|
strval(trans('firefly.average_in_period')) => $numbers['avg_earned'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => trans('firefly.expenses'),
|
||||||
|
'type' => 'bar',
|
||||||
|
'entries' => [
|
||||||
|
strval(trans('firefly.sum_of_period')) => $numbers['sum_spent'],
|
||||||
|
strval(trans('firefly.average_in_period')) => $numbers['avg_spent'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
return Response::json($data);
|
|
||||||
}
|
$data = $this->generator->multiSet($chartData);
|
||||||
// per month!
|
|
||||||
$data = $this->singleYearInOutSummarized($earnedArray, $spentArray, $start, $end);
|
|
||||||
$cache->store($data);
|
$cache->store($data);
|
||||||
|
|
||||||
return Response::json($data);
|
return Response::json($data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $earned
|
|
||||||
* @param array $spent
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function multiYearInOut(array $earned, array $spent, Carbon $start, Carbon $end)
|
|
||||||
{
|
|
||||||
$entries = new Collection;
|
|
||||||
while ($start < $end) {
|
|
||||||
|
|
||||||
$incomeSum = $this->pluckFromArray($start->year, $earned);
|
|
||||||
$expenseSum = $this->pluckFromArray($start->year, $spent);
|
|
||||||
|
|
||||||
$entries->push([clone $start, $incomeSum, $expenseSum]);
|
|
||||||
$start->addYear();
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $this->generator->multiYearInOut($entries);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $earned
|
|
||||||
* @param array $spent
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function multiYearInOutSummarized(array $earned, array $spent, Carbon $start, Carbon $end)
|
|
||||||
{
|
|
||||||
$income = '0';
|
|
||||||
$expense = '0';
|
|
||||||
$count = 0;
|
|
||||||
while ($start < $end) {
|
|
||||||
|
|
||||||
$currentIncome = $this->pluckFromArray($start->year, $earned);
|
|
||||||
$currentExpense = $this->pluckFromArray($start->year, $spent);
|
|
||||||
$income = bcadd($income, $currentIncome);
|
|
||||||
$expense = bcadd($expense, $currentExpense);
|
|
||||||
|
|
||||||
$count++;
|
|
||||||
$start->addYear();
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $this->generator->multiYearInOutSummarized($income, $expense, $count);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $year
|
|
||||||
* @param array $set
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function pluckFromArray($year, array $set)
|
|
||||||
{
|
|
||||||
$sum = '0';
|
|
||||||
foreach ($set as $date => $amount) {
|
|
||||||
if (substr($date, 0, 4) == $year) {
|
|
||||||
$sum = bcadd($sum, $amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sum;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $earned
|
|
||||||
* @param array $spent
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function singleYearInOut(array $earned, array $spent, Carbon $start, Carbon $end)
|
|
||||||
{
|
|
||||||
// per month? simply use each month.
|
|
||||||
|
|
||||||
$entries = new Collection;
|
|
||||||
while ($start < $end) {
|
|
||||||
// total income and total expenses:
|
|
||||||
$date = $start->format('Y-m');
|
|
||||||
$incomeSum = isset($earned[$date]) ? $earned[$date] : 0;
|
|
||||||
$expenseSum = isset($spent[$date]) ? $spent[$date] : 0;
|
|
||||||
|
|
||||||
$entries->push([clone $start, $incomeSum, $expenseSum]);
|
|
||||||
$start->addMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $this->generator->yearInOut($entries);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $earned
|
|
||||||
* @param array $spent
|
|
||||||
* @param Carbon $start
|
|
||||||
* @param Carbon $end
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function singleYearInOutSummarized(array $earned, array $spent, Carbon $start, Carbon $end)
|
|
||||||
{
|
|
||||||
$income = '0';
|
|
||||||
$expense = '0';
|
|
||||||
$count = 0;
|
|
||||||
while ($start < $end) {
|
|
||||||
$date = $start->format('Y-m');
|
|
||||||
$currentIncome = isset($earned[$date]) ? $earned[$date] : 0;
|
|
||||||
$currentExpense = isset($spent[$date]) ? $spent[$date] : 0;
|
|
||||||
$income = bcadd($income, $currentIncome);
|
|
||||||
$expense = bcadd($expense, $currentExpense);
|
|
||||||
|
|
||||||
$count++;
|
|
||||||
$start->addMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $this->generator->yearInOutSummarized($income, $expense, $count);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $array
|
* @param $array
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function arraySum($array) : string
|
private function arraySum($array): string
|
||||||
{
|
{
|
||||||
$sum = '0';
|
$sum = '0';
|
||||||
foreach ($array as $entry) {
|
foreach ($array as $entry) {
|
||||||
@@ -342,4 +228,47 @@ class ReportController extends Controller
|
|||||||
|
|
||||||
return $sum;
|
return $sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects the incomes and expenses for the given periods, grouped per month. Will cache its results
|
||||||
|
*
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getChartData(Collection $accounts, Carbon $start, Carbon $end): array
|
||||||
|
{
|
||||||
|
$cache = new CacheProperties;
|
||||||
|
$cache->addProperty('chart.report.get-chart-data');
|
||||||
|
$cache->addProperty($start);
|
||||||
|
$cache->addProperty($accounts);
|
||||||
|
$cache->addProperty($end);
|
||||||
|
if ($cache->has()) {
|
||||||
|
return $cache->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$tasker = app(AccountTaskerInterface::class);
|
||||||
|
$currentStart = clone $start;
|
||||||
|
$spentArray = [];
|
||||||
|
$earnedArray = [];
|
||||||
|
while ($currentStart <= $end) {
|
||||||
|
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
|
||||||
|
$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');
|
||||||
|
$earnedArray[$label] = $earned;
|
||||||
|
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
|
||||||
|
}
|
||||||
|
$result = [
|
||||||
|
'spent' => $spentArray,
|
||||||
|
'earned' => $earnedArray,
|
||||||
|
];
|
||||||
|
$cache->store($result);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
app/Http/Controllers/Controller.php
Executable file → Normal file
56
app/Http/Controllers/Controller.php
Executable file → Normal file
@@ -13,12 +13,17 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace FireflyIII\Http\Controllers;
|
namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use FireflyIII\Models\AccountType;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionJournal;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Session;
|
||||||
use View;
|
use View;
|
||||||
|
use FireflyConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Controller
|
* Class Controller
|
||||||
@@ -45,7 +50,10 @@ class Controller extends BaseController
|
|||||||
View::share('hideCategories', false);
|
View::share('hideCategories', false);
|
||||||
View::share('hideBills', false);
|
View::share('hideBills', false);
|
||||||
View::share('hideTags', false);
|
View::share('hideTags', false);
|
||||||
|
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
|
||||||
|
View::share('IS_DEMO_SITE', $isDemoSite);
|
||||||
|
View::share('DEMO_USERNAME', env('DEMO_USERNAME',''));
|
||||||
|
View::share('DEMO_PASSWORD', env('DEMO_PASSWORD',''));
|
||||||
|
|
||||||
// translations:
|
// translations:
|
||||||
|
|
||||||
@@ -61,31 +69,37 @@ class Controller extends BaseController
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay
|
* @param TransactionJournal $journal
|
||||||
* and sum up everything in the array in the given range.
|
|
||||||
*
|
*
|
||||||
* @param Carbon $start
|
* @return bool
|
||||||
* @param Carbon $end
|
|
||||||
* @param array $array
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
protected function getSumOfRange(Carbon $start, Carbon $end, array $array)
|
protected function isOpeningBalance(TransactionJournal $journal): bool
|
||||||
{
|
{
|
||||||
$sum = '0';
|
return TransactionJournal::transactionTypeStr($journal) === TransactionType::OPENING_BALANCE;
|
||||||
$currentStart = clone $start; // to not mess with the original one
|
|
||||||
$currentEnd = clone $end; // to not mess with the original one
|
|
||||||
|
|
||||||
while ($currentStart <= $currentEnd) {
|
|
||||||
$date = $currentStart->format('Y-m-d');
|
|
||||||
if (isset($array[$date])) {
|
|
||||||
$sum = bcadd($sum, $array[$date]);
|
|
||||||
}
|
|
||||||
$currentStart->addDay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sum;
|
/**
|
||||||
|
* @param TransactionJournal $journal
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
protected function redirectToAccount(TransactionJournal $journal)
|
||||||
|
{
|
||||||
|
$valid = [AccountType::DEFAULT, AccountType::ASSET];
|
||||||
|
$transactions = $journal->transactions;
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$account = $transaction->account;
|
||||||
|
if (in_array($account->accountType->type, $valid)) {
|
||||||
|
return redirect(route('accounts.show', [$account->id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Session::flash('error', strval(trans('firefly.cannot_redirect_to_account')));
|
||||||
|
|
||||||
|
return redirect(route('index'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ class CurrencyController extends Controller
|
|||||||
$subTitle = trans('firefly.create_currency');
|
$subTitle = trans('firefly.create_currency');
|
||||||
|
|
||||||
// put previous url in session if not redirect from store (not "create another").
|
// put previous url in session if not redirect from store (not "create another").
|
||||||
if (session('currency.create.fromStore') !== true) {
|
if (session('currencies.create.fromStore') !== true) {
|
||||||
Session::put('currency.create.url', URL::previous());
|
Session::put('currencies.create.url', URL::previous());
|
||||||
}
|
}
|
||||||
Session::forget('currency.create.fromStore');
|
Session::forget('currencies.create.fromStore');
|
||||||
Session::flash('gaEventCategory', 'currency');
|
Session::flash('gaEventCategory', 'currency');
|
||||||
Session::flash('gaEventAction', 'create');
|
Session::flash('gaEventAction', 'create');
|
||||||
|
|
||||||
return view('currency.create', compact('subTitleIcon', 'subTitle'));
|
return view('currencies.create', compact('subTitleIcon', 'subTitle'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,54 +85,54 @@ class CurrencyController extends Controller
|
|||||||
Cache::forget('FFCURRENCYSYMBOL');
|
Cache::forget('FFCURRENCYSYMBOL');
|
||||||
Cache::forget('FFCURRENCYCODE');
|
Cache::forget('FFCURRENCYCODE');
|
||||||
|
|
||||||
return redirect(route('currency.index'));
|
return redirect(route('currencies.index'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param CurrencyRepositoryInterface $repository
|
||||||
* @param TransactionCurrency $currency
|
* @param TransactionCurrency $currency
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|View
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||||
*/
|
*/
|
||||||
public function delete(TransactionCurrency $currency)
|
public function delete(CurrencyRepositoryInterface $repository, TransactionCurrency $currency)
|
||||||
{
|
{
|
||||||
if (!$this->canDeleteCurrency($currency)) {
|
if (!$repository->canDeleteCurrency($currency)) {
|
||||||
Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
||||||
|
|
||||||
return redirect(route('currency.index'));
|
return redirect(route('currencies.index'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// put previous url in session
|
// put previous url in session
|
||||||
Session::put('currency.delete.url', URL::previous());
|
Session::put('currencies.delete.url', URL::previous());
|
||||||
Session::flash('gaEventCategory', 'currency');
|
Session::flash('gaEventCategory', 'currency');
|
||||||
Session::flash('gaEventAction', 'delete');
|
Session::flash('gaEventAction', 'delete');
|
||||||
$subTitle = trans('form.delete_currency', ['name' => $currency->name]);
|
$subTitle = trans('form.delete_currency', ['name' => $currency->name]);
|
||||||
|
|
||||||
|
|
||||||
return view('currency.delete', compact('currency', 'subTitle'));
|
return view('currencies.delete', compact('currency', 'subTitle'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param CurrencyRepositoryInterface $repository
|
||||||
* @param TransactionCurrency $currency
|
* @param TransactionCurrency $currency
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
public function destroy(TransactionCurrency $currency)
|
public function destroy(CurrencyRepositoryInterface $repository, TransactionCurrency $currency)
|
||||||
{
|
{
|
||||||
if (!$this->canDeleteCurrency($currency)) {
|
if (!$repository->canDeleteCurrency($currency)) {
|
||||||
Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
Session::flash('error', trans('firefly.cannot_delete_currency', ['name' => $currency->name]));
|
||||||
|
|
||||||
return redirect(route('currency.index'));
|
return redirect(route('currencies.index'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$repository->destroy($currency);
|
||||||
Session::flash('success', trans('firefly.deleted_currency', ['name' => $currency->name]));
|
Session::flash('success', trans('firefly.deleted_currency', ['name' => $currency->name]));
|
||||||
if (auth()->user()->hasRole('owner')) {
|
|
||||||
$currency->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect(session('currency.delete.url'));
|
return redirect(session('currencies.delete.url'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,14 +147,14 @@ class CurrencyController extends Controller
|
|||||||
$currency->symbol = htmlentities($currency->symbol);
|
$currency->symbol = htmlentities($currency->symbol);
|
||||||
|
|
||||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||||
if (session('currency.edit.fromUpdate') !== true) {
|
if (session('currencies.edit.fromUpdate') !== true) {
|
||||||
Session::put('currency.edit.url', URL::previous());
|
Session::put('currencies.edit.url', URL::previous());
|
||||||
}
|
}
|
||||||
Session::forget('currency.edit.fromUpdate');
|
Session::forget('currencies.edit.fromUpdate');
|
||||||
Session::flash('gaEventCategory', 'currency');
|
Session::flash('gaEventCategory', 'currency');
|
||||||
Session::flash('gaEventAction', 'edit');
|
Session::flash('gaEventAction', 'edit');
|
||||||
|
|
||||||
return view('currency.edit', compact('currency', 'subTitle', 'subTitleIcon'));
|
return view('currencies.edit', compact('currency', 'subTitle', 'subTitleIcon'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,11 +170,11 @@ class CurrencyController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
if (!auth()->user()->hasRole('owner')) {
|
if (!auth()->user()->hasRole('owner')) {
|
||||||
Session::flash('warning', trans('firefly.ask_site_owner', ['owner' => env('SITE_OWNER')]));
|
Session::flash('warning', trans('firefly.ask_site_owner', ['site_owner' => env('SITE_OWNER')]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return view('currency.index', compact('currencies', 'defaultCurrency'));
|
return view('currencies.index', compact('currencies', 'defaultCurrency'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -189,7 +189,7 @@ class CurrencyController extends Controller
|
|||||||
if (!auth()->user()->hasRole('owner')) {
|
if (!auth()->user()->hasRole('owner')) {
|
||||||
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(session('currency.create.url'));
|
return redirect(session('currencies.create.url'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $request->getCurrencyData();
|
$data = $request->getCurrencyData();
|
||||||
@@ -197,13 +197,13 @@ class CurrencyController extends Controller
|
|||||||
Session::flash('success', trans('firefly.created_currency', ['name' => $currency->name]));
|
Session::flash('success', trans('firefly.created_currency', ['name' => $currency->name]));
|
||||||
|
|
||||||
if (intval(Input::get('create_another')) === 1) {
|
if (intval(Input::get('create_another')) === 1) {
|
||||||
Session::put('currency.create.fromStore', true);
|
Session::put('currencies.create.fromStore', true);
|
||||||
|
|
||||||
return redirect(route('currency.create'))->withInput();
|
return redirect(route('currencies.create'))->withInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to previous URL.
|
// redirect to previous URL.
|
||||||
return redirect(session('currency.create.url'));
|
return redirect(session('currencies.create.url'));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -226,49 +226,13 @@ class CurrencyController extends Controller
|
|||||||
|
|
||||||
|
|
||||||
if (intval(Input::get('return_to_edit')) === 1) {
|
if (intval(Input::get('return_to_edit')) === 1) {
|
||||||
Session::put('currency.edit.fromUpdate', true);
|
Session::put('currencies.edit.fromUpdate', true);
|
||||||
|
|
||||||
return redirect(route('currency.edit', [$currency->id]));
|
return redirect(route('currencies.edit', [$currency->id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to previous URL.
|
// redirect to previous URL.
|
||||||
return redirect(session('currency.edit.url'));
|
return redirect(session('currencies.edit.url'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TransactionCurrency $currency
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function canDeleteCurrency(TransactionCurrency $currency): bool
|
|
||||||
{
|
|
||||||
$repository = app(CurrencyRepositoryInterface::class);
|
|
||||||
|
|
||||||
// has transactions still
|
|
||||||
if ($repository->countJournals($currency) > 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// is the only currency left
|
|
||||||
if ($repository->get()->count() === 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// is the default currency for the user or the system
|
|
||||||
$defaultCode = Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'))->data;
|
|
||||||
if ($currency->code === $defaultCode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// is the default currency for the system
|
|
||||||
$defaultSystemCode = config('firefly.default_currency', 'EUR');
|
|
||||||
if ($currency->code === $defaultSystemCode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// can be deleted
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ namespace FireflyIII\Http\Controllers;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use ExpandedForm;
|
use ExpandedForm;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Export\Processor;
|
use FireflyIII\Export\ProcessorInterface;
|
||||||
use FireflyIII\Http\Requests\ExportFormRequest;
|
use FireflyIII\Http\Requests\ExportFormRequest;
|
||||||
use FireflyIII\Models\AccountType;
|
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 as EJRI;
|
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface as EJRI;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
use Response;
|
use Response;
|
||||||
@@ -56,25 +57,25 @@ class ExportController extends Controller
|
|||||||
/**
|
/**
|
||||||
* @param ExportJob $job
|
* @param ExportJob $job
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Routing\ResponseFactory
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function download(ExportJob $job)
|
public function download(ExportJobRepositoryInterface $repository, ExportJob $job)
|
||||||
{
|
{
|
||||||
$disk = Storage::disk('export');
|
|
||||||
$file = $job->key . '.zip';
|
$file = $job->key . '.zip';
|
||||||
$date = date('Y-m-d \a\t H-i-s');
|
$date = date('Y-m-d \a\t H-i-s');
|
||||||
$name = 'Export job on ' . $date . '.zip';
|
$name = 'Export job on ' . $date . '.zip';
|
||||||
$quoted = sprintf('"%s"', addcslashes($name, '"\\'));
|
$quoted = sprintf('"%s"', addcslashes($name, '"\\'));
|
||||||
|
|
||||||
if (!$disk->exists($file)) {
|
if (!$repository->exists($job)) {
|
||||||
throw new FireflyException('Against all expectations, zip file "' . $file . '" does not exist.');
|
throw new FireflyException('Against all expectations, zip file "' . $file . '" does not exist.');
|
||||||
}
|
}
|
||||||
|
$content = $repository->getContent($job);
|
||||||
|
|
||||||
|
|
||||||
$job->change('export_downloaded');
|
$job->change('export_downloaded');
|
||||||
|
|
||||||
return response($disk->get($file), 200)
|
return response($content, 200)
|
||||||
->header('Content-Description', 'File Transfer')
|
->header('Content-Description', 'File Transfer')
|
||||||
->header('Content-Type', 'application/octet-stream')
|
->header('Content-Type', 'application/octet-stream')
|
||||||
->header('Content-Disposition', 'attachment; filename=' . $quoted)
|
->header('Content-Disposition', 'attachment; filename=' . $quoted)
|
||||||
@@ -83,7 +84,7 @@ class ExportController extends Controller
|
|||||||
->header('Expires', '0')
|
->header('Expires', '0')
|
||||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||||
->header('Pragma', 'public')
|
->header('Pragma', 'public')
|
||||||
->header('Content-Length', $disk->size($file));
|
->header('Content-Length', strlen($content));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +134,6 @@ class ExportController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, EJRI $jobs)
|
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, EJRI $jobs)
|
||||||
{
|
{
|
||||||
set_time_limit(0);
|
|
||||||
$job = $jobs->findByKey($request->get('job'));
|
$job = $jobs->findByKey($request->get('job'));
|
||||||
$settings = [
|
$settings = [
|
||||||
'accounts' => $repository->getAccountsById($request->get('accounts')),
|
'accounts' => $repository->getAccountsById($request->get('accounts')),
|
||||||
@@ -146,7 +146,9 @@ class ExportController extends Controller
|
|||||||
];
|
];
|
||||||
|
|
||||||
$job->change('export_status_make_exporter');
|
$job->change('export_status_make_exporter');
|
||||||
$processor = new Processor($settings);
|
|
||||||
|
/** @var ProcessorInterface $processor */
|
||||||
|
$processor = app(ProcessorInterface::class, [$settings]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Collect journals:
|
* Collect journals:
|
||||||
|
|||||||
@@ -53,13 +53,30 @@ class HelpController extends Controller
|
|||||||
|
|
||||||
if ($help->inCache($route, $language)) {
|
if ($help->inCache($route, $language)) {
|
||||||
$content = $help->getFromCache($route, $language);
|
$content = $help->getFromCache($route, $language);
|
||||||
Log::debug('Help text was in cache.');
|
Log::debug(sprintf('Help text %s was in cache.', $language));
|
||||||
|
|
||||||
return Response::json($content);
|
return Response::json($content);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = $help->getFromGithub($language, $route);
|
$content = $help->getFromGithub($language, $route);
|
||||||
|
|
||||||
|
// get backup language content (try English):
|
||||||
|
if (strlen($content) === 0) {
|
||||||
|
$language = 'en_US';
|
||||||
|
if ($help->inCache($route, $language)) {
|
||||||
|
Log::debug(sprintf('Help text %s was in cache.', $language));
|
||||||
|
$content = $help->getFromCache($route, $language);
|
||||||
|
}
|
||||||
|
if (!$help->inCache($route, $language)) {
|
||||||
|
$content = $help->getFromGithub($language, $route);
|
||||||
|
$content = '<p><em>' . strval(trans('firefly.help_may_not_be_your_language')) . '</em></p>' . $content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($content) === 0) {
|
||||||
|
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
$help->putInCache($route, $language, $content);
|
$help->putInCache($route, $language, $content);
|
||||||
|
|
||||||
return Response::json($content);
|
return Response::json($content);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user